Java学习笔记(No.16)
2021/7/31 12:36:28
本文主要是介绍Java学习笔记(No.16),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Java泛型与容器(No.16)
容器也称作“集合”。在Java中我们可以使用“容器”来容纳和管理数据。容器很好的解决了数组不灵活,不可以随时扩容的问题。
容器的相关类都定义了泛型,在开发工作中,使用容器类时都要使用泛型。这样,在容器中存储和读取数据时避免了大量的类型判断,更加便捷。
泛型是JDK1.5中的一个新特性,其本质是“参数化类型”,亦即所操作的数据类型被指定为一个类型参数(Type Parameter),这种参数类型(亦即,泛型类型变量或泛型符号)可以用在类、接口、方法的创建中,分别称为泛型类、泛型接口、泛型方法。
1、泛型类和泛型接口(Generic Class And Generic Interface)
参数类型(亦即,泛型类型变量或泛型符号)由尖括号界定,放在类或接口名的后面,如“public class className<T>{}”;其中"T"称为泛型类型变量(亦即,泛型符号),意味着一个变量将被一个类型替代,替代类型变量的值将被当作参数或返回类型,同时参数类型(亦即,泛型类型变量或泛型符号)只是一个符号(即,占位符)而已,不存在任何意义,我们一般习惯使用一个大写的字母(如“T”或"E"或"K"或"V")。其示例如以下代码所示。
public class GenericsClass<V> {}//泛型类 public interface GenericsInterface<T> {}//泛型接口 public class GenericsClass<T> implements GenericsInterface<T> {}//实现泛型接口的泛型类
2、泛型方法(Generic Method)
是否拥有泛型方法,与其所在的类是否泛型无关。要定义泛型方法,只需将泛型参数列表置于返回值前。使用泛型方法时,不必指明参数类型,编译器会自己找出具体的类型。泛型方法除了与普通方法定义不同,其方法调用与普通方法一样。
-
2.1、示例代码(Sample Code)
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.util.Date; public class GenericsMethod { public static void main(String[] args) { new GenericsMethod().callGenericsMethod(); } public void callGenericsMethod() { genericsMethod(new Date()); } /** * 泛型方法 * @param t * @param <T> * @return */ public static <T> T genericsMethod(T t) { System.out.println("genericsMethod:" + t); return null; } }
-
2.2、运行结果(Run Result)
其运行结果,如以下信息所示。
genericsMethod:Sun Jul 18 16:57:24 CST 2021
3、基于ArrayList类的Collection容器与List容器常用方法(Common Methods Of Collection Container And List Container Based On ArrayList Class)
-
3.1、示例代码(Sample Code)
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; public class ArrayListClass { public static void main(String[] args) { /** *创建Collection容器(集合) */ Collection<String> collection=new ArrayList<>(); //清空列表元素 collection.clear(); //添加指定元素至列表末尾 System.out.println(collection.add("1")); System.out.println(collection.add("2")); System.out.println(collection.add("3")); //删除列表指定元素 System.out.println(collection.remove("3")); System.out.println(collection.remove("3")); //获取列表元素的总数量 System.out.println(collection.size()); //判断列表是否为空 System.out.println(collection.isEmpty()); //判断是否包含某个元素 System.out.println(collection.contains("1")); //将列表元素转换为数组 //System.out.println(collection); System.out.println(collection.toArray()); //使用Arrays类toString方法数组元素 System.out.println(Arrays.toString(collection.toArray())); /** *创建List容器(集合) */ List<String> list=new ArrayList<>(); //清空列表元素 list.clear(); //添加指定元素至列表末尾 System.out.println(list.add("a")); System.out.println(list.add("b")); System.out.println(list.add("c")); System.out.println(list.add("d")); //删除列表指定元素 System.out.println(list.remove("c")); System.out.println(list.remove("c")); //获取列表元素的总数量 System.out.println(list.size()); //判断列表是否为空 System.out.println(list.isEmpty()); //判断是否包含某个元素 System.out.println(list.contains("a")); //将列表元素转换为数组 //System.out.println(list); System.out.println(list.toArray()); //将列表中指定索引位置的数据更改为指定数据 list.set(1,"d"); //将列表中指定索引位置插入指定数据,原来指定索引位置及其之后数据整体往后移一位 list.add(1,"e"); //将列表中指定索引位置的数据删除,原来指定索引位置及其之后数据整体往前移一位 list.remove(1); //获取列表中指定索引位置的数据 System.out.println(list.get(1)); //获取列表中指定元素的第一次出现的索引(从前往后顺序查找),若未找到则返回-1 System.out.println(list.indexOf("d")); //获取列表中指定元素的最后一次出现的索引(从后往前顺序查找),若未找到则返回-1 System.out.println(list.lastIndexOf("d")); //截取列表中的指定开始位置到结束位置之间(不含结束位置)的数据 System.out.println(list.subList(1,list.size())); //使用Arrays类toString方法数组元素 System.out.println(Arrays.toString(list.toArray())); //使用Arrays类asList方法初始化列表数据:1、数组的修改会影响原数组,2、允许null值,且处理null值不会有异常 List<String> list1=Arrays.asList("a","b","c"); System.out.println(Arrays.toString(list1.toArray())); //使用List接口of方法(JDK1.9新特性)初始化列表数据:1、数组的修改不会影响原数组,2、不允许null值,且处理null值会有NullPointerException(空指针)异常 List<String> list2=List.of("a","b","c"); System.out.println(Arrays.toString(list2.toArray())); } }
-
3.2、运行结果(Run Result)
其运行结果,如以下信息所示。
true true true true false 2 false true [Ljava.lang.Object;@119d7047 [1, 2] true true true true true false 3 false true [Ljava.lang.Object;@776ec8df d 1 2 [d, d] [a, d, d] [a, b, c] [a, b, c]
4、List容器遍历的三种方式(Three Ways Of List Container Traversal)
-
4.1、示例代码(Sample Code)
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.util.Arrays; import java.util.Iterator; import java.util.List; public class ListContainerTraversal { public static void main(String[] args) { //使用Arrays工具类asList方法静态初始化List容器(集合) System.out.println("使用Arrays工具类asList方法静态初始化List容器(集合):"); List<String> list = Arrays.asList("1", "2", "3", "4", "5", "6"); //使用普通For循环遍历List容器(方式一) System.out.println("使用普通For循环遍历List容器(方式1)"); //for (int i = 0; i < list.toArray().length; i++) {} for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i) + "\t"); } System.out.println(); //使用增强For循环遍历List容器(方式二) System.out.println("使用增强For循环遍历List容器(方式2)"); for (String s : list) { System.out.print(s + "\t"); } System.out.println(); //使用迭代器循环遍历List容器(方式三) System.out.println("使用迭代器循环遍历List容器(方式3)"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.print(iterator.next() + "\t"); } System.out.println(); //使用List容器of方法静态初始化List容器(集合) System.out.println("使用List容器of方法静态初始化List容器(集合):"); List<String> list1 = List.of("未来", "美好", "中国", "繁荣", "世界", "和平");//JDK1.9新特性 //使用普通For循环遍历List容器(方式一) System.out.println("使用普通For循环遍历List容器(方式一)"); //for (int i = 0; i < list1.toArray().length; i++) {} for (int i = 0; i < list1.size(); i++) { System.out.print(list1.get(i) + "\t"); } System.out.println(); //使用增强For循环遍历List容器(方式二) System.out.println("使用增强For循环遍历List容器(方式二)"); for (String s1 : list1) { System.out.print(s1 + "\t"); } System.out.println(); //使用迭代器循环遍历List容器(方式三) System.out.println("使用迭代器循环遍历List容器(方式三)"); Iterator<String> iterator1 = list1.iterator(); while (iterator1.hasNext()) { System.out.print(iterator1.next() + "\t"); } System.out.println(); Iterator<String> iterator2 = list1.iterator(); for (; iterator2.hasNext(); ) { System.out.print(iterator2.next() + "\t"); } System.out.println(); Iterator<String> iterator3 = list1.iterator(); for (Iterator<String> it = iterator3; it.hasNext(); ) { System.out.print(it.next() + "\t"); } System.out.println(); } }
-
4.2、运行结果(Run Result)
其运行结果,如以下信息所示。
使用Arrays工具类asList方法静态初始化List容器(集合): 使用普通For循环遍历List容器(方式1) 1 2 3 4 5 6 使用增强For循环遍历List容器(方式2) 1 2 3 4 5 6 使用迭代器循环遍历List容器(方式3) 1 2 3 4 5 6 使用List容器of方法静态初始化List容器(集合): 使用普通For循环遍历List容器(方式一) 未来 美好 中国 繁荣 世界 和平 使用增强For循环遍历List容器(方式二) 未来 美好 中国 繁荣 世界 和平 使用迭代器循环遍历List容器(方式三) 未来 美好 中国 繁荣 世界 和平 未来 美好 中国 繁荣 世界 和平 未来 美好 中国 繁荣 世界 和平
5、基于HashMap类的Map容器常用方法(Common Methods Of Map Container Based On HashMap Class)
-
5.1、示例代码(Sample Code)
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.util.Arrays; import java.util.HashMap; import java.util.Map; public class MapContainerCommonMethod { public static void main(String[] args) { //创建Map容器 Map<String, Integer> map = new HashMap<>(); //添加Map容器数据 map.put("001",1); map.put("002",2); map.put("003",3); //Map容器数据的总数量 System.out.println(map.size()); //Map容器是否为空 System.out.println(map.isEmpty()); //Map容器是否包含指定Key System.out.println(map.containsKey("001")); //Map容器是否包含指定Value System.out.println(map.containsValue(1)); //获取Map容器指定Key对应Value System.out.println(map.get("001")); //获取Map容器指定Key对应Value,若没有则给指定默认Value System.out.println(map.getOrDefault("003",0)); //移除Map容器指定Key数据 System.out.println(map.remove("002")); //移除Map容器指定Key与指定Value数据 System.out.println(map.remove("003",1)); //替换Map容器指定Key数据 map.replace("003",1); System.out.println(map.get("003")); //替换Map容器指定Key与指定Value数据 map.replace("003",1,2); System.out.println(map.get("003")); //将Map容器数据转换为数组 System.out.println(map.values().toArray()); //输出Map容器数据 System.out.println(map); System.out.println(map.values()); System.out.println(Arrays.toString(map.values().toArray())); //清空Map容器 map.clear(); //静态初始化Map容器 Map<String, Integer> map1=Map.of("IDEA",1,"Eclipse",2,"MyEclipse",3); System.out.println(map1); System.out.println(map1.values()); System.out.println(Arrays.toString(map1.values().toArray())); } }
-
5.2、运行结果(Run Result)
其运行结果,如以下信息所示。
3 false true true 1 3 2 false 1 2 [Ljava.lang.Object;@119d7047 {001=1, 003=2} [1, 2] [1, 2] {MyEclipse=3, IDEA=1, Eclipse=2} [3, 1, 2] [3, 1, 2]
6、基于HashMap类的Map容器的Key不可重复性与Key相等比较方式(The Key’s Non Repeatability And The key’s Equality Comparison Method Of Map Container Based On HashMap Class)
-
6.1、示例代码(Sample Code)
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.io.Serializable; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * Map容器中Key是不允许重复的,若重复只能被存放一次,且后面的数据会覆盖前面的数据(即,只保留最新一次数据) * 由于Map容器中Key都是引用类型数据,所以使用equals方法来比较是否相等 */ public class MapContainerKey { public static void main(String[] args) { //创建Map容器对象 Map<String, Integer> map = new HashMap<>(); //添加Map容器数据 map.put("1", 1); map.put("2", 2); map.put("3", 3); map.put("3", 4); //Map容器数据的总数量 System.out.println(map.size()); //获取Map容器指定Key对应Value System.out.println(map.get("3")); //创建Map容器对象 Map<Employee,Integer> map1=new HashMap<>(); //添加Map容器数据 map1.put(new Employee(1,"昨天"),1); map1.put(new Employee(2,"今天"),2); map1.put(new Employee(3,"未来"),1); map1.put(new Employee(3,"明天"),3); //Map容器数据的总数量 System.out.println(map1.size()); //输出Map容器数据 System.out.println(map1); System.out.println(map1.values()); System.out.println(Arrays.toString(map1.values().toArray())); } } class Employee implements Serializable { private static final long serialVersionUID = -5289179772697078118L; private int id; private String name; //无参构造器 public Employee() { } //有参构造器 public Employee(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } //重写toString方法 @Override public String toString() { return "Employee{" + "id=" + id + ", name='" + name + '\'' + '}'; } //重写equals方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return id == employee.id; } //重写hashCode方法 @Override public int hashCode() { return Objects.hash(id); } }
-
6.2、运行结果(Run Result)
其运行结果,如以下信息所示。
3 4 3 {Employee{id=1, name='昨天'}=1, Employee{id=2, name='今天'}=2, Employee{id=3, name='未来'}=3} [1, 2, 3] [1, 2, 3]
7、Map容器遍历的三种方式(Three Ways Of Map Container Traversal)
-
7.1、示例代码(Sample Code)
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.util.Iterator; import java.util.Map; /** * Map容器中存放的数据是无序的 */ public class MapContainerTraversal { public static void main(String[] args) { //使用Map容器of方法静态初始化Map容器(集合) System.out.println("使用Map容器of方法静态初始化Map容器(集合):"); Map<Integer, String> map = Map.of(1, "1", 2, "2", 3, "3"); //使用Map容器keySet方法获取Key的增强For循环遍历Map容器(方式1) System.out.println("使用Map容器keySet方法获取Key的增强For循环遍历Map容器(方式1)"); for (Integer integer : map.keySet()) { System.out.println("key=" + integer + ",value=" + map.get(integer)); } //使用Map容器entrySet方法获取Key与value的增强For循环遍历Map容器(方式2) System.out.println("使用Map容器keySet方法获取Key与value的增强For循环遍历Map容器(方式2)"); for (Map.Entry<Integer, String> integerStringEntry : map.entrySet()) { System.out.println("key=" + integerStringEntry.getKey() + ",value=" + integerStringEntry.getValue()); } //使用Map容器keySet方法获取Key的迭代器遍历Map容器(方式3) System.out.println("使用Map容器keySet方法获取Key的迭代器遍历Map容器(方式3)"); Iterator<Integer> iterator1 = map.keySet().iterator(); while (iterator1.hasNext()) { Integer key = iterator1.next(); System.out.println("key=" + key + ",value=" + map.get(key)); } Iterator<Integer> iterator2 = map.keySet().iterator(); for (; iterator2.hasNext(); ) { Integer key = iterator2.next(); System.out.println("key=" + key + ",value=" + map.get(key)); } Iterator<Integer> iterator3 = map.keySet().iterator(); for (Iterator<Integer> it = iterator3; it.hasNext(); ) { Integer key = it.next(); System.out.println("key=" + key + ",value=" + map.get(key)); } } }
-
7.2、运行结果(Run Result)
其运行结果,如以下信息所示。
使用Map容器of方法静态初始化Map容器(集合): 使用Map容器keySet方法获取Key的增强For循环遍历Map容器(方式1) key=3,value=3 key=2,value=2 key=1,value=1 使用Map容器keySet方法获取Key与value的增强For循环遍历Map容器(方式2) key=3,value=3 key=2,value=2 key=1,value=1 使用Map容器keySet方法获取Key的迭代器遍历Map容器(方式3) key=3,value=3 key=2,value=2 key=1,value=1 key=3,value=3 key=2,value=2 key=1,value=1 key=3,value=3 key=2,value=2 key=1,value=1
8、List容器的ArrayList类的数组内存结构示意图以及优缺点(List Container ArrayList Class Array Memory Strcture Diagram And Advantages And Disadvantages)
- 8.1、数组内存结构示意图(Array Memory Strcture Diagram)
-
8.2、数组内存结构优缺点(Array Memory Strcture Advantages And Disadvantages)
-
优点
数据类型相同、内存空间连续、可随机访问数据。
-
缺点
插入或删除数据时,会导致数据的整体迁移。
-
总结:访问数组数据速度快,插入或删除数组数据慢。
9、List容器的LinkedList类的链表内存结构示意图以及优缺点(List Container LinkedList Class Linked List Memory Strcture Diagram And Advantages And Disadvantages)
- 9.1、链表内存结构示意图(Linked List Memory Strcture Diagram)
-
9.2、链表内存结构优缺点(Linked List Memory Strcture Advantages And Disadvantages)
-
优点
可以更好的进行插入或删除数据。
-
缺点
查找数据很慢,不支持随机访问数据。
-
总结:访问链表数据速度慢,插入或删除链表数据快。
10、Map数据结构瑕疵的灵魂三问及解决办法(Three Soul Problem And Solution Of Map Data Structure)
问题一(内容):假如Map的Key不是数值型,如何进行取余计算索引下标? 问题一(解决方案):调用Key的hashCode方法,将其转换为哈希码值(即,将任意类型转为数值型)进行取余计算索引下标。 其中,使用数学原理:“将一个未知的问题转换为一个已知的问题,问题就可以解决了”。 且Map的Key都是引用数据类型,根基类是Object,都有hashCode方法。 问题二(内容):新增数据时,Map如何扩容? 问题二(解决方案):当新增数据的数量达到Map门槛时,Map会自动进行扩容,且默认初始容量(即,默认初始数组长度)为“(1<<4)=16”,默认因子为“0.75f”,默认门槛为“16*0.75=12”,最大容量(即,最大数组长度)为“(1<<30)=1073741824”。 哈希码值的上限是“(2^31-1)=((1<<31)-1)=2147483647”,当超过哈希码值的上限时,不管有多大,最终都会存到数组中,理论上只要内存足够大,Map可以存放无限多个数据。 问题三(内容):虽然两个Key的哈希码值不同,但是其取余后索引下标相同,此时同一个索引下标位置如何存放这些数据? 问题三(解决方案一):可以设置一个因子(Factor),然后“容量(数组长度)*因子”就是本次扩容门槛。基于HashMap类的Map容器不能等用完才扩容,否则容易重复,只要达到Map门槛时就会扩容(用空间换时间<缓存>);而基于ArrayList类的List容器是等用完后才开始扩容。 问题三(解决方案二情形1):在JDK8时,先判断两个Key的hashCode是否相同,再判断两个Key的内容是否相同(equals),若相同则直接替换,若不相同,创建新对象,让新对象的next指向索引位置的旧对象,然后在索引位置存放新对象。 问题三(解决方案二情形2):在JDK11时,先判断两个Key的hashCode是否相同,再判断两个Key的内容是否相同(equals),若相同则直接替换,若不相同,创建新对象,接着查找索引位置的最后一个旧对象,让最后一个旧对象的next指向新对象。
11、二叉查找树与红黑树结构示意图(Binary Search Tree And Red Black Tree Structure Diagram)
12、HashMap与HashTable的区别(Difference Between HashMap And HashTable)
- 12.1、继承的父类不同
- 12.1.1、HashMap继承AbstractMap类。
- 12.1.2、HashTable继承Dictionary类。
- 12.1.3、HashMap与HashTable都实现了Map接口。
- 12.2、线程安全性不同
- 12.2.1、在多线程时,HashMap的实现是不同步的,所以是不安全的(与StringBuilder类似)。
- 12.2.2、在多线程时,HashTable的实现是同步的,所以是安全的(与StringBuffer类似)。
- 12.3、是否提供contains方法
- 12.3.1、由于contains方法容易误解,HashMap把HashTable的contains方法去掉了,只有containsKey方法与containsValue方法。
- 12.3.2、HashTable保留了contains方法、containsKey方法与containsValue方法,其中contains方法与containsValue方法功能相同。
- 12.4、key与value是否允许null值
- 12.4.1、HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键对应的值为null,当get方法返回null值时,可能是HashMap中没有该键,也可能是该键所对应的值为null。因此,在HashMap中不能由get方法来判断HashMap中是否存在某个键,而应该用containsKey方法来判断。
- 12.4.2、HashTable中,key(值)与value(值)都不允许出现nulll值。
- 12.5、两个遍历方式的内部实现不同
- 12.5.1、由于历史原因,HashTable使用了Enumeration的方式。
- 12.5.2、HashMap与HashTable使用了Iterator(迭代器)。
- 12.6、HashCode(哈希码值)计算方式不同
- 12.6.1、HashMap重新计算HashCode(哈希码值)。
- 12.6.2、HashTable直接使用对象的HashCode(哈希码值)。
- 12.7、内部实现使用的数组初始化和扩容方式不同
- 12.7.1、HashMap数组初始化时默认容量为”(1<<4)=16“,HashMap扩容时,将容量变为原来的2倍。
- 12.7.2、HashTable数组初始化时默认容量为”11“,HashTable扩容时,将容量变为原来的2倍+1。
13、在Lsit容器遍历时添加与删除数据(During List Container Traversal Add And Delete Data)
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; public class DuringListContainerTraversalAddAndDeleteData { public static void main(String[] args) { List<String> list1 = new ArrayList<>(); list1.add("0"); list1.add("1"); list1.add("1"); list1.add("2"); list1.add("1"); list1.add("3"); // /** // *List容器遍历时添加数据,由于List容器size方法会随着数据添加而一直增加,从而陷入死循环,因此不允许在遍历时添加数据 // */ // for (int i = 0; i < list1.size(); i++) { // System.out.println(i+"-"+list1.size()); // list1.add(String.valueOf(i+1)); // } // /** // *List容器使用For循环遍历时删除数据(错误方式) // *由于删除数组数据后,删除数据索引后的所有数据整体前移,且删除数据索引的新数据不会再被判断,而是从下一个索引继续判断,因此可能会遗漏重复的指定删除数据 // */ // for (int i = 0; i < list1.size(); i++) { // System.out.println(i+"-"+list1.size()+"-"+ list1.get(i)); // if("1".equals(list1.get(i))){ // list1.remove(i); // } // } // System.out.println(Arrays.toString(list1.toArray())); // /** // *List容器使用For循环遍历时删除数据(正确方式) // *由于数组从后往前遍历,无论删除数据是否成功(即,无论数据是否前移),数组中的每一个数据都不会漏掉 // */ // for (int i = list1.size() - 1; i >= 0; i--) { // System.out.println(i + "-" + list1.size() + "-" + list1.get(i)); // if ("1".equals(list1.get(i))) { // list1.remove(i); // } // } // System.out.println(Arrays.toString(list1.toArray())); // /** // *List容器使用增强For循环遍历时删除数据 // *由于删除数据后会改变遍历数据的结构,所以会抛出“并发修改异常(ConcurrentModificationException)”,因此不允许在使用增强For循环遍历时删除数据 // *若实在想使用增强For循环遍历时删除数据,只能在删除一个元素后立刻退出循环,但强烈不建议使用此方法 // */ // for (String s : list1) { // System.out.println(s+"-前"); // if("1".equals(s)){ // list1.remove(s); // } // System.out.println(s+"-后"); // } // /** // *List容器使用迭代器循环遍历时删除数据(方式一):使用while循环 // */ // Iterator<String> iterator1=list1.iterator(); // while(iterator1.hasNext()){ // String strNext=iterator1.next(); // System.out.println(strNext+"-前"); // if("1".equals(strNext)){ // //list1.remove(strNext);//错误的删除方式 // iterator1.remove();//正确的删除方式 // } // System.out.println(strNext+"-后"); // } // System.out.println(Arrays.toString(list1.toArray())); /** *List容器使用迭代器循环遍历时删除数据(方式二):使用快捷键itco,推荐使用 */ for (Iterator<String> iterator = list1.iterator(); iterator.hasNext(); ) { String next = iterator.next(); System.out.println(next+"-前"); if("1".equals(next)){ //list1.remove(next);//错误的删除方式 iterator.remove();//正确的删除方式 } System.out.println(next+"-后"); } System.out.println(Arrays.toString(list1.toArray())); } }
14、创建Resources Root类型的resources目录与resources目录的配置文件(Create The Resources Directory Of Resources Root Type And The Configure File Of Resources Directory)
其操作步骤,如下图所示。
-
14.1、创建resources目录。
-
14.2、将resources目录设置为Resources Root类型。
-
14.3、创建resources目录的配置文件。
-
14.4、设置resources目录中配置文件的参数。
15、使用Properties工具类将配置文件参数读取到内存(Use The Properties Tool Class To Read Configure File Parameter Into Memory)
-
15.1、示例代码(Sample Code)
-
15.1.1、不使用静态代码块读取配置文件
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.io.IOException; import java.util.Properties; /** * 使用Properties工具类将配置文件参数读取到内存(方法1):不使用静态代码块读取配置文件 */ public class UsePropertiesClassReadConfigureFile1 { public static void main(String[] args) throws IOException { //创建Properties工具类对象 Properties properties = new Properties(); //使用Properties工具类load方法加载资源配置文件 properties.load(UsePropertiesClassReadConfigureFile1.class.getClassLoader().getResourceAsStream("set.properties")); //获取配置文件的参数数据 System.out.println("不使用静态代码块读取配置文件"); System.out.println("UID=" + properties.getProperty("UID")); System.out.println("PWD=" + properties.getProperty("PWD")); } }
其运行结果,如以下信息所示。
不使用静态代码块读取配置文件 UID=resources root PWD=properties
-
15.1.2、使用静态代码块读取配置文件
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.io.IOException; import java.util.Properties; /** * 使用Properties工具类将配置文件参数读取到内存(方法2):使用静态代码块读取配置文件 */ public class UsePropertiesClassReadConfigureFile2 { public static final String MYUID; public static final String MYPWD; /** *使用静态代码块读取配置文件 */ static { //创建Properties工具类对象 Properties properties = new Properties(); //使用Properties工具类load方法加载资源配置文件 try { properties.load(UsePropertiesClassReadConfigureFile1.class.getClassLoader().getResourceAsStream("set.properties")); } catch (IOException e) { e.printStackTrace(); } //获取配置文件的参数数据 MYUID=properties.getProperty("UID"); MYPWD=properties.getProperty("PWD"); } public static void main(String[] args) { System.out.println("使用静态代码块读取配置文件"); System.out.println("UID=" + MYUID); System.out.println("PWD=" + MYPWD); } }
其运行结果,如以下信息所示。
使用静态代码块读取配置文件 UID=resources root PWD=properties
-
16、使用Properties工具类将配置文件参数写出到磁盘(Use Properties Tool Class To Write Configure File Parameter Into Disk)
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Properties; /** * 使用Properties工具类将配置文件参数写出到磁盘 */ public class UsePropertiesClassWriteConfigureFile { public static void main(String[] args) throws IOException { //创建Properties工具类对象 Properties propertiesWrite = new Properties(); //添加数据 propertiesWrite.put("ID", "001"); propertiesWrite.put("UID", "ResourcesRoot"); propertiesWrite.put("PWD", "Properties"); propertiesWrite.put("Test", "Test"); //配置文件名称 String myConfigureFileName = "test.properties"; //写入配置文件的参数数据 OutputStream outputStream = new FileOutputStream(new File("").getCanonicalPath() + File.separator + "javase" +File.separator + "resources" + File.separator + myConfigureFileName); propertiesWrite.store(outputStream, "使用Properties工具类将配置文件参数写出到磁盘"); } }
17、实现Comparable接口(Implement Comparable Interface)
-
17.1、示例代码(Sample Code)
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.io.Serializable; import java.util.*; public class ImplementComparableInterface { public static void main(String[] args) { //创建String类的List List<String> list1=new ArrayList<>(); list1.add("1"); list1.add("3"); list1.add("2"); list1.add("4"); //随机排列List Collections.shuffle(list1); System.out.println(Arrays.toString(list1.toArray())); //反转List Collections.reverse(list1); System.out.println(Arrays.toString(list1.toArray())); //排序List Collections.sort(list1); System.out.println(Arrays.toString(list1.toArray())); //创建Student类的List List<Student> list2=new ArrayList<>(); list2.add(new Student(1,"ad",99,11)); list2.add(new Student(3,"ae",66,15)); list2.add(new Student(2,"as",88,17)); list2.add(new Student(5,"ae",77,13)); list2.add(new Student(4,"af",55,5)); Collections.sort(list2); System.out.println(Arrays.toString(list2.toArray())); } } /** *通过实现Comparable接口来比较排序对象 */ class Student implements Serializable,Comparable<Student> { private static final long serialVersionUID = -482298316433918710L; private int id; private String name; private double score; private transient int age; public Student() { } public Student(int id, String name, double score, int age) { this.id = id; this.name = name; this.score = score; this.age = age; } public static long getSerialVersionUID() { return serialVersionUID; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getScore() { return score; } public void setScore(double score) { this.score = score; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return id == student.id && Objects.equals(name, student.name); } @Override public int hashCode() { return Objects.hash(id, name); } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", score=" + score + ", age=" + age + '}'; } //重写Comparable接口的compareTo方法 @Override public int compareTo(Student o) { // //开始比较要排序的属性,按照id进行升序/降序排序 // //return this.id-o.getId();//id升序 // return this.getId()-o.getId();//id升序 // //return (this.id-o.getId())*-1;//id降序 // return (this.getId()-o.getId())*-1;//id降序 //开始比较要排序的属性,先按照name升序/降序排序,若名字相同则按照id进行升序/降序排序 int result=this.getName().compareTo(o.getName()); if(result==0){//name相同 //继续比较id //result=this.id-o.getId(); result=this.getId()-o.getId(); //return result;//id升序 return result*-1;//id降序 } //return result;//name升序 return result*-1;//name降序 } }
-
17.2、运行结果(Run Result)
其运行结果,如以下信息所示。
[3, 2, 1, 4] [4, 1, 2, 3] [1, 2, 3, 4] [Student{id=2, name='as', score=88.0, age=17}, Student{id=4, name='af', score=55.0, age=5}, Student{id=5, name='ae', score=77.0, age=13}, Student{id=3, name='ae', score=66.0, age=15}, Student{id=1, name='ad', score=99.0, age=11}]
18、实现Comparator接口(Implement Comparator Interface)
-
18.1、示例代码(Sample Code)
其示例,如以下代码所示。
package com.xueshanxuehai.genericsandcontainers; import java.io.Serializable; import java.util.*; public class ImplementComparatorInterface { public static void main(String[] args) { System.out.println(new Teacher(4,"af",800,19)); //创建Teacher类的List List<Teacher> list=new ArrayList<>(); list.add(new Teacher(1,"ad",3000,55)); list.add(new Teacher(3,"ae",1000,22)); list.add(new Teacher(2,"as",2000,44)); list.add(new Teacher(5,"ae",1100,33)); list.add(new Teacher(4,"af",800,19)); System.out.println("通过实现Comparator接口来比较排序对象"); System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); //人力部门按照id(编号)升序排序 System.out.println("人力部门按照id(编号)升序排序"); Collections.sort(list,new HumanResourcesComparatorAsc()); System.out.println(Arrays.toString(list.toArray())); //人力部门按照id(编号)降序排序 System.out.println("人力部门按照id(编号)降序排序"); Collections.sort(list,new HumanResourcesComparatorDesc()); System.out.println(Arrays.toString(list.toArray())); //财务部门按照sal(薪资)升序排序 System.out.println("财务部门按照sal(薪资)升序排序"); Collections.sort(list,new FinanceComparatorAsc()); System.out.println(Arrays.toString(list.toArray())); //财务部门按照sal(薪资)降序排序 System.out.println("财务部门按照sal(薪资)降序排序"); Collections.sort(list,new FinanceComparatorDesc()); System.out.println(Arrays.toString(list.toArray())); //考勤部门按照name(名称)升序排序 System.out.println("考勤部门按照name(名称)升序排序"); Collections.sort(list,new AttendanceComparatorAsc()); System.out.println(Arrays.toString(list.toArray())); //考勤部门按照name(名称)降序排序 System.out.println("考勤部门按照name(名称)降序排序"); Collections.sort(list,new AttendanceComparatorDesc()); System.out.println(Arrays.toString(list.toArray())); //管理部门按照age(年龄)升序排序 System.out.println("管理部门按照age(年龄)升序排序"); Collections.sort(list,new ManageComparatorAsc()); System.out.println(Arrays.toString(list.toArray())); //管理部门按照age(年龄)降序排序 System.out.println("管理部门按照age(年龄)降序排序"); Collections.sort(list,new ManageComparatorDesc()); System.out.println(Arrays.toString(list.toArray())); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println("通过匿名内部类来比较排序对象"); System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); //考勤部门按照name(名称)升序排序 System.out.println("考勤部门按照name(名称)升序排序"); Collections.sort(list, new Comparator<Teacher>() { @Override public int compare(Teacher o1, Teacher o2) { return o1.getName().compareTo(o2.getName()); } }); System.out.println(Arrays.toString(list.toArray())); //考勤部门按照name(名称)降序排序 System.out.println("考勤部门按照name(名称)降序排序"); Collections.sort(list, new Comparator<Teacher>() { @Override public int compare(Teacher o1, Teacher o2) { return (o1.getName().compareTo(o2.getName()))*-1; } }); System.out.println(Arrays.toString(list.toArray())); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println("通过Lambda表达式来比较排序对象"); System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); //考勤部门按照name(名称)降序排序 System.out.println("考勤部门按照name(名称)降序排序"); Collections.sort(list,(t1,t2)->{ return 1;//原样排序列表(即,列表排序不变) }); System.out.println(Arrays.toString(list.toArray())); //考勤部门按照name(名称)升序排序 System.out.println("考勤部门按照name(名称)升序排序"); Collections.sort(list,(t1,t2)->{ return -1;//反转排序列表(即,逆序排序列表) }); System.out.println(Arrays.toString(list.toArray())); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); } } /** *通过实现Comparator接口来比较排序对象 *人力部门按照id(编号)升序排序 */ class HumanResourcesComparatorAsc implements Comparator<Teacher>{ //重写Comparator接口的compare方法 @Override public int compare(Teacher o1, Teacher o2) { return o1.getId()-o2.getId(); } } /** *通过实现Comparator接口来比较排序对象 *人力部门按照id(编号)降序排序 */ class HumanResourcesComparatorDesc implements Comparator<Teacher>{ //重写Comparator接口的compare方法 @Override public int compare(Teacher o1, Teacher o2) { return (o1.getId()-o2.getId())*-1; } } /** *通过实现Comparator接口来比较排序对象 *财务部门按照sal(薪资)升序排序 */ class FinanceComparatorAsc implements Comparator<Teacher>{ //重写Comparator接口的compare方法 @Override public int compare(Teacher o1, Teacher o2) { return (int) (o1.getSal()-o2.getSal()); } } /** *通过实现Comparator接口来比较排序对象 *财务部门按照sal(薪资)降序排序 */ class FinanceComparatorDesc implements Comparator<Teacher>{ //重写Comparator接口的compare方法 @Override public int compare(Teacher o1, Teacher o2) { return ((int) (o1.getSal()-o2.getSal()))*-1; } } /** *通过实现Comparator接口来比较排序对象 *考勤部门按照name(名称)升序排序 */ class AttendanceComparatorAsc implements Comparator<Teacher>{ //重写Comparator接口的compare方法 @Override public int compare(Teacher o1, Teacher o2) { return o1.getName().compareTo(o2.getName()); } } /** *通过实现Comparator接口来比较排序对象 *考勤部门按照name(名称)降序排序 */ class AttendanceComparatorDesc implements Comparator<Teacher>{ //重写Comparator接口的compare方法 @Override public int compare(Teacher o1, Teacher o2) { return (o1.getName().compareTo(o2.getName()))*-1; } } /** *通过实现Comparator接口来比较排序对象 *管理部门按照age(年龄)升序排序 */ class ManageComparatorAsc implements Comparator<Teacher>{ //重写Comparator接口的compare方法 @Override public int compare(Teacher o1, Teacher o2) { return o1.getAge()-o2.getAge(); } } /** *通过实现Comparator接口来比较排序对象 *管理部门按照age(年龄)降序排序 */ class ManageComparatorDesc implements Comparator<Teacher>{ //重写Comparator接口的compare方法 @Override public int compare(Teacher o1, Teacher o2) { return (o1.getAge()-o2.getAge())*-1; } } class Teacher implements Serializable { private static final long serialVersionUID = -5389180768711652314L; private int id; private String name; private double sal; private transient int age; public Teacher() { } public Teacher(int id, String name, double sal, int age) { this.id = id; this.name = name; this.sal = sal; this.age = age; } public static long getSerialVersionUID() { return serialVersionUID; } public int getId() { return id; } public void setId(int id) { 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 getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Teacher teacher = (Teacher) o; return id == teacher.id && Objects.equals(name, teacher.name); } @Override public int hashCode() { return Objects.hash(id, name); } @Override public String toString() { return "Teacher{" + "id=" + id + ", name='" + name + '\'' + ", sal=" + sal + ", age=" + age + '}'; } }
-
18.2、运行结果(Run Result)
其运行结果,如以下信息所示。
通过实现Comparator接口来比较排序对象 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 人力部门按照id(编号)升序排序 [Teacher{id=1, name='ad', sal=3000.0, age=55}, Teacher{id=2, name='as', sal=2000.0, age=44}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=4, name='af', sal=800.0, age=19}, Teacher{id=5, name='ae', sal=1100.0, age=33}] 人力部门按照id(编号)降序排序 [Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=4, name='af', sal=800.0, age=19}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=2, name='as', sal=2000.0, age=44}, Teacher{id=1, name='ad', sal=3000.0, age=55}] 财务部门按照sal(薪资)升序排序 [Teacher{id=4, name='af', sal=800.0, age=19}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=2, name='as', sal=2000.0, age=44}, Teacher{id=1, name='ad', sal=3000.0, age=55}] 财务部门按照sal(薪资)降序排序 [Teacher{id=1, name='ad', sal=3000.0, age=55}, Teacher{id=2, name='as', sal=2000.0, age=44}, Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=4, name='af', sal=800.0, age=19}] 考勤部门按照name(名称)升序排序 [Teacher{id=1, name='ad', sal=3000.0, age=55}, Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=4, name='af', sal=800.0, age=19}, Teacher{id=2, name='as', sal=2000.0, age=44}] 考勤部门按照name(名称)降序排序 [Teacher{id=2, name='as', sal=2000.0, age=44}, Teacher{id=4, name='af', sal=800.0, age=19}, Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=1, name='ad', sal=3000.0, age=55}] 管理部门按照age(年龄)升序排序 [Teacher{id=4, name='af', sal=800.0, age=19}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=2, name='as', sal=2000.0, age=44}, Teacher{id=1, name='ad', sal=3000.0, age=55}] 管理部门按照age(年龄)降序排序 [Teacher{id=1, name='ad', sal=3000.0, age=55}, Teacher{id=2, name='as', sal=2000.0, age=44}, Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=4, name='af', sal=800.0, age=19}] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 通过匿名内部类来比较排序对象 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 考勤部门按照name(名称)升序排序 [Teacher{id=1, name='ad', sal=3000.0, age=55}, Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=4, name='af', sal=800.0, age=19}, Teacher{id=2, name='as', sal=2000.0, age=44}] 考勤部门按照name(名称)降序排序 [Teacher{id=2, name='as', sal=2000.0, age=44}, Teacher{id=4, name='af', sal=800.0, age=19}, Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=1, name='ad', sal=3000.0, age=55}] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 通过Lambda表达式来比较排序对象 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 考勤部门按照name(名称)降序排序 [Teacher{id=2, name='as', sal=2000.0, age=44}, Teacher{id=4, name='af', sal=800.0, age=19}, Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=1, name='ad', sal=3000.0, age=55}] 考勤部门按照name(名称)升序排序 [Teacher{id=1, name='ad', sal=3000.0, age=55}, Teacher{id=3, name='ae', sal=1000.0, age=22}, Teacher{id=5, name='ae', sal=1100.0, age=33}, Teacher{id=4, name='af', sal=800.0, age=19}, Teacher{id=2, name='as', sal=2000.0, age=44}] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
19、注意事项(Matters Needing Attentions)
- 1、在泛型中,只能使用引用类型。
- 2、若一个类或接口有一个或多个类型变量(亦即,泛型符号),则可以使用泛型。
- 3、若一个类去实现的接口是泛型接口,则实现类必须是泛型类,且与接口保持一致,还可以新增符号。
- 4、在基于ArrayList类的List容器中使用add方法添加数组数据时,若容量不够则自动扩容,且除第一次外每一次都在上一次基础上扩容50%(即,数组扩充容量依次为“0、10、15、22、33、49、73、109、…”)。
- 5、在基于LinkedList类的List容器中使用add方法添加链表数据时,每一次添加链表数据后链表容量都加1。
- 6、在基于ArrayList类的List容器中使用remove方法按指定元素移除数组数据时,先检查数组是否存在指定元素的索引,若存在再调用FastRemove方法移除数据(即,将指定索引后所有数据整体前移一位,然后将最后索引值置为NULL),因此数组容量不会改变。
- 7、在基于LinkedList类的List容器中使用remove方法按指定元素移除链表数据时,先检查链表是否存在指定元素的索引,若存在且指定索引在前一半索引中则从前往后获取指定索引链表数据,若存在且指定索引在后一半索引中则从后往前获取指定索引链表数据,然后再移除获取的指定索引链表数据,否则不存在抛出下标越界异常(IndexOutOfBoundsException),且将链表容量减1。
- 8、在基于ArrayList类的List容器中使用remove方法按指定索引移除数组数据时,先检查数组是否存在指定索引,若存在再调用FastRemove方法移除数据(即,将指定索引后所有数据整体前移一位,然后将最后索引值置为NULL),因此数组容量不会改变。
- 9、在基于LinkedList类的List容器中使用remove方法按指定索引移除链表数据时,先检查链表是否存在指定索引,若存在且指定索引在前一半索引中则从前往后获取指定索引链表数据,若存在且指定索引在后一半索引中则从后往前获取指定索引链表数据,然后再移除获取的指定索引链表数据,否则不存在抛出下标越界异常(IndexOutOfBoundsException),且将链表容量减1。
- 10、在基于ArrayList类的List容器中使用clear方法清空数组数据时,直接将所有数组数据值置为NULL,且将数组容量置为0。
- 11、在基于LinkedList类的List容器中使用clear方法清空链表数据时,清空所有链表数据(即,节点)之间的关系,同时把所有链表数据值置为NULL,且将链表容量置为0。
- 12、在基于ArrayList类的List容器中使用get方法按指定索引获取数组数据时,先检查数组是否存在指定索引,若存在直接获取指定索引数组数据,否则不存在抛出下标越界异常(IndexOutOfBoundsException)。
- 13、在基于LinkedList类的List容器中使用get方法按指定索引获取链表数据时,先检查链表是否存在指定索引,若存在且指定索引在前一半索引中则从前往后获取指定索引链表数据,若存在且指定索引在后一半索引中则从后往前获取指定索引链表数据,否则不存在抛出下标越界异常(IndexOutOfBoundsException)。
- 14、HashMap类的底层是一个Node类型的数组(即,Node<K,V>[]),且其唯一存放数据的索引范围为唯一Key值取余Node数组容量(即,(key%Node<K,V>.length)=[0,(Node<K,V>.length-1)])。
- 15、基于HashMap类的Map容器中存放的数据是无序的,Map容器中Key是不允许重复的,若重复只能被存放一次,且后面的数据会覆盖前面的数据(即,只保留最新一次数据),且Map容器中Key都是引用类型数据,所以使用equals方法来比较是否相等。
- 16、在基于HashMap类的Map容器中使用put方法添加数据时,若存在同一索引已存储其它不同数据,直接将同一索引的最后一个单向链表节点的next指向添加数据对应的新节点,且当同一索引的单向链表节点数查过8个时会转换为红黑树结构存储数据,否则不存在直接在索引位置存储添加数据。
- 17、无论是For循环或是增强For循环还是Iterator迭代器循环,都不能使用List容器add方法添加数据。
- 18、在For循环中可以使用List容器remove方法删除数据,在增强For循环中不可以使用List容器remove方法删除数据,在Iterator迭代器循环中不可以使用List容器remove方法删除数据,只可以使用Iterator迭代器的remove方法删除数据,且推荐使用Iterator迭代器的remove方法删除数据。
参考资料(Reference Data):泛型与容器、Java9中List.of和Arrays.asList区别、二叉查找树、红黑树
学习网站地址(即"学习网址",Learning Website Address):Java泛型与容器
这篇关于Java学习笔记(No.16)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-27消息中间件底层原理资料详解
- 2024-11-27RocketMQ底层原理资料详解:新手入门教程
- 2024-11-27MQ底层原理资料详解:新手入门教程
- 2024-11-27MQ项目开发资料入门教程
- 2024-11-27RocketMQ源码资料详解:新手入门教程
- 2024-11-27本地多文件上传简易教程
- 2024-11-26消息中间件源码剖析教程
- 2024-11-26JAVA语音识别项目资料的收集与应用
- 2024-11-26Java语音识别项目资料:入门级教程与实战指南
- 2024-11-26SpringAI:Java 开发的智能新利器