Java泛型:有界类型参数的多重继承。

时间:2022-09-23 17:35:14

I am about to create a factory which creates objects of a certain type T which extends a certain class A and another interface I. However, T must not be known. Here are the minimum declarations:

我将要创建一个工厂,它创建一个特定类型T的对象,它扩展了某个类a和另一个接口I。以下是最小的声明:

public class A { }
public interface I { }

This is the factory method:

这是工厂的方法:

public class F {
    public static <T extends A & I> T newThing() { /*...*/ }
}

This compiles all fine.

这个编译所有罚款。

When I try to use the method the following works fine:

当我尝试使用这个方法时,下面的工作很好:

A $a = F.newThing();

...while this does not:

…而这并不是:

I $i = F.newThing();

The compiler complains:

编译器抱怨道:

Bound mismatch: The generic method newThing() of type F is not applicable for the arguments (). The inferred type I&A is not a valid substitute for the bounded parameter

绑定不匹配:类型F的泛型方法newThing()不适用于参数()。推断式I&A不是有界参数的有效替代。

I can't understand why. It is clearly stated that "newThing returns something of a certain type T which does extend the class A and implement the interface I". When assigning to A everything works (since T extends A) but assigning to I does not (because of what?, clearly the thing returned is both an A and an I)

我不明白为什么。很明显,“newThing返回某种类型的T,它确实扩展了类a并实现了接口I”。当分配到一个所有的工作时(由于T扩展A),但是分配给我没有(因为什么?显然,返回的是A和I

Also: When returning an object, say B of the type class B extends A implements I, I need to cast it to the return type T, although B matches the bounds:

另外:当返回一个对象时,B类的B类扩展了一个实现I,我需要将它转换为返回类型T,尽管B与界限匹配:

<T extends A & I> T newThing() {
    return (T) new B();
}

However, the compiler does not throw any warnings like UncheckedCast or the like.

但是,编译器不会抛出任何警告,如未检查或类似的警告。

Thus my question:

因此我的问题:

  • What is going wrong here?
  • 这里出了什么问题?
  • Is there an easy away to achieve the desired behavior (i.e. assigning to a variable of static type A or I), like there is in solving the return-type-problem by casting, in the factory method?
  • 是否有一个容易实现的行为(即分配给静态类型a或I的变量),就像在工厂方法中通过浇铸来解决回车类型问题一样?
  • Why does the assignment to A work, while to I does not?
  • 为什么分配给一个工作,而我却没有?

--

- - -

EDIT: Here the complete code snippet which totally works using Eclipse 3.7, project set up for JDK 6:

编辑:这里是完整的代码片段,该代码片段完全使用Eclipse 3.7,为JDK 6创建的项目:

public class F {
    public static class A { }
    public static interface I { }

    private static class B extends A implements I {  }

    public static <T extends A & I> T newThing() {
        return (T) new B();
}

    public static void main(String... _) {
        A $a = F.newThing();
        // I $i = F.newThing();
    }
}

EDIT: Here is a complete example with methods and invocation which does work at runtime:

编辑:这里有一个在运行时工作的方法和调用的完整示例:

public class F {
    public static class A {
        int methodA() {
            return 7;
        }
    }
    public static interface I {
        int methodI();
    }

    private static class B extends A implements I {
        public int methodI() {
            return 12;
        }
    }

    public static <T extends A & I> T newThing() {
        return (T) new B();
    }

    public static void main(String... _) {
        A $a = F.newThing();
        // I $i = F.newThing();
        System.out.println($a.methodA());
    }
}

4 个解决方案

#1


7  

This doesn't do what you expect it to. T extends A & I indicates that the caller can specify any type that extends A and I, and you'll return it.

这不是你所期望的。T扩展A &我指示调用者可以指定任何扩展A和I的类型,然后您将返回它。

#2


9  

As for the second question:

关于第二个问题:

Consider this case:

考虑这种情况:

 class B extends A implements I {}
 class C extends A implements I {}

Now, the following uses type inference:

下面使用类型推断:

<T extends A & I> T newThing() {
  return (T) new B();
}

So you could call this:

所以你可以这样叫:

C c = F.newThing(); //T would be C here

You see that T could be anything that extends A and I you can't just return an instance of B. In the case above the cast could be written as (C)new B(). This would clearly result in an exception and thus the compiler issues a warning: Unchecked cast from B to T - unless you're supressing those warnings.

你可以看到,T可以是任何扩展A的东西,我不能只返回一个B的实例,在上面的例子中可以写成(C)new B()。这显然会导致一个异常,因此编译器会发出一个警告:从B到T的不受控制的转换——除非您正在压制这些警告。

#3


4  

I think that one way to explain it is by replacing the type parameter with the actual type.

我认为解释它的一种方法是用实际的类型替换类型参数。

The parameterized signature of the methods is:

方法的参数化签名为:

public static <T extends A & B> T newThing(){
   return ...;
}

The <T extends A & B> is what is called a type parameter. The compiler would expect that this value is actually substituted with the actual type (called type argument) when you actually use it.

是所谓的类型参数。编译器会期望这个值实际上是用实际类型(称为类型参数)替换的。 扩展a>

In the case of your method the actual type is decided by means of type inference. That is, <T extends A & B> should be replaced by a real existing type that extends A and implements B.

在您的方法中,实际类型是通过类型推断来确定的。也就是说, 应该被一个实际存在的扩展A和实现B的类型所取代。 扩展a>

So, let's say that classes C and D both extends A and implements B, then if your signature were like this:

假设类C和D都扩展A并实现B,如果你的签名是这样的

public static <T extends A & B> T newThing(T obj){
   return obj;
}

Then, by type inference, your method would be evaluated as follows:

然后,通过类型推断,您的方法将被评估如下:

public static C newThing(C obj){
   return obj;
}

if you invoke with newThing(new C()).

如果调用newThing(new C())。

And would be as follows

如下所示。

public static D newThing(D obj){
   return obj;
}

if you invoke with newThing(new D()).

如果调用newThing(new D())。

This would compile just fine!

这样编译就可以了!

However, since you are not actually providing any kind of type to validate type inference in your method declaration, then the compiler could never be sure what is the actual type (type argument) of your type parameter <T extends A & B>.

然而,由于您实际上并没有提供任何类型的类型来验证方法声明中的类型推断,那么编译器就不能确定您的类型参数 的实际类型(类型参数)是什么。 扩展a>

You might expect that the actual type is C, but there may be thousands of different classes that satisfy that criteria. Which of those should the compiler use as the actual type of your type argument?

您可能期望实际的类型是C,但是可能有数千个不同的类满足这个标准。编译器应该使用哪一个作为类型参数的实际类型?

Let's say that C and D are two classes that extend A and implements B. Which of these two actual types should the compiler use as type argument for your method?

假设C和D是两个扩展A和实现b的类。

You could have even declared a type argument for which there is not even an existing type that you can use, like saying something that extends Serializable and Closable and Comparable and Appendable.

您甚至可以声明一个类型参数,其中甚至没有您可以使用的现有类型,比如说一些扩展了可序列化的、可选择的、可比较的和可应用的。

And perhaps there is not a class in the whole world that satisfies that.

也许整个世界上没有一个阶级能满足这个要求。

As such, you must understand that the type parameter here is just a requirement for the compiler to validate the actual type that you use, a placeholder for the actual type; and this actual type must exist at the end and the compiler will use it to replace appearances of T. Therefore the actual type (type argument) must be inferable from the context.

因此,您必须理解,这里的类型参数只是编译器验证您所使用的实际类型的一个需求,是实际类型的占位符;而这个实际类型必须存在于末尾,编译器将使用它来替换t的外观。因此,实际类型(类型参数)必须从上下文推断。

Since the compiler cannot tell with certainty which is the actual type that you mean, basically because there is no way to determine that by type inference in this case, then you are forced to cast your type, to ensure the compiler that you know what you are doing.

因为编译器不能确定你所指的实际类型是什么,基本上是因为在这种情况下没有办法通过类型推断来确定它,那么你就*转换你的类型,以确保编译器你知道你在做什么。

As such, you could implement your method using type inference like this:

因此,您可以使用这样的类型推断来实现您的方法:

   public static <T extends A & B> T newThing(Class<T> t) throws Exception{
    return t.newInstance();
}

This way, you would be actually telling the compiler what is the actual type argument to be used.

这样,您就会告诉编译器实际使用的类型参数是什么。

Take into account that when the bytecodes are generated, the compiler must substitute T for a real type. There is no way to write method in Java like this

考虑到在生成字节码时,编译器必须将T替换为真正的类型。在Java中没有办法像这样编写方法。

public static A & B newThing(){ return ... }

Right?

对吧?

I hope I have explained myself! This is not simple to explain.

我希望我已经解释清楚了!这并不容易解释。

#4


0  

Simplest solution is create an abstract base class that extends and implements whatever class and interfaces you want and return that type. It doesn't matter that you're constraining your return type to extend this base class as you were already constraining the return type to its superclass.

最简单的解决方案是创建一个抽象基类,它扩展并实现您想要的任何类和接口,并返回该类型。您限制返回类型来扩展这个基类并不重要,因为您已经将返回类型限制为它的超类。

eg.

如。

class C {}
interface I {}

abstract class BaseClass extends C implements I {}
// ^-- this line should never change. All it is telling us that we have created a
// class that combines the methods of C and I, and that concrete sub classes will
// implement the abstract methods of C and I    


class X extends BaseClass {}
class Y extends BaseClass {}

public class F {

    public static BaseClass newThing() {
        return new X();
    }


    public static void main(String[] args) {
        C c = F.newThing();
        I i = F.newThing();
    }
}

#1


7  

This doesn't do what you expect it to. T extends A & I indicates that the caller can specify any type that extends A and I, and you'll return it.

这不是你所期望的。T扩展A &我指示调用者可以指定任何扩展A和I的类型,然后您将返回它。

#2


9  

As for the second question:

关于第二个问题:

Consider this case:

考虑这种情况:

 class B extends A implements I {}
 class C extends A implements I {}

Now, the following uses type inference:

下面使用类型推断:

<T extends A & I> T newThing() {
  return (T) new B();
}

So you could call this:

所以你可以这样叫:

C c = F.newThing(); //T would be C here

You see that T could be anything that extends A and I you can't just return an instance of B. In the case above the cast could be written as (C)new B(). This would clearly result in an exception and thus the compiler issues a warning: Unchecked cast from B to T - unless you're supressing those warnings.

你可以看到,T可以是任何扩展A的东西,我不能只返回一个B的实例,在上面的例子中可以写成(C)new B()。这显然会导致一个异常,因此编译器会发出一个警告:从B到T的不受控制的转换——除非您正在压制这些警告。

#3


4  

I think that one way to explain it is by replacing the type parameter with the actual type.

我认为解释它的一种方法是用实际的类型替换类型参数。

The parameterized signature of the methods is:

方法的参数化签名为:

public static <T extends A & B> T newThing(){
   return ...;
}

The <T extends A & B> is what is called a type parameter. The compiler would expect that this value is actually substituted with the actual type (called type argument) when you actually use it.

是所谓的类型参数。编译器会期望这个值实际上是用实际类型(称为类型参数)替换的。 扩展a>

In the case of your method the actual type is decided by means of type inference. That is, <T extends A & B> should be replaced by a real existing type that extends A and implements B.

在您的方法中,实际类型是通过类型推断来确定的。也就是说, 应该被一个实际存在的扩展A和实现B的类型所取代。 扩展a>

So, let's say that classes C and D both extends A and implements B, then if your signature were like this:

假设类C和D都扩展A并实现B,如果你的签名是这样的

public static <T extends A & B> T newThing(T obj){
   return obj;
}

Then, by type inference, your method would be evaluated as follows:

然后,通过类型推断,您的方法将被评估如下:

public static C newThing(C obj){
   return obj;
}

if you invoke with newThing(new C()).

如果调用newThing(new C())。

And would be as follows

如下所示。

public static D newThing(D obj){
   return obj;
}

if you invoke with newThing(new D()).

如果调用newThing(new D())。

This would compile just fine!

这样编译就可以了!

However, since you are not actually providing any kind of type to validate type inference in your method declaration, then the compiler could never be sure what is the actual type (type argument) of your type parameter <T extends A & B>.

然而,由于您实际上并没有提供任何类型的类型来验证方法声明中的类型推断,那么编译器就不能确定您的类型参数 的实际类型(类型参数)是什么。 扩展a>

You might expect that the actual type is C, but there may be thousands of different classes that satisfy that criteria. Which of those should the compiler use as the actual type of your type argument?

您可能期望实际的类型是C,但是可能有数千个不同的类满足这个标准。编译器应该使用哪一个作为类型参数的实际类型?

Let's say that C and D are two classes that extend A and implements B. Which of these two actual types should the compiler use as type argument for your method?

假设C和D是两个扩展A和实现b的类。

You could have even declared a type argument for which there is not even an existing type that you can use, like saying something that extends Serializable and Closable and Comparable and Appendable.

您甚至可以声明一个类型参数,其中甚至没有您可以使用的现有类型,比如说一些扩展了可序列化的、可选择的、可比较的和可应用的。

And perhaps there is not a class in the whole world that satisfies that.

也许整个世界上没有一个阶级能满足这个要求。

As such, you must understand that the type parameter here is just a requirement for the compiler to validate the actual type that you use, a placeholder for the actual type; and this actual type must exist at the end and the compiler will use it to replace appearances of T. Therefore the actual type (type argument) must be inferable from the context.

因此,您必须理解,这里的类型参数只是编译器验证您所使用的实际类型的一个需求,是实际类型的占位符;而这个实际类型必须存在于末尾,编译器将使用它来替换t的外观。因此,实际类型(类型参数)必须从上下文推断。

Since the compiler cannot tell with certainty which is the actual type that you mean, basically because there is no way to determine that by type inference in this case, then you are forced to cast your type, to ensure the compiler that you know what you are doing.

因为编译器不能确定你所指的实际类型是什么,基本上是因为在这种情况下没有办法通过类型推断来确定它,那么你就*转换你的类型,以确保编译器你知道你在做什么。

As such, you could implement your method using type inference like this:

因此,您可以使用这样的类型推断来实现您的方法:

   public static <T extends A & B> T newThing(Class<T> t) throws Exception{
    return t.newInstance();
}

This way, you would be actually telling the compiler what is the actual type argument to be used.

这样,您就会告诉编译器实际使用的类型参数是什么。

Take into account that when the bytecodes are generated, the compiler must substitute T for a real type. There is no way to write method in Java like this

考虑到在生成字节码时,编译器必须将T替换为真正的类型。在Java中没有办法像这样编写方法。

public static A & B newThing(){ return ... }

Right?

对吧?

I hope I have explained myself! This is not simple to explain.

我希望我已经解释清楚了!这并不容易解释。

#4


0  

Simplest solution is create an abstract base class that extends and implements whatever class and interfaces you want and return that type. It doesn't matter that you're constraining your return type to extend this base class as you were already constraining the return type to its superclass.

最简单的解决方案是创建一个抽象基类,它扩展并实现您想要的任何类和接口,并返回该类型。您限制返回类型来扩展这个基类并不重要,因为您已经将返回类型限制为它的超类。

eg.

如。

class C {}
interface I {}

abstract class BaseClass extends C implements I {}
// ^-- this line should never change. All it is telling us that we have created a
// class that combines the methods of C and I, and that concrete sub classes will
// implement the abstract methods of C and I    


class X extends BaseClass {}
class Y extends BaseClass {}

public class F {

    public static BaseClass newThing() {
        return new X();
    }


    public static void main(String[] args) {
        C c = F.newThing();
        I i = F.newThing();
    }
}