【问题标题】:Call method of unknown object未知对象的调用方法
【发布时间】:2018-10-05 13:12:30
【问题描述】:

我有两个 ArrayList - ArrayList1 和 ArrayList2。它们中的每一个都充满了对象——分别是 Object1 和 Object2。 这两个对象都有方法“getText”。

对象1:

public String getText() { return "1";}

对象2:

public String getText() { return "2";}

在某些时候,我想使用相同的方法(只是使用不同的参数)遍历这些列表中的每一个。

loopThroughList(1)
loopThroughList(2)

如果我想调用一个方法,但我不知道它会是哪个对象,语法是什么?这是我到目前为止的代码:

for (Object o : lists.getList(listNumber)) {
    System.out.println(o.getText());
}

它说 无法解析方法 getText。我搜索了一下,找到了另一个解决方案:

for (Object o : lists.getList(listNumber)) {
    System.out.println(o.getClass().getMethod("getText"));
}

但这给了我 NoSuchMethodException 错误。即使“getText”方法是公开的。

编辑:为了获得正确的列表,我调用了不同对象(列表)的方法“getList”,它返回 ArrayList1 或 ArrayList2(取决于提供的参数)。

class Lists

public getList(list) {
    if (list == 1) {
        return ArrayList1;
    }
    else if (list == 2) {
        return ArrayList2;
    }
}

【问题讨论】:

  • 创建一个声明 getText() 方法的接口,让您的两个对象都实现它,然后将您的列表声明为 List<NameOfTheInterface> 。这样你就可以做for (NameOfTheInterface o : lists.getList(listNumber)) 并打电话给o.getText()
  • lists 是如何声明的?

标签: java class object nosuchmethoderror


【解决方案1】:

只是为这个答案添加更多内容,并让您对此进行更多思考(将尝试以简单、非正式的方式进行)。使用接口是进行此类操作的正确方法。但是,我想站在“坏主意”上:

for (Object o : lists.getList(listNumber)) {
    System.out.println(o.getClass().getMethod("getText"));
}

你在这里做的,是使用一种叫做Reflection的机制:

反射是 Java 编程语言中的一项功能。它允许一个 执行 Java 程序以检查或“自省”自身,以及 操纵程序的内部属性。例如,它是 Java 类可以获取其所有成员的名称,并且 显示它们。

您实际尝试的是使用该机制,通过您的 Class 的 Class 反射对象实例检索方法(听起来很奇怪,不是吗?)。

从这个角度来看,您需要认为,如果您想调用您的方法,那么在某种意义上,您现在拥有一个元类实例来操作您的对象。把它想象成一个比你的对象高出一步的对象(类似于梦中的梦,在Inception中)。从这个意义上说,您需要检索该方法,然后以不同的(类似元的)方式调用它:

java.lang.reflect.Method m = o.getClass().getMethod("getText");
m.invoke(o);

使用该逻辑,您可以遍历对象列表,检查方法是否存在,然后调用您的方法。

虽然这是一个糟糕的糟糕想法。

为什么?好吧,答案依赖于反射本身:反射与 runtime 直接相关 - 即当程序执行时,实际上在运行时执行所有事情,绕过编译世界。

换句话说,通过这样做,您绕过了Java的编译错误机制,允许在运行时发生此类错误。这可能会导致程序在执行时的行为不稳定——除了使用反射的性能开销,这里不再分析。

旁注:虽然使用反射将需要使用检查异常处理,但这样做仍然不是一个好主意 - 因为您实际上试图避免使用错误的解决方案。

另一方面,你可以通过类和接口遵循 Java 的继承机制——用你的方法定义一个接口(我们称之为Textable),确保你的类实现它,然后将它用作你的列表声明中的基本对象(@alexrolea 在他的回答中实现了这一点,@OldCurmudgeon 也实现了这一点)。

这样,您的程序仍然会在运行时做出方法调用决策(通过称为后期绑定的机制),但不会绕过Java的编译错误机制。想一想:如果您在不提供类的情况下定义 Textable 实现会发生什么 - 编译错误!如果你在Textables 列表中设置了一个非Textable 对象呢?你猜怎么了!再次编译错误。名单还在继续......

一般情况下,尽量避免使用反射。反射在某些情况下很有用,您需要以这种元方式处理程序,并且没有其他方法可以制作这样的东西。但事实并非如此。

更新:正如一些答案所建议的,您可以使用instanceof 检查您是否有一个包含您的方法的特定 Class 对象实例,然后分别调用。虽然这似乎是一个简单的解决方案,但在扩展方面却很糟糕:如果您有 1000 个 不同 类实现了要调用的 same 方法怎么办?

【讨论】:

  • 如果这些类来自我无法修改的库并且它们在该库中没有相互接口怎么办?实例检查 + 反射似乎是唯一的解决方案。
【解决方案2】:

getText方法定义一个接口

public interface YourInterface {

    String getText();     

}

在各个类上实现接口

public class Object1 implements YourInterface {

    @Override
    public String getText() { 
        return "1";
    }

}

public class Object2 implements YourInterface {

    @Override
    public String getText() { 
        return "2";
    }

}

修改您的getList 方法以返回List<YourInterface>

public static List<YourInterface> getList(int list){
    List<YourInterface> result = new ArrayList<>();
    if(list == 1){
        // your initial type
         List<Object1> firstList = new ArrayList<>();
         result.addAll(firstList);
    } else {
        // your initial type
        List<Object2> secondList = new ArrayList<>();
        result.addAll(secondList);
    }
    return result;
}

loopThroughList的声明

public static void loopThroughList(List<YourInterface> list){
    list.forEach(yourInterface -> System.out.println(yourInterface.getText()));
}

示例用法。

public static void main(String[] args) {
    loopThroughList(getList(1));
    loopThroughList(getList(2));
}

【讨论】:

  • 您能否详细说明loopThroughList的声明,考虑到原始问题的编辑?
  • loopThroughList 应该接受 ArrayList&lt;Object1&gt;ArrayList&lt;Object2&gt; 类型的对象,考虑到 Object1Object2 实现 YourInterface
  • 您能否在您的问题中提供getList 的实现?
  • 我猜你有ArrayList&lt;Object1&gt;ArrayList&lt;Object2&gt;,不是两种不同类型的ArrayList,对吧?
  • @JanHorčička 更新答案
【解决方案3】:

接口在这里工作得很好,但如果您正在处理遗留代码并且无法使用接口,还有其他几个选项。

首先是将列表项转换为各自的类型:

for (Object o : lists.getList(listNumber)) {
    if(o instanceof Object1) {
        Object1 o1 = (Object1)o;
        System.out.println(o1.getText());
    }
    else if(o instanceof Object2) {
        Object1 o2 = (Object2)o;
        System.out.println(o2.getText());
    }
    else {
        System.out.println("Unknown class");
    }
}

也可以使用反射查看对象是否有getText方法,然后调用它:

for (Object o : lists.getList(listNumber)) {
    try {
        System.out.println(o.getClass().getDeclaredMethod("getName").invoke(o));
    }
    catch(Exception e) {
        System.out.println("Object doesn't have getText method");
    }
}

【讨论】:

    【解决方案4】:

    这太可怕了。您能否详细说明您正在尝试做什么? Java 在设计上是强类型的,您正试图绕过它。为什么?如前所述,使用特定的类或接口代替 Object。如果这不可能,并且您必须使用对象列表,请使用 instanceof 和强制转换,例如:

    for (Object o : lists.getList(listNumber)) {
        if (o instanceof Object1) {
            Object1 o1 = (Object1) o;
            System.out.println(o1.getText());
        } else if (o instanceof Object2) {
            Object2 o2 = (Object2) o;
            System.out.println(o2.getText());
        }
    }
    

    【讨论】:

      【解决方案5】:

      这就是interfaces 的用武之地。

      interface HasText {
          public String getText();
      }
      
      class Object1 implements HasText {
          @Override
          public String getText() {
              return "1";
          }
      }
      
      class Object2 implements HasText {
          @Override
          public String getText() {
              return "2";
          }
      }
      
      private void test() {
          List<HasText> list = Arrays.asList(new Object1(), new Object2());
          for (HasText ht : list) {
              System.out.println(ht);
          }
      }
      

      如果您的某个对象不在您的控制范围内,您可以使用 Wrapper 类。

      class Object3DoesNotImplementHasText {
          public String getText() {
              return "3";
          }
      }
      
      class Object3Wrapper implements HasText{
          final Object3DoesNotImplementHasText it;
      
          public Object3Wrapper(Object3DoesNotImplementHasText it) {
              this.it = it;
          }
      
          @Override
          public String getText() {
              return it.getText();
          }
      }
      
      private void test() {
          List<HasText> list = Arrays.asList(new Object1(), new Object2(), new Object3Wrapper(new Object3DoesNotImplementHasText()));
          for (HasText ht : list) {
              System.out.println(ht);
          }
      }
      

      【讨论】:

      • 在哪个类文件中定义接口?
      • @JanHorčička - 真的在任何地方......这取决于你想在哪里使用它们。
      • 在一个单独的文件中,创建一个新的 HasText.java 文件,里面有公共接口。
      • 我使用了您的解决方案,但它不起作用。我创建了一个接口并让两个类实现它(就像在您的代码中一样)。错误消息仍然相同。也许原因是我从另一个对象获得了正确的列表(请参阅原始帖子中的编辑)。
      • @JanHorčička 我添加了包装器技巧来包装不实现您的接口的对象。
      【解决方案6】:

      您的对象必须实现一个通用接口。

      interface GetTextable {
          String getText();
      }
      
      class One implements GetTextable {
          private final String text;
      
          public One(final String text) {
              this.text = text;
          }
      
          public String getText() {
              return this.text;
          }
      }
      
      class Two implements GetTextable {
          private final String text;
      
          public Two(final String text) {
              this.text = text;
          }
      
          public String getText() {
              return this.text;
          }
      }
      
      @Test
      public void shouldIterate() throws Exception {
          List<GetTextable> toIterate = Arrays.asList(new One("oneText"), new Two("twoText"));
          for(GetTextable obj: toIterate) {
              System.out.println(obj.getText());
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2013-06-22
        • 1970-01-01
        • 2020-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-27
        相关资源
        最近更新 更多