JAVA高级-泛型

泛型

什么是泛型

  • jdk5.0新增的特性。

  • 我们可以将中药柜作比喻,每一种中药是一个类,这个类里面有不同品种的中药。比如,大黄是一个类,但是大黄的品种有很多。

    我们要找到中药这个类,就要在中药柜上面贴上标签,里面的品种就如同一个类下的各种类型属性。

  • 中药柜上的标签,就是我们的泛型,以此推出:

    • 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型

    • 这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

    • 正如:List,这表明该List只能保存字符串类型的对象。

  • JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,

    从而可以在声明集合变量、创建集合对象时传入类型实参。

  • 前一章讲到集合的时候,我们就说过什么是数组,我们了解了数组有哪些优点

    其中,它的优点之一就是可以限制数组的类型,这加强的数据的严密性,安全性更高

    而我们在我们集中当中,数据是无序的,且可以放置多种类型,这就使得安全性减低

    但我们也必须用到集合,所以便引出了泛型,其它规范也是如此。

泛型引入

  • 当我们封装一个指定类型的集合时,需要使用到泛型
    /*问题引入*/
    @Test
    public void test1(){
        List<java.io.Serializable> list=new ArrayList<java.io.Serializable>();
        list.add(1);
        list.add(2);
        list.add(3);

        for (Object obj:list){
//            多态
            int i=(Integer)obj;
            System.out.println(i);
        }

        /*以上输出就不用多说了,但是在这里若是我们执行下面的操作*/

        list.add("a");
        for (Object obj:list){
            int i=(Integer) obj;
            System.out.println(i);
        }
//        输出:java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
//        我们可以看见它报错,也就是说我们存进去了一个字符型,造成了原来的整型数据不安全,但是我们也要执行这样的操作,这个时候泛型就起作用了。

    }

泛型举例

  • List接口使用泛型
    /*List接口泛型例子*/
    @Test
    public void test2(){
        List<Integer> list= new ArrayList<Integer>();//指定泛型为Integer
        list.add(1);
        list.add(2);
        list.add(3);

        for (Integer o:list){
//            因为泛型,不用强转。
            int i= o;
            System.out.println(i);
        }
//        list.add("tom");因为泛型,我们再放String类型的数据就会报错,保证了数据了安全性,这就是泛型的最大优点之处。

        Iterator<Integer> itr=list.iterator();
        while (itr.hasNext()){
            int i=itr.next();
            System.out.println(i);
        }
    }
  • Map接口使用泛型
    /*HashMap泛型举例*/
    @Test
    public void test3(){
        Map<String,Integer> map=new HashMap<String,Integer>();
        map.put("hyb",20);
        map.put("zyl",20);

//        Set entrySet():返回所有key-value对构成的Set集合
//        Set set = map.entrySet();
//        Iterator iterator1 = set.iterator();
//        while (iterator1.hasNext()){
//            Object obj=iterator1.next();
            map里面的静态Entry接口
//            Map.Entry entry= (Map.Entry) obj;
//            System.out.println(entry.getKey()+"->"+entry.getValue());
//        }
//        以上是我们未用泛型之前遍历key-value的方法,那我们使用了泛型之后又怎么遍历呢?
//        我们可以查看源代码,你会发现,不仅Map里使用了泛型,Set里面也使用了泛型,甚至Entry也使用了泛型,
//        所以这里便会引用泛型嵌套。
        Set<Map.Entry<String, Integer>> entry = map.entrySet();
        Iterator<Map.Entry<String, Integer>> itr = entry.iterator();
        while (itr.hasNext()){
            Map.Entry<String, Integer> i=itr.next();
            System.out.println(i);
            System.out.println(i.getKey() + "->" + i.getValue());
        }

    }

泛型使用注意点

  • 泛型必须是类,不能是基本数据类型,当涉及到基本数据类型,拿它的包装类就可以
  • 集合接口和集合类在jak5.0版本之后,都是带泛型的,其内部结构也使用到泛型,例如构造器,方法等。
  • 如何实例化没有显示表明,则是默认泛型结构

自定义泛型

泛型类,泛型接口

package com.hyb.Generic;

/**
 * @program: Generic
 * @description:泛型类
 * @author: Huang Yubin
 * @create: 2021-06-13 23:39
 **/

public class Generic <T>{//要缔造多个泛型变量,只需要用逗号隔开就可以了
    private String name;
    private int age;
    T t;//泛型变量,不知道要输入什么类型的变量

    public Generic() {
    }

    public Generic(String name, int age, T t) {
        this.name = name;
        this.age = age;
        this.t = t;
    }

    public T getT() {
        return t;
    }

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

    @Override
    public String toString() {
        return "Generic{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", t=" + t +
                '}';
    }
}
    /*实现泛型类Generic*/
    @Test
    public void AchieveGeneric(){
//        以下方式不建议,通常要指定
//        Generic generic = new Generic("hyb",20,"zyl");
        Generic<String> generic = new Generic<>("hyb",20,"zyl");
        System.out.println(generic);
//        Generic{name='hyb', age=20, t=zyl}
    }

泛型子类,子接口

  • 泛型类有多重继承方式,下列说明保留情况。
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father{
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}

class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son<A, B> extends Father{//等价于class Son extends Father{
}
// 2)具体类型
class Son2<A, B> extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2, A, B> extends Father<Integer, T2> {
}
  • 多态中,定义一个Object类型的数据,和一个String类型的数据,由于子父类关系,string是可以赋值给Object的,也就是爸爸可以容纳孩子。
  • 但是在泛型里,若是泛型类型是子父类关系整个泛型可不是子父类关系,是不能包容的。这样要使用泛型传参的时候,也不能共用一个。A不能被A赋值,我们这样想象,泛型本身就是为了在具体实现的时候规范类型的,如果这样可以被赋值,不就说明又混进去一个不一样的类型了么?但是若是改为,Object,String就可以赋值,泛型类型一样,Object和String是子父类关系,自然可以赋值。
  • 既然不能进行赋值,在开发当中,若是有多个使用泛型传参的方法,自然大大限制了效率,所以可以*引入通配符:“?”。

泛型注意事项

  • 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:

  • 泛型类的构造器如下:public GenericClass(){}。

    而下面是错误的:public GenericClass(){}

  • 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。

  • 泛型不同的引用不能相互赋值。

  • 尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有

    一个ArrayList被加载到JVM中,所以不可能将相互赋值。

  • 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价

于Object。**经验:**泛型要使用一路都用。要不用,一路都不要用。

  • 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。

  • jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();

  • 泛型的指定中不能使用基本数据类型,可以使用包装类替换。

  • 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态

    属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法(随着了类的加载而加载,而泛型类是要在实例化对象时才指定是哪个类型)中不能使用类的泛型。

  • 异常类不能是泛型

  • 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];

参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。

泛型方法

  • 我们在泛型类,泛型接口标题里举个一个泛型类的例子,那是不是说明里面的方法就是泛型方法呢?
    当然不是,泛型方法与类的类型无关,也就是说泛型方法与你的类是不是泛型无关,举例子的那个泛型类里的方法只是使用了类的泛型而已。
  • 若我们要定义泛型方法,必须声明为泛型,同时,传入参数的时候,要将参数定义为泛型。
public <E> List<E> toList(E[] a){
//    List list = new ArrayList<>();
//    for (E e:a){
//        list.add(e);
//    }
//    return list;
    return new ArrayList<>(Arrays.asList(a));
}

//public  E get(){
	//return E;
//}
public void OrderToList(){
    Generic<String> gni = new Generic<>();
    Integer[] i = new Integer[]{1,2,3};
    List<Integer> integers = gni.toList(i);
    System.out.println(integers);
}
  • 以上便是我们定义泛型方法的规则,那么我们还来思考一个问题,泛型方法可以用static修饰吗?

  • 答案是可以的。

    泛型类加载(此刻还未new对象,不知道泛型是什么)=static加载,那么使用了类的泛型的静态方法,因为不知道泛型是什么,报错。

    同样的,泛型类加载(条件如上)=泛型static方法加载(泛型方法还未说明传入什么类型),不会报错,若是此刻泛型方法里使用了类的泛型,报错。

通配符

  • ?则是通配符,在泛型继承关系里,我们知道A是不能被A赋值,但它们有一个共同父类,A

  • 所以在传参的时候可以让参数为它们的共同父类,这样,就可以实现不能赋值但又不想写多个方法体的问题。

  • 但这里又引出了一个问题,我们将他们赋值给了共同父类,那么共同父类是否可以操作它们呢?

    理论是可以的。但有条件限制。

    • 我们容易得知A的范围是负无穷到正无穷。这个范围代表了Object的范围,而像String等类都是Object的子类,所以当我们用它来读取数据的时候都是可以的只不过返回了一个Object类型而已,要想得到原来的类型,直接多态转型就可以了。但是值得注意的是,既然它是Object类型的,也就是我们具体类型是不知道的,所以我们是无法读入数据的(null例外,因为任何类型都可以是null),若果可以读入,又违背使用泛型的初衷。

    • 但是通配符是一个泛型,可以继承别人,也可以被继承,也即是A和A是允许的。但是我们要理解它们的范围,很容易知道,extend是<=后者…,super是>=后者…。这么说来,只要我们在这个范围内读入数据不就行了?答案自然是肯定的。下面给出具体例子。

      • ** (无穷小 **, Number]只允许泛型为Number及Number子类的引用调用

      • [Number , 无穷大) 只允许泛型为Number及Number父类的引用调用

      • ****只允许泛型为实现Comparable接口的实现类的引用调用

      • 子类可以赋值给父类,但是父类不能赋值给子类,必须强转。

        public  void testFather(){
            Object i=1;
            int a=2;
            a= (int) i;
            System.out.println(a);
        }
        

你可能感兴趣的:(java,笔记,java学习,java,原型模式)