Java中的泛型

一.通用栈
栈的特点:
先进后出
所有类的基类是Object,所以如果我们呢想要写一个通用栈的话,就可以写成Object类的通用栈
写一个简单的入栈出栈的操作:

/**
 * 通用栈  Object 
 * @author wangyu
 * @data 2018年5月31日
 */
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 val) {
        this.elem[this.top++] = val;
    }
    public void pop() {
        --this.top;
    }
    public Object getTop() {
        return this.top - 1;
    }
}
public class TestDemo1 {
    public static void main(String[] args) {
        ObjectStack s1 = new ObjectStack(10);
        s1.push(10);
        s1.push(20);
        s1.push("hello");
        s1.push(20.6);
        //出栈的时候,需要强转,入栈的时候无影响
        double data = (double)s1.getTop();
    }
}

s1.push(10);采用了自动装箱的原理,使用了valueOf的方法(源码)
我们直到栈顶的元素是20.6,也就double类型
所以出栈的时候必须对此出栈的数据进行强转的操作,否则编译器不识别会报错
但是入栈的时候是没有影响的

二.泛型
泛型的意义:
1.会对类型进行自动检查
2.会进行自动类型转换
如上的通用栈,我们出栈的时候需要进行强转才可以,但是我们这里如果改为泛型的话
泛型会对类型进行自动检查
所以先用泛型来实现栈的话:

/**
 * 用泛型实现栈
 * @author wangyu
 * @data 2018年5月31日
 */
class GenericStack<T> {
    private T[] elem;
    private int top;
    public GenericStack() {
        this(10);
    }
    public GenericStack(int size) {
        //this.elem = new T[size];
        this.elem = (T[])new Object[size];
        this.top = 0;
    }
    public void push(T val) {
        this.elem[this.top++] = val;
    }
    public void pop() {
        this.elem[this.top - 1] = null;
        --this.top;
    }
    public T getTop() {
        return this.elem[this.top - 1];
    }
}
public class TestDemo2 {
    public static void main(String[] args) {
        GenericStack<Animal> s1 = new GenericStack<Animal> ();
        GenericStack<Integer> s1 = new GenericStack<Integer> ();
        s1.push(10);
        s1.push(20);
        s1.push(30);
        s1.pop();
        int val = s1.getTop();
    }
}

如果我们测试类中输入
s1.push(“hello”);
会发现有报错
是因为泛型会对类型进行自动检查
只能是Integer类型的
int val = s1.getTop();
发现不报错,不需要强转
是因为会进行一次自动类型转换
这是通用栈和泛型栈的区别,且是泛型的意义的举例

三.Java中泛型是如何编译的?
方法:类型的擦除
擦除成Object类型,注意是类型检查(不是替换)
擦除方向是向上擦除,往基类(Object)方向进行擦除
也就是类型擦除的机制
此时要引入泛型的上界:
如果某一上界的话,那么我们就那些擦除的时候会直接擦除到Object
但是查看源码发现Object没有某些方法,如compareTo方法
擦除过了
此时如果需要用泛型来编写某些方法时,用到这些方法的话,会无法实现
就需要引入一个上界,来限定一下

/**
 * 泛型的上界
 * 找出数组当中的最小值
 * 泛型没有下界
 * @author wangyu
 * @data 2018年5月31日
 */
class GenericAlg<T extends Comparable<T>> {
    public T findMinVal(T[] array) {
        T minVal = array[0];
        for(int i = 0;i < array.length;i++) {
            if(minVal.compareTo(array[i]) > 0) {
                minVal = array[i];
            }
        }
        return minVal;
    }
}
class GenericAlg2 {
    /**
     * 泛型方法
     * @param array
     * @return
     */
    public static<T extends Comparable <T>> T findMinVal(T[] array) {
        T minVal = array[0];
        for(int i = 0;i < array.length;i++) {
            if(minVal.compareTo(array[i]) > 0) {
                minVal = array[i];
            }
        }
        return minVal;
    }
}
class Usr implements Comparable<Usr>{
    private String name;
    private String sex;
    private int age;
    public Usr (String name,String sex,int age) {
        super();
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Usr [name=" + name + ", sex=" + sex + ", age=" + age + "]";
    }

    @Override
    public int compareTo(Usr o) {
        return age > o.age ? 1 : ((age == o.age) ? 0 : -1) ;
    }
}
public class TestDemo3 {
    public static void main(String[] args) {
        Usr[] usr = new Usr[3];
        usr[0] = new Usr("liubei","man",18);
        usr[1] = new Usr("caocao","man",28);
        usr[2] = new Usr("li","man",38);
        GenericAlg<Usr> g2 = new GenericAlg<Usr>();
        System.out.println(g2.findMinVal(usr));
        System.out.println(GenericAlg2.findMinVal(usr));
    }
}

这里是将上界设定为有Comparable接口的地方
因为Object是不能实现compareTo方法的
但是我们比较年龄的时候需要引入compareTo方法的
此时如果向上擦除到Object就过了,无法实现
所以我们需要设定上界到Comparable接口,然后重写compareTo方法来实现比较年龄的大小

泛型没有下界,只会向基类方向擦除

四.泛型中常见的坑有哪些?
1.能不能new泛型类型的数组?
this.elem = new T[size];这种方法是错误的写法
也就是泛型是不能new泛型类型的数组
this.elem = (T[])new Object[size]; 需要强转(有警告)

2.能不能new 泛型类型的对象 ?
例如: T obj = new T();
这种写法是错误的
必须有尖括号,指定此时泛型的类型,不然无法检查,违背了泛型的意义

3.能不能得到泛型类型的对象数组?
例如:

GenericStack<Integer>[] s3 = new GenericStack<Integer>[10];

这样的写法也是错误的,会报错
如果有人说写成如下:

Object obj = new GenericStack<Integer>[10];

假设是成立的,这样的话,会造成我们传参数的时候又回去了Object通用类型,就又需要出栈强转等,白费功夫,违背了泛型的意义

4.简单类型不能作为泛型的参数
例如:

GenericStack<int> s4 = new GenericStack<int>();

int没有基类,擦除机制是向基类擦除,所以说简单类型都不能作为泛型的参数

5.static方法当中不能用泛型类型的参数
因为static定义的方法数据 不依赖于对象 无法指定当前泛型的类型
如上GenericAlg2中的寻找最小值的方法中
采用的是静态方法,但是这里我们指定了类型
如果不指定的话:
如:

public static T findMaxVal(T[] array){
}

会报错
需要进行指定:

public static<T> T findMaxVal(T[] array) {
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值