Skip to content

一、复习

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,它不是用来表示字符串,而是用来实现字符串的拼接

java
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(集合, 一组元素);

java
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 接口,重写 boolean test(T t)方法,告诉 removeIf 删除元素的条件是什么。
  • clear():清空集合
  • retainAll(另一个集合 c):从当前集合中删除两个集合的非交集部分,保留两者的交集部分。this = this ∩ c。
java
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():获取集合的元素个数
java
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 接口,重写 void accept(T t)方法,告诉 forEach 对每一个元素要做 xx 事情。
  • 使用 Iterator 迭代器来遍历元素

增强 for 循环是一种语法糖。

语法糖(Syntactic Sugar)是指编程语言中为方便开发者书写而提供的一些特殊语法结构,这些结构在编译或解释时会被转换为更基础的语法形式,本质上并不会增加语言的功能,但能提升代码的可读性和开发效率。

在 Java 中增强 for 循环会被编译器编译为:

(1)数组:编译为普通 for 循环

(2)集合:编译为迭代器(Iterator)的形式

image-20250716102409557

java
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?

只有我们对元素的大小顺序有要求时,才用它,否则不用。

java
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 核心类库中设计集合时,是无法确定集合的元素是什么类型的?

  • 问题:

    • 安全问题,把不符合类型要求的元素添加到集合中

    • 麻烦,需要向下转型

反过来说,有泛型的好处:

  • 可以避免类型不符合的安全问题
  • 更简便,避免了类型转换
java
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];
    }
}
java
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 泛型类与泛型接口的声明格式

语法格式:

java
【修饰符】 class 类名<泛型字母列表>{ //这种类就是泛型类,例如:ArrayList<E>等

}
java
【修饰符】 interface 接口名<泛型字母列表>{ //这种接口就是泛型接口,例如:Collection<E>、Set<E>、Comparable<T>.....等

}

3.2.2 如何使用泛型类与泛型接口

1、创建泛型类的对象(比较多)

java
类名<具体的类型> 对象名 = new 类名<>();
java
    @Test
    public void test1(){
        //ArrayList<E>、HashSet<E>等都是泛型类
        //1、创建泛型类的对象
        ArrayList<String> list = new ArrayList<>();
        HashSet<String> set = new HashSet<>();
    }

2、继承泛型类(相对较少,源码能看懂即可)

情况一:子类不再是泛型类

java
【修饰符】 class 子类名  extends 泛型父类<具体的类型>{

}

情况二:子类继续是泛型类

java
【修饰符】 class 子类名<泛型字母>  extends 泛型父类<与前面一样的泛型字母>{

}
java
package com.atguigu.generate;

import java.util.ArrayList;

//MyStringArrayList不属于泛型类,它是普通的类
//MyStringArrayList这个集合专门用来装String对象,不能用来装其他的对象
public class MyStringArrayList extends ArrayList<String> {
}
java
package com.atguigu.generate;

import java.util.ArrayList;

//子类仍然是泛型类。并且当我们使用这个泛型子类时,指定的<T>的具体类型,同样传给父类的<T>
public class MySubArrayList<T> extends ArrayList<T> {
}
java
    @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、实现泛型接口(比较多)

java
【修饰符】 class 类名  implements 泛型父接口<具体的类型>{

}
  • Comparable<T>:int compareTo(T t)
  • Comparator<T>:int compare(T t1, T t2)
  • Predicate<T>:boolean test(T t)
  • Consumer<T>:void accept(T t)
  • ...
java
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类型
    }
}
java
	@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、类型的要求

  • 只能填写引用数据类型
  • 不能填写基本数据类型,只能用它们的包装类
java
    @Test
    public void test4(){
//        ArrayList<int> list = new ArrayList<>();
//        ArrayList<double> list = new ArrayList<>();
        //<>里面不能写基本数据类型
    }

3、个数要求

  • <泛型字母列表> 有几个泛型字母,就要指定几个<具体类型>
java
public class XueSheng<T,A> {
      //...
}
java
   @Test
    public void test5(){
        //<String, Integer> 类型实参
        XueSheng<String, Integer> x = new XueSheng<>();
        String score = x.getScore();
        Integer age = x.getAge();

    }

3.2.4 自定义泛型类或泛型接口

java
【修饰符】 class 类名<泛型字母列表>{ //这种类就是泛型类,例如:ArrayList<E>等

}

<泛型字母>有什么要求:

  • 建议用单个大写字母,不要写单词

  • 尽量见名知意,

    • <T>:代表 Type 类型
    • <E>:代表 Element Type,元素的类型
    • <K,V>:代表 map 集合的 key 的类型,value 的类型
  • 如果是类名或接口名后面定义的<泛型字母>,不能用在静态成员(包括静态方法、静态方法、静态内部类等)上

  • <泛型字母>可以是多个,用逗号分隔。如果有多个,使用这个泛型类或泛型接口时,也得指定多个的具体类型

java
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;
    }
}
java
   @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 泛型方法的声明和调用

  • 当我们只想要单独给某个方法定义泛型,而不是给整个类,怎么办?
  • 当我们需要在静态方法上使用<泛型字母>,怎么办?

这就需要使用泛型方法的语法。

java
【修饰符】 class 类名{
    【修饰符】 <泛型字母列表> 返回值类型 方法名(【形参列表】)【throws 异常类型列表】{
        方法体语句;
    }
}

泛型方法的<泛型>具体代表什么类型,由调用方法时,传入的实参的类型自动推断。

例如:

  • 请在 MyArrays 工具类中定义一个方法,可以实现在任意对象数组中,返回指定下标位置的元素。如果下标越界了,返回 null。
java
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];
    }
}
java
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>都是独立的,而且每一次调用都是独立的。
java
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());
    }*///在类名后面定义的<泛型字母>不可以使用到静态成员方法
}
java
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());
    }
}
java
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 <泛型>上限与泛型擦除

当我们新定义一个<泛型字母>时,是可以给它指定上限的。

语法格式:

java
<泛型字母 extends 上限>
java
<泛型字母 extends 上限1 & 上限2 & 上限3>
java
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;
}
java
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 下限>:此时<?>代表的是下限或下限的父类,>=下限

java
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);
        }
    }

}
java
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>
    }
}
java
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(下标,另一个集合)
java
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(下标)
java
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,代表用元素的默认排序,否则用定制排序
java
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)部分的元素
java
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 等方法)

java
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]
    }
}