做电商网站一个常见的需求就是:
我要将商品添加购物车。
可是我们添加的商品间存在重复的可能性,如果只写一个添加的方法,那么同一个商品可能会在购物车中被添加两次。
如:商品A 数量1
商品A 数量1
这明显不是我们想要的,我们希望的是,如果购物车内已有一个商品,那么再次添加就应该让该购物车内商品的数量增加。
如:商品A 数量2
那怎么实现这个功能呢?
一般情况下,我们会定义一个商品对象:
public class Product {
private int id;
private String name;//商品名
private double price;//价格
public Product(int id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
}
那么思路是只要在添加的时候判断添加的商品和购物车内的商品是否相等即可。
Product pro1 = new Product(1,"书1",100);//假设为要添加的商品
Product pro2 = new Product(1,"书1",100);//假设为购物车内的商品
Map<Product,Integer> cart = new HashMap<>();//key为产品,value为数量
cart.put(pro1, 1);
if(pro2==pro1){
cart.put(pro2, (cart.get(pro2)+1));
}else{
cart.put(pro2, 1);
}
map会自动进行去重,理论上是如果pro2==pro1,那么只会存在一个键pro1,并且值的数量+1.
不过可惜的是,输出一下pro1的值我们发现仍为1。
那就说明cart.put(pro2, (cart.get(pro2)+1))的语句没有执行。
唯一的可能便是,pro1==pro2的条件不成立。
可是pro1明明和pro2的所有属性都相等啊!
所以是什么原因?
这就得从"=="所比较的东西说起了。
在比较两个对象时,==并不会把你对象中所有的字段逐个进行比较,而是会比较二者的地址值。
我们这里的两个对象都是通过new进行创建,也就是说在堆中开辟了两块不同的位置,这两块区域有着各自不同的地址值。
所以说两个对象指向堆中的地址也不一样。
那么通过==自然是无法完成我们比较是否相等的需求了。
所以该怎么办?
这时候就需要重写hashCode和equals方法了。
hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值,底层由一套hash算法实现。
这边摘录一段HashCode底层算法。
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
这里的Object a[]代表的是我们这个类所有的成员变量的数组。
通过将类中所有成员变量传入hashCode方法,并且调用这个成员变量的HashCode方法计算出一个值,最终再加上31*此前累加的结果得到最终的结果。
这套算法在大多数不同的对象面前,算出的结果都不会相同。
可是毕竟结果只是一个int数值,而int数值的范围是-2147483648——2147483647,而对象的组合种类的总数肯定远远不止这个值,虽然概率不大,但其中必然会产生重复的情况。
比如如下情况:
Product pro1 = new Product(1,"Aa",100);
Product pro2 = new Product(1,"BB",100);
System.out.println(pro1.hashCode());
System.out.println(pro2.hashCode());
二者的HashCode皆为32,可他们的name属性并不相等。
因此仅依靠比较HashCode就断定两个对象相等,似乎有些武断。
这时候equals方法就要登场了。
equals的实现思路是:通过某个特征值来判断两个对象是否“等价”,当这两个对象等价时,判断结果为true,否则结果为false。
那如果说我们如此重写equals方法:
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return id == product.id &&
Double.compare(product.price, price) == 0 &&
pnum == product.pnum &&
Objects.equals(name, product.name);
}
这个方法的思路是:首先比较二者的地址,如果地址相等,那两者必然相等,无需继续比较。
然后比较二者的类型,如果类型不同那二者必然不同,可直接返回false。
上述两项比较完成后,将传入的对象转换成对应的类型。
最后进行逐个属性的比较,如若全部相等,返回true,不等则返回false。
用这个方法,之前的Aa,BB的问题就不复存在了。
因为它会对name字段进行比较,因为二者不相等,所以会直接返回false。
那么我们可以由此总结出一些规律。
-equals()为true 则hashcode()一定相同
-hashcode()相同 则equals()不一定为true
-equals()/hashcode()可以重写,根据业务逻辑比对指定内容.而==不可重写
那为什么在实际程序中,我们要同时重写两个方法呢?
是为了遵守JDK的契约
JDK内部,尤其集合,很多判重操作都是hashcode与equals的双重判断。
因此为了保持契约,我们必须同时重写两个方法。
那么有了这两个方法,开头的购物车问题也就迎刃而解。
Map<Product,Integer> cart = new HashMap<>();
Product pro1 = new Product(1,"书1",100);//假设为要添加的商品
Product pro2 = new Product(1,"书1",100);//假设为购物车内的商品
cart.put(pro1, 1);
if(cart.containsKey(pro2)){
cart.put(pro2, (cart.get(pro2)+1));
}else{
cart.put(pro2, 1);
}
因为判断pro2和pro1已经相等,因此会自动将原本pro1的value覆盖,再次调用pro1得到的结果就是2了!
问题解决!