0

I have a collection of objects that all override toString(). I want to write them out to console or concatenate to a string, basically creating a toString() for the collection.

I can use a for loop to achieve this. String.join() seems to be a much nicer way though, because it takes away the explicit loop:

import java.util.ArrayList;

public class Foo
{
    private double member;

    public Foo()
    {
        member = Math.random();
    }

    @Override
    public String toString()
    {
        return "I'm foo " + member;
    }

    public static void main(String[] args)
    {
        ArrayList<Foo> foos = new ArrayList<>();
        foos.add(new Foo());
        foos.add(new Foo());
        foos.add(new Foo());
        foos.add(new Foo());
        foos.add(new Foo());
        foos.add(new Foo());

        // print collection with loop
        for (Foo foo : foos)
        {
            System.out.println(foo);
        }

        // print collection with String.join
        System.out.println(String.join(System.lineSeparator(), foos));  // fails
    }
}

For System.out.println() to work, the toString() method is called. No interface has to be implemented explicitly. This seems to be easy and is a common practice.

However, to get String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements) to work, merely having a toString() method is not enough. I need an Iterable<? extends CharSequence>, which means Foo should implement CharSequence.

Now I quickly implemented that interface by delegating to toString():

import java.util.ArrayList;
import java.util.stream.IntStream;

import java.lang.CharSequence;

public class Foo implements CharSequence
{
    private double member;

    public Foo()
    {
        member = Math.random();
    }

    @Override
    public char charAt(int index)
    {
        return toString().charAt(index);
    }

    @Override
    public IntStream chars()
    {
        return toString().chars();
    }

    @Override
    public IntStream codePoints()
    {
        return toString().codePoints();
    }

    @Override
    public int length()
    {
        return toString().length();
    }

    @Override
    public CharSequence subSequence(int start, int end)
    {
        return toString().subSequence(start, end);
    }

    @Override
    public String toString()
    {
        return "I'm foo " + member;
    }

    public static void main(String[] args)
    {
        ArrayList<Foo> foos = new ArrayList<>();
        foos.add(new Foo());
        foos.add(new Foo());
        foos.add(new Foo());
        foos.add(new Foo());
        foos.add(new Foo());
        foos.add(new Foo());

        System.out.println(String.join(System.lineSeparator(), foos));  
    }
}

But this seems to be quite a bit of code which isn't doing much in this situation except guaranteeing the possibility to convert the object into a String which every Object has anyway in the form of toString().

The boilerplate code introduced in the solution is bigger than the boilerplate code it removed.

How can I make a class play nice with String.join()?

Should I go that route and implement the interface or should I run some conversion on the fly?

1
  • You don’t need to implement default methods, so chars() and codePoints() are obsolete. But anyway, implementing CharSequence for this purpose still isn’t useful, so stay with the accepted answer. Commented Dec 9, 2016 at 15:59

1 Answer 1

4

Depending on the usage, that route may be inefficient, because each call to a CharSequence method invokes toString anew. For example, if a method tried to iterate over each character, you'd be looking at abysmal performance. Since you are using Java 8, why not use the Streams API?

System.out.println(foos.stream()
        .map(Object::toString)
        .collect(Collectors.joining(System.lineSeparator())));
Sign up to request clarification or add additional context in comments.

3 Comments

Again, that was a quick implementation, that could be improved in terms of performance. The question is about the boilerplate code necessary to implement the interface.
Well, yes, you do need to implement all members of an interface. However, if you need to do this for several classes, in Java 8 you could use a purpose-designed interface to act like a C++ mixin with default methods.
@Javier Martín: well, you only need to implement the abstract members, so the question’s example does more work than necessary. Still, your statement about the danger of abysmal performance holds.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.