编程人 cdmana.com

Java-泛型

Java泛型

java的泛型并不是新的特性,在JDK1.5时就有的操作,其实我们一直在使用泛型,常见的List,Map中都有它的身影

那它有什么用处呢?以我们最常用的ArrayList为例

public void test1() {
    ArrayList arrayList=new ArrayList();
    arrayList.add("张三");
    arrayList.add("李四");
    arrayList.add("王五");

    for (Object o : arrayList) {
        String name= (String) o;
        System.out.println(name);
    }
}

来看它的get方法返回类型

我们都知道,List中可以存储数据,那么上面的代码看起来没有问题,运行也没有问题

但是,如果我们往list添加个int类型的数据,那么在下面强制转换输出时,就会报出ClassCastException异常

原因也很简单,我们不能把一个int类型的变量强转为String类型,那么问题也很明显了,List的安全性

有解决方法吗,当然有,对于每个类型单独创建对应的List,每个List中只能存放对应的类型,这样虽然能解决问题,但是重复了大量相同代码,重复造轮子

所以在JDK1.5的时候,出现了一个新特性 : 泛型(genericity)

程序不能确定运行时的类型,但是使用Object来表示类型又太过笼统

比如北京大学旁边的理发店,理发店办活动,北京大学的学生来理发有优惠,那么理发店如何来指定那些人有优惠呢?

人类有优惠?范围太大.

具体的某个人有优惠?那么理发店把北京大学所有人名打印贴在墙上?这又太过于具体.

所以JDK想指定一些不确定的类型,但是又不想太过于笼统,这就使用到了泛型

泛型本质是将类型参数化,通过传入的类型来判断当前运行的类型,而泛型又分为泛型类、泛型接口、泛型方法

我们以常见的ArrayList为例:

public class ArrayList<E> ...{
     public boolean add(E e) {...}
     public E get(int index) {...}
}

我们可以看到,在类名后面添加了一对尖括号<>里面有一个大写字母E 这就表示ArrayList为一个泛型类,括号中可以写多个泛型

在实例化时可以通过new ArrayList<>后面的尖括号中,来添加这个List中使用的类型

ArrayList<String> arrayList=new ArrayList<String>();

很明显,当我们设置完List的使用类型后,我们add Int类型的方法出现了异常

idea提示我们所需的类型是一个String类型,而我们提供的是一个int类型,这也说明了泛型具有检查的类型的功能

在JDK1.7之后,如果接收类型中显式设置了类型,那么实例化对象可以不用写具体的类型,只需要写一对尖括号即可

ArrayList<String> arrayList=new ArrayList<>();

那么设置完泛型后,我们在来看get方法的返回类型

这里可以看到,在我们设置完List的泛型后,返回的类型已经为具体的String类了

那么它是如何实现动态的类型变化的呢?

我们来自己创建一个泛型类,一切就都知道了

医院类

public class Hospital<T> {
    
    /**
     * 输出动物的信息方法
     * @param t
     */
    public void print(T t){
	System.out.println(t);
    }
    
    /**
     * 给宠物打疫苗方法
     * @return T
     */
    public T vaccine(T t){
        System.out.println("给宠物打疫苗");
        return t;
    }
}

猫类

public class Cat {
    
    private int age;
    private String name;
    
    //省略构造,get,set,toString方法
}

当然,上面这个例子可能不是太好,这种情况完全可以抽出父类Animal类进行多态的实现,但是我们现在仅仅为了学习泛型

那么上面医院类Hospital的< T > 以及下面两个方法中的T是什么意思呢?

我们可以想象为,当实例化Hospital医院类时候,通过new Hospital< Cat>(); 将猫Cat类传入到Hospital类中,那么在这个已经实例化的Hospital对象中,所有的T都代表为传入的Cat类,简单点说就是,传入啥类型T就是啥类型

那么T是java的关键字吗,并不是,我们可以任意起名,就像和给变量起名一样,但是泛型有些规范我们要尽量遵守

java泛型字母代表意思

E Element (在集合中使用,因为集合中存放的是元素)
T Type(Java 类)
K Key(键)
V Value(值)
N Number(数值类型)
public void test1() {
    Hospital<Cat> catHospital = new Hospital<>();
    catHospital.print(new Cat(5,"小花"));
}

我们可以看到,当设置完泛型后,我们可以动态实现对于一个类的不同实现

继承关系

声明泛型的类和普通的类没有太大的区别,同样可以使用继承

可能会出现子类保留,指定,新增泛型的情况

对于上面这几种情况直接概况一下

  • 如果子类没有保留父类泛型,那么父类泛型默认为Object
  • 如果指定父类泛型的类型,那么父类泛型类型为指定的类型
  • 如果子类保留了父类类型,那么泛型类型就为子类实例化时设置的类型

什么意思呢?我们来看

这种情况在实例化Son对象时,设置AB的泛型类型,那么Son中和Father中的类型就会是设置的类型

比如设置为String,Integer,那么子类和父类中的泛型类型都为String和Integer

//父类
public class Father<A,B> {
}
//子类
public class Son<A,B> extends Father<A,B>{
    public void print(A a,B b){
        System.out.println("Son-A的类型:"+a.getClass());
        System.out.println("Son-B的类型:"+b.getClass());
    }
}
//测试
public void test1() {
    Son<String, Integer> son = new Son<>();
    son.print("",1);
}
//结果
Son-A的类型:class java.lang.String
Son-B的类型:class java.lang.Integer

除了子类保留父类,还可以直接指定父类泛型类型

//父类
public class Father<A,B> {
}
//子类        在这里直接设置了父类泛型类型  ↓
public class Son<A,B> extends Father<Integer,B>{
    public void print(A a,B b){
        System.out.println("Son-A的类型:"+a.getClass());
        System.out.println("Son-B的类型:"+b.getClass());
    }
}

那么在实例化子类时即使设置new Son<String,String>(); 那么父类的两个泛型类型只会是Integer和String,因为泛型A已经在子类中指定了

还有一种没有保留父类泛型类型

//不保留也不指定父类泛型类型            ↓ 去掉这里的 <A,B>
public class Son<A,B> extends Father{
    public void print(A a,B b){
        System.out.println("Son-A的类型:"+a.getClass());
        System.out.println("Son-B的类型:"+b.getClass());
    }
}

如果不保留父类泛型类型,也不指定父类泛型类型,那么泛型类型默认为Object相当于extends Father<Object,Object>

如果部分保留,除非子类需要的话,那么子类只需要声明保留的泛型即可

泛型接口 和泛型类继承一致,这里不再赘述

泛型方法

泛型方法不一定存在泛型类或泛型接口中,只对方法而言,传入的参数类型不确定

例如下面这个getMax方法,如果是Integer返回最大值,如果是String根据ASII码返回最大值,假设传入List中的类型为String或Integer

public <E> E getMax(List<E> es){
    if(es==null || es.size()==0)
        return null;
    E e = es.get(0);
    if (e instanceof Integer) {
        //获取最大值操作...
    }
    if(e instanceof String){
        //获取最大值操作...
    }
    return null;
}

我们可以看到,传入的参数并不确定,这种方法可以称之为泛型方法,泛型方法怎么声明呢?

//在返回类型前面加上<X> X可以换成任何字母,但是尽量符合规范
//那么在返回类型,传入参数类型,方法体中都可以使用这个定义泛型
public <E> List<E> get(E[] arrays){
 
}

?通配符

?表示未知类型,

例如List<?> 那么任何类型的List都可以赋值给这个List,但是注意,并不能直接往这个List中添加数据

也有个例外,添加null可以

public void test1() {
    List<?> list=new ArrayList<>();
    //    list.add("小明"); 报错 需要的类型为 ?    而我们传入类型为String
    //但是下面这些赋值都没有问题
    List<String> stringList = new ArrayList<>();
    List<Integer> integerList = new ArrayList<>();
    list = stringList;
    list = integerList;
}

List<?> 的get方法,获取的类型是Object

通配符集合可以作为方法的形参,来接收List集合中不确定的类型,例如下面这个例子

//这里使用迭代器来进行遍历
public void print(List<?> list){
    ListIterator<?> listIterator = list.listIterator();
    while (listIterator.hasNext()){
        Object next = listIterator.next();
        System.out.println(next);
    }
}

这里有需要注意的点,例如X类为Y类的父类,泛型genericity 简称G G< Y >并不能直接赋值给G< X >,例如下面这个例子

String为Object的子类,为什么不能赋值给Object的集合呢?

这里我们需要将它们想象成两个类,虽然在运行中只是一个类,我们可以想象为一个类只为Object提供服务,另一个只为String提供服务,虽然Object和String有继承关系,但是它们两个集合并没有任何关系,只是功能相同而已并不存在List< String>继承List< Object>的关系

就像请俩技师一个给儿子捏脚一个给爸爸捏脚,虽然儿子和爸爸有继承关系,但是两个技师没有任何关系(假设)

通配符限制条件

我们前面看到了使用?通配符可以传入任何类型,但是使用Object作为形参一样可以完成,所以为了安全性,通配符还提供了一系列限制条件

<? extends xxx><? super xxx>

先来说<? extends xxx> 可以匹配继承于xxx或者xxx类型 小于等于该类的类型

我们使用常用的List来测试,创建3个类,类中什么都没有,仅是来测试通配符

Animal动物类为父类->Cat猫类继承Animal类-> XiaoHuaCat小花猫类继承Cat类

@Test
public void test1() {
    //设置通配符限制条件
    List<? extends Cat> list = new ArrayList<>();
    List<Cat> cats = new ArrayList<>();
    List<XiaoHuaCat> xiaoHuaCats = new ArrayList<>();
    List<Animal> animals = new ArrayList<>();

    list = cats;
    list = xiaoHuaCats;
    //这条赋值语句报错,因为不是继承Cat或Cat类型
    list = animals;
}

那么它get方法返回的类型为Cat类,因为这已经是在这个集合中最大的父类了,它在这个集合中不可能再有父类了

再来说<? extends xxx> 可以匹配xxx继承的类类型或者xxx类型 大于等于该类的类型

@Test
public void test1() {
    List<? super Cat> list = new ArrayList<>();
    List<Cat> cats = new ArrayList<>();
    List<Animal> animals = new ArrayList<>();
    List<XiaoHuaCat> xiaoHuaCats = new ArrayList<>();

    list = cats;
    list = animals;
    //这条语句会报错,因为最小的类型就是Cat类了,而XiaoHuaCat是继承于Cat的
    list = xiaoHuaCats;
}

它的get方法返回类型为Object ,因为无论哪个类,它的祖先类一定是Obejct类,因为父类对象引用子类对象是允许的,所以get的是所有类的父类

本文仅个人理解,如果有不对的地方欢迎评论指出或私信,谢谢٩(๑>◡<๑)۶

Scroll to Top