关于Java中使用容器的几个注意点!?

本文详细探讨了在Java中使用HashSet时,元素类型未正确重写equals和hashCode方法导致的问题。通过具体测试案例展示了不同情况下HashSet的行为差异,强调了正确实现这两个方法的重要性。

在看老代码时,看到一处使用HashSet的场景,检查了放入HashSet的类型参数,发现这个类型并没有重写equals和hashCode方法,这个后果的严重程度可想而知。就此暂时总结了以下几点,并配合测试代码,共勉!

总结点如下:
1. 使用HashSet/HahsMap时,定义的元素/key的类型必须同时重写equals和hashCode方法。

  1. TreeSet来说,只需实现Comparable接口,不需要重写equals和hashCode方法,至少java6是这样

  2. 对其他容器(无hash),建议都重写equals方法,否则无法支持查找和删除操作。比如使用PriorityQueue时,若仅实现Comparable只支持对象的插入,只有在自定义类实现了equals方法后,才支持查找、删除等操作了。

其中,最重要的就是第一条,需谨记。若不确定对象会被如何使用,建议对任何自定义类型重写equals、hashCode和toString方法。

测试代码中示例类型的说明:
BasicType

一个基础的类,包含三个字段,只重写了toString方法
ComparableType

一个可比较的类,只实现了Comparable接口
EqualsType

在BasicType的基础上重写了equals方法
HashCodeType

在BasicType的基础上重写了hashCode方法
EqualsComparableType

在ComparableType的基础上重写了equals方法
HashType

在EqualsType基础上重写了hashCode方法
设计的类图结构:

测试用例
对HashSet的测试
使用BasicType对HashSet进行测试
/**
* 使用{@link BasicType}对HashSet进行测试,对不同对象,equals为false,hashCode不相等
* 因此任一{@link BasicType}的对象在HashSet中都是唯一的,见测试{@link #testBasicTypeInHashSet}
*
* @see BasicType
* @see test.HashTypeTest#testHashTypeInHashSet testHashTypeInHashSet
*/
@Test
public void testBasicTypeInHashSet() {
Set set = new HashSet();
set.add(new BasicType());
set.add(new BasicType());
set.add(new BasicType());
set.add(new BasicType());

    BasicType t = new BasicType();
    set.add(t);
    set.add(t);

    // size为5,不是我们想要的
    Assert.assertEquals(set.size(), 5);
}

使用EqualsType对HashSet进行测试
/**
* 使用{@link EqualsType}对HashSet进行测试,对不同对象,equals可能为true,hashCode不相等
* 任一{@link EqualsType}的对象在HashSet中都是唯一的
*
* @see EqualsType
* @see test.HashTypeTest#testHashTypeInHashSet testHashTypeInHashSet
*/
@Test
public void testEqualsTypeInHashSet() {
Set set = new HashSet();
set.add(new EqualsType());
set.add(new EqualsType());
set.add(new EqualsType());
set.add(new EqualsType());

    EqualsType t = new EqualsType();
    set.add(t);
    set.add(t);

    // size为5,不是我们想要的
    Assert.assertEquals(set.size(), 5);
}

使用HashCodeType对HashSet进行测试
/**
* 使用{@link HashCodeType}对HashSet进行测试,对不同对象,equals可能为false,hashCode可能相等
* 任一{@link HashCodeType}的对象在HashSet中都是唯一的
*
* @see HashCodeType
* @see test.HashTypeTest#testHashTypeInHashSet testHashTypeInHashSet
*/
@Test
public void testHashCodeTypeInHashSet() {
Set set = new HashSet();
set.add(new HashCodeType());
set.add(new HashCodeType());
set.add(new HashCodeType());
set.add(new HashCodeType());

    HashCodeType t = new HashCodeType();
    set.add(t);
    set.add(t);
    // size为5,不是我们想要的
    Assert.assertEquals(set.size(), 5);
}

使用HashType对HashSet进行测试
/**
* 使用{@link HashType}对HashSet进行测试,对不同对象,equals可能为true,hashCode可能相等
* 此时可以用HashSet去除重复对象(hashCode相等且是equals的),正是我们想要的
*
* @see HashType
*/
@Test
public void testHashTypeInHashSet() {
Set set = new HashSet();
set.add(new HashType());
set.add(new HashType());
set.add(new HashType());
set.add(new HashType());

    HashType t = new HashType();
    set.add(t);
    set.add(t);
    // 相等值对象被去除,size为1, 正是我们想要的
    Assert.assertEquals(set.size(), 1);
}

对TreeSet的测试
使用ComparableType对TreeSet进行测试
/**
* 使用ComparableType对TreeSet进行测试
* 使用TreeSet的类必须实现Comparable接口,但不必重写equals和hashCode方法,与TreeSet的内部实现有关
*
* @see tijava.container.type.ComparableType
*/
@Test
public void testComparableTypeInTreeSet() {
Set q = new TreeSet();
q.add(new ComparableType(‘d’, 3, null));
q.add(new ComparableType(‘d’, 4, null));
q.add(new ComparableType());
q.add(new ComparableType());
q.add(new ComparableType());
q.add(new ComparableType());
q.add(new ComparableType());

    Assert.assertEquals(q.size(), 3);
    Assert.assertTrue(q.contains(new ComparableType('d', 3, null)));

    q.remove(new ComparableType('d', 3, null));
    //remove ok
    Assert.assertEquals(q.size(), 2);
}

对PriorityQueue的测试
使用ComparableType对PriorityQueue进行测试
/**
* 使用ComparableType对PriorityQueue进行测试
* 在PriorityQueue中使用自定义类时,若只实现Comparable接口的类型,不支持查找和删除等操作
*
* @see test.EqualsComparableTypeTest#testEqualsComparableTypeInPriorityQueue
* testEqualsComparableTypeInPriorityQueue
*/
@Test
public void testComparableTypeInPriorityQueue() {
Queue q = new PriorityQueue();
q.add(new ComparableType(‘C’, 4, “Empty trash”));
q.add(new ComparableType(‘A’, 2, “Feed dog”));
q.add(new ComparableType(‘B’, 7, “Feed bird”));
q.add(new ComparableType(‘C’, 3, “Mow lawn”));
q.add(new ComparableType(‘A’, 1, “Water lawn”));
q.add(new ComparableType(‘B’, 1, “Feed cat”));

    Assert.assertEquals(q.size(), 6);
    Assert.assertFalse(q
            .contains(new ComparableType('C', 4, "Empty trash")));
    Assert.assertFalse(q.remove(new ComparableType('C', 4, "Empty trash")));

    //siz is still 6, not remove success
    Assert.assertEquals(q.size(), 6);
}

使用EqualsComparableType对PriorityQueue进行测试
/**
* 使用EqualsComparableType对PriorityQueue进行测试
* 在使用PriorityQueue是,在自定义类实现了equals方法后,就支持查找、删除等操作了
*
* @see test.ComparableTypeTest#testComparableTypeInPriorityQueue
* testComparableTypeInPriorityQueue
*/
@Test
public void testEqualsComparableTypeInPriorityQueue() {
Queue q = new PriorityQueue();
q.add(new EqualsComparableType(‘C’, 4, “Empty trash”));
q.add(new EqualsComparableType(‘A’, 2, “Feed dog”));
q.add(new EqualsComparableType(‘B’, 7, “Feed bird”));
q.add(new EqualsComparableType(‘C’, 3, “Mow lawn”));
q.add(new EqualsComparableType(‘A’, 1, “Water lawn”));
q.add(new EqualsComparableType(‘B’, 1, “Feed cat”));

    Assert.assertEquals(q.size(), 6);
    Assert.assertTrue(q.contains(new EqualsComparableType('C', 4,
            "Empty trash")));
    Assert.assertTrue(q.remove(new EqualsComparableType('C', 4,
            "Empty trash")));

    // remove ok
    Assert.assertEquals(q.size(), 5);
}
常见的Java并发容器包括CopyOnWriteArrayList、SynchronousQueue等。 CopyOnWrite是写时复制的容器,通俗理解是往容器添加元素时,先将当前容器复制出一个新容器,在新容器里添加元素,添加完后将原容器的引用指向新容器。这样可对容器进行并发的读,无需加锁,因为当前容器不会添加任何元素,但写时要加锁,它体现了读写分离思想,读和写操作在不同容器。不过读时若有多个线程正在向容器添加数据,读会读到旧数据,只能保证最终一致性。例如Redis中执行bgsave时就用此机制。CopyOnWriteArrayList底层使用ReentrantLock()来实现加锁,印证了AQS占据JUC半壁江山 [^3]。 SynchronousQueue容量为0,不是用来装内容的,而是专门用于两个线程之间传递内容。使用时,一个线程take会阻塞,等待另一个线程put;或者一个线程put会阻塞,等待另一个线程take [^4]。 ```java import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.CopyOnWriteArrayList; public class ConcurrentContainerExample { public static void main(String[] args) { // CopyOnWriteArrayList示例 CopyOnWriteArrayList<String> copyOnWriteList = new CopyOnWriteArrayList<>(); copyOnWriteList.add("element1"); // SynchronousQueue示例 SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>(); try { new Thread(() -> { try { synchronousQueue.put("data"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { String receivedData = synchronousQueue.take(); System.out.println("Received data: " + receivedData); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } catch (Exception e) { e.printStackTrace(); } } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值