我一直在研究Collections.sort
和list.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.compare
对thenComparing
的最后一次调用显示错误。
任何人都可以解释为什么会这样,以及为什么在简单地调用比较方法时没有必要使用(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.compare
,List.sort
和Collections.sort
,当我删除第一个Song
param类型时,它会显示所有调用的语法错误。
非常感谢提前。
编辑包含我在Eclipse Kepler SR2中收到的错误的屏幕截图,我现在发现这些错误是特定于Eclipse的,因为当在命令行上使用JDK8 java编译器进行编译时,它编译正常。
答案 0 :(得分:81)
首先,您所说的所有示例都会导致错误编译与参考实现(来自JDK 8的javac)。它们在IntelliJ中也可以正常工作,因此您看到的错误很可能是特定于Eclipse的。
您的基本问题似乎是:“当我开始链接时,为什么它会停止工作。”原因是,虽然lambda表达式和泛型方法调用在它们作为方法参数出现时是多义表达式(它们的类型是上下文敏感的),但当它们显示为方法接收器表达式时,它们不是。
当你说
时Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));
有足够的类型信息来解决comparing()
的类型参数和参数类型p1
。 comparing()
调用从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
调用的类型变量,从而为其提供一个类型,从而继续沿着链继续。comparing()
电话提供类型见证:comparing()
。 Comparator.<Song, String>comparing(...)
,提供带有强制转换的显式目标类型。 答案 1 :(得分:19)
问题是类型推断。如果不在第一次比较中添加(Song s)
,comparator.comparing
就不知道输入的类型,因此默认为对象。
您可以通过3种方式之一解决此问题:
使用新的Java 8方法参考语法
Collections.sort(playlist,
Comparator.comparing(Song::getTitle)
.thenComparing(Song::getDuration)
.thenComparing(Song::getArtist)
);
将每个比较步骤拉出到本地参考
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)
);
修改强>
强制比较器返回的类型(注意你需要输入类型和比较键类型)
sort(
Comparator.<Song, String>comparing((s) -> s.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
我认为&#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));
}
}
}