Java 8 Comparator类型推断非常困惑

时间:2014-06-26 17:36:58

标签: java sorting lambda java-8 comparator

我一直在研究Collections.sortlist.sort之间的区别,特别是关于使用Comparator静态方法以及lambda表达式中是否需要param类型。在我们开始之前,我知道我可以使用方法参考,例如Song::getTitle克服了我的问题,但我的问题并不是我想解决的问题,而是我想要答案的问题,即为什么Java编译器会以这种方式处理它。

这是我的发现。假设我们有ArrayList类型Song,添加了一些歌曲,有3种标准获取方法:

    ArrayList<Song> playlist1 = new ArrayList<Song>();

    //add some new Song objects
    playlist.addSong( new Song("Only Girl (In The World)", 235, "Rhianna") );
    playlist.addSong( new Song("Thinking of Me", 206, "Olly Murs") );
    playlist.addSong( new Song("Raise Your Glass", 202,"P!nk") );

这是对两种类型的排序方法的调用,没问题:

Collections.sort(playlist1, 
            Comparator.comparing(p1 -> p1.getTitle()));

playlist1.sort(
            Comparator.comparing(p1 -> p1.getTitle()));

一旦我开始链thenComparing,就会发生以下情况:

Collections.sort(playlist1,
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing(p1 -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

即。语法错误,因为它不再知道p1的类型。所以为了解决这个问题,我将类型Song添加到第一个参数(比较):

Collections.sort(playlist1,
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing((Song p1) -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

现在来了Confusing部分。对于p laylist1.sort,即List,这解决了以下thenComparing调用的所有编译错误。但是,对于Collections.sort,它解决了第一个问题,但不是最后一个问题。我测试了thenComparing添加了几个额外的调用,它总是显示最后一个错误,除非我为参数添加了(Song p1)

现在,我继续通过创建TreeSet并使用Objects.compare进行测试:

int x = Objects.compare(t1, t2, 
                Comparator.comparing((Song p1) -> p1.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );


    Set<Song> set = new TreeSet<Song>(
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

同样的事情发生在TreeSet,没有编译错误,但Objects.comparethenComparing的最后一次调用显示错误。

任何人都可以解释为什么会这样,以及为什么在简单地调用比较方法时没有必要使用(Song p1)(没有进一步的thenComparing调用)。

关于同一主题的另一个问题是当我对TreeSet

执行此操作时
Set<Song> set = new TreeSet<Song>(
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

即。从比较方法调用的第一个lambda参数中删除类型Song,它显示比较调用下的语法错误以及对thenComparing的第一次调用,但不显示对thenComparing的最终调用 - 几乎与上面发生的相反!然而,对于所有其他3个示例,即使用Objects.compareList.sortCollections.sort,当我删除第一个Song param类型时,它会显示所有调用的语法错误。

非常感谢提前。

编辑包含我在Eclipse Kepler SR2中收到的错误的屏幕截图,我现在发现这些错误是特定于Eclipse的,因为当在命令行上使用JDK8 java编译器进行编译时,它编译正常。

Sort errors in Eclipse

4 个答案:

答案 0 :(得分:81)

首先,您所说的所有示例都会导致错误编译与参考实现(来自JDK 8的javac)。它们在IntelliJ中也可以正常工作,因此您看到的错误很可能是特定于Eclipse的。

您的基本问题似乎是:“当我开始链接时,为什么它会停止工作。”原因是,虽然lambda表达式和泛型方法调用在它们作为方法参数出现时是多义表达式(它们的类型是上下文敏感的),但当它们显示为方法接收器表达式时,它们不是。

当你说

Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));

有足够的类型信息来解决comparing()的类型参数和参数类型p1comparing()调用从Collections.sort的签名中获取其目标类型,因此知道comparing()必须返回Comparator<Song>,因此p1必须为{{} 1}}。

但是当你开始链接时:

Song

现在我们遇到了问题。我们知道复合表达式Collections.sort(playlist1, comparing(p1 -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist())); 的目标类型为comparing(...).thenComparing(...),但因为链的接收器表达式Comparator<Song>是一个泛型方法调用,我们无法推断它从其他参数中输入参数,我们运气不好。由于我们不知道此表达式的类型,因此我们不知道它具有comparing(p -> p.getTitle())方法等。

有几种方法可以解决这个问题,所有这些方法都涉及注入更多类型信息,以便可以正确键入链中的初始对象。在这里,它们按照降低需求和增加侵入性的粗略顺序:

  • 使用精确的方法引用(没有重载的引用),如thenComparing。然后,这会提供足够的类型信息来推断Song::getTitle调用的类型变量,从而为其提供一个类型,从而继续沿着链继续。
  • 使用显式lambda(就像您在示例中所做的那样)。
  • comparing()电话提供类型见证:comparing()
  • 通过将接收器表达式强制转换为Comparator.<Song, String>comparing(...),提供带有强制转换的显式目标类型。

答案 1 :(得分:19)

问题是类型推断。如果不在第一次比较中添加(Song s)comparator.comparing就不知道输入的类型,因此默认为对象。

您可以通过3种方式之一解决此问题:

  1. 使用新的Java 8方法参考语法

     Collections.sort(playlist,
                Comparator.comparing(Song::getTitle)
                .thenComparing(Song::getDuration)
                .thenComparing(Song::getArtist)
                );
    
  2. 将每个比较步骤拉出到本地参考

      Comparator<Song> byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist());
    
      Comparator<Song> byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration());
    
        Collections.sort(playlist,
                byName
                .thenComparing(byDuration)
                );
    

    修改

  3. 强制比较器返回的类型(注意你需要输入类型和比较键类型)

    sort(
      Comparator.<Song, String>comparing((s) -> s.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );
    
  4. 我认为&#34;最后&#34; thenComparing语法错误会误导您。它实际上是整个链的类型问题,它只是编译器只将链的末尾标记为语法错误,因为当最终返回类型不匹配时我想。

    我不确定为什么List正在做一个比Collection更好的推理工作,因为它应该执行相同的捕获类型,但显然不是。

答案 2 :(得分:0)

playlist1.sort(...)从playlist1的声明中为类型变量E创建一个Song的边界,该声明“涟漪”到比较器。

Collections.sort(...)中,没有这样的约束,并且第一个比较器类型的推断不足以让编译器推断出其余部分。

我认为你会从Collections.<Song>sort(...)获得“正确”的行为,但是没有java 8安装来为你测试它。

答案 3 :(得分:0)

处理此编译时错误的另一种方法:

明确地投射你的第一个比较函数的变量然后好了。我对org.bson.Documents对象的列表进行了排序。请查看示例代码

**************************
HTML
***************************

<table>
<tbody>
<tr>
 <th *ngFor="let colValues of tableData | columnPipe ">{{colValues}}</th>
</tr>
<tr *ngFor="let row of tableData | searchPipe : mysearchFilter.value">
<td *ngFor="let rowValues of row | rowPipe">{{ rowValues }}</td>
</tr>
</tbody>
</table>


**************************
App.ts
***************************

export class JobComponent implements OnInit {         
public tableData: any = [
{ 'ShipmentRef': 'E159 025 035', 'CustomerName' : 'Sample Pty Ltd', 'PickupSuburb' : 'Lynwood WA 6147', 'DeliverySuburb' : 'Rockingham WA 6219' , 'JobType' : 'Domestic', 'Weight' : '40 Kg', 'DispatchStatus': 'Dispatched', 'AccountType': 'Credit', 'Hold': ' ' , 'Managed By': 'Third Party', 'TrackingStatus': 'Track Here', 'Action': ''}
];
constructor() { }
ngOnInit() {
}
}

    **************************
    pipes.ts
    ***************************
export class columnPipe implements PipeTransform {
  transform(value: any, args?: any): any {
    const columnNames = [];
    for (var i = 0; i < value.length; i++) {
      for (const key in value[i]) {
        if (columnNames.indexOf(key) === -1) {
          columnNames.push(key);
        }
      }
    }
    return columnNames;
  }
}

@Pipe({
  name: 'rowPipe'
})

export class rowPipe implements PipeTransform {
  transform(value: any, args?: any): any {
    const rowValues = Object.keys(value);
    return rowValues.map(k => value[k]);
  }
}

@Pipe({
  name: 'searchPipe'
})
export class searchPipe implements PipeTransform {
  transform(values: any[], filter: string): any {
    if (!values || !values.length) return [];
    if (!filter) return values;

    filter = filter.toUpperCase();

    if (filter && Array.isArray(values)) {
      const keys = Object.keys(values[0]);
      return values.filter(v => v && keys.some(k => v[k].toUpperCase().indexOf(filter) >= 0));
    }
  }
}