一、复习
1.1 日期时间
第 1 代:java.util.Date、SimpleDateFormat 日期时间格式化
第 2 代:Calendar 日历类、TimeZone 时区类、Locale 语言环境类
第 3 代:
本地日期时间:java.time.LocalDateTime、LocalDate、LocalTime
ZoneId 时区类、ZonedDateTime(区分时区)、Instant 瞬时类(不区分时区)
Duration 时间区间、Peroid 日期区间
FormatStyle 格式化模板类、DateTimeFormatter 格式化类
1.2 与字符串相关的类型
不可变的字符串:String
可变的字符串:StringBuilder、StringBuffer
StringBuilder、StringBuffer 的区别
StringBuffer 的方法有 synchronized(同步锁),即线程安全的,它是旧版本。
StringBuilder 的方法没有 synchronized(同步锁),即线程不安全的,它是较新的。
StringBuilder、StringBuffer 的默认初始化大小是 16,扩容的机制:2 倍+2。
String 与 StringBuilder、StringBuffer 的区别
- String 不可变:
- 缺点:凡是修改都会产生新对象。如果涉及到比较频繁的修改、拼接等操作,建议使用 StringBuilder、StringBuffer。
- 优点:如果使用的是字符串常量对象,
可以实现共享
,节省内存。字符串的对象可以直接使用""来表示,简洁方便。
- StringBuilder、StringBuffer 可变
- 缺点:只能 new
- 优点:在原对象基础上可以直接修改字符串的内容,
不会产生新对象,效率更高
。
扩展:java.util.StringJoiner,它不是用来表示字符串,而是用来实现字符串的拼接
package com.atguigu.extend;
public class TestStringJoiner {
public static void main(String[] args) {
//现在有一个数组,里面是一组字符串,想要对这组字符串做拼接操作,结果想要得到:
// (hello-world-java-atguigu)
String[] arr = {"hello","world","java","atguigu"};
/*StringJoiner joiner = new StringJoiner("-","(",")");
for (String s : arr) {
joiner.add(s);
}
System.out.println(joiner);*/
String result = "(";
for (int i = 0; i < arr.length; i++) {
if(i<arr.length-1) {
result += arr[i] + "-";
}else{
result += arr[i];
}
}
result += ")";
System.out.println(result);
}
}
StringBuffer、StringBuilder 的方法:
- 增加:append、insert 等
- 删除:delete、deletCharAt 等
- 修改:setLength、setCharAt、reverse、replace 等
- 查询:indexOf、lastIndexOf、charAt、length 等
String 的方法:
- trim:去前后空格
- indexOf、lastIndexOf:查询某个子串的首次/末次下标
valueOf(各种类型的值)
:将各种类型的值转为字符串- equals、equalsIgnoreCase:区分和不区分大小写比较 2 个字符串是否相等
- toUpperCase、toLowerCase:转大小写
- length:字符串长度
- concat:拼接 ,建议推荐直接使用 +
- toCharArray:转 char[]数组
- endsWith、startsWith:判断是否以 xx 结尾和开头
- contains:判断是否包含 xx 子串
- compareTo(实现 Comparable 接口)、compareToIgnoreCase:比较 2 个字符串大小区分、不区分大小写
- isBlank(除了空格等空白字符之外没有其他字符)、isEmpty(长度为 0,即""):判断是否为空
- replace、replaceAll、replaceFirst:字符串替换,replaceAll 和 replaceFirst 支持正则
- substring:字符串截取
- matches:判断字符串是不是满足正则规则
- split:按照某个规则对字符串进行拆分
- getBytes:对字符串进行编码
二、集合(非常非常非常重要)
2.1 集合的概述
Java 中集合分为 2 大类:
- Collection 系列:单列集合,用于存储一组对象,对象与对象之间是独立的,比喻:单身 party
- Map 系列:双列集合,用于存储一组键值对(key,value),key 和 value 之间是有映射关系,比喻:情侣 party
集合和数组的区别:
(1)集合只能用来装对象,不能用来装基本数据类型的值。如果把基本数据类型的值放到集合中,会自动装箱为它包装类的对象。
数组既可以装对象,又可以装基本数据类型的值。
(2)集合的数据结构更丰富,可以使用更多的场景。大部分集合的容量是可以自动扩充,不需要程序员操心扩容的问题。
数组的结构非常单一。而且数组的长度一旦确定,就不能修改,如果要扩容啥的,需要创建新数组。
2.2 Collection 系列
2.2.1 Collection 根接口
Collection 接口中定义了一组方法,这组方法适用于 Collection 系列的所有集合。
1、添加
- add(一个元素):一次添加 1 个元素
- addAll(另一个 Collection 系列的集合 c):把 c 集合中的所有元素都添加到当前集合中
Collections 工具类有一个方法,可以一次添加多个元素。
Collections.addAll(集合, 一组元素)
;
package com.atguigu.collection;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
public class TestCollection {
@Test
public void test1(){
//演示添加
//(1)先创建一个集合,比喻:准备一个容器,用来装东西
// Collection c = new Collection();//错误,因为Collection是接口,接口不能直接new对象。
//这里我们找Collection接口的实现类来创建对象。它下面的实现类很多,使用频率最高的是ArrayList。
Collection list = new ArrayList();//多态引用
//我这里使用多态引用的目的是为了 编译时看左边,即编译时只看 Collection里面有的 方法,此时看不到ArrayList新增的别的方法
//实际开发中,没必要非写成多态引用不可
list.add("hello");
list.add(1);//1会被自动装箱为Integer的对象
list.add(LocalDate.now());
System.out.println(list);
//[hello, 1, 2025-07-16]
}
@Test
public void test2(){
//假设:list集合只想用来装String对象
//<类型>这种语法格式就是泛型,对于集合来说,<>里面是写元素的类型的
//Collection<String> list = new ArrayList<String>();
//从JDK7开始,左边写<类型>,右边可以省略,因为可以自动推断
Collection<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
// list.add(1);//错误,类型不符合
// list.add(LocalDate.now());//错误,类型不符合
System.out.println(list);
}
@Test
public void test3(){
Collection<String> one = new ArrayList<>();
one.add("hello");
one.add("world");
Collection<String> two = new ArrayList<>();
two.add("java");
two.add("atguigu");
two.add("world");
one.addAll(two);//把two集合中的元素,都添加到one中
System.out.println(one);//[hello, world, java, atguigu, world]
System.out.println(two);//[java, atguigu, world]
}
@Test
public void test4(){
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello","world","java","atguigu");
System.out.println(one);
}
}
2、修改(无)
3、删除
remove(一个元素)
:删除一个元素- removeAll(另一个集合 c):从当前集合中删除一组对象。this = this - (this ∩ c)
removeIf(Predicate 接口的实现类对象)
:我们需要编写一个类(有名或匿名)实现 Predicate 接口,重写 booleantest(T t)
方法,告诉 removeIf 删除元素的条件是什么。clear()
:清空集合retainAll(另一个集合 c)
:从当前集合中删除两个集合的非交集部分,保留两者的交集部分。this = this ∩ c。
package com.atguigu.collection;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Predicate;
public class TestCollection2 {
@Test
public void test1(){
//演示修改
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello","world","java","atguigu");
System.out.println(one);
//没有修改方法
}
@Test
public void test2(){
//演示删除
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello","world","java","atguigu");
System.out.println(one);
one.remove("world");
System.out.println(one);
System.out.println("=====================");
Collection<String> two = new ArrayList<>();
two.add("java");
two.add("atguigu");
two.add("world");
one.removeAll(two);
System.out.println(one);
System.out.println(two);
}
@Test
public void test3(){
//演示删除
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello","world","java","atguigu");
System.out.println(one);
//假设我们要删除包含o字母的单词
/*
Collection集合的removeIf方法的形参是 Predicate<T>类型。
Predicate是一个接口,而且是一个函数式接口(只有1个抽象方法需要重写的接口,它有@FunctionalInterface。
Predicate的抽象方法: boolean test(T t);
当我们重写test方法,会在方法体里面编写条件,条件满足返回true,不满足返回false。
这里removeIf方法代表删除,那么表示test方法返回true的元素要被删除,返回false不删除。
Predicate<T>的<T>用于表示要判断的元素的类型,这里元素是String,那么T就写String
*/
Predicate<String> p = new Predicate<String>() {
@Override
public boolean test(String s) { //这里的s就是集合中的一个一个元素
/*if(s包含o字母){
return true;
}else{
return false;
}*/
/*if(s.contains("o")){
return true;
}else{
return false;
}*/
return s.contains("o");
}
};
one.removeIf(p);//在removeIf方法内部,会调用p的test方法依次判断每一个元素是否需要删除
System.out.println(one);
}
@Test
public void test4() {
//演示删除
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello", "world", "java", "atguigu");
System.out.println(one);
one.clear();;
System.out.println(one);
}
@Test
public void test5(){
//演示删除
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello", "world", "java", "atguigu");
System.out.println(one);
Collection<String> two = new ArrayList<>();
two.add("java");
two.add("atguigu");
two.add("world");
one.retainAll(two);//保留两者的交集,删除非交集
System.out.println(one);
System.out.println(two);//不变
}
}
4、查询
- boolean
contains(一个元素)
:是否包含这 1 个元素 - boolean containsAll(另一个集合 c):判断另一个集合 c 的元素是不是在当前集合中都能找到,即判断 c 是不是当前集合的子集
- boolean isEmpty():判断集合是否为空
- int
size()
:获取集合的元素个数
package com.atguigu.collection;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
public class TestCollection3 {
@Test
public void test1(){
//演示查询
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello","world","java","atguigu");
System.out.println(one);
System.out.println(one.contains("hello"));//true
Collection<String> two = new ArrayList<>();
two.add("java");
two.add("atguigu");
two.add("world");
System.out.println(one.containsAll(two));//true 说明 two是one的子集
System.out.println(two);
}
@Test
public void test2(){
//演示查询
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello","world","java","atguigu");
System.out.println(one.size());//元素个数
System.out.println(one.isEmpty());//false 判断集合是否为空集合
}
}
5、遍历
- 增强 for 循环(foreach 循环,快捷键 iter)
forEach(Consumer 接口的实现类对象)
:我们需要编写一个类(有名或匿名)实现 Consumer 接口,重写 voidaccept(T t)
方法,告诉 forEach 对每一个元素要做 xx 事情。- 使用 Iterator 迭代器来遍历元素
增强 for 循环是一种语法糖。
语法糖(Syntactic Sugar)是指编程语言中为方便开发者书写而提供的一些特殊语法结构,这些结构在编译或解释时会被转换为更基础的语法形式,本质上并不会增加语言的功能,但能提升代码的可读性和开发效率。
在 Java 中增强 for 循环会被编译器编译为:
(1)数组:编译为普通 for 循环
(2)集合:编译为迭代器(Iterator)的形式
package com.atguigu.collection;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.function.Consumer;
public class TestCollection4 {
@Test
public void test(){
String[] strings = {"hello","world","java","atguigu"};
for (String s : strings) {//增强for循环遍历数组
System.out.println(s);
}
}
@Test
public void test1(){
//演示遍历
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello","world","java","atguigu");
System.out.println(one);
//查看集合中每一个字符串的长度
for (String s : one) {
System.out.println(s +"的长度:" + s.length());
}
}
@Test
public void test2(){
//演示遍历
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello","world","java","atguigu");
System.out.println(one);
/* for(int i=0; i<one.size(); i++){
System.out.println(one[i]);//错误
}*/
}
@Test
public void test3(){
//演示遍历
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello","world","java","atguigu");
System.out.println(one);
//提供了一个方法forEach
/*
Collection的 forEach方法的形参是 Consumer<T>类型。
Consumer也是一个函数式接口,只有一个抽象必须重写 void accept(T t)
重写 accept方法的目的是告诉forEach方法对每一个集合元素做xx操作。
*/
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s +"的长度:" + s.length());
}
};
one.forEach(c);//在forEach方法内部,会调用c的accept方法,对每一个元素做xx事
}
@Test
public void test4(){
//迭代器遍历。
//什么是迭代器?
//比喻:如果把Collection集合比喻成一个航班,那么迭代器相当于是空姐
//空姐只在这个航班旅途当中可以访问这个顾客
Collection<String> one = new ArrayList<>();
Collections.addAll(one, "hello","world","java","atguigu");
Iterator<String> iterator = one.iterator();//获取迭代器对象。不能自己new。
while(iterator.hasNext()){//判断当前迭代器位置是否有元素
String s = iterator.next();//获取当前位置的元素
System.out.println(s +"的长度:" + s.length());
}
}
}
2.2.2 Set 集合
Set 系列的集合是 Collection 系列集合中的一部分。Set 系列的集合都实现 Set 接口。Set 接口是 Collection 的子接口。
这个系列的集合有一个共同特征:它们的元素不可重复
。它是对数学上集的抽象。
Set 接口并未增加新的方法,仍然使用 Collection 接口的方法。
Set 接口的常见实现类有如下几个:
- HashSet:元素的存储没有规律。
- LinkedHashSet:元素可以体现添加的顺序。因为 LinkedHashSet 比较 HashSet 多一个了 Linked,它是一个链表,这个链表会记录元素的添加顺序。LinkedHashSet 是 HashSet 的子类,子类被父类更丰富。
- HashSet 和 LinkedHashSet 集合的元素去重原则,是看两个对象的 hashCode 值和 equals 方法比较的结果
- TreeSet:元素按照大小顺序排列。
- 凡是添加到 TreeSet 中的元素的类型必须实现 Comparable 接口。
- TreeSet 集合的元素去重原则,是看两个对象的大小是否一样。如果大小一样,就认为是重复元素。
- 如果 Comparable 接口的 compareTo 方法不符合我们的要求,需要考虑使用 Comparator 接口。
如何选择 HashSet 还是 LinkedHashSet?
使用他们的前提条件,都要求元素不可重复。
如果你的业务对元素的存储和遍历没有要求按照添加顺序,建议使用 HashSet 更简洁,效率更高。
如果你执着于添加的顺序,那么只能选择 LinkedHashSet。
什么时候才需要用 TreeSet?
只有我们对元素的大小顺序有要求时,才用它,否则不用。
package com.atguigu.set;
import org.junit.jupiter.api.Test;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.TreeSet;
public class TestSet {
@Test
public void test1(){
//HashSet是Set的实现类,Set是Collection的子接口
HashSet<String> set = new HashSet<>();
set.add("hello");
set.add("hello");
set.add("world");
set.add("world");
set.add("java");
System.out.println(set);
//[world, java, hello]
}
@Test
public void test2(){
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("hello");
set.add("hello");
set.add("world");
set.add("world");
set.add("java");
System.out.println(set);
//[hello, world, java]
}
@Test
public void test3(){
TreeSet<String> set = new TreeSet<>();
set.add("hello");
set.add("hello");
set.add("world");
set.add("world");
set.add("java");
set.add("chai");
set.add("atguigu");
System.out.println(set);
//[atguigu, chai, hello, java, world]
//按照字符串的字母的顺序
//String实现类Comparable接口,所以字符串对象可以通过compareTo方法比较大小
}
@Test
public void test4(){
TreeSet<Integer> set = new TreeSet<>();
set.add(1);
set.add(15);
set.add(6);
set.add(3);
set.add(29);
System.out.println(set);
//[1, 3, 6, 15, 29]
//Integer实现类Comparable接口,所以整数对象可以通过compareTo方法比较大小
}
@Test
public void test5(){
TreeSet<Student> set = new TreeSet<>();
set.add(new Student(1,"张三", 89,23));
set.add(new Student(2,"张三", 89,26));
set.add(new Student(3,"李四", 89,24));
set.add(new Student(4,"王五", 76,25));
//默认按照成绩排序
//遍历集合
for (Student s : set) {
System.out.println(s);
}
}
@Test
public void test6(){
HashSet<Student> set = new HashSet<>();
set.add(new Student(1,"张三", 89,23));
set.add(new Student(1,"张三", 89,23));
set.add(new Student(2,"张三", 89,26));
set.add(new Student(3,"李四", 89,24));
set.add(new Student(4,"王五", 76,25));
//遍历集合
for (Student s : set) {
System.out.println(s);
}
}
@Test
public void test7(){
//按照年龄排序
//当Comparable的compareTo的规则不符合我们的排序要求,就要考虑Comparator接口
Comparator c = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
int result = Integer.compare(s1.getAge(), s2.getAge());
return result !=0 ?result : Integer.compare(s1.getId(),s2.getId());
}
};
TreeSet<Student> set = new TreeSet<>(c);//告诉TreeSet用Comparator比较元素大小
set.add(new Student(1,"张三", 89,23));
set.add(new Student(2,"张三", 89,26));
set.add(new Student(3,"李四", 89,24));
set.add(new Student(4,"王五", 76,23));
//遍历集合
for (Student s : set) {
System.out.println(s);
}
}
@Test
public void test8(){
//按照年龄排序 降序
//当Comparable的compareTo的规则不符合我们的排序要求,就要考虑Comparator接口
Comparator c = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
int result = Integer.compare(s2.getAge(), s1.getAge());
return result !=0 ?result : Integer.compare(s1.getId(),s2.getId());
}
};
TreeSet<Student> set = new TreeSet<>(c);//告诉TreeSet用Comparator比较元素大小
set.add(new Student(1,"张三", 89,23));
set.add(new Student(2,"张三", 89,26));
set.add(new Student(3,"李四", 89,24));
set.add(new Student(4,"王五", 76,23));
//遍历集合
for (Student s : set) {
System.out.println(s);
}
}
}
三、泛型
<T>
、<String>
等都是泛型。
3.1 为什么要用泛型
之前没有泛型时,以集合为例:
JDK 核心类库中设计集合时,是无法确定集合的元素是什么类型的?
问题:
安全问题,把不符合类型要求的元素添加到集合中
麻烦,需要向下转型
反过来说,有泛型的好处:
- 可以避免类型不符合的安全问题
- 更简便,避免了类型转换
package com.atguigu.generate;
public class MyArrayList {//不使用泛型
private Object[] arr = new Object[3];
private int total= 0;
public void add(Object element){//用Object表示
arr[total++] = element;
}
public Object get(int i){//用Object表示
return arr[i];
}
}
package com.atguigu.generate;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.util.ArrayList;
public class TestMyArrayList{
@Test
public void test1(){
//计划放到集合中的都是字符串
MyArrayList my = new MyArrayList();
my.add("hello");
my.add(1);//当我放的不是字符串时,编译器也没有提醒
my.add(LocalDate.now());
String object = (String) my.get(1);//向下转型,麻烦
System.out.println(object);
//上面没有泛型,就有安全问题,且必须类型转换
}
@Test
public void test2(){
//ArrayList<E>是一个泛型类
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
// list.add(1);
// list.add(LocalDate.now());
//对于非String类型的元素,不会被误操作添加到集合中,因为编译器有类型检查
String s = list.get(1);
System.out.println(s);
//有泛型,既可以保证安全,又可以避免类型转换
}
}
3.2 泛型类或泛型接口
3.2.1 泛型类与泛型接口的声明格式
语法格式:
【修饰符】 class 类名<泛型字母列表>{ //这种类就是泛型类,例如:ArrayList<E>等
}
【修饰符】 interface 接口名<泛型字母列表>{ //这种接口就是泛型接口,例如:Collection<E>、Set<E>、Comparable<T>.....等
}
3.2.2 如何使用泛型类与泛型接口
1、创建泛型类的对象(比较多)
类名<具体的类型> 对象名 = new 类名<>();
@Test
public void test1(){
//ArrayList<E>、HashSet<E>等都是泛型类
//1、创建泛型类的对象
ArrayList<String> list = new ArrayList<>();
HashSet<String> set = new HashSet<>();
}
2、继承泛型类(相对较少,源码能看懂即可)
情况一:子类不再是泛型类
【修饰符】 class 子类名 extends 泛型父类<具体的类型>{
}
情况二:子类继续是泛型类
【修饰符】 class 子类名<泛型字母> extends 泛型父类<与前面一样的泛型字母>{
}
package com.atguigu.generate;
import java.util.ArrayList;
//MyStringArrayList不属于泛型类,它是普通的类
//MyStringArrayList这个集合专门用来装String对象,不能用来装其他的对象
public class MyStringArrayList extends ArrayList<String> {
}
package com.atguigu.generate;
import java.util.ArrayList;
//子类仍然是泛型类。并且当我们使用这个泛型子类时,指定的<T>的具体类型,同样传给父类的<T>
public class MySubArrayList<T> extends ArrayList<T> {
}
@Test
public void test2(){
//MyStringArrayList 继承 ArrayList<E>,MyStringArrayList没有泛型
//那么创建MyStringArrayList对象,不用也不能指定泛型
MyStringArrayList list = new MyStringArrayList();
list.add("hello");
//MySubArrayList<E>继承ArrayList<E>,MySubArrayList<E>仍然有泛型
//那么创建MySubArrayList<E>对象,仍然要指定泛型
MySubArrayList<Integer> sub = new MySubArrayList<>();
sub.add(1);
}
3、实现泛型接口(比较多)
【修饰符】 class 类名 implements 泛型父接口<具体的类型>{
}
- Comparable
<T>
:intcompareTo(T t)
- Comparator
<T>
:intcompare(T t1, T t2)
- Predicate
<T>
:booleantest(T t)
- Consumer
<T>
:voidaccept(T t)
- ...
package com.atguigu.generate;
/*
Comparable<T>接口的<T>是代表要比较大小的对象的类型。
这里是两个Circle类型的对象要比较大小,<T>就写<Circle>
*/
public class Circle implements Comparable<Circle>{
private double radius;
@Override
public int compareTo(Circle o) {
return Double.compare(this.radius, o.radius);
//不需要向下转型了,形参的类型已经通过Comparable<Circle>自动推断是Circle类型
}
}
@Test
public void test3(){
//实现Comparator<T>泛型接口
Comparator<Student> c = new Comparator<>() {
@Override
public int compare(Student s1, Student s2) {
int result = Integer.compare(s2.getAge(), s1.getAge());
return result !=0 ?result : Integer.compare(s1.getId(),s2.getId());
}
};
TreeSet<Student> set = new TreeSet<>(c);//告诉TreeSet用Comparator比较元素大小
set.add(new Student(1,"张三", 89,23));
set.add(new Student(2,"张三", 89,26));
set.add(new Student(3,"李四", 89,24));
set.add(new Student(4,"王五", 76,23));
//遍历集合
for (Student s : set) {
System.out.println(s);
}
}
3.2.3 <具体类型>
怎么填
1、位置
- 使用泛型类创建对象时,在类名后面
<具体类型>
- 实现泛型接口或继承泛型类时,在类名或接口名后面填写
<具体类型>
2、类型的要求
- 只能填写引用数据类型
- 不能填写基本数据类型,只能用它们的包装类
@Test
public void test4(){
// ArrayList<int> list = new ArrayList<>();
// ArrayList<double> list = new ArrayList<>();
//<>里面不能写基本数据类型
}
3、个数要求
<泛型字母列表>
有几个泛型字母,就要指定几个<具体类型>
public class XueSheng<T,A> {
//...
}
@Test
public void test5(){
//<String, Integer> 类型实参
XueSheng<String, Integer> x = new XueSheng<>();
String score = x.getScore();
Integer age = x.getAge();
}
3.2.4 自定义泛型类或泛型接口
【修饰符】 class 类名<泛型字母列表>{ //这种类就是泛型类,例如:ArrayList<E>等
}
<泛型字母>
有什么要求:
建议用单个大写字母,不要写单词
尽量见名知意,
<T>
:代表 Type 类型<E>
:代表 Element Type,元素的类型<K,V>
:代表 map 集合的 key 的类型,value 的类型
如果是类名或接口名后面定义的
<泛型字母>
,不能用在静态成员(包括静态方法、静态方法、静态内部类等)上<泛型字母>
可以是多个,用逗号分隔。如果有多个,使用这个泛型类或泛型接口时,也得指定多个的具体类型
package com.atguigu.generate;
/*
需求:
现在要写一个学生类,这个学生类比较复杂。
它包含两个属性,一个属性是String类型的姓名name,一个属性是成绩score,但是此时成绩的类型不确定
语文老师:成绩要用字符串,优秀、良好、及格
数学老师:成绩精确到小数, 96, 80.5
英语老师:成绩用char,A,B,C,D
*/
public class XueSheng<T,A> {//<T,A> 类型形参,此时T的类型是不确定,只是一个占位符。 当我们new Student对象时,或子类继承Student类时才能确定<T>的具体类型
//正常的具体的类型不会是单个大写字母,通常都是单词。所以<T>比较好区分。<T>相当于是一个类型的占位符。
private String name;
private T score;
// private static T age;//错误,因为<T>通常是new对象时确定,或子类继承时确定,与静态的相违背,静态的不需要对象。
private A age;
public XueSheng() {
}
public XueSheng(String name, T score, A age) {
this.name = name;
this.score = score;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getScore() {
return score;
}
public void setScore(T score) {
this.score = score;
}
public A getAge() {
return age;
}
public void setAge(A age) {
this.age = age;
}
}
@Test
public void test5(){
//<String, Integer> 类型实参
XueSheng<String, Integer> x = new XueSheng<>();
String score = x.getScore();
Integer age = x.getAge();
}
3.3 泛型方法
3.3.1 泛型方法的声明和调用
- 当我们只想要单独给某个方法定义泛型,而不是给整个类,怎么办?
- 当我们需要在静态方法上使用
<泛型字母>
,怎么办?
这就需要使用泛型方法的语法。
【修饰符】 class 类名{
【修饰符】 <泛型字母列表> 返回值类型 方法名(【形参列表】)【throws 异常类型列表】{
方法体语句;
}
}
泛型方法的
<泛型>
具体代表什么类型,由调用方法时,传入的实参的类型自动推断。
例如:
- 请在 MyArrays 工具类中定义一个方法,可以实现在任意对象数组中,返回指定下标位置的元素。如果下标越界了,返回 null。
package com.atguigu.generate;
public class MyArrays {
/*
请在MyArrays工具类中定义一个方法,可以实现在任意对象数组中,
返回指定下标位置的元素。如果下标越界了,返回null。
*/
/* public static Object get(Object[] arr, int index){
if(index<0 || index>=arr.length){
return null;
}
return arr[index];
}*/
public static <T> T get(T[] arr, int index){
if(index<0 || index>=arr.length){
return null;
}
return arr[index];
}
}
package com.atguigu.generate;
import org.junit.jupiter.api.Test;
public class TestGenericMethod {
@Test
public void test1(){
String[] arr = {"hello","world","java"};
String o1 = MyArrays.get(arr, 1);//get方法会根据实参arr数组的类型,自动推断形参T[]数组的类型
System.out.println(o1);
String o2 = MyArrays.get(arr, 5);
System.out.println(o2);
}
@Test
public void test2(){
Integer[] arr = {10,20,30};
Integer o1 = MyArrays.get(arr, 1);//get方法会根据实参arr数组的类型,自动推断形参T[]数组的类型
System.out.println(o1);
Integer o2 = MyArrays.get(arr, 5);
System.out.println(o2);
}
@Test
public void test3(){
Collection<String> coll = new ArrayList<>();
coll.add("hello");
coll.add("world");
/*
Collection接口中有一个 <T> T[] toArray(T[] a); 它是一个泛型方法
*/
String[] array = new String[0];//长度为0
//toArray方法中,可以array参数的类型,来确定它内部需要创建的数组的类型
String[] result = coll.toArray(array); //自动确定T是String,因为array的类型是String[]
for (String s : result) {
System.out.println(s);
}
}
@Test
public void test4(){
Collection<Integer> coll = new ArrayList<>();
/*
Collections工具类有一个泛型方法
public static <T> boolean addAll(Collection<? super T> c, T... elements)
*/
Collections.addAll(coll, 1,2,3);//自动确定T是Integer,因为1,2,3是Integer
}
@Test
public void test5(){
/*
Arrays工具类有一个泛型方法:
public static <T> List<T> asList(T... a)
*/
List<String> list1 = Arrays.asList("hello", "java", "world");//自动确定T是String,因为 "hello"等是字符串
List<Integer> list2 = Arrays.asList(1, 2, 3, 4);//自动确定T是Integer,因为1,2,3,4是Integer
}
}
3.3.2 对比
泛型类与泛型方法:
- 在类名后面定义的
<泛型字母>
不可以使用到静态成员方法。而且同一个类中<T>
代表同一个类型。 - 单独的一个泛型方法,可以是静态的,也可以是非静态的。每一个泛型方法的
<T>
都是独立的,而且每一次调用都是独立的。
package com.atguigu.generate;
public class Demo<T> {
public void m1(T t){
System.out.println("Demo.m1的t类型:" + t.getClass());
}
public void m2(T t){
System.out.println("Demo.m2的t类型:" + t.getClass());
}
/* public static void m3(T t){
System.out.println("Demo.m3的t类型:" + t.getClass());
}*///在类名后面定义的<泛型字母>不可以使用到静态成员方法
}
package com.atguigu.generate;
public class Example {
public <T> void m1(T t){
System.out.println("Example.m1的t类型:" + t.getClass());
}
public <T> void m2(T t){
System.out.println("Example.m2的t类型:" + t.getClass());
}
//单独的一个泛型方法,可以是静态的,也可以是非静态的
public static <T> void m3(T t){
System.out.println("Example.m3的t类型:" + t.getClass());
}
}
package com.atguigu.generate;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
public class TestDemoAndExample {
@Test
public void test1(){
Demo<String> d1 = new Demo<>();
d1.m1("hello");
d1.m2("java");
// d1.m2(2);//错误
//对于d1对象来说,所有方法的<T>都是String类型
}
@Test
public void test2(){
Example e1 = new Example();
e1.m1("hello");
e1.m1(LocalDate.now());
e1.m2("java");
e1.m2(1);
//e1对象的每一个方法,每一次调用<T>都是独立
}
}
3.4 <泛型>
上限与泛型擦除
当我们新定义一个<泛型字母>
时,是可以给它指定上限的。
语法格式:
<泛型字母 extends 上限>
<泛型字母 extends 上限1 & 上限2 & 上限3>
package com.atguigu.generate;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/*
坐标类有两个属性,x,y,
现在x,y的类型可能是Integer,Double,BigInteger,BigDecimal...,它们都要求是Number数字类型。
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
//<T extends Number & Comparable >表示为<T>指定的具体类型必须同时继承Number和实现Comparable接口,不是所有类型都可以的。
//一旦没有为T指定具体类型,就发生泛型擦除,此时就按照第一个上限处理。如果T没有上限,默认上限就是Object,泛型擦除就按照Object处理。
//上限可以是1个或多个,如果是多个的话,父类最多只能有1个,其余的是父接口,且先写父类,再写接口
public class Coordinate<T extends Number & Comparable > {//坐标类
private T x;
private T y;
}
package com.atguigu.generate;
import org.junit.jupiter.api.Test;
public class TestCoordinate {
@Test
public void test1(){
Coordinate<Integer> c1 = new Coordinate<>(52,63);
Integer x1 = c1.getX();
Coordinate<Double> c2 = new Coordinate<>(52.0,63.0);
Double x2 = c2.getX();
Coordinate c3 =new Coordinate(52,63);//泛型擦除
Number x3 = c3.getX();
}
}
3.5 <泛型>
通配符<?>
当我们使用一个已经定义好的泛型类或泛型接口声明变量时,本类应该为<泛型字母>
指定具体的类型,例如:ArrayList<String>
,但是有时候用这些泛型类或泛型接口声明形参时,还不能确定<>里面具体的类型,那么此时可能就需要用到通配符<?>
。
通配符<?>
的形式有 3 种:
泛型类
<?>
:此时<?>
代表的是任意类型泛型类
<? extends 上限>
:此时<?>
代表的是上限或上限的子类,<=上限- 此时的上限只能写 1 个,不能多个
泛型类
<? super 下限>
:此时<?>
代表的是下限或下限的父类,>=下限
package com.atguigu.generate;
import java.util.Collection;
public class MyCollections {
//定义一个方法,可以遍历元素是任意类型Collection系列的集合
public static void print(Collection<Object> coll){
for (Object obj : coll) {
System.out.println(obj);
}
}
public static void show(Collection<?> coll){
for (Object obj : coll) {
System.out.println(obj);
}
}
public static <T> void method(Collection<T> coll){
for (T obj : coll) {
System.out.println(obj);
}
}
public static void show2(Collection<? extends Number> coll){
for (Number obj : coll) {
System.out.println(obj);
}
}
public static void show3(Collection<? super Number> coll){
for (Object obj : coll) {
System.out.println(obj);
}
}
}
package com.atguigu.generate;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
public class TestMyCollections {
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
/*
形参是 Collection<Object> coll
实参是 list
实参是给形参赋值。
等价于 Collection<Object> coll = list;
list的类型 ArrayList<String>
Collection<Object> coll = new ArrayList<String>();
赋值是,左右两边<>中的类型必须是一致的。
*/
// MyCollections.print(list);
MyCollections.show(list);
MyCollections.method(list);
}
@Test
public void test2(){
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
ArrayList<Double> list3 = new ArrayList<>();
ArrayList<Number> list4 = new ArrayList<>();
ArrayList<Object> list5 = new ArrayList<>();
// MyCollections.show2(list1);//错误 <String>不符合 <? extends Number>
MyCollections.show2(list2);//<Integer> 符合 <? extends Number>
MyCollections.show2(list3);//<Double> 符合 <? extends Number>
MyCollections.show2(list4);//<Number> 符合 <? extends Number>
// MyCollections.show2(list5);//错误 <String>不符合 <? extends Number>
}
@Test
public void test3(){
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
ArrayList<Double> list3 = new ArrayList<>();
ArrayList<Number> list4 = new ArrayList<>();
ArrayList<Object> list5 = new ArrayList<>();
// MyCollections.show3(list1);//错误 <String>不符合 <? super Number>
// MyCollections.show3(list2);//错误<Integer> 不符合 <? super Number>
// MyCollections.show3(list3);//错误<Double> 不符合 <? super Number>
MyCollections.show3(list4);//<Number> 符合 <? super Number>
MyCollections.show3(list5);//Object 符合 <? super Number>
}
}
package com.atguigu.generate;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Collection;
public class TestDifferent {
@Test
public void test1(){
Collection<String> c1 = new ArrayList<>();
Collection<Integer> c2 = new ArrayList<>();
show(c1,c2);
// method(c1,c2);//错误
}
public void show(Collection<?> one, Collection<?> two){//两个<?>完全无关,?不能单独用来当类型使用
//...
}
public <T> void method(Collection<T> one, Collection<T> two, T t){//同一个方法类似两个<T>类型一样的,T可以单独当类型使用
///...
}
}
四、集合(续)
4.1 List 集合
4.1.1 List 和 Set 区别
Collection 是 Set 和 List 的父接口。List 也是 Collection 系列的集合。
- Set:
- 元素是不可重复的
- 所有 Set 集合是无法通过索引或下标进行操作,称为无序。
- List:
- 元素是可重复的
- 所有 List 集合(无论底层是数组还是链表还是其他)统统都可以按照下标或索引操作元素,称为有序的。
- List 的方法分为 2 大类:
- 一类是从 Collection 接口继承的,它们与下标无关。
- 一类是 List 接口自己扩展的,它们与下标有关
关键词:
Set:无序,不可重复
List:有序,可重复
4.1.2 List 新增的方法
1、增
add(下标,元素)
- addAll(下标,另一个集合)
package com.atguigu.list;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
public class TestList {
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add(0,"java");
list.add(1,"atguigu");
System.out.println(list);//java,atguigu,hello
}
@Test
public void test2(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
ArrayList<String> two = new ArrayList<>();
two.add("张三");
two.add("李四");
list.addAll(1,two);
System.out.println(list);
//[hello, 张三, 李四, world]
}
}
2、删
- remove(下标)
package com.atguigu.list;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
public class TestList2 {
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.remove(1);
System.out.println(list);
//[hello, java, atguigu]
}
@Test
public void test2(){
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
// list.remove(Integer.valueOf(20));
// list.remove((Integer) 20);
list.remove(20);
System.out.println(list);
}
}
3、改
set(下标,新元素)
replaceAll(UnaryOperator 接口的实现类对象)
:必须编写一个有名或匿名的类实现 UnaryOperator 接口,重写 apply 方法,告诉 replaceAll 方法如何修改元素。sort(Comparator 接口的实现类对象)
:如果传入 null,代表用元素的默认排序,否则用定制排序
package com.atguigu.list;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.UnaryOperator;
public class TestList3 {
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.set(1,"世界");
System.out.println(list);
//[hello, 世界, java, atguigu]
}
@Test
public void test2(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
/*
ArrayList的replaceAll方法的形参是 UnaryOperator<T>
UnaryOperator<T> 也是一个函数式接口,也有一个抽象方法必须重写。抽象方法是 T apply(T t);
T此时就是集合的元素的类型。
当我们重写apply方法时,就是告诉replaceAll方法,将元素修改为xx新元素。
apply方法的参赛代表旧元素,返回值代表新元素
例如:把上面所有元素的整个单词改为大写
*/
UnaryOperator<String> u = new UnaryOperator<String>() {
@Override
public String apply(String s) {//s是旧元素,返回值是新元素
return s.toUpperCase();
}
};
list.replaceAll(u);
System.out.println(list);
}
@Test
public void test3(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
//把上述单词首字母改为大写
UnaryOperator<String> u = new UnaryOperator<String>() {
@Override
public String apply(String s) {//s是旧元素,返回值是新元素
return s.substring(0,1).toUpperCase().concat(s.substring(1));
}
};
list.replaceAll(u);
System.out.println(list);
}
@Test
public void test4(){
ArrayList<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(14);
list.add(5);
System.out.println(list);
// list.sort(null);//传入null,代表用元素的自然顺序比较大小,原生药实现Comparable接口
Comparator<Integer> c = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
};
list.sort(c);
System.out.println(list);
}
}
4、查
indexOf(元素)
lastIndexOf(元素)
get(下标)
:返回元素subList(start, end)
:截取[start, end)部分的元素
package com.atguigu.list;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
public class TestList4 {
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.add("java");
System.out.println(list.indexOf("java"));//2
System.out.println(list.lastIndexOf("java"));//4
String str = list.get(1);
System.out.println(str);//world
List<String> sub = list.subList(1, 4);
System.out.println(sub);
//[world, java, atguigu]
}
}
5、遍历
- 普通 for 循环
- 专门的 List 迭代器:ListIterator
ListIterator:只为 List 系列的集合服务
ListIterator 功能强大:
(1)同时支持从前往后,从后往前遍历
(2)遍历的过程中,可以获取下标信息
(3)遍历查看的同时支持增、删、改(注意调用迭代器的 add、remove、set 方法,不要调用集合的 add 等方法)
package com.atguigu.list;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.ListIterator;
public class TestList5 {
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.add("java");
//普通for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
@Test
public void test2(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.add("java");
//获取列表迭代器对象
System.out.println("从前往后遍历:");
ListIterator<String> listIterator = list.listIterator();//()默认迭代器在集合的开头
while (listIterator.hasNext()){
String s = listIterator.next();//下一个
System.out.println(s);
}
System.out.println("从后往前遍历:");
while (listIterator.hasPrevious()){
String s = listIterator.previous();//前一个
System.out.println(s);
}
}
@Test
public void test3(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.add("java");
System.out.println("从后往前遍历:");
ListIterator<String> listIterator = list.listIterator(list.size());//一开始迭代器就走到集合的末尾
while (listIterator.hasPrevious()){
String s = listIterator.previous();//前一个
System.out.println(s);
}
}
@Test
public void test4(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.add("java");
//获取列表迭代器对象
System.out.println("从前往后遍历:");
ListIterator<String> listIterator = list.listIterator();//()默认迭代器在集合的开头
while (listIterator.hasNext()){
int before = listIterator.nextIndex(); //获取迭代器当前位置的下标
String s = listIterator.next();//下一个 next()取出当前位置的元素,并且让迭代器走向下一个元素
int after = listIterator.nextIndex();//迭代器走到下一个元素的下标
System.out.println(before + "=" + s +"= " + after);
}
}
@Test
public void test5(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.add("java");
System.out.println("从后往前遍历:");
ListIterator<String> listIterator = list.listIterator(list.size());//一开始迭代器就走到集合的末尾
while (listIterator.hasPrevious()){
//如何查看接口实现类重写的方法,快捷键Ctrl + Alt + B,选择实现类
int before = listIterator.previousIndex();
String s = listIterator.previous();//前一个
int after = listIterator.previousIndex();
System.out.println(before + "=" + s +"= " + after);
}
}
@Test
public void test6(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.add("java");
//在所有java元素的前面加一个ai元素
ListIterator<String> listIterator = list.listIterator(list.size());//一开始迭代器就走到集合的末尾
while (listIterator.hasPrevious()){
String s = listIterator.previous();
if("java".equals(s)){
listIterator.add("ai");//必须调用 迭代器的add方法
}
}
System.out.println(list);
//[hello, world, ai, java, atguigu, ai, java]
}
@Test
public void test7(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.add("java");
//删除所有包含a字母的单词
ListIterator<String> listIterator = list.listIterator(list.size());//一开始迭代器就走到集合的末尾
while (listIterator.hasPrevious()){
String s = listIterator.previous();
if(s.contains("a")){
listIterator.remove();//用迭代器的remove
}
}
System.out.println(list);//[hello, world]
}
@Test
public void test8(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.add("java");
//把所有单词首字母改为大写
ListIterator<String> listIterator = list.listIterator(list.size());//一开始迭代器就走到集合的末尾
while (listIterator.hasPrevious()){
String s = listIterator.previous();
s = s.substring(0,1).toUpperCase().concat(s.substring(1));
listIterator.set(s);//覆盖原来的
}
System.out.println(list);
//[Hello, World, Java, Atguigu, Java]
}
}