Java高级特性泛型看这一篇就够了

2023-05-16

泛型在我们工作中用到的很多,但是很多同学其实对泛型不怎么了解,包括我,所以我们来一起学习一下泛型,主要是从以下几点来介绍一下泛型为什么需要泛型 、泛型类和泛型接口的定义,泛型方法的辨析,通配符类型,如何限定类型变量,虚拟机中如何实现泛型,如有错误之处,请大佬们多多赐教

1、为什么我们需要泛型?

通过两段代码就可以知道为什么需要泛型

/***
 * 没有泛型的时候实现加法
 */
public class NonGeneric {
    //我们接到一个需求 两个整数相加 ,然后我们就实现了如下代码
    public int addInt(int x,int y){
        return  x+y;
    }
    //业务 发展了  需要 两个浮点数 相加
    public float addFloat(float x,float y){
        return  x+y;
    }
    //业务又又又发展了  需要两个double相加
    public double addDouble(double x,double y){
        return  x+y;
    }
    //就是因为参数类型不同 重写这个方法 ,
    //能不能只写一段代码逻辑相同,传入int  folat  double 都行呢!
    //泛型的应用场景之1,可以传入不同的数据(参数)类型,执行相同的代码

    public void  listDemo(){
        /**
         * list没有用泛型的时候 ;add元素的时候 他们都变成了object类型的,
         * get的时候 必须加一个强制
         * 类型转换,就因为要加强转,所以如果add的是int 他就会报错
         * java.lang.ClassCastException:
         *  java.lang.Integer cannot be cast to java.lang.String
         */
        List list =new ArrayList();
        list.add("爱码士赵Sir");
        list.add("轩轩吖");
        list.add(100);
        for (int i=0;i<list.size();i++){
            String name =(String)list.get(i);
            System.out.println("name:"+name);
        }
        /***
         *   泛型的应用场景之2,定义了泛型之后,
         * 使用的过程中直接指定这个list所盛装的类型只能放String,
         * 这样我们就能在编译期,找到这个错误,
         * 也避免了我们在使用的时候的强制类型转换
         */

        List<String> list2 =new ArrayList<>();
        list2.add("爱码士赵Sir");
        list2.add("轩轩吖");
//        list2.add(100);
        for (int i=0;i<list2.size();i++){
            String name =list2.get(i);
            System.out.println("name:"+name);
        }
    }
    public static void main(String[] args) {
        NonGeneric nonGeneric=new NonGeneric();
        System.out.println(nonGeneric.addInt(1,2));
        System.out.println(nonGeneric.addFloat(1f,2f));
        System.out.println(nonGeneric.addDouble(1d,2d));
        nonGeneric.listDemo();
    }
}

  • 适用于多种数据类型执行相同的代码
  • 我们使用了泛型之后,在我们编码的过程中就可以指定我们数据类型,而不需要进行强制类型转换
  • 如果我们插入了错误的数据类型,在编译期就能发现这个错误,不至于我们到了运行期才抛这个异常

2、泛型类、泛型接口定义

泛型的定义:参数化的类型,在我们普通的方法中,传入的int x,int y这是参数对吧,调用方法的时候传进去一个实际的值,
参数类型:这个参数在定义的时候,这个参数类型本身,把它参数化,在实际 调用的时候,我们再告诉方法这个参数是什么类型,这就是所谓的泛型

  • 泛型类的定义和使用
/***
 * 泛型类的定义
 * @param <T>
 */
public class NormalGeneric <T>{
    private T data;

    public NormalGeneric() {
    }

    public NormalGeneric(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static void main(String[] args) {
        NormalGeneric <String> normalGeneric=new NormalGeneric<>();
        normalGeneric.setData("OK");
        System.out.println(normalGeneric.getData());
    }
}
  • 泛型接口的定义和使用
public interface Genertor<T> {
    public T next();
}

/***
 * 实现方法一,不指定泛型类型,泛型类实现泛型方法,实现类也是一个泛型类
 *  在使用的时候和平常的泛型类没有太大的差别
 * @param <T>
 */
public class ImplGenertor<T> implements  Genertor<T>{
    @Override
    public T next() {
        return null;
    }
}

/***
 * 实现方法二:指定泛型类型,实现方法的返回值 是指定的类型String,而实现方法一里面
 * 返回值还是一个泛型
 */
public class ImplGenertor2 implements Genertor<String> {
    @Override
    public String next() {
        return null;
    }
}

3、泛型方法辨析

泛型方法是独立的,不一定非得再泛型类里,泛型接口里声明
泛型方法的标志就是返回值和权限修饰符中间的<T>
在普通类中可以使用泛型方法,在泛型类里也是可以使用泛型方法的
和泛型类和接口一样 都是在使用它的时候,才告诉编译器我们的数据类型 像下边的<String>
一些高版本的JDK变的比较聪明,会自动推断出类型比如下面的第二行代码

  //泛型方法的标志就是返回值和权限修饰符中间的<T> 
  //在普通类中可以使用泛型方法,在泛型类里也是可以使用泛型方法的
    public <T> T genericMethod(T...a){
        return  a[a.length/2];
    }
    public void  test(int x,int y){
        System.out.println(x+y);
    }
    public static void main(String[] args) {
        GenericMethod genericMethod=new GenericMethod();
        genericMethod.test(1,1);
        //和泛型类和接口一样 都是在使用它的时候,才告诉编译器我们的数据类型 像下边的<String>
        //一些高版本的JDK变的比较聪明,会自动推断出类型比如下面的第二行代码
        System.out.println(genericMethod.<String>genericMethod("小明","小红","小绿"));
        System.out.println(genericMethod.genericMethod(12,34,45));
    }
public class GenericMethod2 {
    public class Generic<T>{
        private T key;
        public Generic(T key) {
            this.key = key;
        }
        //虽然在方法中使用了泛型,但是这并不是一个泛型方法
        //这只是类中的一个普通成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型
        //所以在这个方法中才可以继续使用T这个泛型
        public T getKey(){
            return key;
        }

        /***
         * 这个方法显然是有问题的,在编译器会给我们提示这样的错误消息 cannot reslove symbol “E”
         * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别
         * @param key
         * @return
         */
//        public E setKey(E key){
//            this.key=key;
//        }

    }

    /***
     * 这也不是一个泛型方法,这就是一个普通的方法
     * 只是使用了Generic<Number>这个泛型类做形参而已
     * @param obj
     */
    public void show(Generic<Number> obj){

    }

    /***
     * 这个方法也是有问题的,编译器会为我们提示错误信息:unknown class E
     * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法
     * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道如何处理E这个类型
     * @param ab
     * @param <T>
     * @return
     */
//    public <T> T show(E ab){
//
//    }

    /***
     * 普通的类中没有泛型的T 所以这个方法也是有问题的
     * @param obj
     */
//    public  void show(T obj){
//
//    }
    

}
  • 不是所有写在泛型类里的方法都叫泛型方法
  • 泛型方法的标志是<T>
public class GenericMethod3 {
    static class Fruit{
        @Override
        public String toString() {
            return "Fruit";
        }
    }
    static class Apple extends Fruit{
        @Override
        public String toString() {
            return "Apple";
        }
    }
    static class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }
    static class GenerateTest<T>{
        public  void show_1(T t){
            System.out.println(t.toString());
        }

        /***
         * 在泛型类中 声明了一个泛型方法,使用泛型E,这种泛型可以为任意类型
         * 类型可以与T相同,也可以不同
         * 由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型
         * 编译器也能够正确的识别泛型方法中识别的泛型
         * @param t
         * @param <E>
         */
        public <E> void show_3(E t){
            System.out.println(t.toString());

        }

        /***
         * 在泛型类中声明了一个泛型方法,使用泛型T
         * 注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型
         * @param t
         * @param <T>
         */
        public <T> void show_2(T t){
            System.out.println(t.toString());
        }

        public static void main(String[] args) {
            Apple apple=new Apple();
            Person person=new Person();
            GenerateTest<Fruit> generateTest=new GenerateTest<>();
            generateTest.show_1(apple);//apple 是fruit的子类 这没有问题
//            generateTest.show_1(person);//传person 肯定是不行的
            generateTest.show_2(apple);
            generateTest.show_2(person);//因为泛型方法 所以 完全可以把person传进去,泛型 方法里的参数类型,不管和泛型类,相同还是不相同,都得看成两个不同的参数类型
            generateTest.show_3(apple);
            generateTest.show_3(person);//泛型类里的泛型方法的泛型类型以泛型方法为准 泛型类的泛型类型只影响泛型类的普通方法
        }
    }
}

4、如何限定类型变量

先来一段代码

public class ArrayAlg {
    public static <T> T min(T a,T b){
        if(a.compareTo(b)>0) return a;
        else  return b;
    }
}

首先 这段代码是有问题的,这是一个普通的类,写了一个泛型方法,这个方法做的事情是比较两个数,传进两个数返回一个最小值,我们怎么确定传进来的 a和 b 都有compareTo方法呢,这就用到了类型变量的限定

public class ArrayAlg {
    public static <T extends Comparable> T main(T a,T b){
        if(a.compareTo(b)>0) return a;
        else  return b;
    }
    public static <T extends Comparable> T main(T a,T b){
        if(a.compareTo(b)>0) return a;
        else  return b;
    }
        public static <T,V extends Test&Comparable> T test(T a,T b){
//        if(a.compareTo(b)>0) return a;
//        else  return b;
        return  a;
    }
    static class Test{}
}

extends Comparable 就限定了这个泛型,传进的值必须实现了或者继承了Comparable这个接口,如果传进没有实现或继承Comparable的类或者接口,会在编译器就会报错,提示你传入的类型不对,
限定类型是可以传多个接口的,如果是类和接口混用的话,类只能有一个,并且要放到第一个,放到第一个是java语法的规范!多个限定类型用&连接,泛型方法可以这样限定,泛型类和泛型接口接口也可以这样限定

5、泛型使用中的约束性和局限性

  • 不能实例化类型变量
public class Restrict<T> {
    private T data;
//    不能实例化类型变量

    public Restrict() {
        this.data = new T();//这种是不被允许的
    }
}

  • 静态域或者方法里不能引用类型变量

public class Restrict<T> {
    private T data;

//    静态域或者方法里不能引用类型变量
    private static T instance; //这个也是不被允许的
}

这里为什么静态域或者静态方法不能引用类型变量,是因为泛型的类型,只有在创建这个泛型类型的时候才会知道他的类型是什么,而我们的staitc 执行时间是在构造方法前,所以他不允许,但是如果静态方法是泛型方法,是可以引用的!

  • 泛型的类型不能是基本类型 ,只能是他的包装类型,因为基本类型不是一个对象

  • 泛型不允许用instanceof 来判定类型

    打印的结果是true,打印的name也是一样的Restrict

  • Restrict 是这两个类型的原生类型,getClass的时候也只能获取到原生类型,获取不到泛型类型

  • 可以定义泛型数组,但是不能创建这个数组

  • 泛型类不能继承Exception/Throwable

  • 不能捕获泛型类 对象


但是可以throw出去

    public <T extends Throwable> void doWork(T t) throws  T{
        try{

        }catch (Throwable e){
            throw  t;
        }
    }

6、泛型类型的继承规则

   public static void main(String[] args) {
        Pair<Employee> employeePair=new Pair<>();
        Pair<Worker> workerPair=new Pair<>();
    }

1、Worker 是派生自 Employee,Worker是Employee的子类,但是Pair <Employee> 和 Pair <Worker>没有任何继承关系的
在普通的类中,是可以父类声明直接实例化子类的
但是加上泛型类之后,就不可以了

2、泛型类可以继承或者扩展其他泛型类
什么意思呢?

    public static void main(String[] args) {
        Pair<Employee> employeePair=new Pair<>();
        Pair<Worker> workerPair=new Pair<>();
        Pair<Employee> employeePair3=new EXtendPair<>();

    }
    private static class EXtendPair<T> extends Pair<T>{
        
    }


上面这个图片也说明了Pair <Employee> 和 Pair <Worker>没有任何继承关系,但是我也想set进去怎么办!通配符就派上用场了

7、通配符类型

首先我们定义几个类Fruit Apple Orange HongFuShi,几个类的派生关系如下
在这里插入图片描述
GenericType是一个很标准的泛型类,没有任何特殊的地方

public class GenericType <T>{
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

在这里插入图片描述
虽然Fruit和Orange是派生关系,但是print(b)会报错的, GenericType<Fruit>和GenericType<Orange>是没有关系的,这个上面已经说过

7.1上界通配符

通配符来了? extends Fruit,这个代表什么意思呢?
它表示GenericType 传进来的类型参数可以是Fruit及他的子类

 	public static void print2(GenericType<? extends Fruit> p){
        System.out.println(p.getData().getColor());
    }
     public  static void user2(){
        GenericType<Fruit>a=new GenericType<>();
        print2(a);
        GenericType<Orange>b=new GenericType<>();
        print2(b);
    }

这样你打印b的时候就可以了,通配符只用于方法里,泛型类和泛型接口是不能用的,这个和限定类型是不一样的.
这个通配符规定了传入类型的上界,只能是Fruit本身及子类,
上界通配符有什么限制呢?
在这里插入图片描述
我往里面set值的时候回报编译错误,get的时候也只能用Fruit来接收 为什么呢?
上界通配符传入的时候 一定是Fruit的子类及本身,所以我get的时候,不管我传进去的本身是什么,它一定是个Fruit,但是我不能确定它是个苹果,还是个橘子,但是set的时候为什么不行呢?是因为set的时候它肯定知道你是一个水果,但是你具体是那个Fruit子类,编译器它并不知道,所以是会有问题的
所以这个上界通配符,只用于安全的访问数据

7.2下界通配符

    public static void printSuper(GenericType<? super  Apple> p){
        System.out.println(p.getData());
    }

在这里插入图片描述
由上图可知,我们用了? super之后发现 ,我们只能放进Apple及它的父类,? super Apple 表示GenericType的参数类型的下界是Apple
在这里插入图片描述
使用下界通配符的时候,你只能set Apple 及它的子类,你不能set它的父类,get的时候只能是Objec接收,其他的都不可以,因为Objct是 所有类的父类,你放进去的时候,编译器肯定能确定的是,它是Apple父类,但是具体是那个父类,不知道 ,但Object 一定是他们的父类,为什么set的时候能set进去Apple的子类,是因为所有apple的子类都能安全的转型为Apple
下界通配符,只能用于安全的写入数据

8、虚拟机是如何实现泛型的?

其实java的泛型是一个假的伪泛型,在JVM里是用类型擦除来实现泛型的,在C#里的泛型才是一个真真正正的泛型

public class GenericType <T>{
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

虽然我们定义了一个泛型,在JDK里, data 是一个Object就变成下边的这样

public class GenericType <Object>{
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

但是如果是用了限定符来限定的泛型类呢,会擦除成什么样子的呢

public class GenericType <T extends Comparable&Serializable>{
    private T  data;

    public  T  getData() {
        return data;
    }

    public void setData( T data) {
        this.data = data;
    }
}

它会擦除成这样

public class GenericType <T extends Comparable&Serializable>{
    private Comparable  data;

    public  Comparable  getData() {
        return data;
    }

    public void setData( Comparable data) {
        this.data = data;
    }
}

会以extens 后面的第一个类型来作为擦除类型,但是后边的怎么办呢?后边的 Serializable你在用到它的方法时,编译器会插入一段强制转型的代码
把编译好的class打开看一下,在这里插入图片描述
在这里插入图片描述
擦除之后他们的类型 是一样的,所以这种方式在编译器中是不通过的,在字节码 里有一个属性 Signature (弱记忆)会记录 泛型信息 ,原始类型啥的,并不是把类型擦除的很干净,啥都不剩

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java高级特性泛型看这一篇就够了 的相关文章

随机推荐

  • python中文数字转换为阿拉伯数字

    python中文数字转换为阿拉伯数字 中文数字与阿拉伯数字转换 只能转数字 传参中包含非数字会错 def zhuanhuan str zhong 61 39 零 39 0 39 一 39 1 39 二 39 2 39 三 39 3 39 四
  • Ajax通用数据提交

    Ajax通用数据提交 用于登录注册 xff0c 以及文字的添加 span class token punctuation span ajax span class token punctuation span span class toke
  • 利用scrapy框架获取全国的房价数据

    利用scrapy框架获取全国的房价数据 1 首先这里我是利用scrapy框架来进行爬取的 scrapy框架的使用可谓是既简单效率又高 xff0c 下面来一起爬取 2 直接上代码 xff1a span class token keyword
  • 2021-10-12

    入行两年感慨 xff1a 不知不觉间 xff0c 踏入程序员行业已有两年的时间 xff0c 两年时间说长不长说短也不是很短 xff0c 这两年内 xff0c 我的技术得到了不少的提升 从当初的小白渐渐入了门 先自我说明 xff1a 本人19
  • ubuntu安装chrome浏览器

    1 准备好linux版本的chrome xff0c 下载网址 xff1a https www chromedownloads net chrome64linux 可以通过xshell将本地下载好的上传服务器 xff0c 文件后缀为 deb
  • 7年厨师想转行程序员

    7年厨师想转行 xff0c 跟我学java 可是工资只有7千我惊呆了 xff0c 我还以为7年厨师工资至少1万多呢 看来每个行业都有工资高也有工资低的 xff0c 他可能属于厨师里工资低的吧
  • 什么是项目干系人(project stakeholder)?

    stakeholder理论起源于企业管理领域 xff0c 最早由斯坦福大学在20世纪60年代提出 xff0c 到20世纪年代80年代逐步发展完善 xff0c 成为公司治理和权益保护的理论依据 美国经济学家弗里曼给出的stakeholder定
  • Decoupling GCN with DropGraph Module for Skeleton-Based Action Recognition

    Decoupling GCN with DropGraph Module for Skeleton Based Action Recognition 原文地址 xff1a https www ecva net papers eccv 202
  • Linux—微服务启停shell脚本编写

    run sh bin sh 端口号 PORTS 61 80 模块 MODULES 61 gateway 模块名称 MODULE NAMES 61 网关服务 jar包数组 JARS 61 gateway 1 0 0 SNAPSHOT jar
  • ASP.NET 实现轮播图动态查询数据库加载图片效果HTML+JS+SqlServer+C#(超详细)

    ASP NET 实现轮播图动态查询数据库加载图片效果HTML 43 JS 43 SqlServer 43 C xff08 超详细 xff09 炒鸡详细的轮播图哦 xff01 应用于你的各个地方 效果大概是这样子的 xff1a 点击两侧的箭头
  • 机器学习中的End-to-End到底是怎么回事?

    简单讲就是 xff0c Input gt 系统 xff08 这里指神经网络 xff09 gt Output xff08 直接给出输入 xff0c NN神经网络就给出结果 xff0c 一气喝成 xff01 xff01 xff01 xff09
  • Ubuntu 18.04.3 LTS安装和分区方案

    Ubuntu 18 04 3 LTS安装和分区方案 1 选择Install Ubuntu 2 键盘选择 3 选择下载 4 Installation type 选择Something else 来自定义分区 5 Ubuntu详细分区方案 pa
  • Android 开发的技术方向

    xff11 应用开发 xff12 源码级开发 分为系统应用开发 xff0c Framework开发 xff0c 底层浏览器内核开发 xff0c 音视频编码开发 虚拟机开发 底层驱动开发等系统ROM相关的开发 3 安全 逆向 xff0c 病毒
  • Java内存优化和性能优化的几点建议

    1 没有必要时请不用使用静态变量 使用Java的开发者都知道 xff0c 当某个对象被定义为stataic变量所引用 xff0c 这个对象所占有的内存将不会被回收 有时 xff0c 开发者会将经常调用的对象或者变量定义为static xff
  • Windows下安装和配置WSL

    百度百科描述WSL xff1a Windows Subsystem for Linux xff08 简称WSL xff09 是一个在Windows 10上能够运行原生Linux二进制可执行文件 xff08 ELF格式 xff09 的兼容层
  • 利用STM32的HAL库驱动1.54寸 TFT屏(240*240 ST7789V)

    项目 xff1a 温湿度表 芯片 xff1a STM32F030C6T8 液晶 华迪1 54寸 TFT屏 温湿度传感器 xff1a SHT30 主要对液晶屏官方驱动代码进行了增加和修改 一 STM32CubeMX建立工程 I2C1 给SHT
  • 数组中删数

    题目描述 在给定的数组中删除一个数 输入 多组测试 xff0c 每组第一行输入1个整数n xff08 n lt 20 然后是n个整数 第二行输入1个整数m 输出 删除在第一行的n个整数中第一次出现数字m并删除 xff0c 然后按照顺序输出剩
  • 做程序媛这几年,感受?体验?

    首先 感受 和男程序员一个样 真不是废话 BUG是修不完的 但是不能放过它 因为你的内心会遭受煎熬 直接进入体验 就不用重复的文字去啰嗦了 直接上图哈 以下的这几种情况 在我的生活中 是真的不断出现 连样式都没变过 first 修电脑AND
  • 算法题:求从n个数组任意选取一个元素的所有组合

    http www cnblogs com shuaiwhu archive 2011 06 15 2081552 html http download csdn net detail gz434933205 8728787
  • Java高级特性泛型看这一篇就够了

    泛型在我们工作中用到的很多 xff0c 但是很多同学其实对泛型不怎么了解 xff0c 包括我 xff0c 所以我们来一起学习一下泛型 xff0c 主要是从以下几点来介绍一下泛型为什么需要泛型 泛型类和泛型接口的定义 xff0c 泛型方法的辨