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?

null
  • 5,207
  • 1
  • 19
  • 35
  • 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. – Holger Dec 09 '16 at 15:59

1 Answers1

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())));
Javier Martín
  • 2,537
  • 10
  • 15
  • 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. – null May 15 '16 at 15:42
  • 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 May 15 '16 at 15:58
  • @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. – Holger Dec 09 '16 at 16:00