Java HashSet – Java中的HashSet

时间:2020-02-23 14:36:37  来源:igfitidea点击:

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更好地工作的原因。