美文网首页
Java泛型 --(1)泛型基础篇

Java泛型 --(1)泛型基础篇

作者: 艾剪疏 | 来源:发表于2018-08-12 12:56 被阅读7次

主要内容

(1)能够定义简单的泛型类、泛型方法
(2)知晓类型变量的限定规则和应用
(3)理解虚拟机是如何解析泛型代码
(4)明白在使用泛型的时候需要注意的一些限制
(5)理解泛型类型的继承规则
(6)学会使用通配符类型
(7)知道反射和泛型之间的关系以及如何利用反射操作泛型代码

上面(1)-(5)属于泛型的初级知识也是这篇文章的主要内容,而(6)-(7)属于泛型的高级知识,留着下次说。

1 泛型存在的意义(敲黑板)

泛型存在的意义就是:代码可以被很多不同类型的对象所重用。

例如:

List list = new ArrayList();  
list.add("CSDN_SEU_Calvin");  
list.add(100);  
for (int i = 0; i < list.size(); i++) {  
  String name = (String) list.get(i); //取出Integer时,运行时出现异常  
  System.out.println("name:" + name);  
}  

例子中向list类型集合中加入了一个字符串类型的值和一个Integer类型的值(这样是合法的,因为此时list默认的类型为Object类型)。但是在取值的时候就会出现问题,运行时会出现java.lang.ClassCastException。

泛型提供了一种解决方案:类型参数。例如下面代码:

List<String>list = new ArrayList<String>();  

这样编译器就知道存入的值是String类型的,那么在存入的值的时候就会提示类型不匹配,从而避免上述问题。使得代码具有更好的可读性和安全性

好了,在了解的泛型存在的意义之后,下面我们开始由表及里的去了解泛型。

2 定义简单泛型类

一个泛型类就是具有一个或多个类型变量的类。

public class Pair<T> 
{
   public Pair() { first = null; second = null; }
   
   public Pair(T first, T second) {
       this.first = first;  
       this.second = second; 
    }

   public T getFirst() { return first; }
   public T getSecond() { return second; }

   public void setFirst(T newValue) { first = newValue; }
   public void setSecond(T newValue) { second = newValue; }

   private T first;
   private T second;
}

在Pair类中, <T>就是类型变量,放在类名后面。泛型类可以有很多个类型变量。例如,

public class Pair<T,U>{......}

重点:类型变量指定方法的返回类型以及域和局部变量的类型。

例如上面的Pair类中,返回值类型就是<T>,域和局部变量的类型都是<T>。当然在实际运用中<T> 会用实际类型代替。

Pair<String> 

则Pair类中的返回值类型,域和局部变量的类型都是String类型的。

其实,泛型类就是普通类的工厂。

下面看如何使用这个泛型类,重点!!

public class PairTest1
{
   public static void main(String[] args)
   {
      String[] words = { "1", "2", "3", "4", "5" };
      Pair<String> mm = ArrayAlg.minmax(words);
      System.out.println("min = " + mm.getFirst());
      System.out.println("max = " + mm.getSecond());
   }
}

class ArrayAlg
{
   public static Pair<String> minmax(String[] a)
   {
      if (a == null || a.length == 0) return null;
      String min = a[0];
      String max = a[0];
      for (int i = 1; i < a.length; i++)
      {
         if (min.compareTo(a[i]) > 0) min = a[i];
         if (max.compareTo(a[i]) < 0) max = a[i];
      }
      return new Pair<String>(min, max);
   }
}

上面的PairTest1类是比较得出"1", "2", "3", "4", "5"这些字符中最大的值和最小值。

image.png

对于上面的类,有一下几点需要掌握:

1、通过利用泛型类Pair<T>完成了对于传入类型参数的比较。这个里面可以是String也可以是Double等类型。
2、该类通过指定显示的类型参数String,对参数值进行了比较,用Pair对象返回了两个String类型的结果。
3、String类实现了Comparable接口,所以可以直接调用compareTo方法对String进行比较。

3 泛型方法

如何定义一个带有类型参数的简单方法呢?

public static <T> T getMiddle(T a){
  ..................
}

首先,这是一个泛型方法,从尖括号和类型变量可以看出。注意,类型变量<T>放在修饰符(public static)的后面,在返回值类型 T 的前面。

泛型方法可以定义在普通的类中,也可以定义在泛型类中。

4 类型变量的限定

很多时候,类或方法需要对类型变量加以约束。

class ArrayAlg
{
   public static <T extends Comparable> Pair<T> minmax(T[] a) 
   {
      if (a == null || a.length == 0) return null;
      T min = a[0];
      T max = a[0];
      for (int i = 1; i < a.length; i++)
      {
         if (min.compareTo(a[i]) > 0) min = a[i];
         if (max.compareTo(a[i]) < 0) max = a[i];
      }
      return new Pair<T>(min, max);
   }
}

这里有一个问题:变量min和max类型是T,也就是说T可以是任意一个类的对象,那编译器怎么知道T所属的类有compareTo方法呢?

解决这个问题的方法就是将T限制为实现了Comparable接口的类。这就需要通过对类型变量T设置限定来实现:

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

这里T表示的是绑定类型的子类型。T和绑定类型可以是类,也可以是接口。

下面的类更详细的说明了这个问题

public class PairTest2
{
   public static void main(String[] args)
   {
      GregorianCalendar[] birthdays = 
         { 
            new GregorianCalendar(1906, Calendar.DECEMBER, 9), 
            new GregorianCalendar(1815, Calendar.DECEMBER, 10), 
            new GregorianCalendar(1903, Calendar.DECEMBER, 3),
            new GregorianCalendar(1910, Calendar.JUNE, 22), 
         };
      Pair<GregorianCalendar> mm = ArrayAlg.minmax(birthdays);
      System.out.println("min = " + mm.getFirst().getTime());
      System.out.println("max = " + mm.getSecond().getTime());
   }
}

class ArrayAlg
{
   public static <T extends Comparable> Pair<T> minmax(T[] a) 
   {
      if (a == null || a.length == 0) return null;
      T min = a[0];
      T max = a[0];
      for (int i = 1; i < a.length; i++)
      {
         if (min.compareTo(a[i]) > 0) min = a[i];
         if (max.compareTo(a[i]) < 0) max = a[i];
      }
      return new Pair<T>(min, max);
   }
}

通过指定< T extends Comparable > 表明类型参数需要是实现Comparable接口的类。从而实现了对于GregorianCalendar的比较。

5 虚拟机如何解析泛型代码

重点:虚拟机泛型对象-----所有的对象都属于普通类。

无论何时定义一个泛型类型,虚拟机都会提供一个相应的原始类型。

例如,Pair类在虚拟机中会被识别为:

public class Pair<T> 
{
   public Pair() { first = null; second = null; }
   public Pair(T first, T second) { this.first = first;  this.second = second; }

   public T getFirst() { return first; }
   public T getSecond() { return second; }

   public void setFirst(T newValue) { first = newValue; }
   public void setSecond(T newValue) { second = newValue; }

   private T first;
   private T second;
}

//虚拟机识别为

public class Pair
{
   public Pair() { first = null; second = null; }
   public Pair(Object first, Object second) { this.first = first;  this.second = second; }

   public Object getFirst() { return first; }
   public Object getSecond() { return second; }

   public void setFirst(Object newValue) { first = newValue; }
   public void setSecond(Object newValue) { second = newValue; }

   private Object first;
   private Object  second;
}

在这里原始类型的名字Pair就是Pair<T>类型擦除之后的泛型类型名称。将类型变量替换为限定类型(< T extends Comparable >替换为Comparable ),无限定类型就用Object。这里T是一个无限定的变量,所以直接用Object替换。

擦除的概念很重要,贯穿在泛型代码的编译过程。

5.1 翻译泛型表达式

当调用泛型方法时,如果擦除返回类型。编译器将插入强制类型转换。例如:

Pair<Person> p = ...
Person p = p.getName();

编译器会把这个方法翻译为两条虚拟机指令:

  • 调用p.getName()得到Object类型返回值

  • 将返回的Object类型强制转化为Person类型

5.2 翻译泛型方法(难点)

类型擦除也出现在泛型方法中。

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

类型擦除之后变为

public static Comparable min(Comparable[] a)

这种方法擦除在有些时候会带来一些问题。

class DateInterval extends Pair<Date>{
    public void setSecond(Date second){
      if(second.CompareTo(getFirst())>=0){
        .......
      }
    }
}

这个类在擦除之后变为

class DateInterval extends Pair{
    public void setSecond(Date second){.........}
}

但是,DateInterval类继承了Pair类,该类本身就有一个setSecond方法

public void setSecond(Object second)

这时,如果调用下面的语句

DateInterval interval = new DateInterval(......);
Pair<Date> pair = inteval;
pair.setecond(aDate);

编译器是会调用自身的setSecond(Date second),还是继承自Pair类中的setSecond(Object second)呢?

这里,setSecond的调用是具有多态性的。由于pair引用了DateInterval 对象,所以就应该是调用DateInterval.setSecond。问题就在于,类型的擦除与多态之间发生了冲突。

为了解决这个问题,编译器在DateInterval 类中生成了一个桥方法

public void setSecond(Object second){
    setSecond((Date) second);
}

那么,编译器的调用过程就是:首先pair已经声明为类型Pair<Date>,这个类型的方法就是setSecond(Object second)。虚拟机用pair引用的对象调用这个方法,但是这个对象是DateInterval 类型的,所以会调用DateInterval.setSecond(Object)方法。这个方法就是合成的桥方法。该方法会调用DateInterval .setSecond(Date)(见上面代码)。如此,完成了我们希望的调用。


咳咳,好了,今天先到这儿了!

总结一下上面所说的重点:

  • 虚拟机中没有泛型,只有普通的类和方法。
  • 所有的类型参数都用它们的限定类型替换。
  • 为保持类型安全性,必要时插入强制类型转换。
  • 桥方法被用来合成多态。

未完待续......
O(∩_∩)O哈哈~

相关文章

  • Java泛型 --(1)泛型基础篇

    主要内容 (1)能够定义简单的泛型类、泛型方法(2)知晓类型变量的限定规则和应用(3)理解虚拟机是如何解析泛型代码...

  • Java泛型教程

    Java泛型教程导航 Java 泛型概述 Java泛型环境设置 Java泛型通用类 Java泛型类型参数命名约定 ...

  • Java 泛型

    导读 移动开发知识体系总章(Java基础、Android、Flutter) 为什么会有泛型? 泛型类 泛型方法 泛...

  • Java泛型(黑马程序员武汉中心)

    Java泛型 一、概述 1、泛型含义 2、泛型好处 3、泛型分类 二、常见的泛型案例 1、泛型方法 A.定义时 B...

  • Java泛型基础

    Java泛型基础 1. 认识泛型 泛型是在JDK1.5之后增加的新功能. 泛型可以解决数据的安全性问题, 主要的原...

  • 一文带你认识Java泛型基础

    Java泛型基础 1. 认识泛型 泛型是在JDK1.5之后增加的新功能. 泛型可以解决数据的安全性问题, 主要的原...

  • spring 泛型处理

    java 泛型基础 泛型类型:泛型类型是在类型上参数化的泛型类或接口 泛型使用场景编译时前类型检查。定义为 Col...

  • Kotlin泛型的高级特性(六)

    泛型的高级特性1、泛型实化2、泛型协变3、泛型逆变 泛型实化 在Java中(JDK1.5之后),泛型功能是通过泛型...

  • 【泛型】通配符与嵌套

    上一篇 【泛型】泛型的作用与定义 1 泛型分类 泛型可以分成泛型类、泛型方法和泛型接口 1.1 泛型类 一个泛型类...

  • 详解Java泛型之3——十分钟理解泛型擦除

    前面我们介绍了泛型以及通配符的基础概念,可以参考文章: 详解Java泛型之1——入门泛型必懂的知识点[https:...

网友评论

      本文标题:Java泛型 --(1)泛型基础篇

      本文链接:https://www.haomeiwen.com/subject/qoksbftx.html