Java HashSet – Java中的HashSet
Java HashSet是Set接口的最受欢迎的实现。
HashMap支持java.util.HashSet。
HashSet扩展了AbstractSet类,并实现Set,Cloneable和Serializable接口。
Java HashSet
Java中有关HashSet的一些重要要点是:
HashSet不允许重复输入。
HashSet允许将null作为值。
HashSet不保证元素的插入顺序。
HashSet不是线程安全的。
您可以使用Collections.synchronizedSet
方法获取线程安全的HashSet,但会降低性能。
您也可以使用CopyOnWriteArraySet
并发类来确保线程安全。HashSet迭代器方法快速失败。
因此,在创建迭代器之后对该集合进行的任何结构修改都将引发ConcurrentModificationException。HashSet支持泛型,这是在运行时避免ClassCastException的推荐方法。
HashSet使用HashMap来存储元素,因此对象应提供hashCode()和equals()方法的良好实现,以避免产生不必要的结果。
Java HashSet构造函数
Java HashSet提供了四个构造函数。
public HashSet():创建一个新的空HashSet,使用默认的初始容量为16且负载因子为0.75初始化后备HashMap。
public HashSet(int initialCapacity):创建一个空的HashSet,并使用指定的容量和负载系数0.75初始化后备HashMap。
public HashSet(int initialCapacity,float loadFactor):创建一个空HashSet,并使用指定的容量和指定的加载因子初始化后备HashMap。
public HashSet(Collection <?extends E> c):创建一个包含指定集合中元素的新Set。
使用默认的加载因子(0.75)和足以容纳指定集合中所有元素的初始容量创建后备HashMap。
下面的代码片段显示了所有这些HashSet构造函数示例用法。
Set<String> set = new HashSet<>(); //initial capacity should be power of 2 set = new HashSet<>(32); //setting backing HashMap initial capacity and load factor set = new HashSet<>(32, 0.80f); //creating HashSet from another Collection Set<String> set1 = new HashSet<>(set); Set<String> set2 = new HashSet<>(new ArrayList<>());
Java HashSet方法
一些有用的HashSet方法是:
public boolean add(E e):如果给定元素不存在,则将其添加到Set中。
此方法在内部使用equals()方法检查重复项,因此请确保您的对象正确定义了equals()方法。public void clear():从集合中删除所有元素。
public Object clone():返回Set实例的浅表副本。
public boolean contains(Object o):如果Set包含给定元素,则返回true,否则返回false。
public boolean isEmpty():如果Set不包含任何元素,则返回true,否则返回false。
public Iterator <E> iterator():返回对此集合中的元素进行迭代的迭代器。
元素以不特定的顺序返回。public boolean remove(Object o):如果存在此集合中的给定元素,则将其删除并返回true。
如果该元素不存在,则返回false。public int size():返回集合中元素的数量。
public Spliterator <E> splitter():在此集合中的元素上创建后绑定和故障快速的Spliterator。
这是Java 8中引入的,但是到目前为止我还没有使用过。public boolean removeAll(Collection <?> c):HashSet从AbstractSet继承此方法。
此方法将删除集合中属于指定集合的所有元素。
Java HashSet示例
Java HashSet示例程序,显示了Java中HashSet的常见用法。
package com.theitroad.examples; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; public class HashSetExample { public static void main(String[] args) { Set<String> fruits = new HashSet<>(); //add example fruits.add("Apple"); fruits.add("Banana"); //isEmpty example System.out.println("fruits set is empty = "+fruits.isEmpty()); //contains example System.out.println("fruits contains Apple = "+fruits.contains("Apple")); System.out.println("fruits contains Mango = "+fruits.contains("Mango")); //remove example System.out.println("Apple removed from fruits set = "+fruits.remove("Apple")); System.out.println("Mango removed from fruits set = "+fruits.remove("Mango")); //size example System.out.println("fruits set size = "+fruits.size()); //addAll example List<String> list = new ArrayList<>(); list.add("Apple"); list.add("Apple"); list.add("Banana"); list.add("Mango"); System.out.println("fruits set before addAll = "+fruits); System.out.println("list = "+list); fruits.addAll(list); System.out.println("fruits set after addAll = "+fruits); //iterator example Iterator<String> iterator = fruits.iterator(); while(iterator.hasNext()){ System.out.println("Consuming fruit "+iterator.next()); } //removeAll example fruits.add("Orange"); System.out.println("fruits set before removeAll = "+fruits); System.out.println("list = "+list); fruits.removeAll(list); System.out.println("fruits set after removeAll = "+fruits); //clear example fruits.clear(); System.out.println("fruits set is empty = "+fruits.isEmpty()); } }
上面的HashSet示例程序的输出如下所示,由于它们是自理解的,因此我不解释它们。
fruits set is empty = false fruits contains Apple = true fruits contains Mango = false Apple removed from fruits set = true Mango removed from fruits set = false fruits set size = 1 fruits set before addAll = [Banana] list = [Apple, Apple, Banana, Mango] fruits set after addAll = [Apple, Mango, Banana] Consuming fruit Apple Consuming fruit Mango Consuming fruit Banana fruits set before removeAll = [Apple, Mango, Orange, Banana] list = [Apple, Apple, Banana, Mango] fruits set after removeAll = [Orange] fruits set is empty = true
Java HashSet ConcurrentModificationException示例
Java HashSet迭代器是快速失败的,因此,如果对Set进行结构上的修改,它的方法将抛出" java.util.ConcurrentModificationException"。
下面是一个简单的示例来说明这一点。
package com.theitroad.examples; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class HashSetConcurrentModificationExceptionExample { public static void main(String[] args) { Set<String> fruits = new HashSet<>(); //add example fruits.add("Apple"); fruits.add("Banana"); fruits.add("Orange"); fruits.add("Mango"); Iterator<String> iterator = fruits.iterator(); while(iterator.hasNext()){ String fruit = iterator.next(); System.out.println("Processing "+fruit); //wrong way of removing from Set, can throw java.util.ConcurrentModificationException if("Orange".equals(fruit)) fruits.remove("Orange"); } } }
执行上面的程序时,我得到下面的输出和异常。
Processing Apple Processing Mango Processing Orange Exception in thread "main" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429) at java.util.HashMap$KeyIterator.next(HashMap.java:1453) at com.theitroad.examples.HashSetConcurrentModificationExceptionExample.main(HashSetConcurrentModificationExceptionExample.java:21)
注意,不能保证HashSet元素是有序的,并且iterator.next()
调用会抛出ConcurrentModificationException。
因此,如果"橙色"是迭代器中的最后一个,则不会出现异常,因为iterator.hasNext()
将返回false,而不会调用iterator.next()
。
我们应该始终使用Iterator方法进行结构修改,如下面的示例代码所示。
package com.theitroad.examples; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class HashSetConcurrentModificationExceptionExample { public static void main(String[] args) { Set<String> fruits = new HashSet<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.add("Orange"); fruits.add("Mango"); Iterator<String> iterator = fruits.iterator(); while(iterator.hasNext()){ String fruit = iterator.next(); System.out.println("Processing "+fruit); //correct way of structural modification of Set if("Orange".equals(fruit)) iterator.remove(); } System.out.println("fruits set after iteration = "+fruits); } }
上面的HashSet迭代器示例不会引发异常,您将得到下面的输出。
Processing Apple Processing Mango Processing Orange Processing Banana fruits set after iteration = [Apple, Mango, Banana]
Java HashSet到数组示例
有时我们必须将HashSet转换为array,反之亦然。
下面是一个简单的程序,显示了将HashSet转换为array,然后将Array转换为HashSet的正确方法。
package com.theitroad.examples; import java.util.Arrays; import java.util.HashSet; import java.util.Set; public class HashSetToArrayExample { public static void main(String[] args) { Set<Integer> ints = new HashSet<>(); for(int i=0; i<10; i++){ ints.add(i); } System.out.println("ints set = "+ints); //set to array example Integer[] intArray = new Integer[ints.size()]; intArray = ints.toArray(intArray); System.out.println("intArray = "+Arrays.toString(intArray)); ints.remove(0);ints.remove(1); System.out.println("intArray = "+Arrays.toString(intArray)); //array to set example ints = new HashSet<>(Arrays.asList(intArray)); System.out.println("ints from array = "+ints); ints.remove(0);ints.remove(1); System.out.println("ints from array after remove = "+ints); System.out.println("intArray = "+Arrays.toString(intArray)); } }
以上HashSet的输出到数组示例为;
ints set = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] intArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] intArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ints from array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ints from array after remove = [2, 3, 4, 5, 6, 7, 8, 9] intArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Java HashSet列出示例
Set和List之间没有太大区别,但是有时我们必须从Set转换为List或者List转换为Set。
下面是一个简单的示例,显示了在Java中将Set转换为List然后将List转换为Set的正确方法。
package com.theitroad.examples; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public class HashSetToListExample { public static void main(String[] args) { Set<String> vowels = new HashSet<>(); vowels.add("a"); vowels.add("e"); vowels.add("i"); //set to list example List<String> vowelsList = new ArrayList<>(vowels); System.out.println("vowels set = "+vowels); System.out.println("vowelsList = "+vowelsList); vowels.add("o"); vowelsList.add("a");vowelsList.add("u"); System.out.println("vowels set = "+vowels); System.out.println("vowelsList = "+vowelsList); //list to set example vowels = new HashSet<>(vowelsList); System.out.println("vowels set = "+vowels); } }
上面的java Set to List示例程序的输出是;
vowels set = [a, e, i] vowelsList = [a, e, i] vowels set = [a, e, i, o] vowelsList = [a, e, i, a, u] vowels set = [a, e, u, i]
Java HashSet equals()和hashCode()方法
HashSet利用HashMap来存储其元素。
当您尝试添加元素时,HashSet与equals()和hashCode()方法一起使用以检查重复的元素。
让我们看看如果您的Set对象没有提供equals()方法的实现会发生什么。
package com.theitroad.examples; import java.util.HashSet; import java.util.Set; public class HashSetEqualsMethodImportance { public static void main(String[] args) { Set<Emp> emps = new HashSet<>(); emps.add(new Emp(1,"hyman")); emps.add(new Emp(2, "David")); emps.add(new Emp(1, "hyman")); System.out.println(emps); } } class Emp { private String name; private int id; public Emp(int i, String n) { this.id = i; this.name = n; } @Override public String toString(){ return "{"+id+","+name+"}"; } }
当我们在程序上方运行时,将在Set元素的输出下方得到结果。
[{2,David}, {1,hyman}, {1,hyman}]
因此,看起来我们能够在Set中存储重复的元素。
实际上不是这样,因为Emp类没有定义equals()方法,所以使用Object类的equals()方法实现。
对象类定义如下的equals()方法。
public boolean equals(Object obj) { return (this == obj); }
因此,在添加新元素时,将检查对象引用而不是内容。
因此,我们的对象具有重复的内容,但是它们具有不同的引用。
让我们看看在Emp类中定义hashCode()和equals()方法时会发生什么。
package com.theitroad.examples; import java.util.HashSet; import java.util.Set; public class HashSetEqualsMethodImportance { public static void main(String[] args) { Set<Emp> emps = new HashSet<>(); emps.add(new Emp(1,"hyman")); emps.add(new Emp(2, "David")); emps.add(new Emp(1, "hyman")); System.out.println(emps); Emp e = new Emp(3, "Lisa"); emps.add(e); System.out.println(emps); //set values to make it duplicate e.setId(1); System.out.println(emps); e.setName("hyman"); System.out.println(emps); } } class Emp { private String name; private int id; public Emp(int i, String n) { this.setId(i); this.setName(n); } @Override public boolean equals(Object obj){ if(obj == null || !(obj instanceof Emp)) return false; Emp e = (Emp) obj; if(e.getId() == this.getId() && this.getName().equals(e.getName())) return true; return false; } @Override public int hashCode(){ return getId(); } @Override public String toString(){ return "{"+getId()+","+getName()+"}"; } public String getName() { return name; } public int getId() { return id; } public void setName(String name) { this.name = name; } public void setId(int id) { this.id = id; } }
这次我们的程序产生以下输出。
[{1,hyman}, {2,David}] [{1,hyman}, {2,David}, {3,Lisa}] [{1,hyman}, {2,David}, {1,Lisa}] [{1,hyman}, {2,David}, {1,hyman}]
请注意,当我们尝试添加元素时,HashSet能够检查重复项。
但是我们可以使用setter方法更改对象值并使之重复。
之所以起作用,是因为对Set没有执行任何操作。
这就是为什么不可变对象与Set和Map更好地工作的原因。