Java高级编程:泛型

一、为什么要使用泛型

  • 在增加泛型类以前
    • 使用集合时,并不知道要装的对象是哪种类型,因此Collection及Map的实现类直接使用Object类型的数据集合来存放元素
    • 对集合中添加元素时,任何类型元素都可以放入同一集合
    • 对集合中的数据进行获取时,要进行强制类型转换
    @Test
    public void generictyTest1(){
        // 集合中不使用泛型的问题:
        List list = new ArrayList<>();
        list.add(98);
        list.add(77);
        list.add(36);
        // 问题一:类型不安全:
        list.add("helloworld");
        for (Object o : list) {
            // 问题二:类型转换时可能出现异常
            int score = (Integer)o;
        }
    }
  • 泛型给这种问题提供了一种解决方法:类型参数。List<Integer> list = new ArrayList<>(); (等式右边省略的类型参数可从变量的类型参数推导出)
    • 编译器也可以很好的利用这个信息,当调用get方法时,不需要进行强制类型转换
    • 编译器还知道ArrayList中add方法有一个类型为String的参数,这将比使用Object类型的参数安全。并且这可以进行检查,避免插入错误类型的对象
	// 使用集合时加上泛型
    @Test
    public void generictyTest2(){
        // 集合中不使用泛型的问题:
        List<Integer> list = new ArrayList<>();
        list.add(98);
        list.add(77);
        list.add(36);
//        list.add("helloworld"); // Error:;类型检查报错:
        for (Integer integer : list) {
            System.out.println(integer); // 不用对元素进行强制类型转换
        }
    }

二、泛型类的使用

1. 集合中使用泛型
  1. 集合接口或类在jdk5.0是都修改为带泛型的结构
  2. 在实例化集合类时,可以指明具体的泛型类型
  3. 指明完以后,在集合类或接口中凡是定义类或接口时,内部接口(比如:方法、 构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型
  4. 泛型类必须是类类型,而不能是基本数据类型,使用到基本数据类型的地方,需要使用对应的包装类
  5. 如果实例化时,没有指明泛型类型,默认类型为java.lang.Object类型
2. 自定义泛型类
  1. 首先列出将要定义的类中,需要用泛型实现的变量(即变量类型不确定)。
  2. 使用大写字母T,E,K,V等用来表示这些变量的类型(字母并不规定,则定义即可)
  3. 将这些表示类型的字母添加一对<>中,放在类名后面
  4. 将成员变量中类型不确定的变量类型使用这些字母替带即可
					// 将参数类型T放在尖括号中添加到类名之后
    public class SelfDefineStack<T>{
        private int MAXSIZE = 10 ;
        // 泛型缺陷之一:不能之间创建泛型数组
        private T[] stack = (T[])new Object[MAXSIZE] ; // 栈中要存储的元素的数据类型并不确定,因此使用参数类型T来表示
        int top = 0 ;
    }
  1. 在定义的类的方法中,凡是需要用到该变量类型的地方,均使用类型参数替换即可
		// 插入元素类型不确定,使用类型参数替换,以下方法同理
        public void push(T elem){
            if(!isEmpty())
            this.stack[top++] = elem;
        }
        public T peek(){
            return this.stack[top-1];
        }
        public void pop(){
            this.top-- ;
        }
        public boolean isEmpty(){
            return this.top >= this.MAXSIZE;
        }
3. 自定义泛型方法
  • 自定义泛型方法和使用了泛型的方法并不是同一个概念
  • 泛型方法的类型参数与类的类型参数毫无关系,即泛型方法所属的类是不是泛型类都没有关系
  • 泛型方法可以声明为静态的,原因:泛型参数是在调用方法时确定的,并在实例化类时确定
	// 泛型方法时示例: Collections工具类并不是泛型类,但它有很多泛型方法
	public class Collections {
			/* 其他的方法*/
		    public static <T extends Comparable<? super T>> void sort(List<T> list)
		    {
        		list.sort(null);
    		}
    		/* 其他的方法*/
	}
	// 自定义泛型方法:
	public class DefineGenericityMethod {
    // 使用<E>告诉编译器E是一个泛型而不是一个类
    // public List<E> copyFormArrayToList(E[] arr):如果不在方法返回类型之间加<E>,编译器会认为E是一个类的类型而不是一个类型参数
    public <E> List<E> copyFormArrayToList(E[] arr){
        List<E> list = new ArrayList<>();
        for(E elem:arr){
            list.add(elem);
        }
        return list;
    }
    @Test
    public void userGenericityMethod(){
        DefineGenericityMethod stringDefineGenericityMethod = new DefineGenericityMethod();
        Integer[] arr = new Integer[]{5,3,6,4,2};
        // 泛型方法在调用时,与类并无任何关系
        List<Integer> list = stringDefineGenericityMethod.copyFormArrayToList(arr);
        System.out.println(list);
    }
}

三、泛型类的继承

1. 继承规则:
  1. 子类不包有父类的泛型,按需实现
    没有类型:擦除为Object
    具体类型
// 上述情况说明:
// 父类:
class SuperClass<T,E>{
	T value1 ;
	E value2 ;
}
// 子类不包含父类的泛型,且没有指定类型,因此子类中的两个变量均为Object类型
class SubClass1 extends SuperClass{
	// Object value1
	// Object value2
}
// 子类不包含父类的泛型,并指定了类型
class  SubClass2 extends SuperClass<String ,Integer>{
	// String value1
	// Integer value2
}
  1. 子类保留父类的泛型:泛型子类
    全部保留
    部分保留
    - 父类的泛型类型有两个:子类在继承父类时,要么已经指明了部分类型,要么都没指明类型
// 子类保留父类的泛型(全部保留),此时子类也是一个泛型类
class SubClass3<T,E> extends SuperClass<T,E>{
	// T value1 ;
	// E value2 ;
}
// 子类保留父类的泛型(部分保留),此时子类也是一个泛型类
class SubClass4<E> extends SuperClass<String,E>{
	// String value1 ;
	// E value2 ;
}
class SubClass5<EZ> extends SuperClass<String,E>{
	Z race ; // 子类可以拥有自己的泛型类
	// String value1 ;
	// E value2 ;
}
  • 结论:子类除了指定或保留父类的泛型,还可以增加自己的泛型
  • 测试: 定义一个泛型父类:
// 定义一个角色类:
public class Role<T> {
    String name ;
    int damage ;
    int blood ;
    T race ; // 该角色的种族未知,因此使用这里使用泛型
    public T getRace() {
        return race;
    }

    public void setRace(T race) {
        this.race = race;
    }

    public Role(String name, int damage, int blood, T race) {
        this.name = name;
        this.damage = damage;
        this.blood = blood;
        this.race = race;
    }
}
  • 定义两个泛型子类
// 定义一个人类英雄继承角色类,继承时指定参数类型为Person类(子类没有保留父类的泛型,指定了一个Person的类型参数)
public class Hero extends Role<Person>{
    public Hero(String name, int damage, int blood, Person race) {
        super(name, damage, blood, race);
    }
}
class Person{
    String name ;
    int age ;
}
// 定义一个恶魔角色角色,继承自角色类(对父类的泛型进行保留,并拥有自己的泛型)
public class Ghost<E,T> extends Role<T>{
    E source ; // 恶魔的来源:西方和东方
    public Ghost(String name, int damage, int blood, T race) {
        super(name, damage, blood, race);
    }
    public Ghost(String name, int damage, int blood, T race, E source) {
        super(name, damage, blood, race);
        this.source = source;
    }
}

2. 泛型在继承方面的体现;
  • 类A是类B的父类,G<A>和G<B>不具备子父类关系,二者是并列关系
    • List<Object> 和 List<String>是并列关系,进行赋值时会报错
  • 类A和类B的父类,但是A 和B 两者具备子父类关系
    • List<String> list = new ArrayList<String>();
    public void inheritProblem(){
        String str = null ;
        Object obj = null ;
        obj = str ; // Object和String具有子父类关系
        Object[] objs = null ;
        String[] strs = null ;
        objs = strs ; // Object数组和String数组具有子父类关系
        List<String> strList = null ;
        List<Object> objList = null ;
//        objList = strList; // List<Object>和List<String>不具有子父类关系,因此不可以赋值
    }

四、类型变量的限定

1. 类型参数不加以修饰所产生的问题
  • 如果需要自定义一个对数组进行自然排序的类,调用了compareTo方法,如果该数组的元素所属的类并没有实现Comparable接口,则会出现错误
// 找出数组中的最小元素
class ArrayAlg{
    public static <T>T min(T[] a){
        if(a == null||a.length == 0) return null ;
        T smallest = a[0] ;
        for(int i = 1 ;i<a.length;i++)
            if(smallest.compareTo(a[i]) >0 ) // Error
            	smallest = a[i] ; 
        return smallest ;
    }
}
// 变量smallest类型为T,这意味着它可以是任何一个类的对象,这无法确保它实现了compareTo方法
  • 对变量smallest的类型参数进行修饰:
public static <T extends Comparable>T min(T[] a)...
// 实际上, comparable接口本身就是一个泛型类型,现在暂时其复杂性和编译器产生的警告
2. 类型参数修饰的规则:
  • 一个类型变量或通配符可以有多个限定,限定类型用&分隔,而逗号用来分隔类型变量。在Java中,可以根据需要拥有多个接口超类型,但限定中至多有一个类,如果用一个类作为限定,它必须是限定列表中的第一个
// 要求参数类型T继承了Comparable ,Serializable ,Iterator接口
// 参数类型E继承了Map接口
class MyDefineClass <T extends Comparable & Serializable & Iterator, E extends Map> {
    /* */
}

五、类型参数通配符

1. 通配符的使用 ?
  • 通配符类型中,允许类型参数变化。
// 如:该通配符类型表示任何泛型Pair类型
Pair<? extends Employee> // 它的参数类型是Employee的子类,如Pair<Manager>,但不是Pair<String>
{ // 类的变量和方法
}

  • 类A和类B的父类,G<A>和G<B>不具备父子关系,但G<?>是G<A>和G<B>的公共父类
    • 对于List<?>而言,就不能像向其内部添加数据,除了null之外
2. 有限制条件的通配符
  1. 通配符指定上限:上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
  2. 通配符指定下限:使用时指定的类型不能小于操作的类,即 >=
    举例:
<? extends Number> 			// (无穷小,Number] 只允许泛型为Number及Number子类的引用调用
<? super Number> 			// [Number,无穷大) 只允许泛型为Number及Number父类的引用调用
<? extends Comparable> 		//只允许泛型为实现Comparable接口的实现类引用调用
// 有限制条件的通配符的测试:
    @Test
    public void userAllPowerfulChar2(){
        class Person{}
        class Student extends Person{}
        List<? extends Person> list1 = null ; // 可以引用Person及其子类
        List<? super Person> list2 = null ; // 可以引用Person及其父类
        List<Student> list3 = new ArrayList<>();
        List<Person> list4 = new ArrayList<>();
        List<Object> list5 = new ArrayList<>();

        list1 = list3 ;
        list1 = list4 ;
//        list1 = list5; // 编译不通过

//        list2 = list3; // 编译不通过
        list2 = list4 ;
        list2 = list5 ;

        // 读取数据
        list1 = list3;
        Person person = list1.get(0);
//        Student student = list1.get(0); // 编译不通过:因为 ?extends Person所表示的类型必须是Person及其子类,因此必须使用Person及其父类来引用,
//          至少必须是Perosn,因为如果get(0)得到的元素类型Person,则Student类型无法引用其父类
        list2 = list4;
//        Person person1 = list2.get(0); // 编译不通过,因为如果限制类型为Person及其父类,则最高层次的父类为Object,只能使用Object来进行引用
        Object obj = list2.get(0);

        // 写入数据
        list1 = list4;
//        list1.add(new Student()); // 编译不通过,指定类型若是Student的子类,则无法将子类赋给父类

        list2 = list4;
        list2.add(new Person()); // 可以,
        list2.add(new Student());

    }

六、使用泛型类需要注意的点

  1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号中,比如<E1,E2,E3>
  2. 泛型类在声明构造器时,不应该添加泛型:
    构造器定义:public GenerictyClass(){}
    错误定义: public GenerictyClass(){}
  3. 实例化后,操作原来反省位置的结构必须与指定的泛型类型一直
  4. 泛型不同的引用不能相互赋值
    尽管编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中(类型擦除)
  5. 泛型如果不指定,则泛型对应的类型都按照Object处理,但不等价于Object
  6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象
  7. 泛型的指定中不能使用基本数据类型,需要用包装类进行替换
  8. 在类/接口中声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型,但在静态方法中不能使用类的泛型
    因为泛型类型的指定是在构造对象时指定,但静态方法可以直接通过类名调用,因此静态方法中不能使用泛型
  9. 异常类不能是泛型的
  10. 不能使用new E{},但可以先创建一个Object类型的数组,并肩齐强制转换成想要的类型数组
  11. 子类不包有父类的泛型,按需实现
    没有类型:擦除为Object
    具体类型
  12. 子类保留父类的泛型:泛型子类
    全部保留
    部分保留
    父类的泛型类型有两个:子类在继承父类时,要么已经指明了部分类型,要么都没指明类型
    结论:子类除了指定或保留父类的泛型,还可以增加自己的泛型

七、类型擦除

1. 类型擦除
  • 虚拟机没有泛型类型对象--------所有的对象都属于普通类
  • 无论何时定义一个泛型类型,都自动提供一个相应的原始类型。原始类型的名字就是删去类型参数的泛型类型名。擦除类型变量并替换为限定类型(无限定的变量用Object)
    • 在程序中可以包含不同类型的Pair,而擦除类型后就变成原始的Pair类
  • 原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Object替换,

// 定义一个泛型类:对类型参数不进行修饰:

public MyClass<T>{
	T value ;
}
// 在虚拟机中进行类型擦除之后:
public MyClass{
	Object value ;
}

// 定义一个泛型类:对类型参数进行修饰:

public MyClass<T extends SuperClass>{
	T value ;
}
// 在虚拟机中进行类型擦除之后:
public MyClass{
	SuperClass value ;
}
2. 翻译泛型表达式
  • 当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。
// 示例:
Pair<Employee> buddies = ... ;
Employee buddy = buddies.getFirst() ;
// 在擦除getFirst的返回类型后将返回Object类型,编译器自动插入Employee的强制类型转换。
  • 当存取一个泛型变量也要插入强制类型转换。
  • 编译器把这个方法调用翻译为两条虚拟机指令:
    1. 对原始方法Pair.getFirst的调用
    2. 对返回的Object类型强制转换为Employee类型
3. 翻译泛型方法
  • 类型擦除也会出现在泛型方法中。
 public static <T extends Comparable> T min(T [] a)
 // 以上是一个完整的方法族,擦除类型之后,只剩一个方法:
 public static Comparable min(Comparable[] a) 
 // 类型参数已经被擦除,只留下了限定类型Comparable
  • 类型擦除带来的复杂问题:
    1. 子类方法的返回类型冲突
    2. 子类方法的参数类型冲突(桥方法解决)
  • 桥方法
  • 泛型的类型擦除的补充
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值