Java泛型

泛型的定义以及存在意义

泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
例如:GenericClass<T>{}

使用泛型的主要优点是能够在编译时而不是在运行时检测错误。

泛型只在编译阶段有效。看下面的代码:

List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
    Log.d("泛型测试","类型相同");
}

输出结果:D/泛型测试: 类型相同

通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);

Log.d("泛型测试","key is " + generic.getKey());
Log.d("泛型测试","key is " + generic1.getKey());
Log.d("泛型测试","key is " + generic2.getKey());
Log.d("泛型测试","key is " + generic3.getKey());

D/泛型测试: key is 111111
D/泛型测试: key is 4444
D/泛型测试: key is 55.55
D/泛型测试: key is false

命名类型参数

推荐的命名约定是使用大写的单个字母名称作为类型参数。这与 C++ 约定有所不同(参阅 附录 A:与 C++ 模板的比较),并反映了大多数泛型类将具有少量类型参数的假定。对于常见的泛型模式,推荐的名称是:

K —— 键,比如映射的键。 
V —— 值,比如 List 和 Set 的内容,或者 Map 中的值。 
E —— 异常类。 
T —— 泛型。

泛型有三种常用的使用方式:泛型类,泛型接口和泛型方法。

泛型类

一个泛型类(generic class)就是具有一个或多个类型变量的类。

下文写了一个用泛型定义的栈

为了创建一个字符串堆找,可以使用new GenericStack<String>() 或new GenericStack<>() 。这可能会误导你认为GenericStack 的构造方法应该定义为 public GenericStack\<E>()这是错误的。它应该被定义为 public GenericStack()

public class GenericStack<E>{
    private java.util.ArrayList<E> list=new java.util.ArrayList<>();

    public int getSize(){
        return list.size;
    }

    public T peek(){
        return list.get(getList()-1);
    }

    public void push(E o){
        list.add(o);
    }

    public E pop(){
        E o = list.get(getList()-1);
        list.remove(getSize()-1);
        return o;
    }

    public boolean isEmpty(){
        return list.isEmpty();
    }

    @Overide
    public String toString(){
         return "stack: "+list.toString();
    }
}

 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

泛型方法

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。

/**
 * 泛型方法的基本介绍
 * @param tClass 传入的泛型实参
 * @return T 返回值为T类型
 * 说明:
 *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
 *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
 *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
 *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}

泛型方法的基本用法

public class GenericTest {
   //这个类是个泛型类,在上面已经介绍过
   public class Generic<T>{
        private T key;

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

        //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
        //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
        //所以在这个方法中才可以继续使用 T 这个泛型。
        public T getKey(){
            return key;
        }

        /**
         * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
         * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
        public E setKey(E key){
             this.key = keu
        }
        */
    }

    /**
     * 这才是一个真正的泛型方法。
     * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
     * 这个T可以出现在这个泛型方法的任意位置.
     * 泛型的数量也可以为任意多个
     *    如:public <T,K> K showKeyName(Generic<T> container){
     *        ...
     *        }
     */
    public <T> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        //当然这个例子举的不太合适,只是为了说明泛型方法的特性。
        T test = container.getKey();
        return test;
    }

    //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
    public void showKeyValue1(Generic<Number> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

    //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
    //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
    public void showKeyValue2(Generic<?> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

     /**
     * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
     * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
     * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
    public <T> T showKeyName(Generic<E> container){
        ...
    }
    */

    /**
     * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
     * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
     * 所以这也不是一个正确的泛型方法声明。
    public void showkey(T genericObj){

    }
    */

    public static void main(String[] args) {

    }
}

声明泛型方法,将泛型类型<E>置于方法头中关键字static中,例如:

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

为了调用泛型,需要将实际类型放在尖括号内作为方法名的前缀,例如:

  GenericMethodDemo.<Integer>print(integers);

  GenericMethodDemo.<String>print(strings);

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

要点提示:可以开发一个泛型方法,对一个Comparable对象数组进行排序。

  创建一个泛型方法,对一个Comparable对象数组进行排序。这些对象是Comparable接口的实例,使用compareTo()方法进行比较。

public class GenericSort {

public static void show() {
Integer[] intArray = {new Integer(2),new Integer(4),new Integer(3)};
Double[] doubleArray = {new Double(2.5),new Double(6.4),new Double(3.3)};
Character[] charArray = {new Character('a'),new Character('q'),new Character('c')};
String[] stringArray = {"liu","lu","hhh"};

sort(intArray);
sort(doubleArray);
sort(charArray);
sort(stringArray);

System.out.print("sorted integer objects: ");
printList(intArray);
System.out.print("sorted Double objects:  ");
printList(doubleArray);
System.out.print("sorted char objects:   ");
printList(charArray);
System.out.print("sorted string objects:  ");
printList(stringArray);

}

public static <E extends Comparable<E>> void sort(E[] list) {  //可以对任何对象类型的数组进行排序
E currentMin;
int currentMinIndex;

for(int i = 0; i < list.length -1 ;i++) {
currentMin = list[i];
currentMinIndex = i;
for (int j = i+1 ; j < list.length; j++) {
if(currentMin.compareTo(list[j])>0) {
currentMin = list[j];
currentMinIndex = j;
}
}

if(currentMinIndex != i) {
list[currentMinIndex] = list[i];
list[i] = currentMin;
}
}
}

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

 泛型类型定义为<E extends Comparable <E>>,这具有两个含义:
首先,它指定E 是Comparable 的子类型;其次,它还指定进行比较的元素是E 类型的。

通配泛型

通配泛型类型有三种形式一一?? extends T 或者? super T ,其中T是泛型类型。
第一种形式 ? 称为 非受限通配 (unbounded wildcard) ,它和? extends Object 是一样的。表示任何一种对象类型。

public static void print(GenericStack <?> stack)

第二种形式 ? extends T 称为 受限通配 (bounded wildcard),表示T 或T 的一个子类型

public static double max(GenericStack <? extends Number> stack)
所以max(new GenericStack <Integer/Double>()) 都是合法的

第三种形式 ? super T 称为 下限通配 (Iower-bound wildcard) ,表示T 或T 的一个父类型

GenericStack stack1 = new GenericStack<>(); 一个字串栈
GenericStack stack2 = new GenericStack<>(); 一个对象栈
如果要调用下面的add(stack1,stack2)方法,stack2就应该申明为 <? super T>

public static double max(GenericStack<? extends Number> stack) {  // 子类型
double max = stack.pop().doubleValue();

while(!stack.isEmpty()) {
double value = stack.pop().doubleValue();
if(value>max)
max = value ;
}
return max;
}

public static <T> void add(GenericStack<T> stack1,
GenericStack<? super T> stack2) {
while(!stack1.isEmpty())
stack2.push(stack1.pop());
}

 详细讲解

public class SuperExtneds {
    public static void main(String[] args) {
        List<? extends Apple> list1 = getExtendsAppleList();
        Apple first = list1.get(0);
        list1.add(null);

        first.sayHello();

        //编译错误
        //list.add(new Fruit());
        //list.add(new Apple())
        //list.add(new Object());

        List<? super Apple> list2 = getSupperAppleList();
        list2.add(new Apple());
        list2.add(new HFSApple());
        //只能返回obj
        Object item = list2.get(0);

        //编译 错误
        //Fruit aa1 = list2.get(0);
        //Apple aa2 = list2.get(0);
        //HFSApple aa3 = list2.get(0);
        //Orange aa4 = list2.get(0);
        //list2.add(new Fruit());

        List<Fruit> list3 = new ArrayList<>();
        handlerApple(list3);
        list3.get(0).sayHello();

    }

    public static List<? extends Apple> getExtendsAppleList() {
        List<Apple> appleList = new ArrayList<>();
        appleList.add(new Apple());
        appleList.add(new HFSApple());

        //编译错误
        //return new ArrayList<Fruit>();

        return appleList;
    }

    public static List<? super Apple> getSupperAppleList() {
        List<Fruit> fruitList = new ArrayList<>();
        fruitList.add(new Fruit());
        fruitList.add(new Orange());

        //编译错误
        //return new ArrayList<HFSApple>();

        return fruitList;
    }

    public static void handlerApple(List<? super Apple> list) {
        list.add(new HFSApple());
        list.add(new Apple());

        //编译报错
        //list.add(new Fruit());
        //list.add(new Orange());
    }
}

class Fruit {
    public void sayHello() {
        System.out.println("hello");
    }
}

class Apple extends Fruit {
}

class HFSApple extends Apple {
}

class Orange extends Fruit {
}

 List<? extends Apple> list1 只能获取某个类型的对象,不能向其中添加Fruit,Orange甚至Apple的实例,这是为什么呢?原来是? expends Apple的含义时Apple或者Apple的子类,这是一个不确定的类型,那么List就不知道到底应该装入什么类型的对象,所以不允许添加任何类型的对象,但是奇怪的事我们可用向其中添加null,因为null表示任何对象.

List<? super Apple> list2能够向其中添加Apple,HFSApple等Apple及其子类,但是不能添加Apple的父类,在获取list2中的对象时只能是Object,这是为什么呢?原来? super Apple表示Apple的或者Apple的父类,有一个有限长度的类型范围,直到Object,既然有范围,就可以在这个范围之上向list2添加Apple或者Apple的子类,但是在获取的时候,因为时一个有限的范围,list2不知道应该返回具体持有的是什么类型,所以只能返回所有类型的父类Objct.

list3 简单表述了? super Apple的一个应用场景,

那么我们做出如下总结:

  • extends 可用于的返回类型限定,不能用于参数类型限定。

  • super 可用于参数类型限定,不能用于返回类型限定。

泛型的擦除和限制

List<Integer> list0 = new ArrayList<>();
List<String> list1 = new ArrayList<>();
System.out.println(list0.getClass()==list1.getClass());

运行结果:true

泛型不能使用instanceof,因为类型擦除,但是可用动态的调用isInstance()

泛型中不允许使用new T()这样创建新的对象,但是我们可用使用其他方法创建,第一种时使用newInstance(),这种方法必须保证有默认的构造函数;另一种是构造一个工厂对象,由工厂生成对象.

注意

//在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加
//public <T> T showKeyName(Generic<T extends Number> container),编译器会报错:"Unexpected bound"
public <T extends Number> T showKeyName(Generic<T> container){
    System.out.println("container key :" + container.getKey());
    T test = container.getKey();
    return test;
}

下面的这个例子是不可以的:

List<String>[] ls = new ArrayList<String>[10];

而使用通配符创建泛型数组是可以的,如下面这个例子:

List<?>[] ls = new ArrayList<?>[10];

这样也是可以的:

List<String>[] ls = new ArrayList[10];
(0)

相关推荐

  • 如何使用 Java 泛型来避免 ClassCastException

    泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用. 一句话解释什么是泛型? 泛型是相关语言特性的集合,它允许类或方法对各种类型的对象进行操作,同时提供编译时类型安全性检查 ...

  • 前缀、中缀、后缀表达式以及逆波兰计算器

    前缀.中缀.后缀表达式 前缀表达式 波兰表达式 前缀表达式的运算符位于操作数之前 就比如(3 4)x5-6对应的前缀表达式就是 - X 3 4 5 6 前缀表达式在计算机中的计算机求值 从右至左扫描表 ...

  • Java泛型——泛型矩阵类

    Java泛型--泛型矩阵类 所有矩阵,加法和乘法操作都是类似,所以设计一个父类,不用管元素类型,目的描述所有类型的矩阵共享的通用操作 创建若干适用于指定矩阵类型的子类,实例:两种类型int和Ratio ...

  • 10 道 Java 泛型面试题

    10 道 Java 泛型面试题

  • Java泛型详解:<T>和Class<T>的使用。泛型类,泛型方法的详细使用实例

    一.引入1.泛型是什么首先告诉大家ArrayList就是泛型.那ArrayList能完成哪些想不到的功能呢?先看看下面这段代码:[java] view plain copyArrayList<S ...

  • Java泛型简明教程

    前面我们提到Java集合有个缺点,就是把一个对象"丢进"集合里之后,集合就会"忘记"这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了Object ...

  • (16条消息) 【小家Java】你真的了解Java泛型参数吗?细说java.lang.reflect.Type(ParameterizedType、TypeVariable、WildcardType...)

    每篇一句 不要每天看着励志的句子,过着颓废的生活 前言 咋一看标题,你可能会说.不就是泛型吗,平时都使用着呢,没什么难的吧. 感觉了解了,但是真正的深入才知道自己了解甚少! 没有泛型的时候,只有原始类 ...

  • Java基础之:泛型

    Java基础之:泛型 在不使用泛型的情况下,在ArrayList 中,添加3个Dog. Dog对象含有name 和 age, 并输出name 和 age (要求使用getXxx()). package ...

  • java学习——41、带有泛型参数的方法

    本篇接上篇:java学习--40.泛型 类中的方法有两种:静态方法和实例方法. 带有泛型参数的方法语法格式如下: [public][static] <类型参数列表>返回值类型 方法([参数 ...

  • Java学习——40、泛型

    从版本5.0开始,Java支持包含数据类型作为参数的类定义.这些参数被称为泛型. 泛型通过为类.接口及方法设置类型参数,使一个类或一个方法可以在多种类型的对象上进行操作,从而减少数据类型转换,增加软件 ...