【问题标题】:Stream using Comparator dropping values使用比较器丢弃值进行流式传输
【发布时间】:2018-11-18 21:05:59
【问题描述】:

我是一名初级开发人员,几乎没有使用 Java 流的经验。我已经确定(通过一些额外的日志记录)以下流正在删除我专门寻找的记录,但我不知道为什么。请注意,这段代码不是我写的,写的人已经离开了公司。该项目使用的是 Spring。

ConfirmCode 表结构如下:

  Name              Type    Length  Not Null
  confirm_code_id   int     11      True
  account_id        varchar 20      False
  product_id        varchar 20      False
  dimension         varchar 55      False
  confirm_code      int     1       False
  confirm_desc      varchar 50      False
  action_enum       enum    0       False 
  sms_keywords      varchar 255     False 

此表包含特定的枚举,其中 account_id、product_id 和维度设计允许为 null:

  ConfirmCode
  accountId productId dimension code desc               enum     keywords
                                  2  Need to Reschedule RESCHED  {not important}
                                  7  Call Transferred   TRANSFER {not important}
                                 -1  Help               HELP     {not important}
                                 -2  Opt In             OPT IN   {not important}
                                 -3  Opt Out            OPT OUT  {not important}
                                 -4  Response           RESPONSE {not important}    
                                  1  Confirmed          CONFIRM  {not important}

但是,客户 (accountID) 可以覆盖默认绑定到枚举的代码。例如:

  ConfirmCode
  accountId productId dimension             code desc               enum     keywords
  704442    RemindMe  Appointment Reminder  2    Confirmed          CONFIRM  {not important}    

由于 accountId、productId 和 dimension 允许为 null,Spring 运行的查询需要考虑到这一点以及覆盖默认值的客户,如您在上面看到的(在这种情况下父帐户为 null)。

Query:
public interface ConfirmCodeRepository extends JpaRepository<ConfirmCode, Long> {

  @Query("SELECT c FROM ConfirmCode c WHERE (accountId is null OR accountId = :parentAccountId OR accountId = :accountId) "
        + " AND (productId = :productId OR productId is NULL)"
        + " AND (dimension is null OR dimension = :dimension)")

  public List<ConfirmCode> findByAccountIdAndProductIdAndDimension(
        @Param("accountId") String accountId, @Param("parentAccountId") String parentAccountId,
        @Param("productId") String productId, @Param("dimension") String dimension);

我遇到的问题是,当为客户 704442 查询 ConfirmCode 表时,该记录会为该客户返回,但后来在通过流中的比较器代码后被删除。只有 accountId、productId 和 dimension 为空的记录。这是对查询的结果集执行逻辑的代码:

@Repository
public class ConfirmCodeDAO {

    @Autowired
    ConfirmCodeRepository confirmCodeRepo;
    private static final Logger logger = LoggerFactory.getLogger(PreferenceService.class);

    public Set<ConfirmCode> findByAccountIdAndParentAccountIdAndProductIdAndDimension(
            String accountId,
            String parentAccountId, String productId, String dimension) {

        List<ConfirmCode> list = confirmCodeRepo
                .findByAccountIdAndProductIdAndDimension(accountId, parentAccountId, productId,
                        dimension);
        list.forEach(r -> logger.info("list: " + r.toString()));

        Set<ConfirmCode> set = confirmCodeRepo
                .findByAccountIdAndProductIdAndDimension(accountId, parentAccountId, productId,
                        dimension)
                .stream()
                .sorted(Comparator.comparing(ConfirmCode::getActionEnum,
                        Comparator.nullsFirst(Comparator.naturalOrder())))
                .sorted(Comparator.comparing(ConfirmCode::getDimension,
                        Comparator.nullsFirst(Comparator.naturalOrder())))
                .sorted(Comparator.comparing(ConfirmCode::getProductId,
                        Comparator.nullsFirst(Comparator.naturalOrder())))
                .sorted(Comparator.comparing(ConfirmCode::getAccountId,
                        Comparator.nullsFirst(Comparator.reverseOrder())))
                .collect(Collectors.toMap(ConfirmCode::getConfirmCode, confirmCode -> confirmCode,
                        (c1, c2) -> c2))
                .entrySet()
                .stream()
                .map(e -> e.getValue())
                .collect(Collectors.toSet());
        set.forEach(r -> logger.info("set2: " + r.toString()));
        return set;
    }
}

这是“列表”对象的日志记录:

Calling confirmCodeDAO with: 704442, parentAccountId: null, productId: RemindMe, dimension: Appointment Reminder
list: [accountId=704442, productId=RemindMe, dimension=Appointment Reminder, confirmCode=2, confirmDesc=Confirmed, actionEnum=CONFIRM, smsKeywords={not important}]
list: [accountId=null, productId=null, dimension=null, confirmCode=1, confirmDesc=Confirmed, actionEnum=CONFIRM, smsKeywords={not important}]
list: [accountId=null, productId=null, dimension=null, confirmCode=2, confirmDesc=Need to Reschedule, actionEnum=RESCHED, smsKeywords={not important}]
list: [accountId=null, productId=null, dimension=null, confirmCode=7, confirmDesc=Call Transferred, actionEnum=TRANSFER, smsKeywords={not important}, account=null, responseType=null]
list: [accountId=null, productId=null, dimension=null, confirmCode=3, confirmDesc=Repeat, actionEnum=REPEAT, smsKeywords={not important}, account=null, responseType=null]
list: [accountId=null, productId=null, dimension=null, confirmCode=-1, confirmDesc=Help, actionEnum=HELP, smsKeywords={not important}, account=null, responseType=null]
list: [accountId=null, productId=null, dimension=null, confirmCode=-2, confirmDesc=Opt In, actionEnum=OPT IN, smsKeywords={not important}, account=null, responseType=null]
list: [accountId=null, productId=null, dimension=null, confirmCode=-3, confirmDesc=Opt Out, actionEnum=OPT OUT, smsKeywords={not important}, account=null, responseType=null]
list: [accountId=null, productId=null, dimension=null, confirmCode=-4, confirmDesc=Response, actionEnum=RESPONSE, smsKeywords={not important}, account=null, responseType=null]
list: [accountId=null, productId=null, dimension=null, confirmCode=-5, confirmDesc=Cancelled, actionEnum=CANCEL, smsKeywords={not important}, account=null, responseType=null]

这是“set”对象的日志记录:

set: [accountId=null, productId=null, dimension=null, confirmCode=-1, confirmDesc=Help, actionEnum=HELP, smsKeywords={not important}]
set: [accountId=null, productId=null, dimension=null, confirmCode=1, confirmDesc=Confirmed, actionEnum=CONFIRM, smsKeywords={not important}]
set: [accountId=null, productId=null, dimension=null, confirmCode=-5, confirmDesc=Cancelled, actionEnum=CANCEL, smsKeywords={not important}]
set: [accountId=null, productId=null, dimension=null, confirmCode=7, confirmDesc=Call Transferred, actionEnum=TRANSFER, smsKeywords={not important}]
set: [accountId=null, productId=null, dimension=null, confirmCode=3, confirmDesc=Repeat, actionEnum=REPEAT, smsKeywords={not important}, account=null, responseType=null]
set: [accountId=null, productId=null, dimension=null, confirmCode=-2, confirmDesc=Opt In, actionEnum=OPT IN, smsKeywords={not important}, account=null, responseType=null]
set: [accountId=null, productId=null, dimension=null, confirmCode=-4, confirmDesc=Response, actionEnum=RESPONSE, smsKeywords={not important}, account=null, responseType=null]
set: [accountId=null, productId=null, dimension=null, confirmCode=-3, confirmDesc=Opt Out, actionEnum=OPT OUT, smsKeywords={not important}, account=null, responseType=null]

您可以看到,在通过流代码之后,我需要的记录被删除了。请就此提出建议,感谢您的宝贵时间!

【问题讨论】:

  • 您正在将条目收集到Set,因此收集器之前的所有比较器都是多余的。

标签: java null stream equals comparator


【解决方案1】:

排序本身不能丢弃元素。但是通过使用

收集列表
toMap.collect(Collectors.toMap(ConfirmCode::getConfirmCode, confirmCode -> 
confirmCode, (c1, c2) -> c2))

您只为每个属性保留一个确认代码。

这个 toMap 收集器的工作原理如下:

第一个参数定义了key,所以它将对象ConfigmCode的confirmCode属性作为key(名字有点误导)。第二个是值confirmCode -&gt; confirmCode,所以整个对象都保留为值。因此我们得到一个Map&lt;Integer, ConfirmCode&gt;

第三个参数描述了如果map中存在key areay如何合并两个值。函数(c1,c2) -&gt; c2 简单地说:拿第二个。

在您的列表中,第一个和第三个元素都具有 confirmCode=2,因此只有第二个元素保留在地图中。

正如在 cmets 中所讨论的,这只会删除第二个元素(在输入列表中),confirmItem=2,因为比较器首先对 null 进行排序。

地图现在包含预期的元素,但是当值被收集到一个集合时,ConfirmCode 的 equals 方法将对象定义为相等,如果它们具有相同的 actionEnum。虽然第一个元素(排序到最后)与列表中的第二个元素具有相同的 actionEnum,但收集器只保留第一个。

【讨论】:

  • 感谢您的解释。如果 (c1,c2) -> c2 表示保留第二个,那么由于流首先比较空值,因此不应该保留客户 704442 的记录吗?还是 .sorted(Comparator.comparing(ConfirmCode::getAccountId, Comparator.nullsFirst(Comparator.reverseOrder()))) 反转整个事情?
  • 不,你是对的。预期的应该是使用此比较器的第二个。奇怪的是 is no 元素末尾有 confirmCode=2 。可能是您的对象 ConfirmCode 具有奇怪的 equals 实现,因此它与列表中的其他元素错误地匹配?
  • 这是可能的,因为 ConfirmCode obj 的 equals() 被覆盖:@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ConfirmCode other = (ConfirmCode) obj; if (actionEnum == null) { if (other.actionEnum != null) return false; } else if (!actionEnum.equals(other.actionEnum)) return false; return true; } 如果有两个返回语句,是否应该这样?它应该只是第一个,然后是返回 true 的 else 吗?
  • 就是这样。如果两个 ConfirmCode 对象具有相同的 actionEnum,则它们被视为相等。这意味着 collect-toMap 仅删除列表的第三个元素。但是当将所有内容收集到一个集合时,equals 方法将只保留一个相等的项目,并且第一个和第二个项目具有相同的 actionEnum。这就是你的第一个项目被丢弃的原因。
  • 我根据这个编辑了答案。顺便说一句:从某些步骤创建结果非常有帮助,特别是如果有一个有用的类型作为地图。可以在之后进行下一次流式收集。
猜你喜欢
  • 1970-01-01
  • 2014-05-09
  • 2011-12-04
  • 1970-01-01
  • 1970-01-01
  • 2016-12-19
  • 2011-03-12
  • 2018-10-10
  • 2017-05-26
相关资源
最近更新 更多