什么是集合

集合,集合是java中提供的一种容器,可以用来存储多个数据。

集合和数组的区别

  • 数组的长度是固定的。集合的长度是可变的
  • 集合中存储的元素必须是引用类型数据

存储结构

数据的存储结构

  • 栈结构:后进先出/先进后出(手枪弹夹) FILO (first in last out)
  • 队列结构:先进先出/后进后出(银行排队) FIFO(first in first out)
  • 数组结构:查询快:通过索引快速找到元素 增删慢:每次增删都需要开辟新的数组,将老数组中的元素拷贝到新数组中
  • 链表结构:查询慢:每次都需要从链头或者链尾找起 增删快:只需要修改元素记录的下个元素的地址值即可不需要移动大量元素

哈希表的数据结构

  • 加载因子:表中填入的记录数/哈希表的长度

数组中的16个位置,其中存入16*0.75=12个元素,如果在存入第十三个(>12)元素,导致存储链子过长,会降低哈希表的性能,那么此时会扩充哈希表(在哈希),底层会开辟一个长度为原长度2倍的数组,把老元素拷贝到新数组中,再把新元素添加数组中

当存入元素数量>哈希表长度*加载因子,就要扩容,因此加载因子决定扩容时机

集合继承关系

查看ArrayList类发现它继承了抽象类AbstractList同时实现接口List,而List接口又继承了Collection接口。Collection接口为最顶层集合接口了。

interface List extends Collection {
}
public class ArrayList extends AbstractList implements List{
}

这说明我们在使用ArrayList类时,该类已经把所有抽象方法进行了重写。那么,实现Collection接口的所有子类都会进行方法重写

                              Collection 接口     
                                   |
     ----------------------------------------------------------------
     |                                                              |
    List接口                                                       Set接口
     |                                                              |
 ----------------                                             -------------
 |              |                                             |            |
ArrayList类    LinkedList类                                 HashSet类     LinkedHashSet类

集合Collection

  • Collection接口中的方法
  • 是集合中所有实现类必须拥有的方法
  • 使用Collection接口的实现类,程序的演示
  • ArrayList implements List
  • List extends Collection
  • 方法的执行,都是实现的重写
Modifier and Type Method and Description
boolean add(E e)
确保此集合包含指定的元素(可选操作)。
boolean addAll(Collection c)
将指定集合中的所有元素添加到此集合(可选操作)。
void clear()
从此集合中删除所有元素(可选操作)。
boolean contains(Object o)
如果此集合包含指定的元素,则返回 true
boolean containsAll(Collection c)
如果此集合包含指定 集合中的所有元素,则返回true。
boolean equals(Object o)
将指定的对象与此集合进行比较以获得相等性。
int hashCode()
返回此集合的哈希码值。
boolean isEmpty()
如果此集合不包含元素,则返回 true
Iterator iterator()
返回此集合中的元素的迭代器。
default Stream parallelStream()
返回可能并行的 Stream与此集合作为其来源。
boolean remove(Object o)
从该集合中删除指定元素的单个实例(如果存在)(可选操作)。
boolean removeAll(Collection c)
删除指定集合中包含的所有此集合的元素(可选操作)。
default boolean removeIf(Predicate filter)
删除满足给定谓词的此集合的所有元素。
boolean retainAll(Collection c)
仅保留此集合中包含在指定集合中的元素(可选操作)。
int size()
返回此集合中的元素数。
default Spliterator spliterator()
创建一个Spliterator在这个集合中的元素。
default Stream stream()
返回以此集合作为源的顺序 Stream
Object[] toArray()
返回一个包含此集合中所有元素的数组。
 T[] toArray(T[] a)
返回包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。

toArray()方法 Collection接口方法

Object[] toArray() 集合中的元素,转成一个数组中的元素, 集合转成数组

返回是一个存储对象的数组, 数组存储的数据类型是Object

Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("itcast");
coll.add("itheima");
coll.add("money");
coll.add("123");

Object[] objs = coll.toArray();
for(int i = 0 ; i < objs.length ; i++){
  System.out.println(objs[i]);
}

/*
*   Java中三种长度表现形式
*   数组.length 属性  返回值 int
*   字符串.length() 方法,返回值int
*   集合.size()方法, 返回值int
*/

contains()方法 Collection接口方法

判断对象是否存在于集合中,对象存在返回true

Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("itcast");
coll.add("itheima");
coll.add("money");
coll.add("123");

boolean b = coll.contains("itcast");
System.out.println(b);// true

clear()方法 Collection接口方法

清空集合中的所有元素 集合容器本身依然存在

Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("bcd");
System.out.println(coll);
coll.clear();
System.out.println(coll);

remove()方法 Collection接口方法

boolean remove(Object o)移除集合中指定的元素

Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("money");
coll.add("itcast");
coll.add("itheima");
coll.add("money");
coll.add("123");  
System.out.println(coll);

boolean b = coll.remove("money");
System.out.println(b);
System.out.println(coll);

Iterator迭代器

Modifier and Type Method and Description
default void forEachRemaining(Consumer action)
对每个剩余元素执行给定的操作,直到所有元素都被处理或动作引发异常。
boolean hasNext()
如果迭代具有更多元素,则返回 true
E next()
返回迭代中的下一个元素。
default void remove()
从底层集合中删除此迭代器返回的最后一个元素(可选操作)。

迭代器代码实现部分

Collection<String> coll = new ArrayList<String>();
// 如果ArrayList改成其他Iterator的实现类也可以实现
// 比如HashSet也是Iterator的实现类
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
//迭代器,对集合ArrayList中的元素进行取出

//调用集合的方法iterator()获取出,Iterator接口的实现类的对象
Iterator<String> it = coll.iterator();

//迭代是反复内容,使用循环实现,循环的条件,集合中没元素, hasNext()返回了false
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}    

迭代器的执行过程

//cursor记录的索引值不等于集合的长度返回true,否则返回false
 public boolean hasNext() {       
   return cursor != size; //cursor初值为0
 }

//next()方法作用:
//①返回cursor指向的当前元素 
//②cursor++
public Object next() {            
         int i = cursor; 
         cursor = i + 1;  
         return  elementData[lastRet = i]; 
}

while循环和for循环迭代写法

Collection<String> coll = new ArrayList<String>();
coll.add("abc1");
coll.add("abc2");
Iterator<String> it = coll.iterator();
Iterator<String> it2 = coll.iterator();
while(it.hasNext()) {
    System.out.println(it.next());
}

for (Iterator<String> it2 = coll.iterator(); it2.hasNext();  ) {
 System.out.println(it2.next());
}

集合迭代中的转型

在使用集合时,我们需要注意以下几点

  • 集合中存储其实都是对象的地址
  • jdk1.5版本以后可以存储基本数值,因为出现了基本类型包装类,它提供了自动装箱操作,所以集合中的元素就是基本数值的包装类对象

如果集合没有声明类型默认就是Object类型可以存储任何类型的对象,取出时要使用元素的特有内容,必须向下转型,如果集合中存放的是多个对象,这时进行向下转型可能会发生类型转换异常

 Collection coll = new ArrayList();
 coll.add("abc");
 coll.add("aabbcc");
 coll.add("shitcast");
 Iterator it = coll.iterator();
 while (it.hasNext()) {
  //由于元素被存放进集合后全部被提升为Object类型
 //当需要使用子类对象特有方法时,需要向下转型
  String str = (String) it.next();
  System.out.println(str.length());
 }

Iterator接口也可以使用<>来控制迭代元素的类型的

Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("aabbcc");
coll.add("shitcast");
Iterator<String> it = coll.iterator();
while (it.hasNext()) {
    String str =  it.next(); 
//当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
System.out.println(str.length());
}

迭代器的并发修改异常

迭代器的并发修改异常 java.util.ConcurrentModificationException ,在遍历的过程中,使用了集合方法修改了集合的长度导致异常

解决办法

  • 在迭代时,不要使用集合的方法操作元素。
  • 通过ListIterator迭代器操作元素是可以的,ListIterator的出现,解决了使用Iterator迭代过程中可能会发生的错误情况。
List<String> list = new ArrayList<String>();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");

//对集合使用迭代器进行获取,获取时候判断集合中是否存在 "abc3"对象
//如果有,添加一个元素 "ABC3"
Iterator<String> it = list.iterator();
while(it.hasNext()){
  String s = it.next();
  //对获取出的元素s,进行判断,是不是有"abc3"
  if(s.equals("abc3")){
    list.add("ABC3");
  }
  System.out.println(s);

增强for循环

JDK1.5版本后,出现新的接口 java.lang.Iterable Collection开是继承Iterable Iterable作用,实现增强for循环

// 格式
/*
for( 数据类型  变量名 : 数组或者集合 ){
    sop(变量);
} 
*/
// 遍历数组
String[] str = {"abc","itcast","cn"};
for(String s : str){
  System.out.println(s);
}

int[] arr = {3,1,9,0};
for(int i : arr){
  System.out.println(i);
}

// 遍历集合
ArrayList<String> array = new ArrayList<String>();
array.add("a");
array.add("b");
for(String p : array){
    System.out.println(p);
}

泛型

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型的引入

集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。比如下面程序:

ArrayList list = new ArrayList();

//由于集合没有做任何限定,任何类型都可以给其中存放
//相当于:Object obj=new Integer(5);
list.add("abc");
list.add("itcast");
list.add(5);

Iterator it = list.iterator();
while(it.hasNext()){

  //需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
  String str = (String) it.next();

  //编译时期仅检查语法错误,String是Object的儿子可以向下转型
  //运行时期String str=(String)(new Integer(5))
  //String与Integer没有父子关系所以转换失败
  //程序在运行时发生了问题java.lang.ClassCastException
  //String str=(String)obj;

  System.out.println(str.length());
}

泛型的定义和使用

JDK1.5 出现新的安全机制,保证程序的安全性

泛型: 指明了集合中存储数据的类型 <数据类型>

Collection<String> coll = new ArrayList<String>();
coll.add("abc");
coll.add("rtyg");
coll.add("43rt5yhju");
// coll.add(1); // 限定了类型 整型不能通过编译

Iterator<String> it = coll.iterator();
while(it.hasNext()){
  String s = it.next();
  System.out.println(s.length());
}

Java中的伪泛型

泛型只在编译时存在,编译后就被擦除,在编译之前我们就可以限制集合的类型,起到作用

例如:ArrayList al = new ArrayList();

编译后:ArrayList al = new ArrayList();

泛型类

/*
修饰符 class 类名<代表泛型的变量> {  }
*/

// API中的ArrayList集合
class ArrayList<E>{ 
    public boolean add(E e){ }
    public E get(int index){  }
}

// 创建对象时,确定泛型的类型
// 例如,ArrayList<Integer> list = new ArrayList<Integer>();
class ArrayList<String>{ 
    public boolean add(String e){ }
    public String get(int index){  }
}

// 例如,ArrayList<Integer> list = new ArrayList<Integer>();
class ArrayList<Integer>{ 
    public boolean add(Integer e){ }
    public Integer get(int index){  }
}

泛型方法

/*
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){  }
*/

// 把集合元素存储到指定数据类型的数组中,返回已存储集合元素的数组
// public <T> T[] toArray(T[] a){  } 

ArrayList<String> list = new ArrayList<String>();
String[] arr = new String[100];
String[] result = list.toArray(arr);

// 变量T的值就是String类型。变量T,可以与定义集合的泛型不同
// public <String> String[] toArray(String[] a){  } 

泛型接口

// 带有泛型的接口
public interface List <E>{
    abstract boolean add(E e);
}
// 实现类,先实现接口,不理会泛型
public class ArrayList<E> implements List<E>{ }
// 后期创建集合对象的时候,指定数据类型
new ArrayList<String>();
// 实现类,实现接口的同时,也指定了数据类型
public class XXX implements List<String>{ }
new XXX();

泛型的好处

  • 将运行时期的ClassCastException,转移到了编译时期变成了编译失败
  • 避免了类型强转的麻烦
ArrayList<String> list = new ArrayList<String>();
list.add("abc");
list.add("itcast");
//list.add(5);

// 当集合明确类型后,存放类型不一致就会编译报错
// 集合已经明确具体存放的元素类型,那么在使用迭代器的时候
// 迭代器也同样会知道具体遍历元素类型

Iterator<String> it = list.iterator();
while(it.hasNext()){
   String str = it.next();
   System.out.println(str.length()); 
}
// 当使用Iterator<String>
// 控制元素类型后,不需要强转,获取到的元素直接就是String类型

泛型的通配符

public static void main(String[] args) {
  ArrayList<String> array = new ArrayList<String>();

  HashSet<Integer> set = new HashSet<Integer>();

  array.add("123");
  array.add("456");

  set.add(789);
  set.add(890);

  iterator(array);
  iterator(set);
}
/*
 *  定义方法,可以同时迭代2个集合
 *  参数: 怎么实现 , 不能写ArrayList,也不能写HashSet
 *  参数: 或者共同实现的接口
 *  泛型的通配,匹配所有的数据类型  ?
 */
public static void iterator(Collection<?> coll){
  Iterator<?> it = coll.iterator();
  while(it.hasNext()){
    //it.next()获取的对象,什么类型
    System.out.println(it.next());
  }
}

泛型的限定

public static void main(String[] args) {
  //创建3个集合对象
  ArrayList<ChuShi> cs = new ArrayList<ChuShi>();
  ArrayList<FuWuYuan> fwy = new ArrayList<FuWuYuan>();
  ArrayList<JingLi> jl = new ArrayList<JingLi>();

  //每个集合存储自己的元素
  cs.add(new ChuShi("张三", "后厨001"));
  cs.add(new ChuShi("李四", "后厨002"));

  fwy.add(new FuWuYuan("翠花", "服务部001"));
  fwy.add(new FuWuYuan("酸菜", "服务部002"));

  jl.add(new JingLi("小名", "董事会001", 123456789.32));
  jl.add(new JingLi("小强", "董事会002", 123456789.33));

  // ArrayList<String> arrayString = new ArrayList<String>();
  iterator(jl);
  iterator(fwy);
  iterator(cs);
}

/*
 * 定义方法,可以同时遍历3集合,遍历三个集合的同时,可以调用工作方法 work
 * ? 通配符,迭代器it.next()方法取出来的是Object类型,怎么调用work方法
 * 强制转换:  it.next()=Object o ==> Employee
 * 方法参数: 控制,可以传递Employee对象,也可以传递Employee的子类的对象
 * 泛型的限定  本案例,父类固定Employee,但是子类可以无限?
 *   ? extends Employee 限制的是父类, 上限限定, 可以传递Employee,传递他的子类对象
 *   ? super   Employee 限制的是子类, 下限限定, 可以传递Employee,传递他的父类对象
 */
public static void iterator(ArrayList<? extends Employee> array){

   Iterator<? extends Employee> it = array.iterator();
   while(it.hasNext()){
     //获取出的next() 数据类型,是什么Employee
     Employee e = it.next();
     e.work();
   }
}

List接口

它是一个元素存取有序的集合,通过索引就可以精确的操作集合中的元素,集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素,List接口有多个子类,比如ArrayList、LinkedList

Modifier and Type Method and Description
boolean add(E e)
将指定的元素追加到此列表的末尾(可选操作)。
void add(int index, E element)
将指定的元素插入此列表中的指定位置(可选操作)。
boolean addAll(Collection c)
按指定集合的迭代器(可选操作)返回的顺序将指定集合中的所有元素附加到此列表的末尾。
boolean addAll(int index, Collection c)
将指定集合中的所有元素插入到此列表中的指定位置(可选操作)。
void clear()
从此列表中删除所有元素(可选操作)。
boolean contains(Object o)
如果此列表包含指定的元素,则返回 true
boolean containsAll(Collection c)
如果此列表包含指定 集合的所有元素,则返回true。
boolean equals(Object o)
将指定的对象与此列表进行比较以获得相等性。
E get(int index)
返回此列表中指定位置的元素。
int hashCode()
返回此列表的哈希码值。
int indexOf(Object o)
返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
boolean isEmpty()
如果此列表不包含元素,则返回 true
Iterator iterator()
以正确的顺序返回该列表中的元素的迭代器。
int lastIndexOf(Object o)
返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
ListIterator listIterator()
返回列表中的列表迭代器(按适当的顺序)。
ListIterator listIterator(int index)
从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。
E remove(int index)
删除该列表中指定位置的元素(可选操作)。
boolean remove(Object o)
从列表中删除指定元素的第一个出现(如果存在)(可选操作)。
boolean removeAll(Collection c)
从此列表中删除包含在指定集合中的所有元素(可选操作)。
default void replaceAll(UnaryOperator operator)
将该列表的每个元素替换为将该运算符应用于该元素的结果。
boolean retainAll(Collection c)
仅保留此列表中包含在指定集合中的元素(可选操作)。
E set(int index, E element)
用指定的元素(可选操作)替换此列表中指定位置的元素。
int size()
返回此列表中的元素数。
default void sort(Comparator c)
使用随附的 Comparator排序此列表来比较元素。
default Spliterator spliterator()
在此列表中的元素上创建一个Spliterator
List subList(int fromIndex, int toIndex)
返回此列表中指定的 fromIndex (含)和 toIndex之间的视图。
Object[] toArray()
以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。
 T[] toArray(T[] a)
以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。

add()方法 List接口方法

  • add(Object e) 向集合末尾处,添加指定的元素

  • add(int index, Object e) 向集合指定索引处,添加指定的元素,原有元素依次后移,带有索引的操作,防止越界问题

  • StringIndexOutOfBoundsException

  • IndexOutOfBoundsException

  • ArrayIndexOutOfBoundsException

List<String> list = new ArrayList<String>();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
System.out.println(list);

list.add(1, "itcast");
System.out.println(list);

remove()方法 List接口方法

  • remove(Object e) 将指定元素对象,从集合中删除,返回值为被删除的元素
  • remove(int index) 将指定索引处的元素,从集合中删除,返回值为被删除的元素
List<Double> list = new ArrayList<Double>();
list.add(1.1);
list.add(1.2);
list.add(1.3);
list.add(1.4);

Double d = list.remove(0);
System.out.println(d);
System.out.println(list);

set()方法 List接口方法

  • set(int index, Object e) 将指定索引处的元素,替换成指定的元素,返回值为替换前的元素
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);

Integer i = list.set(0, 5);
System.out.println(i);
System.out.println(list);

get()方法 List接口方法

  • get(int index) 获取指定索引处的元素,并返回该元素
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);

System.out.println(list.get(1));

removeFirst()方法和removeLast()方法 LinkedList特有方法

LinkedList<String> link = new LinkedList<String>();
link.add("1");
link.add("2");
link.add("3");
link.add("4");

String first = link.removeFirst();
String last = link.removeLast();
System.out.println(first);
System.out.println(last);

System.out.println(link);

getFirst()方法和getLast()方法 LinkedList特有方法

LinkedList<String> link = new LinkedList<String>();
link.add("1");
link.add("2");
link.add("3");
link.add("4");

if(!link.isEmpty()){
  String first = link.getFirst();
  String last = link.getLast();
  System.out.println(first);
  System.out.println(last);
}

addFirst()方法和addLast()方法 LinkedList特有方法

LinkedList<String> link = new LinkedList<String>();

link.addLast("heima");

link.add("abc");
link.add("bcd");

link.addFirst("itcast");
System.out.println(link);

Set接口

它是个不包含重复元素的集合,取出元素的方式可以采用:迭代器、增强for ,Set接口有多个子类,比如HashSet、LinkedHashSet

存取原理

  • 首先调用本类的hashCode()方法算出哈希值
  • 在容器中找是否与新元素哈希值相同的老元素,如果没有直接存入,如果有转到第三步
  • 新元素会与该索引位置下的老元素利用equals方法一一对比,一旦新元素.equals(老元素)返回true,停止对比,说明重复,不再存入,如果与该索引位置下的老元素都通过equals方法对比返回false,说明没有重复,存入
Modifier and Type Method and Description
boolean add(E e)
如果指定的元素不存在,则将其指定的元素添加(可选操作)。
boolean addAll(Collection c)
将指定集合中的所有元素添加到此集合(如果尚未存在)(可选操作)。
void clear()
从此集合中删除所有元素(可选操作)。
boolean contains(Object o)
如果此集合包含指定的元素,则返回 true
boolean containsAll(Collection c)
返回 true如果此集合包含所有指定集合的元素。
boolean equals(Object o)
将指定的对象与此集合进行比较以实现相等。
int hashCode()
返回此集合的哈希码值。
boolean isEmpty()
如果此集合不包含元素,则返回 true
Iterator iterator()
返回此集合中元素的迭代器。
boolean remove(Object o)
如果存在,则从该集合中删除指定的元素(可选操作)。
boolean removeAll(Collection c)
从此集合中删除指定集合中包含的所有元素(可选操作)。
boolean retainAll(Collection c)
仅保留该集合中包含在指定集合中的元素(可选操作)。
int size()
返回此集合中的元素数(其基数)。
default Spliterator spliterator()
在此集合中的元素上创建一个 Spliterator
Object[] toArray()
返回一个包含此集合中所有元素的数组。
 T[] toArray(T[] a)
返回一个包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。

add()方法 Set接口方法

  • 无序集合,存储和取出的顺序不同,没有索引,不存储重复元素,代码的编写上,和ArrayList完全一致
Set<String> set = new HashSet<String>();
set.add("cn");
set.add("heima");
set.add("java");
set.add("java");
set.add("itcast");

Iterator<String> it = set.iterator();
while(it.hasNext()){
    System.out.println(it.next());
}
System.out.println("==============");

for(String s : set){
    System.out.println(s);
}

hashCode()方法 Set接口方法

  • 对象的哈希值,普通的十进制整数 父类Object,方法 public int hashCode() 计算结果int整数
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());

/*System.out.println("重地".hashCode());
System.out.println("通话".hashCode());*/

String类重写hashCode()方法

public int hashCode() {
  int h = hash;//hash初值为0
  if (h == 0 && value.length > 0) {
      char val[] = value;

      for (int i = 0; i < value.length; i++) {
          h = 31 * h + val[i];
      }
      hash = h;
  }
  return h;
}

哈希表的存储自定义对象

底层数据结构,哈希表,存储,取出都比较快,线程不安全,运行速度快

每个对象的地址值都不同,调用Obejct类的hashCode方法返回不同哈希值,直接存入,如果重写了hashCode和equals就可以防止相同的对象存入

public static void main(String[] args) {
//将Person对象中的姓名,年龄,相同数据,看作同一个对象
//判断对象是否重复,依赖对象自己的方法 hashCode,equals
HashSet<Person> setPerson = new HashSet<Person>();
setPerson.add(new Person("a",11));
setPerson.add(new Person("b",10));
setPerson.add(new Person("b",10));
setPerson.add(new Person("c",25));
setPerson.add(new Person("d",19));
setPerson.add(new Person("e",17));
System.out.println(setPerson);
}
public class Person {
  private String name;
  private int age;

  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
  public Person(String name, int age) {
    super();
    this.name = name;
    this.age = age;
  }
  public Person(){}

  public String toString(){
    return name+".."+age;
  }
}

自定义对象重写hashCode和equals

public static void main(String[] args) {
//将Person对象中的姓名,年龄,相同数据,看作同一个对象
//判断对象是否重复,依赖对象自己的方法 hashCode,equals
HashSet<Person> setPerson = new HashSet<Person>();
setPerson.add(new Person("a",11));
setPerson.add(new Person("b",10));
setPerson.add(new Person("b",10));
setPerson.add(new Person("c",25));
setPerson.add(new Person("d",19));
setPerson.add(new Person("e",17));
System.out.println(setPerson);
}
public class Person {
    private String name;
    private int age;

    public int hashCode(){
        return name.hashCode()+age*55;
    }
    //方法equals重写父类,保证和父类相同
    public boolean equals(Object obj){
        if(this == obj)
        return true;
        if(obj == null)
        return false;
    if(obj instanceof Person){
        Person p = (Person)obj;
        return name.equals(p.name) && age==p.age;
        }
        return false;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public Person(){}
    public String toString(){
        return name+".."+age;
    }
}

ArrayList,HashSet判断对象是否重复的原因

ArrayList的contains方法原理:底层依赖于equals方法

ArrayList的contains方法会使用调用方法时,

传入的元素的equals方法依次与集合中的旧元素所比较,

从而根据返回的布尔值判断是否有重复元素。

此时,当ArrayList存放自定义类型时,由于自定义类型在未重写equals方法前,

判断是否重复的依据是地址值,所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。

HashSet的add()方法和contains方法()底层都依赖 hashCode()方法与equals方法()

Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。

HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode与equals方法的返回结果。规则如下:

先判断新元素与集合内已经有的旧元素的HashCode值

  • 如果不同,说明是不同元素,添加到集合。
  • 如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合。

所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode与equals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashcode与equals方法。