集合框架背后的 Java 知识

集合框架背后的 Java 知识

泛型

1.1什么是泛型

[!NOTE]
⼀般的类和⽅法,只能使⽤具体的类型: 要么是基本类型,要么是⾃定义的类。如果要编写可以应⽤于多种类型的代码,这种刻板的限制对代码的束缚就会很⼤。

泛型是在JDK1.5引⼊的新的语法,通俗讲,泛型:就是适⽤于许多许多类型。从代码上讲,就是对类型实现了参数化。

1.2泛型引入

[!NOTE]
需求:实现⼀个类,类中包含⼀个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员⽅法返回数组中某个下标的值?

[!TIP]
思路

我们以前学过的数组,只能存放指定类型的元素,例如:int[] array = new int[10]; String[] strs = new String[10]; 所有类的⽗类,

默认为Object类。数组是否可以创建为Object?

[!NOTE]
代码实现

class MyArray {
    public Object[] array = new Object[10];

    public Object getPos(int pos) {
        return this.array[pos];
    }

    public void setVal(int pos,Object val) {
        this.array[pos] = val;
    }
}

public class TestDemo {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.setVal(0,10);
        myArray.setVal(1,"hello");//字符串也可以存放
        String ret = myArray.getPos(1);//编译报错
        System.out.println(ret);
    }
}

[!WARNING]
image

[!NOTE]
思考与总结:

  1. 任何类型数据都可以存放
  2. 1号下标本⾝就是字符串,但是却编译报错。必须进⾏强制类型转换

虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望他只能够持有⼀种数据类型。⽽不是同时持有这么多类型。

所以,泛型的主要⽬的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传⼊什么类型。

1.3泛型的定义

1.3.1泛型类

[!NOTE]
格式:

public class 类名<E> {

}

[!NOTE]
1.泛型<>里面填的是引用类型,如果想操作基本类型,需要写包装类;

如果<>​里面啥也不写,默认类型Object类型。

2.类名后面的<>表示占位符,表示当前的类是泛型类。

3.里面的 E 叫做类型形参,类型形参一般使用一个大写字母表示

常见的名称有:E 表示 Element;K 表示 Key;V 表示 Value;N 表示 Number;T 表示 Type

4.当 new 对象时,泛型的类型就确定了,这样代码可以更加灵活。

[!NOTE]
将上面的代码改造成泛型类:

class MyArray<T> {
    public Object[] array = new Object[10];

    public T getPos(int pos) {
        return (T)this.array[pos];
    }

    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
}

public class TestDemo {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>();
        myArray.setVal(0,10);
        int ret = myArray.getPos(1);
        System.out.println(ret);
        myArray.setVal(1,"bit");
    }
}

代码分析:

在第 15 行的时候我们将 myArray 定义为 Integer 类型了,故第 19 行添加一个 String 类型的字符串是会提示编译错误

image

就达成上面所说的泛型的主要⽬的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传⼊什么类型。

1.3.2 泛型方法

[!NOTE]
格式

修饰符 <E> 返回值类型 方法名(形参){
    方法体
    return 结果
}

[!NOTE]
1.方法自己独立声明泛型参数,不受类的泛型约束,静态方法只能用泛型方法定义泛型。

2.调用泛型的时候,其类型就确定了

[!TIP]

public class MyCollections {
    // 构造器私有化 -> 禁止外部通过new MyCollections()实例化这个类
    private MyCollections(){
    }

    // 泛型方法
    public static <E> void addAll(ArrayList<E> list, E... elements){
        for (E e : elements) {
            list.add(e);
        }
    }
}
public void test03() {
    // 创建一个名为 list 的 String 类型的集合
    ArrayList<String> list = new ArrayList<>();
    // 调用该泛型方法的时候,泛型类型就确定了
    MyCollections.addAll(list, "小猫", "小狗", "小猪", "小牛", "小羊");
    System.out.println(list);
} 
运行结果:
[小猫, 小狗, 小猪, 小牛, 小羊]

1.3.3 泛型接口

[!NOTE]
定义泛型接口Storage<T>。接口抽象方法:

  1. void save(T item)
  2. T get()

要求:

  1. 创建实现类StringStorage​,实现Storage<String>,使用成员变量存储单个字符串。
  2. 创建实现类ObjectStorage<T>​,实现Storage<T>,支持任意类型。
  3. main 方法测试:
  • StringStorage​存入"读书"​,调用get()打印;
  • ObjectStorage<Integer>​存入99​,调用get()打印。

[!TIP]

interface Storage<T> {
    void save(T item);
    T get();
}

class StringStorage implements Storage<String> {
    private String item;

    @Override
    public void save(String item) {
        this.item = item;
    }

    @Override
    public String get() {
        return item;
    }
}

class ObjectStorage<T> implements Storage<T> {
    private T item;

    @Override
    public void save(T item) {
        this.item = item;
    }

    @Override
    public T get() {
        return item;
    }
}

public class Test1 {
    public static void main(String[] args) {
        StringStorage s1 = new StringStorage();
        s1.save("读书");
        System.out.println(s1.get());

        ObjectStorage<Integer> o1 = new ObjectStorage<>();
        o1.save(99);
        System.out.println(o1.get());
    }
}

  ‍

1.4泛型通配符

  ? -> 一般情况出现在方法的参数位置

    public void test01() {
        ArrayList<String> list = new ArrayList<>();
        list.add("小猫");
        list.add("小狗");
        list.add("小猪");
        list.add("小牛");

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        list2.add(3);

        method(list);
        method(list2);
    }
    
    // 此处的?表示可以接收任意的数据类型
    public void method(ArrayList<?> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }

1.5泛型的上下限

1、上界(上限) ? extends 父类

? extends Number

  传过来的类型只能是 Number 类型或者是 Number 类型的子类

2、下界(下限)? super 子类

? super Integer

  传过来的类型只能是 Integer 类型或者是 Integer 类型的父类

/**
 * Integer -> Number -> Object
 * String -> Object
 */
public class Demo03FanXing {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();
        ArrayList<Number> list3 = new ArrayList<>();
        ArrayList<Object> list4 = new ArrayList<>();

        //get1(list1); 可以
        //get1(list2); 不行
        //get1(list3); 可以
        //get1(list4); 不行

        System.out.println("=================");

        //get2(list1); 不行
        //get2(list2); 不行
        //get2(list3); 可以
        //get2(list4); 可以
    }
    //上限  ?只能接收extends后面的本类类型以及子类类型
    public static void get1(Collection<? extends Number> collection){

    }

    //下限  ?只能接收super后面的本类类型以及父类类型
    public static void get2(Collection<? super Number> collection){

    }
}

[!TIP]
应用场景:

1.如果我们在定义类,方法,接口的时候,如果类型不确定,我们可以考虑定义含有泛型的类,方法,接口

2.如果类型不确定,但是能知道以后只能传递某个类的继承体系中的子类或者父类,就可以使用泛型的上限或者下限 -> 给泛型类型规定了范围

posted @ 2026-08-09 20:06  iooiBam  阅读(13)  评论(0)    收藏  举报