【Java集合】逻辑结构超强、表达十分清晰的“Collection接口”及“List接口、Set接口”解析

如标题,你将获得Collection、List、Set接口,以及其实现类ArrayList,LinkedList,HashSet、TreeSet类的讲解。众所周知,Java的学习就是从API里有啥、是啥、怎么用开始,再进阶至源码。

注意:

  • 文章较长,但非常基础和实用。
  • 一句废话也没有,再说我也不喜欢搞那些花里胡哨的。
  • 这是我学习笔记,其中一定有一部分是你能够拿去做成你的笔记的。
  • enjoy…

文章目录

  • 诞生背景
  • 集合(Collection)接口
    • 方法
    • 迭代(遍历方法)
  • 迭代器(Iterator)接口
  • List接口
    • List接口专属迭代器:ListIterator
      • 使用迭代器的注意事项
      • 几种遍历方式
    • ArrayList类
      • 核心方法
      • 遍历方式
    • LinkedList类
      • 核心方法
  • Set接口
    • HashSet类
      • 自定义对象如何实现自动去重、保证唯一?
      • LinkedHashSet类
    • TreeSet类
      • 自动排序的核心原理

集合体系

【Java集合】逻辑结构超强、表达十分清晰的“Collection接口”及“List接口、Set接口”解析_第1张图片


诞生背景

为啥要诞生集合,还得从数组的缺陷说起。

数组(存储同一种数据类型的集合容器)的特点:

  1. 只能存储同一种数据类型的数据,例如在同一个容器中不能同时存储int型和double型。
  2. 一旦初始化,容量固定,容量不能扩容或缩减。
  3. 数组中的元素与元素之间的内存地址是连续的。

注意: Object类型的数组可以存储任意类型的数据。

Object[] arr = new Object[10];
arr[1] = "abc";
arr[2] = 'a';
arr[3] = 12;

基于数组的仅能存储同一种类型和容量不可变化的劣势下,诞生的集合!

集合的定义: 是存储任何对象数据的集合容器。在同一个容器中,可以同时存储int型和double型,甚至是自定义类型。

//试一试
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList array = new ArrayList();
        array.add("我是字符串");
        array.add(134);
        array.add(3.14534);

        for(int i=0; i<array.size(); i++) {
            System.out.println(array.get(i));
        }
    }
}
/*
输出:
我是字符串
134
3.14534

 */

集合比数组的优势:
1. 集合可以存储任意类型的对象数据,数组只能存储同一种数据类型的数据。
2. 集合的长度是允许发生变化的,数组的长度是固定的。


集合(Collection)接口

Collection的体系结构(图片来自“Java3y”):
【Java集合】逻辑结构超强、表达十分清晰的“Collection接口”及“List接口、Set接口”解析_第2张图片

方法

看别人的技术文章和看API(源码)的根本区别就是,技术文章会把最重要、最核心的方法先罗列出来,还会把方法进行分门别类,这样学习才能事半功倍!

方法:(增、删、查、判断、遍历/迭代)
增加

  • boolean add(E e) 添加成功返回true,添加失败返回false.
  • boolean addAll(Collection c) 把一个集合的元素添加到另外一个集合中去。

删除

  • void clear() //移除所有元素
  • boolean remove(Object o) //指定集合中的元素删除,删除成功返回true,删除失败返回false.
  • boolean removeAll(Collection c) //删除交集元素
  • boolean retainAll(Collection c) //保留交集元素,其他元素删除;retain[保持、记住]

查看

  • int size()` //返回元素的个数

判断:Boolean类型;

  • boolean isEmpty()
  • boolean contains(Object o) //判断集合中是否存在参数的元素;
  • boolean containsAll(Collection c) //判断调用集合是否包含参数集合的全部元素,即判断集合是否完全相等;
//试一试
import java.util.ArrayList;
import java.util.Collection;

public class Main {
    public static void main(String[] args) {
        Collection col = new ArrayList();

        col.add("por*hub"); //增
        col.add("xvideo*");

        col.remove("xvideo*"); //删

        System.out.println("数量:"+col.size()); //查
        System.out.println("含por*hub?"+col.contains("por*hub"));

        System.out.println("空?"+col.isEmpty());//判断
    }
}
/*
输出:
  数量:1
  含por*hub?true
  空?false
*/

迭代(遍历方法)

  • Object[] toArray() //返回一个Object的数组,把集合中的元素全部存储到一个Object的数组中返回;
  • Iterator iterator() //迭代器遍历集合中的元素

两种迭代方法示例:

//迭代:toArray()方法
import java.util.ArrayList;
import java.util.Collection;

public class Main {
	public static void main(String[] args) {
		Collection<String> coll = new ArrayList<String>();
		coll.add("abc1");
		coll.add("abc2");
		coll.add("abc3");
		coll.add("abc4");
		
		Object[] arr = coll.toArray(); //将ArrayList集合转换为Object数组
		
		for(Object i:arr) { //遍历Object数组
			System.out.println(i);
		}
	}
}
//迭代:迭代器方法
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Main {
	public static void main(String[] args) {
		Collection<String> coll = new ArrayList<String>();
		coll.add("abc1");
		coll.add("abc2");
		coll.add("abc3");
		coll.add("abc4");

		// 获取迭代器对象
		Iterator it = coll.iterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}

	}
}

迭代器(Iterator)接口

迭代器诞生的背景:

  1. java中提供了很多个集合,它们在存储元素时,采用的存储方式不同。
  2. 对于众多的集合,取出集合中的元素,每个集合都需要不同的操作。
  3. 由此就会有很多的取出集合操作的类。
  4. 为了减少操作类,Java使用一个专业术语“迭代器iterator”来统称所有集合的取出操作。

迭代器功能:
取出任意种类的集合中的元素。

方法概览:

  1. 判定集合中是否还有元素可以迭代:boolean hasNext();
  2. 返回迭代的下一个元素:E next();
  3. 移除迭代器最后一次返回的元素,即删除该指针现在指向元素:void remove();

迭代器的应用的底层原理:

  1. 因为Iterator是接口,就需要找实现类。
  2. 在Collection接口中,有一个方法:Iterator iterator();
  3. 在Collection接口中的实现类中(如ArrayList、HashSet),都会重写iterator方法,返回Iterator接口的实现类对象。
  4. 在具体集合遍历时,就会调用相应的方法。

快速理解迭代器的使用:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Main {
	public static void main(String[] args) {
		Collection<String> coll = new ArrayList<String>();
		coll.add("abc1");
		coll.add("abc2");
		coll.add("abc3");
		coll.add("abc4");
		//迭代器,对集合ArrayList中的元素进行取出
		
		//调用集合的方法iterator()获取出,Iterator接口的实现类的对象
		Iterator<String> it = coll.iterator();
		//接口实现类对象,调用方法hasNext()判断集合中是否有元素,集合中没元素, hasNext()返回了false
		//接口的实现类对象,调用方法next()取出集合中的元素
		while(it.hasNext()){
			String s = it.next();
			System.out.println(s);
		}
	}
}

List接口

实现类:

  1. ArrayList
  2. LinkedList
  3. Vector(已淘汰)

特点:

  1. 有序(即添加的顺序与出来的顺序一致);
  2. 索引;
  3. 可重复;

(除了Collection)特有方法:(增、删、获取、改、迭代)

添加

  • boolean add(int index, E element) //把元素添加到指定的索引值里
  • void add(int index, E element) //把元素插入指定位置
  • boolean addAll(int index, Collection c) //把指定的集合添加到指定的索引里;

删除

  • E remove(int index) //移除指定索引上的元素,返回被删除的元素

获取:

  • E get(int index) //根据索引值,返回值
  • int indexOf(Object o) //找元素第一次出现的索引值
  • int lastIndexOf(Object o) //找元素最后一次出现的索引值
  • E subList(int fromIndex, int toIndex) //截取元素:从fromIndex到toIndex【注意:顾头不顾尾】;

修改:

  • E set(int index, E element) //使用指定的元素替换该索引值的内容,返回修改之前的元素

总结:

  • List接口中的特有方法特点:操作的方法都与索引值有关。
  • 只有List接口下的集合类才具备索引值的方法,其他的都没有!
  • 因为List的本质是Object数组;

List接口专属迭代器:ListIterator

特性:

  1. ListIterator的超接口是Iterator;并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
  2. ListIterator只能用来遍历List,不能遍历其他。
  3. ListIterator既可以前向也可以后向遍历,而Iterator只能是前向遍历。因为List接口的本质是个双向链表。
  4. 可以一边遍历、一边修改List元素。

方法:(ListIterator接口除了Iterator接口的额外方法有)

增加操作:void add(E e);
删除操作:void remove();
修改操作:void set(E e);
取出操作(查询)

  1. boolean hasNext() 向前判断是否有元素
  2. E next() 先取出当前指针指向的元素,然后指针向下移动一个单位。
  3. int nextIndex() 返回调用next()的元素索引
  4. boolean hasPrevious() 判断是否存在上一个元素。
  5. E previous() 当前指针先向上移动一个单位,然后再取出当前指针指向的元素。
//实例
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class Main {
    public static void main(String[] args) {
        List col = new ArrayList();
        col.add("thepor*tube com");
        col.add("por**hub com");
        col.add("javbus com");
        col.add("3atv com");

        ListIterator it = col.listIterator(); //获取到List的迭代器
        System.out.println("向前迭代:");
        while(it.hasNext()) {
            System.out.println(it.next());
        }
        System.out.println("向后迭代:");
        while(it.hasPrevious()) {
            System.out.println(it.previous());
        }
    }
}
/*
输出:
    向前迭代:
    thepor*tube com
    por**hub com
    javbus com
    3atv com
    向后迭代:
    3atv com
    javbus com
    por**hub com
    thepor*tube com
*/

使用迭代器的注意事项

  1. 在迭代过程使用add函数,它不会立即添加到本个循环的指针所在位置;而是等待本个迭代器完成后再添加入;
  2. 在迭代器迭代元素过程中,不允许使用集合对象的方法改变集合中元素个数;
  3. 只有在迭代器方法使用完毕后,才能使用集合的操作方法,即在使用迭代器进行迭代的时候,不能改变集合的元素个数;

在迭代器里next()和add():

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class Main {
	public static void main(String[] args) {
		List list = new ArrayList();
		
		list.add("AA");
		list.add("BB");
		list.add("CC");
		
		ListIterator it = list.listIterator();	//获取到迭代器
		while(it.hasNext()){
			System.out.print(it.next()+",");
			it.add("additional"); // 把元素添加到当前指针指向位置;它不会立即添加到本个循环的指针所在位置;而是等待本个迭代器完成后再添加入;
		}
		System.out.print("\n集合list的内容是:"+list);
	}
}
/* 输出:
	AA,BB,CC,
	集合list的内容是:[AA, additional, BB, additional, CC, additional]
*/

迭代时不能改变个数:

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class Main {
	public static void main(String[] args) {
		List list = new ArrayList();

		list.add("AA");
		list.add("BB");
		list.add("CC");

		ListIterator it = list.listIterator(); // 获取到迭代器
		while (it.hasNext()) {
			System.out.println(it.next());
			/*
			 如果使用集合对象的方法操作集合元素个数,则有异常抛出: ConcurrentModificationException
			list.add("aa"); // add方法是把元素添加到集合的末尾处的。相当于改变集合个数
			list.remove("BB"); //remove方法是删除集合中的指定元素。相当于改变集合个数
			*/
		}
	}
}
/* 输出:
	AA
	BB
	CC
 */

只有在迭代器使用完毕后,才能改变集合个数:

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class Main {
	public static void main(String[] args) {
		List list = new ArrayList();
		
		list.add("AA");
		list.add("BB");
		list.add("CC");
		
		ListIterator it = list.listIterator();	//获取到迭代器
		while(it.hasNext()){
			System.out.print(it.next()+",");
		}
		
		list.add("aa"); //改变了集合元素个数的后面不能出现迭代器的使用,只能在迭代器使用完毕后才能使用集合对象方法来操作集合
		//it.next(); 异常抛出!
		
		System.out.print("\n集合中的元素"+list);
	}
}
/* 输出:
	AA,BB,CC,
	集合中的元素[AA, BB, CC, aa]
 */

几种遍历方式

三种遍历集合的方式:
第一种: 使用get方法遍历。
第二种: 使用迭代器正序遍历。
第三种: 使用迭代器逆序遍历。

import java.util.ArrayList;
import java.util.Collection;
import java.util.ListIterator;
import java.util.List;

public class TestMain {

	public static void main(String[] args) {
		List list = new ArrayList();
		
		list.add("AA");
		list.add("BB");
		list.add("CC");
		
		//get遍历
		for(int i=0;i<list.size();i++) {
			System.out.println(list.get(i));
		}
		
		//迭代器正序遍历
		ListIterator it = list.listIterator();
		while(it.hasNext()) {
			System.out.println(it.next());
		}
		
		//迭代器逆序遍历
		while(it.hasPrevious()) {
			System.out.println(it.previous());
		}
	}
}

再次提醒:只能在List接口的实例中使用ListIterator!!!

ArrayList类

学习目标:
由于该类的绝大部分方法都是其父接口中继承而来,故需要学习的是该类的实现原理与特点!

实现原理:
底层是维护一个Object数组来实现;

使用指导: (什么时候使用ArrayList?)
如果目前的数据是查询比较多,增删比较少的时候,那么就使用ArrayList存储这批数据。 比如:高校的图书馆。

特点:

  1. 查询速度快(像数组一样以连续的内存空间存储元素),增删慢(检查容量、拓展容量、把旧数组的内容拷贝到新数组中,见ArrayList源代码中的具体方法)
  2. ArrayList底层维护了一个Object数组实现 的,使用无参构造函数时,Object数组默认的容量是10,当长度不够时,自动增长0.5倍。
  3. 为追求效率,ArrayList 没有实现同步(synchronized),如果需要多个线程并发访问,用户可以手动同步,也可使用 Vector 替代。

特有方法:

  • void ensureCapacity(int minCapacity) //自己制定ArrayList数组的初始容量,亦可用构造方法来实现;
  • trimToSize() //为当前ArrayList实例根据已经存储的内容个数量调整容量

核心方法

增:

  • public boolean add(E e):添加元素
  • public void add(int index, E element):在指定的索引处添加一个元素

删:

  • public boolean remove(Object o):删除指定的元素,返回删除是否成功
  • public E remove(int index):删除指定索引处的元素,返回被删除的元素

改:

  • public E set(int index,E element):修改指定索引处的元素,返回被修改的元素
    查:
  • public E get(int index):返回指定索引处的元素
  • public int size():返回集合中的元素的个数
增方法详解
import java.util.ArrayList;

public class Main {
	public static void main(String[] args) {
		ArrayList<String> array = new ArrayList<String>();
		array.add("hello");
		array.add("kyle");
		array.add("Java");
		
		// add(int index,E element):在指定的索引处添加一个元素
		array.add(1, "android");
		System.out.println("ArrayList目前的状态是:" + array); //ArrayList目前的状态是:[hello, android, kyle, Java]
	}
}
删方法详解
import java.util.ArrayList;

public class Main {
	public static void main(String[] args) {
		ArrayList<String> array = new ArrayList<String>();
		array.add("hello");
		array.add("kyle");
		array.add("Java");
		
		array.remove("hello"); //根据元素名删除
		System.out.println("删除某元素后ArrayList目前的状态是:" + array); //删除某元素后ArrayList目前的状态是:[kyle, Java]

		String s = array.remove(0); //根据下标删除
		System.out.println("删除第一个元素后的ArrayList目前的状态是:" + array); //删除第一个元素后的ArrayList目前的状态是:[Java]
	}
}
改方法详解
import java.util.ArrayList;

public class Main {
	public static void main(String[] args) {
		ArrayList<String> array = new ArrayList<String>();
		array.add("hello");
		array.add("kyle");
		array.add("Java");
		
		array.set(0, "olleh");
		System.out.println("ArrayList目前的状态是:" + array); //输出:ArrayList目前的状态是:[olleh, kyle, Java]
	}
}
查方法详解
import java.util.ArrayList;

public class Main {
	public static void main(String[] args) {
		ArrayList<String> array = new ArrayList<String>();
		array.add("hello");
		array.add("kyle");
		array.add("Java");
		
		//public E get(int index):返回指定索引处的元素
		System.out.println("获取的元素是:" + array.get(0));

		//public int size():返回集合中的元素的个数
		System.out.println("元素的个数:" + array.size());
	}
}

遍历方式

遍历方法:

  1. 类似数组的遍历方法,通过size()和get()配合实现的
  2. 通过迭代器遍历
类似数组的遍历方法,通过size()get()配合实现的
import java.util.ArrayList;
public class ArrayListDemo3 {
	public static void main(String[] args) {
		//创建集合对象
		ArrayList<String> array = new ArrayList<String>();
		
		//添加元素
		array.add("hello");
		array.add("world");
		array.add("java");
		
		//获取元素
		//原始做法
		System.out.println(array.get(0));
		System.out.println(array.get(1));
		System.out.println(array.get(2));
		System.out.println("----------");
		
		for(int x=0; x<3; x++) {
			System.out.println(array.get(x));
		}
		System.out.println("----------");
		
		//如何知道集合中元素的个数呢?size()
		for(int x=0; x<array.size(); x++) {
			System.out.println(array.get(x));
		}
		System.out.println("----------");
		
		//最标准的用法
		for(int x=0; x<array.size(); x++) {
			String s = array.get(x);
			System.out.println(s);
		}
	}
}
迭代器遍历ArrayList
import java.util.ArrayList;
import java.util.Iterator;
public class Main {
	public static void main(String[] args) {
		ArrayList<String> array = new ArrayList<String>();
		array.add("hello");
		array.add("kyle");
		array.add("Java");
		
		Iterator it = array.iterator();
		
		while(it.hasNext()) {
			System.out.println("数据是:" + it.next());
		}
	}
}
/* 输出:
	数据是:hello
	数据是:kyle
	数据是:Java
*/

LinkedList类

特点:

  1. 链表实现;
  2. 增删快;
  3. 查找慢

实现原理:
LinkedList 同时实现了 List 接口和 Deque 接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。

底层原理:

  • 由于LinkedList在内存中的地址不连续,需要让上一个元素记住下一个元素。所以每个元素中保存的有下一个元素的位置。虽然也有角标,但是查找的时候,需要从头往下找,显然是没有数组查找快的。但是,链表在插入新元素的时候,只需要让前一个元素记住新元素,让新元素记住下一个元素就可以了。所以插入很快。
  • 由于链表实现, 增加时只要让前一个元素记住自己就可以,删除时让前一个元素记住后一个元素,后一个元素记住前一个元素。这样的增删效率较高。

核心方法

增:

  • addFirst(E e) //把元素添加到集合的首位置上。
  • addLast(E e) //把元素添加到集合的末尾处。

删:

  • removeFirst() //删除集合中的首位置元素,并返回
  • removeLast() //

查:

  • getFirst() //获取集合中的首位置元素
  • getLast() //获取集合中末尾的元素

方法的使用方式类似于ArrayList,遍历的方式和ArrayList一样!
由于Vector已经被时间所淘汰,这里将不再详解。待后面Java集合进阶(研究源码和线程安全)时再做分析!


Set接口

Set的特点:

  1. 无序(存储和读取的顺序可能不一样)
  2. 不允许重复,仅针对于系统提供的数据类型;如果是自定义的类型/类,则允许重复
  3. 没有整数索引

方法

从其父接口(Collection)那里继承了绝大部分方法;

增:

  1. boolean add(E e);
  2. boolean addAll(Collection c);

删:

  1. void clear();
  2. boolean remove(Object obj);
  3. boolean reomveAll(Collection c);

迭代:

  1. Object[] toArray();
  2. Iterator iterator();

查:

  1. int size();
  2. boolean isEmpty();
  3. boolean contains(Collection c);

特有方法(相对于List接口):

  1. boolean equals(Object o);
  2. int hashCode();
//试一试
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set ss = new HashSet();
        ss.add("Aaa"); //增
        ss.add("Bbb");
        ss.add(1234);
        ss.add(8848);
        ss.add(3.1415926);

        ss.remove(8848); //删

        System.out.println("长度:"+ss.size()); //获取
        System.out.println("空?"+ss.isEmpty());
        System.out.println("含1234?"+ss.contains(1234));

        //迭代
        System.out.println("内容迭代:");
        Iterator it = ss.iterator();
        while(it.hasNext()) {
            System.out.print(it.next() + " ");
        }
    }
}
/*
输出:
    长度:4
    空?false
    含1234?true
    内容迭代:
    Aaa 1234 Bbb 3.1415926 
 */

遍历迭代

Set接口的三种遍历方式:

  1. 转换为Object;
  2. 迭代器
  3. 增强for循环
遍历Set
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Main {
	public static void main(String[] args) {
		//创建集合对象
		//HashSet hs = new HashSet();
		Set<String> set = new HashSet<String>(); //父接口引用指向子类对象,无法使用子类对象中特有的成员
		
		//添加元素
		set.add("hello"); //实现了Collection接口的add方法
		set.add("world");
		set.add("java");
		
		//遍历集合的三种方式
		//第一种:转Object数组
		Object[] objs = set.toArray();
		for(int i=0;i<objs.length;i++) {
			System.out.println(objs[i]);
		}
		
		//第二种:迭代器
		Iterator<String> it = set.iterator();
		while(it.hasNext()) {
			String s = it.next();
			System.out.println(s);
		}
		
		//第三种:增强for
		for(String s:set) {
			System.out.println(s);
		}
	}
}

HashSet类

HashSet的核心功能就是自动去重,保证整个容器中的每个元素都是唯一的。
【Java集合】逻辑结构超强、表达十分清晰的“Collection接口”及“List接口、Set接口”解析_第3张图片
注:图片来自Java3y

特性:

  1. 底层的数据结构是哈希表(hash table);
  2. 存储,取出的速度都比较快;
  3. 线程不安全(也叫实现不同步);

HashSet如何检查重复(add方法的执行流程):

  1. HashSet的add()方法,首先会使用当前集合中的每一个元素和新添加的元素进行hash值比较,
  2. 如果hash值不一样,则直接添加新的元素
  3. 如果hash值一样,比较地址值或者使用equals方法进行比较
  4. 比较结果一样,则认为是重复不添加
  5. 所有的比较结果都不一样则添加

为什么要调用equals方法?

  1. hashCode()与equals()的相关规定:
    a) 如果两个对象相等,则hashcode一定也是相同的
    b) 两个对象相等,对两个equals方法返回true
    c) 两个对象有相同的hashcode值,它们也不一定是相等的
  2. 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖。hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
//HashSet保证元素唯一,自动去重
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set ss = new HashSet();
        ss.add("cc");
        ss.add("oo");
        ss.add("bd");
        ss.add("ba");
        ss.add("ba");
        ss.add("oo");
        ss.add("cc");

        Iterator it = ss.iterator();
        while(it.hasNext()) {
            System.out.print(it.next() + " ");
        }
    }
}
//输出:cc oo bd ba

自定义对象如何实现自动去重、保证唯一?

需要解决的问题: HashSet保存自定义对象时不会进行去重!
如何解决: 重写hashCode和equals方法

HashSet保存自定义对象时不会进行去重
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Main {
	public static void main(String[] args) {
		//创建集合对象
		HashSet<Student> hs = new HashSet<Student>();
		// 创建元素对象
		Student s = new Student("zhangsan", 18);
		Student s2 = new Student("lisi", 19);
		Student s3 = new Student("lisi", 19);
		// 添加元素对象
		hs.add(s);
		hs.add(s2);
		hs.add(s3);
		// 遍历集合对象
		for (Student student : hs) {
			System.out.println(student);
		}
	}
}

class Student {
	String name;
	int age;
	
	public Student(String name,int age) {
		this.name=name;
		this.age=age;
	}
	
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
}

//输出:
Student [name=lisi, age=19]
Student [name=zhangsan, age=18]
Student [name=lisi, age=19]
实现去重:重写hashCode和equals方法
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Main {
	public static void main(String[] args) {
		//创建集合对象
		HashSet<Student> hs = new HashSet<Student>();
		// 创建元素对象
		Student s = new Student("zhangsan", 18);
		Student s2 = new Student("lisi", 19);
		Student s3 = new Student("lisi", 19);
		// 添加元素对象
		hs.add(s);
		hs.add(s2);
		hs.add(s3);
		// 遍历集合对象
		for (Student student : hs) {
			System.out.println(student);
		}
	}
}

class Student {
	String name;
	int age;
	
	public Student(String name,int age) {
		this.name=name;
		this.age=age;
	}
	
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	
	@Override
	public int hashCode() {
		return 1; //如果hashCode返回一样的内容,则就会调用equals方法
	}
	
	@Override
	public boolean equals(Object obj) {
		//System.out.println("-------------------");
		Student s = (Student)obj;//向下转型,可以获取子类特有成员
		
		//比较年龄是否相等,如果不等则返回false
		if(this.age != s.age) {
			return false;
		}
		
		//比较姓名是否相等,如果不等则返回false
		if(!this.name.equals(s.name)) {
			return false;
		}
		
		//默认返回true,说明两个学生是相等的
		return true;
	}
}
//输出:
Student [name=zhangsan, age=18]
Student [name=lisi, age=19]

优化

问题:

  1. 当hashCode方法返回整数1时,所有对象的hash值都是一样的。
  2. 而有一些对象的成员变量完全不同,如果它们的Hash值不同,则它们还需要进行hash和equals方法的比较。
  3. 这样的话会造成不必要的时间、资源浪费。
  4. 如果我们可以让成员变量不同的对象,他们的hash值也不同,这就可以减少一部分equals方法的比较,从而可以提高我们程序的效率。

解决方案:

  1. 可以尝试着让hashCode方法的返回值和对象的成员变量有关;
  2. 也即让hashCode方法返回所有成员变量之和,让基本数据类型直接想加,然后引用数据类型获取hashCode方法返回值后再相加(boolean不可以参与运算)。如下代码:
@Override
public int hashCode() {
   return age + name.hashCode();
}
优化代码:
import java.util.HashSet;
public class Main {
	public static void main(String[] args) {
		//创建集合对象
		HashSet<Person> hs = new HashSet<Person>();
		//创建元素对象
		Person p = new Person("zhangsan",20);
		Person p2 = new Person("lisi",20);
		Person p3 = new Person("lisi",20);
		//添加元素对象
		hs.add(p);
		hs.add(p2);
		hs.add(p3);
		//遍历集合对象
		for (Person person : hs) {
			System.out.println(person);
		}
	}
}

class Person {
	String name;
	int age;
	
	public Person(String name,int age) {
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

	//自动生成hashCode方法和equals方法:"source","generator xX() and xX()"
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass()) //提高程序的健壮性、安全性
			return false;
		Person other = (Person) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
}

LinkedHashSet类

LinkedHashSet架构(图片来自Java3y):
【Java集合】逻辑结构超强、表达十分清晰的“Collection接口”及“List接口、Set接口”解析_第4张图片
特性:

  1. 基于链表的哈希表实现;

  2. 继承自HashSet;

  3. 维护着一个双重链表;

  4. 具有顺序,存储和取出的顺序相同

  5. 不允许有重复的元素

  6. 线程不安全,运行速度快


TreeSet类

HashSet的核心功能就是自动排序,保证整个容器中的所有元素都是有序的!

TreeSet类的架构(图片来自Java3y):
【Java集合】逻辑结构超强、表达十分清晰的“Collection接口”及“List接口、Set接口”解析_第5张图片

//试一试
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        Set<String> ss = new TreeSet<String>(); //尖括号中是泛型,表明这个Set容器中只能存放String类型的数据
        ss.add("por*hub");
        ss.add("porahub");
        ss.add("xvideo*");
        ss.add("thepor*tuebe");
        ss.add("javbus");

        Iterator<String> it = ss.iterator();
        while(it.hasNext()) {
            System.out.println(it.next());
        }
    }
}
/* 输出已经排好序了的串:
    javbus
    por*hub
    porahub
    thepor*tuebe
    xvideo*
 */

自动排序的核心原理

  1. 如果元素具备自然顺序(如数字、单个字符、字符串) 的特性,那么就按照元素自然顺序的特性进行排序存储。 其进行排序的底层实现是:红-黑树;

  2. 而元素没有具备自然顺序的时候(如自定义的类型、对象等):
    1 .往TreeSet添加元素的时候,如果元素本身不具备自然顺序的特性,那么该元素所属的类必须要实现Comparable接口,把元素的比较规则定义在compareTo(T o)方法上。
    2 .如果比较元素的时候,compareTo方法返回的是0,那么该元素就被视为重复元素,不允许添加.(注意:TreeSet与HashCode、equals方法是没有任何关系。)
    3 .往TreeSet添加元素的时候, 如果元素本身没有具备自然顺序的特性,而元素所属的类也没有实现Comparable接口,那么必须要在创建TreeSet的时候传入一个比较器。
    4 .往TreeSet添加元素的时候,如果元素本身不具备自然顺序的特性,而元素所属的类已经实现了Comparable接口,在创建TreeSet对象的时候也传入了比较器,那么是以比较器的比较规则优先使用。
    5 .如何自定义比较器: 自定义一个类实现Comparator接口即可,把元素与元素之间的比较规则定义在compare方法内即可。

class  类名  implements Comparator{
 	...
 }

例子:

import java.util.Comparator;
import java.util.TreeSet;

class  Emp implements Comparable<Emp>{

	int id;
	String name;
	int salary;

	public Emp(int id, String name, int salary) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
	}

	@Override
	public String toString() {
		return "{ 编号:"+  this.id+" 姓名:"+ this.name+" 薪水:"+ this.salary+"}";
	}

	//@Override //元素与元素之间的比较规则。
	// 负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。 
	public int compareTo(Emp o) {
		//		System.out.println(this.name+"compare"+ e.name);
		return this.salary- o.salary;
	}	
}

//自定义一个比较器
class MyComparator implements Comparator<Emp>{

	@Override
	public int compare(Emp o1, Emp o2) {
		return o1.id-o2.id;
	}

	//根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。 
	/*@Override
	public int compare(Object o1, Object o2) {
		Emp e1 = (Emp) o1;
		Emp e2 = (Emp) o2;
		return e1.id - e2.id;
	}*/
}
public class TestMain {

	public static void main(String[] args) {
		//创建一个比较器对象
		MyComparator comparator = new MyComparator();
		//创建TreeSet的时候传入比较器
		TreeSet tree = new TreeSet(comparator);

		tree.add(new Emp(110, "老陆", 100));
		tree.add(new Emp(113, "老钟", 200));
		tree.add(new Emp(220, "老汤", 300));
		tree.add(new Emp(120, "老蔡", 500));
		System.out.println("集合的元素:"+tree);
	}
}

你可能感兴趣的:(#,JavaSE)