Java学习小记--泛型

案例引入:

想象你开了一家快递站,但客户可能寄任何东西(衣服、零食、玻璃杯)

传统:什么都往盒子里面装。。。

但是有了泛型,也就是相当于给盒子贴类型标签,分类型去装,例如:

  • 快递盒<衣服> → 只能放衣服
  • 快递盒<玻璃杯> → 只能放玻璃杯

小案例:

// 泛型学习
package com.logindemo;

import java.sql.SQLOutput;

// 定义一个泛型类:盒子(可以装任何类型的东西)
class Box<T>{
    private T content;

    public void put(T item) {
        this.content = item;
    }

    public T get() {
        return content;
    }
}
public class GenericDemo1 {
    public static void main(String[] args) {
        // 创建一个装String的盒子
        Box<String> stringBox = new Box<String>();
        stringBox.put("Hello");
//        stringBox.put(123);    这行会编译报错,因为只能放String

        Box<Integer> integerBox = new Box<Integer>();
        integerBox.put(456);

        // 取出内容时不需要强制类型转换
        String string = stringBox.get();
        Integer integer = integerBox.get();

        System.out.println("字符串盒子: " + string);
        System.out.println("数字盒子: " + integer);
    }
}

运行结果:

泛型概念:

泛型,可以用于类,接口,集合的定义中,在实体化对象时将泛型的部分变为具体的数据类型:

//这里以集合为例
		//底层源码public class ArrayList<E>,E为泛型,在实体化对象时,Dog替代了E
		ArrayList<Dog> dogs = new ArrayList<Dog>();
        dogs.add(new Dog("tom"));
        dogs.add(new Dog("happt"));
        //加入别的类则会报错
        //dogs.add(new Cat("cat"));

(1)编译时就拦住错误类型,即往箱子里放猫 dogs.add(new Cat()) 时,就会立刻被红线警告,不用等运行

(2)

  • 不用泛型:从箱子里拿出东西时,要先判断是不是狗(Object -> Dog 强制转换)

  • 用泛型:直接确定是狗,拿出来就能撸狗(Dog dog = dogs.get(0)

泛型的好处:

  1. 适用于多种数据类型执行相同的代码
  2. 跳过了向下转型,不需要强制类型转换

泛型的细节:

1.  泛型具体化时只能是引用类型,而不能是基本数据类型。

        例如是Integer,而不能是int:

        //1.泛型中实现的只能是引用型数据类型
        ArrayList<Integer> integers = new ArrayList<>();
        //ArrayList<int> ints = new ArrayList<int>();

2.  若没有给泛型具体化,系统会默认其为Object类型。

3.  泛型具体化后,其输入的类型可以是本类型,也可以是本类型的子类。

4.  泛型不能用于修饰类的静态成员。

        因为泛型类在类的实体化时进行具体化,而静态成员则是在类加载时就已启动,此时泛型还不能确定具体类型。

5.  泛型数组不能初始化。

        因为在确定具体类型之前,每个元素的大小是不确定的,也就不能提前分配空间。

    自定义泛型类:

    在类名后加上<E,J,T,...>(不限个数),然后用于修饰类的方法/属性/返回值,在类实体化时赋予其相应的实际类型

    小案例:

    package com.logindemo;
    // 键值对
    
    import java.util.HashMap;
    import java.util.Map;
    
    // 定义一个泛型类,可以存储任意类型的键值对
    class GenericStorage<K, V> {  // K代表键的类型,V代表值的类型
        private final Map<K, V> storage = new HashMap<>();
    
        /**
         * 存储键值对
         *
         * @param key   键
         * @param value 值
         */
        public void put(K key, V value) {
            storage.put(key, value);
        }
    
        /**
         * 根据键获取值
         *
         * @param key 键
         * @return 对应的值
         */
        public V get(K key) {
            return storage.get(key);
        }
    
        /**
         * 显示所有存储的内容
         */
        public void displayAll() {
            System.out.println("存储内容:");
            for (Map.Entry<K, V> entry : storage.entrySet()) {
                System.out.println(entry.getKey() + " => " + entry.getValue());
            }
        }
    }
    public class GenericDemo2 {
        public static void main(String[] args) {
            // 创建一个存储<String, Integer>类型的GenericStorage实例
            GenericStorage<String, Integer> ageStorage = new GenericStorage<>();
    
            // 添加数据
            ageStorage.put("张三", 25);
            ageStorage.put("李四", 30);
    //        ageStorage.put(123, "测试");  // 这行会编译错误,因为类型不匹配
    
            // 获取数据
            System.out.println("李四的年龄是:" + ageStorage.get("李四"));
    
            // 显示所有数据
            ageStorage.displayAll();
    
            // 创建一个存储<Integer, String>类型的GenericStorage实例
            GenericStorage<Integer, String> productStorage = new GenericStorage<>();
    
            // 添加数据
            productStorage.put(1001, "手机");
            productStorage.put(1002, "笔记本电脑");
    
            // 获取数据
            System.out.println("产品1002是:" + productStorage.get(1002));
    
            // 显示所有数据
            productStorage.displayAll();
    
        }
    }
    

    运行结果:

     

    自定义泛型接口:

    泛型接口即是在其被其他接口继承/被类实现时。一旦实体化,其内部的方法,属性都会自动变为具有实体化属性的形式。方法参数/返回值类型自动同步替换。

            例如 : 泛型实体化后,这些方法里的 M/S 都会变成具体的 String/String

    小案例:

    MoneyBox.java-----定义一个泛型存钱罐接口
    package com.logindemo;
    
    /**
     * 泛型存钱罐接口
     * @param <M> 钱币类型(可以是人民币、美元等)
     * @param <S> 存钱罐形状(圆形、方形等)
     */
    interface MoneyBox<M, S> {
        // 抽象方法:存钱(必须由具体存钱罐实现)
        void save(M money);
    
        // 默认方法:展示存钱罐形状(可以直接用,也能被重写)
        default void showShape(S shape) {
            System.out.println("默认存钱罐形状:" + shape);
        }
    }
    

    RMBRoundBox.java-----实现具体存钱罐(泛型实体化)

    package com.logindemo;
    
    /**
     * 人民币圆形存钱罐(把泛型M和S具体化为String和String)
     */
    class RMBRoundBox implements MoneyBox<String, String> {  // 这里把 M 换成 String ; 把 S 换成 String
        @Override
        public void save(String money) {     // 抽象方法:必须实现,且参数类型会变成具体类型
            System.out.println("存入人民币:" + money);
        }
    
        // 可选择重写默认方法
        @Override
        public void showShape(String shape) {    //默认方法 :会自动变成具体类型
            System.out.println("[高级定制] 存钱罐形状:" + shape);
        }
    }
    
    
    
    DollarSquareBox.java
    package com.logindemo;
    
    /**
     * 美元方形存钱罐(把泛型M和S具体化为Integer和String)
     */
    class DollarSquareBox implements MoneyBox<Integer, String> {
        @Override
        public void save(Integer money) {
            System.out.println("存入美元:" + money + "刀");
        }
        // 不重写showShape(),直接使用接口的默认实现
    }
    
    Test.java------测试代码
    package com.logindemo;
    
    public class Test {
        public static void main(String[] args) {
            // 人民币圆形存钱罐
            RMBRoundBox rmbBox = new RMBRoundBox();
            rmbBox.save("100元");          // 输出:存入人民币:100元
            rmbBox.showShape("圆形");      // 输出:[高级定制] 存钱罐形状:圆形
    
            // 美元方形存钱罐
            DollarSquareBox dollarBox = new DollarSquareBox();
            dollarBox.save(50);            // 输出:存入美元:50刀
            dollarBox.showShape("方形");    // 输出:默认存钱罐形状:方形
        }
    }
    

    运行结果:

    泛型方法:

    1.普通类中的泛型方法:

    Printer.java
    // 定义一个万能打印机(普通类)
    class Printer {
        // 泛型方法:可以打印任何类型
        public <T> void print(T thing) {
            System.out.println("正在打印:" + thing);
        }
    }

    Test.java

    public class Test {
        public static void main(String[] args) {
            Printer printer = new Printer();
            
            // 不需要手动指定类型,编译器自动判断
            printer.print("字符串");  // 自动识别 T = String
            printer.print(100);     // 自动识别 T = Integer
            printer.print(3.14);    // 自动识别 T = Double
        }
    }

    运行结果:

    普通方法:只能打印String

    泛型方法:能打印任何类型

     2.泛型类中的泛型方法

    class C<M> { // 带M的类
        // 方法自带独立的<M,T>(和类的M无关!!!!)
        public <M, T> void f2(M m, T t) {
            System.out.println(m.getClass()); // 由调用时的参数决定
            System.out.println(t.getClass());
        }
    }
    
    // 使用:
    C<ArrayList> c = new C<>(); // 类的M=ArrayList
    c.f2(1, 1.1f);              // 方法的M=Integer, T=Float

    运行结果:

    class java.lang.Integer
    class java.lang.Float

    方法的<M>和类的<M>两个独立插槽,互不干扰 

    泛型的继承:

    具体说是其中的多态性质,泛型是不具有多态性质的,所以==两边的泛型实体化类型要相同,不存在什么父类子类

    //错误示例
    D<Object> objectD = new D<String>();

    通配符:

    (1)List<?>-------允许任何类型的实体化

    List<?> c = new ArrayList<任何类型>();
    通配符能赋值给它的集合(只能实体化的)能添加的元素能读取的类型
    <? extends AA>AA/BB/CC的集合不能添加任何元素视为AA
    <? super AA>AA/Object的集合AA及其子类(BB/CC)视为Object
    (2)List<? extends AA>-------只能取不能存
    //规则: AA是爷爷,BB是爸爸,CC是儿子
    class AA{}
    class BB extends AA{}
    class CC extends BB{}
    
    List<? extends AA> list1 = new ArrayList<AA>(); // ✅
    list1 = new ArrayList<BB>(); // ✅
    list1 = new ArrayList<CC>(); // ✅
    
    // 尝试添加元素:
    // list1.add(new AA()); // ❌ 编译错误!
    // list1.add(new BB()); // ❌ 编译错误!
    // list1.add(new CC()); // ❌ 编译错误!
    
    // 允许读取:
    AA a = list1.get(0); // ✅ 只能视为AA

    (3)List<? super AA>------能存AA及其子类,取只能Object

    List<? super AA> list2 = new ArrayList<AA>(); // ✅
    list2 = new ArrayList<Object>(); // ✅
    
    // 允许添加:
    list2.add(new AA()); // ✅
    list2.add(new BB()); // ✅ (因为BB是AA的子类)
    list2.add(new CC()); // ✅ (因为CC是AA的子类的子类)
    
    // 不允许添加:
    // list2.add(new Object()); // ❌ 编译错误!
    
    // 读取时:
    Object obj = list2.get(0); // ✅ 只能视为Object
    // AA a = list2.get(0);    // ❌ 编译错误!

    为什么这样设计?

    1. extends 安全性

      • 如果允许list1.add(new BB()),但list1可能是ArrayList<CC>,会导致类型污染。

    2. super 灵活性

      • 允许存入子类,因为子类一定能向上转型为AAObject

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值