Java泛型基础

1. 为什么要使用泛型?
设想你有很多数字要进行排序,你选择把数字先放到下面的集合中
ArrayList list = new ArrayList();
你当然可以往里面加数字,但你添加字符串编译时也不报错,可是list里面有字符串显然不是你想见到的。
list.add(“hello”);
JVM虚拟机在执行排序时当然无法把数字与字符串排序,会产生一个运行时错误(运行时才能发现),这使你无法完成对数字的排序。要是能在编译时就发现错误就好了,怎么实现呢?面对这个问题jdk1.5提出的泛型发挥了作用,如下创建集合list即可。限制了集合中只能存放Integer类型的数据,此时你添加字符串编译就会报错。
ArrayList list = new ArrayList<>();
可以说,使用java泛型的动机就是在编译时检测出错误。

\1. 下面来谈谈怎样使用泛型类、泛型接口、泛型方法。一般用表示泛型,T可以是广泛的任意类型的数据。

2. 泛型类

泛型类声明看起来像非泛型类声明,除了类名后跟一个类型参数部分。我觉得类型参数部分主要是为了给类中的变量和方法传递参数的。泛型类的类型参数部分可以有一个或多个用逗号分隔的类型参数,通常大写字母表示。 它表示通用类型,只有外界传入具体类型时才能确定。

public class Generate<E>{
  private T name;
 }

我理解的泛型类就是一种可接收参数的类,它可以进行实例化。比如上面的例子,你可以传入泛型参数得到Generate或者Generate。
我理解的是Generate是类,Generate是Generate的实例对象,所以Generate和Generate是不同的。但实际上这两者都是Generate这种类型。下面的代码可以验证上述事实

public class GeneTest {
    public static void main(String[] args) {
        Generate<Integer> integerGenerate = new Generate<>();
        Generate<String> stringGenerate = new Generate<>();
        Class gene1 = integerGenerate.getClass();
        Class gene2 = stringGenerate.getClass();
        System.out.println(gene1.equals(gene2));
    }

}注意T绝对不是泛型类,它只是一个泛型参数。

3. 泛型接口

泛型接口和泛型类的关系,类似于类与接口的关系。

public interface Male<T,E>{

  public T eat();

}

4. 泛型方法

泛型方法这一概念比较抽象,下面的get和构造方法不是泛型方法。只有标记了的方法才是泛型方法。

class Generate<T>{
    private T name;

    public Generate(){

    }

    //构造器参数由外部指定
    public Generate(T name) {
        this.name = name;
    }

    public T getName( ){   //泛型方法getName的返回值类型为T由外部指定
        return this.name;
    }

    public  <T> T method1(T t){
        List<Integer> list = new ArrayList<>();
        return t;
    }

    //泛型方法
    public  <K,P> P method2(K k,P p){
        List<Integer> list = new ArrayList<>();
        return p;
    }

    //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
    //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
    public <E> void show1(E t){
        System.out.println(t.toString());
    }

    //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
    public <T> void show2(T t){
        System.out.println(t.toString());
    }
}

泛型方法中的方形标记与泛型类无关,我觉得方法中的标记<K, E>这些大写字母就代表了入参和出参的范围。它表示该方法可以接受任何类型的参数,并且可以返回任何类型的参数。非泛型类中也可以有泛型方法。

5. 泛型通配符

class Fruit{
  public void call() {
    System.out.println("这是一个水果");
  }
}

class Banana extends Fruit{
  @Override
  public void call() {
    System.out.println("这是一个香蕉");
  }
}

 

class Apple extends Fruit{
  @Override
  public void call() {
    System.out.println("这是一个苹果");
  }

}

现在我定义一个“水果盘子”,逻辑上水果盘子当然可以装苹果。但是变量Plate不能指向Plate,尽管Apple是子类。因为plate从表面看也可以装Banana,这与它实际指向的类型不一致,下面的代码会报编译错误。

Plate<Fruit> plate = new Plate<Apple>();

<? extends T>上界通配符

上面的问题可以用泛型通配符来解决。使用extends的变量fruits1 可以指向任意类型参数为T子类的泛型类。

Plate<? extends Fruit> fruits1 = new Plate<Apple>();

当你指定了一个Plate< ? extends Fruit>,add的参数也变成了“? extends Fruit”。因此编译器并不能了解这里到底需要哪种Fruit的子类型,因此他不会接受任何类型的Fruit。因此add无法执行,get可以执行。

<? super T>下届通配符

使用super的变量可以指向任意类型参数为T的父类的泛型类,?代表T的父类,不符合这一情况的会编译失败。

     Plate<? super Apple> list = new Plate<Apple>();

     Plate<? super Apple> list2 = new Plate<Fruit>();

    // Plate<? super Apple> list3 = new Plate<Banana>(); //编译失败

在这里插入图片描述

此处list可以添加元素,但是只能加Apple的子类。正是因为?代表Apple的父类,但是编译器不知道你要添加哪种Apple的父类,因此不能安全地添加。

list.setItem(new Apple());  //成功list.setItem(new Banana()); //编译失败

对于super,get返回的是Object,因为编译器不能确定列表中的是Apple的哪个子类,所以只能返回Object。

在这里插入图片描述

PECS原则

如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)

如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)

如果既要存又要取,那么就不要使用任何通配符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值