Java中的泛型

概念

  • Java泛型是JDK5引入的新特性,提供编译时类型安全监测机制,监测非法类型数据结构
  • 本质是参数化类型,也就是锁操作的数据类型被指定为一个参数
  • 泛型相当于提供了一个安全限制,不符合类型转换的就会报错
  • 可以用在类、接口和方法中,被称为泛型类、泛型接口和泛型方法

特点 

  • 保证了类型的安全性
  • 避免不必要的装箱拆箱操作,提高性能
  • 提高代码的重用性

泛型标识 

是一种约定,也可以使用A-Z英文字母的其中一个。

T :代表一般的任何类(Java类)。
E :代表 Element 元素的意思,或者 Exception 异常的意思。
K :代表 Key 的意思。
V :代表 Value 的意思,通常与 K 一起配合使用。
?:表示不确定的java类型

泛型类 

类型参数用于类型的定义称为泛型类,使用位置:

  • 非静态的成员属性类型
  • 非静态方法的形参类型(包括非静态成员方法和构造器)
  • 非静态成员方法的返回值类型

注意事项: 

  • 类中的静态方法和静态变量不可以使用泛型类所声明的类型函数。类型参数在创建时候确定,但静态变量和方法在类加载时初始化,直接使用类名调用,可能会类型未确定时被调用
  • 静态泛型方法中使用自身新定义的类型参数,不能用泛型类中定义的类型参数 
  • 泛型类不只接受一个类型参数,还可以接受多个类型参数
public class Person<T> {
    // T的类型由外部指定
    // name成员变量的数据类型为T => 非静态的成员属性类型
    private T name;

    // 构造方法形参name的类型也为T => 非静态方法的形参类型(包括非静态成员方法和构造器)
    public Person(T name) {
        this.name = name;
    }

    // 泛型方法getName的返回值类型为T => 非静态成员方法的返回值类型
    public T getName(){
        return name;
    }

    // 泛型类定义的类型参数 T 不能在静态方法中使用
    // 这是正确的,因为 E 是在静态方法签名中新定义的类型参数
    public static <E> E show(E one){
        return null;
    }
}

public class Student<E,T> {
    E name;
    T sex;

    public E getName(){
        return name;
    }

    public T getSex(){
        return sex;
    }
}

使用场景

 在集合中限制添加对象的类型,若添加对象类型不一致,则会直接报错,这就是类型安全监测机制的实现原理

public void test() {

    // 传入String类型,原泛型类可以想象它会自动扩展,其类型参数会被替换
    Person<String> person= new Person<>();

    // <> 中什么都不传入,等价于 Person <Object> person = new Person <>();
    Person person = new Person ();
}

泛型接口

注意事项

  • 类型参数在接口被继承或者实现的时候确定
  • 静态成员不能使用泛型接口定义的类型参数

使用场景

  1. 定义一个描述人的接口IPerson
  2. 定义一个IStudent接口继承泛型接口IPerson,在接口IStudent中确认泛型借口IPerson中的类型参数
  3. 定义一个类WorkStudent实现泛型接口IPerson,在类WorkStudent中确定泛型接口IPerson的类型参数
  4. 定义一个类SchoolStudent实现泛型接口IPerson,若没有确定泛型接口IPerson的类型参数,则默认为Object
  5. 定义一个类SocietyStudent实现泛型接口IPerson,若没有确定泛型接口IPerson的类型参数,也可以将SocietyStudent定义成泛型类,类型参数和泛型接口保持相同
//1、定义一个描述人的接口IPerson
interface IPerson<U, R> {
    // 报错! 接口中的属性默认是静态的,因此不能使用类型参数声明
    U name;

    int n = 10;

    // 普通方法中,可以使用类型参数
    R getRunSpeed(U u);

    // 抽象方法中,可以使用类型参数
    void StudyMaths(R r);

    // 在jdk8 中,可以在接口中使用默认方法, 默认方法可以使用泛型接口的类型参数
    default R method(U u) {
        return null;
    }
}

//2、定义一个IStudent接口继承泛型接口IPerson,
//在接口IStudent中确认泛型借口IPerson中的类型参数
interface IStudent extends IPerson<String, Double> {
	...
}

//使用 String 替换 U,用 Double 替换 R
class StudentOne implements IStudent{

    @Override
    public Double getRunSpeed(String s) {
        return null;
    }

    @Override
    public void StudyMaths(Double d) {
		...
    }
}


//3、定义一个类WorkStudent实现泛型接口IPerson,
//在类WorkStudent中确定泛型接口IPerson的类型参数,
//会使用 Integer 替换 U, 使用 Float 替换 R
class WorkStudent implements IPerson<Integer, Float> {

    @Override
    public Float getRunSpeed(Integer integer) {
        return null;
    }

    @Override
    public void StudyMaths(Float afloat) {
		...
    }
}

//4、定义一个类SchoolStudent实现泛型接口IPerson,
//若没有确定泛型接口IPerson的类型参数,则默认为Object,
//建议直接写成 IPerson<Object, Object>,
//等价 class SchoolStudent implements IPerson<Object, Object> 
class SchoolStudent implements IPerson{

    @Override
    public Object getRunSpeed(Object o) {
        return null;
    }

    @Override
    public void StudyMaths(Object o) {
    	...
    }
}

//5、定义一个类SocietyStudent实现泛型接口IPerson,
//若没有确定泛型接口IPerson的类型参数,
//也可以将SocietyStudent定义成泛型类,类型参数和泛型接口保持相同
class SocietyStudent<U, R> implements IPerson<U, R> { 
	...
}


泛型方法 

定义

  •  当在一个方法签名中的返回值前面声明一个<T>时,该方法称为泛型方法
  • <T>表明该方法声明了一个类型参数T,并且咋合格方法只能在该方法中使用
  • 泛型方法中也可以使用泛型类中定义的泛型参数

注意事项 

  • 只有在方法签名中声明<T>的方法才是泛型方法,使用了泛型类定义的类型参数的方法不是泛型方法
  • 泛型方法中可以同时声明多个参数类型
  • 泛型方法中可以使用泛型类型定义的泛型参数
  • 泛型方法和泛型类中定义的类型参数是相互独立的,是没有关系的,最好不同名
  • 将静态方法声明为泛型方法,在静态成员中不能使用泛型类定义的类型参数,但我们可以将静态成员方法定义为一个泛型方法
class Person<U>{

    // 该方法只是使用了泛型类定义的类型参数,不是泛型方法
    public void say(U u){
        System.out.println(u);
    }

    //<T>真正声明了下面的方法是一个泛型方法,可以接收任意类型的数据
    public <T> T Run(T t) {
        return t;
    }

    //泛型方法中可以同时声明多个参数类型
    public <T, S> T SearchOut(T t, S s) {
        return null;
    }

    //泛型方法中可以使用泛型类型定义的泛型参数
    public <T> U WalkAround(T t, U u) {
        return u;
    }

    // 静态的泛型方法
    public static <E> void show(E one){
        System.out.println(one);
    }
}

public class Test{
    public static void main(String args[]) {
        // 创建Person对象
        Person<Object> person = new Person();
        //非泛型方法调用
        person.say("hello world");
        // 给Person中的泛型方法传递字符串
        String s = person.Run("跑的很快");
        //给Person中的泛型方法传递数字,自动装箱
        int i = person.Run(30);

        // 输出 跑的很快
        System.out.println(s);
        // 输出 30
        System.out.println(i);
        // 输出 Good
        Person.show("Good");
    }
}

 泛型方法中的类型推断

  • 可以显示指定类型参数,也可以不指定
  • 指定时,传入泛型方法的参数数据类型必须是指定数据类型或者子类
  • 不指定时,多个类型参数时,取共同父级的最小级,直到Object
public class Student{

    // 这是一个简单的泛型方法  
    public static <T> T add(T x, T y) {
        return y;
    }

    public static void main(String[] args) {
        //不显式地指定类型参数
        //传入的两个实参都是Integer,所以泛型方法中的<T> => <Integer> 
        int i = Student.add(1, 5);

        //传入的两个实参一个是 Integer,另一个是 Float,
        //所以<T>取共同父类的最小级,<T> => <Number>
        Number f = Student.add(1, 3.6);

        //传入的两个实参一个是 Integer,另一个是 String,
        //所以<T>取共同父类的最小级,<T> => <Object>
        Object o = Student.add(1, "qwer");

        //显式地指定类型参数
        //指定了<T> => <Integer>,所以传入的实参只能为 Integer 对象    
        int a = Student.<Integer>add(1, 6);

        //指定了<T> => <Integer>,所以不能传入 Float 对象
        //编译错误
        int b = Student.<Integer>add(1, 3.2);

        //指定<T> => <Number>,所以可以传入 Number 对象
        //Integer 和 Float 都是 Number 的子类,因此可以传入两者的对象
        Number c = Student.<Number>add(1, 8.2);
    }
}

类型擦除

概念定义

  • 泛型信息只存在于代码编译阶段,代码编译结束后,与泛型相关的信息会被擦掉
  • 成功编译过后的 class 文件中不包含任何泛型信息,泛型信息不会进入到运行时阶段
  • 代码成功编译后,其内的所有泛型信息都会被擦除,并且类型参数 T 会被统一替换为其原始类型(默认是 Object 类,若有 extends 或者 super 则另外分析)
  • 在泛型信息被擦除后,若还需要使用到对象相关的泛型信息,编译器底层会自动将对象进行类型转换
public class GenericType {
    public static void main(String[] args) {  
        ArrayList<String> arrayString = new ArrayList<String>();   
        ArrayList<Integer> arrayInteger = new ArrayList<Integer>(); 
        //ArrayList<String>,ArrayList<Integer>编译后都会变成ArrayList< Objec t>类型  
        //输出true
        System.out.println(arrayString.getClass() == arrayInteger.getClass());
    }  
}

泛型通配符 

定义概念:希望泛型能够处理某一类型范围内的类型参数

泛型通配符有 3 种形式:

  • <?> :被称作无限定的通配符。
  • <? extends T> :被称作有上界的通配符。
  • <? super T> :被称作有下界的通配符。

<? extends T> 有上界的通配符 => 可以读,不能写

  • T代表类型参数的上界,表示类型范围是T和T的子类,ArrayList<Integer  和 ArrayList< umber>不存在继承关系,在逻辑上可以看做父子类关系
  • ArrayList<? extends Number> 不能确定到底是哪一个集合,是一个未知类型集合,所以不能进行写入操作,否则产生ClassCastException异常
  • 目的是为了拓展方法形参中类型参数的范围
  • 但是可以往集合中添加 null,因为 null 表示任何类型
public class Test {
    public static void main(String[] args) {

        // 编译错误
        ArrayList<Number> list01 = new ArrayList<Integer>();

        // 编译正确
        ArrayList<? extends Number> list02 = new ArrayList<Integer>();

        // 写操作
        ArrayList<? extends Number> list = new ArrayList<>();
        // 编译正确,只能添加空值 null
        list.add(null);
        // 编译错误
        list.add(new Integer(1));
        list.add(new Float(1.5));

        // 创建一个 ArrayList<Integer> 集合
        ArrayList<Integer> integerList = new ArrayList<>();
        integerList.add(1);
        integerList.add(2);
        // 将 ArrayList<Integer> 传入 printIntVal() 方法
        printIntVal(integerList);

        // 创建一个 ArrayList<Float> 集合
        ArrayList<Float> floatList = new ArrayList<>();
        floatList.add((float) 1.0);
        floatList.add((float) 2.0);
        // 将 ArrayList<Float> 传入 printIntVal() 方法
        printIntVal(floatList);
    }

    public static void printIntVal(ArrayList<? extends Number> list) {
        // 遍历传入的集合,并输出集合中的元素       
        for (Number number : list) {
            System.out.print(number.intValue() + " ");
        }
        System.out.println();
    }
}

<? super T>下界的通配符 => 可以写,不能读

  • T 代表了类型参数的下界,表示类型参数的范围是T和T的父类,直至Object
  • ArrayList<? super Number> 集合中可以添加 Number 类及其子类的对象(不包括 Number 类)
  • 不能将传入集合的元素赋值给 Number 对象,因为传入的可能是 ArrayList< Object > 集合,向下转型可能会产生ClassCastException异常
public class Test {
    public static void main(String[] args) {

        // 编译错误
        ArrayList<Integer> list01 = new ArrayList<Number>();

        // 编译正确
        ArrayList<? super Integer> list02 = new ArrayList<Number>();

        // 创建一个 ArrayList<? super Number> 集合
        ArrayList<Number> list = new ArrayList();

        // Object 是 Number 的父类 
        // 编译错误
        list.add(new Object());

        // 往集合中添加 Number 类及其子类对象
        list.add(new Integer(1));
        list.add(new Float(1.1));
        // 调用 fillNumList() 方法,传入 ArrayList<Number> 集合
        fillNumList(list);
        System.out.println(list);
    }

    public static void fillNumList(ArrayList<? super Number> list) {
        list.add(new Integer(0));
        list.add(new Float(1.0));

        // 遍历传入集合中的元素,并赋值给 Number 对象;
        // 会编译错误
        for (Number number : list) {
            System.out.print(number.intValue() + " ");
            System.out.println();
        }

        // 遍历传入集合中的元素,并赋值给 Object 对象;
        // 但只能调用 Object 类的方法,不建议这样使用
        // 可以正确编译
        for (Object obj : list) {
            System.out.println(obj);
        }

    }
}

<?>  无限定通配符

  • ?代表任意一种数据类型,能代表任何一种数据类型的只有 null
  • Object 本身也算是一种数据类型,但却不能代表任何一种数据类型
  • ArrayList<?> 是 ArrayList< Object > 逻辑上的父类,本质上没有继承关系
  • 大多数情况下,可以用类型参数 < T > 代替 <?> 通配符
public class Test{
	public static void main(String[] args) {
        ArrayList<Integer> integerList = new ArrayList<>(110, 668);
        // 安全地向上转型
        ArrayList<?> list = integerList ; 

        ArrayList<?> listTwo = new ArrayList<>();
        // 编译正确
        listTwo .add(null);
        Object obj = listTwo .get(0);

        // 编译错误,不能指定 ArrayList<?> 的数据类型
        listTwo .add(new Integer(1));
        Integer num = listTwo .get(0);
    }
}

PECS 原则 

Producer Extends Consumer Super

  • 如果需要返回T,则它是生产者(Producer),需要用到extends通配符
  • 如果需要写入T,则它是消费者(Consumer ),需要super通配符

Collections 的 copy() 方法 

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    int srcSize = src.size();
    if (srcSize > dest.size())
        throw new IndexOutOfBoundsException("Source does not fit in dest");

    if (srcSize < COPY_THRESHOLD ||
        (src instanceof RandomAccess && dest instanceof RandomAccess)) {
        for (int i=0; i<srcSize; i++)
            // src 是 producer,dest 是 consumer
            dest.set(i, src.get(i));
    } else {
        ListIterator<? super T> di=dest.listIterator();
        ListIterator<? extends T> si=src.listIterator();
        for (int i=0; i<srcSize; i++) {
            di.next();
            di.set(si.next());
        }
    }
}

  • 22
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值