• Java泛型的那些事


    1.泛型概述

    1.1.为什么使用泛型

    没有泛型,在编写代码时只能使用具体类型或Object类型,无法做到使用者想要使用什么类型就是类型。比如:创建一个方法,形参需要指定需要使用的数据类型,在创建方法之初就已经决定了该方法可以处理的数据类型,这大大限制了编程的灵活性。正因如此,才出现了在使用时才决定具体类型是什么的泛型编程。

    1.2.泛型是什么

    泛:广泛、普遍,非具体的东西,泛型就是定义之初用符号表示不具体的类型,在使用的时候才动态地指定具体的类型。更应该明白这种泛型编程设计思想,使用泛型带来的好处是代码更加简洁、更加灵活、使程序更加健壮(编译期没警告,运行期不会出现类强转异常--ClassCastException)。

    2.泛型接口、类、方法

    泛型允许在定义接口、类、方法时使用,将在声明变量、创建对象、调用方法时动态地指定。

    2.1.泛型接口

    定义泛型接口:比如集合中的List接口

    // 定义接口时指定泛型:E,E类型在接口中就可以作为类型使用
    public interface List<E> extends Collection<E>{
        ……
        boolean add(E e);
        Iterator<E> iterator();
        ……
    }
    // 定义接口时指定泛型:K 和 V,K和V类型在接口中就可以作为类型使用
    public interface Map<K,V>{
        ……
        Set<K> keySet();
        Collection<V> values();
        Set<Map.Entry<K, V>> entrySet();
        ……
    }
    

    使用泛型接口:List接口的泛型类型E,在使用时指定为具体类型String

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();// 指定泛型类型E=String
        list.add("我只认识字符串");//boolean add(E e); 等价于boolean add(String e);
        Iterator<String> iterator = list.iterator();//Iterator<E> iterator();
    
        while (iterator.hasNext()){
            String next = iterator.next();//不需要强转为String
            System.out.println(next);
        }
    }
    

    关于泛型接口Map<K,V> 集合怎么用,就自行编写感受下。

    2.2.泛型类

    普通泛型类

    定义泛型类

    public class DemoFx<D> {
        private D dd;
        public D getDd(){
            return this.dd;
        }
        public void setDd(D dd){
            this.dd = dd;
        }
    }
    

    使用泛型类

    public static void main(String[] args) {
        DemoFx<String> stringDemoFx = new DemoFx<>();
        stringDemoFx.setDd("我是字符串类型");
        System.out.println(stringDemoFx.getDd());
    }
    

    泛型类的继承与实现

    定义泛型类:以ArrayList 类为例,继承泛型抽象类和实现泛型接口:

    public class ArrayList<E> extends AbstractList<E> 
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
        ……
        public E get(int index) {
            rangeCheck(index);
            return elementData(index);
        }
        ……
    }
    

    使用泛型类

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();// 指定泛型类型E=String
        list.add("我只认识字符串");
        String s = list.get(0);// 返回值为String
    }
    

    2.3.泛型方法

    定义泛型方法:还是ArrayList案例

    public class ArrayList<E> extends AbstractList<E> 
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
        ……
        public <T> T[] toArray(T[] a) {
            if (a.length < size)
                // Make a new array of as runtime type, but my contents:
                return (T[]) Arrays.copyOf(elementData, size, a.getClass());
            System.arraycopy(elementData, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }
        ……
    }
    

    使用泛型方法:public <T> T[] toArray(T[] a)

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("s1");
        list.add("s2");
        list.add("sn");
        // public <T> T[] toArray(T[] a) 
        String[] strings = list.toArray(new String[list.size()]);
        System.out.println(Arrays.asList(strings));
    }
    

    3.类型通配符

    3.1.使用类型通配符

    通配符表示符号是问号<?>,它是未知类型,可以匹配任何类型,也称为无界通配符

    对比”通配符“和”泛型“创建的方法

    // 通配符定义
    public void foreach(List<?> list){
        for (int i =0 ;i<list.size();i++) {
            Object o = list.get(i);
            System.out.println(o.toString());
        }
    }
    // 泛型定义
    public <T> void foreach2(List<T> list){
        for(T t : list){
            System.out.println(t.toString());
        }
    }
    // 使用
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("s1");
        list.add("s2");
        list.add("sn");
        Demo demo = new Demo();
        demo.foreach(list); // 通配符
        demo.foreach2(list); // 泛型
    }
    

    通配符和泛型都可以实现相同的效果,并且泛型方法还可以使用本身定义的泛型类型,而通配符的”?“不可以当作数据类型来使用,所以通配符方法案例中只能用Object来接收list的元素,这也是通配符的缺点:无法确定未知类型是什么类型。

    所以通配符的出现到底有什么用呢?

    通配符为泛型的一种特例,无需定义既可在形参中使用的未知类型。

    泛型和通配符的区别

    • Java编译器把泛型【T】推断成T类型,在代码块中允许出现 T类型变量;而把通配符【?】推断成未知类型,不存在 ?类型变量;
    • Class<T>需要依赖于T,需要在方法声明时指定<T>,而Class<?>则不需要;

    这样可能更好理解泛型和通配符:泛型 强调的是类型,通配符 强调的是符号

    Class<?> 表示任意类型,但又不等同于Class<Object>,前者在类型不匹配的情况下只能够插入null,但是后者可以插入Object或任何Object对象的子类。

    例如:不能往List<?> list里添加任意类型的对象,除了null

    image

    3.2.类型上限

    通配符上限:<? extends Demo> ,通配符【?】的上限是Demo类型,既是<? extends Demo> 的范围是Demo或其子类类型。

    泛型上限:<T extends Demo> ,和通配符理解一样。类型上限如图

    image

    案例

    创建三个类DemoFather、Demo、DemoChildren,关系如上图

    public class DemoTest {
    
        public static void main(String[] args) {
            List<DemoChildren> demoChildrens = new ArrayList<>();
            demoChildrens.add(new DemoChildren());
            demoChildrens.add(new DemoChildren());
    
            DemoTest test = new DemoTest();
            test.testDemo(demoChildrens); // 通配符
            test.testDemo2(demoChildrens);// 泛型
    
        }
    
        // 通配符上限:控制list 集合允许的类型范围为Demo或其子类
        public void testDemo(List<? extends Demo> list){
            // 若无上限,这里只能用Object类型代替Demo类型
            for (Demo demo : list){
                System.out.println(demo.toString());
            }
        }
    
        // 泛型上限:控制list 集合允许的类型范围为Demo或其子类
        public <T extends Demo> void testDemo2(List<T> list){
            for (T t : list){
                System.out.println(t.toString());
            }
            // or
            for(Demo demo:list){
                System.out.println(demo.toString());
            }
        }
    
    }
    

    泛型的上限是在定义时确定上限<T extends Demo>;通配符直接在形参上确定上限<? extends Demo>。其实都很好理解,类型上限就是在一般写法的基础上加入范围“上限”,既是 extends xxx。

    源码的一些例子

    // 接口泛型上限
    public interface ObservableArray<T extends ObservableArray<T>> extends Observable {……}
    // 抽象类泛型上限
    public abstract class ArrayListenerHelper<T extends ObservableArray<T>> extends ExpressionHelperBase {……}
    public abstract class CellBehaviorBase<T extends Cell> extends BehaviorBase<T> {……}
    // 方法泛型上限
    public static <T extends Number> ReadOnlyLongProperty readOnlyLongProperty(final ReadOnlyProperty<T> property) {……}
    // 通配符上限
    void putAll(Map<? extends K, ? extends V> m);
    

    3.3.类型下限

    通配符下限:<? super Demo> ,通配符【?】的下限是Demo类型,既是<? super Demo> 的范围是Demo的父类类型。

    泛型下限:。主要是因为类型下限会令人困惑并且不是特别有用。为什么类型参数没有下限的一些解释:http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ107

    image

    public static void main(String[] args) {
        Demo demo = new Demo();
        List<Demo> demos = new ArrayList<>();
        demos.add(demo);
        DemoTest test = new DemoTest();
        test.testSuper(demos);
    
        DemoFather demoFather = new DemoFather();
        List<DemoFather> demoFathers = new ArrayList<>();
        demoFathers.add(demoFather);
        DemoTest test2 = new DemoTest();
        test2.testSuper(demoFathers);
    }
    
    public void testSuper(List<? super Demo> list){
        // 虽然有下限,但无法直接使用Demo类型接收参数
        for (Object obj : list){
            System.out.println(obj.toString());
        }
    }
    

    虽然有下限,但无法直接使用Demo类型接收参数。这就像“向上转型”和“向下转型”,向上转型是自动的,向下转型需要强转;类型上限可以使用最大类型(父类)接收比它小的类型(子类),类型下限不可以使用最小类型(子类)接受可能比它大的类型(父类)。

    源码的一些例子

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        ……
        public void sort(Comparator<? super E> c) {
            final int expectedModCount = modCount;
            Arrays.sort((E[]) elementData, 0, size, c);
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }
    }
    
    public class Arrays {
        public static <T> void sort(T[] a, int fromIndex, int toIndex,
                                    Comparator<? super T> c) {
            if (c == null) {
                sort(a, fromIndex, toIndex);
            } else {
                rangeCheck(a.length, fromIndex, toIndex);
                if (LegacyMergeSort.userRequested)
                    legacyMergeSort(a, fromIndex, toIndex, c);
                else
                    TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
            }
        }
    }
    

    关于泛型字母E、K、V、T是什么?

    • E表示Element,
    • K表示Key,
    • V表示Value,
    • T表示Type,
    • N表示Number,
    • ? 表示 未知类型

    除了【?】,其他泛型符号你写成字符串都可以,但要注意可读性,一般都是使用单个大写字母表示,不然代码反而不简洁、不易阅读。

    4.泛型实现原理--类型擦除

    把一个具有泛型信息的对象 赋给 另一个没有泛型信息的变量引用,类型信息都将被擦除

    案例一:不指定泛型上限,类型擦除后为Object

    public static void main(String[] args) {
        // 定义集合泛型E = String
        List<String> stringArrayList = new ArrayList<>();
        stringArrayList.add("test1");
        // 获取到的类型为:String
        String s = stringArrayList.get(0);
        // 把带有泛型信息的stringArrayList 对象赋给不确定泛型的List
        List listObject = stringArrayList;
        // listObject 只知道get的类型为Object,而不是String
        Object obj = listObject.get(0);
    }
    

    案例二:指定泛型上限,类型擦除后为上限的类型

    public class DemoFather {}
    public class Demo extends DemoFather{}
    public class DemoChildren<T extends DemoFather> {
        private T t;
        public T getT(){
            return this.t;
        }
        public void setT(T t){
            this.t= t;
        }
    }
    // 测试public class DemoTest {
     public static void main(String[] args) {
            //class DemoChildren<T extends DemoFather>,指定泛型T=Demo类型
            DemoChildren<Demo> demoChildren = new DemoChildren<Demo>();
            // 拿到的方法类型确实是T=Demo类型
            Demo demo = demoChildren.getT();
            // 把带有泛型信息的 demoChildren 对象赋给不确定泛型的demoChildren2
            DemoChildren demoChildren2 =demoChildren;
            // 再来获取方法的类型时,变为了上限的DemoFather类型
            DemoFather demoFather = demoChildren2.getT();
        }
    }
    

    结论:

    指定泛型上限时,类型擦除后为上限的类型;反之是Object类型,因为Java中所有类都默认继承了Object类。

    所以案例二的泛型类在编译阶段是长这样的

    public class DemoChildren {
        private DemoFather t;
        public DemoFather getT(){
            return this.t;
        }
        public void setT(DemoFather t){
            this.t= t;
        }
    }
    // 原来的泛型类,对比一下
    public class DemoChildren<T extends DemoFather> {
        private T t;
        public T getT(){
            return this.t;
        }
        public void setT(T t){
            this.t= t;
        }
    }
    

    image

    Java往期文章
    Java全栈学习路线、学习资源和面试题一条龙
    我心里优秀架构师是怎样的?
    免费下载经典编程书籍

    image

  • 相关阅读:
    可爱的中国电信 请问我们的电脑还属于我们自己吗?
    了解客户的需求,写出的代码或许才是最优秀的............
    DELPHI DATASNAP 入门操作(3)简单的主从表的简单更新【含简单事务处理】
    用数组公式获取字符在字符串中最后出现的位置
    在ehlib的DBGridEh控件中使用过滤功能(可以不用 MemTableEh 控件 适用ehlib 5.2 ehlib 5.3)
    格式化json返回的时间
    ExtJs中使用Ajax赋值给全局变量异常解决方案
    java compiler level does not match the version of the installed java project facet (转)
    收集的资料(六)ASP.NET编程中的十大技巧
    收集的资料共享出来(五)Asp.Net 权限解决办法
  • 原文地址:https://www.cnblogs.com/dennyLee2025/p/15945478.html
Copyright © 2020-2023  润新知