java泛型详解

一,java泛型简介
java泛型避免了运行期错误,在编译时对代码进行了检查。在新代码中我们不应该使用原生态类型,否则就失掉了泛型在安全性和表述性方面的所有优势。
泛型不像数组,可以支持协变。比如Apple是Fruit的子类型。但是List不是List的子类型。这个问题怎么解决,我们将在五六小节详细说明。
再一方面,java里是没有真正的泛型的,所以java的泛型也叫做伪泛型。泛型在运行期都会擦除。这个知识点我们将在泛型擦除这一小节详细介绍。
二,学会使用泛型
举一个effective版中的一个简单例子。
首先,这是一个简单的堆栈实现,代码如下

package stackDemo;

import java.util.Arrays;
import java.util.EmptyStackException;

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULLT_INITIAL_CAPACITY = 16;

    public Stack(Object[] elements) {
        elements = new Object[DEFAULLT_INITIAL_CAPACITY];
    }

    public void push(Object e){
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if(size == 0){
            throw new EmptyStackException();
        }
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }

    public void ensureCapacity() {
        if(elements.length == size){
            elements = Arrays.copyOf(elements,2*size+1);
        }
    }
}

将类泛型化的第一个步骤是给它的声明添加一个或者多个类型参数。下一步是用相应的类型参数替换所有的Object类型。
修改后,代码如下

package stackDemo;

import java.util.Arrays;
import java.util.EmptyStackException;

public class Stack {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULLT_INITIAL_CAPACITY = 16;

    public Stack(E[] elements) {
        elements = new E[DEFAULLT_INITIAL_CAPACITY];
    }

    public void push(E e){
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if(size == 0){
            throw new EmptyStackException();
        }
        E result = elements[--size];
        elements[size] = null;
        return result;
    }

    public void ensureCapacity() {
        if(elements.length == size){
            elements = Arrays.copyOf(elements,2*size+1);
        }
    }
}

这里呢,编译器会产生一个错误

        elements = new E[DEFAULLT_INITIAL_CAPACITY];
stackDemo/Stack.java:13

因为我们不能创建不可具体化类型的数组,数组是不支持泛型的。effective java一书中给出了两种解决方案,这里只写第二种。
将elements域的类型从E[]改为Object[]。
然后将pop方法改动如下

 @SuppressWarnings("unchecked")
        E result = (E)elements[--size];

三,泛型方法
首先,泛型方法能够独立于类而产生变化,是否是泛型方法与其所在的类是否是泛型没关系。
其次,无论何时,只要能够做到,就应该尽量使用泛型方法。另外,对于static方法而言,无法访问泛型类的类型参数。所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
简单举两个泛型方法的例子来加深大家的理解

public static   void union(Set s1,Set s2){
    Set result = new HashSet(s1);
    result.addAll(s2);
    return result;
}
public static  T max(List list){
   Iterator i = list.iterator();
   T result = i.next();
   while(i.hasNext()){
     T t = i.next();
     if(t.compareTo(result) > 0){
       result = t;
     }
   }
   return result;
}

四,泛型擦除
泛型技术在C#和java中有着根本性的分歧。C#里面的泛型无论是在源码中或是运行期的CLR中,都是切实存在的。List与List就是不同的类型。它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型。
java语言中的泛型则不同,它只在程序源码中存在,在编译后的字节码文件中就已经替换为原来的原生类型。所以java语言的泛型技术本质上来说就是一颗语法糖。
泛型擦除,一般来说类型参数将会被替换为Object类型,但如果是下面这种情况

Class A

那么泛型类型参数将擦除到它的第一个边界。(可能会有多个边界)
再一点,泛型擦除的代价是显著的,因为它不能显示地引用运行时类型的操作中,例如转型,instance of和new 表达式。因为所有关于参数的类型信息都丢失了。所以我们写代码时必须时刻提醒自己,我们只是看起来好像拥有有关参数的类型信息而已。
擦除既然有缺陷,java也相应的对它进行了补偿
例如,我们不能用instanceof来判断类型,因为其类型信息已被擦除。我们可以转而使用动态的isInstance()。
例子如下

class Building{}
class House extends Building{}
public classs ClassTypeCapture{
   Class  kind;
   public ClassTypeCapture(Class kind){
      this.kind = kind;
   }
   public boolean f(Object arg){
     return kind.isInstance(arg);
   }
   public static void main(String[] args){
      ClassTypeCapture ctt1 = new ClassTypeCapture(Building.class);
      system.out.println(cttq1.f(new Building()));
      system.out.println(cttq1.f(new House()));
   } 
   true
   true
   
}

java中对于new T()这种尝试将无法实现,一部分原因是因为擦除,另一部分原因是因为编译器不能验证T具有默认(无参)构造器。但是在C#中,这种操作很自然,并且很安全。
java中的解决方案是传递一个工厂对象,并使用它来创建新的实例。最方便的工厂对象就是Class对象,因此如果使用类型标签,那么你就可以使用newInstance()来创建这个类型的新对象。

class ClassAsFactory{
   T x;
   public ClassAsFactory(Class  kind){
     try{
      x = kind.newInstance();
     }catch(Exception e){
       throw new RuntimeException(e);
    }
   }
}
class Employee{}
public class InstantiateGenericType{
   public static void main(String[] args){
     ClassAsFactory fe = new ClassAsFactory(Employee.class);
   }
}

再一个方式是使用显示的工厂,并限制其类型,这种方式就是类似Spring中的FactoryBean。实现如下

Interface Factory{
  T create();
}
class Foo2{
   private T x;
   public > foo2(F factory){
     x = factory.create();
   }
   //........
}
class IntegerFactory implements Factory{
   public Integer create(){
      return new Integer(0);
   }
}
class Widget{
  public static class Factory implements Factory{
      pulic Widget create(){
         return new Widget();
      }
  }
}
public class FactoryConstraint{
   public static void main(String[] args){
      new Foo2(new IntegerFactory());
      new Foo2(new Widget.Factory());
   }
}

五,泛型边界
边界使得你可以在泛型的类型参数上设置限制条件,因为泛型擦除,所以无界泛型参数只可以调用Object的方法。但是,如果能够将这个参数限制为某个类型子集,那么你就可以用这些类型子集来调用方法。为了执行这种限制,Java泛型重用了extends关键字。

package com.zy.test;
 
import java.awt.Color;
 
public class BasicBounds {
    public static void main(String[] args) {
        Solid solid = new Solid(new Bounded());
        System.out.println(solid.color());
        System.out.println(solid.getY());
        System.out.println(solid.weight());
    }
}
 
interface HasColor {
    java.awt.Color getColor();
}
 
class Colored {
    T item;
    Colored(T item) {
        this.item = item;
    }
    T getItem() {
        return item;
    }
    java.awt.Color color() {
        return item.getColor();
    }
}
 
class Dimension {
    public int x, y, z;
}
 
class ColoredDimension {// 类和接口都是放进来
    T item;
    public T getItem() {
        return item;
    }
    public void setItem(T item) {
        this.item = item;
    }
    ColoredDimension(T item) {
        this.item = item;
    }
    
    java.awt.Color color() {
        return item.getColor();
    }
    
    int getX() {
        return item.x;
    }
    
    int getY() {
        return item.y;
    }
    
    
    int getZ() {
        return item.z;
    }
}
 
interface Weight {
    int weight();
}
 
class Solid {
    T item;
 
    Solid(T item) {
        this.item = item;
    }
    
    public T getItem() {
        return item;
    }
    
    java.awt.Color color() {
        return item.getColor();
    }
    
    int getX() {
        return item.x;
    }
    
    int getY() {
        return item.y;
    }
    
    
    int getZ() {
        return item.z;
    }
    
    int weight() {
        return item.weight();
    }
}
 
 
class Bounded extends Dimension implements HasColor, Weight {
    
    @Override
    public int weight() {
        return 0;
    }
 
    @Override
    public Color getColor() {
        return null;
    }
    
}

六,泛型通配符
通配符分为三种
上界通配符,下界通配符,无界通配符
先讲有界通配符,简单来说,有这么两点
1.上界不能往里存,只能往外取
2.下界往外取只能赋值给Object变量,不影响往里存

我们在本文一开始就说过,泛型和数组不一样,是不支持协变的。而通配符则能够在两个类型之间建立某种向上的转型关系。它能够给我们以最大限度的灵活性。
那我们怎样去使用有界通配符呢?在effective java中给出了一种简单高效的方法 名为"PECS",即 producer- extends ,consumer -super。
换句话说,如果参数化类型表示一个T生产者,就使用;如果它表示一个T消费者,就使用
举个例子

public static  Set union (Set s1,Set s2){
  Set result = new HashSet(s1);
  result.addAll(s2);
  return result;
}

s1和s2着两个参数都是生产者,根据PECS规则,这个声明应该是

public static  Set union (Set s1,Set s2)

书中提到,Comparable始终是消费者,因此使用时始终应该是Comparable优先于Comparable。对于Comparator也一样。
举例如下

    public static > T max(List list){
        Iterator t = list.iterator();
        T result = t.next();
        while (t.hasNext()){
            T i = t.next();
            if(i.compareTo(result) > 0){
                result = i;
            }
        }
        return result;
    }

你可能感兴趣的:(java基础)