JavaStudy4(集合+泛型)—B站hsp
1.集合
1.1 集合的理解和好处
前面我们保存多个数据使用的是数组,那么数组有不足的地方,我们分析一下
1.1.1 数组
1.1.2 集合
1.2 集合的框架体系
Java 的集合类很多,主要分为两大类,如图 :[背下来]
代码演示:
public class Collection_{
@SuppressWarnings("all") //注解
public static void main(String[] args) {
//老韩解读
// 1. 集合主要是两组(单列集合 , 双列集合)
// 2. Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合
// 3. Map 接口的实现子类 是双列集合,存放的 Key-Value
// 4. 把老师梳理的两张图记住
//Collection
//Map
ArrayList arrayList = new ArrayList();
arrayList.add("jack");
arrayList.add("tom");
HashMap hashMap = new HashMap();
hashMap.put("no1","北京");
hashMap.put("no2","上海");
}
}
1.3 Collection 接口和常用方法
1.3.1 Collection 接口实现类的特点
- Collection 接口常用方法,以实现子类 ArrayList 来演示. CollectionMethod.java
代码演示:
public class CollectionMethod {
@SuppressWarnings("all")
public static void main(String[] args) {
List list = new ArrayList();
//1. add:添加单个元素
list.add("jack");
list.add(10); //自动装箱,转为Integer 相当于list.add(new Integer(10));
list.add(true); //自动装箱
list.add(false); //自动装箱
System.out.println("list=" + list);
//2. remove:删除指定元素
list.remove(0); //删除下标为0的元素
list.remove(false); //删除"false"
list.remove(new Integer(10)); //删除数字
System.out.println("list=" + list); //list=[true]
//3. contains:查找元素是否存在
System.out.println(list.contains(false)); //false
//4. size:获取元素个数
System.out.println(list.size()); //1
//5. isEmpty:判断是否为空
System.out.println(list.isEmpty()); //false
//6. clear:清空元素
list.clear();
System.out.println("list=" + list); //list=[]
//7. addAll: 添加多个元素
List list2 = new ArrayList();
list2.add("三国演义");
list2.add("红楼梦");
list2.add("西游记");
list.addAll(list2);
System.out.println("list=" + list); //list=[三国演义, 红楼梦, 西游记]
//8. containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));//true
//9. removeAll:删除多个元素
list.add("聊斋");
list.removeAll(list2); //删除list集合中存放的list2集合中的多个元素
System.out.println("list=" + list);//list=[聊斋]
}
}
1.3.2 Collection 接口遍历元素方式 1-使用 Iterator(迭代器)
迭代器的使用案例
看下老师的案例演示 CollectionIterator.java
public class CollectionIterator {
@SuppressWarnings("all")
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
// System.out.println("col=" + col);
//现在希望遍历集合,一个一个输出
//1. 先得到 col 对应的 迭代器
//只要实现了Collection接口 的都会实现iterator方法
Iterator iterator = col.iterator();
//使用while()循环遍历
while (iterator.hasNext()){ //判断是否还有数据
//返回下一个元素,类型是Object 因为集合可以添加任何 Object类型的元素,所以返回Object
Object obj = iterator.next();
System.out.println("obj=" + obj); // 编译时为Object类型 运行类型为存放的元素类型 多态
}
//集合遍历 快捷键 itit ctrl+J 可以把所有的快捷键显示出来
// while (iterator.hasNext()) {
// Object obj = iterator.next();
// System.out.println("obj+" + obj);
// }
//3. 当退出while循环后,这是iterator迭代器,只想最后的元素
//iterator.next();//异常:NosuchElementException
//4.如果希望再次遍历,需要我们重置迭代器。
iterator = col.iterator();
System.out.println("===第二次遍历===");
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next" + next);
}
}
}
class Book{
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
1.3.3 Collection 接口遍历对象方式 2-for 循环增强
public class CollectionFor {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
//使用增强for循环
for (Object book : col){
System.out.println("book=" + book);
}
//1.增强for也可以直接用在数组
//2. 增强for 底层仍然时iterator迭代器
//3. 增强for,就是简化版的迭代器
int[] a = {1,2,3,5,44,88};
for (int k : a){
System.out.println(k);
}
// 快捷键 I
for (Object o : col) {
System.out.println(o);
}
}
}
public class CollectionExrcise {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Dog("tom",2));
list.add(new Dog("jack",3));
for (Object dog : list) {
System.out.println("dog=" + dog);
}
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object dogs = iterator.next();
System.out.println("iterator dogs=" + dogs);
}
}
}
class Dog{
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = 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;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1.4 List 接口和常用方法
1.4.1 List 接口基本介绍
代码演示:
public class list_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复 [案例]
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
list.add("hsp");
list.add("tom");
System.out.println("list=" + list);
//2. List 集合中的每个元素都有其对应的顺序索引,即支持索引
// 索引是从 0 开始的
System.out.println(list.get(3));
}
}
1.4.2 List 接口的常用方法
代码演示:
public class ListMethod {
@SuppressWarnings({"ALL"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("贾宝玉");
list.add("张三丰");
//1. void add(int index, Object ele):在 index 位置插入 ele 元素
//在 index = 1 的位置插入一个对象
list.add(1,"韩顺平");
System.out.println("list=" + list); //list=[贾宝玉, 韩顺平, 张三丰]
//2. boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
List list1 = new ArrayList();
list1.add("jack");
list1.add("tom");
list.addAll(1,list1);
System.out.println("list=" + list); //list=[贾宝玉, jack, tom, 韩顺平, 张三丰]
//3. Object get(int index):获取指定 index 位置的元素
System.out.println(list.get(4)); //张三丰
//4. int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("tom")); //2
//5. int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
list.add("韩顺平");
System.out.println("list=" + list); //list=[贾宝玉, jack, tom, 韩顺平, 张三丰, 韩顺平]
System.out.println(list.lastIndexOf("韩顺平")); //5
//6. Object remove(int index):移除指定 index 位置的元素,并返回此元素
list.remove(5);
list.remove("韩顺平");
System.out.println("list=" + list); //list=[贾宝玉, jack, tom, 张三丰]
//7. Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换
list.set(1,"玛丽");
System.out.println("list=" + list);//list=[贾宝玉, 玛丽, tom, 张三丰]
//8. List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
// 注意返回的子集合 fromIndex <= subList < toIndex 左闭右开
List returnList = list.subList(1,3);
System.out.println("returnList=" + returnList); //returnList=[玛丽, tom]
}
}
1.4.3 List 接口课堂练习
代码演示:
public class ListExercise {
public static void main(String[] args) {
/*添加 10 个以上的元素(比如 String "hello" ),在 2 号位插入一个元素"韩顺平教育",
获得第 5 个元素,删除第 6 个元素,修改第 7 个元素,在使用迭代器遍历集合
要求:使用 List 的实现类 ArrayList 完成。
*/
List list = new ArrayList();
for (int i = 0; i < 12; i++) {
list.add("hello"+i);
}
System.out.println("list= " + list);
list.add(1,"韩顺平");
System.out.println("list= " + list);
System.out.println("第五个元素:" + list.get(4));
list.remove(5);
System.out.println("删除第6个元素list= " + list);
list.set(7,"哈喽");
System.out.println("list= " + list);
for (Object hello : list) {
System.out.print("hello=" + hello + "\t");
}
}
}
1.4.4 List 的三种遍历方式 [ArrayList, LinkedList,Vector]
public class ListFor {
public static void main(String[] args) {
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("北京考研");
list.add("覃波");
//遍历
//1. iterator 迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next=" + next);
}
//2. 增强for
for (Object o : list) {
System.out.println("o=" + o);
}
//3. 普通for
for (int i = 0; i < list.size(); i++) {
System.out.println("list" +list.get(i));
}
1.4.5 List实现类的课堂练习
public class ListExercise02 {
//省略了Book类
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Book("三国演义",80,"罗贯中"));
list.add(new Book("红楼梦",100,"曹雪芹"));
list.add(new Book("西游记",10,"吴承恩"));
listSort(list);
for (Object o : list) {
System.out.println(o);
}
/*输出
Book{name='西游记', price=10.0, author='吴承恩'}
Book{name='三国演义', price=80.0, author='罗贯中'}
Book{name='红楼梦', price=100.0, author='曹雪芹'}
*/
}
public static void listSort(List list){
for (int i = 0; i < list.size()-1; i++) {
for (int j = 0; j < list.size() - 1 - i; j++) {
//取出Book对象
Book book1 = (Book)list.get(j);
Book book2 = (Book)list.get(j+1);
if (book1.getPrice() > book2.getPrice()){
list.set(j,book2);
list.set(j+1,book1);
}
}
}
}
}
1.5 ArrayList 底层结构和源码分析
1.5.1 ArrayList 的注意事项
1.5.2 ArrayList 的底层操作机制源码分析(重点,难点.)
-
ArrayList 中维护了一个Object类型的数组elementData.
transient Object[] elementData; //transient 表示瞬间的,短暂的,表示该属性不会被序列化;
-
当创建ArrayList 对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如果需要再次扩容,则扩容elementData为1.5倍;
0 -->10 -->15–> 22–>33
-
如果使用的是指定大小的构造器,则初始化elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。
比如初始化为8:8–> 12–> 18–>27–> 40
示意图:
1.6 Vector 底层结构和源码剖析
1.6.1 Vector 的基本介绍
代码演示:
@SuppressWarnings({"all"})
public class Vector_ {
public static void main(String[] args) {
//无参构造
Vector vector = new Vector();
for (int i = 0; i < 10; i++) {
vector.add(i);
}
vector.add(100);
System.out.println("vector=" + vector);
老韩解读源码
// 1. new Vector() 底层
/*
public Vector() {
this(10);
}
补充:如果是 Vector vector = new Vector(8);
走的方法:
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
2. vector.add(i) 2.1
//下面这个方法就添加数据到 vector 集合
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2.2 //确定是否需要扩容 条件 : minCapacity - elementData.length>0
private void ensureCapacityHelper(int minCapacity) {// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity); }
2.3 //如果 需要的数组大小 不够用,就扩容 , 扩容的算法
//newCapacity = oldCapacity + ((capacityIncrement > 0) ?
// capacityIncrement : oldCapacity);
//就是扩容两倍.
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
*/
}
}
1.7 LinkedList 底层结构
1.7.1 LinkedList 的全面说明
- LinkedList底层实现了双向链表和双端队列特点
- 可以添加任何元素(元素可以重复),包括null
- 线程不安全,没有实现同步
1.7.2 LinkedList 的底层操作机制
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向首结点和尾节点
- 每个节点(Node)对象,里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
- 所以LinkedList的元素添加和删除,不是通过数组完成的,相对来说效率较高
- 模拟一个简单的双向链表【代码】
代码演示:
public class LinkedList01 {
public static void main(String[] args) {
//模拟一个简单的双向链表
Node jack = new Node("jack");
Node tom = new Node("tom");
Node hsp = new Node("老韩");
//连接三个结点,形成双向链表
//jack -> tom -> hsp
jack.next = tom;
tom.next = hsp;
//hsp -> tom -> jack
hsp.pre = tom;
tom.pre = jack;
Node first = jack; //让 first 引用指向 jack,就是双向链表的头结点
Node last = hsp; //让 last 引用指向 hsp,就是双向链表的尾结点
//演示,从头到尾进行遍历
System.out.println("===从头到尾进行遍历===");
while (true) {
if (first == null) {
break;
}
//输出first信息
System.out.println(first);
first = first.next;
}
//演示,从尾到头进行遍历
System.out.println("===从尾到头进行遍历===");
while (true) {
if (last == null) {
break;
}
//输出first信息
System.out.println(last);
last = last.pre;
}
//演示链表的添加对象/数据,是多么的方便
// 要求,是在 tom --------- 老韩直接,插入一个对象 smith
//1. 先创建一个 Node 结点,name 就是 smith
Node smith = new Node("smith");
//下面就把 smith 加入到双向链表了
smith.pre = tom;
smith.next = hsp;
tom.next = smith;
hsp.pre = smith;
//让 first 再次指向 jack
first = jack;//让 first 引用指向 jack,就是双向链表的头结点
//演示,从头到尾进行遍历
System.out.println("===从头到尾进行遍历===");
while (true) {
if (first == null) {
break;
}
//输出first信息
System.out.println(first);
first = first.next;
}
//让 last 再次重新指向 hsp
last = hsp; //让 last 引用指向 hsp,就是双向链表的尾结点
//演示,从尾到头进行遍历
System.out.println("===从尾到头进行遍历===");
while (true) {
if (last == null) {
break;
}
//输出first信息
System.out.println(last);
last = last.pre;
}
}
}
//定义一个 Node 类,Node 对象 表示双向链表的一个结点
class Node {
public Object item; //真正存放数据
public Node next; //指向后一个结点
public Node pre; //指向前一个结点
public Node(Object name) {
this.item = name;
}
public String toString() {
return "Node name=" + item;
}
}
1.7.3 LinkedList 的增删改查案例
@SuppressWarnings({"all"})
public class LinkedListCRUD {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
System.out.println("linkedList=" + linkedList);
//演示删除一个节点的元素
linkedList.remove(); // 这里默认删除的是第一个结点
//linkedList.remove(2);
System.out.println("linkedList=" + linkedList);
//修改某个结点对象
linkedList.set(1, 999);
System.out.println("linkedList=" + linkedList);
//得到某个结点对象
// get(1) 是得到双向链表的第二个对象
Object o = linkedList.get(1);
System.out.println(o);//999
//因为 LinkedList 是 实现了 List 接口, 遍历方式
System.out.println("===LinkeList 遍历迭代器====");
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next=" + next);
}
//普通for循环遍历
System.out.println("====普通for循环====");
for (int i = 0; i < linkedList.size(); i++) {
System.out.println("linkedList=" + linkedList.get(i));
}
}
}
//老韩源码阅读.
/* 1. LinkedList linkedList = new LinkedList();
public LinkedList() {
}
2. 这时 linkeList 的属性 first = null last = null
3. 执行 添加 public boolean add(E e) {
linkLast(e);
return true;
}
4.将新的结点,加入到双向链表的最后 void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null) first = newNode;
else l.next = newNode; size++; modCount++; } */
/* 老韩读源码 linkedList.remove();
// 这里默认删除的是第一个结点
1. 执行 removeFirst public E remove() {
return removeFirst();
}
*/
/*2. 执行public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null; f.next = null;
// help GC first = next;
if (next == null) last = null;
else next.prev = null; size--;
modCount++; return element; }
*/
}
1.7.4 ArrayList 和 LinkedList 比较
1.8 Set 接口和常用方法
1.8.1 Set 接口基本介绍
1.8.2 Set 接口的常用方法
和 List 接口一样, Set 接口也是 Collection 的子接口,因此,常用方法和 Collection 接口一样.
Set遍历方式:
@SuppressWarnings({"all"})
public class SetMethod {
public static void main(String[] args) {
// 1.以set接口的实现类HashSet 来讲解 接口的方法
//2. set 接口的实现类对象(Set接口对象),不能放重复的元素,可以添加一个null值
//3. set 接口对象存放数据是无序的(即添加的顺序和取出的顺序不一致),
// 4. 注意:但是一直是这个是固定顺序,不会一直改变
Set set = new HashSet();
set.add("jhon");
set.add("lucy");
set.add("jhon"); //重复
set.add("jack");
set.add(null);
set.add(null); //空
for (int i = 0; i < 10; i++) {
System.out.println("set=" + set);
}
//遍历,迭代器
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("set=" + next);
}
//增强for循环
for (Object o :set) {
System.out.println("set=" + o);
}
}
}
1.9 Set 接口实现类-HashSet
1.9.1 HashSet 的全面说明
代码演示:
@SuppressWarnings({"all"})
public class HashSet_ {
public static void main(String[] args) {
/*1.构造器走源码
public HashSet() {
map = new HashMap<>();
}
2. HashSet 可以存放 null ,但是只能有一个 null,即元素不能重复
*/
Set hashSet = new HashSet();
hashSet.add(null);
hashSet.add(null);
System.out.println("hashSet=" + hashSet);
}
}
1.9.2 HashSet 案例说明
@SuppressWarnings({"all"})
public class HashSet01 {
public static void main(String[] args) {
Set set = new HashSet();
//说明
// 1. 在执行 add 方法后,会返回一个 boolean 值
// 2. 如果添加成功,返回 true, 否则返回 false
// 3. 可以通过 remove 指定删除哪个对象
System.out.println(set.add("john")); //t
System.out.println(set.add("lucy")); //t
System.out.println(set.add("john")); //f
System.out.println(set.add("jack")); //t
System.out.println(set.add("Rose")); //t
set.remove("john");
System.out.println("set=" + set); //set=[Rose, lucy, jack]
set = new HashSet();
System.out.println("set=" + set);//0个
//4 Hashset 不能添加相同的元素/数据?
set.add("lucy");//添加成功
set.add("lucy");//加入不了
set.add(new Dog("tom"));//OK
set.add(new Dog("tom"));//Ok
System.out.println("set=" + set);
//在加深一下. 非常经典的面试题.
//看源码,做分析, 先给小伙伴留一个坑,以后讲完源码,你就了然
// 去看他的源码,即 add 到底发生了什么?=> 底层机制.
set.add(new String("hsp"));//ok
set.add(new String("hsp"));//加入不了.
System.out.println("set=" + set);
}
}
1.10 HashSet 底层机制说明
1.10 HashSet 底层机制说明
代码演示:
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("java");//到此位置,第 1 次 add 分析完毕.
hashSet.add("php");//到此位置,第 2 次 add 分析完毕
hashSet.add("java");
System.out.println("set=" + hashSet);
}
/*老韩对 HashSet 的源码解读
1. 执行 HashSet() public HashSet() {
map = new HashMap<>();
}
2. 执行 add()
public boolean add(E e) { //e = "java"
return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
}
3.执行 put() , 该方法会执行 hash(key) 得到 key 对应的 hash 值 算法 h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
4.执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
//table 就是 HashMap 的一个数组,类型是 Node[]
//if 语句表示如果当前 table 是 null, 或者 大小=0
//就是第一次扩容,到 16 个空间.
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据 key,得到 hash 去计算该 key 应该存放到 table 表的哪个索引位置
//并把这个位置的对象,赋给 p
//(2)判断 p 是否为 null
//(2.1) 如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="java",value=PRESENT)
//(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建
Node<K,V> e; K k;
//如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样
//并且满足 下面两个条件之一:
//(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
//(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同
//就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断 p 是不是一颗红黑树,
//如果是一颗红黑树,就调用 putTreeVal , 来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果 table 对应索引位置,已经是一个链表, 就使用 for 循环比较
//(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
// 注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点
// , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
// 注意,在转成红黑树时,要进行判断, 判断条件
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize(); // 如果上面条件成立,先 table 扩容.
// 只有上面条件不成立时,才进行转成红黑树
//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//size 就是我们每加入一个结点 Node(k,v,h,next), size++
if (++size > threshold)
resize();//扩容
afterNodeInsertion(evict);
return null;
}
*/
}
1.10.2 分析HashSet的扩容转成红黑树机制
代码演示:
@SuppressWarnings({"all"})
public class HashSetIncrement {
public static void main(String[] args) {
/*HashSet 底层是 HashMap, 第一次添加时,table 数组扩容到 16,
临界值(threshold)是 16*加载因子(loadFactor)是 0.75 = 12
如果 table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32
新的临界值就是 32*0.75 = 24, 依次类推
*/
HashSet hashSet = new HashSet();
// for(int i = 1; i <= 100; i++) {
// hashSet.add(i);//1,2,3,4,5...100 //
// }
/*在 Java8 中, 如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),
并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认 64),就会进行树化(红黑树),
否则仍然采用数组扩容机制
*/
// for (int i = 1; i < 12; i++) {
// hashSet.add(new A(i));
// }
System.out.println("hashSet=" + hashSet);
/* 当我们向 hashset 增加一个元素,-> Node -> 加入 table , 就算是增加了一个 size++ */
for (int i = 1; i <= 7; i++) {//在 table 的某一条链表上添加了 7 个 A 对象
hashSet.add(new A(i));
}
for (int i = 1; i <= 7; i++) {//在 table 的另外一条链表上添加了 7 个 A 对象
hashSet.add(new B(i));
}
}
}
class B {
private int n;
public B(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 200;
}
}
class A {
private int n;
public A(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 100;
}
}
1.10.3 HashSet 课堂练习 1
public class HashSetExercise {
public static void main(String[] args) {
/**定义一个 Employee 类,该类包含:private 成员属性 name,age
* 要求: 创建 3 个 Employee 对象放入 HashSet 中 当 name 和 age 的值相同时,
* 认为是相同员工, 不能添加到 HashSet 集合中 */
Set set = new HashSet();
set.add(new Employee("jack",15));
set.add(new Employee("rose",15));
set.add(new Employee("jack",15));
System.out.println("hash=" + set);
}
}
class Employee{
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 Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//如果 name 和 age 值相同,则返回相同的 hash 值
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
HashSet 课堂练习 2
public class HashSetExercise02 {
public static void main(String[] args) {
Set hashSet = new HashSet();
hashSet.add(new Employee1("jack",15.2,new MyDate("2000","11","5")));
hashSet.add(new Employee1("smith",15.2,new MyDate("1999","11","5")));
hashSet.add(new Employee1("jack",15.2,new MyDate("2000","11","5")));
System.out.println("hashSet=" + hashSet);
}
}
class Employee1 {
private String name;
private double sal;
private MyDate birthday;
public Employee1(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee1{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee1 employee1 = (Employee1) o;
return Objects.equals(name, employee1.name) && Objects.equals(birthday, employee1.birthday);
}
@Override
public int hashCode() {
return Objects.hash(name, birthday);
}
}
class MyDate {
String year;
String month;
String day;
public MyDate(String year, String month, String day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyDate myDate = (MyDate) o;
return Objects.equals(year, myDate.year) && Objects.equals(month, myDate.month) && Objects.equals(day, myDate.day);
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
@Override
public String toString() {
return "MyDate{" +
"year='" + year + '\'' +
", month='" + month + '\'' +
", day='" + day + '\'' +
'}';
}
}
1.11 Set 接口实现类-LinkedHashSet
1.11.1 LinkedHashSet 的全面说明
- LinkedHashSet是HashSet 的子类
- LinkedHashSet 底层是一个LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet 根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,使元素看起来是以插入的元素保存的。、
- LinkedHashSet 不允许重复添加元素。
1.11.2 LinkedHashSet 源码分析:
@SuppressWarnings({"all"})
public class LinkedHashSet_ {
public static void main(String[] args) {
Set set = new LinkedHashSet();
set.add(new String("AA"));
set.add(456);
set.add(456);
set.add(new Customer("刘",1001));
set.add(123);
set.add("HSP");
System.out.println("set" + set);
//老韩解读
//1,LinkedHashSet加入顺序和取出元素/数据的顺序一致
//2,LinkedHashSet底层维护的是一个LinkedHashMap(是HashMap的子类)
//3.LinkedHashSet底层结构(数组table+双向链表)
//4.添加第一次时,直接将数组table扩容到16,存放的结点类型是LinkedHashMap$Entry
//5,数组是HashMap$Node[]存放的元素/数据是LinkedHashMap$Entry类型
/*
//继承关系在内部类完成的
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
*/
}
}
class Customer{
private String name;
private int number;
public Customer(String name, int number) {
this.name = name;
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
1.11.3 LinkedHashSet 课后练习题 LinkedHashSetExercise.java
代码演示:
@SuppressWarnings({"all"})
public class LinkedHashSetExercise01 {
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new Car("奥拓", 1000));//OK
linkedHashSet.add(new Car("奥迪", 300000));//OK
linkedHashSet.add(new Car("法拉利", 10000000));//OK
linkedHashSet.add(new Car("奥迪", 300000));//加入不了
linkedHashSet.add(new Car("保时捷", 70000000));//OK
linkedHashSet.add(new Car("奥迪", 300000));//加入不了
System.out.println("linkedHashSet=" + linkedHashSet);
}
}
/* Car 类(属性:name,price), 如果 name 和 price 一样,
* 则认为是相同元素,就不能添加。 5min */
class Car {
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "\nCar{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
//重写 equals 方法 和 hashCode
// 当 name 和 price 相同时, 就返回相同的 hashCode 值, equals 返回 t
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
}
1.12 Map 接口和常用方法
1.12.1 Map 接口实现类的特点 [很实用]
代码演示:
@SuppressWarnings({"all"})
public class Map_ {
public static void main(String[] args) {
//老韩解读 Map 接口实现类的特点, 使用实现类 HashMap
// 1. Map 与 Collection 并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
// 2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
// 3. Map 中的 key 不允许重复,原因和 HashSet 一样,前面分析过源码.
// 4. Map 中的 value 可以重复
// 5. Map 的 key 可以为 null, value 也可以为 null ,
// 注意 key 为 null,,只能有一个,value 为 null ,可以多个
Map map = new HashMap();
map.put("no1", "韩顺平");//k-v
map.put("no2", "张无忌");//k-v
map.put("no1", "张三丰");//有相同的key,相当于替换
map.put("no3", "张三丰");//k-v
map.put(null,null);//k-v
map.put(null, "abc"); //等价替换
map.put("no4", null); //k-v
map.put("no5", null); //k-v
map.put(1, "赵敏");//k-v ,key可以是Object
map.put(new Object(), "金毛狮王");//k-v
// 通过 get 方法,传入 key ,会返回对应的 value
System.out.println(map.get(1)); //赵敏
System.out.println("map=" + map);
//map={no2=张无忌, null=abc, no1=张三丰, 1=赵敏, no4=null,
// no3=张三丰, no5=null, java.lang.Object@1b6d3586=金毛狮王}
}
}
public class MapSrouse_ {
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1","韩顺平");//k-v
map.put("no2","张无忌");//k-v
map.put(new Car(),new Person());//k-v
//老汉解读
//1. k-v最后是 HashMap$Node node = newNode(hash,key,value,null)
//2. k-v为了方便程序员的遍历,还会创建EntrySet 集合,该集合存放的元素的类型 Entry,而一个Entry
// 对象就有k-v EntrySet<Entry<K,V>>,即:transient Set<Map.Entry<K,V>> entrySet;
//3. entrySet中,定义的类型是Map.Entry,但实际上存放的还是HashMap$Node
// 这是因为 static class Node<K,V> implement Map.Entry<K,V>
//4. 当把HashMap$Node 对象存放到 entrySet 就方便我们的遍历,因为Map.Entry提供了重要方法
//K getKev():V getValue():
Set set = map.entrySet();
System.out.println(set.getClass()); // HashMap$EntrySet
for (Object obj : set) {
//System.out.println(obj.getclass());//HashMap$Node
//为了从HashMap$Node取出k-v
//1.先做一个向下转型
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "-" + entry.getValue());
}
}
}
class Car{}
class Person{}
K-V值是在HashMap$Node中的,Node是HashMap的内部类。
EntrySet集合里面的元素为Entry,一个Entry里面就有一个k-v EntrySet<Entry<K,V>>,
entrySet中定义的类型是Map.Entry,但实际上存放的还是HashMap$Node。
因为Node实现了接口Map.Entry<K,V>。
HashMap$Node 对象存放到 entrySet 就方便我们的遍历,因为Map.Entry提供了重要方法。getKey()和getValue()。
1.12.2 Map 接口常用方法
@SuppressWarnings({"all"})
public class MapMethod {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超", new Book("", 100));
map.put("邓超", "孙俪");//替换-> 一会分析源码
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
map.put("hsp", "hsp 的老婆");
System.out.println("map=" + map);
//remove:根据键删除映射关系
map.remove(null);
System.out.println("map=" + map);
//get:根据键获取值
map.get("鹿晗");
System.out.println("map=" + map);
//size:获取元素个数
System.out.println("k-v=" + map.size());
//isEmpty:判断个数是否为 0
System.out.println(map.isEmpty()); //F
//clear:清除 k-v
// map.clear();
// System.out.println("map=" + map);
//containsKey:查找键是否存在
System.out.println(map.containsKey("hsp"));//Ture
System.out.println(map.containsValue("hsp"));
}
}
class Book {
private String name;
private int num;
public Book(String name, int num) {
this.name = name;
this.num = num;
}
1.12.3 Map 接口遍历方法
代码演示:
@SuppressWarnings({"all"})
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
//第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keySet = map.keySet();
//(1)增强for
System.out.println("----第一种方式增强for----");
for (Object key : keySet) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式迭代器--------");
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的 values 取出
Collection values = map.values();
//这里可以使用所有的 Collections 使用的遍历方法
//这里可以使用所有的 Collections 使用的遍历方法
// (1) 增强 for
System.out.println("---取出所有的 value 增强 for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的 value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过 EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//增强for循环
System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
for (Object entry : entrySet) {
//将entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
// 向下转型 Map.Entry
Map.Entry m = (Map.Entry)entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
1.12.4 Map 接口课堂练习
代码演示:
@SuppressWarnings({"all"})
public class MapExercise {
public static void main(String[] args) {
Map hashMap = new HashMap();
hashMap.put(1,new Emp("jack",30000,1));
hashMap.put(2,new Emp("tom",21000,2));
hashMap.put(3,new Emp("milan",12000,3));
//遍历 2 种方式
//并遍历显示工资>18000 的员工(遍历方式最少两种)
//1.使用keyset 增强for
Set keySet = hashMap.keySet();
for (Object key : keySet) {
Emp emp = (Emp)hashMap.get(key);
if (emp.getSal()>18000) {
System.out.println(emp);
}
}
//2. 使用 EntrySet -> 迭代器
// 体现比较难的知识点
// 慢慢品,越品越有味道.
Set entrySet = hashMap.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry)iterator.next(); //iterator.next();这里取出来就是一个Entry,
//通过 entry 取得 key 和 value
Emp emp = (Emp) entry.getValue();
if (emp.getSal()>18000){
System.out.println(emp);
}
}
}
}
/*** 使用 HashMap 添加 3 个员工对象,要求
* 键:员工 id
* 值:员工对象
* 并遍历显示工资>18000 的员工(遍历方式最少两种)
* 员工类:姓名、工资、员工 id
*/
class Emp{
private String name;
private double sal;
private int id;
public Emp(String name, double sal, int id) {
this.name = name;
this.sal = sal;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", sal=" + sal +
", id=" + id +
'}';
}
}
1.13 Map 接口实现类-HashMap
1.13.1 HashMap 小结
1.13.2 HashMap 底层机制及源码剖析
1.13.3 HashMap 底层机制及源码剖析
源码演示讲解:
@SuppressWarnings({"all"})
public class HashMapSource1 {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("java", 10);//ok
map.put("php", 10);//ok
map.put("java", 20);//替换 value
System.out.println("map=" + map);//
/*老韩解读 HashMap 的源码+图解
1. 执行构造器 new HashMap()
初始化加载因子 loadfactor = 0.75
HashMap$Node[] table = null
2. 执行 put 调用 hash 方法,计算 key 的 hash 值 (h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {//K = "java" value = 10
return putVal(hash(key), key, value, false, true);
}
3.执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //辅助变量
//如果底层的 table 数组为 null, 或者 length =0 , 就扩容到 16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//取出 hash 值对应的 table 的索引位置的 Node, 如果为 null, 就直接把加入的 k-v
//, 创建成一个 Node ,加入该位置即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;;//辅助变量
// 如果 table 的索引位置的 key 的 hash 相同和新的 key 的 hash 值相同,
// 并 满足(table 现有的结点的 key 和准备添加的 key 是同一个对象 || equals 返回真)
// 就认为不能加入新的 k-v
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//如果当前的 table 的已有的 Node 是红黑树,就按照红黑树的方式处
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果找到的结点,后面是链表,就循环比较
for (int binCount = 0; ; ++binCount) {//死循环
if ((e = p.next) == null) {{//如果整个链表,没有和他相同,就加到该链表的最后
p.next = newNode(hash, key, value, null);
//加入后,判断当前链表的个数,是否已经到 8 个,到 8 个,后
//就调用 treeifyBin 方法进行红黑树的转换
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && //如果在循环比较过程中,发现有相同,就 break,就只是替换 value
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//每增加一个 Node ,就 size++
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
5. 关于树化(转成红黑树)
//如果 table 为 null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
//否则才会真正的树化 -> 剪枝
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
}
*/
}
}
1.13.4 HashMap出发扩容、树化情况。
代码演示:
public class HashMapSource2 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
for (int i = 1; i <= 12; i++) {
hashMap.put(new A(i),"hello");
}
System.out.println("hashMap=" + hashMap); //12个k-v
//布置一个任务,自己设计代码去验证,table 的扩容
// 0 -> 16(12) -> 32(24) -> 64(64*0.75=48)-> 128 (96) ->
// 自己设计程序,验证-》 增强自己阅读源码能力. 看别人代码。
}
}
class A{
private int num;
public A(int num) {
this.num = num;
}
@Override
public String toString() {
return "\nA{" +
"num='" + num + '\'' +
'}';
}
//所有的HashCode都一样了
@Override
public int hashCode() {
return 100;
}
}
1.14 Map 接口实现类-Hashtable
1.14.1 HashTable 的基本介绍
public class HashTableExercise {
public static void main(String[] args) {
Hashtable table = new Hashtable();
table.put("john",100);//ok
table.put(null,100);//NullPointerException异常
table.put("john",null);//NullPointerException异常
table.put("lucy",100);//ok
table.put("lic",100);//ok
table.put("lic",88);//替换
System.out.println(table);
//1,底层有数组Hashtable$Entry[]初始化大小为11
//2.临界值threshold 8=11*0.75;
//3。扩容:按照自己的扩容机制来进行即可,
//4.执行方法addEntry(hash,key,vaue,index); 添加K-V封装到Entry
//5.当if(count >= threshold)满足时,就进行扩容
//6.按照int newCapacity = (oldCapacity < 1)+1;的大小扩容。
}
}
1.14.2 Hashtable 和 HashMap 对比
1.15 Map 接口实现类-Properties
1.15.1 基本介绍
1.15.2 基本使用
public class Properties_ {
public static void main(String[] args) {
//老韩解读
//1. Properties 继承 Hashtable
//2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
//增加
Properties properties = new Properties();
//properties.put(null, "abc");//抛出 空指针异常
//properties.put("abc", null); //抛出 空指针异常
properties.put("john", 100);//k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的 key , value 被替换
System.out.println("properties=" + properties);
//通过 k 获取对应值
System.out.println(properties.get("lic"));//88
//删除
properties.remove("lic");
System.out.println("properties=" + properties);
//修改
properties.put("john", "约翰");
System.out.println("properties=" + properties);
}
}
1.16 总结-开发中如何选择集合实现类(记住)
手敲:在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下
-
先判断存储类型(一组对象[单列]或一组键值对[双列])
-
一组对象[单列]:Collection接口
允许重复:list
增删多:LinkedList(底层维护了一个双向链表)
改查多:ArrayList(底层维护Object类型的可变数组)
不允许重复:Set
无序:HashSet(底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树))
排序:TreeSet (可以使用内部类自定义排序)
插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
-
一组键值对[双列]:Map
键无序:HashMap [底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树]
键排序:TreeMap[]
键插入和取出顺序一致:LinkedHashMap
读取文件:Perproties
1.17 TreeSet和TreeMap
1.17.1 TreeSet源码讲解
@SuppressWarnings({"all"})
public class TreeSet_ {
public static void main(String[] args) {
//老韩解读
// 1. 当我们使用无参构造器,创建 TreeSet 时,仍然是无序的(没有按照插入顺序),这里是按照ASCII码进行自动排序
// 2. 老师希望添加的元素,按照字符串大小来排序
// 3. 使用 TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类) 并指定排序规则
// 4,简单看看源码
//老韩解读
/*
1. 构造器把传入的比较器对象,赋给了 TreeSet 的底层的 TreeMap 的属性 this.comparator
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
2.在 调用 treeSet.add("tom"), 在底层会执行到
if (cpr != null) { //cpr 就是我们的匿名内部类(对象)
do {
parent = t;
//动态绑定到我们的匿名内部类(对象)compare
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else//如果相等,即返回 0,这个 Key 就没有加入
return t.setValue(value);
} while (t != null);
}
*/
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// return ((String)o1).compareTo((String) o2);正序
// return ((String)o2).compareTo((String) o1);//倒序
return ((String)o2).length() - ((String)o1).length(); //按照长度大小排序
}
});
//添加数据
treeSet.add("jack");
treeSet.add("tom");
treeSet.add("hsp");
treeSet.add("a");
treeSet.add("tom");
System.out.println("treeSet=" + treeSet);
}
}
1.17.2 TreeMap源码讲解
@SuppressWarnings({"all"})
public class TreeMap_ {
public static void main(String[] args) {
//使用默认的构造器,创建 TreeMap, 是无序的(也没有排序)
//仍然是无序的(没有按照插入顺序),这里是按照ASCII码进行自动排序
/*
老韩要求:按照传入的 k(String) 的大小进行排序
*/
// TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator() {
//按照传入的 k(String) 的大小进行排序
// 按照 K(String) 的长度大小排序
@Override
public int compare(Object o1, Object o2) {
//return ((String)o2).compareTo((String)o1) ;
return ((String)o2).length() - ((String)o1).length();
}
});
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
treeMap.put("hsp", "韩顺平");//加入不了
System.out.println(treeMap);
/*
1. 构造器. 把传入的实现了 Comparator 接口的匿名内部类(对象), 传给给 TreeMap 的 comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
\2. 调用 put 方法
2.1 第一次添加, 把 k-v 封装到 Entry 对象,放入 root
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
2.2 以后添加
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do { //遍历所有的 key , 给当前 key 找到适当位置
parent = t;
因为传入的o1 o2,比较的时候o1-o2那就是从小到大,o2-o1就是从大到小,可以带数进去试一试,这里很巧秒的
cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的 compare
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果遍历过程中,发现准备添加 Key 和当前已有的 Key 相等,就不添加
return t.setValue(value);
}
while (t != null);
}
*/
}
}
如何比较大小:因为传入的o1 o2,比较的时候o1-o2那就是从小到大,o2-o1就是从大到小,可以带数进去试一试,这里很巧秒的
TreeMap底层源码
1.18 Collections 工具类
1.18.1 Collections 工具类介绍
1.18.2 Collections 方法
排序操作:(均为 static 方法)
查找、替换
代码演示:
public class Collections_ {
public static void main(String[] args) {
//创建 ArrayList 集合,用于测试.
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");
//reverse(List):反转 List 中元素的顺序
Collections.reverse(list);
System.out.println("list=" + list);
//shuffle(List):对 List 集合元素进行随机排序
System.out.println("====shuffle(List)===");
for (int i = 0; i < 5; i++) {
Collections.shuffle(list);
System.out.println("list=" + list);
}
//sort(List):根据元素的自然顺序对指定(ASCII) List 集合元素按升序排序
Collections.sort(list);
System.out.println("===自然排序后===");
System.out.println("list=" + list);
//sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
//我们希望按照 字符串的长度大小排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1,Object o2){
//可以加入校验代码.
return ((String)o2).length() - ((String)o1).length();
}
});
System.out.println("====字符串的长度大小排序====");
System.out.println("list=" + list);
//swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Collections.swap(list,0,1);
System.out.println("元素进行交换 :list=" + list);
//Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
System.out.println("====集合中的最大元素====");
System.out.println(Collections.max(list));
//Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
// 比如,我们要返回长度最大的元素
Object max = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("==长度最大的元素==");
System.out.println(max);
//Object min(Collection)
// Object min(Collection,Comparator)
// 上面的两个方法,参考 max 即可
//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println("tom 出现的次数=" + Collections.frequency(list, "tom"));
//void copy(List dest,List src):将 src 中的内容复制到 dest 中
ArrayList dest = new ArrayList();
//为了完成一个完整拷贝,我们需要先给 dest 赋值,大小和 list.size()一样
for (int i = 0; i < list.size(); i++) {
dest.add("");
}
//拷贝
Collections.copy(dest,list);
System.out.println("===将 src 中的内容复制到 dest 中===");
System.out.println(dest);
//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
//如果 list 中,有 tom 就替换成 汤姆
Collections.replaceAll(list, "tom", "汤姆");
System.out.println("list 替换后=" + list);
}
}
1.19 本章作业
1.封装一个新闻类,包含标题和内容属性,
代码演示:
public class HomeWork01 {
/*
按要求实现:
(1)封装一个新闻类,包含标题和内容属性,提供get、set方法,重写toString方法,打印对擦
时只打印标题;
(2)只提供一个带参数的构造器,实例化对象时,只初始化标题:并且实例化两个对象:
新闻一:新冠确诊病例超干万,数百万印度教信徒赴恒河“圣浴”引民众担忧
新闻二:男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生
(3)将新闻对象添加到ArrayList:集合中,并且进行倒序遍历;
(4)在遍历集合过程中,对新闻标题进行处理,超过15字的只保留前15个,然后在后边加
(5)在控制台打印遍历出经过处理的新闻标题;
*/
public static void main(String[] args) {
List list = new ArrayList();
News news1 = new News("新闻一:新冠确诊病例超干万,数百万印度教信徒赴恒河“圣浴”引民众担忧");
News news2 = new News("新闻二:男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生");
list.add(news1);
list.add(news2);
for (int i = list.size() - 1; i >= 0; i--) {
News news = (News) list.get(i);
System.out.println(processTitle(processTitle(news.getTitle())));
}
}
//专门写一个方法,处理现实新闻标题process处理
public static String processTitle(String title) {
if (title == null) {
return "";
}
if (title.length() > 15) {
return title.substring(0, 15) + "..."; //左闭右开
} else {
return title;
}
}
}
class News {
private String title;
private String content;
public News(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "News{" +
"title='" + title + '\'' +
'}';
}
}
2.使用ArrayList完成对对象Car{name,price}的各种操作
代码演示:
@SuppressWarnings({"all"})
public class HomeWork02 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
Car car1=new Car("宝马",400000);
Car car2=new Car("宾利",5000000);
arrayList.add(car1);
arrayList.add(car2);
//2.remove:删除指定元素
arrayList.remove(0);
System.out.println("2arrayList=" + arrayList);
arrayList.add(car1); //重新添加
// 3.contains:查找元素是否存在
System.out.println("3contains:查找元素是否存在:" + arrayList.contains(car1));
//4.size获取元素个数
System.out.println("4size获取元素个数:" + arrayList.size());
//5.isEmpty:判断是香为空
System.out.println("5isEmpty:判断是香为空:" + arrayList.isEmpty());
//6.clear:清空
//arrayList.clear();
//System.out.println("arrayList=" + arrayList);
//7.addAll::添加多个元素
List list = new ArrayList();
Car car3 = new Car("吉利",20000);
Car car4 = new Car("奔驰",60000);
list.add(car3);
list.add(car4);
arrayList.addAll(list);
System.out.println("7添加多个元素arrayList=" + arrayList);
//8.containsAll:查找多个元素是否都存在
System.out.println("8查找多个元素是否都存在" + arrayList.contains(list));
//9.removeAll:删除多个元素
arrayList.removeAll(list);
System.out.println("9删除多个元素arrayList=" + arrayList);
//使用增强for和迭代器来遍历所有的car,需要重写Car的toString.方法
Iterator iterator = arrayList.iterator();
System.out.println("迭代器来遍历");
while (iterator.hasNext()) {
Object car = iterator.next();
System.out.println(car);
}
System.out.println("增强for");
for (Object car : arrayList) {
System.out.println(car);
}
}
}
class Car {
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
3. 使用HashMap类实例化一个Map类型的对象m,键(String)和值(int)
public class HomeWork03 {
public static void main(String[] args) {
/*
按要求完成下列任务
1.使用HashMap类实例化一个Map类型的对象m,键(String)和值(int)分别用于存储员
工的姓名和工资,存入数据如下:jack—650元;tom一1200元;smith—2900元;
2.将jack的工资更改为2600元
3.为所有员工工资加薪100元:
4.遍历集合中所有的员工
遍历集合中所有的工资
*/
HashMap hashMap = new HashMap();
hashMap.put("jack",650); //in -> Integer
hashMap.put("tom",1200);//in -> Integer
hashMap.put("smith",2900);//in -> Integer
System.out.println("hashMap =" + hashMap);
//2.将jack的工资更改为2600元
hashMap.put("jack",2600);
System.out.println("将jack的工资更改为2600元hashMap=" + hashMap);
//3.为所有员工工资加薪100元:
/*自己的写法
Set entrySet = hashMap.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry)iterator.next();
int s =((int)(entry.getValue()))+100;
hashMap.put(entry.getKey(),s);
}
System.out.println(hashMap);
*/
//韩老师写法
Set keySet = hashMap.keySet();
for (Object key : keySet) {
//更新
hashMap.put(key,(Integer)hashMap.get(key)+100); //hashMap.get(key)取出来是Object类
}
//迭代器
Set keySet1 = hashMap.keySet();
Iterator iterator = keySet.iterator();
System.out.println("==迭代器==");
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + hashMap.get(key));
}
//通过 EntrySet 来获取 k-v
Set entrySet = hashMap.entrySet();// EntrySet<Map.Entry<K,V>>
for (Object entry :entrySet) {
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-"+ m.getValue());
}
//遍历集合中所有的工资
for (Object entry : entrySet) {
Map.Entry m = (Map.Entry) entry;
System.out.println("遍历集合中所有的工资" + m.getValue());
}
}
}
4.试分析HashSeti和TreeSet:分别如何实现去重的
5.
public class HomeWork05 {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
//分析源码
//add方法,因为TreeSet()构造器没有传入Comparator接口的匿名内部类
//所以在底层Comparable<? super K>k=(Comparable<?super K>)key;
//即把Person转成Comparable类型
treeSet.add(new Person());//ClassCastException.
treeSet.add(new Person());//ClassCastException.
treeSet.add(new Person());//ClassCastException.
System.out.println("treeSet" + treeSet); //只有一个,因为compareTo返回0
}
}
class Person implements Comparable{
@Override
public int compareTo(Object o) {
return 0;
}
}
6.
*/
public class HomeWork06 {
public static void main(String[] args) {
HashSet set = new HashSet();//ok
Person p1 = new Person(1001,"AA");//ok
Person p2 = new Person(1002,"BB");//ok
set.add(p1);//ok
set.add(p2);//ok
p1.name = "CC";
set.remove(p1); //删除不了,
System.out.println(set);//2
set.add(new Person(1001,"CC"));//ok
System.out.println(set);//3
set.add(new Person(1001,"AA"));//ok
System.out.println(set);//4个对象
}
}
class Person{
String name;
int id;
public Person(int id,String name) {
this.name = name;
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, id);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
7. 试写出Vector和ArrayList的比较?
底层结构 | 版本 | 线程安全(同步)效率 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 不安全,高效 | 无参构造是初始第一次扩容是10,然后第二次1.5倍扩容。初始(有参)有长度就直接1.5倍扩容 |
Vector | 可变数组Object[] | jdk1.0 | 安全,不高效 | 如果是无参,默认为10,默认按2倍扩容。如果是指定大小创建Vector每次按照两倍扩容 |
2 .泛型
2.1 泛型的理解和好处
2.1.1 看一个需求
public class Generic {
public static void main(String[] args) {
//使用传统的方法来解决
ArrayList arrayList = new ArrayList();
arrayList.add(new Dog("旺财",10));
arrayList.add(new Dog("来福",15));
arrayList.add(new Dog("小黄",5));
//添加猫猫
arrayList.add(new Cat("招财",5));
for (Object o :arrayList) {
//.ClassCastException
Dog dog = (Dog)o;
System.out.println(dog.name+dog.age);
}
}
}
/*
请编写程序,在ArrayList中,添加3个Dog对象
Dog对象含有name 和 age,并输出name 和 age(要求使用getXxx())
*/
class Dog{
public String name;
public int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Cat{
public String name;
public int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2.1.2使用传统方法的问题分析
2.1.3 泛型快速体验-用泛型来解决前面的问题
代码演示:
@SuppressWarnings({"all"})
public class Generic02 {
public static void main(String[] args) {
//使用传统的方法来解决==>泛型
//老汉解读
//1.当我ArrayList<Dog> 表示存放到ArrayList 集合中的元素是Dog类型
//2. 如果编译器发现添加的类型,不满足要求,就会报错
//3. 在遍历的时候可以直接取出 Dog 类型而不是 Object 类型
ArrayList<Dog> arrayList = new ArrayList<Dog>();
arrayList.add(new Dog("旺财",10));
arrayList.add(new Dog("来福",15));
arrayList.add(new Dog("小黄",5));
//不小心添加猫猫
//arrayList.add(new Cat("招财",5));
System.out.println("==使用泛型==");
for (Dog dog :arrayList) {
System.out.println(dog.name+dog.age);
}
}
}
/*
请编写程序,在ArrayList中,添加3个Dog对象
Dog对象含有name 和 age,并输出name 和 age(要求使用getXxx())
//用泛型来完成
*/
class Dog{
public String name;
public int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Cat{
public String name;
public int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2.2 泛型的理解和好处
2.2.1 泛型的好处
2.3 泛型介绍
代码演示:
public class Generic03 {
public static void main(String[] args) {
//注意,特别强调: E 具体的数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
Person<String> stringPerson = new Person<String>("hsp");
stringPerson.show();
//可以这样理解,上面的Person类
/*
class Person<E> {
E s;
public Person(E s) { //传入参数类型为E
this.s = s;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
}
public E f(){//返回类型使用E
return s;
}
public void show(){
System.out.println(s.getClass());//显示s的运行类型
}
}
*/
Person<Integer> integerPerson = new Person<>(100);
integerPerson.show();
/*
class Person<E> {
E s;
public Person(E s) { //传入参数类型为E
this.s = s;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
}
public E f(){//返回类型使用E
return s;
}
public void show(){
System.out.println(s.getClass());//显示s的运行类型
}
}
*/
}
}
//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
//或者是某个方法的返回值的类型,或者是参数类型
class Person<E> {
E s;
public Person(E s) { //传入参数类型为E
this.s = s;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
}
public E f(){//返回类型使用E
return s;
}
public void show(){
System.out.println(s.getClass());//显示s的运行类型
}
}
2.4 泛型的语法
2.4.1 泛型的声明
2.4.2 泛型的实例化
2.4.3 泛型使用举例
代码演示:
/** 创建 3 个学生对象 * 放入到 HashSet 中学生对象,
* 使用 放入到 HashMap 中,要求 Key 是 String name,
* Value 就是 学生对象 * 使用两种方式遍历 */
public class GenericExercise01 {
public static void main(String[] args) {
HashSet<Student> hashSet = new HashSet<Student>();
//使用泛型方式给 HashSet 放入 3 个学生对象
hashSet.add(new Student("jack",18));
hashSet.add(new Student("tom",28));
hashSet.add(new Student("mary",19));
//遍历
Iterator<Student> iterator = hashSet.iterator();
while (iterator.hasNext()) {
Student student = iterator.next();
System.out.println("student=" + student);
}
System.out.println("=========hashSet增强for:======");
for (Student student : hashSet) {
System.out.println(student.getName() + "-" + student.getAge());
}
使用泛型方式给 HashMap 放入 3 个学生对象
HashMap<String,Student> hashMap = new HashMap<String, Student>();
hashMap.put("milan",new Student("milan",18));
hashMap.put("smith",new Student("smith",28));
hashMap.put("hsp",new Student("hsp",25));
//EntrySet 迭代器
/*public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}*/
Set<Map.Entry<String, Student>> entries = hashMap.entrySet();
/*
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
*/
System.out.println("==============================");
Iterator<Map.Entry<String, Student>> iterator1 = entries.iterator();
while (iterator1.hasNext()) {
Map.Entry<String, Student> studentEntry = iterator1.next();
System.out.println(studentEntry.getKey() + "-" + studentEntry.getValue());
}
}
}
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2.4.4 泛型使用的注意事项和细节
public class GenericDetail {
public static void main(String[] args) {
//1.给泛型指向数据类型是,要求是引用类型,不能是基本数据类型
List<Integer> list = new ArrayList<Integer>(); //OK
//List<int> list2 = new ArrayList<int>();//错误
//2. 说明
// 因为 E 指定了 A 类型, 构造器传入了 new A()
// 在给泛型指定具体类型后,可以传入该类型或者其子类类型
Pig<A> aPig = new Pig<A>(new A());
aPig.f();
Pig<A> aPig2 = new Pig<A>(new B());
aPig2.f();
//3. 泛型的使用形式
ArrayList<Integer> list1 = new ArrayList<Integer>();
List<Integer> integers = new ArrayList<Integer>();
//在实际开发中,我们往往简写
// 编译器会进行类型推断, 老师推荐使用下面写法
ArrayList<Integer> lists = new ArrayList<>();
List<Integer> integers1 = new ArrayList<>();
ArrayList<Pig> pigs = new ArrayList<>();
}
}
class A{}
class B extends A{}
class Pig<E>{
E e;
public Pig(E e) {
this.e = e;
}
public void f(){
System.out.println(e.getClass());
}
}
2.5 泛型课堂类型
2.5.1 泛型课堂练习题
代码演示:
/**定义 Employee 类
* 1) 该类包含:private 成员变量 name,sal,birthday,其中 birthday 为 MyDate 类的对象;
* 2) 为每一个属性定义 getter, setter 方法;
* 3) 重写 toString 方法输出 name, sal, birthday
* 4) MyDate 类包含: private 成员变量 month,day,year;并为每一个属性定义 getter, setter 方法;
* 5) 创建该类的 3 个对象,并把这些对象放入 ArrayList 集合中(ArrayList 需使用泛型来定义),对集合中的元素进
行排序,并遍历输出:
*
* 排序方式: 调用 ArrayList 的 sort 方法 ,
* 传入 Comparator 对象[使用泛型],先按照 name 排序,如果 name 相同,则按生日日期的先后排序。【即:定制排序】
* 有一定难度 15min , 比较经典 泛型使用案例 GenericExercise02.java
*/
public class GenericExercise02 {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("jack",2000.0,new MyDate(2000,11,14)));
employees.add(new Employee("tom",3000.2,new MyDate(1999,11,24)));
employees.add(new Employee("mary",2050.0,new MyDate(2000,04,29)));
//迭代器
System.out.println("====迭代器====");
Iterator<Employee> iterator = employees.iterator();
while (iterator.hasNext()) {
Employee employee = iterator.next();
System.out.println(employee);
}
employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee employee1, Employee employee2) {
//先按照 name 排序,如果 name 相同,则按生日日期的先后排序。【即:定制排序】
//先对传入的参数进行验证
if (!(employee1 instanceof Employee && employee2 instanceof Employee)){
System.out.println("类型不正确");
return 0;
//我觉得这里没有必要,因为compare(Employee employee1, Employee employee2)
// 这里只能传入这个类型或者他的子类
}
int i = employee1.getName().compareTo(employee2.getName());
if (i != 0){
return i;
}
/*
//如果name相同 比较生日birthday -year
int yearMinus = employee1.getBirthday().getYear() - employee2.getBirthday().getYear();
if (yearMinus != 0){
return yearMinus;
}
//如果year相同 比较生日birthday -month
int monthMinus = employee1.getBirthday().getMonth() - employee2.getBirthday().getMonth();
if (monthMinus != 0){
return monthMinus;
}
//如果year相同 比较生日birthday -month
return employee1.getBirthday().getDay() - employee2.getBirthday().getDay();
*/
//上面是对 birthday 的比较,因此,我们最好把这个比较,放在 MyDate 类完成
//封装后,将来可维护性和复用性,就大大增强
//这里秩序返回比较的结果
return employee1.getBirthday().compareTo(employee2.getBirthday());
}
});
System.out.println("对员工进行排序");
System.out.println(employees);
}
}
class Employee{
private String name;
private double sal;
private MyDate birthday;
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "\nEmployee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
}
class MyDate implements Comparable<MyDate>{
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
@Override
public int compareTo(MyDate o) {
//把对year-month-day的比较放在这里
int yearMinus = year - o.getYear();
if (yearMinus != 0){
return yearMinus;
}
//如果year相同 比较生日birthday -month
int monthMinus = month - o.getMonth();
if (monthMinus != 0){
return monthMinus;
}
//如果year相同 比较生日birthday -month
return day - o.getDay();
}
}
2.6 自定义泛型
2.6.1 自定义泛型类 (难度)
代码演示:
public class CustomGeneric_ {
}
//老韩解读
//1. Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类
//2, T, R, M 泛型的标识符, 一般是单个大写字母
//3. 泛型标识符可以有多个.
//4. 普通成员可以使用泛型 (属性、方法)
//5. 使用泛型的数组,不能初始化
//6. 静态方法中不能使用类的泛型
class Tiger<T,R,M>{
String name;
T t; //属性使用到泛型
R r;
M m;
//定义数组不能定义数组空间 ,因为数组在 new 不能确定 T 的类型,就无法在内存开空间
T[] ts;
//构造器使用泛型(方法)
public Tiger(String name, T t, R r, M m) {
this.name = name;
this.t = t;
this.r = r;
this.m = m;
}
//因为静态是和类相关的,在类加载时,对象还没有创建
// 所以,如果静态方法和静态属性使用了泛型,JVM 就无法完成初始化
//static R r2;
//public static void m1(M m){}
public String getName() {//方法使用泛型
return name;//返回类型使用泛型
}
public void setName(String name) {
this.name = name;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public R getR() {
return r;
}
public void setR(R r) {
this.r = r;
}
public M getM() {
return m;
}
public void setM(M m) {
this.m = m;
}
}
public class CustomGeneric_ {
public static void main(String[] args) {
//T=Double, R=String, M=Integer
Tiger<Double, String, Integer> g = new Tiger<>("john");
g.setT(10.9); //OK
// g.setT("yy"); //错误,类型不对
System.out.println(g);
Tiger g2 = new Tiger("john~~");//OK T=Object R = Object M=Object
g2.setT("yy"); //OK ,因为 T=Object "yy"=String 是 Object 子类
System.out.println("g2="+g2);
}
}
//老韩解读
//1. Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类
//2, T, R, M 泛型的标识符, 一般是单个大写字母
//3. 泛型标识符可以有多个.
//4. 普通成员可以使用泛型 (属性、方法
//5. 使用泛型的数组,不能初始化
//6. 静态方法中不能使用类的泛型
class Tiger<T, R, M> {
String name;
T t; //属性使用到泛型
R r;
M m;
//定义数组不能定义数组空间 ,因为数组在 new 不能确定 T 的类型,就无法在内存开空间
T[] ts;
public Tiger(String name) {
this.name = name;
}
//构造器使用泛型(方法)
public Tiger(String name, T t, R r, M m) {
this.name = name;
this.t = t;
this.r = r;
this.m = m;
}
//因为静态是和类相关的,在类加载时,对象还没有创建
// 所以,如果静态方法和静态属性使用了泛型,JVM 就无法完成初始化
//static R r2;
//public static void m1(M m){}
public String getName() {//方法使用泛型
return name;//返回类型使用泛型
}
public void setName(String name) {
this.name = name;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public R getR() {
return r;
}
public void setR(R r) {
this.r = r;
}
public M getM() {
return m;
}
public void setM(M m) {
this.m = m;
}
}
2.6.2 自定义泛型接口
代码演示:
public class CustomInterfaceGeneric {
public static void main(String[] args) {
}
}
/**
* 泛型接口使用的说明 *
* 1. 接口中,静态成员也不能使用泛型 *
* 2. 泛型接口的类型, 在继承接口或者实现接口时确定 *
* 3. 没有指定类型,默认为 Object
*/
//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double>{
}
//当我们去实现 IA 接口时,因为 IA 在继承 IUsu 接口时,指定了 U 为 String R 为 Double
// ,在实现 IUsu 接口的方法时,使用 String 替换 U, 是 Double 替换 R
class AA implements IA{
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double r1, Double r2, String u1, String u2) {
}
}
//实现接口时,直接指定泛型接口的类型
// 给 U 指定 String 给 R 指定了 Integer
// 所以,当我们实现 IUsb 方法时,会使用 String 替换 U, 使用 Integer 替换 R
class BB implements IUsb<String,Integer>{
@Override
public Integer get(String s) {
return null;
}
@Override
public void hi(Integer integer) {
}
@Override
public void run(Integer r1, Integer r2, String u1, String u2) {
}
}
//没有指定类型,默认为 Object
// 建议直接写成 IUsb<Object,Object>
class CC implements IUsb{// 等价class CC implements IUsb<Object,Object> {
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi(Object o) {
}
@Override
public void run(Object r1, Object r2, Object u1, Object u2) {
}
}
interface IUsb<U,R>{
int n = 10;
//U name; 不能这样使用,因为还不是知道U是什么类型
//普通方法中,可以使用接口泛型
R get(U u);
void hi(R r);
void run(R r1,R r2, U u1, U u2);
//再jdk8中,可以在接口中,使用默认方法;
default R method(R r){
return null;
}
}
2.6.3 自定义泛型方法
代码演示:
public class CustomMethodGeneric {
public static void main(String[] args) {
Car car = new Car();
car.fly("宝马",100);//当调用方法时,传入参数,编译器,就会确定类型
car.fly(100,200);//当调用方法时,传入参数,编译器,就会确定类型
}
}
//泛型方法,可以定义在普通类中, 也可以定义在泛型类中
class Car {//普通类
public void run() { //普通方法
}
//说明 泛型方法
// 1. <T,R> 就是泛型
// 2. 是提供给 fly 使用的
public <T, R> void fly(T t, R r) { //泛型方法
System.out.println(t.getClass());
System.out.println(r.getClass());
//测试
// T->String, R-> ArrayList
Fish<String, ArrayList> fish = new Fish<>();
fish.hello(new ArrayList(), 11.3f);
}
}
class Fish<T, R> {//泛型类
public void run() {//普通方法
}
public <U, M> void eat(U u, M m) {//泛型方法
}
//说明
// 1. 下面 hi 方法不是泛型方法
// 2. 是 hi 方法使用了类声明的 泛型
public void hi(T t) {
}
//泛型方法,可以使用类声明的泛型,也可以使用自己声明泛型
public <K> void hello(R r, K k) {
System.out.println(r.getClass());
System.out.println(k.getClass());
}
}
自定义泛型方法练习
代码演示
public class CustomMethodGenericExercise {
public static void main(String[] args) {
//T->String, R->Integer, M->Double
Apple<String, Integer, Double> apple = new Apple<>();
apple.fly(10);//10 会被自动装箱 Integer10, 输出 Integer
apple.fly(new Bird());//Bird
}
}
class Apple<T, R, M> {//自定义泛型类
public <E> void fly(E e) { //泛型方法
System.out.println(e.getClass().getSimpleName());
}
//public void eat(U u) {}//错误,因为 U 没有声明
public void run(M m) { } //ok
}
class Bird { }
2.7 泛型的继承和通配符
2.7.1 泛型的继承和通配符说明 GenericExtends
代码演示:
public class GenericExtends {
public static void main(String[] args) {
Object o = new String("xx");
//泛型没有继承性
//List<Object> list = new ArrayList<String>();
//举例说明下面三个方法的使用
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AA> list3 = new ArrayList<>();
List<BB> list4 = new ArrayList<>();
List<CC> list5 = new ArrayList<>();
//如果是 List<?> c ,可以接受任意的泛型类型
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
//List<? extends AA> c: 表示 上限,可以接受 AA 或者 AA 子类
//printCollection2(list1);//x Object 是父类
// printCollection2(list2);//x String 和AA没有关系
printCollection2(list3);//√
printCollection2(list4);//√
printCollection2(list5);//√
printCollection3(list1);//√
//printCollection3(list2);//x String 和AA没有关系
printCollection3(list3);//√
//printCollection3(list4);//x AA的子类
//printCollection3(list5);//x AA的子类
}
//说明: List<?> 表示 任意的泛型类型都可以接受
public static void printCollection1(List<?> c) {
for (Object object : c) { // 通配符,取出时,就是 Object
System.out.println(object);
}
}
// ? extends AA 表示 上限,可以接受 AA 或者 AA 子类
public static void printCollection2(List<? extends AA> c) {
for (Object object : c) {
System.out.println(object);
}
}
// ? super 子类类名 AA:支持 AA 类以及 AA 类的父类,不限于直接父类,
// 规定了泛型的下限
public static void printCollection3(List<? super AA> c) {
for (Object object : c) {
System.out.println(object);
}
}
}
class AA {
}
class BB extends AA {
}
class CC extends BB {
}
2.8 本章作业
public class HomeWork01 {
public static void main(String[] args) {
}
@Test
public void testList(){
//说明,我们这里给T 指定的泛型就是User
DAO<User> userDAO = new DAO<>();
userDAO.save( "11",new User(11,12,"jack"));
userDAO.save( "12",new User(12,15,"tom"));
System.out.println("使用save()后的userDAO.map:" + userDAO.map);
System.out.println("使用get()后" + userDAO.get("11"));
userDAO.update("11",new User(13,22,"mary"));
System.out.println("使用update后的userDAO.map:" + userDAO.map);
System.out.println("使用list()后:" + userDAO.list());
userDAO.delete("12");
System.out.println("使用delete()后的userDAO.map:" + userDAO.map);
}
}
class DAO<T> {
Map<String, T> map = new HashMap<>();
public void save(String id, T entry) {
map.put(id, entry);
}
public T get(String id) {
return map.get(id);
}
public void update(String id, T entry) {
map.put(id, entry);
}
//返回map中存放的所有T对象
//遍历map[k-v],将map的所有valve(T entity),封装到ArrayList返回即可
public List<T> list() {
List<T> list = new ArrayList();
Set set = map.keySet();
for (Object key: set) {
list.add(map.get(key));
}
return list;
}
public void delete(String id) {
map.remove(id);
}
}
class User {
private int id;
private int age;
private String name;
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
2.9 JUnit
2.9.1 为什么需要 JUnit
2.9.2 基本介绍
2.9.3 使用步骤,看老师演示 JUnit_.java
我亦无他,惟手熟尔
坦克大战!!!