Map.get()的逻辑判断

本文深入探讨了HashMap在Java中如何判断两个对象是否相等。通过分析HashMap.getEntry()方法源码,揭示了对象的hash值和equals方法在判等过程中的作用,并提供了具体的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

假设一个map里放有两个不同的对象,但这两个对象某些属性相同,从程序员的逻辑角度上来看可以认为是相同的。那该如何做呢?

一. 源码

首先来看HashMap.getEntry()的源码

    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

二. 分析

从getEntry()可以看出,判断时会遍历map中所有的entry,当以下两个条件都满足时才会判断相等:
  • hash值一样;
  • key为同一个对象,或两个key使用其equals()方法比较时相等。

因此在使用map.get(key)时
  • 要么key为同一个对象;
  • 要么key类要有自己的public int hashCode()及public boolean equals(Object obj)方法,使其相等。

三. 示例

@Test
public void get() {
	Person person1 = new Person(1, "test");
	Map<Person, String> map = new HashMap<Person, String>();
	map.put(person1, "hello,world");
	logger.debug(map.get(new Person(1, "test")));

}

class Person {
	private int id;
	private String name;

	public Person(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj instanceof Person) {
			return (this.id == ((Person) obj).id)
					&& (this.name.equals(((Person) obj).name));
		}
		return false;
	}

	public int hashCode() {
		return this.toString().hashCode();
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + "]";
	}

}


<think>我们需要将给定的for循环使用Iterator遍历并移除元素的方式,改写为使用Collection的removeIf方法。 原代码逻辑:遍历insMapsList(一个包含Map<String, Object>的集合),根据一系列条件判断,如果满足条件则移除当前元素。 改写步骤: 1. 使用removeIf方法,它接受一个Predicate(条件判断)作为参数,当条件为true时移除元素。 2. 将原if条件中的逻辑提取为一个Predicate,即当条件成立时,返回true(表示需要移除)。 注意:原条件是一个复杂的逻辑或(||)组合,有多个条件,只要满足其中一个就移除。 原条件: if("SBLOCK".equals((String)map.get("moveTyp"))||"CNL".equals(map.get("status"))||"ACT".equals(map.get("status")) ||"ERR".equals(map.get("status"))||"REJ".equals(map.get("status"))||!bestMoveTyp.equals(map.get("moveTyp")) || (wlTruckBlockNo.equals((String)map.get("blockFrom"))&&!wlTruckBayNo.equals(map.get("bayFrom"))) ||"COM".equals(map.get("status"))||"MCOM".equals(map.get("status")) ||"0".equals(checkContainerStowFactor(bestMap.get("cntr").toString(),map.get("cntr").toString())) ||(wlTruckBayNo.equals(otherMachBay)) 注意:其中有些条件可能涉及外部变量(如bestMoveTyp, wlTruckBlockNo, wlTruckBayNo, bestMap, otherMachBay等),在Lambda表达式中可以直接使用这些变量(但要求它们是final或等效final)。 改写后的代码: insMapsList.removeIf(map -> "SBLOCK".equals(map.get("moveTyp")) || "CNL".equals(map.get("status")) || "ACT".equals(map.get("status")) || "ERR".equals(map.get("status")) || "REJ".equals(map.get("status")) || !bestMoveTyp.equals(map.get("moveTyp")) || (wlTruckBlockNo.equals(map.get("blockFrom")) && !wlTruckBayNo.equals(map.get("bayFrom"))) || "COM".equals(map.get("status")) || "MCOM".equals(map.get("status")) || "0".equals(checkContainerStowFactor(bestMap.get("cntr").toString(), map.get("cntr").toString())) || wlTruckBayNo.equals(otherMachBay) ); 注意:原代码中有一个条件:`(wlTruckBlockNo.equals((String)map.get("blockFrom"))&&!wlTruckBayNo.equals(map.get("bayFrom")))`,这里我们假设map.get("blockFrom")返回的是String类型(原代码进行了强制转换),而在Lambda中,由于map.get("blockFrom")返回的是Object,我们需要确保比较时类型正确。这里我们可以保持原样,因为equals方法会处理类型不匹配的情况(返回false),但为了安全,最好进行类型转换(如果确定是String)或者使用String.valueOf。不过原代码强制转换为String,所以我们在Lambda中也可以做同样的转换,或者如果确定是String,也可以直接使用equals,因为如果map.get("blockFrom")不是String,那么equals会返回false(因为wlTruckBlockNo是String)。但是,如果map.get("blockFrom")为null,那么调用equals会抛出NullPointerException。原代码也有同样的问题。 另外,注意条件`wlTruckBayNo.equals(otherMachBay)`,这里otherMachBay是一个外部变量,注意其含义。 但是,我们注意到原代码中有一个条件:`!wlTruckBayNo.equals(map.get("bayFrom"))`,这里比较的是map.get("bayFrom")(一个Object)和wlTruckBayNo(假设是String)。同样,如果map.get("bayFrom")不是String,那么equals会返回false。如果为null,则equals会返回false(因为wlTruckBayNo肯定不为null?)或者抛出异常?需要根据实际情况判断。 为了安全,我们可以考虑对可能为null的值进行null检查,但原代码没有做,所以我们按照原逻辑。 另外,原代码中有一个条件:`"0".equals(checkContainerStowFactor(...))`,这个函数返回一个字符串,然后与"0"比较,所以没有问题。 最后,注意原代码中有一行日志记录被移除的元素。在removeIf中,我们无法在移除的同时记录日志。因此,我们需要在移除之前先记录符合条件的元素,或者使用一个临时列表收集要移除的元素,然后记录日志后再移除。但是,removeIf是一次性操作,无法在条件中插入日志。 因此,如果需要保留日志,我们可以先遍历一遍,记录要移除的元素,然后再用removeIf移除(或者用removeAll)。但这样会多遍历一次。或者我们可以使用stream的filter,但这里要求修改原集合,所以removeIf更合适。 考虑到原代码是在迭代过程中每移除一个元素就记录一条日志,我们可以这样改写: 方案1:先记录日志,再移除 我们可以先创建一个临时列表,用于存放需要移除的元素,然后遍历这个临时列表记录日志,最后再移除。但是这样需要额外的空间。 方案2:使用forEach和removeIf分开,但这样需要遍历两次(一次记录日志,一次移除)。但注意,在记录日志时,我们实际上已经知道哪些元素会被移除,所以可以在removeIf之前,先根据条件找出所有要移除的元素,记录日志,然后执行removeIf。 但是,由于条件比较复杂,为了避免重复计算,我们可以这样做: List<Map<String, Object>> toRemove = insMapsList.stream() .filter(map -> 条件表达式) .collect(Collectors.toList()); // 记录日志 toRemove.forEach(map -> { logUtils.setLogger(...); logger.info(...); }); insMapsList.removeAll(toRemove); 但是,removeAll会移除所有在toRemove中的元素,而原条件中可能有重复?但集合中每个元素是唯一的(根据引用),所以可以。 但是,原代码是边迭代边移除,不会先收集。如果集合很大,可能会占用较多内存。 方案3:如果不想使用额外空间,可以保留迭代器方式,因为removeIf无法在条件中插入日志。所以,如果必须保留日志,迭代器方式可能是更好的选择。 但是,问题要求用removeIf改写,并且原代码有日志,我们需要考虑日志如何处理? 回顾问题:用户要求用Collection.removeIf()改写,所以我们需要优先满足这个要求。但是原代码中的日志记录也是重要部分。 我们可以这样:在removeIf之前,先遍历列表,将满足条件的元素记录日志,然后再执行removeIf。这样需要遍历两次,但代码清晰。 改写后的代码: // 先记录日志 insMapsList.stream() .filter(map -> 条件表达式) // 和removeIf的条件一样 .forEach(map -> { logUtils.setLogger((String)bestMap.get("jobId"),"","INFO","optimalFind", "最优指令查找","不符合参加找优条件job"+map.get("jobId"),map.toString()); logger.info("不符合参加找优条件job:{}",(String)map.get("jobId")); }); // 然后移除 insMapsList.removeIf(map -> 条件表达式); 这样条件表达式重复了两次,我们可以将Predicate定义为一个变量,避免重复。 最终代码: Predicate<Map<String, Object>> shouldRemove = map -> "SBLOCK".equals(map.get("moveTyp")) || "CNL".equals(map.get("status")) || "ACT".equals(map.get("status")) || "ERR".equals(map.get("status")) || "REJ".equals(map.get("status")) || !bestMoveTyp.equals(map.get("moveTyp")) || (wlTruckBlockNo.equals(map.get("blockFrom")) && !wlTruckBayNo.equals(map.get("bayFrom"))) || "COM".equals(map.get("status")) || "MCOM".equals(map.get("status")) || "0".equals(checkContainerStowFactor(bestMap.get("cntr").toString(), map.get("cntr").toString())) || wlTruckBayNo.equals(otherMachBay); // 记录日志 insMapsList.stream() .filter(shouldRemove) .forEach(map -> { logUtils.setLogger((String)bestMap.get("jobId"),"","INFO","optimalFind", "最优指令查找","不符合参加找优条件job"+map.get("jobId"),map.toString()); logger.info("不符合参加找优条件job:{}",(String)map.get("jobId")); }); // 移除 insMapsList.removeIf(shouldRemove); 这样,我们使用同一个Predicate,避免了重复代码。 注意:在日志记录中,我们使用了bestMap.get("jobId"),注意bestMap在循环中是否变化?原代码中bestMap是外部变量,在循环中没有变化,所以可以。 另外,原代码中条件`wlTruckBayNo.equals(otherMachBay)`,这里otherMachBay是外部变量,注意它是否在循环中变化?如果没有变化,则没有问题。 但是,我们注意到原代码中有一个条件:`!bestMoveTyp.equals(map.get("moveTyp"))`,这里bestMoveTyp也是外部变量。 因此,我们假设这些外部变量在遍历过程中不变。 总结:改写后的代码使用removeIf,同时为了记录日志,我们先使用同一个条件过滤出要移除的元素并记录日志,然后移除。 但是,如果用户不需要记录日志,那么直接removeIf即可。但原代码有日志,所以我们必须保留。 因此,我们按照上述方式改写。 注意:原代码中,条件`wlTruckBayNo.equals(otherMachBay)`,这里otherMachBay是什么?在原代码中,它是定义在循环外部的一个变量。我们假设它是常量或不变。 另外,原代码中条件`(wlTruckBlockNo.equals((String)map.get("blockFrom"))&&!wlTruckBayNo.equals(map.get("bayFrom")))`,我们在Lambda中直接使用,注意map.get("blockFrom")需要转换为String?因为wlTruckBlockNo是String,所以如果map.get("blockFrom")不是String,那么equals会返回false。但是,原代码进行了强制转换,所以如果map.get("blockFrom")不是String,原代码会抛出ClassCastException。因此,我们也要保证类型,或者修改为安全的比较。但为了和原代码一致,我们不做修改。 改写后的完整代码: // 定义条件Predicate Predicate<Map<String, Object>> removalCondition = map -> "SBLOCK".equals(map.get("moveTyp")) || "CNL".equals(map.get("status")) || "ACT".equals(map.get("status")) || "ERR".equals(map.get("status")) || "REJ".equals(map.get("status")) || !bestMoveTyp.equals(map.get("moveTyp")) || (wlTruckBlockNo.equals(map.get("blockFrom")) && !wlTruckBayNo.equals(map.get("bayFrom"))) || "COM".equals(map.get("status")) || "MCOM".equals(map.get("status")) || "0".equals(checkContainerStowFactor(bestMap.get("cntr").toString(), map.get("cntr").toString())) || wlTruckBayNo.equals(otherMachBay); // 记录不符合条件的元素日志 insMapsList.stream() .filter(removalCondition) .forEach(map -> { String jobId = (String) map.get("jobId"); logUtils.setLogger((String)bestMap.get("jobId"), "", "INFO", "optimalFind", "最优指令查找", "不符合参加找优条件job" + jobId, map.toString()); logger.info("不符合参加找优条件job:{}", jobId); }); // 执行移除 insMapsList.removeIf(removalCondition); 注意:这里我们使用了两次同一个Predicate,确保条件一致。 另外,原代码中,日志记录使用了bestMap.get("jobId")作为第一个参数,我们保持不变。 这样改写后,逻辑与原代码一致。 但是,注意原代码中条件`wlTruckBayNo.equals(otherMachBay)`,这里otherMachBay在循环中是否变化?如果变化,那么两次使用同一个Predicate时,otherMachBay的值必须不变。根据原代码,它是在循环外部定义的,所以应该不变。 最后,如果insMapsList是ArrayList,那么removeIf是高效的(内部使用批量移除)。如果是LinkedList,则效率较低(需要遍历)。但原代码使用迭代器,对于LinkedList也是高效的。不过,我们这里改写后,对于LinkedList,removeIf会使用迭代器方式,所以效率相当。 因此,我们这样改写。</think>以下是用 `Collection.removeIf()` 改写后的代码及详细说明: ### 改写后的代码 ```java // 定义移除条件的 Predicate Predicate<Map<String, Object>> removalCondition = map -> "SBLOCK".equals(map.get("moveTyp")) || "CNL".equals(map.get("status")) || "ACT".equals(map.get("status")) || "ERR".equals(map.get("status")) || "REJ".equals(map.get("status")) || !bestMoveTyp.equals(map.get("moveTyp")) || (wlTruckBlockNo.equals(map.get("blockFrom")) && !wlTruckBayNo.equals(map.get("bayFrom"))) || "COM".equals(map.get("status")) || "MCOM".equals(map.get("status")) || "0".equals(checkContainerStowFactor(bestMap.get("cntr").toString(), map.get("cntr").toString())) || wlTruckBayNo.equals(otherMachBay); // 记录日志并执行移除 insMapsList.stream() .filter(removalCondition) .forEach(map -> { logUtils.setLogger( (String) bestMap.get("jobId"), "", "INFO", "optimalFind", "最优指令查找", "不符合参加找优条件job" + map.get("jobId"), map.toString() ); logger.info("不符合参加找优条件job:{}", (String) map.get("jobId")); }); insMapsList.removeIf(removalCondition); // 核心移除操作 ``` ### 关键改写说明 1. **使用 Predicate 封装条件** 将复杂的 `if` 条件抽象为 `Predicate<Map<String, Object>>` 对象,提高可读性和复用性。 2. **分离日志记录与移除操作** - 先用 `stream().filter()` 筛选出需移除的元素 - 在 `forEach` 中记录日志 - 最后通过 `removeIf()` 批量移除 3. **类型安全优化** 移除冗余的 `(String)` 强制转换(`map.get()` 返回 `Object` 可直接调用 `equals`) 4. **保持原逻辑不变** 完全保留原始条件的布尔逻辑(OR 组合),仅重构实现方式 ### 优势对比 | 原迭代器方式 | removeIf() 方式 | |------------|----------------| | 需手动管理 `Iterator` 对象 | 无需显式迭代器 | | 每次循环执行条件判断 | 条件逻辑集中封装 | | 移除时需调用 `it.remove()` | 自动批量移除 | | 日志与移除耦合 | 日志与移除解耦 | ### 注意事项 1. 确保 `insMapsList` 是支持 `removeIf` 的可变集合(如 `ArrayList`) 2. 外部变量(如 `bestMoveTyp`, `wlTruckBlockNo` 等)需为 `final` 或等效 final 3. 复杂条件建议拆分为辅助方法,例如: ```java private boolean isInvalidStatus(Map<String, Object> map) { String status = (String) map.get("status"); return Stream.of("CNL", "ACT", "ERR", "REJ", "COM", "MCOM").anyMatch(status::equals); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值