泛型

泛型

一. 泛型程序设计(Generic programming)

意味着编写的代码可以被很多不同类型的对象重用。

  1. 例如实现一个简单的栈:若想要这个栈可重用,可以尝试将其所存放元素定义为Object类型
class ObjectStack {
    private Object[] elem;
    private int top;

    public ObjectStack() {
        this(10);
    }
    public ObjectStack(int size) {
        this.elem = new Object[size];
        this.top = 0;
    }
    public void push(Object value) {
        this.elem[this.top++] = value;
    }
    public void pop() {
        --this.top;
    }
    public Object getTop() {
        return this.elem[this.top-1];
    }
}
public class ObjectStackDemo {
    public static void main(String[] args) {
        ObjectStack objectStack = new ObjectStack();
        objectStack.push(10);   //自动装包
        objectStack.push(5.8);
        objectStack.push("Daria");
        String str = (String)objectStack.getTop();
    }
}

但在实际运行过程中遇到这样的问题:

  • 想要获取一个值时,要将Object类型根据栈顶所存放数据的类型进行强制类型转换;
  • 且当获取一个值就要进行一次强转,此外,没有错误检查,即可以压栈任何类型的数据以及类的对象。
  1. 泛型提供了一个更好的解决方案:类型参数(type parameters)。

对上面的代码做出修改,这使代码具有很好的可读性,当定义一个Integer类型的栈,可以看到当压栈double类型的元素时会提示报错,且要读出元素时不用进行强转。

class GenericStack<T> {
    private T[] elem;
    private int top;
    public GenericStack() {
        this(10);
    }
    public GenericStack(int size) {
        this.elem = (T[])new Object[size];
        this.top = 0;
    }
    public void push(T value) {
        this.elem[this.top++] = value;
    }
    public void pop() {
        --this.top;
    }
    public T getTop() {
        return this.elem[this.top-1];
    }
}

public class GenericStackDemo {
    public static void main(String[] args) {
        GenericStack<Integer> genericStack = new GenericStack<Integer>();
        genericStack.push(10);
        //genericStack.push(10.5); 只能入栈 Integer
        int data = genericStack.getTop();

        GenericStack<String> genericStack2 = new GenericStack<String>();
        genericStack2.push("Daria");
        genericStack2.push("Student");
        String str = genericStack2.getTop();
    }
}
  1. 泛型的意义:
  • 在编译期间对类型进行自动检查,并不是替换;
  • 自动对类型进行转换。

二、泛型类

  1. 一个泛型类(generic class)就是具有一个或多个类型变量的类。
  • GenericAlg类引入了一个类型变量T,用尖括号括起来放在类名的后面。
  • 泛型类可以有许多个类型的变量,例如可以定义GenericAlg类有两个域,使用不同的类型
class GenericAlg<T,U> {...}
  • 用具体的类型替换类型变量就可以实例化泛型类型,例如:
GenericAlg<Integer>
GenericAlg<String>
  • 可以将结果想象成带有构造器的普通类和方法,例如:
public Integer findMaxValue(Integer[] array) {...}
public String findMaxValue(String[] array) {...}
  1. 例如写出一个通用算法找到数组的最大值
  • 在类型比较大小时需要实现Comparable接口,利用接口中定义的CompareTo方法进行比较;
  • 例如在main方法里分别定义Integer和Double类型的数组,求它们中元素的最大值。
class GenericAlg<T extends Comparable<T>> {  //泛型类
    public T findMaxValue(T[] array) {
        T max = array[0];
        for (int i = 0;i < array.length;i++) {
            if (array[i].compareTo(max) > 0) {
                max = array[i];
            }
        }
        return max;
    }
}
public class GenericDemo2 {
    public static void main(String[] args) {
        Integer[] array = {1,2,3};
        GenericAlg<Integer> genericAlg = new GenericAlg<Integer>();
        System.out.println(genericAlg.findMaxValue(array)); //通过类的对象调用类的成员方法

        Double[] array2 = {1.0,2.0,3.0};
        GenericAlg<Double> genericAlg2 = new GenericAlg<Double>();
        System.out.println(genericAlg2.findMaxValue(array2));
    }
}

运行结果:

在这里插入图片描述

三、泛型方法

  1. 定义带有类型参数的泛型方法
  • 泛型方法是在普通类中定义也可以定义在泛型类中;
  • 类型变量放在修饰符public static的后面,返回类型的前面;
  • 当调用泛型方法时,方法名前的尖括号内放入具体的类型,但编译器实际上可以通过实参的类型推演出泛型类型。
  1. 写出一个泛型方法找到数组的最大值:
  • 同样需要实现Comparable接口来进行类型的比较;
class GenericAlg2 {     //泛型方法
    public static <T extends Comparable<T>> T findMaxValue(T[] array) {
        T max = array[0];
        for (int i = 0;i < array.length;i++) {
            if (array[i].compareTo(max) > 0) {
                max = array[i];
            }
        }
        return max;
    }
 }
public class GenericDemo2 {
    public static void main(String[] args) {
        Integer[] array = {1,2,3};
        Double[] array2 = {1.0,2.0,3.0};
        System.out.println(GenericAlg2.findMaxValue(array));//编译器会通过实参的类型推演出泛型类型
        System.out.println(GenericAlg2.<Double>findMaxValue(array2));//指明泛型类型
    }

运行结果:

在这里插入图片描述

四、类型变量的限定

  1. 在上面的例子中将T限制为实现了Comparable接口(只包含了CompareTo一个抽象方法)的类,通过对类型变量T设置限定来实现:
  • T只是一个类型占位符,表示当前类是一个泛型类;
public static <T extends Comparable<T>> T findMaxValue(T[] array)...
  • Comparable接口本身就是一个泛型类型
  • 在实现Comparable接口时使用的是关键字extends而不是implements
<T extends 限定类型>

表示T应该是绑定类型的子类型,T和绑定类型可以是类也可以是接口,选择extends的原因是这样更接近子类的概念,且Java中一个类型变量或通配符可以有多个限定,限定类型用“&”隔开;

五、泛型的类型擦除机制

  1. 泛型的编译
  • 类型向上(父类)擦除到Object类;
  • 在编译期间,编译器把泛型全部擦除为Object类;
  • 若有限定类型,则其为泛型类型擦除的上界,例如:
extends Comparable<T>

六、内存泄漏

  1. 用上面实现的通用栈存放Student类对象(仅作实例,只写一个空类未实现其属性以及方法)
  • 压栈两个Student对象再出栈一个;
class Student {

}
public class GenericStackDemo {
    public static void main(String[] args) throws InterruptedException {
        GenericStack<Student> genericStack = new GenericStack<Student>();
        genericStack.push(new Student());   //放入自定义类对象
        genericStack.push(new Student());
        genericStack.pop();
        System.gc();
        Thread.sleep(100000);
        //在当前对象没有被引用时垃圾回收器自动回收
    }

在这里插入图片描述

  • 可以看到在出栈之后栈中依然有两个Student对象,说明出栈只是下移了栈顶指针Top的位置,而依然有引用指向被出栈的那个Student对象;内存没有及时释放;

在这里插入图片描述

  1. 修改通用栈中出栈的pop方法:
    public void pop() {
        this.elem[top-1] = null;
        --this.top;
    }
  • 可以看到出栈之后只剩下一个Student对象;
    在这里插入图片描述

七、约束与局限

  1. 不能用基本类型实例化类型参数
  2. 运行时类型查询只适用于原始类型
  3. 不能创建参数化类型的数组
  4. 不能实例化类型变量
  5. 不能构造泛型数组

八、泛型类型的继承规则

九、通配符类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值