我经常遇到Java lambda表达式的问题,当我想对对象的任意属性或方法上的stream()进行区分(但要保留该对象而不是将其映射到该属性或方法上)时,就会遇到问题。我开始创建容器,如这里讨论的那样,但是我开始做足够的工作,直到它变得令人讨厌并制作了许多样板课程。
我把这个Pairing类放在一起,它包含两种类型的两个对象,并允许你指定从左,右或两个对象上抠出的键。我的问题是……在某种类型的关键供应商上,实际上没有内置的lambda流函数可对distinct()吗?那真的会让我感到惊讶。如果不是,此类将可靠地实现该功能吗?
这就是它的称呼
BigDecimal totalShare = orders.stream().map(c -> Pairing.keyLeft(c.getCompany().getId(), c.getShare())).distinct().map(Pairing::getRightItem).reduce(BigDecimal.ZERO, (x,y) -> x.add(y));
这是配对课程
public final class Pairing<X,Y> {
private final X item1;
private final Y item2;
private final KeySetup keySetup;
private static enum KeySetup {LEFT,RIGHT,BOTH};
private Pairing(X item1, Y item2, KeySetup keySetup) {
this.item1 = item1;
this.item2 = item2;
this.keySetup = keySetup;
}
public X getLeftItem() {
return item1;
}
public Y getRightItem() {
return item2;
}
public static <X,Y> Pairing<X,Y> keyLeft(X item1, Y item2) {
return new Pairing<X,Y>(item1, item2, KeySetup.LEFT);
}
public static <X,Y> Pairing<X,Y> keyRight(X item1, Y item2) {
return new Pairing<X,Y>(item1, item2, KeySetup.RIGHT);
}
public static <X,Y> Pairing<X,Y> keyBoth(X item1, Y item2) {
return new Pairing<X,Y>(item1, item2, KeySetup.BOTH);
}
public static <X,Y> Pairing<X,Y> forItems(X item1, Y item2) {
return keyBoth(item1, item2);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
if (keySetup.equals(KeySetup.LEFT) || keySetup.equals(KeySetup.BOTH)) {
result = prime * result + ((item1 == null) ? 0 : item1.hashCode());
}
if (keySetup.equals(KeySetup.RIGHT) || keySetup.equals(KeySetup.BOTH)) {
result = prime * result + ((item2 == null) ? 0 : item2.hashCode());
}
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Pairing<?,?> other = (Pairing<?,?>) obj;
if (keySetup.equals(KeySetup.LEFT) || keySetup.equals(KeySetup.BOTH)) {
if (item1 == null) {
if (other.item1 != null)
return false;
} else if (!item1.equals(other.item1))
return false;
}
if (keySetup.equals(KeySetup.RIGHT) || keySetup.equals(KeySetup.BOTH)) {
if (item2 == null) {
if (other.item2 != null)
return false;
} else if (!item2.equals(other.item2))
return false;
}
return true;
}
}
更新:
在下面测试了Stuart的功能,它看起来很棒。下面的操作在每个字符串的第一个字母上有所区别。我要弄清楚的唯一部分是ConcurrentHashMap
如何仅为整个流维护一个实例
public class DistinctByKey {
public static <T> Predicate<T> distinctByKey(Function<? super T,Object> keyExtractor) {
Map<Object,Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
public static void main(String[] args) {
final ImmutableList<String> arpts = ImmutableList.of("ABQ","ALB","CHI","CUN","PHX","PUJ","BWI");
arpts.stream().filter(distinctByKey(f -> f.substring(0,1))).forEach(s -> System.out.println(s));
}
输出是…
ABQ
CHI
PHX
BWI
该distinct操作是有状态的管道操作;在这种情况下,它是一个有状态过滤器。自己创建它们有点不方便,因为没有内置的东西,但是一个小助手类应该可以解决问题:
/**
* Stateful filter. T is type of stream element, K is type of extracted key.
*/
static class DistinctByKey<T,K> {
Map<K,Boolean> seen = new ConcurrentHashMap<>();
Function<T,K> keyExtractor;
public DistinctByKey(Function<T,K> ke) {
this.keyExtractor = ke;
}
public boolean filter(T t) {
return seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
}
我不知道你的域类,但是我认为,有了这个帮助器类,你可以像这样做:
BigDecimal totalShare = orders.stream()
.filter(new DistinctByKey<Order,CompanyId>(o -> o.getCompany().getId())::filter)
.map(Order::getShare)
.reduce(BigDecimal.ZERO, BigDecimal::add);
不幸的是,类型推断无法在表达式中得到足够的支持,因此我必须为DistinctByKey类明确指定类型参数。
这比Louis Wasserman所描述的收集器方法涉及更多的设置,但这具有以下优点:不同的项目会立即通过而不是被缓冲直到收集完成。空间应该是相同的,因为(不可避免地)两种方法最终都会累积从流元素中提取的所有不同密钥。
更新
可以摆脱K类型参数,因为除了存储在地图中之外,它实际上没有用于其他任何用途。这样Object就足够了。
/**
* Stateful filter. T is type of stream element.
*/
static class DistinctByKey<T> {
Map<Object,Boolean> seen = new ConcurrentHashMap<>();
Function<T,Object> keyExtractor;
public DistinctByKey(Function<T,Object> ke) {
this.keyExtractor = ke;
}
public boolean filter(T t) {
return seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
}
BigDecimal totalShare = orders.stream()
.filter(new DistinctByKey<Order>(o -> o.getCompany().getId())::filter)
.map(Order::getShare)
.reduce(BigDecimal.ZERO, BigDecimal::add);
这简化了一些事情,但是我仍然必须为构造函数指定type参数。尝试使用菱形或静态工厂方法似乎并没有改善。我认为困难在于,编译器无法在方法引用的实例表达式中推断出泛型类型参数(用于构造函数或静态方法调用)。那好吧。
(与此有关的另一种变体可能会简化DistinctByKey<T> implements Predicate<T>
该方法,并将其重命名为eval。这将消除使用方法引用的需要,并可能会改善类型推断。但是,它不太可能像下面的解决方案一样好。)
更新2
不能停止思考。代替帮助器类,使用高阶函数。我们可以使用捕获的本地人来维护状态,因此我们甚至不需要单独的类!奖金,事情简化了,所以类型推断起作用了!
public static <T> Predicate<T> distinctByKey(Function<? super T,Object> keyExtractor) {
Map<Object,Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
BigDecimal totalShare = orders.stream()
.filter(distinctByKey(o -> o.getCompany().getId()))
.map(Order::getShare)
.reduce(BigDecimal.ZERO, BigDecimal::add);
下面是它的名称 这是配对类 更新: 测试了斯图尔特下面的功能,它似乎工作很好。下面的操作区分每个字符串的第一个字母。我想要弄清楚的是,ConcurrentHashMap如何只为整个流维护一个实例 输出为...
问题内容: 我将如何在Python中“按任意键”(或抓住菜单选项)? raw_input要求您按回车键。 Windows msvcrt具有getch()和getche()。 有使用标准库执行此操作的可移植方法吗? 问题答案:
主要内容:语法,实例SQLite 的 DISTINCT 关键字与 SELECT 语句一起使用,来消除所有重复的记录,并只获取唯一一次记录。 有可能出现一种情况,在一个表中有多个重复的记录。当提取这样的记录时,DISTINCT 关键字就显得特别有意义,它只获取唯一一次记录,而不是获取重复记录。 语法 用于消除重复记录的 DISTINCT 关键字的基本语法如下: 实例 假设 COMPANY 表有以下记录: 首先,让我们来
主要内容:语法在 T-SQL 中,DISTINCT 关键字与 SELECT 语句一起使用以消除重复记录并仅给出唯一记录值。 下面是表中有很多重复记录的情况。当我们获取记录时,只用唯一的记录值代替重复的记录值更有意义。 语法 DISTINCT 关键字的语法如下: 例子: EMPLOYEES 表中有以下记录: 下面来看看 SELECT 查询如何返回重复的工资记录。 执行上面查询语句,得到以下结果 - 注意:此命令生
问题内容: 我有为任意键实现“锁定处理程序”的代码。给定一个,它确保一次只能有一个线程可以(或等于)该键(这意味着调用该调用)。 到目前为止,我有这样的代码: 我知道这段代码可能导致,因为没有清晰的地图。 我考虑如何制作地图,该地图将累积有限数量的元素。当超过限制时,我们应该用new替换最旧的访问元素(此代码应与最旧的元素作为监视器同步)。但是我不知道如何进行回调,这将告诉我超出限制。 请分享您的
我的代码实现了一个任意键的“锁处理程序”。给定,它确保每次只有一个线程可以该(或等于)key(这里表示调用调用)。 到目前为止,我有这样的代码: 我理解这段代码可能导致,因为没有一个清晰的映射。 我重新阅读了该任务,现在我发现我的限制是方法不能被调用超过8个线程。我不知道它对我有什么帮助,但我刚刚提到了。 P.S.2 @蜘蛛鲍里斯建议了一个很好又简单的解决方案: 但在Boris注意到我们的代码不是