Java:为什么我的列表包含不同的类型? (过滤列表)

时间:2018-07-11 07:21:37

标签: java list casting instanceof

我一直以为,如果我使用的是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类型的列表进行过滤以仅对这些对象执行某些操作的“好”方法吗?

4 个答案:

答案 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)

  

有人可以提供一个链接(或一些可用于搜索的关键字)来解释这种行为,或者可以向我解释吗?

这种行为称为“多态”。基本上,由于ObjectAObjectB实现了Thing,因此ObjectAObjectB的实例可以像Thing对象一样使用。在您的代码中,您将它们添加到了可以包含Thing个对象的列表中。

请注意,即使这些对象现在(编译时)的类型为Thing,在运行时它们仍然知道它们是什么。当您在它们上调用toString时,将分别调用toStringObjectA中覆盖的ObjectB方法。就像Thing“变形”为ObjectAObjectB一样。

  

为所有类型的ObjectA过滤此列表以仅对这些对象执行某些操作的“好”方法是吗?

人们之所以说这是一种不好的做法,是因为如果您想根据对象是ObjectA还是ObjectB来做不同的事情,为什么要让他们实现Thing呢?列出Thing来存储它们?您可能只使用了List<Object>。使用List<Thing>的真正优势在于,您避免知道列表中存在哪些实际对象。您所知道的是列表中的内容实现了Thing,您可以调用在Thing中声明的方法。

因此,如果您需要过滤列表以将两种类型分开,则可以只创建两个列表以首先存储它们。一个用于ObjectA,另一个用于ObjectB。显然,这并不总是可能的,特别是如果列表来自其他地方(例如外部库)。在这种情况下,您当前的代码就可以了。

答案 3 :(得分:1)

thingsList<Thing>。这意味着Java在编译时将确保您写入things 的任何对象都是 Thing。当ObjectAObjectB都实现Thing时,things的任何成员的实际实现可以是ObjectA或{{1} }。这是设计,并且该功能称为多态性:不同类的对象共享一个公共接口,并且可以通过该接口访问其实际类型,而与它们的实际类型无关。例如,您可以使用:

ObjectB

使用for(final Thing thing : things) { System.stdout.println("Found a " + thing.getType() + " named " + thing.getName()); } 和强制转换不一定是坏习惯,并且可以具有正确的用例。但这通常提示类和接口的层次结构设计不当。理想情况下,如果必须处理instanceof,则不要怀疑它的实际类:您拥有Thing,并且使用Thing方法就足够了。

从这个意义上讲,Thing与反射的层次相同:它是一种低层工具,可以查看隐藏在引擎盖下的内容。而且,无论何时使用它,都应该问您是否多态性还不够。