独到理解_Java@集合

集合

一. 集合概述

介绍

Java集合是使程序能够存储和操纵元素不固定的一组数据。 所有Java集合类都位于java.uti包中。
【问】:
之前我们需要把多个元素放到一起的时候,使用的是数组。那么为何还要提供Java集合工具类呢?

  • 我们通过对比数组和Java集合工具类来解释Java集合工具类的必要性。
数组集合
长度固定长度不固定
存放任意类型不能存放基本数据类型,只能存放对象的引用

注意:
如果集合中存放基本类型,一定要将其 “装箱”成对应的”基本类型包装类”。

二. 层次结构

Java的集合类主要由两个接口派生而出:
Collection和Map
Collection和Map是Java结合框架的根接口,这两个接口又包含了一些子接口或实现类。

1. Collection的继承层次结构

在这里插入图片描述

2. Map的继承层次结构

在这里插入图片描述

3. 总结

由以上两图我们可以看出Java集合类有清晰的继承关系,有很多子接口和实现类。但是,并不是所有子接口或实现类都是最常用的。
下面我们列举出最常用的几个子接口和实现类:
Collection ——> List ——> ArrayList类
Collection ——> List ——> LinkedList类
Collection ——> Set ——> HashSet类
Collection ——> Set ——> SortedSet接口 ——> TreeSet类
Map ——> HashMap类
Map ——> SortedMap ——> TreeMap类

三. Collection接口和Iterator

1. Collection介绍

Collection接口是List接口和Set接口的父接口,它定义的方法可以用于操作List集合和Set集合。

刚才得知继承体系结构,Collection类是最大的,也就意味着,Collection中定义的方法规定是List和Set都有的

集合中 只能存储单个元素,并且只能保存引用数据类型,保存Object

  • 能放Object 就意味着什么都能放,因为多态

Collection接口定义的方法

方法描述
boolean add(Object o)该方法用于向集合里添加一个元素,添加成功返回true
void clear()清除集合里的所有元素,将集合长度变为0
boolean contains(Object o)返回集合里是否包含指定元素
boolean containsAll(Collection c)返回集合里是否包含集合c里的所有元素
int hashCode()返回此collection的哈希码值
boolean isEmpty()返回集合是否为空,当集合长度为0时,返回true
Iterator iterator()返回一个Iterator对象,用于遍历集合里的元素
boolean remove(Object o)删除集合中指定元素o,当集合中包含了一个或多个元素o时,这些元素将被删除,该方法将返回true
boolean removeAll(Collection c)从集合中删除集合c里包含的所有元素,如果删除了一个或一个以上的元素,返回true
boolean retainAll(Collection c)从集合中删除不在集合c里包含的元素,如果删除了一个或一个以上的元素,返回true
int size()返回集合中的元素个数
Object[] toArray()该方法把集合转换成一个数组,所有集合元素变成对应的数组元素

【示例】

public class Collection_01 {

	public static void main(String[] args) {
		// 创建集合
		Collection c1 = new ArrayList();

		// 自动装箱为 Integer 类型,然后向上转型为 Object 类型,发生多态
		c1.add(1);
		c1.add(1);
		c1.add(1);
		c1.add(1);
		c1.add(1.2);
		c1.add(true);
		c1.add(new Collection_01());
		c1.add(new Object());
		System.out.println(c1.size());// 8
		System.out.println(c1.isEmpty());// false

		// 直接遍历集合
		for (Object object : c1) {
			System.out.println(object);
		}
//		1
//		1
//		1
//		1
//		1.2
//		true
//		com.Collection_01@15db9742
//		java.lang.Object@6d06d69c

		// 把集合转换为 Object 数组,再遍历
		Object[] arr = c1.toArray();
		for (Object object : arr) {
			System.out.println(object);
		}

		c1.clear();
		System.out.println(c1.size());// 0
	}

}
  • 删除和判断是否包含

boolean contains(Object o)
判断是否包含某个元素,底层会自动调用该对象的equals
c.contains(m1) : m1调用equals方法,和集合中的元素进行比较,

boolean remove(Object o) 删除集合中某个元素 也会调用 equals方法进行比较

所以 我们再使用 remove和contains的时候,注意覆写equals方法,因为删除和判断是否包含是需要通过equals方法定位元素的
但是 Object中的equals方法默认比较内存地址,所以我们需要根据需求进行equals方法重写

public class Collection_03 {
	public static void main(String[] args) {
		// 创建集合
		Collection c = new ArrayList();
		Integer i1 = new Integer(129);
		Integer i2 = new Integer(129);
		// 插入 i1
		c.add(i1);
		System.out.println(c.contains(i2));// true

		Manager m1 = new Manager(1, "张三");
		Manager m2 = new Manager(1, "张三");
		c.add(m1);
		System.out.println(c.contains(m2));// true
		System.out.println(c.size());// 2
		c.remove(m2);
		System.out.println(c.size());// 1
	}
}

class Manager {
	int no;
	String name;

	public Manager(int no, String name) {
		super();
		this.no = no;
		this.name = name;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj instanceof Manager) {
			Manager m = (Manager) obj;
			if (m.no == this.no && m.name.equals(this.name)) {
				return true;
			}
		}
		return false;
	}
}

2. Iterator

【介绍】

  1. Collection接口的iterator()和toArray()方法都用于获得集合中的所有元素,前者返回一个Iterator对象,后者返回一个包含集合中所有元素的数组。
  2. Iterator接口隐藏底层集合中的数据结构,提供遍历各种类型集合的统一接口。
Iterator it = 集合对象.iterator();

调用集合对象自己的iterator() 就能创建这个集合的迭代器

迭代器中有三个方法

  1. boolean hasNext() : 判断当前游标的下一位是否还有元素,有就返回true,没有就返回false
  2. E next() : 将迭代器的游标向下移动一位,并返回这个数据
  3. remove() : 删除当前游标指向的元素
  • 原则上,上面三个方法必须按照这个顺序调用

迭代器一旦创建,集合中不能添加或删除元素,如果添加或删除了集合中的元素,需要重新生成迭代器
但是更改里面的数据 不用重新生成

  • 使用迭代器
public class Collection_02 {

	public static void main(String[] args) {
		Collection c = new ArrayList();
		c.add(1);
		c.add("1wq3");
		c.add(false);
		c.add(12.32);

		// 创建迭代器
		Iterator it = c.iterator();

		// 如果添加或删除数据之后,一定要重新生成迭代器
		c.add(2);
		it = c.iterator();

		while (it.hasNext()) {
			Object o = it.next();
			System.out.println(o);
		}

		// 1.迭代器使用完成之后,不会自动复原,需要重新创建才能再次遍历
		Iterator it1 = c.iterator();
		while (it1.hasNext()) {
			Object o = it1.next();
			System.out.println(o);
		}
	}

}
  • 使用迭代器的时候,不能使用集合的删除,应该使用迭代器的删除

  • 实例 :

public class Collection_04 {

	public static void main(String[] args) {
//		Collection c = new ArrayList();
//		List l = new ArrayList();
		ArrayList c = new ArrayList();
		c.add(1);
		c.add(2);
		c.add(3);
		c.add(4);
		// ArrayList 有个 remove 方法重载,传入整型是删除对应的下标
//		c.remove(2);
//		for (Object object : c) {
//			System.out.println(object);
//		}
//		c.remove(1);
//		for (Object object : c) {
//			System.out.println(object);
//		}

		Iterator it = c.iterator();
		while (it.hasNext()) {
			Object object = it.next();
			// 不能使用迭代器的删除,迭代器创建后,集合不能添加删除元素
//			c.remove(object);//java.util.ConcurrentModificationException

			// 可以使用迭代器的删除,会把迭代器和集合中都删除
			// 删除当前指向的元素
			it.remove();
		}
	}

}

【接口主要方法】

方法描述
boolean hasNext()如果被迭代的集合有下一个元素,则返回true
Object next()返回集合里下一个元素
void remove()删除集合里上一次next方法返回的元素

【示例】

【for与iterator对比】

  • Iterator的好处在于可以使用相同方式去遍历集合中元素,而不用考虑集合类的内部实现。
  • 使用Iterator来遍历集合中元素,如果不再使用List转而使用Set来组织数据,则遍历元素的代码不用做任何修改
  • 使用for来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样
  • for循环需要下标

【示例】
使用Collection接口演示iterator访问不需要下标。(Collection接口没有提供下标访问)

【foreach增强循环】
foreach遍历集合相当于获取迭代器,通过判断是否有下一个元素,执行操作。遍历数组相当于经典的for循环。

优点缺点
遍历的时候更加简洁不能同时遍历多个集合
不用关心集合下标的问题。减少了出错的概率在遍历的时候无法修改和删除集合数据

3. List

【特点】

  • List是一个有序集合,既存入集合的顺序和取出的顺序一致
  • List集合允许添加的元素重复

List不单单继承了Collection的方法,还增加了一些新的方法。

方法描述
void add(int index, Object element)将元素element插入到List的index处
boolean addAll(int index, Collection c)将集合c所包含的所有元素都插入在List集合的index处
Object get(int index)返回集合index处的元素
int indexOf(Object o)返回对象o在List集合中出现的位置索引
int lastIndexOf(Object o)返回对象o在List集合中最后一次出现的位置索引
Object remove(int index)删除并返回index索引处的元素
Object set(int index, Object element)将index索引处的元素替换成element对象,返回新元素
List subList(int fromIndex, int toIndex)返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素组成的子集合
  • List

    List元素的特点 有序可重复,存入顺序和取出顺序一致,并且可以有多个重复元素

    ArrayList : 底层是数组 , 查询快,增删慢

    LinkedList : 底层是双向链表 , 增删快,查询慢

  • ArrayList :

默认初始化容量是10 , 扩大容量是原始容量的1.5倍
veator : 默认容量是10,扩大容量是2倍
ArrayLList是veator的升级版,所以vector已经废弃
vector是线程安全,执行效率低
ArrayList是非线程安全,执行效率高

【示例】

public class Collection_05_List_01 {
	@Override
	public String toString() {
		return "===";
	}

	public static void main(String[] args) {
		List l1 = new ArrayList();
		l1.add(1);
		l1.add("张三");
		l1.add(new Collection_05_List_01());
		System.out.println(l1);
		// [1, 张三, ===] , 因为输出一个 list 的时候,结构是这样的[元素1 , 元素2 , ...]
		// 会调用集合中每个元素自身的 toSting()方法

		// get : 获取指定索引对应的元素,等于 数组[index]
		for (int i = 0; i < l1.size(); i++) {
			System.out.println(l1.get(i));
		}
//		1
//		张三
//		===

		// 把元素插入到指定位置,原位置数据和原位置之后的数据向后移动一位
		l1.add(1, "你好");
		for (Object object : l1) {
			System.out.println(object);
		}
//		1
//		你好
//		张三
//		===

		// 用指定的元素把指定位置上的元素替换
		l1.set(2, "Hello");
		Iterator it = l1.iterator();
		while (it.hasNext()) {
			Object object = it.next();
			System.out.println(object);
		}
//		1
//		你好
//		Hello
//		===
	}

}
  • 排序

List想要对里面的数据进行排序,那么存入的元素必须实现一个接口 Comparable
Collections.sort(li);

实例 :

public class Collection_06_SortList {

	public static void main(String[] args) {
		List li = new ArrayList();
		li.add(2);
		li.add(1);
		li.add(5);
		li.add(4);
//		Arrays.sort(a);
		Collections.sort(li);
		for (Object object : li) {
			System.out.println(object);
		}
//		1
//		2
//		4
//		5

		A a = new A();
		a.add(123);
		a.add("张三");
		a.add(2);
		a.set(2, "李四");
		for (int i = 0; i < a.size(); i++) {
			System.out.println(a.get(i));
		}
//		123
//		张三
//		[Ljava.lang.Object;@15db9742
	}

}

class A {
	private Object[] elementData;
	private int index = 0;

	public A() {
		this.elementData = new Object[10];
	}

	public int size() {
		return index;
	}

	public void add(Object element) {
		elementData[index] = element;
		index++;
	}

	public void set(int index, Object object) {
		elementData[index] = elementData;
	}

	public Object get(int index) {
		return elementData[index];
	}
}
3.1 ArrayList、Vector

【特点对比】

  1. ArrayList和Vector都是基于数组实现的,两者用法差不多
  2. ArrayList随机查询效率高,随机增删元素效率较低
  3. Vector提供了一个Stack子类,模拟“栈”数据结构——”先进后出”
  4. ArrayList是线程不安全的,Vector是线程安全的
    【重点示例】
    ArrayList中存放对象,在判断包含或者删除元素时,通过equals比较是否为同一个对象,然后进行操作
3.2 LinkedList

【特点】

  • LinkedList是双向链表实现的
  • 随机查询效率低,随机增删效率高

【LinkedList新增方法】

方法描述
void addFirst(Object e)将指定元素插入该集合的开头
void addLast(Object e)将指定元素插入该集合结尾
boolean offerFirst(Object e)将指定元素插入该集合的开头
boolean offerLast(Object e)将指定元素插入该集合结尾
boolean offer(Object e)将指定元素插入该集合结尾
Object getFirst()获取,但不删除集合第第一个元素
Object getLast()获取,但不删除集合最后一个元素
Object peekFirst()获取,但不删除该集合第一个元素,如果集合为空,则返回null
Object peekLast()获取,但不删除该集合最后一个元素,如果集合为空,则返回null
Object pollFirst()获取,并删除该集合第一个元素,如果集合为空,则返回null
Object pollLast()获取,并删除该集合最后一个元素,如果集合为空,则返回null
Object removeFirst()获取,并删除该集合的第一个元素
Object removeLast()获取,并删除该集合的最后一个元素
Object pop()pop出该集合的第一个元素
void push(Object e)将一个元素push到集合

链表 : 链表中保存节点,节点中有三个元素,自身对象(添加的元素),下一个节点的地址,上一个节点的地址

  • LinkedList就是基于双向链表实现的

链表是没有下标的,所以查询慢, 当然我们可以通过 get方法 使用下标找到数据,

但是get方法中,也是通过循环,从首节点一步步向后找的,不像ArrayList,底层是数组,有下标,可以直接数组[下标],可以参考源码

【示例】

public class Collection_07_LinkedList {

	public static void main(String[] args) {
		LinkedList li = new LinkedList();
		// 尾部添加 成功返回true
		li.add(1);

		// 头部添加
		li.push(7);

		// 头部添加
		li.addFirst(2);

		// 尾部添加
		li.addLast(3);

		// 头部添加,成功返回true
		li.offerFirst(4);

		// 尾部添加,成功返回true
		li.offerLast(5);

		// 尾部添加,成功返回true
		li.offer(6);

		// 上面几个方法 , 本质调用的就是两个方法, linkLast和linkFirst 所以没有任何区别,主要为了解决大家的命名习惯问题
		// 4271356
		System.out.println(li);

		// 获取指定下标对应的元素
		System.out.println(li.get(2));
		System.out.println("--");
		// 获取首位元素
		System.out.println(li.getFirst());

		// 获取最后一个元素
		System.out.println(li.getLast());

		// 获取首位元素,并删除该元素,如果没有首位元素(就是集合中元素个数为0 ) 返回null
		System.out.println(li.poll());

		// 获取首位元素,并删除该元素,如果没有首位元素(就是集合中元素个数为0 ) ,报错
		// java.util.NoSuchElementException
		System.out.println(li.pop());

		for (Object object : li) {
			System.out.println(object);
		}
	}

}

【总结】

  • List主要有两个实现ArrayList和LinkedList,他们都是有顺序的,也就是放进去是什么顺序,取出来还是什么顺序
  • ArrayList——遍历、查询数据比较快,添加和删除数据比较慢(基于可变数组)
  • LinkedList——查询数据比较慢,添加和删除数据比较快(基于链表数据结构)
  • Vector——Vector已经不建议使用,Vector中的方法都是同步的,效率慢,已经被ArrayList取代
  • Stack——继承Vector实现的栈,栈结构是先进后出,但已被LinkedList取代

实例 :

public class Collection_08_LinkedList {

	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		LinkedList linkedList = new LinkedList();
		linkedList.add("a");
		linkedList.add("b");
		linkedList.add("c");
		linkedList.add("d");
		linkedList.remove("c");
		System.out.println(linkedList);
		// [a, b, d]
	}

}

4. Set

【特点】

  1. Set是一个无序集合,既存入集合的顺序和取出的顺序不一致
  2. Set集合中元素不重复

【常用方法】

方法描述
boolean add(E e)如果此set中尚未包含指定元素,则添加指定元素
boolean isEmpty()如果此set不包含任何元素,则返回true
boolean contains(Object o)如果此set包含指定元素,则返回 true
boolean remove(Object o)如果指定元素存在于此set中,则将其移除
int size()返回此set中的元素的数量
void clear()从此set中移除所有元素

【示例】

public class Collection_09_SortedSet_01 {

	public static void main(String[] args) throws ParseException {
		SortedSet ss = new TreeSet();
		ss.add(1);
		ss.add(22);
		ss.add(2);
		System.out.println(ss);
		// [1, 2, 22]
		ss = new TreeSet();
		ss.add("abc");
		ss.add("abd");
		ss.add("ba");
		ss.add("ac");
		System.out.println(ss);
		// [abc, abd, ac, ba]

		ss = new TreeSet();
		String st1 = "2008-08-08";
		String st2 = "2008-08-07";
		String st3 = "2008-09-01";
		String st4 = "2007-09-01";
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Date d1 = sdf.parse(st1);
		Date d2 = sdf.parse(st2);
		Date d3 = sdf.parse(st3);
		Date d4 = sdf.parse(st4);
		ss.add(d1);
		ss.add(d2);
		ss.add(d3);
		ss.add(d4);
		for (Object object : ss) {
			Date date = (Date) object;
			System.out.println(sdf.format(date));
		}
//		2007-09-01
//		2008-08-07
//		2008-08-08
//		2008-09-01

	}

}

实例 :

public class Collection_10_SortedSet_02 {

	public static void main(String[] args) {
		SortedSet users = new TreeSet();
		users.add(new User(19));
		users.add(new User(20));
		users.add(new User(1));
		users.add(new User(23));
		System.out.println(users.size());
		System.out.println(users);
	}

}

class User implements Comparable {
	int age;

	public User(int age) {
		super();
		this.age = age;
	}

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

	@Override
	public int compareTo(Object o) {
		if (!(o instanceof User)) {
			return 0;
		}
		User u1 = (User) o;
		return this.age - u1.age;
		// 返回0 说明集合中有这个元素,就不添加
		// 小于0 说明要添加的元素比集合中的元素小,往前放
		// 大于0 说明大,往后放
		// return -1;
	}

}
4.1 HashSet

哈希表 : 又叫散列表,用来保存键值对(key,value), 就是一个数组中,每个元素都是一个单向链表
根据添加元素的hash值,通过hash算法,就能够确定要把元素添加到哪个链表中

hash算法 : 一种安全加密算法,把不定长的值,更改为定长的值,不能保证其值的唯一性

其中算法包括 :

  1. 直接寻址法
  2. 数字分析法
  3. 平方取中法
  4. 随机数
  5. 除数取余法

java中屏蔽了散列数据结构,封装到了HashMap/HashSet/HashTable中,其中HashTable已经过时

hash算法 : java中指的是 hashCode()函数
目标 : 给每个对象生成一个唯一的标识符

散列表的数组下标还是0~N,只不过根据key得到的hash值,再通过hash算法就能得到这个元素所在的下标

hash的目的 : 为了查询快,hash值是一个固定的值,整型值,所以在定长的情况下,查询极快

HashSet和HashMap :

  1. HashSet 就是HashMap的一个封装,只不过把value值屏蔽了
  2. HashMap中是key,value 键值对 但是在HashSet中 只有 key

如何向hash表中添加数据

  1. 添加的时候,HashMap保存的是映射关系,这个映射关系靠 Map.Entry(K,V) 来维护,是一个接口
    单向链表 : 每个节点有三部分
    1 有一个Entry(K,V)
    2 next
    3 hash值

  2. 添加过程 : 先调用要存储的映射关系的key对象,调用key对象的hashCode()方法,生成hash码值

然后根据hash值通过hash算法进行hash,得到数组下标,然后想数组中添加元素
如果数组中还没有对应的hash的数据,就占用一个数组的空间,保存这个key-value的映射关系
如果有,就调用key的equals方法,挨个在hash值相同的链表中,进行比较,如果返回true,那么代表 该链表中有这个key
如果已经有这个元素.key不添加,value值覆盖原来的value
如果equals方法返回false,说明两个对象只是hash值相同,但是并不是同一个对象
那么就把该对象插入到链表中

HashSet 只保存一个元素
HashMap 保存键值对

添加的元素 要实现equals方法和hashCode方法
实例 :

public class Collection_13_Set_01 {

	public static void main(String[] args) {
		Set s = new HashSet();
		s.add(1);
		s.add(1);
		s.add(2);// id=1
		System.out.println(s);

		HashSet hs = new HashSet();
		Employee e1 = new Employee("1000", "obama");
		Employee e2 = new Employee("1000", "obalv");
		Employee e3 = new Employee("2000", "张三");
		Employee e4 = new Employee("3000", "张三");
		Employee e5 = new Employee("4000", "张三");
		Employee e6 = new Employee("5000", "李四");

//		System.out.println(e1.hashCode());
//		System.out.println(e2.hashCode());
//		System.out.println(e3.hashCode());
//		System.out.println(e4.hashCode());
//		System.out.println(e5.hashCode());
//		System.out.println(e6.hashCode());
//		hashCode执行了
//		1507423
//		hashCode执行了
//		1507423
//		hashCode执行了
//		1537214
//		hashCode执行了
//		1567005
//		hashCode执行了
//		1596796
//		hashCode执行了
//		1626587

		hs.add(e1);
		// 如果重复就不添加
		hs.add(e2);
		hs.add(e3);
		hs.add(e4);
		hs.add(e5);
		hs.add(e6);
		for (Object object : hs) {
			System.out.println(object);
		}
//		hashCode执行了
//		hashCode执行了
//		equals执行了
//		hashCode执行了
//		hashCode执行了
//		hashCode执行了
//		hashCode执行了
//		Employee [no=5000, name=李四]
//		Employee [no=4000, name=张三]
//		Employee [no=1000, name=obama]
//		Employee [no=2000, name=张三]
//		Employee [no=3000, name=张三]

	}

}

class Employee {
	String no;
	String name;

	public Employee(String no, String name) {
		super();
		this.no = no;
		this.name = name;
	}

	@Override
	public boolean equals(Object obj) {
		System.out.println("equals执行了");
		if (this == obj) {
			return true;
		}
		if (obj instanceof Employee) {
			Employee e = (Employee) obj;
			if (no.equals(e.no)) {
				return true;
			}
		}
		return false;
	}

	static int i = 16;

	@Override
	public int hashCode() {
		System.out.println("hashCode执行了");
		return no.hashCode();
	}

	@Override
	public String toString() {
		return "Employee [no=" + no + ", name=" + name + "]";
	}
}
4.2 TreeSet

TreeSet底层由TreeMap实现。可排序,默认自然升序。

  • 想要让TreeSet集合中元素进行排序 有三种方式
  1. 要添加的元素对应的类实现java.lang.Comparable接口,并实现compareTo方法

  2. 让SortedSet集合做到排序还有一种方法,java.util.Comparator比较器类

  3. 写一个匿名内部类 ,还是 java.util.Comparator

默认的一些类型中,都实现了比较器,比如Date,String,Integer等等

所以 我们自定义类一个数据类型的时候(类),当需要对这个类对象进行排序,就需要我们自定义比较器,用于比较

如果 两种排序方式都有的话,优先使用compare()方法
源码中是这样调用的,先判断有没有compare(),如果没有再去执行compareTo(),在TreeMap类中的1294行

Comparable : 我们叫做元素自身的比较器,就是要添加的元素需要实现这个接口,这种情况下一般用于我们自定义的类中

Comparator : 比较器类,常应用于:比如Integer是默认升序,我想降序,就需要使用Comparator来重新设置排序,因为它的优先级高

如果添加的元素不是我们自定义的类,

  1. 该类有排序(实现了Comparable), 但不是我们想要的排序效果,需要使用 Comparator 来进行调整排序,因为它的优先级高

  2. 如果该类没有排序(没有实现Comparable),我们就需要使用Comparator来设置排序,因为这个时候我们不可能去更改人家的源码,不可能让这个类去实现Comparable的

实例 :

public class Collection_11_SortedSet_03 {

	public static void main(String[] args) {
//		SortedSet products = new TreeSet(new ProductComparator());
		@SuppressWarnings("unchecked")
		SortedSet products = new TreeSet(new Comparator() {
		
			//匿名内部类实现
			@Override
			public int compare(Object o1, Object o2) {
				// o1是要添加的元素
				// o2是集合中的元素
				double price1 = ((Product1)o1).price;
				double price2 = ((Product1)o2).price;
				if (price1 == price2) {
					return 0;
				}else if (price1 > price2) {
					return 1;
				}else {
					return -1;
				}
			}
			

		});
		Product1 p1 = new Product1(1.5);
		Product1 p2 = new Product1(2.2);
		Product1 p3 = new Product1(0.5);
		products.add(p1);
		products.add(p2);
		products.add(p3);
		System.out.println(products);
		//[Product1 [price=0.5], Product1 [price=1.5], Product1 [price=2.2]]

	}

}
class Product1{
	double price;
	public Product1(double price) {
		super();
		this.price=price;
	}
	@Override
		public String toString() {
			return "Product1 [price="+price+"]";
		}
}

class ProductComparator implements Comparator{

	@Override
	public int compare(Object o1, Object o2) {
		// o1是要添加的元素
		// o2是集合中的元素
		double price1 = ((Product1)o1).price;
		double price2 = ((Product1)o2).price;
		if (price1 == price2) {
			return 0;
		}else if (price1 > price2) {
			return 1;
		}else {
			return -1;
		}
	}
	
}

如果一个类没有实现Comparable接口,那么把对象放到list中之后,不能直接调用sort(list)进行排序

但是可以使用sort的方法重载,sort(list,比较器类),来实现对list的排序
实例 :

public class Collection_12_SortList {

	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		List list = new ArrayList();
		list.add(new User1(18));
		list.add(new User1(12));
		list.add(new User1(23));

		Collections.sort(list, new Comparator() {

			@Override
			public int compare(Object o1, Object o2) {
				return ((User1) o1).age - ((User1) o2).age;
			}

		});
		System.out.println(list);
	}

}

class User1 {
	int age;

	public User1(int age) {
		super();
		this.age = age;
	}

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

【总结】

  • HashSet底层由HashMap实现
  • TreeSet底层由TreeMap实现
  • <详解见HashMap、TreeMap>

5. Collections工具类和Comparable、Comparator比较器

5.1 Collections工具类

Collections是一个包装工具类。它包含有各种有关集合操作的静态多态方法,此类不能实例化,服务于Java的Collection接口。
【常用方法】
sort、reverse、fill、copy、max、min、swap等
【重点讲解:Sort排序】
其他方法,我们自行练习

public static <T extends Comparable<? super T>> void sort(List list)

  1. 根据元素的自然顺序 对指定列表按升序进行排序。列表中的所有元素都必须实现 Comparable 接口。
  2. 此外,列表中的所有元素都必须是可相互比较的(也就是说,对于列表中的任何 e1 和 e2 元素,e1.compareTo(e2) 不得抛出 ClassCastException)。
    【示例】
    我们打开API查看String类,它是实现了Comparable接口的。所以,我们可以使用字符串对象为例,演示sort排序。
5.2 Comparable、Comparator比较器

对象排序,就是比较大小,要实现Comparable或Comparator比较器之一,才有资格做比较排序。
【介绍】

  1. Comparable:与对象紧相关的比较器,可以称“第一方比较器”。
  2. Comparator:此为与具体类无关的第三方比较器。

【示例】

四. Map接口

Map用于保存具有映射关系的数据,因此Map集合里保存两组值。

  1. 一组值用于保存key,一组值用于保存value
  2. key~value之间存在单向一对一关系,通过指定key可以找到唯一的value值
  3. key和value都可以是任何引用类型对象
  4. 允许存在value为null,但是只允许存在一个key为null

【常用方法】

方法描述
V put(K key, V value)将指定的值与此映射中的指定键关联
boolean containsKey(Object key)如果此映射包含指定键的映射关系,则返回true
boolean containsValue(Object value)如果此映射将一个或多个键映射到指定值,则返回true
boolean isEmpty()如果此映射未包含键-值映射关系,则返回true
V get(Object key)返回指定键所映射的值,如果此映射不包含该键的映射关系,则返回null
Set keySet()返回此映射中包含的键的set集合
Collection values()返回此映射中包含的值的Collection集合
Set<Map.Entry<K,V>> entrySet()返回此映射中包含的映射关系的set集合
boolean equals(Object o)返回指定的对象与此映射是否相等
int hashCode()返回此映射的哈希码值
V remove(Object key)如果存在一个键的映射关系,则将其从此映射中移除
void clear()从此映射中移除映射关系
int size()返回此映射中的键-值关系数

【示例】

1. HashMap类

【特点】

  • key无序不可重复
  • 底层是哈希表
    【哈希表实现原理】
  • HashMap实际上是一个"链表的数组"的数据结构,每个元素存放链表头结点的数组,即数组和链表的结合体。
  • 当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
  • 只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。
  • 如果这两个Entry的key通过equals比较返回true,新添加Entry的value将覆盖集合中原有Entry的value,但key不会覆盖。

在这里插入图片描述

【总结】

  • HashMap中key的hashCode值决定了<k,v>键值对组成的entry在哈希表中的存放位置。
  • HashMap中key通过equals()比较,确定是覆盖key对应的value值还是在链表中添加新的entry。
  • 综合前两条,需要重写hashCode()和equals()方法。
    Map : 无序可重复 value可重复 , key不可重复

Map和Collection集合不一样,但是操作都是类似的

put(key,value) 添加数据

clear()

size()

isEmpty();

get(key) 根据key 获取value值

values() : 获取所有的value,以集合形式返回

containsKey(key)

containsValue(value)

keySet() 获取所有key,以set形式返回

entrySet() 获取键值对,以set形式返回

remove()

map不能直接遍历

【示例】

public class Collection_14_Map_01 {

	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		Map map = new HashMap();
		map.put("A", "one");
		map.put("B", "two");
		map.put("C", "three");
		map.put(1003, "rose"); // Integer
		map.put('A', "1000");
		map.put(65, "1000");
		map.put("'A'", "3000");
		map.put("A", "2000"); // value 覆盖
		// 可以什么都是 null ,但是没什么意义
		// key 只能有一个为null,因为唯一,value可以有很多null
		map.put(null, null);

		// 8个
		System.out.println(map.size());

		// false
		System.out.println(map.containsKey("1003"));

		// true
		System.out.println(map.containsValue("2000"));

		// 2000
		System.out.println(map.get("A"));

		System.out.println("---");

		// 获取所有的value
		Collection values = map.values();
		for (Object object : values) {
			System.out.println(object);
		}
//		null
//		2000
//		1000
//		1000
//		two
//		three
//		rose
//		3000
		System.out.println("==--");
		// 根据key删除,返回对应的value值
		map.remove(null);
		System.out.println("===");

		// 把map中的key取出,形成set
		Set s = map.keySet();
		for (Object object : s) {
			System.out.println(object + " : " + map.get(object));
		}
//		A : 2000
//		A : 1000
//		65 : 1000
//		B : two
//		C : three
//		1003 : rose
//		'A' : 3000
		System.out.println("-==-");

		// 把map中的所有entry 取出,形成Set
		// 并且 entry 覆写了 toString方法,会以 key=value的形式展示
		Set set = map.entrySet();
		for (Object object : set) {
			System.out.println(object);
		}
//		A=2000
//				A=1000
//				65=1000
//				B=two
//				C=three
//				1003=rose
//				'A'=3000
		System.out.println("---");
		System.out.println(1);
		System.out.println("1");
		System.out.println('1');
	}

}

2. TreeMap

TreeMap是SortedMap接口的实现类,可以根据Key进行排序,HashMap没有这个功能。
【特点】

  • 底层由可排序二叉树实现
  • 不指定比较器默认按照key自然升序,指定比较器按照比较器排序

【示例】

  • 匿名内部类实现
package com;

import java.util.Comparator;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

public class Collection_15_SortedMap_01 {

	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		@SuppressWarnings("unchecked")
		SortedMap products = new TreeMap(new Comparator() {

			@Override
			public int compare(Object o1, Object o2) {
				double price1 = ((Product) o1).price;
				double price2 = ((Product) o2).price;
				if (price1 == price2) {
					return 0;
				} else if (price1 < price2) {
					return -1;
				} else {
					return 1;
				}
			}

		});
		Product p1 = new Product("water", 1.0);
		Product p2 = new Product("苹果", 3.0);
		Product p3 = new Product("香蕉", 2.0);
		Product p4 = new Product("梨", 1.5);

		// value 可以设置为已购买的数量
		products.put(p1, 4.0);
		products.put(p2, 2.0);
		products.put(p3, 1.0);
		products.put(p4, 4.0);

		Set keys = products.keySet();
		for (Object key : keys) {
			Product p = (Product) key;
			double value = (double) products.get(key);
			System.out.println(p + "->" + value + "kg 总价 = " + (value * p.price));
		}
//		Product [name=water, price=1.0]->4.0kg 总价 = 4.0
//				Product [name=梨, price=1.5]->4.0kg 总价 = 6.0
//				Product [name=香蕉, price=2.0]->1.0kg 总价 = 2.0
//				Product [name=苹果, price=3.0]->2.0kg 总价 = 6.0

	}
}

class Product {
	String name;
	double price;

	public Product(String name, double price) {
		super();
		this.name = name;
		this.price = price;
	}

	@Override
	public String toString() {
		return "Product [name=" + name + ", price=" + price + "]";
	}
}
  • 元素自身覆写了 compareTo 方法
package com;

import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

public class Collection_16_SortedMap_02 {

	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		SortedMap products = new TreeMap();

		Product2 p1 = new Product2("water", 1.0);
		Product2 p2 = new Product2("苹果", 3.0);
		Product2 p3 = new Product2("香蕉", 2.0);
		Product2 p4 = new Product2("梨", 1.5);

		// value 可以设置为 已购买的数量
		products.put(p1, 4.0);
		products.put(p2, 2.0);
		products.put(p3, 1.0);
		products.put(p4, 4.0);

		Set keys = products.keySet();
		for (Object key : keys) {
			Product2 p = (Product2) key;
			double value = (double) products.get(key);
			System.out.println(p + "--->" + value + "kg 总价 = " + (value * p.price));
		}

	}
}

class Product2 implements Comparable {
	String name;
	double price;

	public Product2(String name, double price) {
		super();
		this.name = name;
		this.price = price;
	}

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

	@Override
	public int compareTo(Object o) {
		double price1 = this.price;
		double price2 = ((Product2) o).price;
		if (price1 == price2) {
			return 0;
		} else if (price1 < price2) {
			return -1;
		} else {
			return 1;
		}
	}

}
  • 再来一个匿名内部类:
package com;

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

public class Collection_17_SortedMap_03 {

	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		Set set = new TreeSet(new Comparator() {

			@Override
			public int compare(Object o1, Object o2) {
				int a = ((Integer) o1);
				int b = ((Integer) o2);
				return b - a;
			}
		});
		set.add(1);
		set.add(5);
		set.add(3);
		set.add(2);

		System.out.println(set);
	}

}

Map转list

Map 转换为 List 并以 value 进行排序

实例 :

public class Collection_21_MapToList {

	public static void main(String[] args) {
		Map<String, Integer> map = new HashMap<String, Integer>();
		map.put("a", 1);
		map.put("c", 2);
		map.put("d", 1);
		map.put("b", 12);

		// 因为 map 中存储的是entry,所以想要把 map 转换为 list ,那么list 中的元素应该就是 Entry类型

		// 先把entry拿出来
		Set set = map.entrySet();

		// 调用ArrayList 的有参构造,把 set 直接转换为 List
		List<Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(set);

		Collections.sort(list, new Comparator<Entry<String, Integer>>() {

			@Override
			public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
				// TODO Auto-generated method stub
				return o1.getValue() - o2.getValue();
			}
		});

		System.out.println(list);
		// [a=1, d=1, c=2, b=12]

	}

}

【总结】
TreeMap需要key实现Comparable接口,排序主要看compareTo()方法。

五. 泛型

泛型是指所操作的数据类型被指定为一个参数,在用到的时候再指定具体的类型。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
【示例】

  • 定义一个对象池对象,可以根据需求存放各种类型对象。
class ProjectPool<T>{
    private List<T> list = new ArrayList<T>();
    public void add(T t){
        list.add(t);
    }
    public int size(){
        return list.size();
    }
}
  • 什么是泛型

类型检查
编译过程中,检查数据类型是否匹配

  • 什么是泛型 :
    集合实际上和数组一样,只能存储统一数据类型,只不过这个类型是Object,所有元素都会发生向上转型,所以导致什么都能放

泛型不能使用基本数据类型,只能使用引用数据类型

转型为多态之后,子类的属性就访问不到了,想要使用,必须向下转型,很麻烦

类似于中药柜子

优点 : 统一类型,减少类型转换
缺点 : 只能存储单一类型的元素
实例 :

public class Collection_18_Generic_01 {

	public static void main(String[] args) {
		Set s = new HashSet();
		A a = new A();
		B b = new B();
		C c = new C();
		s.add(a);
		s.add(b);
		s.add(c);

		for (Object object : s) {
//			A d = (A)object;
			if (object instanceof A) {
				A d = (A) object;
				d.m1();
			} else if (object instanceof B) {
				B d = (B) object;
				d.m1();
			}
		}
//		A 的 m1方法
//		B 的 m1方法

		List<D> ds = new ArrayList<D>();
		ds.add(a);
		ds.add(b);
		ds.add(c);
		for (D d : ds) {
			d.m1();
		}
//		A 的 m1方法
//		B 的 m1方法
//		C 的 m1方法
	}

}

interface D {
	public void m1();
}

class A implements D {
	public void m1() {
		System.out.println("A 的 m1方法");
	}
}

class B implements D {
	public void m1() {
		System.out.println("B 的 m1方法");
	}
}

class C implements D {
	public void m1() {
		System.out.println("C 的 m1方法");
	}
}

使用大写字母A,B,C,D…X,Y,Z定义的,就都是泛型,把T换成A也一样,这里T只是名字上的意义而已

? 表示不确定的java类型
T (type) 表示具体的一个java类型
K V (key value)分别代表java键值中的Key Value
E (element) 代表Element

如果人家规定了泛型,我们没有传递类型使用的情况,下, 泛型类型 就等于是Object
实例 :

public class Collection_19_Generic_02 {

	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		// 只能添加String
		// list.add(1);
		list.add("aaa");
		list.add("aadsaa");
		list.add("aaa321");

		// 不需要强制类型转换
		for (String string : list) {
			System.out.println(string);
		}
//		aaa
//		aadsaa
//		aaa321

		List<User> users = new ArrayList<User>();
		users.add(new User(12));
		users.add(new User(11));
		users.add(new User(15));
		for (User user : users) {
			System.out.println(user.age);
		}
//		12
//		11
//		15

		Map<String, Integer> map = new HashMap<String, Integer>();
		map.put("sad", 2);
	}

}

class User {
	int age;

	public User(int age) {
		super();
		this.age = age;
	}

}
  1. 集合中使用泛型
List<Student> stuList = new ArrayList<Student>();

实例 :

package com;

public class Collection_20_Generic_03 {

	public static void main(String[] args) {
		MyClass myClass = new MyClass();
		myClass.m1(1);
		myClass.m1("asd");
//		1
//		asd

		MyClass<String> myClass2 = new MyClass();
//		myClass2.m1(2);
		myClass2.m1("asdf");
	}

}

class MyClass<T> {
	public void m1(T t) {
		System.out.println(t);
	}
}

【总结】

  1. 泛型能更早的发现错误,如类型转换错误
  2. 使用泛型,那么在编译期将会发现很多之前要在运行期发现的问题
  3. 代码量往往会少一些、运维成本减少
  4. 抽象层次上更加面向对象
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值