深入学习Java泛型

175 阅读7分钟

笔者做android开发也有几年的时间,对java的高级特性一直以来都处于一种浅尝则止的状态,最近恰好处于一段摸鱼期,所以就想深入学习一下java里的高级特性来充实下自己的武器库。

这是第一篇,研究一下java泛型,相信项目中很多人都用过,笔者也是在项目中经常用到,但是要是让大家具体说说泛型相信大家可能不知道该如何说起。那我就从下面几个点来学习泛型。

a.为什么需要泛型?

b.泛型类、泛型接口的定义。

c.泛型方法辨析

d.限定类型变量

e.泛型中的约束性和局限性

f.泛型类型的继承规则

g.通配符

h.虚拟机如何实现泛型?

1.首先我们来说第一个:为什么需要泛型?

这里举这么一个例子:计算两个数字之和;相信大家都觉得这个太简单了,代码如下:

public class NonGeneric {
   public int addInt(int x,int y){
    return  x+y;
}}

调用:

   NonGeneric nonGeneric = new NonGeneric();
   System.out.println("计算1+2="+nonGeneric.addInt(1,2));

so easy! 那么问题来了,上面例子中我们传入的数据类型是int,如果我们要计算两个float数据之和呢,两个double数据呢?

再来看第二个例子:

     private void nonGeneric2() {
        List list = new ArrayList();
        list.add("nice");
        list.add("ok");
        list.add(100);
       for (int i = 0; i < list.size(); i++) {
          String name  = (String) list.get(i);
          System.out.println("name:"+name);
       }
     }

运行上面一段代码时:我们发现这么一个错误:

Caused by: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

而如果在创建list的时候直接使用泛型指定数据类型List,就可以在编写代码的时候给我们错误提示,避免错误,这就是使用泛型的好处之一。这里只是一个简单的例子,相信大家在编写代码中也是这么写的。

2.泛型类,泛型接口,泛型方法的定义:

泛型类:

public class NormalGeneric<T> {
private T data;

public NormalGeneric(T data) {
    this.data = data;
}

public T getData() {
    return data;
}

public void setData(T data) {
    this.data = data;
}}

泛型类的定义方式如上,这里看到,在泛型类中,同样可以使用set,get方法,也可以使用构造方法。(注:这里的T不是强制规定的,可以用其他字母代替)

泛型接口:

  public interface Genertor<T> {
    public T next();   
  }

泛型接口实现方式1(泛型类实现泛型接口):

  public class ImplGenertor<T> implements Genertor<T> {
     @Override
   public T next() {
      return null;
  }
 }

泛型接口实现方式2(指定数据类型):

 public class ImplGenentor2 implements Genertor<String> {
   @Override
    public String next() {
     return null;
 }}

泛型方法辨析1:

public class GenericMethod{
   public <T> T GenericMethod(T...a){
    return  a[a.length/2];
  }
}

这里定义的是正常类里的一个泛型方法,T...a是一种可变参数的表示,这种可变参数底层其实会转化为T[] x的形式,所以可以接受多个T类型的传参。如:

 GenericMethod genericMethod = new GenericMethod();
 System.out.println(genericMethod.GenericMethod("12","56","333"));

泛型方法辨析2:

public class GenericMethod2 {
  public  class  Generic<T>{
    private  T key;

   public  Generic(T key){
    this.key = key;

    }
   public T getKey(){
    return  key;
  }
   public  void  show(Generic<Number> object){
  }
 }
}

虽然在方法getKey()中使用了泛型,但是这并不是一个泛型方法。这只是类中的一个普通成员方法,只不过它的返回值是在声明泛型类已经声明过的泛型;所 以在这个方法中才可以继续使用T这个泛型。show(Generic object)方法也不是一个泛型方法,只是使用Generic作为行参而已。

泛型方法辨析3:

 public class GenericMethod3 {

  static  class  Fruit{
    @NonNull
    @Override
    public String toString() {
        return "fruit";
    }
}

   static  class Apple extends  Fruit{
    @NonNull
    @Override
    public String toString() {
        return "apple";
    }
}


   static  class  Person{

    @NonNull
    @Override
    public String toString() {
        return "person";
    }
}



   static class  GenerateTest<T>{

     public void  show_1(T t){
      System.out.println(t.toString());
   }


  //在泛型类中声明了一种泛型方法,使用泛型E,这种泛型E可以为任意类型。
  //可以类型与T相同,也可以不同。
  //由于泛型方法在声明的时候会声明泛型<E>因此即使泛型类中并未声明泛型,
  //编译器也能够正确的识别泛型方法中识别的泛型;  
   public  <E> void show3(E t){
      System.out.println(t.toString());
   }

  //在泛型类中声明了一个泛型方法,使用泛型T,
  //注意这个T是一种全新的泛型,可以与泛型类中声明的T不是同一种类型。  
   public  <T> void  show_2(T t){
       System.out.println(t.toString());
  }
}

  public static void main(String[] args) {
    Apple apple = new Apple();
    Person person = new Person();
    GenerateTest<Fruit> generateTest = new GenerateTest<>();
    generateTest.show_1(apple);

    //无法传入person
    // generateTest.show_1(person);

    generateTest.show_2(apple);
    generateTest.show_2(person);

    generateTest.show3(apple);
    generateTest.show3(person);

}

}

在如上所示的 GenericMethod3中,定义了一个四个静态内部类,Fruit、Apple、Person、GenerateTest。其中Apple是Fruit的子类; 通过上面的调用实例可以得出结论:在泛型类里定义泛型方法,参数类型以泛型方法里的为准。

3.限定类型变量

例:取a,b,两个变量最小值:

 public class ArrayAlg {

  public static  <T> T min(T a,T b){
    if (a.comapareTo(b)>0)return  a;else return  b;
  }

  static  class  Test{
  }

  public static void main(String[] args) {

 }}

首先我们可能想到的写法min();这里我们无法保证a可以实现compareTo()方法,那么如何确保一定可以用compareTo()去比较a,b变量,这里我们就用到限定类型变量,让T 派生自 Comparable,这样就可以使传入变量一定可以使用compareTo();

优化后:

 public static  <T extends  Comparable> T min(T a,T b){
  return  a.compareTo(b)>0 ?a:b;
 } 

注:这里可实现多个接口,也可以派生自某个类,但是如果需要实现类,同时实现接口时,必须将类置于首位,如:<T extends ArrayList & Comparable>,在泛型类中使用泛型是同样需要遵循此规则。

4.泛型中的约束性和局限性

(1)不能实例化类型变量

 public class Restrict<T>  {

  private  T   data;

//不能实例化类型变量
//    public  Restrict(){
//        this.data = new T();
//    }

}
这里的T作为泛型,是不能进行实例化的;

(2)静态域或者方法里不能引用类型变量

  private static  T instance;//这里同样不能引用

注:在创建对象时虚拟机会先执行static 方法,再执行类的构造方法,此时虚拟机无法识别泛型T;但是静态方法本身泛型方法就行;

 private  static   <T> T getInstance(){}

(3)基础类型不能作为泛型实例化具体参数

 RestrictM<double> restrict;//不能使用;
 Restrict<Double> restrict; //可以使用;

(4)泛型中不允许使用instanceof关键字

 if (restrict instanceof Restrict<Double>) //不允许使用;
 if( restrict instanceof Restrict<T>) // 不允许使用;

(5)泛型类的类型与传入的泛型参数无关

    例:
    Restrict<Double> restrict = new Restrict<>();
    Restrict<String> restrictString = new Restrict<>();

    System.out.println(restrict.getClass().getName());
    System.out.println(restrictString.getClass().getName());
    
    结果:
    com.terry.genericpractice.restrict.Restrict
    com.terry.genericpractice.restrict.Restrict

(6)泛型类不能extends Exception/Throwable;

  private  class  Problem<T> extends Exception; //不能使用;
  
  //不能捕获异常
   /*public  <T extends  Throwable> void  doWork(T t){
        try {
        
          }catch (T t){
        }
    }*/
    
 //可以抛出异常    
 public  <T extends  Throwable> void  doWork(T t)throws T{
     try {

    }catch (Throwable e){
        throw  t;

    }
}        

5.泛型类型的继承规则
(1)泛型类的派生关系与其传入的参数无关;

例: 有普通类Worker 派生于 Employee;泛型类Pair;

   //Pair<Employee> 和 Pair<Worker> 没有任何的继承关系;
     Pair<Employee> employeePair = new Pair<>();
     Pair<Worker> workerPair = new Pair<>();
 
   //可以定义
    Employee employee = new Worker();
   //不可以定义    
    Pair<Employee> employeePair1 = new Pair<Worker>();

(2)泛型类可以继承或者扩展其他内部类如List与ArrayList;

6.通配符(适用于泛型方法,不适用泛型类) 例:现有基类Fruit,apple派生于Fruit、orange派生于Fruit,HongFuShi派生于apple;泛型类 GenericType;

public class WildChar {


 public static  void  print(GenericType<Fruit> p){
    System.out.println(p.getData().getColor());
 }


 public static  void  use(){
    GenericType<Fruit> a = new GenericType<>();
    print(a);
    GenericType<Orange> b = new GenericType<>();
    print(b); //编译报错

 }
}

如上 GenericType<Fruit) 和 GenericType<Orange)并没有继承关系,所以这里传入orange去调用print方法时会报错,这里我们就可以使用通配符:

 public static  void  print(GenericType<? extends Fruit> p){
    System.out.println(p.getData().getColor());
 }
 注:这里表示传入的类是Fruit子类及其本身;

同样可以用super public static void print(GenericType<? super Fruit> p){ System.out.println(p.getData().getColor()); } 注:这里表示传入的类是Fruit父类及其本身;

  1. 虚拟机如何实现泛型?

在Java中实现泛型中是用类型擦除;

例:

  public class GenericRaw<T extends Comparable & Serializable> {

  public  T data;

   public T getData() {
    return data;
  }

   public void setData(T data) {
    this.data = data;
  }

  public static void main(String[] args) {
    
}
}


注:当 T extends Comparable & Serializable 时,它会以Comparable 作为原生类型,如果在某个位置使用到Serializable,编译器会在合适的位置插入一个强制转型的代码;

例:

 public class Conflict {


  public static  void  method (List<String> stringList){

  }

   public static  void  method(List<Integer > integerList){

  }
}

注:虽然这里List<String> 和List<Integer> 不同,但是编译器会报错,这里就是因为类型的擦除。