【Java基础】泛型

文章目录

泛型

一、概述

泛型(Generic)是一种机制,允许你编写与数据类型无关的代码,增加代码的灵活性和可重用性。

  • 泛型在定义的时候不具体,使用的时候才变得具体。在使用的时候确定泛型的具体数据类型。

泛型的作用:

  1. 安全性 :编译时检查类型,将运行时期的ClassCastException,转移到编译时期的编译失败。
  2. 灵活性 :使类型参数化,可以预先地使用未知的类型,让设计的代码更通用灵活。
  3. 重用性:一个泛型类或方法可以处理多种数据类型,减少代码重复。
  4. 维护性:泛型代码通常更清晰,容易理解和维护。

注意事项:

  • 泛型只能在编译阶段起作用 ,到了运行阶段就会被擦除
  • 泛型只能是引用数据类型,不能是 基本数据类型。
  • 泛型在使用时指定实际的类型,如果不指定默认为Object类型。

二、泛型的使用

1、类

定义:类名之后

java 复制代码
// 泛型一般用大写的单个字母表示,可以定义多个泛型,使用逗号分隔。
修饰符 class 类名<A, B, C> {}

使用:在类中使用,可以作为 实例方法 的 参数 和 返回值(静态方法不支持)

java 复制代码
class Example<T> {
    // 作为实例方法的参数类型
    public void show(T t) {
        System.out.println(t);
    }

    // 作为实例方法的返回值类型
    public T get(int index) {
        return null;
    }

    // 静态方法 不能使用 类上定义的泛型
    // public static void test1(T t) {...}
    // public static T test2(int index) {...}
}

指定类型:创建对象时

java 复制代码
public class Test {
    public static void main(String[] args) {
        // 在创建对象时,根据需要指定泛型的类型
        DataShow<String> ds = new DataShow<>();
        ds.show("Hello");  
    }
}

2、方法

定义:返回值之前

java 复制代码
修饰符 <T> 返回值类型 方法名(T t) {}

使用:在方法内部使用

java 复制代码
class Example {
    public static <T> void show(T t) {
        // 在方法内部使用
        System.out.println("t = " + t);
    }
    
    // 不同方法的泛型名称可以一致
    public static <T> void show2(T t) {}
}

指定类型:调用方法时,根据传参的类型

java 复制代码
public class Test {
    public static void main(String[] args) {
        show(100);		// T -> Integer
        show("Hello");	// T -> String
    }
}

3、接口

定义:接口名之后

java 复制代码
public interface 接口名<E> {}

使用:作为 接口方法 的 参数 或 返回值

java 复制代码
public interface Example<T> {
    void method1(T t);
    T method2();
}

指定类型:

1、定义接口的实现类时,确定泛型的类型

java 复制代码
public class ExampleChild1 implements Example<String>{
    @Override
    public void method1(String s) {
        System.out.println(s);
    }

    @Override
    public String method2() {
        return null;
    }
}

2、定义接口的实现类时,继续沿用泛型

java 复制代码
public class ExampleChild2<T> implements Example<T> {
    @Override
    public void method1(T t) {
        System.out.println(t);
    }

    @Override
    public T method2() {
        return null;
    }
}

在创建接口的实现类对象时,确定泛型的类型

java 复制代码
public class Test {
    public static void main(String[] args) {
        ExampleChild2<String> exampleChild2 = new ExampleChild2<>();
        exampleChild2.method1("hello");
        String str = exampleChild2.method2();
    }
}

三、泛型通配符

泛型通配符<?> ,常用于泛型方法和类中,帮助实现更加灵活和通用的类型操作。

1、<?>

<?>:表示任意类型,适用于我们不关心具体类型的场景。(只能使用Object类中的共性方法)

java 复制代码
public void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

使用举例

java 复制代码
public class Test {
    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        integerList.add(1);
        integerList.add(2);
        
        // 使用泛型方法
        printList(integerList);
    }

    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.println(obj);
        }
    }
}

2、<? extends T>

<? extends T>:表示 TT 的子类型,适用于读取操作。(可以使用父类T 中的公共方法)

java 复制代码
public void processNumbers(List<? extends Number> numbers) {
    for (Number number : numbers) {
        System.out.println(number.intValue());
    }
    Long a = 1L;	 // Long extends Number
    numbers.add(a);  // error,Java 的泛型系统为了类型安全,不允许向这样的列表中添加特定类型的元素(除 null 外)。
}
java 复制代码
public <T extends Number> void processNumbers(List<T> numbers) {
    for (Number number : numbers) {
        System.out.println(number.intValue());
    }
    Long a = 1L;	 // Long extends Number
    numbers.add(a);  // error
}

使用举例

java 复制代码
public class Test {
    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        integerList.add(1);
        integerList.add(2);

        // 使用泛型方法
        printNumbers(integerList);
    }

    public static <T extends Number> void printNumbers(List<T> list) {
        for (T number : list) {
            System.out.println(number);
        }
    }
}

3、<? super T>

<? super T>:表示 TT 的父类型,适用于写入操作。

java 复制代码
public void addNumbers(List<? super Integer> list) {
    list.add(1); // 可以安全地添加 Integer 类型的元素
    list.add(2); // 也可以添加其他 Integer 类型的元素
}
java 复制代码
// 编译错误! `super` 只能用在 泛型方法 或 类中的方法参数 中来指定一个类型范围。
public <T super Integer> void addNumbers(List<T> list) {...}

使用举例

java 复制代码
public class Test {
    public static void main(String[] args) {
        List<Number> numbers = new ArrayList<>();
        
        // 使用泛型方法
        addNumbers(numbers);
        System.out.println(numbers);
    }
    
    public static void addNumbers(List<? super Integer> list) {
        list.add(1);  // 可以添加 Integer 类型的对象
        list.add(2);  // 可以添加 Integer 类型的对象
    }
}

四、泛型的擦除

泛型的擦除是指,在编译期间,Java 编译器会将泛型信息擦除掉,泛型类型参数会被替换为其限定类型。

  • 默认情况下,泛型类型参数会被替换为 Object
  • 如果泛型有上限,如 T extends Number,擦除后会使用上限类型(Number)。

例如:

  • List<String>List<Integer> 在编译后都会被擦除为 List
  • List<T extends Number> 会擦除为 List<Number>

泛型擦除的主要目的是为了 兼容 Java 的早期版本 和 简化虚拟机的实现。

  • 泛型擦除将泛型类型转换为 Object 或其超类,从而使得 泛型代码 可以与 旧版本的 Java 代码 相互操作。
  • 泛型擦除意味着 泛型的具体类型(T)在字节码中不可见,保持向后兼容性的同时,避免对字节码格式进行重大修改。

但是,这也会导致一些泛型相关的信息在运行时不可用,需要在编写泛型代码时注意擦除造成的影响。

1、泛型的擦除

java 复制代码
public class GenericMethodExample {
    // 泛型方法  在擦除后会变成 `print(Object data)` 
    public <T> void print(T data) {
        System.out.println(data);
    }

    // 原始类型的方法 -> 编译错误!因为与擦除后的泛型方法冲突了
    public void print(Object data) {
        System.out.println(data);
    }
}
java 复制代码
public class GenericMethodExample {
    // 泛型方法  在擦除后会变成 `print(Object data)` 
    public <T> void print(T data) {
        System.out.println(data);
    }

    // 原始类型的方法 -> 编译通过!因为泛型默认擦除为 Object,这里是 String,重载
    public void print(String data) {
        System.out.println(data);
    }
}

2、泛型边界的擦除

java 复制代码
public void processNumbers(List<? extends Number> numbers) {
    for (Number number : numbers) {
        System.out.println(number.intValue());
    }
    Long a = 1L;	 // Long extends Number
    numbers.add(a);  // error
}
  • List<T extends Number> 在编译后会被擦除为 List<Number>
java 复制代码
public <T extends Number> void processNumbers(List<T> numbers) {
    for (Number number : numbers) {
        System.out.println(number.intValue());
    }
    Long a = 1L;	 // Long extends Number
    numbers.add(a);  // error
}
  • 泛型参数 T 在运行时被擦除为 Number 类型。

3、无法实例化泛型类型

java 复制代码
public class GenericInstantiationExample<T> {
    private T instance;

    public GenericInstantiationExample() {
        // 编译错误:无法直接实例化泛型类型
        // instance = new T(); // 错误
    }
    
    public GenericInstantiationExample(Class<T> clazz) {
        try {
            instance = clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • new T() 在编译时会引发错误,因为 T 的实际类型在运行时未知。
  • 可以使用反射(通过传递 Class<T> 对象)来实例化对象。
相关推荐
掘金-我是哪吒11 分钟前
分布式微服务系统架构第156集:JavaPlus技术文档平台日更-Java线程池使用指南
java·分布式·微服务·云原生·架构
亲爱的非洲野猪37 分钟前
Kafka消息积压的多维度解决方案:超越简单扩容的完整策略
java·分布式·中间件·kafka
wfsm40 分钟前
spring事件使用
java·后端·spring
微风粼粼1 小时前
程序员在线接单
java·jvm·后端·python·eclipse·tomcat·dubbo
缘来是庄1 小时前
设计模式之中介者模式
java·设计模式·中介者模式
rebel2 小时前
若依框架整合 CXF 实现 WebService 改造流程(后端)
java·后端
代码的余温3 小时前
5种高效解决Maven依赖冲突的方法
java·maven
慕y2743 小时前
Java学习第十六部分——JUnit框架
java·开发语言·学习
paishishaba3 小时前
Maven
java·maven
张人玉3 小时前
C# 常量与变量
java·算法·c#