【问题标题】:Type Inference in Method Reference方法参考中的类型推断
【发布时间】:2016-08-22 07:40:11
【问题描述】:

我最近开始使用 Java 8 并尝试使用方法引用。 我尝试了不同类型的方法引用,却陷入了“对特定类型的任意对象的实例方法的引用”类型中。

String[] arr = {"First", "Second", "Third", "Fourth"};

Arrays.sort(arr, String::compareToIgnoreCase);

这很好用。但是当我尝试通过其类型引用用户定义类的方法时:

Demo2[] arr = {a, b};

Arrays.sort(arr, Demo2::compare);

这会将编译时错误显示为“无法从静态上下文引用非静态方法”。

这是 Demo2 类:

public class Demo2 implements Comparator<Demo2> {
    Integer i;

    Demo2(Integer i1){
        i = i1;
    }

    public Integer getI() {
        return i;
    }

    @Override
    public int compare(Demo2 o1, Demo2 o2) {
        return o1.getI().compareTo(o2.getI());
    }
}

【问题讨论】:

  • StringcompareToIgnoreCase方法的声明。
  • 一般来说,如果你将一个类声明为Comparator,而不是Comparable,那么你的设计是错误的。 Comparator 应该用于比较不能自然比较的对象。作为一个对象,它与它比较的对象是分开的,并且只包含有助于它进行比较的状态。
  • 这似乎是 ComparatorComparable 之间相当标准的混淆。

标签: java java-8 method-reference


【解决方案1】:

正如 greg-449 指出的那样,您的代码存在错误。

通过像YourObjet::yourMethod 这样的方法引用,您可以对实例的方法进行静态 引用。因此将为每个对象调用该方法,因此签名需要与之前的不同

将编译的代码将采用以下形式:

Demo2[] demos = {a, b};
Arrays.sort(demos, Demo2::compareTo);

public class Demo2 {
    Integer i;

    Demo2(Integer i1){
        i = i1;
    }

    public Integer getI() {
        return i;
    }

    public int compareTo(Demo2 other) {
        return this.getI().compareTo(other.getI());
    }
}

但正如 RealSkeptic 指出的那样,这不是实现和对象比较的正确方法。你应该给 Arrays.sort 方法一个比较器:

 Arrays.sort(demos, (obj1, obj2) -> obj1.getI().compareTo(obj2.getI()));

【讨论】:

  • 实际上,在您的示例中使用 compareTo 非常好,它实际上不是任何接口的一部分(尽管如果您声明 Demo2 implements Comparable&lt;Demo2&gt; 它也可以,但您不会不需要Arrays.sort(array,comparator))。我的评论是关于滥用Comparator 接口,而某人实际上是指Comparable
【解决方案2】:

Arrays.sort(T[],Comparator&lt;T&gt;) 所需的Comparator 接口有一个方法,该方法接受两个相同类型的对象引用T,并返回一个整数。

方法引用有一点“魔力”。 Java 所做的是以符合接口要求的方式包装方法。

当然,接口不需要静态方法。但是包装可以创建一个调用静态方法的方法,如Tutorial的第一个例子:

public static int compareByAge(Person a, Person b) {
    return a.birthday.compareTo(b.birthday);
}

它以这样一种方式包装它,你会得到类似的东西

new Comparator<Person>() {
    @Override
    public int compare(Person a, Person b) {
        return Person.compareByAge(a,b);
    }
}

满足界面。

但在“对特定类型的任意对象的实例方法的引用”部分的示例中,它以不同的方式包装它。它需要一个接收两个字符串的方法,但它有一个只接收一个字符串的方法。这就是String::compareToIgnoreCase 的定义方式:

public int compareToIgnoreCase(String str)

但在这种情况下,它是一个实例方法。 Java 现在所做的是,因为这个方法属于String 类型的对象,并且接受String 类型的对象,所以很容易围绕它构建一个“包装”,使其成为一个接受两个对象的方法,很像 lambda 表达式:

(String a, String b) -> {
    return a.compareToIgnoreCase( b );
}

或者,如果我们将正式包装想象为Comparator

new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareToIgnoreCase(b);
    }
}

因此,它是属于T 类型的实例方法,接受T 类型并返回int,这一事实允许Java 适当地包装它,使其适合Comparator 接口。

但是int compare(Demo2 o1, Demo2 o2) 不符合这种模式。它接受两个参数。如果一个方法接受两个参数,它必须是一个符合包装规则的静态方法——没有办法将“this”对象传递给Comparator接口。因此它尝试将其包装为静态方法,但失败了,因为它不是静态方法。

底线:你得到了错误,因为对于这种特定类型的方法引用,你需要一个实例方法,其中只有一个与类相同类型的 一个参数。

正如@Holger 在评论中提到的那样,如果您正在构建一个新类,则不应在其中专门为此类排序任务放置比较方法。如果该类具有自然排序,则将其设为Comparable 并使用Arrays.sort(Object[])。如果不是,并且您有时需要根据它的任何属性对其进行排序,请使用 lambda 表达式或Comparator.comparing(Demo2::getI),这样可以更好地利用现有的 getter 来进行特定的比较。

【讨论】:

    【解决方案3】:

    按照惯例,Comparator&lt;T&gt; 是使用 lambda 表达式实现的,在类中实现它看起来很奇怪。

        Demo2[] array = new Demo2[2];
        array[0] = new Demo2(12);
        array[1] = new Demo2(32);
    
        Comparator<Demo2> demo2Comparator = (e1,e2)->e1.getI().compareTo(e2.getI());
        Arrays.sort(array, demo2Comparator);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-20
      • 1970-01-01
      • 2017-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多