集合
集合非常的重要,在开发中使用的非常之多,是重点中的重点。
1、集合概述
1.1、什么是集合
数组其实就是一个集合。集合实际上就是一个容器。可以用来容纳其他类型的数据。
集合在开发中使用的非常多,因为集合本身就是一个容器,是一个载体,可以一次容纳多个对象。在实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这十条记录查询出来,Java程序会把这十条数据封装到10个Java对象,然后将10个对象放到某个一个集合当中,再将这个集合传到前端,使用遍历的方式将数据展现出来。
集合不能直接存储基本数据类型,另外集合也不能直接存储Java对象,集合当中存储的都是对象的内存地址。(或者说集合中存储的是引用)
list.add(100); // 自动装箱Integer
注意:集合在Java中本身是一个容器,是一个对象,其中的任何存储的都是“引用”。
在Java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构中。什么是数据结构?数据存储的结构就是数据结构,不同的数据结构数据存储的方式不同。例如:数组、二叉树、链表、哈希表……
new ArrayList(); // 创建一个集合,底层是数组
new LinkedList(); // 创建一个集合对象,底层是链表
new TreeSet(); // 创建一个集合对象,底层是二叉树
1.2、在Java中集合分为两大类
一类是以单个方式存储元素
单个方式存储元素,这一类集合中超级父接口:java.util.Collection
一类是以键值对的方式存储元素
以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map
1.3、Java集合继承的体系
Java中所有的集合类和接口都在java.util.*包下。
1.3.1 Collection集合继承结构图:
1.3.2 Map集合继承结构图:
1.3.3 实现类总结
- ArrayList:底层是数组。
- LinkedList:底层是双向链表。
- Vector:底层是数组,线程安全的,效率较低,使用较少。
- HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了。
- HashMap:底层是哈希表。
- Hashtable:底层也是哈希表,只不过线程是安全的,效率较低,使用较少。
- Properties:是线程安全的,并且key和value只能存储字符串String。
- TreeMap:底层是二叉树。TreeMap集合的key可以自动按照大小顺序排序。
List集合存储元素的特点:
有序可重复
有序:存进去的顺序和取出来的顺序相同,每一个元素有下标。
可重复:存进去1,可以在存储一个1.
Set(Map)集合存储元素的特定:
无序不可重复
无序:存进去的顺序和取出来的顺序不一定相同。另外Set集合中元素没有下标。
不可重复:存进去1,不能在存储1了。
SortedSet(SortedMap)存元素的特点:
首先是无序不可重复的,但是SortedSet集合中的元素是可排序的。
无序:存进去的顺序和取出来的顺序不一定相同。另外Set集合中元素没有下标。
不可重复:存进去1,不能在存储1了。
可排序:可以按照大小顺序排序。
Map集合的key,就是一个Set集合。
往Set集合中放数据,实际上放到了Map集合的Key部分。
1.4、Collection集合中常用方法
package javase.collection;
import java.util.ArrayList;
import java.util.Collection;
/*
关于java.util.Collection接口中的常用方法。
1、Collection中能存储的元素:
没有使用“泛型”之前,Collection中可以存储Object的所有子类型。
使用了“泛型”之后,Collection中只能存储某个具体的类型(集合后期会讲解“泛型”的语法)。
Collection中什么都能存储,只要是Object子类型就行(集合中不能直接存储基本数据类型,
也不能直接存放Java对象,只能存放Java对象的内存地址)。
2、Collection中的常用方法
boolean add(Object e) 向集合中添加元素
int size() 返回次collection中元素的个数
void clear() 清空collection中所有的元素
boolean contains(Object o) 判断collection中是否包含对象o,包含返回true
boolean remove(Object o) 删除collection中的某个元素
boolean isEmpty() 判断 collection 中的元素是否为 0
Object[] toArray() 将 collection 转换为Object数组
*/
public class CollectionTest01 {
public static void main(String[] args) {
// 创建一个集合对象
// Collection c =new Collection(); // 接口是抽象类,无法实例化
// 多态
Collection c = new ArrayList();
// 测试Collection接口中的常用方法
c.add(200); // 自动装箱(java5新特性),实际上放进去了一个对象的内存地址。即new Integer(200);
c.add(3.14); // 自动装箱
c.add(new Object());
c.add(new Student());
c.add(true); // 自动装箱
// 获取集合中元素的个数
System.out.println("集合中元素个数是 : " + c.size()); // 5
// 清空c中的元素
c.clear();
System.out.println("集合中元素个数是 : " + c.size()); // 0
// 向集合中添加元素
c.add("hello"); // "hello"对象的内存地址放到了集合当中。
c.add("world");
c.add("java");
c.add(1);
// 判断 c 中是否包含某个对象
System.out.println(c.contains("java")); // true
System.out.println(c.contains("php")); // false
System.out.println(c.contains(1)); // true
// 删除 c 中的1
c.remove(1);
System.out.println(c.contains(1)); // false
// 清空元素
c.clear();
//添加元素
c.add("java");
c.add("php");
c.add("hello world");
// 转换为数组(了解,使用不多)
Object[] objs = c.toArray();
for (Object obj: objs){
// 遍历数组
System.out.println(obj); // 输出引用时,自动调用toString()方法
}
}
}
class Student{}
1.5、集合迭代Iterator
注意:集合结构只要发生改变,迭代器必须重新获取。
package javase.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* 关于集合迭代(遍历)专题。(重点:五颗星*****)
*
*/
public class CollectionTest02 {
public static void main(String[] args) {
// 注意:以下讲解的遍历方式/迭代方式,是所有Collection通用的一种方式,
// 但是在Map集合中不能使用,在所有的Collection以及子类中使用。
// 创建集合对象
// ArrayList 有序可重复
Collection c = new ArrayList();// 后面的集合无所谓,主要是看前面的Collection接口怎么迭代。
// 添加元素
c.add("abc");
c.add("def");
c.add(100);
c.add(new Object());
// 对集合Collection进行迭代
// 第一步:获取集合对象的迭代对象Iterator
Iterator iterator = c.iterator();
// 第二步:通过以上获取的迭代器对象开始迭代
/*
以下两个方法是迭代器对象Iterator中的方法:
boolean hasNext(); 如果仍有元素可以迭代,则返回 true
Object next(); 返回迭代的下一个元素
*/
while (iterator.hasNext()){ // 如果这里iterator.hasNext()写成true会出现:java.util.NoSuchElementException
// 不管当初存进去的是什么,取出来统一都是Object
System.out.println(iterator.next());
}
}
}
1.6、contains()
资源地址:https://www.bilibili.com/video/BV1Rx411876f?p=673&spm_id_from=pageDriver
package javase.collection;
import java.util.ArrayList;
import java.util.Collection;
/**
* 测试contains()方法
* 结论:放在一个集合中的类型,一定要重写equals()方法。
*/
public class CollectionTest04 {
public static void main(String[] args) {
// 创建集合
Collection users = new ArrayList();
// 创建两个User对象
User u1 = new User("jack");
User u2 = new User("jack");
// 将 u1 加入users 集合
users.add(u1);
// 没有重写equals()方法之前
// System.out.println(users.contains(u2)); // false
// 重写equals()方法之后
// 由于u1和u2的username都是"jack",即它们在堆中保存的内存地址是相同的,由于User类的equals方法已经重写了。equals在判断的时候返回true。
// 而contains()方法底层调用的是equals,由于equals返回true,故contains()方法也返回true。
System.out.println(users.contains(u2)); // true
// 例如
Integer x = new Integer(1000);
users.add(x); // 将x加入到users集合中
Integer y = new Integer(1000);
// 判断user集合中是否包含y
System.out.println(users.contains(y)); // true
}
}
class User{
private String username;
public User(){}
public User(String username){
this.username = username;
}
// 重写equals()方法
// 将来调用equals()方法的时候,一定是调用这个重写了的equals()方法。
// 这个equals()方法的比较原理是:只要姓名一样同一个用户。
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
// username = "jack" , user.username = "jack"
// return Objects.equals(username, user.username); // idea 生成
// 这里调用String中已经重写了的equals()方法
return this.username.equals(user.username);
}
}
1.7、remove()
package javase.collection;
import java.util.ArrayList;
import java.util.Collection;
/**
* 测试remove()
* 结论:集合中的元素一定要重写equals()方法
*/
public class CollectionTest05 {
public static void main(String[] args) {
Collection cc = new ArrayList();
// 加入两个"hello"字符串对象
String s1 = "hello";
String s2 = "hello";
cc.add(s1); // s1加入集合cc
System.out.println(cc.size()); // 1
// 这里的s2并没有加入cc中,但是remove()底层还是会调用equals()方法,Java在执行equals()方法的时候认为s1与s2是相同的。
// 所以在删除s2的时候实际上也将s1删除了。
cc.remove(s2); // 删除s2
System.out.println(cc.size()); // 0
}
}
1.8、集合中元素的删除
package javase.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* 关于集合元素的remove
* 重点:当集合的结构发生改变是,迭代器必须重新获取,如果还是使用以前老状态的迭代器,会出现
* 异常:java.util.ConcurrentModificationException
*
* 重点:在迭代集合元素的过程中,不能调用集合对象的remove()方法来删除元素,如:
* c.remove(o); 在迭代的过程中不能这样,否则会出现:java.util.ConcurrentModificationException
*
* 注意点:在使用迭代器删除集合中的元素时,一定要使用迭代器Iterator中的remove()方法删除,不能使用集合自带的remove()方法删除元素。
*/
public class CollectionTest06 {
public static void main(String[] args) {
// 创建集合
Collection c = new ArrayList();
// 注意:此时获取到的迭代器指向的是那时集合中没有元素状态下的迭代器。
// 一定要注意:集合结构只要发生改变,迭代器必须重新获取。
// 当集合结构发生了改变,迭代器没有重新获取时,调用next()方法时会出现:java.util.ConcurrentModificationException异常。
Iterator it = c.iterator();
// 添加元素
c.add(1); // Integer类型 , 即“自动装箱”
c.add(2);
c.add(3);
// 遍历
// 因为这里的it迭代器是在向集合中添加元素之前的状态,然而集合中添加元素之后,集合的状态改变了,但是it并没有更新,所以出现异常。
/*
while (it.hasNext()) {
// 编写代码是next()方法返回值类型必须是Object
Object obj = it.next();
System.out.println(obj); // 抛出异常:java.util.ConcurrentModificationException
}
*/
Collection c2 = new ArrayList();
c2.add("abc");
c2.add("def");
c2.add("xyz");
Iterator it2 = c2.iterator();
while (it2.hasNext()) {
Object obj = it2.next();
// 删除元素
// 在删除元素之后,集合的结构发生了变化,应该重新获取迭代器,但是循环下一次并没有重新获取迭代器,所以会出现异常。
// 出现异常的根本原因:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合发生变化了)。
// c2.remove(); // 直接通过集合去删除元素,没有通知迭代器。
// 使用迭代器来删除可以吗?
// 迭代器去删除时,会自动更新迭代器和集合(即也会删除集合中的元素)。
it2.remove(); // 迭代器删除的一定是迭代器指向的当前元素
System.out.println(obj);
}
System.out.println(c2.size()); // 0
}
}
2、List
2.1、List常用方法
package javase.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 1、List集合存储元素特点:有序可重复。
* 有序:List集合中的元素有下标
* 可重复:存储一个1,还可以在存储一个1.
* 2、List既然是Collection接口的子接口,那么肯定List接口有自己“独一”方法。
* 以下只列出List接口特有常用的方法:
* void add(int index, Object element); 在列表的指定位置插入指定元素
* Object get(int index);
* int indexOf(Object o);
* int lastIndexOf(Object o);
* Object remove(int index); 移除列表中指定位置的元素
* Object set(int index, Object element); 用指定元素替换列表中指定位置的元素
*
* 以上的这些方法只需要编写代码测试一下,不用硬记,以后开发的时候还是需要翻阅帮助文档。
*/
public class ListTest01 {
public static void main(String[] args) {
// 创建List类型的集合
List list = new ArrayList();
// 添加元素
// add()方法默认在列表的最后添加元素,这个方法是用的比较多。
list.add("a");
list.add("b");
list.add("c");
list.add("d");
// 使用add(int index, Object element)添加元素,这个方法使用不多,因为对于ArrayList集合来说效率比较低。
list.add(1, "java"); // 在列表的第二个位置添加字符串"java"
// 迭代
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// 根据下标获取元素
System.out.println(list.get(1)); // java
// 因为有下标,所以List集合有自己比较特殊的遍历方式,通过下标进行遍历。【List集合特有的方式,Set没有】
for (int i = 0; i < list.size(); i++) {
Object obj = list.get(i);
System.out.print(obj + " "); // a java b c d
}
System.out.println();
// 获取指定对象第一次出现处的索引
System.out.println(list.indexOf("java")); // 1
// 获取指定对象最后一次出现的索引
System.out.println(list.lastIndexOf("c")); // 3
// 删除集合中的指定元素
list.remove(1);
System.out.println(list.size()); // 4
// 用指定元素替换列表中指定位置的元素
list.set(1, "JavaSE");
for (Object o : list) {
System.out.print(o + " ");
}
}
}
2.2、ArrayList初始化及扩容
建议阅读一下jdk中的源代码。
package javase.ArrayList;
import java.util.ArrayList;
import java.util.List;
/**
* ArrayList集合:
* 1、默认初始化容量 10 (底层优先创建了一个长度为0的数组,当添加第一个元素的时候,将容量初始化为10)。
* 2、集合底层是一个Object[]数组。
* 3、构造方法:
* new ArrayList();
* new ArrayList(20); // 这里的20是指定数组的大小
* ArrayList(Collection <? extends E> c)
* 第三种方式:
* // 创建一个HashSet集合
* Collection c = new HashSet();
* c.add(100);
* c.add(200);
* c.add(300);
* // 通过这个构造方法可以将HashSet集合转换成List集合
* List list = new ArrayList(c);
* 4、ArrayList集合的扩容:
* 增长到原容量的1.5倍。
* ArrayList集合底层是数组,怎么优化?
* 尽可能少的扩容。因为数组扩容的效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。
* 5、数组优点:检索效率很高(每个元素占用空间大小相同,内存地址又是连续的,知道首元素内存地址,然后知道下标,通过数据表达式
* 计算出元素的内存地址,所以检索效率很高)。
* 6、缺点:
* 随机增删元素效率较低(末尾处添加或删除元素除外)。
* 另外数组无法存储大数据量(很难找到一块非常巨大的连续的内存空间)。
* 7、面试官经常问的一个问题:
* 这个多的集合中,那个集合使用最多?答:ArrayList集合。
* 因为往数组末尾添加元素,效率不会受到影响。另外,我们检索某个元素的操作比较多。
* 8、ArrayList不是线程安全的,效率较高。
*
*/
public class ArrayListTest01 {
public static void main(String[] args) {
// 默认初始化容量是 10
// 数组的长度是 10
List list1 = new ArrayList();
// 集合中的size()方法获取当前集合中元素的个数,不是获取集合的容量。
System.out.println(list1.size()); // 0
// 指定初始化容量,数组的长度指定为20
List list2 = new ArrayList(20);
System.out.println(list2.size()); // 0
}
}
2.3、LinkedList
2.3.1、单向链表
代码实现单向链表:
Link.java
package javase.singleLink;
/*单链表*/
public class Link {
// 头结点
private Node header = null;
// 链表元素的个数
private int size = 0;
// 统计列表元素的个数
public int size(){
return size;
}
// 向链表中添加元素的方法(在末尾添加)
public void add(Object data){
// 创建一个新的节点对象,让之前单链表的末尾节点next指向新节点对象。
// 有可能这个元素是第一个,也可能是第二个,也可能是第三个。
if (header == null){
// 说明还没有节点
// new 一个新的节点对象,作为头节点对象
header = new Node(100, null);
} else {
// 说明头节点不为空,头结点已经存在了。找出当前末尾节点,让当前末尾节点的next是新节点。
Node currentLastNode = findLast(header);
currentLastNode.next = new Node(data, null);
}
size++; // 每添加一个元素 size加1
}
// 使用递归的方式来查找最后一个节点
private Node findLast(Node node) {
if (node.next == null){ // 如果一个节点的next为null,这说明当前这个节点就是最后一个节点。
return node;
}
// 程序能够到这里说明当前的node节点不是最后一个节点。
return findLast(node.next);
}
}
// 节点类
// 每一个节点都有两个属性,一个用来保存数据,另一个用来保存下一个节点的内存地址(也是节点)
class Node{
// 数据
Object data;
// 下一个节点
Node next;
public Node(){}
public Node(Object data, Node next){
this.data = data;
this.next = next;
}
}
Test.java
package javase.singleLink;
public class Test {
public static void main(String[] args) {
Link link = new Link();
link.add(100);
link.add(200);
link.add(300);
System.out.println(link.size()); // 3
}
}
2.3.2、双向链表
视频资源:https://www.bilibili.com/video/BV1Rx411876f?p=686&spm_id_from=pageDriver
2.3.3、LinkedList原理
package javase.LinkedList;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* 链表的优点:
* 由于链表上的元素在空间存储上内存地址不连续,所有随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
* 在以后开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList。
*
* 链表的缺点:
* 不能通过数据表达式计算被查找元素的内存地址,每一次查找都是从头开始遍历,知道找到为止。
* 所以LinkedList集合检索的效率比较低。
* ArrayList:把检索发挥到极致,由于加元素大多数情况都是王末尾添加,所以ArrayList用的比LinkedList多。
* LinkedList:把随机增删发挥到极致。
*
*/
public class LinkedListTest01 {
public static void main(String[] args) {
// LinkedList集合底层也是有下标的。
// 注意:ArrayList之所以检索效率标记高,不是单纯因为下标的原因,还有底层数组发挥的主要作用。
// LinkedList集合照样有下标,但是检索某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历。
List list = new LinkedList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
// 通过下标遍历LinkedList中的元素
for (int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.print(o + " ");
}
System.out.println();
// LinkedList集合有初始化容量吗?答案:没有。
// 最初这个链表中没有任何元素,first和last引用都是null,不管是LinkedList还是ArrayList,以后写代码时不需要关心具体是那个集合。
// 因为我们是面向接口编程,调用的方法都是接口中的方法。
// List list1 = new ArrayList(); // 这样写表示底层使用了数组。
List list1 = new LinkedList(); // 这样写表示底层使用了双向链表。
list1.add("abc");
list1.add("def");
list1.add("xyz");
list1.add("java");
for (int i = 0; i < list1.size(); i++) {
System.out.print(list1.get(i) + " ");
}
System.out.println();
}
}
2.4、Vector
java.util.Collections包下面有一个synchronizedList()方法可以将非线程安全的ArrayList集合转变为线程安全的ArrayList集合。
package javase.Vector;
/**
* Vector:
* 1、底层也是一个数组
* 2、初始化容量:10
* 3、怎么扩容?
* 扩容之后是原容量的两倍。 10 -> 20 -> 40
* 4、ArrayList集合扩容特点:ArrayList集合扩容是原容量的1.5倍。10 -> 15
* 5、Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的,效率比较低,使用较少。
* 6、怎么将一个线程不安全的ArrayList集合转换为线程安全的?
* 使用结合工具类:
* java.util.Collections
* java.util.Collection 是集合接口
* java.util.Collections 是集合工具类
*/
import java.util.*;
public class VectorTest01 {
public static void main(String[] args) {
// 创建Vector对象
List list = new Vector();
// 添加元素
// vector初始化的默认容量是:10
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
list.add(10);
// vector扩容,扩容之后是:20
list.add(11);
// Vector遍历
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " "); // 1 2 3 4 5 6 7 8 9 10 11
}
System.out.println();
// 将一个ArrayList非线程安全的转换为Vector线程安全的?可以使用java.util.Collections类中的方法进行转换。
// 创建ArrayList对象
List list1 = new ArrayList(); // 非线程安全的。
// 将ArrayList转为Vector,变成线程安全的。
Collections.synchronizedList(list1);
// list1集合就是线程安全的了。
list1.add(10);
list1.add(20);
list1.add(30);
list1.add(40);
}
}
3、泛型
泛型属于编译阶段的功能,与运行阶段的关系不大。
3.1、泛型的概述
package javase.generic;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 1、JDK5.0之后推出的新特性:泛型
* 2、泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型作用不大)
* 3、泛型的好处:
* 第一:集合中存储的元素类型统一。
* 第二:从集合中取出的元素类型都是泛型指定的类型,不需要进行大量的“向下转型”。
* 4、泛型的缺点:
* 导致集合中存储的元素缺乏多样性!
* 但实际上在大多数业务中,集合中元素的类型还是统一的。所有这种泛型特性还是被很多人所认可的。
*/
public class GenericTest01 {
public static void main(String[] args) {
// 未使用“泛型”
/*
// 创建集合
List animals = new ArrayList();
// 创建动物
Cat cat = new Cat();
Bird bird = new Bird();
// 将动物添加到集合中
animals.add(cat);
animals.add(bird);
// 使用迭代器遍历集合,输出猫爪老鼠与鸟儿在飞翔!
Iterator it = animals.iterator();
while (it.hasNext()) {
// 迭代器中取出来的元素只能是Object对象(但是可以使用向下转型)
Object o = it.next();
if (o instanceof Cat){
((Cat) o).catchMouse();
} else if (o instanceof Bird){
((Bird)o).fly();
}
}
*/
// 使用JDK5.0之后的新特性“泛型机制”后,代码变得更为直观
// 使用泛型List<Animal>之后,表示List集合中只运行存储Animal类型的数据。
// 用泛型来指定集合中存储的数据类型。
List<Animal> animals = new ArrayList<Animal>();
// 指定List集合中指定存储Animal,如果存储其他类型如String的编译器就会报错。
// 这样用了泛型之后,集合中元素的数据类型更加统一了。
Cat cat = new Cat();
Bird bird = new Bird();
// 指定了泛型的集合中,可以存储器类型的子类。
animals.add(cat);
animals.add(bird);
// 获取迭代器,迭代器也可以指定其迭代的类型。
// 这个表示迭代器迭代的是Animal类型。如果不指定,则返回的默认还是“Object”类型。
Iterator<Animal> it = animals.iterator();
while (it.hasNext()) {
// 使用泛型之后,每一次迭代返回的数据都是Animal类型
Animal animal = it.next();
// 这里不需要进行强制类型转换了,可以直接使用。
animal.move(); // 动物在移动!
// 但是这里如果是调用子类型中特有的方法时还是需要向下转型的。
if (animal instanceof Cat){
((Cat) animal).catchMouse(); // 猫爪老鼠!
}
}
}
}
class Animal{
public void move(){
System.out.println("动物在移动!");
}
}
class Cat extends Animal{
public void catchMouse(){
System.out.println("猫抓老鼠!");
}
}
class Bird extends Animal{
public void fly(){
System.out.println("鸟儿飞翔!");
}
}
3.2、钻石表达式
package javase.generic;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* JDK8之后引入了:自动类型推断的机制(又称为“钻石表达式”)。
*/
public class GenericTest02 {
public static void main(String[] args) {
// ArrayList<这里的类型可以自动推断>();前提是JDK8之后才运行的。
// 自动类型推断
List<String> myList = new ArrayList<>(); // ArrayList<>这里尖括号里面的类型可以不用写了(JDK8之后)。
myList.add("java");
myList.add("php");
myList.add("python");
// 不允许添加其他类型的数据
// myList.add(new Animal()); // 报错
// 获取迭代器对象,并指定迭代器类型为String
Iterator<String> iterator = myList.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " "); // java php python
}
System.out.println();
}
}
3.3、自定义泛型
package javase.generic;
/**
* 如何自定义泛型?
* 自定义泛型的时候,<>尖括号中的是一个标识符,可以随便写。
* java源代码中经常出现的是:
* <E>和<T>
* E是element单词首字母。
* T是type单词首字母。
*/
// 这里的<Test>是随便写的,只要符合标识符命名规则即可。
public class GenericTest03<Test> {
public void doSome(Test e){ // 这里的e也只是一个表标识符而已
System.out.println(e);
}
public static void main(String[] args) {
// new对象的时候确定了泛型的对象,如:String类型
GenericTest03<String> gt = new GenericTest03<>();
// 上面指定了gt泛型的类型为String类型,如果我们传入其他类型编译器会报错。如:int
// gt.doSome(100); // 类型不匹配
// 传入一个String类型的数据,编译器不会报错
gt.doSome("java is the best language!");
// 再如:这里指定泛型的类型为Integer的类型。
GenericTest03<Integer> igt = new GenericTest03<>();
// 传入一个100(会自动装箱)编译器不会报错。
igt.doSome(100);
// 传入一个字符串类型编译器会报错
// igt.doSome("java"); // 类型不匹配
// 如果不使用泛型,默认就是Object类型
// GenericTest03 igt2 = new GenericTest03();
// igt2.doSome(new Object());
}
}
// 自定义一个类,并指定为泛型
class MyGeneric<T>{
public T getType(T j){
return null;
}
}
3.4、增强版for循环
package javase.generic;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
// JDK5.0之后推出一个新特性,叫做增强for循环,或叫做foreach循环
public class ForeachTest01 {
public static void main(String[] args) {
// 定义一个数组
int[] array = {1, 2, 4, 6, 7};
// 普通for循环的遍历方式
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
// 增强版for循环(又称为“foreach”循环)
// foreach有个小小的缺点:就是没有下标。
for (int i : array) {
System.out.print(i + " ");
}
System.out.println();
// 创建集合
List<String> strings = new ArrayList<>();
strings.add("hello");
strings.add("world");
strings.add("javaSE");
// 集合的几种遍历方式
// 第一种使用迭代器遍历
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " "); // hello world javaSE
}
System.out.println();
// 第二种使用下标(针对有下标的集合)
for (int i = 0; i < strings.size(); i++) {
System.out.print(strings.get(i) + " "); // hello world javaSE
}
// 第三种是有foreach
for (String string : strings) { // 这里为什么是String string?因为我们上面定义strings的泛型类型为String类型。
System.out.print(string + " "); // // hello world javaSE
}
}
}
3.5、HashSet与TreeSet简单演示
HashSet:
package javase.set;
import java.util.HashSet;
/**
* HashSet集合:
* 无序不可重复。
*/
public class HashSetTest01 {
public static void main(String[] args) {
// HashSet集合特点
HashSet<String> strs = new HashSet<>();
// 添加元素
strs.add("hello2");
strs.add("hello1");
strs.add("hello3");
strs.add("hello3");
strs.add("hello3");
// 遍历
for (String str : strs) {
// 输出:hello1 hello2 hello3
// 1、存储时顺序和取出的顺序不同
// 2、不可重复
// 3、放到HashSet集合中的元素实际上是放到HashMap结合的key部分了。
System.out.print(str + " ");
}
}
}
TreeSet:
package javase.set;
import java.util.Set;
import java.util.TreeSet;
/**
* TreeSet集合存储元素特点:
* 1、无序不可重复,但是存储的元素可以自动按照从小到大顺序排序!
* 称为:可排序集合。
* 2、无序:这里的无序指的是存进去的顺序和取出来的顺序不同,并且没有下标。
*/
public class TreeSetTest01 {
public static void main(String[] args) {
// 创建集合对象
Set<String> strs = new TreeSet<>();
// 添加元素
strs.add("a");
strs.add("b");
strs.add("z");
strs.add("y");
strs.add("k");
strs.add("z");
// 遍历
// 输出:a b k y z , 从小到大自动排序
for (String str : strs) {
System.out.print(str + " ");
}
}
}
4、Map集合
4.1、Map集合常用方法
package javase.map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* java.util.Map接口:
* 1、Map和Collection没有继承关系。
* 2、Map集合以key和value的方式存储数据,即键值对。
* key和value都是引用数据类型。
* key和value都是存储对象的内存地址。
* key起到主导的地位,value是key的一个附属品。
* 3、Map接口中常用的方法:
* V put(K key, V value) 向Map集合中添加键值对
* V get(Object key) 通过key获取value
* void clear() 清空Map集合
* boolean containsKey(Object key) 判断Map中是否包含某个key
* boolean containsValue(Object value) 判断Map中是否包含某个Value
* boolean isEmpty() 判断Map中的元素是否为 0
* Set<K> keySet() 获取Map集合所有的key(所有的键是一个集合)
* V remove(Object key) 通过key删除键值对
* int size() 获取Map集合中键值对的个数
* Collection<V> values() 获取Map集合中所有的value,返回一个Collection
* Set<Map.Entry<K,V>> entrySet()
* 将Map集合转换为Set集合
* 假设现在有一个Map集合,选下所示:
* map1集合对象
* key value
* ---------------------
* 1 zhangsan
* 2 lisi
* 3 wangwu
*
* Set set = map1.entrySet();
* set集合对象
* 1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中的元素的类型是Map.Entry<K,V>】
* 2=lisi 【MapEntry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】
* 3=wangwu --> 这个东西是啥?答案:Map.Entry
*
* 4、关于内部类可以看这个包下面的MyClass.java类.
*/
public class MapTest01 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
// 向Map集合中添加键值对
map.put(1, "龙小萌"); // 这里的 1 会自动装箱,和new Integer(1);是一样的。
map.put(2, "小萌狗");
map.put(3, "龙萌萌");
// 通过key获取value以及Map的遍历
for (Integer integer : map.keySet()) {
System.out.println(integer + ":" + map.get(integer));
/*
输出:
1:龙小萌
2:小萌狗
3:龙萌萌
*/
}
// 获取Map集合中键值对的数量
System.out.println("键值对的数量: " + map.size()); // 3
// 通过key删除value
map.remove(3);
System.out.println("键值对的数量: " + map.size()); // 2
// 获取所有的value
Collection<String> values = map.values();
for (String value : values) {
System.out.print(value + " ");
}
System.out.println();
// 判断是否包含某个key
// 需要注意的是,contains()方法底层调用的都是equals()方法进行比较的,所以自定义类都需要重写equals()方法。
System.out.println(map.containsKey(2)); // true
// 判断是否包含某个value
System.out.println(map.containsValue("小萌狗")); // true
// 清空Map集合
map.clear();
System.out.println("键值对的数量: " + map.size()); // 0
// 判断Map集合中的键值对是否为0
System.out.println(map.isEmpty()); // true
}
}
4.2、Map集合的遍历
常用两种方法进行遍历。
package javase.map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Map集合的遍历【非常重要】
*/
public class MapTest02 {
public static void main(String[] args) {
// 第一种方式,获取所有的key,通过遍历key来遍历value
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
// 遍历Map集合
// 获取所有的key,所有的key是一个Set集合
Set<Integer> keySet = map.keySet();
// 遍历key,通过key获取value
// 使用迭代器来实现
Iterator<Integer> iterator = keySet.iterator();
while (iterator.hasNext()) {
// 取出其中一个key
Integer key = iterator.next();
// 通过key获取value
String value = map.get(key);
System.out.println(key + "=" + value);
}
// foreach来实现
for (Integer key : keySet) {
System.out.println(key + " = " + map.get(key));
}
/*
输出:
1 = zhangsan
2 = lisi
3 = wangwu
4 = zhaoliu
*/
// 第二种方式,Set<Map.Entry<K,V>> entrySet()
// 以上这个方法是把Map集合直接全部转换成Set集合,Set集合中元素的类型是:Map.Entry
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
// 遍历Set集合,每一次取出一个Node
// 这里使用迭代器来遍历
Iterator<Map.Entry<Integer, String>> it2 = entrySet.iterator();
while (it2.hasNext()) {
Map.Entry<Integer, String> node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + " = " + value);
}
// 增强for循环
// [这一种方式的效率要高一点],适用有大数据量的情况
for (Map.Entry<Integer, String> stringEntry : entrySet) {
System.out.println(stringEntry.getKey() + " = " + stringEntry.getValue());
}
/*
输出:
1 = zhangsan
2 = lisi
3 = wangwu
4 = zhaoliu
*/
}
}
4.3、HashMap中两个重要的方法
map.put(k,v);
v = map.get(k);
这两个方法应该重点掌握其原理。
package javase.map.HashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 1、HashMap集合底层是哈希表/散列表的数据结构。
* 2、哈希表示一个怎样的数据结构?
* 哈希表是一个数组和单向链表的结合体。
* 数组:在查询方面效率很高,随机删除方面效率很低。
* 单向链表:在随机增删方面效率较高,在查询方面效率很低。
* 哈希表将以上的两种数据结构融合在一起,充分发挥了它们各自的优点。
* 3、HashMap集合底层的源代码:
* public class HashMap<K,V>{
* // HashMap底层实际上就是一个数组(一维数组)。
* Node<K,V>[] table;
*
* // 静态内部类Hash.Node
* static class Node<K,V>{
* final int hash; // 哈希值(哈希值是key的hashCode()方法执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。
* final K key; // 存储到Map集合中的那个key
* V value; // 存储到Map集合中的那个value
* Node<K,V> next; // 下一个节点的内存地址
* }
* 哈希表/散列表:是一个一维数组,这个数组中每一个元素是一个单向链表(数组和链表的结合体)。
* 4、重点掌握:
* map.put(K, V);
* v = map.get(K);
* 以上这两个方法的实现原理,是必须掌握的。
*
* 5、HashMap集合的key部分特点:
* 无序,不可重复。
* 为什么无序?答案:因为不一定挂到那个单向链表上。
* 不可重复是怎么保证的?答案:equals方法来保证HashMap集合的key不可重复,如果key重复了,value会覆盖。
*
* 放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
* 所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。
*
* 6、哈希表HashMap使用不当时无法发挥性能!
* 假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们称为:散列分布不均匀。
* 什么是散列分布均匀?
* 假设有100个元素和10个单向链表,那么如果每个单向链表上有10个节点,这是最好的,这时也称为:散列分布均匀。
*
* 假设将所有的hashCode()方法返回值都设定为不一样的值,又会有什么问题?
* 这样会导致底层哈希表变成一个纯一维数组,没有链表这个概念。也是散列分布不均匀。
*
* 所以散列分布均匀就需要在重写hashCode()方法的时候有一定的技巧。
*
* 7、重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode()和equals()方法。
* 8、HashMap集合的默认初始化容量是16,默认加载因子是0.75(了解)
* 这个默认加载因子是当HashMap集合底层数据容量达到75%的时候,数组开始扩容。
*
* 重点记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的。这也是达到散列均匀,为了提高HashMap集合的存取效率所必须的。
*
*/
public class HashMapTest01 {
public static void main(String[] args) {
// 测试Hashmap集合key部分的元素特点
// Integer是key,它的hashCode和equals方法都已经重写了
Map<Integer, String> map = new HashMap<>();
map.put(1111, "zhangsan");
map.put(2222, "lisi");
map.put(3333, "wangwu");
map.put(4444, "zhaoliu");
map.put(4444, "zh0u"); // key重复的时候value会自动覆盖
System.out.println(map.size()); // 4
// 遍历Map集合
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
for (Map.Entry<Integer, String> entry : entrySet) {
// 验证结果:HashMap集合key部分元素:无序不可重复。
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
}
4.4、重写hashCode()方法
package javase.map.HashMap;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/*
1、向Map集合中存、取元素时,都是先调用key的hashCode()方法,然后在调用equals()方法!
equals()方法有可能调用,也有可能不会调用。
拿put(k,v)举例:什么时候equals()不会调用?
k.hashCode()方法返回哈希值,当哈希值经过哈希算法转换成数组下标,数组下标位置上如果是null,equals()方法不需要执行。
拿get(k)举例:什么时候equals()不会调用?
k.hashCode()方法返回哈希值,当哈希值经过哈希算法转换成数组下标,数组下标位置上如果是null,equals()方法不需要执行。
2、注意:如果一个类的equals()方法重写了,那么hashCode()方法必须重写,并且equals()方法如果返回true,hashCode()方法返回的值必须是一样的。
3、hashCode()方法和equals()方法不用手写了,直接使用idea工具生成,但是需要注意的是这两个方法应该同时生成。
4、最后总结:
放在HashMap集合key部分,已经放在HashSet集合中的元素,需要同时重写hashCode()和equals()方法。
*/
public class HastMapTest02 {
public static void main(String[] args) {
Student s1 = new Student("zhangsan");
Student s2 = new Student("zhangsan");
// 重写equals()方法之前
// System.out.println(s1.equals(s2)); // false
// 重写equals()方法之后
System.out.println(s1.equals(s2)); // true
System.out.println("s1的hashCode = " + s1.hashCode());// 1163157884 (hashCode()方法重写之后:-1432604525)
System.out.println("s2的hashCode = " + s2.hashCode());// 1956725890 (hashCode()方法重写之后:-1432604525)
// s1.equals(s2);结果已经是true了,表示s1和s2是一样的,相同的,那么往HashSet集合中放元素,理论上来说这里应该只能放进去一个,
// HashSet集合的特点:无序不可重复。
Set<Student> students = new HashSet<>();
students.add(s1);
students.add(s2);
// hashCode()方法重写之前,理论上来说这里输出的应该是1,但是结果去输出的是2,显然不符合HashSet集合存储元素的特点。
System.out.println(students.size());
// 重写Student类当中的hashCode()方法之后
System.out.println(students.size()); // 1
}
}
// 学生类
class Student{
private String username;
public Student() {
}
public Student(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return Objects.equals(getUsername(), student.getUsername());
}
@Override
public int hashCode() {
return Objects.hash(getUsername());
}
}
4.5、补充
package javase.map.HashMap;
import java.util.HashMap;
import java.util.Map;
/*
HashMap集合的key可以为null吗?
答案:可以为null。但是需要注意,key值为null的只能有一个(因为key不可重复)。
有可能面试的时候会遇到,开发用不到。
*/
public class HashMapTest03 {
public static void main(String[] args) {
Map map = new HashMap();
map.put(null, null);
// 通过key获取到value
System.out.println(map.get(null)); // null
// 输出map中元素的个数
System.out.println(map.size()); // 1
// 值覆盖
map.put(null, 100);
System.out.println(map.get(null)); // 100
}
}
4.6、Hashtable
package javase.map.Hashtable;
import java.util.Hashtable;
import java.util.Map;
/**
* 1、Hashtable的key可以为null吗?
* Hashtable的key和value都是不能为null的。
* HashMap集合的key和value都是可以为null的。
*
* 2、Hashtable和HashMap一样,底层都是哈希表数据结构。
* 3、Hashtable的初始化容量是11,默认加载因子是:0.75f
* 4、Hashtable的扩容是:原容量 * 2 + 1
*/
public class HashtableTest01 {
public static void main(String[] args) {
Map map = new Hashtable();
// map.put(null, 123); // java.lang.NullPointerException
// map.put(1, null); // java.lang.NullPointerException
}
}
4.7、Properties
package javase.map.Hashtable;
import java.util.Properties;
/**
* 目前只需要掌握Properties属性类对象的相关方法即可。
* Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
* Properties被称为属性类对象。
* Properties是线程安全的。
*/
public class PropertiesTest01 {
public static void main(String[] args) {
// 创建一个Properties对象
Properties properties = new Properties();
// 需要掌握Properties的两个方法,一个存,一个取。
properties.setProperty("url", "jdbc:mysql://localhost:3306/zh0u");
properties.setProperty("driver", "com.mysql.jdbc.Driver");
properties.setProperty("username", "root");
properties.setProperty("password", "123");
// 通过key获取value
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
System.out.println(url); // jdbc:mysql://localhost:3306/zh0u
System.out.println(driver); // com.mysql.jdbc.Driver
System.out.println(username); // root
System.out.println(password); // 123
}
}
4.8、TreeSet
package javase.map.TreeSet;
import java.util.TreeSet;
/**
* 1、TreeSet集合底层实际上是一个TreeMap。
* 2、TreeMap集合底层是一个二叉树。
* 3、放到TreeSet集合中的元素,实际上是放到TreeMap集合key部分了。
* 4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合。
*/
public class TreeSetTest01 {
public static void main(String[] args) {
// 创建一个TreeSet集合
TreeSet<String> ts = new TreeSet<>();
// i添加字符串
ts.add("zhangsan");
ts.add("lisi");
ts.add("wangwu");
ts.add("zhangsi");
ts.add("wangliu");
// 遍历
for (String t : ts) {
// 按照字典顺序,升序
System.out.println(t);
}
/*
输出:
lisi
wangliu
wangwu
zhangsan
zhangsi
*/
// 创建一个Integer类型的TreeSet集合
TreeSet<Integer> integers = new TreeSet<>();
integers.add(30);
integers.add(20);
integers.add(3);
integers.add(60);
integers.add(1);
// 遍历
for (Integer integer : integers) {
// 升序
System.out.println(integer);
}
/*
输出:
1
3
20
30
60
*/
}
}
4.9、自定义类型实现Comparable接口
package javase.map.TreeSet;
import java.util.TreeSet;
/*
对自定义的类型来说,TreeSet可以排序吗?
以下程序中对于Person类型来说,无法排序。因为没有指定Person对象之间的比较规整。
谁大谁小并没有说明。
以下程序运行的时候出现了这个异常:
java.lang.ClassCastException:
javase.map.TreeSet.Person cannot be cast to java.lang.Comparable
出现这个异常的原因是:
Person类没有实现java.lang.Comparable接口。
*/
public class TreeSetTest02 {
public static void main(String[] args) {
Person p1 = new Person(32);
Person p2 = new Person(20);
Person p3 = new Person(30);
Person p4 = new Person(24);
// 创建集合
TreeSet<Person> personTreeSet = new TreeSet<>();
personTreeSet.add(p1);
personTreeSet.add(p2);
personTreeSet.add(p3);
personTreeSet.add(p4);
// 遍历
for (Person person : personTreeSet) {
// 输出引用数据类型时,会自动调用toString()方法。
System.out.println(person);
}
}
}
// 放在TreeSet集合汇总的元素需要实现java.lang.Comparable接口
// 并且实现compareTo()方法。equals()方法可以不写。
class Person implements Comparable<Person> {
private int age;
public Person(){
}
public Person(int age){
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
// 需要在这个方法中编写比较的逻辑,或者说是比较规则,按照什么原理进行比较!
// k.compareTo(t.key);
// 拿着参数k和集合中的每一个k进行比较,返回值可能是大于零、小于零、等于零。
// 比较规则最终还是由程序员指定的:例如这里按照年龄升序。
@Override
public int compareTo(Person o) {
return this.age - o.age; // 升序
}
}
4.10 自定义排序规则
package javase.map.TreeSet;
import java.util.TreeSet;
// 先按照年龄升序,如果年龄一样的在按照姓名升序。
public class TreeSetTest03 {
public static void main(String[] args) {
TreeSet<Vip> vips = new TreeSet<>();
vips.add(new Vip("zhangsi", 20));
vips.add(new Vip("zhangsan", 20));
vips.add(new Vip("king", 18));
vips.add(new Vip("ying", 17));
for (Vip vip : vips) {
System.out.println(vip);
}
/*
从这里可以看出,这里就是先按照年龄进行升序,如果年龄相同,在按照字典顺序进行排序。
Vip{username='ying', age=17}
Vip{username='king', age=18}
Vip{username='zhangsan', age=20}
Vip{username='zhangsi', age=20}
*/
}
}
class Vip implements Comparable<Vip> {
private String username;
private int age;
public Vip(){}
public Vip(String username, int age){
this.username = username;
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Vip{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
// 这里的排序规则为:先按照年龄进行排序,如果年龄一样。在按照姓名字典进行排序。
/*
compareTo()方法的返回值很重要:
返回0表示相同,value会进行覆盖。
返回>0,会继续在右子树上找。【10-9=1>0,说明自编的这个数字比较大,所以回到右子树上去找】
返回<0,会继续在左子树上找。
*/
@Override
public int compareTo(Vip o) {
// 这里编写的就是放入Set集合中时,排序的规则。
if (this.age == o.age){
// String类型与Integer类型中的compareTo()方法已经实现了重写直接调用即可。
return this.username.compareTo(o.username);
} else {
return this.age - o.age;
}
}
}
4.11、自平衡二叉树数据结构
4.12、实现比较器接口
package javase.map.TreeSet;
import java.util.Comparator;
import java.util.TreeSet;
/*
TreeSet集合中元素可排序的第二种方式:使用比较器的方式。
重要结论:
放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中元素实现java.lang.Comparable接口。
第二种;在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
Comparable和Comparator怎么选择?
当比较规则不会发生改变的时候,或者说当比较规则只有一个的时候,建议实现comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用comparator接口,comparator的设计符合OCP原则。
*/
public class TreeSetTest04 {
public static void main(String[] args) {
// 创建TreeSet集合的时候,需要使用这个比较器。
// TreeSet<WuGui> wuGuis = new TreeSet<>(); // 这样不行,没有通过构造方法传递比较器
// 方式一:使用匿名内部类,直接new接口,然后在接口的后面加上{}
TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
@Override
public int compare(WuGui o1, WuGui o2) {
return o1.getAge() - o2.getAge();
}
});
// 方式二:给构造方法传递一个比较器
// TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
// 添加元素
wuGuis.add(new WuGui(1000));
wuGuis.add(new WuGui(300));
wuGuis.add(new WuGui(150));
wuGuis.add(new WuGui(40));
wuGuis.add(new WuGui(800));
// 遍历
for (WuGui wuGui : wuGuis) {
System.out.println(wuGui);
}
}
}
// 创建乌龟对象
class WuGui{
private int age;
public WuGui(int age){
this.age = age;
}
@Override
public String toString() {
return "WuGui{" +
"age=" + age +
'}';
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 创建乌龟对象比较器,比较的规则是按照年龄进行升序。
class WuGuiComparator implements Comparator<WuGui>{
@Override
public int compare(WuGui o1, WuGui o2) {
return o1.getAge() - o2.getAge();
}
}
4.13、Collections工具类
package javase;
import java.util.*;
/*
Collection : java.util.Collection集合接口。
Collections: java.util.Collections 集合工具类。
*/
public class CollectionsTest01 {
public static void main(String[] args) {
// ArrayList集合是非线程安全的。
List<String> stringList = new ArrayList<>();
// 变成线程安全的
Collections.synchronizedList(stringList);
// 添加元素
stringList.add("abf");
stringList.add("abx");
stringList.add("abc");
stringList.add("abe");
Collections.sort(stringList);
for (String s : stringList) {
System.out.println(s);
}
// 自定义数据类型
List<User> userList = new ArrayList<>();
userList.add(new User(2, "zh0u"));
userList.add(new User(1, "margin"));
userList.add(new User(0, "alice"));
userList.add(new User(1, "java"));
// 注意:对List集合中元素进行排序,需要保证List集合中的元素实现了Comparable接口。
Collections.sort(userList);
for (User user : userList) {
System.out.println(user);
}
// 对Set集合怎么排序?
Set<String> stringSet = new HashSet<>();
stringSet.add("java");
stringSet.add("php");
stringSet.add("python");
stringSet.add("mysql");
// 先将Set集合转换成List集合
List<String> stringArrayList = new ArrayList<>(stringSet);
Collections.sort(stringArrayList);
// 遍历
for (String s : stringArrayList) {
System.out.println(s);
}
// 还有一种排序的方式
// Collections.sort(列表, 比较器);
}
}
class User implements Comparable<User>{
private int id;
private String username;
public User(int id, String username) {
this.id = id;
this.username = username;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
// 重写Comparable接口中的compareTo()方法。
@Override
public int compareTo(User o) {
if (this.id == o.id) {
return this.username.compareTo(o.username);
} else {
return this.id - o.id;
}
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
'}';
}
}
;
for (String s : stringList) {
System.out.println(s);
}
// 自定义数据类型
List<User> userList = new ArrayList<>();
userList.add(new User(2, "zh0u"));
userList.add(new User(1, "margin"));
userList.add(new User(0, "alice"));
userList.add(new User(1, "java"));
// 注意:对List集合中元素进行排序,需要保证List集合中的元素实现了Comparable接口。
Collections.sort(userList);
for (User user : userList) {
System.out.println(user);
}
// 对Set集合怎么排序?
Set<String> stringSet = new HashSet<>();
stringSet.add("java");
stringSet.add("php");
stringSet.add("python");
stringSet.add("mysql");
// 先将Set集合转换成List集合
List<String> stringArrayList = new ArrayList<>(stringSet);
Collections.sort(stringArrayList);
// 遍历
for (String s : stringArrayList) {
System.out.println(s);
}
// 还有一种排序的方式
// Collections.sort(列表, 比较器);
}
}
class User implements Comparable<User>{
private int id;
private String username;
public User(int id, String username) {
this.id = id;
this.username = username;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
// 重写Comparable接口中的compareTo()方法。
@Override
public int compareTo(User o) {
if (this.id == o.id) {
return this.username.compareTo(o.username);
} else {
return this.id - o.id;
}
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
'}';
}
}