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.
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.
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.
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 List
s, 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.
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.
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.
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>();
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...
}
}
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);
}
}
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 String
s 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 List
s. 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.