• 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

  • 相关阅读:
    httpclient 发送 json数据,微信security.msgSecCheck,
    win10 输入法,添加美式键盘,调整顺序
    activiti与spring的集成
    spring 中实例化Bean的三种方式
    工作流activiti的HelloWorld
    工作流Activiti的前置准备工作。
    Eclipse中配置约束(DTD,XSD)
    微信开发中,本地缓存,不同步的问题
    微信开发中碰到的坑,json传值,前台遍历json对象,跨页面转值,navigate层级限制
    用pageInfo对List手工分页
  • 原文地址:https://www.cnblogs.com/leizzige/p/12290534.html
Copyright © 2020-2023  润新知