简体   繁体   English

Java Generics:使用通用 class 作为另一个通用 ZA2F2ED4F8EBC2CBB4C21A29DC40AB6 的类型参数

[英]Java Generics: Use generic class as type parameter of another generic class

Is there any way in Java to use a generic class as a type parameter of another generic class? Java 中是否有任何方法可以使用通用 class 作为另一个通用 class 的类型参数?

For example, I have Message and MessageBus like this:例如,我有这样的MessageMessageBus

public interface Message<V> {};

public interface MessageBus {
    <V> V execute(Message<V> message);
}

It works well now.现在效果很好。

And then, I get Command and CommandBus like this:然后,我得到这样的CommandCommandBus

public interface Command<V> {};

public interface CommandBus {
    <V> V execute(Command<V> command);
}

It works well, too.它也很好用。

But, the implementation of CommandBus is almost the same as MessageBus .但是, CommandBus的实现与MessageBus几乎相同。 I want to define MessageBus as a generic class like this:我想将MessageBus定义为通用 class ,如下所示:

public interface Message<V> {};
public interface MessageBus<T extends Message> {};
public interface Command<V> extends Message<V> {};
public interface CommandBus extends MessageBus<Command> {};

It seems to be fine, but problem comes when I want to define method execute :似乎很好,但是当我想定义方法时出现问题execute

public interface MessageBus<T extends Message> {
    <V> V execute(T<V> message);       // compiles error, T<V> is invalid
    <V> V execute(T message);          // T means Message<Object>, not Message<V>
    <V> V execute(Message<V> message); // CommandBus cannot override Message<V> to Command<V> in subclass
}

Note that V is different type on each call, so I cannot define MessageBus like this:请注意, V在每次调用时都是不同的类型,所以我不能像这样定义MessageBus

public interface MessageBus<V, T extends Message<V>> {
    /**
     * In this way, each bus can have only one fixed return type.
     * But different return type is expected on different message,
     * just like the first interface above.
     */
    V execute(T message);
}

I want to use them like this:我想像这样使用它们:

public class Command1 implements Command<Integer> {};
public class Command2 implements Command<String> {};
public class Command3 implements Command<String> {};
public class Message1 implements Message<String> {};

CommandBus commandBus;
Integer v1 = commandBus.execute(new Command1());  // fine
String v2 = commandBus.execute(new Command2());   // fine
String v3 = commandBus.execute(new Command3());   // fine
String v4 = commandBus.execute(new Command1());   // should compile error, return type mismatch
String v5 = commandBus.execute(new Message1());   // should compile error, only support Command

I will have kinds of MessageBus , like CommandBus extends MessageBus<Command> , QueryBus extends MessageBus<Query> , EventBus extends MessageBus<Event> and so on.我会有各种MessageBus ,比如CommandBus extends MessageBus<Command>QueryBus extends MessageBus<Query>EventBus extends MessageBus<Event>等等。

How would I implement this behavior?我将如何实现这种行为?

Thanks.谢谢。

You lost the T from Message , which was the original return type.您丢失了Message中的T ,这是原始返回类型。

public interface Message<T> {};
public interface MessageBus<T> {};
public interface Command<T> extends Message<T> {};
public interface CommandBus<T> extends MessageBus<T> {};

Now your classes will be like现在你的课程就像

class MessageBus<String> {
    String execute(Message<String> m) { ... }
}
class CommandBus<Integer> {
    String execute(Message<Integer> m) { ... }
    Integer execute(Command<Integer> c) { ... }
}

…How would I implement this behavior?… ……我将如何实现这种行为?……

Here's how I'd implement it以下是我将如何实现它……

public interface MessageBus< S extends Foo< ? > > {

    < T extends Foo< U >, U > U execute( T message );
}

I introduced the Foo interface you see there because in order to be able to do what you say you want to do — and do it in a type safe wayMessage and Command needed to be the same type .我介绍了你在那里看到的Foo接口,因为为了能够做你说你想做的事——并且以一种类型安全的方式来做—— MessageCommand需要是相同的type

In the context of this representative example , Foo takes on the responsibility of that necessary commonality.这个代表性示例的上下文中, Foo承担了必要的共性的责任。 You'd rename Foo to be something more meaningful in your actual business domain.您可以将Foo重命名为在您的实际业务领域中更有意义的名称。

...I want to use them like this :“ ...我想这样使用它们:“

 ... public class Command1 implements Command<Integer> {}; public class Command2 implements Command<String> {}; public class Command3 implements Command<String> {}; public class Message1 implements Message<String> {}; ...

In my demo I've confirmed that the solution is usable like this…我的演示中,我已经确认该解决方案可以像这样使用......

    CommandBus commandBus = new StandardCmdBus() ;
    
    CharSequence v0 = commandBus.execute(new CharSeqCommand());   
    
    Integer v1 = commandBus.execute(new IntegerCommand());    // fine
    
    String v2 = commandBus.execute(new StringCommand());      // fine    
    
    String v3 = commandBus.execute(new StringMessage());      // fine
    
    /*String v4 = commandBus.execute(new CharSeqCommand());*/ // error: incompatible types: inference variable U has incompatible bounds

With the concrete Messages and Commands that I also introduced, this gets printed to stdout使用我还介绍的具体MessagesCommands ,这将打印到stdout ……

 Hail Bopp!
        666
Heal Setan!
Hell Santa!

You already know, from the various compilation errors you got originally, your original implementation can't do what you've now clarified is what you actually want to do.您已经知道,从您最初遇到的各种编译错误中,您的原始实现无法执行您现在已经阐明的您真正想要做的事情。 In the comments to his/her answer @daniu aludes to one reason why that is.在对他/她的回答的评论中,@daniu 暗示了一个原因。

Your original code wants to do something the language forbids .您的原始代码想要做一些语言禁止的事情 Namely: Override a method using a signature that's totally different from the super signature .即:使用与超级签名完全不同的签名覆盖方法

So you don't want to structure your application the way the code in your original question illustrated.因此,您不想按照原始问题中的代码所示的方式构建应用程序。 To do what you say it is you actually want to do, your original code needs some significant refactors.要按照您所说的去做,您的原始代码需要进行一些重要的重构。 So does the code in my first answer.我的第一个答案中的代码也是如此。 Hence this separate answer…因此,这个单独的答案......

public interface MessageBus< V > { 
    
    V evaluate( Message<V> msg );
}

public interface CommandBus< V > { 
    
    V evaluate( Command<V> msg );
}

public interface EventBus< V > { 
    
    V evaluate( Event<V> msg );
}
...
public interface Message < V >{ 
    
    V evaluate( );
}
public interface Command< V > extends Message< V >{ 
    
    V evaluate( );
}
...
public class StandardCmdBus { 
    
    public static <S, T extends Command<S>> S evaluate( T cmd ){ return cmd.evaluate();  }
}

I demonstrate how that could be actually used like this…我演示了如何像这样实际使用它……

public static void main(String args[]) {
  
    Stream< Command< ? > > shellCmds = of( () -> 3.14, () -> "javac", () -> "gcc", () -> 42.424242D, () -> "grep", () -> 666);
    
    Stream< Command< ? > > robotCmds = of( () -> "Assemble 666,666,666,666 Cars!", () -> "Exterminate!", () -> "Disallow: /questions/63242515/", () -> 777.9311, () -> "Rescue Will Robinson!", () -> "Kill all humans!", () -> 666);
    
    Stream< Message< ? > > msgs = of( () -> "What hath God wrought?", () -> "Sending out an S.O.S...", () -> "I like apples...", () -> 666, () -> "Friends of space, how are you all? Have you eaten yet? Come visit us if you have time.", () -> "?.?.?", () -> "The answer is...", () -> 42);

    Stream< Event< ? > > evts = of( () -> "The Big Bang", () -> "First Contact", () -> 867.5309, () -> "The Moon Landing", () -> "onLoad()", () -> 666, () -> "The Rapture" );
    
    Stream< Query< ? > > queries = of( () -> "The Ultimate Query...", () -> 42 );
    
    CommandBus< ? > cmdBus = ( cmd ) -> cmd.evaluate( );
    
    MessageBus< ? > msgBus = ( msg ) -> msg.evaluate( );
    
    EventBus< ? > evtBus = ( evt ) -> evt.evaluate( );
    
    QueryBus< ? > qBus = ( q ) -> q.evaluate( );
    
    /* Totally type safe; no unchecked warnings (i.e. no casts invovled) */
    robotCmds.map( StandardCmdBus::evaluate ).forEach( out::println );
    
    /* Less type safe; But totally fine; „uses unchecked or unsafe opertions“ (i.e. casts invovled) */
    use( shellCmds, cmdBus::evaluate ); 
    
    use( msgs, msgBus::evaluate ); 
    
    use( evts, evtBus::evaluate );
    
    use( queries, qBus::evaluate );
    
    Message< String > wtf = ( ) -> "// should compile error";
    
    /* cmdBus.evaluate( wtf );*/ /* error: incompatible types: Message<String> cannot be converted to Command<CAP#1> */ 
    
}

…Which prints out… …打印出来…

Assemble 666,666,666,666 Cars!
Exterminate!
Disallow: /questions/63242515/
777.9311
Rescue Will Robinson!
Kill all humans!
666
...
-----------

What hath God wrought?
Sending out an S.O.S...
I like apples...
666
Friends of space, how are you all? Have you eaten yet? Come visit us if you have time.
?.?.?
The answer is...
42        
...
-----------

The Ultimate Query...
42

...

Your original implementation uses Generics sort of over-zealously.您的原始实现使用 Generics 有点过分热心。 We all are guilty of that at some point.在某些时候,我们都为此感到内疚。 They are a cool form of polymorphism.它们是一种很酷的多态形式。 But used in the wrong way, they cause more problems than they solve.但是以错误的方式使用它们会导致比解决的问题更多的问题。

CommandBus IS A special MessageBus CommandBus是一种特殊的MessageBus

Consider the following class structure.考虑以下 class 结构。 Think of MessageBus as Animal , CommandBus as Cat , Message analogous to Food and Candy analogous to CommandMessageBus视为AnimalCommandBus视为CatMessage类似于FoodCandy类似于Command ...

 +----------------+
 |     Animal     |
 +----------------+                +----------------+
 |eat(Food)       |                |      Food      |
 |                                 |                |
 |                |                +--------^-------+
 +-------^--------+                         |
         |                                  |
         |                      >-----------------------<
 +----------------+             |                       |
 |      Cat       |    +----------------+      +----------------+
 +----------------+    |      Candy     |      |      Grass     |
 |eat(Food)       |    |                |      |                |
 |eat(Candy)      |    +----------------+      +----------------+
 |eat(Grass)      |
 +----------------+



Anything that „ IS A Animal “ absolutely MUSTeat(Food) “.任何“Animal ”的东西都必须eat(Food) ”。 If it doesn't eat food, then it is NOT an Animal .如果它不吃东西,那么它就不是Animal That is what inheritance is all about.这就是 inheritance 的全部意义所在。

Analogously: Anything that „ IS A MessageBus “ absolutely MUSTexecute(Message) “.类似地:任何“是 MessageBusMessageBus东西绝对必须execute(Message) ”。 If it doesn't execute a message, then it is NOT a MessageBus .如果它不执行消息,那么它不是MessageBus

To say: „ A command bus should only send commands, no other messages “ is exactly like saying: „ A Cat should only eat Candy , no other Food “.说:“一个命令总线应该只发送命令,没有其他消息”就像在说:“一只Cat应该只吃Candy ,不应该吃其他Food ”。 If it doesn't eat Food , then it is NOT an Animal .如果它不吃Food ,那么它就不是Animal

Sure, a Cat can eat Candy and Grass under specialized circumstances.当然, Cat特殊情况下可以CandyGrass It's not a Cat's default behavior though.不过,这不是Cat's默认行为。

„… I'd like inheritance and generics… “……我想要 inheritance 和仿制药……

It is possible to have both.两者都有是可能的。 But I think the expectations you have for combining them, are not possible.但我认为你对组合它们的期望不可能的。

If you truly want inheritance, then you must accept that a Cat MUST eat(Food) .如果您真的想要 inheritance,那么您必须接受Cat MUST eat(Food) If you want a CommandBus that does not execute(Message) , then you do not want inheritance.如果您想要一个不execute(Message)CommandBus ,那么您不需要inheritance。

Sure, it's possible to implement a CommandBus to execute(Command) .当然,可以实现一个CommandBusexecute(Command) But that must be in addition to execute(Message) Because of inheritance , execute(Message) is the default behavior for CommandBus .但这必须是除了execute(Message)因为inheritanceexecute(Message)CommandBus的默认行为。

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

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