简体   繁体   English

如何使具有有界类型参数的方法排除一个子类?

[英]How can I make a method with a bounded type parameter exclude one subclass?

Let us assume that gibbons, orangutans, gorillas, chimpanzees and humans are all apes.让我们假设长臂猿、猩猩、大猩猩、黑猩猩和人类都是猿。 I have modeled this relationship accordingly.我已经相应地模拟了这种关系。

class Ape {}
class Gibbon extends Ape {}
class Orangutan extends Ape {}
class Gorilla extends Ape {}
class Chimpanzee extends Ape {}
class Human extends Ape {}

I now want to write a hunt() method with a bounded type parameter for hunting apes.我现在想编写一个带有有界类型参数的hunt() 方法来狩猎猿类。

public static <T extends Ape> void hunt(T type) {}

Let us assume that there is nothing wrong with hunting non-human apes because they are just animals.让我们假设猎杀非人类猿没有错,因为它们只是动物。 However, hunting humans is wrong because it is murder.然而,猎杀人类是错误的,因为这是谋杀。 How can I re-write the above bounded type parameter to exclude humans as a legal parameter?如何重写上述有界类型参数以将人类排除为合法参数? I am not interested in exceptions here.我对这里的例外不感兴趣。 I do not want the hunt() method to compile at all if invoked with a human parameter.如果使用人工参数调用,我根本不希望hunt() 方法编译。

I don't believe there's a way to do this.我不相信有办法做到这一点。 You could make a sub-type of Ape that excludes Humans, or you can check for Humans as a type in the method itself (which would violate the Open-Closed principle) :您可以创建一个排除 Humans 的 Ape 子类型,或者您可以在方法本身中检查 Humans 作为类型(这将违反开放-封闭原则):

public static <T extends Ape> void hunt(T type) {
  if (type instanceOf Human) {
     throw new IllegalArgumentException("It's immoral to hunt Humans.  You monster.");
  }
  . . .
}

You can't exclude a specific subclass the way you intend there.你不能按照你想要的方式排除一个特定的子类。 What you can do however, is create an interface 'isHuntable', implemented by the required animals you want to hunt, and you use that as a type for your method instead of the generic type bound.但是,您可以做的是创建一个接口“isHuntable”,由您想要狩猎的所需动物实现,然后将其用作方法的类型,而不是泛型类型绑定。 Another maybe less elegant solution is to create another level in your hierarchy called 'Monkeys' for example which extend from Ape, having all the animals extending from it, and you use that Monkey type for your method.另一个可能不太优雅的解决方案是在您的层次结构中创建另一个称为“猴子”的级别,例如从 Ape 扩展而来,所有动物都从它扩展而来,并且您将这种 Monkey 类型用于您的方法。 I'd go with the first approach though.不过我会采用第一种方法。 You could use explicit type checking, but you'd be breaking the 'open-closed' principle in you code, so it's better to leverage those checks to the type system.您可以使用显式类型检查,但您会破坏代码中的“开闭”原则,因此最好将这些检查用于类型系统。

Just extending a bit on the concept of interface behaviour-contract which is powerful tool which is sadly underused.只是对接口行为契约的概念进行了扩展,这是一个功能强大的工具,可惜没有得到充分利用。 What you have among your Apes is a "is-a" relationship which tightly couples your subjects.你在你的类人猿之间是一种“是一种”关系,它把你的主题紧密结合在一起。 Being "Huntable" on the other hand, is not an inherent or defining characteristic of the structure of that hierarchy, and is more an added condition/behaviour/check you'd like to add to a subset of that hierarchy.另一方面,“可狩猎”不是该层次结构的固有或定义特征,而是您想添加到该层次结构子集的附加条件/行为/检查。 That is better achieved by adding contracts (interface implementation) to the subset you intend to have that behaviour.通过将合同(接口实现)添加到您打算具有该行为的子集,可以更好地实现这一点。 That will allow to add further contracts (interface implementations) in the future with minimal refactorings, maximal semantic clarity, less bugs and without tightly bounding a type to a behaviour.这将允许在未来以最少的重构、最大的语义清晰度、更少的错误和不将类型与行为紧密绑定的情况下添加更多的契约(接口实现)。 In short, you don't break the Liskov Substitution principle, nor the open closed principle, nor any SOLID principle.简而言之,您不会违反 Liskov 替换原则、开闭原则或任何 SOLID 原则。

You cannot do this.你不可以做这个。 There is no way to specify a bound to say "any subclass, except this one".没有办法指定一个绑定说“任何子类,除了这个”。

And even if you could, it wouldn't stop you invoking it:即使你可以,它也不会阻止你调用它:

chimp.hunt((Ape) human);

would get around it.会绕过它。

All you can do is to check this at runtime.您所能做的就是在运行时检查这一点。

You could, via some tool such as Error Prone (written by Google, I am an employee, and I contribute; other tools are available) write a compile-time check to ensure that the argument is neither Ape nor Human ;您可以通过诸如Error Prone 之类的工具(由 Google 编写,我是一名员工,我做出贡献;其他工具可用)编写编译时检查以确保参数既不是Ape也不是Human but that's outside the capabilities of "pure" Java.但这超出了“纯”Java 的能力。

I would make your hunt method non-static;我会让你的hunt方法非静态; it's an action on the object itself, after all.毕竟,这是对对象本身的操作。 Then, you can define the behavior for all Apes:然后,您可以为所有 Apes 定义行为:

public void hunt() {
    this.die(); // oh no!
}

And of course, humans override that basic idea:当然,人类超越了这个基本想法:

@Override
public void hunt() {
    throw new IllegalMurderException("Human horn hunting is against intergalactic law");
}

Adding a check for #isHuntable would be advisable, as keeping with the metaphor it's nice to know if you're allowed to hunt something before doing so.添加对#isHuntable的检查是可取的,因为与比喻保持一致,很高兴知道在这样做之前是否允许您寻找某些东西。 Additionally, I went with an exception for human hunting as it violates the general behavior of what you (well, I) would expect #hunt to achieve.此外,我对人类狩猎有一个例外,因为它违反了您(好吧,我)期望#hunt实现的一般行为。

For the sake of overengineering and various concepts of SOLID, you can achieve this with a Huntable interface, which could extend a Living interface:为了过度设计和 SOLID 的各种概念,您可以使用Huntable接口来实现这一点,它可以扩展一个Living接口:

public interface Living {
    public void die();
}

public interface Huntable extends Living {
    default public void hunt() {
        this.die();
    }
    //could use this for a generic exception in the interface too
    public boolean isHuntable(); 
}

public interface Ape extends Huntable {} // already Huntable

public interface Human extends Ape {
    //And finally, we break that behavioral contract like above
}

Overall this makes your generics unnecessary.总的来说,这使您的泛型变得不必要。 You'll be forced into a type cast and manual check if you go that route, as other answers have shown.如果您走那条路线,您将被迫进行类型转换和手动检查,如其他答案所示。 So while it sidesteps the original question, I think this is a better solution to the underlying problem (XY problem?).因此,虽然它回避了最初的问题,但我认为这是对潜在问题(XY 问题?)的更好解决方案。 More importantly, behavior concerning humans remains in a class describing humans.更重要的是,关于人类的行为仍然属于描述人类的一类。

Edit:编辑:

In keeping with the static method and a checked exception, while I strongly recommend against it (per my comments), you can use a method overload so Human objects go through a different signature:为了与静态方法和检查异常保持一致,虽然我强烈建议反对它(根据我的评论),但您可以使用方法重载,以便Human对象通过不同的签名:

public static void hunt(Ape example) { ... }

public static void hunt(Human example) throws HuntingException {
    throw new HuntingException("cannot hunt humans");
}

HuntingException would extend Exception instead of RuntimeException . HuntingException将扩展Exception而不是RuntimeException Exception / Error / Throwable objects that you make yourself are all essentially checked exceptions. Exception / Error / Throwable对象,你让自己本质上都是检查的异常。 Short of that, there's no creating compiler errors yourself as a developer.除此之外,作为开发人员自己不会产生编译器错误。 You can generate warnings, but you face the same pitfalls.您可以生成警告,但您会面临相同的陷阱。

You can kind of see why this is silly, and a human is still able to be hunted without a compiler error at all by just casting:您可以理解为什么这很愚蠢,并且通过强制转换,仍然可以在没有编译器错误的情况下对人类进行追捕:

Main.hunt((Ape) someHuman);

So now you go back down the rabbit hole of adding type checks in your #hunt method, which won't throw a compiler error (or will have to always throw one).所以现在你回到在 #hunt 方法中添加类型检查的兔子洞,它不会抛出编译器错误(或者必须总是抛出一个错误)。 If you always throw it, then developers using your code (and quite possibly yourself) will just auto fill try-catch blocks around every single invocation of #hunt just to handle an error for Human .如果你总是抛出它,那么使用你的代码(很可能你自己)的开发人员只会在每次调用 #hunt 时自动填充 try-catch 块,只是为了处理Human的错误。 This adds a lot of "code noise", which just makes things messy.这增加了很多“代码噪音”,只会让事情变得混乱。

In short, runtime exceptions are for developer errors like "hunting a human", and there's too many reasons to list for why having this as a static method (let alone main class) is not good from a design standpoint.简而言之,运行时异常是针对“猎杀人类”之类的开发人员错误的,从设计的角度来看,为什么将其作为静态方法(更不用说主类)不好的原因太多了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM