The whole time I thought, if I am using a List like List<Thing> things = new ArrayList<>()
all items in this list are of Type Thing
. Yesterday i was taught the other way.
I've created the following stuff and wonder why it is like it is.
An Interface Thing
public interface Thing {
String getType();
String getName();
}
A class ObjectA
public class ObjectA implements Thing {
private static final String TYPE = "Object A";
private String name;
public ObjectA(String name) {
this.name = name;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("ObjectA{");
sb.append("name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
@Override
public String getType() {
return TYPE;
}
@Override
public String getName() {
return name;
}
// equals and hashCode + getter and setter
}
A class ObjectB
public class ObjectB implements Thing {
private static final String TYPE = "Object B";
private String name;
private int value1;
private String value2;
private boolean value3;
public ObjectB(String name, int value1, String value2, boolean value3) {
this.name = name;
this.value1 = value1;
this.value2 = value2;
this.value3 = value3;
}
@Override
public String getType() {
return TYPE;
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("ObjectB{");
sb.append("name='").append(name).append('\'');
sb.append(", value1=").append(value1);
sb.append(", value2='").append(value2).append('\'');
sb.append(", value3=").append(value3);
sb.append('}');
return sb.toString();
}
// equals and hashCode + getter and setter
}
The main
method
public static void main(String[] args) {
final List<Thing> things = new ArrayList<>();
final ObjectA objA = new ObjectA("Thing 1");
final ObjectB objB = new ObjectB("Thing 2", 123, "extra", true);
things.add(objA);
things.add(objB);
// The List doesn't contain Thing entities, it contains ObjectA and ObjectB entities
System.out.println(things);
for(final Thing thing : things) {
if (thing instanceof ObjectA) {
System.out.println("Found Object A: " + thing);
final ObjectA object = (ObjectA) thing;
}
if (thing instanceof ObjectB) {
System.out.println("Found Object B: " + thing);
}
}
}
The output of this method is:
[ObjectA{name='Thing 1'}, ObjectB{name='Thing 2', value1=123, value2='extra', value3=true}]
So i assume i've ObjectA
entities and ObjectB
entities in my List<Thing>
.
Question: Can someone provide a link (or some keywords which can be used for searching), which explain this behavior, or can explain it to me?
additional Question: I've started to filter this List<Thing>
with instanceof
but i have read instanceof and casting are bad practice (e.g. no good model design). Is the are "good" way to filter this List for all Types of ObjectA
to perform only on these objects some operations?
Can someone provide a link (or some keywords which can be used for searching), which explain this behavior, or can explain it to me?
This behaviour is called "polymorphism". Basically, since ObjectA
and ObjectB
implements Thing
, instances of ObjectA
and ObjectB
can be used like a Thing
object. In your code, you added them to a list that can contain Thing
objects.
Note how even if those objects are now of (compile time) type Thing
, at runtime they still know what they are. When you call toString
on them, the respective overridden toString
methods in ObjectA
and ObjectB
will be called. It is as if the Thing
"morphs" into ObjectA
or ObjectB
.
Is the are "good" way to filter this List for all Types of ObjectA to perform only on these objects some operations?
The reason why people say this is bad practice is because if you want to do different things depending whether the object is ObjectA
or ObjectB
, why did you make them implement Thing
and make a list of Thing
to store them? You could have just used a List<Object>
. The real advantage of using List<Thing>
is that you avoid knowing what actual objects are in there when you are working with the list. All you know is that the things inside the list implement Thing
and you can call methods declared in Thing
.
So if you need to filter the list to separate the two types, you could have just created two lists to store them in the first place. One for ObjectA
and one for ObjectB
. Obviously, this is not always possible, especially if the list comes from somewhere else (like a external library). In that case, your current code is fine.