JAVA基础知识|泛型

一、什么是泛型?

泛型,即“参数化类型”。

比如定义一个变量A,我们可以通过如下方式将这个变量定义为字符串类型或者整形。

String A;
Integer A;

当然这是在变量类型已知的情况下,如果有一种情况,我们在定义变量的时候不知道以后会需要什么类型,或者说我们需要兼容各种类型的时候,又该如何定义呢?

鉴于以上这种情况,我们可以引入“泛型”,将String和Integer类型进行参数化。在使用的时候,再传入具体的参数类型。

泛型的本质是为了参数化类型(通过传入的不同类型来决定形参的具体类型)。也就是说在泛型使用过程中,数据的类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

 二、泛型类

Computer类,这个类中包含一个属性t,类型为String。

    public class Computer {

        private String t;

        public void set(String t) {
            this.t = t;
        }

        public String get() {
            return this.t;
        }
    }

泛型类Computer

    //这里的"T"并不是固定写法,也可以用V或M等字符
    public class Computer<T> {

        private T t;

        public void set(T t) {
            this.t = t;
        }

        public T get() {
            return this.t;
        }
    }

    //可以传入多种泛型参数
    public class Computer<T, V> {

        private T t;
        private V v;

        public void set(T t, V v) {
            this.t = t;
            this.v = v;
        }

        public T getT() {
            return this.t;
        }

        public V getV() {
            return this.v;
        }
    }

 

定义泛型类的好处就是,我们可以在需要的时候,再去指定属性t的类型,增强了通用性。

        Computer<String> computer1= new Computer<String>();
        Computer<Integer> computer2= new Computer<Integer>();
        Computer<Double> computer3= new Computer<Double>();

三、泛型接口

    //定义接口Calculatable
public interface Calculatable<T> { T execute(); }
//传入String类型的实参
public class Computer implements Calculatable<String> { @Override public String execute() { return "ok"; } }
//传入Integer类型的实参
public class Calculator implements Calculatable<Integer> { @Override public Integer execute() { return 100; } }
//未传入具体实参,继续抛出,由下层传入
public class Phone<V> implements Calculatable<V> { private V v; @Override public V execute() { return this.v; } }

 四、泛型方法

    public class Computer<T> {

        private T t;

        //不是泛型方法
        public void set(T t) {
            this.t = t;
        }

        //不是泛型方法
        public T get() {
            return this.t;
        }

        //泛型方法
        //首先public与返回值类型之间的<V>必不可少,只有声明了<V>的方法才是泛型方法
        //可以声明多个泛型,如 public <V,M> genericMethod(V v,M m)
        public <V> V genericMethod(V v){
            return v;
        }
    }
        Computer<String> computer = new Computer<String>();
        Integer v= 100;
        System.out.println(computer.genericMethod(v).getClass().toString());

输出结果:

class java.lang.Integer

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。

泛型方法,可以通过传入的实参,判断V的具体类型。

五、静态方法与泛型

静态方法不可以使用类中定义的泛型,如果静态方法想要使用泛型,需要将自身声明为泛型方法。

public class Computer<T> {    
    public static <V> void get(V v){
        //T t;报错,不能使用类中定义的泛型
    }
}

六、通配符及上下边界

举一个简单的例子

public class Fruit {
    private String name;

    public Fruit(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Apple extends Fruit {
    public Apple(String name) {
        super(name);
    }
}
public class GenericHolder<T> {
    private T obj;

    public GenericHolder() {
    }

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

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
}
public class AppTest extends TestCase {

    @Test
    public void test() {

        //这是一个贴了水果标签的袋子
        GenericHolder<Fruit> fruitHolder = new GenericHolder<Fruit>();
        //这是一个贴了苹果标签的袋子
        GenericHolder<Apple> appHolder = new GenericHolder<Apple>();
        //这是一个水果
        Fruit fruit = new Fruit("水果");
        //这是一个苹果
        Apple apple = new Apple("苹果");

        //现在我们把水果放进去
        fruitHolder.setObj(fruit);
        //调用一下吃水果的方法
        eatFruit(fruitHolder);

        //把苹果放到水果的袋子中
        fruitHolder.setObj(apple);
        //调用一下吃水果的方法
        eatFruit(fruitHolder);

        //放苹果的标签,自然只能放苹果
        appHolder.setObj(apple);
        // eatFruit(appHolder);//报错,这时候无法把appHolder传入eatFruit,因为GenericHolder<Fruit> 和 GenericHolder<Apple>是两种不同的类型
    }

    public static void eatFruit(GenericHolder<Fruit> fruitHolder){
        System.out.println("我正在吃 " + fruitHolder.getObj().getName());
    }
}

执行结果:

我正在吃 水果
我正在吃 苹果

GenericHolder<Fruit> 和 GenericHolder<Apple>是两种不同的类型,所以无法通过编译。

从Java继承的角度上可以分析:

 苹果 IS-A 水果

装苹果的袋子 NOT-IS-A 装水果的袋子

么问题来了,如果我想让eatFruit方法能同时处理GenericHolder<Fruit> 和 GenericHolder<Apple>两种类型怎么办?而且这也是很合理的需求,毕竟Apple是Fruit的子类,能吃水果,为啥不能吃苹果???如果要把这个方法重载一次,未免也有些小题大做了。

这个时候,泛型的边界符就有它的用武之地了。我们先来看效果:

public class AppTest extends TestCase {

    @Test
    public void test() {

        //这是一个贴了水果标签的袋子
        GenericHolder<Fruit> fruitHolder = new GenericHolder<Fruit>();
        //这是一个贴了苹果标签的袋子
        GenericHolder<Apple> appHolder = new GenericHolder<Apple>();
        //这是一个水果
        Fruit fruit = new Fruit("水果");
        //这是一个苹果
        Apple apple = new Apple("苹果");

        //现在我们把水果放进去
        fruitHolder.setObj(fruit);
        //调用一下吃水果的方法
        eatFruit(fruitHolder);

        //放苹果的标签,自然只能放苹果
        appHolder.setObj(apple);
        // 这时候可以顺利把appHolder 传入eatFruit
        eatFruit(appHolder);
    }

    public static void eatFruit(GenericHolder<? extends Fruit> fruitHolder){
        System.out.println("我正在吃 " + fruitHolder.getObj().getName());
Fruit fruit1 = new Fruit("水果1");
//fruitHolder.setObj(fruit1);//报错,不能存入新的内容。Error:(35, 28) java: 不兼容的类型: Fruit无法转换为capture#1, 共 ? extends Fruit
} }

运行结果:

我正在吃 水果
我正在吃 苹果

这就是泛型的边界符,用<? extends Fruit>的形式表示。边界符的意思,自然就是定义一个边界,这里用?表示传入的泛型类型不是固定类型,而是符合规则范围的所有类型,用extends关键字定义了一个上边界。

有上边界,自然有下边界,泛型里使用形如<? super Fruit>的方式使用下边界。

这两种方式基本上解决了我们之前的问题,但是同时,也有一定的限制(PECS原则)。

1.上界<? extends T>不能往里存,只能往外取

不要太疑惑,其实很好理解,因为编译器只知道容器里的是Fruit或者Fruit的子类,但不知道它具体是什么类型,所以存的时候,无法判断是否要存入的数据的类型与容器种的类型一致,所以会拒绝set操作。

2.下界<? super T>往外取只能赋值给Object变量,不影响往里存

因为编译器只知道它是Fruit或者它的父类,这样实际上是放松了类型限制,Fruit的父类一直到Object类型的对象都可以往里存,但是取的时候,就只能当成Object对象使用了。

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

所以如果需要经常往外读,则使用<? extends T>,如果需要经常往里存,则使用<? super T>。

 如果阅读过一些Java集合类的源码,可以发现通常我们会将两者结合起来一起用,比如像下面这样:

public class Collections {
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<src.size(); i++)
            dest.set(i, src.get(i));
    }
}

 参考网址:

http://www.importnew.com/24029.html

https://blog.csdn.net/sunxianghuang/article/details/51982979

posted @ 2018-04-13 16:40  无聊的三文鸡  阅读(259)  评论(0编辑  收藏  举报