Java - 泛型

in TCEHJava with 0 comment

一、泛型简介

官方定义

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数
这种参数类型可以用在类、接口和方法中创建,分别称为泛型类、泛型接口、泛型方法

通俗解释

通俗的讲,泛型就是操作类型的 占位符,即:假设占位符为T,那么此次声明的数据结构操作的数据类型为T类型。

二、泛型作用

泛型要解决的问题本质上还是代码复用。换句话说,使用泛型可以很好的解决数据类型的统一问题。比如说,多个方法实现的功能(传入a、b,并输出至控制台)是一样的,仅仅是形参类型(String、int)不一致,如果用泛型仅需一个方法即可。

示例:

    private static void println(String s1, String s2) {
        System.err.println(s1);
        System.err.println(s2);
    }

    private static void println(int s1, int s2) {
        System.err.println(s1);
        System.err.println(s2);
    }

    private static <T> void println(T t1, T t2) {
        System.err.println(t1);
        System.err.println(t2);
    }

泛型实现父类为Number的基本数据类型相加

private static <T extends Number> Number add(T t1, T t2) {
        Number sum = null;
        if (t1.getClass().equals(Integer.class) && t2.getClass().equals(Integer.class)) {
            sum = t1.intValue() + t2.intValue();
        } else if (t1.getClass().equals(Float.class) && t2.getClass().equals(Float.class)) {
            sum = t1.floatValue() + t2.floatValue();
        } else if (t1.getClass().equals(Double.class) && t2.getClass().equals(Double.class)) {
            sum = t1.doubleValue() + t2.doubleValue();
        } else if (t1.getClass().equals(Long.class) && t2.getClass().equals(Long.class)) {
            sum = t1.longValue() + t2.longValue();
        } else if (t1.getClass().equals(Short.class) && t2.getClass().equals(Short.class)) {
            sum = t1.shortValue() + t2.shortValue();
        }
        return sum;

    }

三、泛型标记符

E - Element (在集合中使用,因为集合中存放的是元素)

T - Type(Java 类)

K - Key(键)

V - Value(值)

N - Number(数值类型)

? - 通配符,表示不确定的Java类型

在通配符“?”上又衍生出了两个子符号:

例如:? extends Number,表示只能是Number或者是Number的子类Integer等;

例如:? super String,表示只能是String或者是String的父类(Object)

四、泛型的使用

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

泛型类

泛型的类型参数只能是类类型(包括自定义类),不能是基本类型如int、float
泛型类基本写法

class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
  private 泛型标识 /*(成员变量类型)*/ var; 
  .....

  }
}

定义一个泛型类

package com.back.web;

public class GenericsTest {
    public static void main(String[] args) {
        // 传入的实参类型需与泛型的类型参数类型相同,即为String.
        Genrics<String> genricsString = new Genrics<String>("abc");
        // 传入的实参类型需与泛型的类型参数类型相同,即为Integer.
        Genrics<Integer> genricsInteger = new Genrics<Integer>(123);
        System.out.println(genricsString.getKey());// 输出 abc
        System.out.println(genricsInteger.getKey());// 输出 123
        println(genricsInteger);// 输出 abc
        println(genricsString);// 输出 123
    }

    /**
     * 当为了可以传递多种数据类型时,可以使用通配符?来标识,或 ? extends 限制上限 或 ? super 限制下限
     *
     */
    public static void println(Genrics<?> genrics) {
        System.out.println(genrics.getKey());
    }

}

// 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
// 在实例化泛型类时,必须指定T的具体类型
class Genrics<T> {
    // key这个成员变量的类型为T,T的类型由外部指定
    private T key;

    public Genrics(T key) { // 泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey() { // 泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }

}

泛型接口

泛型接口与泛型类的定义及使用基本相同。

package com.back.web;

public class GenericsTest {
    public static void main(String[] args) {
        GenricsFruit genricsString = new GenricsFruit("apple");
        System.out.println(genricsString.get());// 输出 apple
    }
}

/**
 * 方法一  
 * 在实现类上继续定义泛型,同时此泛型在接口上继续使用
 */
class Genrics<T> implements Generator<T> {

    @Override
    public T get() { // 返回类型必须与接口中定义的一致

        return null;
    }

}

/**
 * 方法二  
 * 在子类上设置具体类型
 */
class GenricsFruit implements Generator<String> {
    private String fruit = null;

    public GenricsFruit(String fruit) {
        this.fruit = fruit;
    }

    @Override
    public String get() {

        return this.fruit;
    }

}

/**
 * 定义一个泛型接口
 */
interface Generator<T> {
    public T get();
}

泛型方法

对于泛型除了可以定义在类上之外,也可以在方法上进行定义,而在方法上定义泛型的时候,这个方法不一定非要在泛型类中定义。

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

1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法,只是因为泛型类中已经声明过标识符,所以泛型类成员可以使用该标识符。
3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。

示例

public static <T> String println(T a) {

    return null;
}

五、类型擦除

泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除

先看一个例子

package com.back.web;

import java.util.ArrayList;
import java.util.List;

public class GenericsTest {
    public static void main(String[] args) {
        List<String> t1 = new ArrayList<String>();
        List<Integer> t2 = new ArrayList<Integer>();

        System.out.println(t1.getClass() == t2.getClass());
        // 输出结果为true
        // 是因为 List<String>和 List<Integer>在 jvm 中的 Class 都是 List.class。
        // 会转化成  List t1 = new ArrayList();
        //          List t2 = new ArrayList()
        // 需用 Beyond compare 反编译。配置工具-文件格式-.class ,打开.class 文件即可。其余反编译
        // (如jd-gui)工具,可能可以看到泛型。因为类似jd-gui等功能比较强大,会自动反编译出泛型类型
    }

}

再看一个例子,更容易理解

package com.back.web;

import java.util.ArrayList;
import java.util.List;

public class GenericsTest {
    public static void main(String[] args) {
        List<String> t1 = new ArrayList<String>();
        List<Integer> t2 = new ArrayList<Integer>();

        System.out.println(t1.getClass() == t2.getClass());
    }

    public static void println(Genrics<Integer> genrics) {
        System.err.println(genrics.getKey());
    }
        //此时,println方法会报错,并提示“Erasure of method println(Genrics<Integer>) 
        //is the same as another method in type GenericsTest”。也就是受到类型擦除的影响,
        //参数Genrics<Integer>和Genrics<String>均会转化成Genrics,因此会造成方法重名,无法重载。
    public static String println(Genrics<String> genrics) {

        return null;
    }

}

//// 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//// 在实例化泛型类时,必须指定T的具体类型
class Genrics<T> {
    // key这个成员变量的类型为T,T的类型由外部指定
    private T key;

    public Genrics(T key) { // 泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey() { // 泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }

}

泛型信息只存在于代码编译阶段,因此我们可以利用反射绕过编译阶段,

package com.back.web;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class GenericsTest {
    public static void main(String[] args) {
        List<String> t1 = new ArrayList<String>();
        try {
            Method method = t1.getClass().getDeclaredMethod("add", Object.class);

            method.invoke(t1, "test");
            method.invoke(t1, 123);
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (Object o : t1) {
            System.out.println(o);
            // 输出结果test、123
        }
    }
}
Comments are closed.