Java泛型背后是什么?

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Java泛型背后是什么?

点击上方“Java知音”,选择“置顶公众号”

技术文章第一时间送达!

作者:的一幕 www.jianshu.com/p/dd34211f2565

www.jianshu.com/p/dd34211f2565

这一节主要讲的内容是java中泛型的应用,通过该篇让大家更好地理解泛型,以及面试中经常说的泛型类型擦除是什么概念,今天就带着这几个问题一起看下:

举一个简单的例子:

Java泛型背后是什么?

这里可以看出来在代码编写阶段就已经报错了,不能往string类型的集合中添加int类型的数据。

那可不可以往List集合中添加多个类型的数据呢,答案是可以的,其实我们可以把list集合当成普通的类也是没问题的,那么就有下面的代码:

Java泛型背后是什么?

从这里可以看出来,不定义泛型也是可以往集合中添加数据的,所以说泛型只是一种类型的规范,在代码编写阶段起一种限制

下面我们通过例子来介绍泛型背后数据是什么类型


public class BaseBean {
    T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

上面定义了一个泛型的类,然后我们通过反射获取属性和getValue方法返回的数据类型:

Java泛型背后是什么? Java泛型背后是什么?

从日志上看到通过反射获取到的属性是Object类型的,在方法中返回的是string类型,因此咋们可以思考在getValue方法里面实际是做了个强转的动作,将object类型的value强转成string类型。

是的,没错,因为泛型只是为了约束我们规范代码,而对于编译完之后的class交给虚拟机后,对于虚拟机它是没有泛型的说法的,所有的泛型在它看来都是object类型,因此泛型擦除是对于虚拟机而言的。

下面我们再来看一种泛型结构:

Java泛型背后是什么?

这里我将泛型加了个关键字extends,对于泛型写得多的伙伴们来说,extends是约束了泛型是向下继承的,最后我们通过反射获取value的类型是String类型的,因此这里也不难看出,加extends关键字其实最终目的是约束泛型是属于哪一类的。所以我们在编写代码的时候如果没有向下兼容类型,会警告错误的:

Java泛型背后是什么?

大家有没有想过为啥要用泛型呢,既然说了泛型其实对于jvm来说都是Object类型的,那咱们直接将类型定义成Object不就是的了,这种做法是可以,但是在拿到Object类型值之后,自己还得强转,因此泛型减少了代码的强转工作,而将这些工作交给了虚拟机。

比如下面我们没有定义泛型的例子:

Java泛型背后是什么?

势必在getValue的时候代码有个强转的过程,因此在能用泛型的时候,尽量用泛型来写,而且我认为一个好的架构师,业务的抽取是离不开泛型的定义。

常见的泛型主要有作用在普通类上面,作用在抽象类、接口、静态或非静态方法上。

类上面的泛型

比如实际项目中,我们经常会遇到服务端返回的接口中都有errMsg、status等公共返回信息,而变动的数据结构是data信息,因此我们可以抽取公共的BaseBean:


public class BaseBean {
    public String errMsg;
    public T data;
    public int status;
}

抽象类或接口上的泛型


//抽象类泛型
public abstract class BaseAdapterT {
    List DATAS;
}
//接口泛型
public interface FactoryT {
    T create();
}

//方法泛型
public static  T getData() {
    return null;
}

多元泛型


public interface BaseK, V {
    void setKey(K k);

    V getValue();
}

泛型二级抽象类或接口


public interface BaseCommonK extends Common1, V extends BaseK, V {
}
//或抽象类
public abstract class BaseCommonK extends Common1, V implements BaseK, V {
}

抽象里面包含抽象


public interface BaseK, V {
   //    void setKey(K k);
//
//    V getValue();
   void addNode(MapK, V map);

   MapK, V getNode(int index);
}

public abstract class BaseCommonK, V implements BaseK, V {
   //多重泛型
   LinkedListMapK, V DATAS = new LinkedList();

   @Override
   public void addNode(MapK, V map) {
       DATAS.addLast(map);
   }

   @Override
   public MapK, V getNode(int index) {
       return DATAS.get(index);
   }

}

?通配符****

?通配符和``区别是 ?在你不知道泛型类型的时候,可以用 ?通配符来定义,下面通过一个例子来看看 ?的用处:


//定义了一个普通类
public class BaseBeanT {
    T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

//用来定义泛型的
public class Common1 extends Common {
}
Java泛型背后是什么?

在定义的时候将Common的泛型指向Common1的泛型,可以看到直接提示有问题,这里可以想,虽然Common1是继承自Common的,但是并不代表BaseBean之间是等量的,在开篇也讲过,如果泛型传入的是什么类型,那么在BaseBean中的getValue返回的类型就是什么,因此可以想两个不同的泛型类肯定是不等价的,但是如果我这里写呢:


public static void main(String[] args) {
    BaseBeanCommon commonBaseBean = new BaseBean();
    //通配符定义就没有问题
    BaseBean? common1BaseBean = commonBaseBean;
    try {
        //通过反射猜测setValue的参数是Object类型的
        Method setValue = common1BaseBean.getClass().getDeclaredMethod("setValue", Object.class);
        setValue.invoke(common1BaseBean, "123");
        Object value = common1BaseBean.getValue();
        System.out.println("result:" + value);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

在上面如果定义的泛型是通配符是可以等价的,因为此时的setValue的参数是Object类型,所以能直接将上面定义的泛型赋给通配符的BaseBean。

通配符不能定义在类上面、接口或方法上,只能作用在方法的参数上

Java泛型背后是什么?

其他的几种情况自己去尝试,正确的使用通配符:


public void setClass(Class? class){
    //todo
}

T extends T super ? extends ? super
T extends **表示上限泛型、 T super **表示下限泛型

为了演示这两个通配符的作用,增加了一个类:

Java泛型背后是什么?

//新增加的一个BaseCommon
public class Common extends BaseCommon{
}
Java泛型背后是什么?

第二个定义的泛型是不合法的,因为BaseCommon是Common的父类,超出了Common的类型范围。

T super 不能作用在类、接口、方法上,只能通过方法传参来定义泛型

在BaseBean里面定义了个方法:


public void add(Class? super Common clazz) {
}
Java泛型背后是什么?

可以看到当传进去的是Common1.class的时候是不合法的,因为在add方法中需要传入Common父类的字节码对象,而Common1是继承自Common,所以直接不合法。

在实际开发中其实知道什么时候定义什么类型的泛型就ok,在mvp实际案例中泛型用得比较广泛,大家可以根据实际项目来找找泛型的感觉,只是面试的时候需要理解类型擦除是针对谁而言的。

类型擦除

其实在开篇的时候已经通过例子说明了,通过反射绕开泛型的定义,也说明了类中定义的泛型最终是以Object被jvm执行。所有的泛型在jvm中执行的时候,都是以Object对象存在的,加泛型只是为了一种代码的规范,避免了开发过程中再次强转。

泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

END

Java面试题专栏

【20期】你知道为什么HashMap是线程不安全的吗?

【19期】为什么Java线程没有Running状态?

【18期】Java序列化与反序列化三连问:是什么?为什么要?如何做?

【17期】什么情况用ArrayList or LinkedList呢?

【16期】你能谈谈HashMap怎样解决hash冲突吗

【15期】谈谈这几个常见的多线程面试题

【14期】你能说说进程与线程的区别吗

【13期】谈谈 Redis 的过期策略

【12期】谈谈项目中单点登录的实现原理?

【11期】分布式系统接口,如何避免表单的重复提交?

Java泛型背后是什么?

我知道你 “在看Java泛型背后是什么?

原文始发于微信公众号(Java知音):Java泛型背后是什么?

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Java泛型背后是什么?


 上一篇
Java中最大的数据结构——LinkedHashMap了解一下? Java中最大的数据结构——LinkedHashMap了解一下?
点击上方“Java知音”,选择“置顶公众号” 技术文章第一时间送达! 作者:莫那鲁道 https://thinkinjava.cn https://thinkinjava.cn 前言Map 家族数量众多,其中 HashMap
下一篇 
面试官——关于Java性能优化,你有什么技巧 面试官——关于Java性能优化,你有什么技巧
点击上方“Java知音”,选择“置顶公众号” 技术文章第一时间送达! 通过使用一些辅助性工具来找到程序中的瓶颈,然后就可以对瓶颈部分的代码进行优化。 一般有两种方案:即优化代码或更改设计方法。我们一般会选择后者,因为不去调用以下代码要比调用