views:

205

answers:

5

In Java is there anyway to have one constructor that will accept an array or a collection? I have been fiddling with this for a while but I don't think it is possible.

I would like to be able to initialize MyClass, like this:

MyClass c = new MyClass({"rat", "Dog", "Cat"});

And like this:

LinkedList <String> l = new <String> LinkedList();
l.add("dog");
l.add("cat");
l.add("rat");
MyClass c = new MyClass(l);

This is what MyClass looks like. What can I make XXX be so that this will work? I know that I could overload the constructor, but if I can minimize code that would be awesome right?

public class MyClass{

   private LinkedHashSet <String> myList;

   public MyClass(XXX <String> input){
       myList = new LinkedHashSet <String> ();
       for(String s : input){
           myList.put(s);
       }

   }

}
+13  A: 

You can declare two constructors and call second from first:

class MyClass {
    public MyClass(String... x) {
        // for arrays
        // this constructor delegate call to the second one
        this(Arrays.asList(x));
    }
    public MyClass(List<String> x) {
        // for lists
        // all logic here
    }
}

Calls would look like

new MyClass(new ArrayList<String>());
new MyClass("dog", "cat", "rat");
new MyClass(new String[] {"rat", "Dog", "Cat"});

Since there's only one line of code in the first constructor, it's pretty minimalistic.

Nikita Rybak
Nice one. I would maybe do it the other way round, since getting a `String[]` out of a `List<String>` is less costly than constructing a `List<String>` based on `String[]`.
BalusC
BalusC - you're mistaken. Arrays.asList(..) is returns a view of the same array as an immutable list. Almost no cost. On the other hand, going to String[] necessitates copying the all the references to a new allocated array
@usersmarvin: Thanks for the heads up! By the way, to notify users about comments in questions/answers which are not their own, put a `@` in front of the nickname, e.g. `@BalusC`. A minimum of first 3 chars in name is required to find a match.
BalusC
+1  A: 

Hi, try these

public class MyClass1 {

    public MyClass1(final String... animals) {
        for (final String animal : animals) {
            System.out.println("eat " + animal);
        }
    }

    public static void main(final String[] args) {
        new MyClass1();
        new MyClass1("dog", "cat", "rat");
        new MyClass1(new String[] { "dog", "cat", "rat" });
    }
}

or

public class MyClass2 {

    public MyClass2(final Iterable<String> animals) {
        for (final String animal : animals) {
            System.out.println("eat " + animal);
        }
    }

    public static void main(final String[] args) {
        new MyClass2(Arrays.asList("cat", "rat", "dog", "horse"));
        final LinkedList<String> animals = new LinkedList<String>();
        animals.add("dog");
        animals.add("house");
        animals.addAll(Arrays.asList("cat", "rat"));
        new MyClass2(animals);
    }
}
A: 

If you really want a single constructor you could use Arrays.asList and then have the constructor take a Collection which covers List and Set.

I personally would have two constructors.

TofuBeer
A: 

It appears that arrays are not iterable so there is no way to do this. It is impossible to have one constructor that accepts arrays AND other iterables. This is especially annoying since we can do this:

Foo[] foos = ...
for (Foo foo : foos)

See this post for more details: http://stackoverflow.com/questions/1160081/why-is-an-array-not-assignable-to-iterable

sixtyfootersdude
A: 

In theory, you could declare the constructor something like this:

MyClass(Object args) {
    if (args instanceof List) {
        ...
    } else if (args instanceof Set) {
        ...
    } else if (args.getClass().isArray()) {
        ...
    } else {
        thrown new IllegalArgumentException("arg's type is wrong");
    }
}

but IMO, that would be a bad API design as it punts all type checking to runtime. (There is no other way to do this with a single constructor. The only common supertype of array types and the Collection interface is Object.)

It is far better to use constructor overloading as described in the other answers.

Incidentally, the following (from your example) is a Java syntax error, irrespective of the constructor's declared argument types:

MyClass c = new MyClass({"rat", "Dog", "Cat"});

That form of array initializer can only be used in a variable declaration; e.g.

String[] foo = {"rat", "Dog", "Cat"};

or as part of an array creation expression; e.g.

String[] foo = new String[]{"rat", "Dog", "Cat"};
String[][] bar = new String[][]{{"rat", "Dog", "Cat"}, /* ... */};
Stephen C