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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定