Java泛型浅析一二

2024-01-03 02:50
文章标签 java 浅析 泛型 一二

本文主要是介绍Java泛型浅析一二,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • Java泛型
    • 1. 泛型的好处
      • 1.1 能够适用于代码复用
      • 1.2.泛型能够实现类型安全
    • 2. 泛型的使用
      • 2.1 简单泛型
      • 2.2 多元泛型
      • 2.3 泛型接口
      • 2.4 泛型方法一:方法声明
        • 2.4.1 泛型方法二:方法声明多参数
        • 2.4.2 泛型方法三:用反射生成对象
      • 2.5 泛型方法四:泛型方法调用
    • 3. 泛型使用中需要注意的上限与下限
      • 3.1 问题的引入:为什么需要上限与下限
      • 3.2 泛型上限与下限应用
      • 3.3 泛型多限制:同时实现多个接口
    • 4. 泛型的原理——了解泛型擦除
      • 4.1 泛型擦除的实验1
      • 4.2 泛型擦除的实验2
      • 4.3 泛型擦除的实验3
    • 5. 泛型的其他问题
      • 5.1 泛型中的参数不考虑继承
      • 5.2 泛型的静态变量
      • 5.3 通过反射获取泛型参数类型
      • 5.4 关于泛型的一个思考题
    • 6.常见问题
  • 总结

Java泛型

1. 泛型的好处

1.1 能够适用于代码复用

<T extends Number> 指定了参数类型。

public class Test0 {public static <T extends Number> double add(T a,T b) {return a.doubleValue()+b.doubleValue();}public static void main(String[] args) {System.out.println(Test0.add(1.0f,2l));}
}

运行结果

3.0

1.2.泛型能够实现类型安全

无需强制转换
在这里插入图片描述

2. 泛型的使用

2.1 简单泛型

public class Test2 {static class Message<T> {private T t;public T getT() {return t;}public void setT(T t) {this.t = t;}}public static void main(String[] args) {Message<String> message = new Message<>();message.setT("abc");System.out.println(message.getT());}
}

运行结果

abc

2.2 多元泛型

public class Test3 {static class Entry<K,V> {private K key;private V val;public K getKey() {return key;}public void setKey(K key) {this.key = key;}public V getVal() {return val;}public void setVal(V val) {this.val = val;}}public static void main(String[] args) {Entry<Integer,String> entry = new Entry<>();entry.setKey(200);entry.setVal("成功");System.out.println(entry.getKey()+","+entry.getVal());}
}

运行结果

200,成功

2.3 泛型接口

public class Test4 {interface Response<T> {public T getVar();}static class ResponseImpl<T> implements Response<T> {private T var;@Overridepublic T getVar() {return var;}public void setVar(T var) {this.var = var;}public ResponseImpl(T var) {this.var = var;}}public static void main(String[] args) {Response<String> response = new ResponseImpl<>("abc");System.out.println(response.getVar());}
}

运行结果

abc

2.4 泛型方法一:方法声明

<T>声明泛型方法持有T

public class Test5 {public static <T> void print(T[] arr) {for(T t:arr) {System.out.print(t+"\t");}}public static void main(String[] args) {Test5.print(new String[]{"a","b","c"});}
}

运行结果

a	b	c	
2.4.1 泛型方法二:方法声明多参数
public class Test6 {public static <T,V> void print(T[] arr,V[] arr2) {for(T t:arr) {System.out.print(t+"\t");}for(V v:arr2) {System.out.print(v+"\t");}}public static void main(String[] args) {Test6.print(new String[]{"a","b","c"},new Integer[]{1,2,3,4,5});}
}

运行结果

a	b	c	1	2	3	4	5	
2.4.2 泛型方法三:用反射生成对象

通过泛型与反射创建对象

public class Test7 {public static <T> T getInstance(Class<T> c) {T t = null;try {t = c.newInstance();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return t;}public static void main(String[] args) throws ClassNotFoundException {Object instance = Test7.getInstance(Class.forName("com.generic.O"));System.out.println(instance.getClass());}
}

运行结果

class com.generic.O

2.5 泛型方法四:泛型方法调用

在调用泛型方法时,可以指定类型,此时编译器会校验参数类型
在这里插入图片描述

3. 泛型使用中需要注意的上限与下限

3.1 问题的引入:为什么需要上限与下限

  • 问题一
public class Test8 {static class A {}static class B extends A {}public void putA(A a ){}public void putB(B b) {putA(b);System.out.println("自动进行类型转换,里氏替换");}public static void main(String[] args) {Test8 test8 = new Test8();test8.putB(new B());}
}

在这个方法可以正常调用,因为编译器会识别 putA(b);的参数类型是B。

  • 问题二
    但是如果是List类型就会报错哦.因此此处是隐含的类型转换。在这里插入图片描述

  • 用泛型解决
    <? extends A>表示该类型参数可以是A(上边界)或者A的子类类型。编译通过。

    public void putA(List<? extends A> lista ){}public void putB(List<B> listb) {putA(listb);System.out.println("自动进行类型转换,里氏替换");}

3.2 泛型上限与下限应用

extends 关键字声明了类型的上界;super 关键字声明了类型的下界。

public class Test11 {static class Point<T> {}public static void main(String[] args) {Point<? super Number> p1 = new Point<Object>();Point<? extends Number> p2 = new Point<Integer>();}
}

3.3 泛型多限制:同时实现多个接口

<T extends Staff & Passenger>要求泛型必须同时实现两个接口。

public class Test12 {interface Staff{int getSalary();}interface Passenger{boolean isStanding();}static class Person implements Staff,Passenger {@Overridepublic int getSalary() {return 11110;}@Overridepublic boolean isStanding() {return false;}}//工资低于2500元的上斑族并且站立的乘客车票打8折public static <T extends Staff & Passenger> void discount(T t){if(t.getSalary()<2500 && t.isStanding()){System.out.println("恭喜你!您的车票打八折!");}}public static void main(String[] args) {discount(new Person());}
}

4. 泛型的原理——了解泛型擦除

Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

    1. 无限制类型擦除
      当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如和<?>的类型参数都被替换为Object。
      在这里插入图片描述
    1. 擦除类定义中的类型参数
      在类型擦除中替换为类型参数的上界或者下界,比如形如和<? extends Number>的类型参数被替换为Number,<? super Number>被替换为Object。
      在这里插入图片描述
    1. 擦除方法定义中的类型参数
      擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的
      在这里插入图片描述

4.1 泛型擦除的实验1

实验证明,l1 和 l2 的类型相同。

public class Test13 {public static void main(String[] args) {List<String> l1 = new ArrayList<>();List<Integer> l2= new ArrayList<>();System.out.println(l1.getClass());System.out.println(l2.getClass());}
}

运行结果

class java.util.ArrayList
class java.util.ArrayList

4.2 泛型擦除的实验2

通过反射,仍然可以向泛型类里存放字符串。

public class Test14 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {ArrayList<Integer> l1 = new ArrayList<>();l1.add(42);l1.getClass().getMethod("add",Object.class).invoke(l1,"abc");System.out.println(l1);}
}

运行结果

[42, abc]

4.3 泛型擦除的实验3

由于引用类型的声明没有声明为泛型,实际上什么都可以往里存。
所以泛型的检查是针对引用的,而无关它真正引用的对象。

public class Test16 {public static void main(String[] args) {List ls = new ArrayList<String>();ls.add("ba");ls.add(1);System.out.println(ls);}
}

5. 泛型的其他问题

5.1 泛型中的参数不考虑继承

泛型就是需要明确关系,如果参数是可以继承的,但是仍然不允许传递。
在这里插入图片描述

5.2 泛型的静态变量

因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。因此是错误的。
在这里插入图片描述
但是静态方法居然可以通过,请注意此T是方法声明,非类定义的泛型T。

    public static <T> T getT(){return null;}

5.3 通过反射获取泛型参数类型

public class Test20<T> {public static void main(String[] args) {Test20<Integer> genericType = new Test20<Integer>() {};Type superclass = genericType.getClass().getGenericSuperclass();Type type = ((ParameterizedType) superclass).getActualTypeArguments()[0];System.out.println(type);//class java.lang.String}
}

结果:

class java.lang.Integer

5.4 关于泛型的一个思考题

看这个方法,D继承了C,覆写了C的方法。

public class Test18 {static class C<T>{private T val;public T getVal() {return val;}public void setVal(T val) {this.val = val;}}static class D extends C<String>{@Overridepublic String getVal() {return super.getVal();}@Overridepublic void setVal(String val) {super.setVal(val);}}public static void main(String[] args) {C c = new C();c.setVal(new Object());D d = new D();d.setVal(new Object());}
}

但是注意,C方法在编译期中会被擦除泛型参数,变为Object,因此D的get、set方法由于入参和返回值不一样,就不是对C的覆写了。
真的是这样吗?
运行显示,d不允许d.setVal(new Object());。可是我们之前的思路,应当父类的方法并没有被覆盖啊,是可以调用父类的方法的啊.
在这里插入图片描述
问题在于虚拟机的优化实现。
运行javap -c命令查看字节码,发现jvm帮助我们实现了子类的方法,一共有两套get和set方法,以匹配父类方法。
当然我们自己是无法这样实现的,以为编译不会通过,因为jvm违反了重载的约束,仅仅返回值不同就定义了多个方法。
在这里插入图片描述

6.常见问题

1.Java中的泛型是什么 ? 使用泛型的好处是什么?
泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。
泛型是一种编译时类型确认机制。它提供了编译期的类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现ClassCastException。

2.Java的泛型是如何工作的 ? 什么是类型擦除 ?
泛型的正常工作是依赖编译器在编译源码的时候,先进行类型检查,然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令实现的。编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List在运行时仅用一个List类型来表示。为什么要进行擦除呢?这是为了避免类型膨胀。

3.什么是泛型中的限定通配符和非限定通配符 ?
限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。

4.List<? extends T>和List <? super T>之间有什么区别 ?
List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List或List。

总结

泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。
泛型是一种编译时类型确认机制。它提供了编译期的类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现ClassCastException。

java基础 系列在github上有一个开源项目,主要是本系列博客的demo代码。https://github.com/forestnlp/javabasic
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。
您的支持是对我最大的鼓励。

这篇关于Java泛型浅析一二的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/564369

相关文章

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

java中long的一些常见用法

《java中long的一些常见用法》在Java中,long是一种基本数据类型,用于表示长整型数值,接下来通过本文给大家介绍java中long的一些常见用法,感兴趣的朋友一起看看吧... 在Java中,long是一种基本数据类型,用于表示长整型数值。它的取值范围比int更大,从-922337203685477

java Long 与long之间的转换流程

《javaLong与long之间的转换流程》Long类提供了一些方法,用于在long和其他数据类型(如String)之间进行转换,本文将详细介绍如何在Java中实现Long和long之间的转换,感... 目录概述流程步骤1:将long转换为Long对象步骤2:将Longhttp://www.cppcns.c

SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程

《SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程》LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑,下面给大... 目录一、基础概念1.1 组件(Component)1.2 规则(Rule)1.3 上下文(Conte

SpringBoot服务获取Pod当前IP的两种方案

《SpringBoot服务获取Pod当前IP的两种方案》在Kubernetes集群中,SpringBoot服务获取Pod当前IP的方案主要有两种,通过环境变量注入或通过Java代码动态获取网络接口IP... 目录方案一:通过 Kubernetes Downward API 注入环境变量原理步骤方案二:通过