编程语言
首页 > 编程语言> > 13_Java泛型总结

13_Java泛型总结

作者:互联网

文章目录

1. 泛型概述

1.1 了解泛型

1.2 泛型的好处:

由上面介绍,很自然的,泛型的好处为:

  1. 类型安全
  2. 消除了强制类型的转换

1.3 泛型标识符

2. 泛型类与接口

2.1 定义泛型类与接口

  1. 泛型类的定义语法:
class 类名 <泛型标识, 泛型标识...> {
	private 泛型标识 变量名;
	...
}
  1. 泛型接口的定义语法:
interface 接口名 <泛型标识, 泛型标识...> {
	泛型标识 方法名(); // 返回值为泛型的方法
    ...
}

2.2 使用泛型类与接口

类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
// 从jdk1.7开始,后面的<>中具体的数据类型可省略不写:
类名<具体的数据类型> 对象名 = new 类名<>();

// 示例:使用集合类
List<String> list = new ArrayList<>();

泛型类的注意事项:

  1. 如果使用泛型类定义对象时,没有指定具体的数据类型,则默认数据类型为Object

  2. 泛型的类型参数只能是类类型,不能是基本数据类型

  3. 泛型类型在逻辑上可以看成多个不同的类型,但实际上都是相同类型(运行前会进行类型擦除)

    因此也就不能使用泛型类型不一样的方法重载了(也可以理解为类型擦除之后都一样)

  4. 同时不能对确切的泛型类型使用instanceof操作

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
	public E set(int index, E element) {
        Objects.checkIndex(index, size);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

	private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }
}

3. 泛型方法

3.1 为什么使用泛型方法

无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法能将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而言,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。

静态方法不可以访问类上定义的泛型
如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。

3.2 泛型方法的定义语法

权限修饰符 <T,E...> 返回值类型 方法名(形参列表){
	...
}
// 示例:
// 表示返回值类型为E,参数类型也为E的方法:
public <E> E fun(E e){
    
}

4. 类型擦除

4.1 概念

由于泛型是java1.5之后才引入的概念,在此之前是没有泛型的

那为什么泛型代码能向下兼容呢?

原因是泛型信息只存在代码编译阶段,在进入JVM之前与泛型相关的信息都会被擦除,也就是说,泛型信息不会进入到运行时阶段

4.2 类型擦除前后比较

public class A<T> {
	private T key;
	
	public T getKey(){
		return key;
	}
	
	public void setKey(T key){
		this.key = key;
	}
}

// 泛型擦除后:
public class A {
	private Object key;
	
	public Object getKey(){
		return key;
	}
	
	public void setKey(Object key){
		this.key = key;
	}
}
public class A<T extends Number> {
	private T key;
	
	public T getKey(){
		return key;
	}
	
	public void setKey(T key){
		this.key = key;
	}
}

// 泛型擦除后:
public class A {
	private Number key;
	
	public Number getKey(){
		return key;
	}
	
	public void setKey(Number key){
		this.key = key;
	}
}
public <T extends Number> T getValue(T value){
	return value;
}

// 泛型擦除后:
public Number getValue(Number value){
	return value;
}
public interface Info<T> {
 	T info(T var);   
}

public class InfoImpl implement Info<Integer> {
 	@Override
    public Integer info(Integer var) {
     	return var;   
    }
}

// 类型擦除后:
public interface Info {
 	Object info(Object var);   
}

public class InfoImpl implement Info {
    public Integer info(Integer var) {
     	return var;   
    }
    
    // 桥接方法,保证接口与类的实现关系
 	@Override
    public Object info(Object var) {
     	return info( (Integer)var );   
    }
}

4.3 代码验证:

List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();

Class classStrList = strList.getClass();
Class classIntList = intList.getClass();

if(classStrList.equals(classIntList)){
    System.out.println("泛型类型实质是相同的");
}

输出结果:

泛型类型实质是相同的

5. 类型通配符

5.1 通配符的引入

我们知道Ingeter是Number的一个子类,同时在类型擦除中我们也验证过Generic<Ingeter>与Generic<Number>实际上是相同的一种基本类型。那么问题来了,在使用Generic<Number>作为形参的方法中,能否使用Generic<Ingeter>的实参传入呢?在逻辑上类似于Generic<Number>和Generic<Ingeter>是否可以看成具有父子关系的泛型类型呢?

实际上,这种类似多态的形参实参传入方法是错误的,因为同一种泛型可以对应多个版本(因为参数类型是不确定的),而不同版本的泛型类实例是不兼容的

对此,Java引入了泛型类型通配符:?,类型通配符可以代替具体的类型实参

也就是说,?是一个类型实参,而不是类型形参

以ArrayList构造器示例:

	public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

我们注意到,在ArrayList构造器中的参数中,写法是<? extends E>,而E是ArrayList定义类时使用的泛型标识符,那这个extends又是什么意思呢?这就涉及了通配符的上限与下限了:

5.2 类型通配符的上限

语法:

类/接口<? extends 实参类型1>

这表示该泛型的类型只能是实参类型1或者是实参类型1的子类,这也就限定了类型通配符的上限

代码示例:

public void show(List<? extends Number> list) {
    ...
    // 如果只使用?,不使用extends,则定义变量list时list中的泛型参数默认为Object,此时list中的元素变成其他类型需要强转
    // 但使用extends之后,要求list中的泛型参数默认为上限Number,下限则可以是任意Number的子类
}

5.3 类型通配符的下限

语法:

类/接口<? super 实参类型2>

表示该泛型的类型只能是实参类型2,或者是实参类型2的父类类型

6. 泛型与数组

  1. 当创建泛型数组的时候,可以说明带泛型的数组引用(即等号前面),但是不能直接创建带泛型的数组对象

    因为数组在编程期需要全程知道数据类型,而泛型在编译期中会有类型擦除

    但是可以通过匿名数组的形式将数组引用传递给泛型数组:

ArrayList<String>[] listarr = new ArrayList<>[5]; // 报错
// 通过匿名数组引用传递给泛型数组:
ArrayList<String>[] listarr = new ArrayList[5];// 后半部分为匿名非泛型数组
// 相当于:
ArrayList[] list = new ArrayList[5];// 注意两个的后半部分
ArrayList<String>[] listarr = list; // 以上相当于这俩句
/**
  但是要使用匿名数组引用传递给泛型数组
  而不能创建普通数组再传递(防止因为类型被改来改去不一致)
*/ 

  1. 可以通过java.lang.reflect.Array的newInstance(Class, int)来创建T<>数组(Array.newInstance)

    (但是在真正开发中尽量使用泛型集合代替泛型数组)

    返回值方法描述
    static ObjectnewInstance(类<?> componentType(成分类型, int length)创建具有指定组件类型和长度的新数组
    static ObjectnewInstance(类<?> componentType, int… dimensions)创建具有指定组件类型和尺寸的新数组

注意: 因为返回Object所以需要强转!

T[] arr = new T(5); // 报错,因为还不知道T类型
// 只能通过Array.newInstance来创建T<>数组
// 示例:
public class Fruit<T> {
    private T[] arr; // 不能直接初始化
    
    // 通过构造方法调用Array.newInstance创建泛型数组
    public Fruit(Class<T> clz, int len){
        arr = (T[])Array.newInstance(clz, len);
    }
    
    // 填充数组放入元素
    public void put(int index, T item){
        arr[index] = item;
    }
    
    // 获取数组元素
    public T get(int index){
        return arr[index];
    }
    
    // 获取数组
    public T[] getArr(){
        return arr;
    }
}

// 使用上面写的类
public class Test{
    public static void main(String[] agrs){
        Fruit<String> fruit = new Fruit<>(Stirng.class, 3);
        // 创建String类型,将String.class传进去
        // 接着通过上面创建的三个方法使用
    }
}

标签:13,Java,class,类型,擦除,key,泛型,public
来源: https://blog.csdn.net/qq_41781632/article/details/122287070