• [06] 泛型



    1、泛型

    泛型也称之为参数化类型,就是在定义类、接口和方法时,不去指定具体的类型,而是把类型也当成一种参数,在运行时再去指定具体的类型。

    Java中泛型的作用:
    • 编译安全,避免非法类型的传入
    • 对于如集合而言,省略了强制类型转换
    • 可读性,从字面上就可以判断内容类型



    2、泛型的声明

    • 泛型的声明都包裹在尖括号 <> 中,多个泛型类声明用逗号隔开
    • 泛型声明的类型参数只能代表引用数据类型,不能是基本数据类型(像int、double等)
    • 泛型的命名通常用一个大写字母定义,没有特别的约束,但是有些约定俗成的规范(当然,你也可以不遵守)
      • E:Element 常用在集合中作为元素,如List<E>
      • K,V:Key,Value,代表Map的键值对
      • N:Number,数字
      • T:Type,类型,如String,Integer等

    2.1 泛型类

    所谓的泛型类就是具有一个或多个类型参数的类,泛型声明在该类的类名之后。如下例:
    public class Pair <E1, E2> {  
        private E1 element1;
        private E2 element2;
    
        public E1 getElement1() {
            return element1;
        }
    
        public void setElement1(E1 element1) {
            this.element1 = element1;
        }
    
        public E2 getElement2() {
            return element2;
        }
    
        public void setElement2(E2 element2) {
            this.element2 = element2;
        }
    }

    2.2 泛型接口

    接口其实本身类似于一个很特殊的类,所以泛型接口的声明和泛型类的声明方式一致,泛型声明在接口的接口名之后。如下例:
    public interface IPool <T> {
        T get();
        int add(T t);
    }

    2.3 泛型方法

    泛型方法,即带有类型参数的方法。泛型声明在方法的返回参数之前。如下例:
    public class ArrayAlg {
        public static <T> T getMiddle(T[] a) {
            return a[a.length / 2];
        }
    }

    再举个例子,像ArrayList中我们知道可以存入任意Object,如果定义了泛型取出时则不必进行强制转换,其get调用了名为elementData的方法:
    E elementData(int index) {
        return (E) elementData[index];
    }

    2.4 类型擦除

    我们知道,Java有Java编译器和Java虚拟机,编译器将Java源代码转换为.class文件,虚拟机加载并运行.class文件。

    对于泛型类,Java编译器会将泛型代码转换为普通的非泛型代码,将类型参数T擦除,插入必要的强制类型转换。Java虚拟机实际执行的时候,它是不知道泛型这回事的,它只知道普通的类及代码。

    如果限定了类型,对应的泛型参数会替换为限定的类,如果没有限定类型,则替换为Object。



    3、泛型参数的限定

    我们知道,泛型的引入实际上和Object有些类似,允许你放入任何的引用数据类型,但是也正因为它无法确定一个类型范围,所以你无法通过这个定义的泛型参数来调用方法,如下例中有Animal类,在某个泛型方法中期望传入为Animal类时执行其eat方法是编译无法通过的:
    public abstract class Animal {
    
        public abstract void eat();
    
    }
    
    //在其他类中定义execute方法,期望当传入参数为Animal类时可以执行eat方法
    public static <E> void execute(E ele) {
        ele.eat(); //编译错误
    }

    但是如果我们对泛型参数做一个限定,规定它只要是Animal的子类就可以:
    public static <E extends Animal> void execute(E ele) {
        ele.eat();
    }

    这就是泛型类型的限定。

    3.1 泛型参数的上界

    public class NumberGenericPool< T extends Number>
    • 上述方式的声明规定了NumberGenericPool类所能处理的参数类型和Number有继承关系
    • extends关键字声明的上界既可以是一个类,也可以是一个接口
    • 当泛型参数如此声明时,在实例化一个泛型类时,需要明确类型必须为上界类型或其子类

    另外,需要注意的是,如果给泛型参数限定多个类型,则需要使用符号&
    <T extends Runnable & Serializable>
    同时要注意的是,限定多个类型时,class只能放在第一个且只能有一个(单继承),interface则放在之后。

    3.2 泛型参数的下界

    相对的,泛型参数的下界限定关键字为super,用以固定泛型参数的类型为某种类型或其父类
    List<? super Fruit> flist = new ArrayList<Fruit>();

    注意,super是不能使用多个限定的符号&的:<T super File & Runnable> // 错误

    3.3 通配符?

    ?代表了一个未知的类型。

    ?和 T 的区别在于,两者都可以表示不确定的类型,但是T的话,在函数中可以进行操作,而?则不行:
    • 类型参数“<T>”主要用于声明泛型类或泛型方法
    • 无界通配符“<?>”主要用于使用泛型类或泛型方法

    通配符正因为其不确定性,所以不能用来声明泛型,如下是错误的:
    //Error Example
    class Box<?> {
      private ? item1;
      private ? item2;
    }

    所以通配符是拿来使用定义好的泛型,再次注意,是使用,比如之前提到过的例子:
    List<? super Fruit> flist = new ArrayList<Fruit>();

    3.3.1 例子1

    看个例子来理解这个所谓的通配符,参考《Java 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同》中 胖胖 的回答:

    假如有泛型类的盘子:
    class Plate<T> {
      private T item;
      private Plate(T t) {item = t;}
      public void set(T t) {item = t;}
      public T get() {return item;}
    }

    现在我们要使用这个泛型类Plate,用它定义一个水果盘子,逻辑上认为水果盘子可以放苹果:
    Plate<Fruit> p = new Plate<Apple>(new Apple());

    实际上编译无法通过,编译器认为苹果“IS-A”水果,但是装苹果的盘子“IS-NOT-A”装水果的盘子。为了解决这个问题,出现了:
    • <? extends T> 上界通配符
    • <? super T> 下界通配符

    Plate<? extends Fruit> p = new Plate<Apple>(new Apple());
    这样就可以定义一个“啥水果都能放的盘子”,但同时要注意的是,这种方式get有效,却会让往盘子放东西的set方法失效。取出来的操作只能存放在Fruit或者Fruit的父类中;而set的时候并不能确定类型,我是会放入Apple,还是Orange呢,编译器并不知道,所以不让你通过编译。

    所以通配符<?>和类型参数<T>的区别就在于,对编译器来说所有的T都代表同一种类型。但通配符<?>没有这种约束,Plate<?>单纯的就表示:盘子里放了一个东西,是什么我不知道。

    同样,再回过头来理解:
    // compile error
    //		List <? extends Fruit> appList2 = new ArrayList();
    //		appList2.add(new Fruit());
    //		appList2.add(new Apple());
    //		appList2.add(new RedApple());
    
    		List <? super Fruit> appList = new ArrayList();
    		appList.add(new Fruit());
    		appList.add(new Apple());
    		appList.add(new RedApple());

    可以看到,extends时集合的set操作编译错误;super时集合的set操作编译通过。

    如果是<? extends Fruit>,那么定义了该List是某种水果,所以你取出来的肯定是水果(继承链向下走方法固定会拥有父类方法,所以元素可用),但是不允许你放,因为不确定放什么,是苹果?香蕉?所以这种List禁止插入,只能读取。

    而<? super Fruit>则定义了该List的元素只要是水果的父类即可,却没有统一的根(除了Object),所以不能确定取出来时候元素是什么,所以元素你没法用(继承链向上走方法层出不穷无法确定),所以只能做插入,不能做读取。

    相当于<? extends Fruit>确定了取出来的一定是水果,但是不能确定放进去的具体类型所以禁止插入;而<? super Fruit>不确定取出来的类型所以只能插入不能做读取。

    3.3.2 例子2

    最后,再看一个例子:Class类也是一个泛型类 class Class<T> implements java.io.Serializable ... 

    这意味着要求你在使用时候传入某个具体的类作为T进行替换,比如Class<Fruit>,但是我在使用时还是不确定类型,就可以Class<? extends Fruit>进行限定。

    所以,?使用的前提是泛型T已经被定义,我们不确定传入某个参数,比如Class<?>,这个符号的前提是Class<T>已经被某处定义。在定义Class<T>的时候,我们不用?而用T,因为T要在Class的定义体里被用到,要是个名词,而?号是无法被代码使用的。

    贴个代码示意下Class<?>的用法,在已经定义Class为泛型类的情况下,期望该类型参与运算,但是此时还是无法确定具体的类型,于是给一个限定范围:
    @Transactional
    protected static boolean hasValue(Class<? extends Entityable> entityClass, String property, Object value) {
    	//property只能由数字_字母组成
    	if(!propertyPattern.matcher(property).matches())
    		throw new IllegalArgumentException("property值只能由字母数字和下划线组成");
    	String hql = "select count(t) from " + entityClass.getName() + " t where t." + property + "=:value";
    	org.hibernate.Query query = sessionFactory.getCurrentSession().createQuery(hql);
    	query.setParameter("value", value);
    	return ((Number) query.uniqueResult()).longValue() > 0;
    }



    4、其他

    4.1 不能实例化泛型对象

    T t= new T();//error 
    T.class.newInstance();//error 
    T.class;//error

    解决办法是传入Class<T> t参数,调用t.newInstance():
    public <T> void sayHi(Class<T> c){ 
        T t = null; 
        try { 
            t = c.newInstance(); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
        System.out.println("Hi "+t); 
    }

    4.2 不能在泛型类的静态域中使用泛型类型

    public class Singleton<T>{
        private static T singleton; //error
        public static T getInstance(){} //error
        public static void print(T t){} //error 
    }

    但是,静态的泛型方法可以使用泛型类型:
    public static <T> T getInstance(){return null;} //ok 
    public static <T> void print(T t){} //ok

    可以这样理解:
    • 泛型类中,<T>称为类型变量,实际上就相当于在类中隐形的定义了一个不可见的成员变量:“private T t;”,这是对象级别的,对于泛型类型变量来说是在对象初始化时才知道其具体类型的。而在静态域中,不需要对象初始化就可以调用,这是矛盾的;
    • 静态的泛型方法,是在方法层面定义的,就是说在调用方法时,T所指的具体类型已经明确了。



    5、参考链接



  • 相关阅读:
    堆表修改内幕
    HBase集群安装
    ZooKeeper群集安装
    CentOS 6+Hadoop 2.6.0分布式集群安装
    Hive中使用Python实现Transform时遇到Broken pipe错误排查
    SQL Server审计功能入门:SQL Server审核 (SQL Server Audit)
    SQL Server审计功能入门:CDC(Change Data Capture)
    SQL Server审计功能入门:更改跟踪(Change Tracking)
    Redis 学习笔记(一) 字符串 SDS
    asp.net mvc core 管道以及拦截器初了解
  • 原文地址:https://www.cnblogs.com/deng-cc/p/7512109.html
Copyright © 2020-2023  润新知