我与夏风皆过客,惟愿博肖揽星河!

xzwyb

导航

泛型

1、泛型与Object对象造型的优势

1.1泛型本质

    • 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

    • 这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法

    • 泛型是JavaSE1.5的新特性

1.2参数化类型

泛型类型的“类型参数”,它们也被称为参数化类型(parameterizedtype)或参量多态(parametricpolymorphism)

1.3泛型的好处

    • Java语言引入泛型的好处是安全简单。可以将运行时类型相关错误提前到编译时错误

      说明:

      • 泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率.

      • 在JavaSE1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”(Java中的所有类型都是Object类的子类),“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

1.4泛型使用对比

    • 无泛型
    • 查看代码
      public class SimpleGen {
          private Object obj;
          public SimpleGen(Object obj) {
              this.obj = obj;
          }
          public Object getObj() {
              return obj;
          }
          public void setObj(Object obj) {
              this.obj = obj;
          }
          public String showType() {
              //反射,obj的对象类型名称
              return obj.getClass().getName();
          }
          public static void main(String[] args) {
              SimpleGen strGen1 = new SimpleGen("张三");
              System.out.println("strGen1的属性obj的类型:" + strGen1.showType());
              String obj1 = (String) strGen1.getObj();
              System.out.println("strGen1的obj的值为:" + obj1);
              SimpleGen strGen2 = new SimpleGen(123);
              System.out.println("strGen2的属性obj的类型:" + strGen2.showType());
              //Double obj2 = (Double) strGen2.getObj();
              //编译正常,运行报错java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Double
              Integer obj2 = (Integer) strGen2.getObj();
              System.out.println("strGen2的obj的值为:" + obj2);
          }
      }

    • 有泛型
    • 查看代码
      public class GenericSimpleGen<T> {
          private T obj;
          public GenericSimpleGen(T obj) {
              this.obj = obj;
          }
          public T getObj() {
              return obj;
          }
          public void setObj(T obj) {
              this.obj = obj;
          }
          public String showType(){
              return obj.getClass().getName();
          }
          public static void main(String[] args) {
              GenericSimpleGen<String> genericSimpleGen1=new GenericSimpleGen("肖皇博帝");
              System.out.println(genericSimpleGen1.showType());
              String strObj1 = genericSimpleGen1.getObj();
              System.out.println(strObj1);
              System.out.println("---------------------------");
              GenericSimpleGen<Integer> genericSimpleGen2=new GenericSimpleGen(135);
              System.out.println(genericSimpleGen2.showType());
              Integer strObj2 = genericSimpleGen2.getObj();
              System.out.println(strObj2);
          }
      }

2、泛型类

实例化类的时候指明泛型的具体类型

泛型的类型参数只能是引用类型,不能是基本数据类型。

2.1语法

权限修饰符 class 类名<泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{

     private 泛型标识  /*(成员变量类型)*/ var;

}

2.2使用泛型

ArrayList<Integer> list = new ArrayList<>(); 此时,变量E的值就是Integer类型,那么我们的类型就可以理解为Integer

ArrayList<String> list = new ArrayList<>(); 此时,变量E的值就是String类型,那么我们的类型就可以理解为String

2.3例子

    • /**
       * 泛型类型只是标识,定义的时候没有确定具体类型,使用者确定具体类型
       * @param <MVP> 第一个泛型类型
       * @param <MVC> 第二个泛型类型
       * @param <T>   第三个泛型类型
       *              多个泛型之间用逗号隔开
       *              一般习惯泛型类型用T  V  K  E 来表示
       */
      //<可任意写>
      public class Generic<MVP,MVC,T>{
          private MVP name;
          private MVC age;
          private T result;
          
          public Generic(MVP name, MVC age, T result) {
              this.name = name;
              this.age = age;
              this.result = result;
          }
          public MVP getName() {return name;}
          public void setName(MVP name) {this.name = name;}
          public MVC getAge() {return age;}
          public void setAge(MVC age) {this.age = age;}
          public T getResult() {return result;}
          public void setResult(T result) {this.result = result;}
          public static void main(String[] args) {
              //使用泛型,泛型的类型是引用类型和String,基本类型不能用
              Generic<String,Integer,Boolean> generic1=new Generic<>("琑儿",6,false);
              String name=generic1.getName();
              Integer age = generic1.getAge();
              Boolean result = generic1.getResult();
              System.out.println("name="+name+",age="+age+",result="+result);
          }
      }

3、泛型方法

在调用方法的时候指明泛型的具体类型

3.1语法

【访问权限修饰符】【修饰符】<T,E...> 返回值类型  方法名  (形参列表){

方法体......

}

3.2注意事项

(1)public 返回值中间的<T,E..>可以理解为声明此方法为泛型方法;

(2)与泛型类的定义一样,此处T可以随便写为任意标识,T,E,K,V等形式的参数常于表示泛型

(3)在泛型类中使用泛型的方法无需再声明泛型

(4)是否拥有泛型方法,与其所在的类是否泛型类没有关系。要定义泛型方法,只需将泛型参数列表置于返回值前。

3.3例子

    • public class GenericMethod {
          public <T> void show1(T t) {
              System.out.println(t.getClass().getName());
          }
          //泛型方法中的泛型类型,可以用在形参列表中,也可以用在返回值中
          public <T, E> T show2(E e, T t) {
              return (T) e;
          }
          public static void main(String[] args) {
              GenericMethod genericMethod = new GenericMethod();
              //泛型方法在使用中,不能指定类型,实参已经有了具体类型,所以方法的泛型类型也就确定了
              genericMethod.show1("jack");/*输出java.lang.String*/
              Integer o = genericMethod.show2(123, 345);//默认返回Object
              System.out.println(o.getClass().getName());/*输出java.lang.Integer*/
              System.out.println(o);/*输出123*/
          }
      }

4、泛型接口

4.1语法

public interface MyGenericInterface<E>{
    void add(E e);
    E getE();  
}

4.2说明

泛型接口的声明同泛型类的声明类似,在接口名之后,用尖括号将所有类型参数括起来

4.3使用格式

4.3.1定义实现类时确定泛型的类型 

      • public class MyGenericImpl1 implements GenericInterface<String> {
            @Override
            public void add(String s) {
                System.out.println("add添加成功");
            }
            @Override
            public String getE() {
                return "hello world";
            }
            public static void main(String[] args) {
                MyGenericImpl1 myGenericImpl1 = new MyGenericImpl1();
                myGenericImpl1.add("123");/*add添加成功*/
                System.out.println(myGenericImpl1.getE());/*hello world*/
            }
        }

4.3.2始终不确定泛型的类型,直到创建对象时,确定泛型的类型

      • public class MyGenericImpl2<T> implements GenericInterface<T> {
            @Override
            public void add(T t) {
                System.out.println("add方法调用");
            }
            @Override
            public T getE() {
                return null;
            }
            public static void main(String[] args) {
                MyGenericImpl2<String> myGenericImpl2 = new MyGenericImpl2();
                myGenericImpl2.add("adafa");/*add方法调用*/
                System.out.println(myGenericImpl2.getE());/*null*/
            }
        }
    • 接口
      public interface GenericInterface<E> {
          void add(E e);
          E getE();
      }

5、泛型不支持协变

5.1数组可协变

xie1第一行创建了一个 Apple 数组并把它赋给 Fruit 数组的引用。 这是有意义的,Apple 是 Fruit 的子类,一个 Apple 对象也是一种 Fruit 对象, 所以一个 Apple 数组也是一种 Fruit 的数组。这称作数组的协变

5.2泛型不支持协变

泛型设计的目的之一是要使运行时期的类型相关错误在编译期就能发现

当涉及到泛型时,尽管 Apple 是 Fruit 的子类型,但是 ArrayList <Apple>不是 ArrayList<Fruit> 的子类型,泛型不支持协变

  • public class ArrayTest1 {
        public static void main(String[] args) {
            xie1();
        }
        private static void xie1() {
            Fruit[] fruits = new Apple[10];//数组协变,数组的运行时类型是Apple[]
            fruits[0] = new Apple();//fruits数组实际上是Apple数组,存储Apple对象获Apple的子类对象都可以
            fruits[1] = new HongFuShi();
            //编译通过运行报错,存储异常  java.lang.ArrayStoreException: com.bx.generic.Fruit
            //fruits数组实际上是Apple的数组,运行时添加Fruits对象是不允许的
            //fruits[2]=new Fruit();
            //Apple a=new Fruit();//报错,向下转型要强转
            fruits[3] = new Orange();//编译通过运行报错,  java.lang.ArrayStoreException: com.bx.generic.Orange
        }
        private static void xie2() {
            /*ArrayList<Fruit> fruits=new ArrayList<Apple>();//编译不通过,不允许协变*/
        }
    }
    class Fruit {}
    class Apple extends Fruit {}
    class HongFuShi extends Apple {}
    class Orange extends Fruit {}

6、泛型通配符

不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。?代表可以接收任意类型

6.1通配符基本使用

    1. 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是 什么,它包含的都是Object
    2. 写入list中的元素时,不行。因为我们不知道的元素类型,我们不能向其中添加对象。唯一的例外是null,它是所有类型的成员
    3. 调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object
      • import java.util.ArrayList;
        import java.util.List;
        public class Test3 {
            public static void main(String[] args) {
                //List<Number> list=new ArrayList<Integer>();//不支持协变
                List<?> list = new ArrayList<Integer>();//?表示可以接受任何类型
                list = new ArrayList<Integer>();
                list = new ArrayList<String>();
        
                List<?> list1 = new ArrayList<>();
                //?表示任何类型,未知,如果?表示Integer,add(3)允许的话就能通过,但?表示String就不可,
                //出现了解释的矛盾,javac编译不知道?是什么类型,所以不让添加
                //list1.add(3);
                list1.add(null);//null可以添加进去,null是所有引用类型的值都可以赋值为null,除了null的任何类型都不能添加
                read(list1);/*输出null*/
                System.out.println("*******************************");
                List<String> list2 = new ArrayList<>();
                list2.add("jack");
                //list2.add(3);//需要String类型
                read(list2);/*输出jack*/
                System.out.println("*******************************");
                List<Integer> list3 = new ArrayList<>();
                list3.add(1);
                list3.add(100);
                read(list3);/*1  \n   100*/
            }
            static void read(List<?> list) {
                for (int i = 0; i < list.size(); i++) {
                    Object o = list.get(i);
                    System.out.println(o);
                }
            }
        }

6.2通配符高级使用----受限类型

JAVA的泛型中可以指定一个泛型的上限下限

现已知Object类,String 类,Number类,Integer类,其中Number是 Integer的父类

    • import java.util.ArrayList;
      import java.util.List;
      public class ShangXiaXianTest {
          public static void main(String[] args) {
              List<Integer> list1 = new ArrayList<>();
              List<String> list2 = new ArrayList<>();
              List<Number> list3 = new ArrayList<>();
              List<Object> list4 = new ArrayList<>();
              getElement1(list1);
              //getElement1(list2);//Integer的子类没有String类
              getElement1(list3);
              //getElement1(list4);//Integer的子类没有Object类
              //getElement2(list1);//Number的父类没有Integer类
              //getElement2(list2);//Number的父类没有String类
              getElement2(list3);
              getElement2(list4);
          }
          /*泛型上限 List集合泛型的类型   是Number或Number的子类*/
          private static void getElement1(List<? extends Number> list) {}
          /*泛型下限 List集合泛型的类型   是Number或Number的父类(包括Object)*/
          private static void getElement2(List<? super Number> list) {}
      }

6.3泛型上下限

    • public class Person<T> {
          private T val;
          public Person(T val) {
              this.val = val;
          }
          public T getVal() {
              return val;
          }
          public void setVal(T val) {
              this.val = val;
          }
          //<? extends Number>表示Person类型,Person的泛型是Number或Number的子类
          public static void show(Person<? extends Number> p) {
              System.out.println(p);
          }
          //<? super Number>表示Person类型,Person的泛型是Number或Number的父类
          public static void show1(Person<? super Number> p) {
              System.out.println(p);
          }
          @Override
          public String toString() {
              return "Person{" + "val=" + val + '}';
          }
          public static void main(String[] args) {
              Person<Integer> p1 = new Person<>(123);
              Person<Double> p2 = new Person<>(12.3);
              Person<String> p3 = new Person<>("abc");
              Person<Object> p4 = new Person<>(new Object());
              show(p1);
              show(p2);
              //show(p3);
              //show()里面的参数1、Person类型,2、Person类型的泛型是Number或Number的子类
              //p3确实是Person类型,但p3的Person的泛型是String,不是Number或Number类型的子类,所以报错了
              //show1(p1);//Integer,Double不是number或Number的父类
              //show1(p2);
              //show1(p3);
              show1(p4);//Object是Number的父类
          }
      }

7、泛型擦除

泛型是java1.5版本才引进的概念,在这之前没有泛型,但是,泛型代码能够很好的和之前版本的代码兼容,原因是泛型只存在代码编译阶段,在进入JVM 之前,泛型相关的信息会被擦除掉,我们称之为--类型擦除 

7.1证明Java类型的类型擦除

    • List<Integer> list1=new ArrayList<Integer>();
      List<String> list2=new ArrayList<String>();
      System.out.println(list1.getClass()==list2.getClass());
      /*true*/

定义了两个List集合,一个存储整数,一个存储字符串,通过list1对象和list2对象的 getClass()方法获取他们的类的信息,结果为true。说明泛型类型String和 Integer都被擦除掉了,只剩下原始类型。

7.2类型擦除后保留的原始类型

原始类型就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

7.2.1原始类型

      • public class Pair<T> {
            private T value;
            public T getValue() {
                return value;
            }
            public void setValue(T value) {
                this.value = value;
            }
            public static void main(String[] args) {
                //使用的时候没有给定类型,类型为Object
                Pair p = new Pair();
                p.setValue(null);
            }
        }

7.2.2如果类型变量有限定,那么原始类型就用第一个边界的类型替换

      • import java.util.Comparator;
        public class Pair1<T extends Comparator> {
            private T value;
            public T getValue() {
                return value;
            }
            public void setValue(T value) {
                this.value = value;
            }
            public static void main(String[] args) {
                //使用的时候没有给定类型,类型为Comparator
                Pair1 p = new Pair1();
                p.setValue(null);
            }
        }

7.3类型擦除的限制

7.3.1无法利用同一泛型类的实例区分方法签名

      • //两个方法不能重载,编译报错,同种类型,擦除后字节码相同都是List,因此不能用于区分方法签名
        public void test(List<String> list){}
        public void test(List<Integer> list){}

7.3.1泛型类的静态变量是共享的

      • public class StaticTest {
            public static void main(String[] args) {
                GT<Integer> gti = new GT();
                gti.var = 1;
                GT<Integer> gts = new GT();
                gts.var = 2;//赋值给var,泛型类的静态变量是共享的
                System.out.println(gti.var);//输出2
            }
        }
        class GT<T> {
            public static int var = 0;//静态变量
            public void nothing(T t) {
            }
        }

7.4类型擦除的特征

    • 所有泛型类的类型参数在编译时都会被擦除,虚拟机运行时中没有泛型,只有普通类和普通方法,从这一点上来说,Java中的泛型从某种程度上是一种语法糖

    • Java泛型不支持基本类型

    • 在泛型代码内部,无法获得任何有关泛型参数类型的信息,如果传入的类型参数为T,那么在泛型代码内部你不知道T有什么方法,属性,关于T的一切信息都丢失了

    • 创建泛型对象时请指明类型,让编译器尽早的做参数检查

    • 不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你

    • Java的泛型类型不能用于new构建对象(也不能用于初始化数组).泛型不能用于显性地引用运行时类型的操作之中,例如转型,instanceof和new操作(包括new一个对象,new一个数组),因为所有关于参数的类型信息都在运行时丢失了,所以任何在运行时需要获取类型信息的操作都无法进行工作

posted on 2022-04-11 20:12  B2328X  阅读(33)  评论(0编辑  收藏  举报