Loading

Java泛型

1、什么是泛型

Java 泛型(generics)是 JDK5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发人员在编译时检测到非法的类型。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

2、泛型的使用

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

2.1、泛型类

泛型类型用于类的定义中,被称为泛型类。

通过泛型可以完成对一组类对外开放相同的接口。最典型的就是各种容器类,比如:List、Set、Map。

一个普通的泛型类:

/**
 * @param <T>
 * @author Arley
 * date: 2020/02/09 night 20:56
 * desc: 泛型类
 */
public class Generic<T> {

    /* key这个成员变量的类型为T,T的类型由外部指定 */
    private T key;

    public Generic(){

    }

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

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

定义的泛型类,并不以一定非得传入泛型类型实参!

如果在使用泛型的时候传入泛型实参,则会根据传入的泛型实参做出限制。

如果没有传入泛型实参,在泛型类中用泛型的方法或成员变量定义的类型可以为任何类型。

下面我们看一个栗子:

Generic generic1 = new Generic(666666);
Generic generic2 = new Generic("666666");
Generic generic3 = new Generic(666666L);
Generic generic4 = new Generic(true);

2.2、泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,下面是一个简单的泛型接口:

/**
 * @param <T>
 * @author Arley
 * desc:泛型接口
 */
public interface Generator<T> {
    T method();
}

当实现泛型接口的类,未传入泛型实参时候:

/**
 * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
 * 即:class FruitGenerator<T> implements Generator<T>{
 * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,
 * 编译器会报错:"Unknown class"
 */
public class FruitGenerator<T> implements Generator<T> {
    @Override
    public T method() {
        return null;
    }
}

当实现泛型接口的类,传入泛型实参的时候:

/**
 * 传入泛型实参时:
 * 定义一个类实现这个接口,虽然我们只创建了一个泛型接口 Generator<T>
 * 但是我们可以为 T 传入无数个参数类型,形成了无数个参数的 Generator 接口
 * 在实现类实现泛型接口时,如果将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
 *  即:Generator<String>, public T method()中的 T 也要替换成 String类型
 */
class FruitGenerator2<T> implements Generator<String> {
    @Override
    public String method() {
        return "good";
    }
}

2.3、泛型方法

在 Java 中,泛型类的定义十分简单,但是泛型方法就有点复杂了。

尤其是我们看到大多数的泛型类中的成员方法也都使用了泛型,有的甚至在泛型类中包含泛型方法。

泛型类:是指在实例化类的时候指定泛型的具体类型;

泛型方法:实在调用方法的时候指明泛型的具体类型;

泛型方法介绍:

	/**
     * @param Clazz 传入的泛型实参
     * @return 返回值为 T 类型
     * @throws InstantiationException
     * @throws IllegalAccessException
     * desc:
     *  1): public 与返回值中间 <T> 非常重要,可以理解为声明此方法为泛型方法
     *  2): 只有声明了 <T> 的方法才是泛型方法,泛型类中使用了泛型的成员方法不是泛型方法
     *  3): <T> 表明该方法将使用泛型类型 T,此时才可以在泛型方法中使用 T
     *  4): 与泛型类的定义一样,此处 T 可以随便写为任何标识
     */
public <T> T genericMethod(Class<T> Clazz){
    T  instance = null;
    try {
        instance = Clazz.newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return instance;
}
2.3.1、泛型类中的泛型方法

泛型方法可以出现在任何地方和任何场景中使用。

但是有一种情况特别特殊,当泛型方法出现在泛型类中的时候,我们再来看一下:

/**
 * @author Arley
 * desc:泛型类中的泛型方法
 */
@SuppressWarnings("all")
class Generic2<T> {

    /**
     * 这里的 T 只能是泛型类实参或者实参的子类
     * @param t
     */
    public void test1(T t) {
        System.out.println(t.toString());
    }

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

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

    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        Apple apple = new Apple();
        Person person = new Person();

        Generic2<Fruit> generic2 = new Generic2<>();
        
        /* apple是 Fruit 的子类,这里编译通过*/
        generic2.test1(fruit);
        generic2.test1(apple);

        /* 泛型指定实参为 Fruit,这里传入 Person 编译不通过*/
        //generic2.test1(person);

        /* 使用这个方法两个都可编译通过 */
        generic2.test2(apple);
        generic2.test2(person);

        /* 使用这个方法两个都可编译通过 */
        generic2.test3(apple);
        generic2.test3(person);
    }
}
2.3.2、泛型方法和可变参数
	/**
     * 泛型方法
     * @param args 参数可变
     */
    public <T> void pringMsg(T... args){
        for (T arg : args) {
            System.out.print(arg);
        }
    }
pringMsg("11","22","33");
pringMsg(11,22,33);
pringMsg(1.1,2.2,3.3);

2.3.3、静态方法与泛型

静态方法有一种情况需要注意,那就是在类中的静态方法上使用泛型:

静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

即:如果静态方法要使用泛型,必须将静态方法定义为泛型方法。

/**
 * @author Arley
 * date:2020/02/10 forenoon 10:54
 * desc:静态方法泛型
 */
@SuppressWarnings("all")
public class StaticGenerator<T> {

    /**
     * 如果在类中定义使用泛型的静态方法,需要将这个方法定义为泛型方法
     * 即使静态方法中要使用泛型类中已经声明过的泛型也不可以
     * 如:
     *      public static void test(T t)
     *      此时会编译错误!
     * @param t
     * @param <T>
     */
    public static <T> void test(T t) {
        System.out.println(t.toString());
    }
}

2.4、泛型上下边界通配符

在使用泛型的时候,我们可以为传入的泛型类型实参进行上下文边界的限制。

如:类型实参只允许传入某种类型的父类或者某种类型的子类。

2.4.1、上边界通配符

利用 ? extends Number 形式的通配符,可以实现泛型的向上转型。

  • 为泛型添加上边界通配符,即传入的类型实参必须是指定的类型的子类型。
/**
 * @author Arley
 * desc:泛型的上边界通配符
 */
@SuppressWarnings("all")
class Generic3<T> {

    private T key;

    public Generic3() {

    }

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

    public T getKey() {
        return key;
    }

    /**
     * 为泛型方法添加上边界通配符,传入实参必须是Number的子类
     * @param obj
     */
    public static void test(Generic3<? extends Number> obj) {
        System.out.println(obj.getKey());
    }

    public static void main(String[] args) {
        Generic3<String> generic1 = new Generic3("11");
        Generic3<Integer> generic2 = new Generic3(11);
        Generic3<Long> generic3 = new Generic3(11L);
        Generic3<Float> generic4 = new Generic3(11F);
        Generic3<Double> generic5 = new Generic3(1.1);

        /* 因为 String 不是 Number 的子类,这行代码编译错误*/
        //test(generic1);
        test(generic2);
        test(generic3);
        test(generic4);
        test(generic5);
    }
}

如果我们将泛型类修改一下:

class Generic3<T extends Number> 
/* 这一行代码也会直接报错 */
Generic3<String> generic1 = new Generic3("11");

声明上边界通配符需要注意的地方:

    /**
     * 在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,即在泛	  *	型声明的时候添加
     * 如:
     *      public <T> T showKeyName(Generic<T extends Number> container)
     *      这样编译会报错
     */
    public <T extends Number> T test2(Generic3<T> t){
        return t.getKey();
    }

2.4.2、下边界通配符

通配符的另一个方向是 “超类型的通配符“ ? super TT 是类型参数的下界。使用这种形式的通配符,我们就可以 ”传递对象” 了

  • 为泛型添加下边界通配符,即传入的类型实参必须是指定的类型的父类型,直至Object。
/**
 * @author Arley
 * desc:泛型的下边界通配符
 */
@SuppressWarnings("all")
class Generic4<T> {

    private T key;

    public Generic4() {

    }

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

    public T getKey() {
        return key;
    }

    /**
     * 为泛型方法添加下边界通配符,传入实参必须是Apple的父类,直至Object
     * @param obj
     */
    public static void test(Generic4<? super Apple> obj) {
        System.out.println(obj.getKey());
    }

    public static void main(String[] args) {
        Object obj = new Object();
        Fruit fruit = new Fruit();
        Apple apple = new Apple();
        Person person = new Person();
        Generic4<Object> generic1 = new Generic4<>(obj);
        Generic4<Fruit> generic2 = new Generic4(fruit);
        Generic4<Apple> generic3 = new Generic4(apple);
        Generic4<Person> generic4 = new Generic4(person);
        
        /* Object Fruit 是Apple父类*/
        test(generic1);
        test(generic2);
        test(generic3);

        /* Person 不是 Apple 的父类,编译不通过*/
        //test(generic4);
    }

项目地址:https://github.com/CrazyArley/Java-Learning

If you’re going to reuse code, you need to understand that code!
posted @ 2020-02-10 12:42  不颓废青年  阅读(155)  评论(0编辑  收藏  举报