• Java学习笔记(二一)——Java 泛型


    【前面的话】

         最近脸好干,掉皮,需要买点化妆品了。

         Java泛型好好学习一下。

    【定义】

    一、泛型的定义主要有以下两种:

    1. 在程序编码中一些包含类型参数的类型,也就是说泛型的参数只可以代表类,不能代表个别对象。(这是当今较常见的定义)
    2. 在程序编码中一些包含参数的。其参数可以代表类或对象等等。(现在人们大多把这称作模板)

            不论使用那个定义,泛型的参数在真正使用泛型时都必须作出指明。

    二、使用泛型的目的:

    1. 一些强类型程序语言支持泛型,其主要目的是加强类型安全及减少类转换的次数,但一些支持泛型的程序语言只能达到部份目的。
    2. 泛型程序设计(Generic programming)意味着编写的代码可以被很多不同类型的对象所重用。
    3. 是对java语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值得占位符一样。

    Java泛型的几种类型代码】

    一、不使用泛型的代码

         我们定义一个Person类,包含三个属性x,y,z。在我们开始定义地时候,我们也不知道这三个属性是用来干什么的,所以我们定义为Object类型。但是在使用的时候,我们分别对x,y,z赋予了int,double,String类型,所以在取出的时候,我们需要把这三个类型值进行强制转换。如下代码:

        1. Person.java

     1 public class Person {
     2     private Object x;
     3     private Object y;
     4     private Object z;
     5     //使用Object类型。可以转化为任何类型
     6     public Object getX() {
     7         return x;
     8     }
     9     public void setX(Object x) {
    10         this.x = x;
    11     }
    12     public Object getY() {
    13         return y;
    14     }
    15     public void setY(Object y) {
    16         this.y = y;
    17     }
    18     public Object getZ() {
    19         return z;
    20     }
    21     public void setZ(Object z) {
    22         this.z = z;
    23     }
    24 }

        2. NoGenericTest.java

     1 public class NoGenericTest {
     2     public static void main(String[]args){
     3         Person boy=new Person();
     4         boy.setX(20);
     5         boy.setY(22.2);
     6         boy.setZ("帅哥TT");
     7         //这里根据设置的不同类型的值,我们需要进行强制类型转化。
     8         int x=(Integer)boy.getX();
     9         double y=(double)boy.getY();
    10         String z=(String)boy.getZ();
    11         
    12         System.out.println(x);
    13         System.out.println(y);
    14         System.out.println(z);
    15     }
    16 }

        3. 运行结果

    1 20
    2 22.2
    3 帅哥TT

    二、使用一个类型变量泛型的代码

          我们定义一个泛型类Person,定义三个属性x,y,z,在测试类中,我们设置属性的值,并打印。

        1. Person.java

     1 public class Person<T> {
     2     private T x;
     3     private T y;
     4     private T z;
     5     public T getX() {
     6         return x;
     7     }
     8     public void setX(T x) {
     9         this.x = x;
    10     }
    11     public T getY() {
    12         return y;
    13     }
    14     public void setY(T y) {
    15         this.y = y;
    16     }
    17     public T getZ() {
    18         return z;
    19     }
    20     public void setZ(T z) {
    21         this.z = z;
    22     }
    23 }

        2. GenericTest.java

     1 public class GenericTest {
     2     public static void main(String[]args){
     3         Person boy=new Person();
     4         boy.setX(20);
     5         boy.setY(22.2);
     6         boy.setZ("帅哥TT");
     7         //不用进行类型转化
     8         System.out.println(boy.getX());
     9         System.out.println(boy.getY());
    10         System.out.println(boy.getZ());
    11     }
    12 }

        3. 运行结果

    1 20
    2 22.2
    3 帅哥TT

    三、使用两个类型变量泛型的代码

         我们定义一个泛型类Person,定义两个属性x,y,使用了两种不同的类型变量,在测试类中,我们设置属性的值,并打印。

        1. Person.java

     1 public class Person<T1,T2> {
     2     private T1 x;
     3     private T2 y;
     4     public T1 getX() {
     5         return x;
     6     }
     7     public void setX(T1 x) {
     8         this.x = x;
     9     }
    10     public T2 getY() {
    11         return y;
    12     }
    13     public void setY(T2 y) {
    14         this.y = y;
    15     }
    16 }

        2. GenericTest.java

     1 public class GenerricTest {
     2     public static void main(String[] args){
     3         Person<String,Integer> boy=new Person<String,Integer>();
     4         boy.setX("帅哥TT");
     5         boy.setY(20);
     6         System.out.println(boy.getX());
     7         System.out.println(boy.getY());
     8     }
     9 
    10 }

        3. 运行结果

    1 帅哥TT
    2 20

    四、使用泛型的继承

         我们定义一个泛型类Person,定义两个属性x,y,然后定义另一个泛型类Boy,定义属性z,Boy继承Person类,在测试类中,我们设置属性的值,并打印。

        1. Person.java

     1 public class Person<T1,T2> {
     2     private T1 x;
     3     private T2 y;
     4     public T1 getX() {
     5         return x;
     6     }
     7     public void setX(T1 x) {
     8         this.x = x;
     9     }
    10     public T2 getY() {
    11         return y;
    12     }
    13     public void setY(T2 y) {
    14         this.y = y;
    15     }
    16 }

        2. Boy

    1 public class Boy<T1,T2,T3>extends Person<T1,T2> {
    2     private T3 z;
    3     public T3 getZ() {
    4         return z;
    5     }
    6     public void setZ(T3 z) {
    7         this.z = z;
    8     }
    9 }

        3. GenericTest.java

     1 public class GenericTest {
     2     public static void main(String[] args){
     3         Boy<String,Integer,Double> boy=new Boy<String,Integer,Double>();
     4         boy.setX("帅哥TT");
     5         boy.setY(20);
     6         boy.setZ(200000.22);
     7         
     8         System.out.println(boy.getX());
     9         System.out.println(boy.getY());
    10         System.out.println(boy.getZ());
    11     }
    12 }

       4. 运行结果

    1 帅哥TT
    2 20
    3 200000.22

    五、使用泛型的接口

         我们定义一个泛型接口Person,定义两个方法,然后定义另一个泛型类Boy,实现泛型接口Person,定义属性x,y,z,在测试类中,我们设置属性的值,并打印。

        1. Person.java

    1 public interface Person<T1,T2> {
    2     public T1 getX();
    3     public T2 getY();
    4 }

        2. Boy

     1 public class Boy<T1,T2,T3>implements Person<T1,T2> {
     2     private T1 x;
     3     private T2 y;
     4     private T3 z;
     5     public T1 getX() {
     6         return x;
     7     }
     8     public void setX(T1 x) {
     9         this.x = x;
    10     }
    11     public T2 getY() {
    12         return y;
    13     }
    14     public void setY(T2 y) {
    15         this.y = y;
    16     }
    17     public T3 getZ() {
    18         return z;
    19     }
    20     public void setZ(T3 z) {
    21         this.z = z;
    22     }
    23 
    24 }

        3. GenericTest.java

     1 public class GenericTest {
     2     public static void main(String[] args){
     3         Boy<String,Integer,Double> boy=new Boy<String,Integer,Double>();
     4         boy.setX("帅哥TT");
     5         boy.setY(20);
     6         boy.setZ(200000.22);
     7         System.out.println(boy.getX());
     8         System.out.println(boy.getY());
     9         System.out.println(boy.getZ());
    10     }
    11 }

        4. 运行结果

    1 帅哥TT
    2 20
    3 200000.22

    六、使用泛型方法

         说明一下,定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。

          定义一个普通类Person,定义一个泛型方法,如下代码:

         1. Person.java

     1 public class Person{
     2     public static<T>T getMiddle(T[]a){
     3         return a[a.length/2];
     4     }
     5     public static void main(String [] args){
     6         String[]name={"帅哥TT","帅哥TT1","帅哥TT2"};
     7         String middle=Person.<String>getMiddle(name);
     8         System.out.println(middle);
     9         
    10         Integer[]num={20,22,25};
    11         Integer middle1=Person.<Integer>getMiddle(num);
    12         System.out.println(middle1);
    13         
    14         Double[]num1={20.0,22.2,25.5};
    15         Double middle2=Person.<Double>getMiddle(num1);
    16         System.out.println(middle2);
    17     }
    18 }

        2. 运行结果

    1 帅哥TT1
    2 22
    3 22.2

    七、类型变量的限定

         如下代码,我们在方法min中定义了一个变量smallest类型为T,这说明了smallest可以是任何一个类的对象,我们在下面的代码中需要使用compareTo方法, 但是我们没有办法确定我们的T中含有CompareTo方法,所以我们需要对T进行限定,在代码中我们让T继承Comparable类。如下:

    1 public static<T extends Comparable>T min(T[]a)

        1. Person.java

     1 public class Person{
     2     public static<T extends Comparable>T min(T[]a){
     3         if(a==null||a.length==0){
     4             return null;
     5         }
     6         T smallest=a[0];
     7         for(int i=1;i<a.length;i++){
     8             if(smallest.compareTo(a[i])>0){
     9                 smallest=a[i];
    10             }
    11         }
    12         return smallest;
    13     }
    14     public static void main(String [] args){
    15         Integer[]num={20,25,30,10};
    16         Integer middle=Person.<Integer>min(num);
    17         System.out.println(middle);
    18     }
    19 }

        2. 运行结果

    1 10

    Java泛型理解】

    一、类型擦除

         正确理解泛型概念的首要前提是理解类型擦除(type erasure)。 Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。

         很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:

    1. 泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。
    2. 静态变量是被泛型类的所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。
    3. 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>和MyException<Integer>的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。

    二、最佳实践

    在使用泛型的时候可以遵循一些基本的原则,从而避免一些常见的问题。

    1. 在代码中避免泛型类和原始类型的混用。比如List<String>和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。当需要利用JDK 5之前开发的遗留代码,而不得不这么做时,也尽可能的隔离相关的代码。
    2. 在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。
    3. 泛型类最好不要同数组一块使用。你只能创建new List<?>[10]这样的数组,无法创建new List<String>[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。因此,当需要类似数组的功能时候,使用集合类即可。
    4. 不要忽视编译器给出的警告信息。

    【参考资料】

    1. Java深度历险(五)——Java泛型
    2. java核心技术

    【后面的话】

         好好学习。

    ——TT

  • 相关阅读:
    Exception handling 异常处理的本质
    一个人运气不好怎么办?做什么事能够马上改变运气?
    autoreleasing on a thread
    Tagged Pointer
    Objective-C 引用计数原理
    oc引用计数原理-引用计数相关变化
    黑箱中的 retain 和 release
    黑幕背后的Autorelease
    自动释放池的前世今生 ---- 深入解析 autoreleasepool
    Exceptions and Errors on iOS
  • 原文地址:https://www.cnblogs.com/xt0810/p/3664280.html
Copyright © 2020-2023  润新知