泛型类构造函数构建另一个具有有界类型的泛型类的实例时出现类型推断错误

问题描述 投票:0回答:1

当类型参数有界时 (

T extends Comparable<? super T>
),我遇到了类型推断问题,使用
<>
让编译器推断类型,如下例所示:

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class InferTypeArgApp {
    static class ACollection<E extends Comparable<? super E>> {
    // static class ACollection<E> {  // <-- If E isn't bounded, BCollection can use <>
        private Set<E> mySet;

        ACollection(Comparator<? super E> comparator) {
            mySet = new TreeSet<>(comparator);
        }
    }

    static class BCollection<E extends Comparable<? super E>> {
        private ACollection<E> myACollection;

        BCollection(Comparator<? super E> comparator) {
            myACollection = new ACollection<>(comparator);
            //              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <-- Cannot infer type
            //                                                arguments for
            //                                                ACollection<>Java(16778094)
            //
            // myACollection = new ACollection<E>(comparator);  // <-- This works
        }
    }

    public static void main(String[] args) {
        ACollection<Integer> col = new ACollection<>(null); // <-- Java manages to
                                                            //     implicitly infer
                                                            //     the type here
    }
}

如果我将

ACollection
声明为类
ACollection<E extends Comparable<? super E>>
那么我会收到类型参数推断错误。但是,如果它被声明为
class ACollection<E>
那么就没有错误。

在我看来,Java 编译器有足够的信息来推断

ACollection
我在
BCollection
构造函数中初始化时具有类型参数
E
,无论
ACollection
类型参数是否扩展任何内容。

为什么会出现类型推断错误?

为了完整起见,这是完整的编译错误,它没有为我澄清任何内容,但希望有人可以解析它:

$ /usr/local/java/jdk/bin/javac -Xdiags:verbose InferTypeArgApp.java
InferTypeArgApp.java:20: error: cannot infer type arguments for ACollection<>
            myACollection = new ACollection<>(comparator);
                            ^
  reason: cannot infer type-variable(s) E#1
    (argument mismatch; Comparator<CAP#1> cannot be converted to Comparator<? super E#3>)
  where E#1,E#2,E#3 are type-variables:
    E#1 extends Comparable<? super E#1> declared in class ACollection
    E#2 extends Comparable<? super E#2> declared in class BCollection
    E#3 extends CAP#1,Comparable<? super E#3>
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object super: E#2 from capture of ? super E#2
1 error
java generics type-inference
1个回答
0
投票

你的类定义没有意义。

您选择一扇门:您执行以下 2 个策略中的一个。你试图同时做这两件事,这是荒谬的:

  1. “关心排序的事物”中的对象具有自然顺序。换句话说,这些对象知道如何根据自己类型的其他实例对“自己”进行排序。 Integer 是一个类的示例,其实例可以做到这一点:我可以询问一个 Integer
    itself
    它相对于其他整数实例的排序:
  2. Integer i = 5; Integer j = 10; i.compareTo(j); // returns negative to indicate i is lower.
为了表示这一点,您可以设置泛型边界来指示类型,无论它是什么,都需要能够将其自身与其他 E 进行比较。您尝试过这个 - 这就是 
<E extends Comparable<? super E>>

的用途。

您不需要比较器,它们在这里没有意义

Comparable的全部是你

不需要一个
——这些对象已经可以将自己与自己进行比较。

或者,您可以向“关心排序的事物”的构造函数传递一个预言机,该预言机可以确定该事物所操作的任意两个实例中的哪一个位于另一个实例之前(或者它们在排序方面是否相同) 。您可以通过要求
    Comparator<? super E>
  1. 来做到这一点。 __在这种情况下,您不需要或不希望 E 为
    extends Comparable
    
    
  2. 你已经完成了
两者

。事实上,这不起作用,这是无关紧要的,因为两者都做是没有意义的。传递 Comparator是你的泛型类型

不需要是 Comparable
您应该查看

TreeSet

的来源,它与您试图走的路线完全相同:TreeSet 有 2 种不同的方式可以构建:


    不要通过比较器 - 只需
  1. new TreeSet<>()

    。然而,TreeSet

    要求
    你的集合的泛型类型是Comparable的。因为如果不这样做,TreeSet 就无法工作。它需要某人告诉它如何订购东西。

  2. 一定要通过比较器 - 例如
  3. new TreeSet<>(String.CASE_INSENSITIVE_ORDERING)

    。在这种情况下,TreeSet 并不关心泛型类型是什么。它可以是

    任何东西
    ;它不需要延长Comparable
    
    

  4. 不幸的是,TreeSet 认输了,“解决”了这个问题。

这是完全合法的,因为它编译得很好:

Set<InputStream> sortedStreams = new TreeSet<>();

它可以编译。它不应该,因为该代码是编译时可检测的废话。输入流不知道如何对自己进行排序。如果您向此树集中添加任何内容,它将立即引发异常。

但是..你可以做得更好!

将构造函数设为私有,并使用静态方法作为构造函数。现在您可以为这些静态方法提供正确的泛型!

TreeSet 应该是这样的:

// Current, 'broken' TreeSet: public class TreeSet<E> { private final TreeMap<E, Object> backingMap; public TreeSet() { backingMap = new TreeMap<>(); } }

// This is what it should have been

public class TreeSet<E> {
  private final TreeMap<E, Object> backingMap;

  private TreeSet(Comparator<? super E> comparator) {
    this.backingMap = new TreeMap<>(comparator);
  }

  public static <E> TreeSet<E> usingComparator(Comparator<? super E> comparator$
    return new TreeSet<>(comparator);
  }

  public static <E extends Comparable<? super E>> TreeSet<E> naturalOrder() {
    return new TreeSet<>(Comparator.naturalOrder());
  }
}
TreeSet 没有这个,可能是因为现有的有些损坏的构造函数不太可能消失(java 不喜欢打扰维护者现有的代码,这些代码可以很好地工作,并且需要更新它),如果它们留下来,

拥有一堆静态方法可能有点矫枉过正。 话又说回来,那条确切的路径(应该是静态方法的构造函数,然后在保留构造函数的同时添加方法,然后标记构造函数

@Deprecated

)就是

Integer
Long
所经历的,所以,也许有一天.
无论如何,您现在可以使用更好的解决方案,您不再需要高度重视向后兼容性。

© www.soinside.com 2019 - 2024. All rights reserved.