Java语言04之泛型

本文详细介绍了Java泛型的概念、类型擦除原理,展示了泛型在接口、类、子类、方法中的使用,以及类型通配符的上下限。通过实例说明了泛型在实际编程中的应用,如创建集合和定制返回结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 泛型

1.1 泛型是什么

Java泛型是JDK 5中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型包括泛型类、泛型接口、泛型方法。

1.2 类型擦除

Java的泛型是伪泛型,这是因为Java在运行期间,所有的泛型信息都会被擦掉,这也就是通常所说的类型擦除

// 分别创建List<String>对象和List<Integer>对象
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
// 调用getClass()方法来比较l1和l2的类是否相等
System.out.println(l1.getClass() == l2.getClass());	// true

不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。

换句话说,Java并不存在泛型类!Java提供的泛型只是在编译期提高检查,以提高代码的安全性!

2 泛型使用方式

2.1 泛型接口

在定义接口、类时声明类型形参,类型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通类型的地方都可以使用这种类型形参。

// 定义接口时指定了一个类型形参,该形参名为E
public interface List<E> {
	// 在该接口里,E可作为类型使用
	// 下面方法可以使用E作为参数类型
	void add(E x);
	Iterator<E> iterator();
	...
}

// 定义接口时指定了一个类型形参,该形参名为E
public interface Iterator<E> {
	// 在该接口里E完全可以作为类型使用
	E next();
	boolean hasNext();
}

// 定义该接口时指定了两个类型形参,其形参名为K、V
public interface Map<K , V> {
	// 在该接口里K、V完全可以作为类型使用
	Set<K> keySet();
	V put(K key, V value) ;
	...
}

2.2 泛型类

// 定义Apple类时使用了泛型声明
public class Apple<T> {
    // 使用T类型形参定义实例变量
    private T info;

    public Apple() {
    }

    // 下面方法中使用T类型形参来定义构造器
    public Apple(T info) {
        this.info = info;
    }

    public void setInfo(T info) {
        this.info = info;
    }

    public T getInfo() {
        return this.info;
    }

    public static void main(String[] args) {
        // 因为传给T形参的是String实际类型,所以构造器的参数只能是String
        Apple<String> a1 = new Apple<>("苹果");
        System.out.println(a1.getInfo());
        // 因为传给T形参的是Double实际类型,所以构造器的参数只能是Double或者double
        Apple<Double> a2 = new Apple<>(5.67);
        System.out.println(a2.getInfo());
    }
}

2.3 泛型子类

当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,但需要指出的是,当使用这些泛型接口、泛型类时不能再包含类型形参

// 定义类A继承Apple类,Apple类不能跟类型形参
public class A extends Apple<T>{ }	// 定义错误!!!

定义方法时可以声明数据形参,调用方法(使用方法)时必须为这些数据形参传入实际的数据。与此类似的是,定义类、接口、方法时可以声明类型形参,使用类、接口、方法时应为类型形参传入实际的类型

// 使用Apple类时为T形参传入String类型
public class A extends Apple<String>

如果使用Apple类时没有传入实际的类型参数,Java编译器可能发出警告:使用了未经检查或不安全的操作——这就是泛型检查的警告。如果希望看到该警告提示的更详细信息,则可以通过为javac命令增加-Xlint:unchecked选项来实现。此时,系统会把Apple类里的T形参当成Object类型处理

2.4 泛型方法

泛型方法的定义方式:

修饰符 <T , S> 返回值类型 方法名(形参列表) {
	// 方法体...
}

比如,假设需要实现这样一个方法——负责将一个数组的所有元素添加到一个Collection集合中:

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o);
    }
}

上述泛型方法中定义了一个T类型形参,这个T类型形参就可以在该方法内当成普通类型使用。与接口、类声明中定义的类型形参不同的是,方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用

与类、接口中使用泛型参数不同的是,方法中的泛型参数无须显式传入实际类型参数。当程序调用fromArrayToCollection()方法时,无须在调用该方法前传入String、Object等类型,但系统依然可以知道类型形参的数据类型,因为编译器根据实参推断类型形参的值,它通常推断出最直接的类型参数

3 类型通配符

3.1 类型通配符

为了表示各种泛型List的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是未知类型元素的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。

程序可以调用get()方法来返回List<?>集合指定索引处的元素,其返回值是一个未知类型,但可以肯定的是,它总是一个Object。因此,把get()的返回值赋值给一个Object类型的变量,或者放在任何希望是Object类型的地方都可以

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> c = new ArrayList<>();
        c.add(1);
        c.add(2);
        c.add(3);
        test(c);
    }

    public static void test(List<?> c) {
        for (Object o : c) {
            System.out.println(o);
            System.out.println(o instanceof Integer);
        }
    }
}

3.2 类型通配符的上限

当直接使用List<?>这种形式时,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,我们不想使这个List<?>是任何泛型List的父类,只想表示它是某一类泛型List的父类。

public class Test {
    public static void main(String[] args) {
        ArrayList<Son> sons = new ArrayList<>();
        ArrayList<Father> fathers = new ArrayList<>();
        ArrayList<GrandFather> grandFathers = new ArrayList<>();
        test(sons);
        test(fathers);
        test(grandFathers); // 编译不通过
    }

    public static void test(List<? extends Father> c) {
        for (Object o : c) {
            System.out.println(o);
        }
    }
}

3.3 类型通配符的下限

与类型通配符上限类似,使用? super来表示通配符的下限:

public class Test {
    public static void main(String[] args) {
        ArrayList<Son> sons = new ArrayList<>();
        ArrayList<Father> fathers = new ArrayList<>();
        ArrayList<GrandFather> grandFathers = new ArrayList<>();
        test(sons); // 编译不通过
        test(fathers);
        test(grandFathers); 
    }

    public static void test(List<? super Father> c) {
        for (Object o : c) {
            System.out.println(o);
            System.out.println(o instanceof Integer);
        }
    }
}

4 使用例子

  • 构建集合时,比如ArrayList、HashMap时需要指定泛型
  • 发送数据到kafka和消费kafka的数据时,需要指定泛型,代表消息的key和value的类型
  • 自定义通用返回结果CommonResult,通过参数T指定返回结果data的数据类型
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值