Java从入门到放弃 之 泛型

Java从入门到放弃 - 泛型

引入泛型的背景

在Java中当我们使用容器存储元素的时候(建议先了解一部分Java容器知识),实际上使用的是Object存储的元素,比如下面ArrayList部分源码

java 复制代码
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
    private static final long serialVersionUID = 8683452581122892189L;
    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
    transient Object[] elementData;
    private int size;
}

从这部分源码我们可以看出ArrayList使用的是Object[] 数组存储的元素,但是这里就引出了一个问题。我们使用Object存储,是不是就把原始类型就给丢弃了。导致我们在从容器中拿出元素之后,想要进一步使用元素还需要向下转型,这让代码变得麻烦、臃肿、不安全,并且容易发生类型转换异常。

怎么解决这个问题?

因为存储的时候是通用的容器,用Object没有问题,但是使用的时候我们还是需要知道原始类型。

解决方式一

使用特定容器不用通用容器不就好了, 比如存储String我们就写一个存储String数组的ArrayList,存储Integer 就写一个存储Integer数组的ArrayList

java 复制代码
public class StringArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
    private static final long serialVersionUID = 8683452581122892189L;
    private static final int DEFAULT_CAPACITY = 10;
    private static final String[] EMPTY_ELEMENTDATA = new Object[0];
    private static final String[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
    transient String[] elementData;
    private int size;
}
java 复制代码
public class IntegerArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
    private static final long serialVersionUID = 8683452581122892189L;
    private static final int DEFAULT_CAPACITY = 10;
    private static final Integer[] EMPTY_ELEMENTDATA = new Object[0];
    private static final Integer[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
    transient Integer[] elementData;
    private int size;
}

这样可不可以呢? 当然是可以的,但是有没有问题? 聪明的你,看上面的代码一下子就看出来了,这样写,会产生一大堆重复代码,而且还要写一大堆类。 这肯定不是一种很好的实现方式。

所以有没有更好的实现方式呢?

解决方式二

Java是强类型编程语言,强类型的意思就是在编写代码的时候必须要明确变量的类型,不然的话编译不通过。但是有时候我们不知道某些变量的具体类型是什么,只能用公共父类Object类型,这就是容器或者上面代码ArrayList里面为啥使用Object[]数组存储元素,因为我们不知道具体要存储的数据类型。其实观察上面两段代码,我们其实发现大部分都是相同的,只有标记用什么类型存储数组的地方是不一样的。解决这个问题的方式就是我们在程序的某个地方标识完全未知的类型,使程序顺利通过编译,等到使用的时候确定具体类型。 泛型(Generics)就是泛化的类型,即使用 表示一个暂时没有确定的类型。

使用泛型

接下我们就通过实际代码了解如何使用泛型

代码案例一

java 复制代码
        public class Pair<T> {
            T first;
            T second;
            public Pair(T first, T second){
                this.first = first;
                this.second = second;
            }
            public T getFirst() {
                return first;
            }
            public T getSecond() {
                return second;
            }
        }
java 复制代码
        Pair<Integer> minmax = new Pair<Integer>(1,1);
        Integer min = minmax.getFirst();
        Integer max = minmax.getSecond();

通过使用泛型,我们达到了使得Pair类的代码和它处理的数据类型不是绑定的,具体类型可以变化。也就是说Pair类即可以处理Integer也可以处理String等等

代码案例二

类型参数还可以是多个

java 复制代码
        public class Pair<K, V> {
            K first;
            V second;
            public Pair(K first, V second){
                this.first = first;
                this.second = second;
            }
            public K getFirst() {
                return first;
            }
            public V getSecond() {
                return second;
            }
        }
java 复制代码
        Pair<Integer, String> test = new Pair<Integer, String>(1,"test");
        Integer num = test.getFirst();
        String test = mintest.getSecond();

Pair<Integer, String> test = new Pair<>(1,"test") 这样写,因为编译器可以自动推断泛型类型

通配符的使用

ArrayList 不是 ArrayList的父类, 你可以这样理解。 动物类是人类的父类, 装动物类的汽车类不是装人类的汽车类的父类。

extends 通配符

java 复制代码
        public void addAll(ArrayList<Number> array) {
        	
        }

对于这个把所有Number的list放到一个其他容器的方法,Integer是number的子类,Number能放的 Integer也可以放进去。但是上面我们说的继承关系可以知道。ArrayList 跟ArrayList 没有父子关系,直接把ArrayList 传入这个方法,会报错的。

怎么办? 这时候可以使用extends关键字

java 复制代码
        public void addAll(ArrayList< ? extends Number> array) {
        	
        }

这样使得方法接收所有泛型类型为Number或Number子类的ArrayList类型了,这样就解决了我们的问题。

super 通配符

ArrayList<? super Integer>表示,方法参数接受所有泛型类型为Integer或Integer父类的ArrayList类型。

对比extends和super通配符

  • <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
  • <? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。

一个是允许读不允许写,另一个是允许写不允许读。

泛型的原理

这个是我们写的泛型类,编译器看到的就是我们写的源代码,就是下面这个案例写的这样,

java 复制代码
        public class Pair<T> {
            T first;
            T second;
            public Pair(T first, T second){
                this.first = first;
                this.second = second;
            }
            public T getFirst() {
                return first;
            }
            public T getSecond() {
                return second;
            }
        }

实际上JVM执行的时候看到的代码是,

java 复制代码
        public class Pair<T> {
            Object first;
            Object second;
            public Pair(Object  first, Object  second){
                this.first = first;
                this.second = second;
            }
            public Object  getFirst() {
                return first;
            }
            public Object  getSecond() {
                return second;
            }
        }

使用泛型的地方,编译器看到的源码,

java 复制代码
        Pair<Integer> minmax = new Pair<Integer>(1,1);
        Integer min = minmax.getFirst();
        Integer max = minmax.getSecond();

这段代码实际上JVM看到的是

java 复制代码
        Pair minmax = new Pair(1,1);
        Integer min = (Integer)minmax.getFirst();
        Integer max = (Integer)minmax.getSecond();

所以Java是通过擦除法实现泛型

  • 编译器会把 类似泛型地方的写法都会写成Object
  • 编译器根据 实现安全的类型转换

所以实际上Java的泛型是由编译器在编译时实行的,编译器泛型视为Object处理,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。

相关推荐
Mikhail_G几秒前
Python应用函数调用(二)
大数据·运维·开发语言·python·数据分析
lanfufu2 分钟前
记一次诡异的线上异常赋值排查:代码没错,结果不对
java·jvm·后端
枣伊吕波12 分钟前
第十三节:第四部分:集合框架:HashMap、LinkedHashMap、TreeMap
java·哈希算法
weixin_4723394622 分钟前
使用Python提取PDF元数据的完整指南
java·python·pdf
PascalMing25 分钟前
Ruoyi多主键表的增删改查
java·若依ruoyi·多主键修改删除
橘子青衫31 分钟前
Java并发编程利器:CyclicBarrier与CountDownLatch解析
java·后端·性能优化
天天摸鱼的java工程师42 分钟前
高考放榜夜,系统别崩!聊聊查分系统怎么设计,三张表足以?
java·后端·mysql
天天摸鱼的java工程师1 小时前
深入理解 Spring 核心:IOC 与 AOP 的原理与实践
java·后端
漫步者TZ1 小时前
【Netty系列】解决TCP粘包和拆包:LengthFieldBasedFrameDecoder
java·网络协议·tcp/ip·netty
木木黄木木1 小时前
Python制作史莱姆桌面宠物!可爱的
开发语言·python·宠物