我一直以为,如果我使用的是List<Thing> things = new ArrayList<>()
之类的列表,则此列表中的所有项目均为Thing
类型。昨天我被教导了另一种方式。
我创建了以下内容,想知道为什么会这样。
接口Thing
public interface Thing {
String getType();
String getName();
}
班级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
}
班级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
}
main
方法
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);
}
}
}
此方法的输出为:
[ObjectA{name='Thing 1'}, ObjectB{name='Thing 2', value1=123, value2='extra', value3=true}]
所以我假设我的ObjectA
中有ObjectB
个实体和List<Thing>
个实体。
问题:有人可以提供一个链接(或一些可用于搜索的关键字)来解释这种行为,还是可以向我解释呢?
其他问题:我已开始用List<Thing>
过滤此instanceof
,但我已阅读instanceof和cast是不好的做法(例如,没有好的模型设计)。是对所有ObjectA
类型的列表进行过滤以仅对这些对象执行某些操作的“好”方法吗?
答案 0 :(得分:4)
您应该避免在其他问题示例中使用instanceof检查。使用列表项时,具有可用的接口方法就足够了。如果只需要对ObjectA或ObjectB进行操作,则建议对ObjectA或ObjectB仅使用另一个List。例如,您可以定义不同的方法来完成Thing特定的工作和ObjectB特定的工作:
public void processThings(List<Thing> things) {
for(final Thing thing : things) {
// we work only with methods that provided by interface Thing
System.out.println(thing.getType());
System.out.println(thing.getName());
}
}
public void processObjectsB(List<ObjectB> objectsB) {
// here we do some specific things with only B objects,
// assuming class ObjectB has an additional method doSomeSpecificB()
for(final ObjectB objectB : objectsB) {
objectB.doSomeSpecificB();
}
}
答案 1 :(得分:2)
我有一个花园,里面有土豆,胡萝卜和西兰花。我有一个非常严格的规定-我不会在花园里种任何我不能吃的东西。所以这里没有毒藤!
所以这是Garden<Edible>
-我在花园里种的所有东西都必须可食用。
现在class Potato implements Edible
表示每个土豆都可以食用。但这也意味着我可以在花园里种土豆。同样,class Carrot implements Edible
-所有的胡萝卜都可以食用,并且允许我种胡萝卜。
这是一个漆黑的夜晚,我饿了。我到花园去,把手放在花园里的东西上。我看不到它是什么,但是我知道我花园里的所有东西都是可食用的。所以我把它从花园里拿出来,拿进去做饭。我抓到什么都没关系-我知道那是我可以吃的东西。
因为这是Garden<Edible>
。它可能包含或可能不包含Potato
个对象。它可能包含或可能不包含Broccoli
个对象。它不包含PoisonIvy
个对象。
现在,将所有内容翻译为您的示例。您有class ObjectA implements Thing
-这意味着每个ObjectA
都是Thing
。您有class ObjectB implements Thing
-这意味着每个ObjectB
都是Thing
。并且您有一个List<Thing>
-一个List
,其中可以包含ObjectA
个对象,ObjectB
个对象以及任何implements Thing
类的任何其他对象。您不能放入的对象是不实现Thing
的任何类的对象。
答案 2 :(得分:1)
有人可以提供一个链接(或一些可用于搜索的关键字)来解释这种行为,或者可以向我解释吗?
这种行为称为“多态”。基本上,由于ObjectA
和ObjectB
实现了Thing
,因此ObjectA
和ObjectB
的实例可以像Thing
对象一样使用。在您的代码中,您将它们添加到了可以包含Thing
个对象的列表中。
请注意,即使这些对象现在(编译时)的类型为Thing
,在运行时它们仍然知道它们是什么。当您在它们上调用toString
时,将分别调用toString
和ObjectA
中覆盖的ObjectB
方法。就像Thing
“变形”为ObjectA
或ObjectB
一样。
为所有类型的ObjectA过滤此列表以仅对这些对象执行某些操作的“好”方法是吗?
人们之所以说这是一种不好的做法,是因为如果您想根据对象是ObjectA
还是ObjectB
来做不同的事情,为什么要让他们实现Thing
呢?列出Thing
来存储它们?您可能只使用了List<Object>
。使用List<Thing>
的真正优势在于,您避免知道列表中存在哪些实际对象。您所知道的是列表中的内容实现了Thing
,您可以调用在Thing
中声明的方法。
因此,如果您需要过滤列表以将两种类型分开,则可以只创建两个列表以首先存储它们。一个用于ObjectA
,另一个用于ObjectB
。显然,这并不总是可能的,特别是如果列表来自其他地方(例如外部库)。在这种情况下,您当前的代码就可以了。
答案 3 :(得分:1)
things
是List<Thing>
。这意味着Java在编译时将确保您写入things
的任何对象都是 Thing
。当ObjectA
和ObjectB
都实现Thing
时,things
的任何成员的实际实现可以是ObjectA
或{{1} }。这是设计,并且该功能称为多态性:不同类的对象共享一个公共接口,并且可以通过该接口访问其实际类型,而与它们的实际类型无关。例如,您可以使用:
ObjectB
使用for(final Thing thing : things) {
System.stdout.println("Found a " + thing.getType() + " named " + thing.getName());
}
和强制转换不一定是坏习惯,并且可以具有正确的用例。但这通常提示类和接口的层次结构设计不当。理想情况下,如果必须处理instanceof
,则不要怀疑它的实际类:您拥有Thing
,并且使用Thing
方法就足够了。
从这个意义上讲,Thing
与反射的层次相同:它是一种低层工具,可以查看隐藏在引擎盖下的内容。而且,无论何时使用它,都应该问您是否多态性还不够。