10-Java泛型

本文详细介绍了Java泛型的使用,包括为何使用泛型、基本用法(接口、类、方法)、上界和下界限定符、通配符的应用以及类型擦除的原理及其带来的问题。通过实例展示了泛型在避免代码冗余和类型安全方面的优势。

看泛型篇总结

一,为什么使用泛型

我们假设要编写一个Stack类用于存储不同的类型数据,如果不用泛型此时有两种写法:

1)定义不同的Stack类

如Integer的IntegerStack类

public class IntegerStack{
  private Integer[] arr;
  private int size;
  private int top;

  public IntegerStack(int size){
    this.arr = new Integer[size];
    this.size = size;
    this.top = 0;
  }

  public void push(Integer elem){
    if(top == size) return ;
    arr[top++] = elem;
  }

  public Integer pop(){
    if(top == 0) return null;
    return arr[--top];
  }
}

如果要存储Long则需要创建一个LongStack类

public class LongStack{
  private Long[] arr;
  private int size;
  private int top;

  public LongStack(int size){
    this.arr = new Long[size];
    this.size = size;
    this.top = 0;
  }

  public void push(Long elem){
    if(top == size) return ;
    arr[top++] = elem;
  }

  public Long pop(){
    if(top == 0) return null;
    return arr[--top];
  }
}

此时每增加一种存储的数据类型,就需要多创建一个类,会导致类型太多,且大量的重复代码。

2)抽象出统一的父类或接口

按这种方式,我们只需要创建一个类就可以存储所有的子类。比如我们创建一个Number的NumberStack,那么Short,Int,Long,Float,Double等都可以存储了

public class NumberStack{
  private Number[] arr;
  private int size;
  private int top;

  public NumberStack(int size){
    this.arr = new Number[size];
    this.size = size;
    this.top = 0;
  }

  public void push(Number elem){
    if(top == size) return ;
    arr[top++] = elem;
  }

  public Number pop(){
    if(top == 0) return null;
    return arr[--top];
  }
}

上面的如果不够或者子类不确定,可以将父类设置为Object,这样就可以存储所有的类了。

public class Stack{
  private Object[] arr;				
  private int size;
  private int top;

  public Stack(int size){
    this.arr = new Object[size];
    this.size = size;
    this.top = 0;
  }

  public void push(Object elem){
    if(top == size) return ;
    arr[top++] = elem;
  }

  public Object pop(){
    if(top == 0) return null;
    return arr[--top];
  }
}

但是此时有个问题,编译器没有办法帮我们做类型检查了,如下:

Stack stack = new Stack(3);
stack.push(3);
stack.push("34");
Integer elem = (Ingeger)stack.pop();					//编译期不会报错,运行会报错

此代码编译器并不会报错,但是在运行的时候会报错,因为栈的最上面是String,没有办法转换为Integer。

另外还有一个缺陷,从父类转换为子类是需要显式强制转换的,最后会存在大量的显式转换。

此时在jdk1.5加入了泛型,刚好继承了上面两种方式的优点,又解决了其缺陷

使用泛型实现的Stack类

public class Stack{
  private Object[] arr;		//因为类型擦除,此处需要使用Object,不使用Object那么就需要在构造器传入定义好的数组		
  private int size;
  private int top;

  public Stack(int size){
    this.arr = new Object[size];		//不在构造器中定义,那么可以改为传入,int  size, T[] arr
    this.size = size;
    this.top = 0;
  }

  public void push(E elem){
    if(top == size) return ;
    arr[top++] = elem;
  }

  public E pop(){
    if(top == 0) return null;
    return (E)arr[--top];
  }
}

3)泛型与类型参数

通过上面的可以发现,泛型本质是对类型的参数化,在类的定义中,我们可以把类型作为参数,在使用类时,我们把具体的类型传入到类型参数。

二,泛型的基本用法

1)基本用法

泛型的用法有三种:泛型接口,泛型类,泛型方法;代码如下:

上面尖括号中的T,E表示类型参数,可以是任意的大写字母,但我们一般使用T,E,K,V,N来表示各种类型参数

1,E是element的首字母,一般用来表示容器中的元素的类型参数。

2,T是type的首字母,一般用来表示非容器元素的数据类型参数。

3,K,V分别是key和value的首字母,一般用来表示键值对中键和值的类型参数。

4,N是number的首字母,一般用来表示数字类型参数。

当然一个泛型接口,类,方法中可以有多个类型参数,如HashMap:

2)上界限定符

除了上面的基本用法以外,还可以使用上界限定符,限定参数的具体取值范围。

例如<T extends Number> 限定了传入的类型参数必须是Number或Number的子类。

另外在泛型中只有extends没有implements,而extends即值继承,也指实现接口。

如<T extends Comparable<T>> 和 <T extends Closeable> 分别指传入的参数得实现接口Comparable或Closeable。

举例,设计一个泛型方法,用来比较两个类的大小,只支持实现了Comparable接口的两个类进行比较

public class Util{
  public <T extends Comparable<T>> T max(T a, T b){
    if(a.compareTo(b) >= 0)  return a;
    return b;
  }
}

3)注意项

1,泛型接口和泛型类,要求每个方法中的泛型使用相同的数据类型,只能是声明时定义的,即整个类或接口要使用相同的类型参数。

如List<String> list = new ArrayList<>();

在定义类时类型参数定义为String了,那么list的所有方法里的参数也是String了。

2,泛型方法则没有上面的要求,两个不同的方法可以完全使用不同的数据类型。

如下:如果不是泛型类或接口,可以一个方法是Number或其的子类,另一个方法则是别的的子类。

public <T extends Number> void method1(T a);
public <T extends Student> void method2(T a);

3,使用泛型方法时,前面要记得声明。如不想声明可以使用通配符 ?

三,泛型中的通配符

1)定义

除了类型参数以外还有另一种常用语法,通配符 "?"

通配符与类型参数不太一样,类型参数一般用于泛型接口,类,方法,而通配符则与Integer,Number这种具体的类型无异,用来具体化类型参数,可以看作是比较特殊的具体类型。

2)使用

通配符常用于方法中,假如某个方法传入的是泛型接口或泛型类,但我们又没法确认具体类型时,可以使用通配符。

当然上面的方法也可以使用如下泛型方法替代

3)只能使用通配符的地方

1,下界限定符 super

除了上界限定符extends外,还有下界限定符super,如:

<? super People> 指传的类型必须是People或其父类,此时只能使用通配符?

2,<? super T> 和<? extends T>

通配符可以extends或super类型参数,返过来则不行

四,泛型的类型擦除

1)定义

在编译时,编译器会对泛型做类型检查,但是当编译成字节码时,泛型中的类型参数统统会替换为其上界,比如

<T> 会替换成 Object,<T extends String> 会替换为String

2)类型擦除带来的问题:

1,另外因为类型擦除,所以泛型没有办法支持基本类型,基本类型并不继承自Object。

同时针对基本类型的方法没法使用泛型,那么就像最开始的情况,针对int,double,char等都需要单独定义其方法。

2,而因为类型擦除,无法使用new T()来创建对象,因为jvm没法知道T的具体类型,也没有办法知道其是否有无参构造器。

3,编译错误,如下因为不确定T里面有什么方法,可以通过T extends HasF来解决,因为不加时变成了Object,加了类型擦除后为Hasf,还是存在f()方法的

class HasF {
    public void f() {
        System.out.println("HasF.f()");
    }
}
public class Manipulator<T> {
    private T obj;

    public Manipulator(T obj) {
        this.obj = obj;
    }

    public void manipulate() {
        obj.f(); //无法编译 找不到符号 f()
    }

    public static void main(String[] args) {
        HasF hasF  = new HasF();
        Manipulator<HasF> manipulator = new Manipulator<>(hasF);
        manipulator.manipulate();

    }

还不错的别的方章,上面的HasF就是下面看到的

滑动验证页面

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值