views:

314

answers:

9

I have to define a List and it has two types of possible values 1)String 2)some user defined Class

How can I make a List that is type safe in that it only accepts these two types?

I want to avoid the use of raw List.

+4  A: 
CPerkins
@CPerkins: I edited the code a bit, please check my logic.
akf
@akf - thanks. Looks good.
CPerkins
+8  A: 

Since String is an immediate subclass of Object and is final, you won't find a common supertype between String and your user-defined class other than Object. So List<Object> is what you have to use.

From a design perspective, mixing unrelated classes in a collection is a bad idea. Think about what you're trying to accomplish, and you'll probably come up with a better approach.

kdgregory
@kdgregory thanx for the reply..but is there any approach to restrict the types of Object to 1)string 2)user defined class
Sam Rudolph
No, what you're trying to accomplish is impossible and for good reason. Imagine you were able to do so, what kind of reference would you retrieve from your list?That aside, what kind of common interface does your class and String have? Why not try and create another class which stores either a String or your class under the hood and exposes only the behaviour which is common to both of them and relevant to your list and have your list only store instances of that class?
laginimaineb
While I agree with you that the design might need some rethinking, I disagree that heterogeneous containers are a "bad idea". See Effective Java Item 29. I think the OP is just a little unclear on what his use case is... but heterogeneous containers aren't always a bad idea.
Tom
I apologize for downvoting your answer... I actually didn't mean to, and didn't realize it until you posted on my answer... now I'm not allowed to change it. If you edit your answer, I can undo it. I merely just wanted to point out that I disagree with the blanket statement that "mixing unrelated classes in a collection is a bad idea". Most of the time, you're probably right, but sometimes it makes sense. That's all I wanted to get across.
Tom
+1  A: 

If you mean "type-safe" as in checking for type safety at compile time, then trying to use generics to solve this problem is going to be difficult.

The primary reason is because the String class is final, so it is not possible to make a subclass from String.

If subclassing String was possible, it would be possible to include both String and a user-defined subclass of String into a list declared as List<? extends String>:

// Not possible.
List<? extends String> list = new ArrayList<? extends String>;
list.add("A string");
list.add(new UserDefinedSubclassOfString());   // There can be no such class.

One option is to make a class which contains methods to interact with the two types, which actually contains Lists, parametrized to the two types that needs to be stored:

class MyList {

    List<String> strings;
    List<UserDefined> objects;

    public void add(String s) {
        strings.add(s);
    }

    public void add(UserDefined o) {
        objects.add(o);
    }

    // And, so on.
}

The problem with this approach, however, is that it won't be possible to use the List interface, as it expects the parameter to be of type E. Therefore, using Object or ? as the parameter (i.e. List<Object> or List<?>, respectively) is going to defeat the purpose, since there can't be a compile-time check for types, as all classes in Java has Object as its ancestor.

One thing to think about is how to handle getting objects from this hypothetical MyList. If there were a single get method, the return type would have to be a common ancestor of both String and the UserDefined classes. This is going to be Object.

The only way around this is going to be to provide two getters, one for each type. For example, getString and getUserDefined. At this point, is should be apparent that it is not going to be possible to use the List interface, which would necessitate the return of type E in the get method.

As kdgregory's answer says, having these problems in a solution seems to indicate that it is probably not the best approach to a problem that needs to be solved.

To get an idea about what generics is and what is possible and impossible with it, Lesson: Generics from The Java Tutorials would be a good start.

coobird
sorry to say but i am not able to understand your explanation can you please elaborate the solution?please
Sam Rudolph
Another nuance to this approach is that it wont maintain ordering of objects as added to the list.
akf
That's a good point, akf.
coobird
A: 

As kdgregory says, you probably want to rethink your approach. Maybe two lists, List<String> and List<Whatever>. Maybe a List<SomeContainer>, containing a String or a user object. Maybe the class containing this list wants to become class Blah<T> instead of class Blah, where T can then be String, UserClass, or whatever.

Jon Bright
A: 

Or maybe a single List that takes a custom type MyType that encapsulates your String and whatever else you need into a single abstraction.

If you're thinking in terms of primitives and data structures all the time you need to raise you sights. Object-orientation is about encapsulation, abstraction, and information hiding. It sounds to me like you aren't doing enough.

duffymo
A: 

My question is what is the "user defined class" supposed to be used for? Without that knowledge that is hard to give a good advise. Users are not creating classes, programmers are. Do you develop some generic framework?

What is a business purpose of your lists? Is String supposed to be used as kinda default type if no user-specific class provided? In that case, can you just setup BaseUserDefinedClass, and use lists like:

List<? extends BaseUserDefinedClass> = new ArrayList<DerivedFromBaseUserDefinedClass>();
Zorkus
A: 

It depends on what you're using the list for...

If you're going to just be using it to get Strings, you could just use:

List<String> stringList = new ArrayList<String>();
// Add String
stringList.add("myString");
// add YourObject
YourObject obj = new YourObject(...);
stringList.add(obj.toString());
// ...
for(String s : stringList) {
    System.out.println(s);
}

If you're going to be using it for getting YourObject references:

List<YourObject> objList = new ArrayList<YourObject>();
// Add String 
objList.add(new YourObjectAdapter("myString"));
// add YourObject
YourObject obj = new YourObject(...);
objList.add(obj)
// ...
for (YourObject y : objList) {
    System.out.println(y.toString());
    // Assuming YourObject defines the "doSomething() method"
    y.doSomething();
}
// ...
class YourObjectAdapter extends YourObject {
    private String wrappedString;
    public YourObjectAdapter(String s) {
        this.wrappedString = s;
    }
    @Override
    public void toString() {
        return wrappedString();
    }
    @Override
    public void doSomething() {
      // provide some default implementation...
    }
}
Nate
+1  A: 

This does what you are asking for.

public class MyList extends ArrayList<Object> {

    public MyList() {
        super();
    }

    public MyList(int initialSize) {
        super(initialSize);
    }

    @Override
    public void add(Object obj) {
        if ((obj instanceof String) || (obj instanceof SomeType)) {
            add(obj);
        } else {
            throw new IllegalArgumentException("not a String or SomeType");
        }
    }

    public void add(String s) {
        super.add(s);
    }

    public void add(SomeType s) {
        super.add(s);
    }
}
Stephen C
Be aware, using instanceof will allow any subclass of SomeType to be added. You really want to check obj.getClass() == SomeType.class. I didn't see you answer before I posted, but our thoughts are somewhat similar.
Tom
Thanx it Worked..!!
Sam Rudolph
+1  A: 

Please see the book Effective Java, Item 29: Consider typesafe heterogeneous containers. You might be able to adapt the ideas in that item to your specific use case.

I assume that you want to use your List like a List<Object>, but you only want to allow the insertion of Strings and some other specific type? Is this what you are trying to achieve?

If so, you can do something like this:

import java.util.*;

public class HetContainer {
  Set<Class<?>> allowableClasses;

  List<Object> items;

  public HetContainer(List<Class<?>> allowableClasses) {
    this.allowableClasses = new HashSet<Class<?>>(allowableClasses);
    items = new ArrayList<Object>();
  }

  public void add(Object o) {
    if (allowableClasses.contains(o.getClass())) {
      items.add(o);
    } else {
      throw new IllegalArgumentException("Object of type " + o.getClass() + " is not allowed.");
    }
  }

  public Object get(int i) {
    return items.get(i);
  }

  public static void main(String[] args) {
    List<Class<?>> classes = new ArrayList<Class<?>>();
    classes.add(String.class);
    classes.add(Integer.class);
    HetContainer h = new HetContainer(classes);
    h.add("hello");
    h.add(new Integer(5));
    try {
      h.add(new Double(5.0));
    } catch (IllegalArgumentException e) {
      System.out.println(e);
    }
  }
}

This is just simplified to show you the kinds of things you can do. Also, one caveat is that you can't put generic types into the container... why you ask? Because it is impossible to say List<Integer>.class or List<Double>.class. The reason is because of "erasure"... at runtime, both are just Lists. So you can put a raw List in HetContainer, but not a generic List. Again, read Effective Java so you can understand all the limitations of java and adapt things for your needs.

Tom
You'll note that the OP is looking for compile-time type safety. Should I downvote your answer because of that?
kdgregory
You're right that this doesn't provide compile time type safety... I didn't notice the comment on the question that stated that. The main point of my answer also is to "read effective java". I think it shows what the limitations of the language are. If the OP read that he would understand the pros/cons and how to implement heterogeneous containers. To get compile time type safety he'll need to wrap a list and only provide ways of adding the supported objects.
Tom