泛型

泛型可以使我们在编译时而不是在运行时检测出错误

       泛型是JDK1.5推出的新特性,java允许定义泛型类、泛型接口和泛型方法。Java API中的一些类和接口使用泛型进行了修改。

package java.lang;

public interface Comparable{

    public int compareTo(Object o);

}

package java.lang;

public interface Comparable<T>{

    public int compareTo(T o);

}

                     Jdk1.5之前                                                           jdk1.5

       这里的<T>表示形式泛型类型(formal generic type),随后可以用一个实际具体类型(actual concrete type)来替换它。替换泛型类型称为泛型实例化(generic instantiation)。按照约定俗成的惯例,像E或T这样的单个大写字母用于表示形式泛型类型。

       为了理解泛型的好处,对比下面两段代码

A:

Comparable c = new Date();

System.out.println(c.compareTo("peppa"));

B:

Comparable<Date> c = new Date();

System.out.println(c.compareTo("peppa"));

  B代码块中可以在编译期而不是在运行期得到这个错误,因此泛型使程序更加可靠。

  再比如:

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

  现在,只能向该线性表中添加字符串,例如:list.add(“suzy”),如果试图向其中添加非字符串,就会产生编译错误。

  泛型类型必须是引用类型。不能使用int,long或char等基本类型来替换泛型类型。

  其次,使用泛型从线性表等中拿取数据时无须类型转换,因为编译器已经知道了这个元素类型。例如:

ArrayList<Integer> list = new ArrayList<>();
list.add(5);//自动装箱
list.add(10);//自动装箱
Integer num = list.get(0);//无须类型转换
int num1 = list.get(1);//无须类型转换,自动拆箱。

 定义泛型类和接口

  可以为类或者接口定义泛型,使用该类来创建对象(或者声明引用变量)时,须指定具体的实际类型来替换它

import java.util.ArrayList;
/**
 * 定义泛型类:自定义栈
 *
 * @param <T>形式类型参数,使用该类来创建对象(或者声明引用变量)时,须指定具体的实际类型来替换它
 */
public class GenericStack<T>{
       //一个数组列表,用于存储元素
       private ArrayList<T> stack = new ArrayList<>();
       /**
        * 添加一个新的元素到栈顶
        * @param ele 要添加的类型元素
        */
       public void push(T ele) {
              stack.add(ele);
       }

       /**
        * 返回栈中元素个数
        * @return 元素数目
        */
       public int getSize() {
              return stack.size();
       }

       /**
        * 返回并移除栈顶元素(弹栈)
        * @return 栈顶元素
        */
       public T pop() {
              T ele = stack.get(stack.size() - 1);
              stack.remove(ele);
              return ele;
       }
       /**
        * 返回栈顶元素
        * @return 栈顶元素
        */
       public T peek() {
              T ele = stack.get(stack.size() - 1);
              return ele;
       }

       /**
        * 是否为空栈
        * @return 如果栈为空,返回true
        */
       public boolean isEmpty() {
             return stack.isEmpty();
       }
}

  当使用类来创建对象,或者使用类或接口来声明引用变量时,必须指定具体的类型。 

GenericStack<String> stack = new GenericStack<>();
stack.push("peppa");
stack.push("suzy");
stack.push("pedro");

GenericStack<Integer> stack1 = new GenericStack<>();
stack1.push(1020);
stack1.push(9527);
stack1.push(886);

  可以不使用泛型,而将元素类型设置为Object,也可以容纳任何对象类型。但是,使用泛型能够提高软件的可靠性和可读性,因为某些错误能在编译时而不是运行时被检测到。 

泛型方法

  使用泛型类型来定义泛型方法示例如下:

public class GenericMethodDemo {
  public static void main(String[] args ) {
    Integer[] integers = {1, 2, 3, 4, 5};
    String[] strings = {"London", "Paris", "New York", "Austin"};
    GenericMethodDemo.<Integer>print(integers);//或者print(integers);
    GenericMethodDemo.<String>print(strings);//或者print(strings);实际类型没有明确指定。编译器自动发现实际类型
  }

  public static <E> void print(E[] list) {
    for (int i = 0; i < list.length; i++)
      System.out.print(list[i] + " ");
    System.out.println();
  }
}

  可以将泛型指定为另外一种类型的子类型。这样的泛型类型称为受限的(bounded)。

public class BoundedTypeDemo {

  public static void main(String[] args ) {
    Rectangle rectangle = new Rectangle(2, 2);
    Circle circle = new Circle(2);

    System.out.println("Same area? " + equalArea(rectangle, circle));
  }

  public static <E extends GeometricObject> boolean equalArea(E object1, E object2) {
    return object1.getArea() == object2.getArea();
  }
} 

  示例学习:对一个对象数组进行排序

  对一个Comparable对象数组进行排序。这些对象是Comparable接口的实例,它们使用compareTo方法进行比较。

import java.util.Arrays;

/**
 * 对一个对象数组进行排序
 *
 */
public class GenericSort {
       public static void main(String[] args) {
              Integer[] intArray = {Integer.valueOf(2),Integer.valueOf(4),Integer.valueOf(6),Integer.valueOf(3),Integer.valueOf(5)};
              Double[] doubleArray = {Double.valueOf(3.14),Double.valueOf(2.14),Double.valueOf(0.14)};
              Character[] charArray = {Character.valueOf('a'),Character.valueOf('c'),Character.valueOf('b'),Character.valueOf('0')};
              String[] strArray = {"Hello","world","peppa"};
              sort(intArray);
              sort(doubleArray);
              sort(charArray);
              sort(strArray);
              System.out.println(Arrays.toString(intArray));
              System.out.println(Arrays.toString(doubleArray));
          System.out.println(Arrays.toString(strArray));
       }
       private static <T extends Comparable<T>> void sort(T[] array) {
              T currentMin;
              int currentMinIndex;
              for(int i = 0; i < array.length; i++) {
                     currentMin = array[i];
                     currentMinIndex = i;
                     for(int j = i + 1;j < array.length;j++) {
                            if(currentMin.compareTo(array[j]) > 0) {
                                   currentMin = array[j];
                                   currentMinIndex = j;
                            }
                     }
                     if(currentMinIndex != i) {
                            array[currentMinIndex] = array[i];
                            array[i] = currentMin;
                     }
              }
       }
}

原始类型和向后兼容

  没有指定具体类型的泛型类和泛型接口被称为原始类型,用于和早期的java版本向后兼容。

GenericStack stack = new GenericStack();

  等价于

GenericStack<Object> stack = new GenericStack<>();

  像这样不带类型参数的GenericStack和ArrayList泛型类称为原始类型(raw type),使用原始类型可以向后兼容java的早期版本。

通配泛型

  可以使用非受限通配、受限通配或者下限通配来对一个泛型类型指定范围。

  为什么要使用通配泛型?首先观察下面这个例子:

public class WildCardNeedDemo {
  public static void main(String[] args ) {
    GenericStack<Integer> intStack = new GenericStack<Integer>();
    intStack.push(1); // 1 is autoboxed into new Integer(1)
    intStack.push(2);
    intStack.push(-2);
    System.out.print("The max number is " + max(intStack));
  }

  /** Find the maximum in a stack of numbers */
  public static double max(GenericStack<Number> stack) {
     double max = stack.pop().doubleValue(); // initialize max
     while (!stack.isEmpty()) {
       double value = stack.pop().doubleValue();
       if (value > max)
         max = value;
     }
     return max;
  }
}

  尽管Integer是Number的子类型,但是GenericStack<Integer>并不是GenericStack<Number>的子类型。为了避免这个问题,可以使用通配泛型类型。通配泛型类型有三种形式----?、? extends T或者? super T,其中T是泛型类型。上面飘红的代码会出现编译错误,因为intStack不是GenericStack<Number>的实例。因此,不能调用max(intStack).

  • ?称为非受限通配(unbounded wildcard),它和? extends Object是一样的。
  • ? extends T称为受限通配(bounded wildcard),表示T或者T的一个子类型。
  • ? super T称为下限通配(lower-bounded wildcard),表示T或者T的一个父类型。

  使用下面代码替换掉上面程序瓢蓝代码就可以修复上面的错误。

    public static double max(GenericStack<? extends Number> stack)

  <? extends Number>表示Number或者Number子类型的通配类型。

public class AnyWildCardDemo {
  public static void main(String[] args ) {
     GenericStack<Integer> intStack = new GenericStack<>();
     intStack.push(1); // 1自动装箱为 Intege.valueOf(1)
     intStack.push(2);
     intStack.push(-2);
     print(intStack);
  }

  /** 打印栈中元素并清空栈 */
  public static void print(GenericStack<?> stack) {
    while (!stack.isEmpty()) {
      System.out.print(stack.pop() + " ");
    }
  }
}

  上面程序清单在print方法中使用?通配符,打印栈中的对象以及清空栈。<?>是一个通配符,表示任何一种对象类型。它等价于<? extends Object>.如果用GenericStack<Object>替换GenericStack<?>,这样调用print方法就会报错,因为intStack不是GenericStack<Object>的实例。注意:尽管Integer是Object的一个子类型,但是GenericStack<Integer>并不是GenericStack<Object>的子类型。

public class SuperWildCardDemo {
  public static void main(String[] args) {   GenericStack<String> stack1 = new GenericStack<String>();   GenericStack<Object> stack2 = new GenericStack<Object>();   stack2.push("Java");    stack2.push(2);   stack1.push("Sun");   add(stack1, stack2);   AnyWildCardDemo.print(stack2);  } public static <T> void add(GenericStack<T> stack1,GenericStack<? super T> stack2) { while (!stack1.isEmpty()) stack2.push(stack1.pop()); } }

泛型类型和通配类型之间的关系图

消除泛型和对泛型的限制

  编译器可以使用泛型信息,但这些信息在运行时是不可用的。这被称为类型消除。

  泛型是使用一种叫做类型消除(擦除)的方法来实现的。编译器使用泛型类型信息来编译代码,但是随后会消除它。因此,泛型信息在运行时是不可用的。这种方法可以使泛型代码向后兼容使用原始类型的遗留代码。

  泛型存在于编译时。一旦编译器确认泛型类型是安全使用的,就会将它转换为原始类型。例如:编译器检查左边的代码里泛型是否被正确使用,然后将它翻译成右边的代码

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

list.add(“peppa”);

String name = list.get(0);

ArrayList list = new ArrayList();

list.add(“peppa”);

String name = (String)list.get(0);

  当编译泛型类、接口和方法时,编译器用Object类型代替泛型类型.如下翻译:

public static <E> void print(E[] list){

  for(int i = 0; i < list.length; i++)

     System.out.print(list[i] + “ “);

  System.out.println();

}

public static void print(Object[] list){

  for(int i = 0; i < list.length; i++)

    System.out.print(list[i] + “ “);

  System.out.println();

}

  如果一个泛型类型是受限的,那么编译器就会使用该受限类型来替换它。例如:

public static <E extends Geometric> boolean equalArea(E o1,E o2){

   return o1.getArea() == o2.getArea();

}

public static boolean equalArea(Geometric o1, Geometric o2){

   return o1.getArea() == o2.getArea();

}

  非常需要注意的是,不管实际的具体类型是什么,泛型类是被它的所有实例所共享的。

ArrayList<String> list1 = new ArrayList<>();

ArrayList<Integer> list2 = new ArrayList<>();

  尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但是,在运行时只有一个ArrayList类会被加载到JVM中。list1和list2都是ArrayList的实例。因此:

Sout:list1 instanceof ArrayList    //true

Sout:list2 instanceof ArrayList    //true

  然而表达式list1 instanceof ArrayList<String>是错误的。由于ArrayList<String>并没有在JVM中存储为单独一个类,所以,在运行时使用它是毫无意义的。

  由于泛型类型在运行时被擦除,因此,对于如何使用泛型类型是有一些限制的:

  限制1:不能使用new T()

    运行时泛型T是不可用的。

   限制2:不能使用new T[]

    可以通过创建一个Object的数组,然后将它的类型转换为T[]来规避这个限制,

    E[] elements = (E[])new Object[capacity];

    类型转换到E[]会导致一个免检的编译警告,因为编译器无法确保在运行时类型转换是否能成功,这种类型的编译警告是使用java泛型的不足之处,也是无法避免的。

  使用泛型类型创建泛型数组也是不允许的。

    GenericStack<String>[] stack = new GenericStack<String>[10];//错误的

  可以修改为下面代码来规避,但是会有编译警告

    GenericStack<String>[] stack = (GenericStack<String>[])new GenericStack [10];

  限制3:在静态上下文中不允许类的参数是泛型类型

  由于泛型类的所有实例都有相同的运行时类,所以泛型类的静态变量和方法是被它的所有实例所共享的。因此,在静态方法、数据域或者初始化的语句中,为类引用泛型类型参数是非法的。

  限制4:异常类不能是泛型的

示例学习:泛型矩阵类

  对于所有矩阵,除了元素类型不同以外,它们的加法和乘法操作都是类似的。因此,可以设计一个父类,不管它们的元素类型是什么,该父类描述所有类型的矩阵共享的通用操作,还可以创建若干个适用于指定矩阵类型的子类。

import com.iweb.generic.exception.MatrixException;

/**
 * 使用泛型来设计用于矩阵运算的类
 * @param <T>
 */
public abstract class GenericMatrix<T extends Number> {
    protected  abstract T add(T num1, T num2);
    protected  abstract T multiply(T num1, T num2);
    protected  abstract T zero();
    
    /**
     * 矩阵加法
     */
    public T[][] addMatrix(T[][] matrix1,T[][] matrix2){
        if(matrix1.length != matrix2.length || matrix1[0].length != matrix2[0].length)
            throw new MatrixException("两个矩阵的行列不相等,不能完成加法操作");
        @SuppressWarnings("unchecked")
        T[][] results = (T[][])new Number[matrix1.length][matrix1[0].length];
        for(int i = 0; i < results.length;i++) {
            for(int j = 0; j < results[i].length;j++) {
                results[i][j] = add(matrix1[i][j], matrix2[i][j]);
            }
        }
        return results;
    }
    
    /**
     * 将泛型类型T[][]的两个矩阵进行相乘
     */
    public T[][] multiplyMatrix(T[][] matrix1,T[][] matrix2){
        if(matrix1[0].length != matrix2.length) {
            throw new MatrixException("两个矩阵不符合乘法运算规则");
        }
        @SuppressWarnings("unchecked")
        T[][] results = (T[][])new Number[matrix1.length][matrix2[0].length];
        for(int i = 0; i < results.length; i++) {
            for(int j = 0; j < results[0].length;j++) {
                results[i][j] = zero();
                for(int k = 0; k < matrix1[0].length;k++) {
                    results[i][j] = add(results[i][j],multiply( matrix1[i][k], matrix2[k][j]));
                }
            }
        }
        return results;
    }
    //显示矩阵、操作以及它们的结果
    public static void printResult(Number[][] matrix1,Number[][] matrix2,Number[][] matrix3,char operator) {
        for(int i = 0; i < matrix1.length; i++) {
            for(int j = 0; j < matrix1[0].length; j++)
                System.out.print(" " + matrix1[i][j]);
            if(i == matrix1.length / 2)
                System.out.print("  " + operator + "  ");
            else
                System.out.print("      ");
            for(int j = 0;j < matrix2[0].length; j++) {
                System.out.print(" " + matrix2[i][j]);
            }
            if(i == matrix1.length / 2)
                System.out.print("  =  ");
            else
                System.out.print("     ");
            for(int j = 0;j < matrix3[0].length; j++) 
                System.out.print(matrix3[i][j] + " ");
            System.out.println();
        }
        System.out.println();
    }
}

  addMatrix和multiplyMatrix方法在进行操作之前检测矩阵的边界。如果两个矩阵的边界不匹配,那么程序就会抛出一个异常(自定义异常)

public class MatrixException extends RuntimeException{
    private static final long serialVersionUID = 4955819099245243429L;
    
    public MatrixException(String message) {
        super(message);
    }
}

  两个具体子类之一---整型矩阵

public class IntegerMatrix extends GenericMatrix<Integer>{

    @Override
    protected Integer add(Integer num1, Integer num2) {
        return num1 + num2;
    }

    @Override
    protected Integer multiply(Integer num1, Integer num2) {
        return num1 * num2;
    }

    @Override
    protected Integer zero() {
        return 0;
    }

}

  整型矩阵测试类

public class IntegerMatrixTest {
    public static void main(String[] args) {
        Integer[][] matrix1 = {{1,2,3},{4,5,6},{1,1,1}};
        Integer[][] matrix2 = {{1,1,1},{2,2,2},{0,0,0}};
        
        IntegerMatrix integerMatrix = new IntegerMatrix();
        
        IntegerMatrix.printResult(matrix1, matrix2, integerMatrix.addMatrix(matrix1, matrix2), '+');
        IntegerMatrix.printResult(matrix1, matrix2, integerMatrix.multiplyMatrix(matrix1, matrix2), '*');
    }
}

  两个具体子类之一---Rational矩阵(具体Rational类详见面向对象程序设计章节)

public class RationalMatrix  extends GenericMatrix<Rational>{

    @Override
    protected Rational add(Rational num1, Rational num2) {
        return num1.add(num2);
    }

    @Override
    protected Rational multiply(Rational num1, Rational num2) {
        return num1.multiply(num2);
    }

    @Override
    protected Rational zero() {
        return new Rational(0, 1);
    }
}

 

posted @ 2020-06-24 09:12  Tiger-Adan  阅读(972)  评论(1编辑  收藏  举报