避免在Java中使用instanceof

发布于 2024-09-01 10:24:41 字数 1994 浏览 10 评论 0原文

拥有一系列“instanceof”操作被认为是“代码味道”。标准答案是“使用多态性”。在这种情况下我该怎么做?

一个基类有多个子类;他们都不在我的控制之下。类似的情况是 Java 类 Integer、Double、BigDecimal 等。

if (obj instanceof Integer) {NumberStuff.handle((Integer)obj);}
else if (obj instanceof BigDecimal) {BigDecimalStuff.handle((BigDecimal)obj);}
else if (obj instanceof Double) {DoubleStuff.handle((Double)obj);}

我确实可以控制 NumberStuff 等。

我不想使用很多行代码,而几行代码就可以了。 (有时我会创建一个 HashMap,将 Integer.class 映射到 IntegerStuff 的实例,将 BigDecimal.class 映射到 BigDecimalStuff 的实例等。但今天我想要更简单的东西。)

我想要像这样简单的东西:

public static handle(Integer num) { ... }
public static handle(BigDecimal num) { ... }

但 Java 不这样做那样工作。

我想在格式化时使用静态方法。我正在格式化的内容是复合的,其中 Thing1 可以包含 Thing2s 数组,Thing2 可以包含 Thing1s 数组。当我实现这样的格式化程序时,我遇到了一个问题:

class Thing1Formatter {
  private static Thing2Formatter thing2Formatter = new Thing2Formatter();
  public format(Thing thing) {
      thing2Formatter.format(thing.innerThing2);
  }
}
class Thing2Formatter {
  private static Thing1Formatter thing1Formatter = new Thing1Formatter();
  public format(Thing2 thing) {
      thing1Formatter.format(thing.innerThing1);
  }
}

是的,我知道 HashMap,更多的代码也可以解决这个问题。但相比之下,“instanceof”似乎更具可读性和可维护性。有没有什么简单又不臭的东西?

注:2010 年 5 月 10 日添加:

事实证明,将来可能会添加新的子类,而我现有的代码将必须妥善处理它们。在这种情况下,类上的 HashMap 将不起作用,因为找不到该类。一连串的 if 语句,从最具体的开始,到最一般的结束,可能是最好的:

if (obj instanceof SubClass1) {
    // Handle all the methods and properties of SubClass1
} else if (obj instanceof SubClass2) {
    // Handle all the methods and properties of SubClass2
} else if (obj instanceof Interface3) {
    // Unknown class but it implements Interface3
    // so handle those methods and properties
} else if (obj instanceof Interface4) {
    // likewise.  May want to also handle case of
    // object that implements both interfaces.
} else {
    // New (unknown) subclass; do what I can with the base class
}

Having a chain of "instanceof" operations is considered a "code smell". The standard answer is "use polymorphism". How would I do it in this case?

There are a number of subclasses of a base class; none of them are under my control. An analogous situation would be with the Java classes Integer, Double, BigDecimal etc.

if (obj instanceof Integer) {NumberStuff.handle((Integer)obj);}
else if (obj instanceof BigDecimal) {BigDecimalStuff.handle((BigDecimal)obj);}
else if (obj instanceof Double) {DoubleStuff.handle((Double)obj);}

I do have control over NumberStuff and so on.

I don't want to use many lines of code where a few lines would do. (Sometimes I make a HashMap mapping Integer.class to an instance of IntegerStuff, BigDecimal.class to an instance of BigDecimalStuff etc. But today I want something simpler.)

I'd like something as simple as this:

public static handle(Integer num) { ... }
public static handle(BigDecimal num) { ... }

But Java just doesn't work that way.

I'd like to use static methods when formatting. The things I'm formatting are composite, where a Thing1 can contain an array Thing2s and a Thing2 can contain an array of Thing1s. I had a problem when I implemented my formatters like this:

class Thing1Formatter {
  private static Thing2Formatter thing2Formatter = new Thing2Formatter();
  public format(Thing thing) {
      thing2Formatter.format(thing.innerThing2);
  }
}
class Thing2Formatter {
  private static Thing1Formatter thing1Formatter = new Thing1Formatter();
  public format(Thing2 thing) {
      thing1Formatter.format(thing.innerThing1);
  }
}

Yes, I know the HashMap and a bit more code can fix that too. But the "instanceof" seems so readable and maintainable by comparison. Is there anything simple but not smelly?

Note added 5/10/2010:

It turns out that new subclasses will probably be added in the future, and my existing code will have to handle them gracefully. The HashMap on Class won't work in that case because the Class won't be found. A chain of if statements, starting with the most specific and ending with the most general, is probably the best after all:

if (obj instanceof SubClass1) {
    // Handle all the methods and properties of SubClass1
} else if (obj instanceof SubClass2) {
    // Handle all the methods and properties of SubClass2
} else if (obj instanceof Interface3) {
    // Unknown class but it implements Interface3
    // so handle those methods and properties
} else if (obj instanceof Interface4) {
    // likewise.  May want to also handle case of
    // object that implements both interfaces.
} else {
    // New (unknown) subclass; do what I can with the base class
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

梦行七里 2024-09-08 10:24:41

您可能对 Steve Yegge 的 Amazon 博客中的这篇文章感兴趣:“当多态性失败时”< /a>.本质上,他正在解决这样的情况,即多态性造成的麻烦多于其解决的麻烦。

问题是,要使用多态性,您必须将“处理”逻辑作为每个“切换”类的一部分 - 即本例中的“整数”等。显然这是不切实际的。有时,从逻辑上讲,它甚至不是放置代码的正确位置。他推荐“instanceof”方法,因为它是几种弊端中较小的一个。

与所有被迫编写有气味的代码的情况一样,将其保留在一种方法(或最多一个类)中,这样气味就不会泄漏出去。

You might be interested in this entry from Steve Yegge's Amazon blog: "when polymorphism fails". Essentially he's addressing cases like this, when polymorphism causes more trouble than it solves.

The issue is that to use polymorphism you have to make the logic of "handle" part of each 'switching' class - i.e. Integer etc. in this case. Clearly this is not practical. Sometimes it isn't even logically the right place to put the code. He recommends the 'instanceof' approach as being the lesser of several evils.

As with all cases where you are forced to write smelly code, keep it buttoned up in one method (or at most one class) so that the smell doesn't leak out.

时间你老了 2024-09-08 10:24:41

正如评论中强调的那样,访问者模式将是一个不错的选择。但如果没有对目标/接受者/访问者的直接控制,你就无法实现该模式。这是访问者模式仍然可以在这里使用的一种方式,即使您无法通过使用包装器直接控制子类(以 Integer 为例):

public class IntegerWrapper {
    private Integer integer;
    public IntegerWrapper(Integer anInteger){
        integer = anInteger;
    }
    //Access the integer directly such as
    public Integer getInteger() { return integer; }
    //or method passthrough...
    public int intValue() { return integer.intValue(); }
    //then implement your visitor:
    public void accept(NumericVisitor visitor) {
        visitor.visit(this);
    }
}

当然,包装最终类可能会被认为是它自己的味道,但也许它非常适合您的子类。就我个人而言,我不认为 instanceof 这里有那么难闻,特别是如果它仅限于一种方法并且我很乐意使用它(可能超过我自己的建议)。正如你所说,它具有良好的可读性、类型安全性和可维护性。一如既往,保持简单。

As highlighted in the comments, the visitor pattern would be a good choice. But without direct control over the target/acceptor/visitee you can't implement that pattern. Here's one way the visitor pattern could possibly still be used here even though you have no direct control over the subclasses by using wrappers (taking Integer as an example):

public class IntegerWrapper {
    private Integer integer;
    public IntegerWrapper(Integer anInteger){
        integer = anInteger;
    }
    //Access the integer directly such as
    public Integer getInteger() { return integer; }
    //or method passthrough...
    public int intValue() { return integer.intValue(); }
    //then implement your visitor:
    public void accept(NumericVisitor visitor) {
        visitor.visit(this);
    }
}

Of course, wrapping a final class might be considered a smell of its own but maybe it's a good fit with your subclasses. Personally, I don't think instanceof is that bad a smell here, especially if it is confined to one method and I would happily use it (probably over my own suggestion above). As you say, its quite readable, typesafe and maintainable. As always, keep it simple.

过期情话 2024-09-08 10:24:41

您可以将处理的实例放入映射中(键:类,值:处理程序),而不是使用巨大的 if

如果按键查找返回 null,则调用一个特殊的处理程序方法来尝试查找匹配的处理程序(例如,通过对映射中的每个键调用 isInstance())。

找到处理程序后,将其注册到新密钥下。

这使得一般情况变得快速而简单,并允许您处理继承。

Instead of a huge if, you can put the instances you handle in a map (key: class, value: handler).

If the lookup by key returns null, call a special handler method which tries to find a matching handler (for example by calling isInstance() on every key in the map).

When a handler is found, register it under the new key.

This makes the general case fast and simple and allows you to handle inheritance.

染柒℉ 2024-09-08 10:24:41

您可以使用反射:

public final class Handler {
  public static void handle(Object o) {
    try {
      Method handler = Handler.class.getMethod("handle", o.getClass());
      handler.invoke(null, o);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  public static void handle(Integer num) { /* ... */ }
  public static void handle(BigDecimal num) { /* ... */ }
  // to handle new types, just add more handle methods...
}

您可以扩展这个想法来一般处理实现某些接口的子类和类。

You can use reflection:

public final class Handler {
  public static void handle(Object o) {
    try {
      Method handler = Handler.class.getMethod("handle", o.getClass());
      handler.invoke(null, o);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  public static void handle(Integer num) { /* ... */ }
  public static void handle(BigDecimal num) { /* ... */ }
  // to handle new types, just add more handle methods...
}

You can expand on the idea to generically handle subclasses and classes that implement certain interfaces.

烈酒灼喉 2024-09-08 10:24:41

我认为最好的解决方案是以 Class 为键、Handler 为值的 HashMap。请注意,基于 HashMap 的解决方案以恒定的算法复杂度 θ(1) 运行,而 if-instanceof-else 的嗅觉链以线性算法复杂度 O(N) 运行,其中 N 是 if-instanceof-else 链中的链接数(即要处理的不同类的数量)。因此,基于 HashMap 的解决方案的性能比 if-instanceof-else 链解决方案的性能渐进高 N 倍。
考虑到您需要以不同的方式处理 Message 类的不同后代:Message1、Message2 等。下面是基于 HashMap 的处理的代码片段。

public class YourClass {
    private class Handler {
        public void go(Message message) {
            // the default implementation just notifies that it doesn't handle the message
            System.out.println(
                "Possibly due to a typo, empty handler is set to handle message of type %s : %s",
                message.getClass().toString(), message.toString());
        }
    }
    private Map<Class<? extends Message>, Handler> messageHandling = 
        new HashMap<Class<? extends Message>, Handler>();

    // Constructor of your class is a place to initialize the message handling mechanism    
    public YourClass() {
        messageHandling.put(Message1.class, new Handler() { public void go(Message message) {
            //TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message1
        } });
        messageHandling.put(Message2.class, new Handler() { public void go(Message message) {
            //TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message2
        } });
        // etc. for Message3, etc.
    }

    // The method in which you receive a variable of base class Message, but you need to
    //   handle it in accordance to of what derived type that instance is
    public handleMessage(Message message) {
        Handler handler = messageHandling.get(message.getClass());
        if (handler == null) {
            System.out.println(
                "Don't know how to handle message of type %s : %s",
                message.getClass().toString(), message.toString());
        } else {
            handler.go(message);
        }
    }
}

有关 Java 中 Class 类型变量的使用的更多信息:http://docs .oracle.com/javase/tutorial/reflect/class/classNew.html

I think that the best solution is HashMap with Class as key and Handler as value. Note that HashMap based solution runs in constant algorithmic complexity θ(1), while the smelling chain of if-instanceof-else runs in linear algorithmic complexity O(N), where N is the number of links in the if-instanceof-else chain (i.e. the number of different classes to be handled). So the performance of HashMap based solution is asymptotically higher N times than the performance of if-instanceof-else chain solution.
Consider that you need to handle different descendants of Message class differently: Message1, Message2, etc. . Below is the code snippet for HashMap based handling.

public class YourClass {
    private class Handler {
        public void go(Message message) {
            // the default implementation just notifies that it doesn't handle the message
            System.out.println(
                "Possibly due to a typo, empty handler is set to handle message of type %s : %s",
                message.getClass().toString(), message.toString());
        }
    }
    private Map<Class<? extends Message>, Handler> messageHandling = 
        new HashMap<Class<? extends Message>, Handler>();

    // Constructor of your class is a place to initialize the message handling mechanism    
    public YourClass() {
        messageHandling.put(Message1.class, new Handler() { public void go(Message message) {
            //TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message1
        } });
        messageHandling.put(Message2.class, new Handler() { public void go(Message message) {
            //TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message2
        } });
        // etc. for Message3, etc.
    }

    // The method in which you receive a variable of base class Message, but you need to
    //   handle it in accordance to of what derived type that instance is
    public handleMessage(Message message) {
        Handler handler = messageHandling.get(message.getClass());
        if (handler == null) {
            System.out.println(
                "Don't know how to handle message of type %s : %s",
                message.getClass().toString(), message.toString());
        } else {
            handler.go(message);
        }
    }
}

More info on usage of variables of type Class in Java: http://docs.oracle.com/javase/tutorial/reflect/class/classNew.html

如何视而不见 2024-09-08 10:24:41

您可以考虑责任链模式。对于您的第一个示例,类似于:

public abstract class StuffHandler {
   private StuffHandler next;

   public final boolean handle(Object o) {
      boolean handled = doHandle(o);
      if (handled) { return true; }
      else if (next == null) { return false; }
      else { return next.handle(o); }
   }

   public void setNext(StuffHandler next) { this.next = next; }

   protected abstract boolean doHandle(Object o);
}

public class IntegerHandler extends StuffHandler {
   @Override
   protected boolean doHandle(Object o) {
      if (!o instanceof Integer) {
         return false;
      }
      NumberHandler.handle((Integer) o);
      return true;
   }
}

然后对于其他处理程序也类似。然后是按顺序将 StuffHandler 串在一起的情况(从最具体到最不具体,带有最终的“后备”处理程序),并且您的调度程序代码只是 firstHandler.handle(o);

(另一种方法是,不使用链,而是在调度程序类中使用 List ,并让它循环遍历列表,直到 handle() 返回真的)。

You could consider the Chain of Responsibility pattern. For your first example, something like:

public abstract class StuffHandler {
   private StuffHandler next;

   public final boolean handle(Object o) {
      boolean handled = doHandle(o);
      if (handled) { return true; }
      else if (next == null) { return false; }
      else { return next.handle(o); }
   }

   public void setNext(StuffHandler next) { this.next = next; }

   protected abstract boolean doHandle(Object o);
}

public class IntegerHandler extends StuffHandler {
   @Override
   protected boolean doHandle(Object o) {
      if (!o instanceof Integer) {
         return false;
      }
      NumberHandler.handle((Integer) o);
      return true;
   }
}

and then similarly for your other handlers. Then it's a case of stringing together the StuffHandlers in order (most specific to least specific, with a final 'fallback' handler), and your despatcher code is just firstHandler.handle(o);.

(An alternative is to, rather than using a chain, just have a List<StuffHandler> in your dispatcher class, and have it loop through the list until handle() returns true).

小帐篷 2024-09-08 10:24:41

只需使用instanceof即可。所有的解决方法似乎都更加复杂。这是一篇讨论它的博客文章: http://www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html

Just go with the instanceof. All the workarounds seem more complicated. Here is a blog post that talks about it: http://www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html

请远离我 2024-09-08 10:24:41

我已经使用 reflection 解决了这个问题(大约 15 年前的前泛型时代)。

GenericClass object = (GenericClass) Class.forName(specificClassName).newInstance();

我定义了一个通用类(抽象基类)。我已经定义了基类的许多具体实现。每个具体类都将以 className 作为参数进行加载。此类名称被定义为配置的一部分。

基类定义了所有具体类的公共状态,具体类将通过​​覆盖基类中定义的抽象规则来修改状态。

当时我还不知道这个机制的名字,一直被称为反射

这篇文章中列出了更多替代方案:Mapenum 除了反射之外。

I have solved this problem using reflection (around 15 years back in pre Generics era).

GenericClass object = (GenericClass) Class.forName(specificClassName).newInstance();

I have defined one Generic Class ( abstract Base class). I have defined many concrete implementations of base class. Each concrete class will be loaded with className as parameter. This class name is defined as part of configuration.

Base class defines common state across all concrete classes and concrete classes will modify the state by overriding abstract rules defined in base class.

At that time, I don't know the name of this mechanism, which has been known as reflection.

Few more alternatives are listed in this article : Map and enum apart from reflection.

魂ガ小子 2024-09-08 10:24:41

在 BaseClass 中添加一个返回类名称的方法。并使用特定类名覆盖方法

public class BaseClass{
  // properties and methods
  public String classType(){
      return BaseClass.class.getSimpleName();
  }
}

public class SubClass1 extends BaseClass{
 // properties and methods
  @Override
  public String classType(){
      return SubClass1.class.getSimpleName();
  }
}

public class SubClass2 extends BaseClass{
 // properties and methods
  @Override
  public String classType(){
      return SubClass1.class.getSimpleName();
  }
}

现在按以下方式使用 switch case-

switch(obj.classType()){
    case SubClass1:
        // do subclass1 task
        break;
    case SubClass2:
        // do subclass2 task
        break;
}

Add a method in BaseClass which returns name of the class. And override the methods with the specific class name

public class BaseClass{
  // properties and methods
  public String classType(){
      return BaseClass.class.getSimpleName();
  }
}

public class SubClass1 extends BaseClass{
 // properties and methods
  @Override
  public String classType(){
      return SubClass1.class.getSimpleName();
  }
}

public class SubClass2 extends BaseClass{
 // properties and methods
  @Override
  public String classType(){
      return SubClass1.class.getSimpleName();
  }
}

Now use the switch case in following way-

switch(obj.classType()){
    case SubClass1:
        // do subclass1 task
        break;
    case SubClass2:
        // do subclass2 task
        break;
}
罗罗贝儿 2024-09-08 10:24:41

我在 Java 8 中使用的是:

void checkClass(Object object) {
    if (object.getClass().toString().equals("class MyClass")) {
    //your logic
    }
}

What I use for Java 8:

void checkClass(Object object) {
    if (object.getClass().toString().equals("class MyClass")) {
    //your logic
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文