强化基础-Java-泛型基础

什么是泛型?

泛型其实就参数化类型,也就是说这个类型类似一个变量是可变的。

为什么会有泛型?

在没有泛型之前,java中是通过Object来实现泛型的功能。但是这样做有下面两个缺陷:
1 获取值的时候必须进行强转
2 没有错误检查

Object data[] = new Object[]{"one", "two", 1, new Object()};
String str = (String) data[0];

一般来说我们会把相同类型的数据放到一起,但是有没有发现如果使用object我们可以放入任意类型的数据,编译器也不会报错,这样在使用的时候就增加了类型转换异常的概率。
那么使用泛型呢?

List<String> strList = new ArrayList<>();
strList.add("one");
// 这句代码编译器就会提醒你不能这样使用
strList.add(1);

非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。

泛型擦除

泛型信息只存在于代码编译阶段,但是在java的运行期(已经生成字节码文件后)与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

// 这个例子 最后输出的结果为 true class的结果为:java.util.ArrayList
public class Pair<T> {
    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        List<String> stringList = new ArrayList<>();
        System.out.println(Objects.equals(integerList, stringList));
        System.out.println(integerList.getClass());
        System.out.println(stringList.getClass());
    }
}

再看下面这个例子:

public class Pair<T> {
    private T data;
    public static void main(String[] args) {
        Pair<String> stringPair = new Pair<>();
        System.out.println(stringPair.getClass());
        Field[] declaredFields = stringPair.getClass().getDeclaredFields();
        Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
        System.out.println("========================");
        Pair<Integer> integerPair = new Pair<>();
        System.out.println(integerPair.getClass());
        Field[] fields = integerPair.getClass().getDeclaredFields();
        Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
    }
}

从运行结果我们可以证明在运行时类型已经被擦除为Object类型
在这里插入图片描述

在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如则会被转译成普通的Object 类型,如果指定了上限,如则类型参数就被替换成类型上限。
1 没有限定泛型的界限

public class Pair<T> {
    private T first;
    private T second;
}

擦除后,没有限定泛型的界限所以是Object类型:

public class Pair {
	private Object first;
	private Object second;
}

2 限定了泛型的界限

public class Pair<T extends Comparable> {
    private T data;
    public static void main(String[] args) {
        Pair<String> stringPair = new Pair<>();
        System.out.println(stringPair.getClass());
        Field[] declaredFields = stringPair.getClass().getDeclaredFields();
        Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
        System.out.println("========================");
        Pair<Integer> integerPair = new Pair<>();
        System.out.println(integerPair.getClass());
        Field[] fields = integerPair.getClass().getDeclaredFields();
        Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
    }
}

在这里插入图片描述

这就证明在擦除后:

public class Pair<T extends Comparable & Serializable> {
    private Comparable first;
    private Comparable second;
}

如果交换泛型的顺序: Pair<T extends Serializable & Comparable > 那么擦除以后的类型为Serializable,这个时候编译器会插入强制类型转换(也就是说我们获取Comparable 类型时候会强制转换),为了提高效率一般将标记接口往末尾放。

public class Pair<T extends Serializable & Comparable> {
    private T data;
    public static void main(String[] args) {
        Pair<String> stringPair = new Pair<>();
        System.out.println(stringPair.getClass());
        Field[] declaredFields = stringPair.getClass().getDeclaredFields();
        Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
        System.out.println("========================");
        Pair<Integer> integerPair = new Pair<>();
        System.out.println(integerPair.getClass());
        Field[] fields = integerPair.getClass().getDeclaredFields();
        Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
    }
}

在这里插入图片描述

所谓的插入强制类型转换,就是编译器在编译泛型表达式的时候会转化为两条指令:

  • 对原始方法的调用得到Object
  • 将返回的Object类型强制转换为具体的类型。

3 泛型方法的擦除
首先我们要区分一下泛型方法,泛型方法。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。如果单纯的在方法中使用了泛型它不是泛型方法。
泛型方法:

public <E> E convert(T t) { 
}

非泛型方法:

public T getT(T t) {
}
public static <T extends Comparable> T min(T[] data) {
   return null;
}

泛型方法擦除后:

public static Comparable min(Comparable [] data) {
   return null;
}

需要注意的是泛型方法的多态是通过桥方法实现的

public class Pair<T> {
    private T time;
    public void setTime(T time) {
       this.time = time;
    }
}
// 被擦除以后
public void setTime(Object time) {
}

如果这个时候子类继承Pair,并指定了类型:

class DateInterVal extends Pair<LocalDate> {
    @Override
    public void setTime(LocalDate time) {
    }
}

这个时候如果调用Pair的setTime方法,由于多态其实底层是这样来实现的:

setTime(setTime((LocalDate) time));

总结:1 虚拟机中没有类型,只有普通的类和方法 2 所有的类型参数都会替换为他们的限定类型 3 合成桥方法来保持多态 4为保证类型安全,必要时会插入强制类型转换

泛型方法

1 在类中的泛型方法
首先我们来区分几个定义方式,看注释部分。

public class Pair<T extends Comparable & Serializable> {
    private T data;
    // 编译无法通过 因为这个方法是静态方法,所以我们不能使用T类型,但是我们可以使用E类型,因为E类型是申明的
    public static <E> E convert2E(T t) {
        return null;
    }
    // 在非静态方法的情况下 可以使用上面的类中定义的泛型T
    public <E> E convert2E(T t) {
        return null;
    }
    // 注意这里我们在静态方方法申明了一个T类型,这个T和类上的T类型是没有关联的,是一个全新的类型
    // 这个T可以和类的T是同一个类型,也可以不是同一个类型
    public static  <T> T accept(T t) {
        return null;
    }
}

2 泛型方法中的可变参数

public class Pair<T> {
    static class Dot {
        private int x;
        private int y;
        public Dot(int x, int y) {
            this.x = x;
            this.y = y;
        }
        @Override
        public String toString() {
            return "Dot{" +
                    "x=" + x +
                    ", y=" + y +
                    '}';
        }
    }
    // 泛型方法可变参数
    private static <T> void print(T ...es) {
        for (T t : es) {
            System.out.println(t  + "");
        }
    }
    public static void main(String[] args) {
        Dot dot = new Dot(1, 1);
        print(15000, "15000", dot);
    }
}

2 静态方法与泛型
一个基本原则,如果要在静态方法使用泛型的话,这个方法必须为泛型方法。

// 也就是说必须申明
public static  <T> T accept(T t) {
   return null;
}

泛型缺陷

1 不能使用基本类型实例化类型参数
也就是说没有

Pair<int> Pair<double> 类型

因为泛型在擦除以后为object类型,但是原生类型不能直接赋值为object,而是要使用包装类。
2 不能实例化类型参数
本质也是因为类型擦除导致的

String string = new T();
// 类型擦除以后,很显然是存在问题的
String string = new Object();

但是我们可以通过反射来创建一个实例:

 Pair<String> pair = Pair.class.newInstance();

3 运行时类型查询只适用于原始类型
下面这三条语句都是编译会报错的,因为虚拟中的对象总是一个特定的非泛型类型,所以类查询只能查询原始类型。

pair instanceof String;
pair instanceof Pair<String>;
pair instanceof Pair<T>;
// 这条语句是可以的 原始类型的查询
pair instanceof Pair

并且下面的语句也会返回true:

Pair<String> ps = new Pair<>();
Pair<Double> pd = new Pair<>();
System.out.println(ps.getClass() == pd.getClass());

4 不能创建参数化类型的数组
类型擦除以后变为Pair[] pars = new Pair[10]; 然后我们可以赋予pairs[0] = new Pair(); 没有编译错误,但存在运行时错误。

// 可以申明
Pair<String> [] pairs;
// 不可以实例化 也是一样的如果把String擦除 为Object 可能会导致运行时异常 不安全
Pair[] pairs = new Pair<String>[10];
// 如果是通配类型的 则可以 但是这样的话不是很安全因为里面可以存 Pair<String> 也可以存 Pair<Double> 
// 在使用的时候可能类型转换异常
Pair[] pairs = new Pair<?>[10];

5 Varargs 警告

public static <T> void addAll(Collection<T> coll, T... ts){
   // 这里其实创建了数组就违背了不能创建数组
   for(T t : ts) {
       coll.add(t);
   }
}
public static void main(String[] args) {
   Collection<Pair<String>> table = new ArrayList<>();
   Pair<String> pair1 = new Pair<>();
   Pair<String> pair2 = new Pair<>();
   addAll(table, pair1, pair2);
}
static class Pair<T> {

}

6 不能实例化类型变量
T[] array = new T[10]
类型擦除后上述定义变为Object[] array = new Object[10]; 这样一来我们可以将任何类型赋予array[0], 比如array[0] = “1”; 编译器不会报错,但运行时在使用的时候就有可能会出错了。

// 编译不会通过
T t = new T();
// 编译不会通过
T[] array = new T[10]

这里也可以通过反射来进行:

public T[] demo() {
    T data[] = (T[]) Array.newInstance(Pair.class, 2);
    return data;
}

7 泛型类的静态上下文中类型变量无效
也就是静态不能和泛型一起使用,如果一定要一起使用的话,必须申明。

// 通不过
public static T t;
// 如果一定要使用则需要申明
public static <T> void addAll(Collection<T> coll, T... ts){
     for(T t : ts) {
         coll.add(t);
     }
 }

8 不能抛出或捕获泛型类的实例

public static <T extends Throwable> void doWork() {
  try {
	// 会产生编译错误
  } catch (T e) {

  }
}

泛型中的继承

继承泛型类时,必须对父类中的类型参数进行初始化
1 使用泛型初始化父类的泛型

public class Bar<T> extends Foo<T> {
    
}

2 使用具体的类型

public class Bar extends Foo<String> {

}

特别注意:

// 这里的继承关系是 Integer 和 Double 继承
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); 
box.add(new Double(10.1));
// Box<Integer> Box<Number> 他们并不是继承关系 这一定要注意 

原文链接
以 Collections 类为例,ArrayList 实现 List ,List 继承 Collection 。 所以 ArrayList 是 List 的一个子类型,它是 Collection 的一个子类型。 只要不改变类型参数,子类型关系在类型之间保留。
在这里插入图片描述

泛型中的限定

在通配符类型中,允许类型参数发生变化。
1 子类限定

// 类上
class Son<T extends Foo> {
}
// 方法上
public <T> T demo2(Bar<? extends Number> data) {
}
// 方法的申明
public <T extends Integer> T demo3(Bar<? super Integer> data) {
}

2 超类限定

// 方法上
public <T> T demo1(Bar<? super Integer> data) {
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/504183.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

音视频开发之旅(80)- AI数字人-腾讯开源AniPortrait-音频驱动的肖像动画

目录 1、前言 2、效果展示 3、原理学习 4、遇到的问题与解决方案 5、资料 一、前言 一个月前阿里Emo发布&#xff0c;通过音频驱动的非常自然的肖像视频&#xff0c;引起很大反响。具体看下面的视频&#xff0c;但是并没有开源其代码。 这两天腾讯开源了其音频驱动的肖像…

2024年美团笔试题(1)

一.题目描述 小美拿到了一个排列&#xff0c;其中初始所有元素都是红色&#xff0c;但有些元素被染成了白色。 小美每次操作可以选择交换任意两个红色元素的位置。她希望操作尽可能少的次数使得数组变成非降序&#xff0c;你能帮帮她吗? 排列是指:一个长度为n的数组&#…

Java | Leetcode Java题解之第1题两数之和

题目&#xff1a; 题解&#xff1a; class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer, Integer> map new HashMap<>();for(int i 0; i< nums.length; i) {if(map.containsKey(target - nums[i])) {return new int[] {map.get(tar…

【React】vite + react 项目,进行配置 eslint

安装与配置 eslint 1 安装 eslint babel/eslint-parser2 初始化配置 eslint3 安装 vite-plugin-eslint4 配置 vite.config.js 文件5 修改 eslint 默认配置 1 安装 eslint babel/eslint-parser npm i -D eslint babel/eslint-parser2 初始化配置 eslint npx eslint --init相关…

应急物资管理系统|实现应急物资的全生命周期管理和监控

应急物资管理系统是一种现代化、智能化、可视化的物资管理平台&#xff0c;主要用于实现对应急物资的全生命周期管理和监控&#xff0c;并提供可靠的应急响应支持。 应急物资管理系统功能 准入控制&#xff1a;东识应急物资管理系统可以实现准入控制&#xff0c;确保只有经过授…

C语言----strcmp()函数:比较两个字符串

C语言中strcmp&#xff08;&#xff09;用于对两个字符串进行比较&#xff08;区分大小&#xff09;。 头文件&#xff1a;string.h 语法原型 int strcmp(const char*str1,const char*str2) 参数str1和str2是参与比较的两个字符串。 strcmp()是根据ASCLL编码依次比较str1和str…

MP设置动态表名

Mybatis设置动态表名 Mybatis设置动态表名1.动态表名插件2.传递表名3.注意事项 Mybatis设置动态表名 1.动态表名插件 MybatisPlus中提供了一个动态表名的插件&#xff1a;https://baomidou.com/pages/2a45ff/#dynamictablenameinnerinterceptor 插件的部分源码如下&#xff…

大模型面试准备(十):大模型数据处理方法及优秀的开源数据介绍

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何备战、面试常考点分享等热门话题进行了深入的讨论。 合集在这…

【Consul】Linux安装Consul保姆级教程

【Consul】Linux安装Consul保姆级教程 大家好 我是寸铁&#x1f44a; 总结了一篇【Consul】Linux安装Consul保姆级教程✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 今天要把编写的go程序放到linux上进行测试Consul服务注册与发现&#xff0c;那怎么样才能实现这一过程&am…

内网渗透之域环境探索和简单提权

参考文章&#xff1a;http://t.csdnimg.cn/AZ2OR 一个简单的域环境可以这样子搭建&#xff1a; 其中边界服务器有两张网卡&#xff0c;一个是对外的公网网卡&#xff0c;另一张是对内的局域网网卡。一般渗透过程中&#xff0c;拿下这个作为跳板机&#xff0c;进而继续渗透。 …

P23—P25:标识符和关键字

标识符 什么是标识符&#xff1f; 在java源程序中&#xff0c;程序员有权自己命名的单词都是标识符在EditPlus编译器中&#xff0c;表示符以黑色高亮字体显示 标识符可以标识什么元素&#xff1f; 类名方法名变量名接口名常量名 … 标识符的命名规则&#xff1a; 只能由**数…

C++学习随笔(8)——模板初阶

本章我们来学习一下C的模版部分&#xff01; 目录 1. 泛型编程 2. 函数模板 2.1 函数模板概念 2.1 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 3. 类模板 3.1 类模板的定义格式 3.2 类模板的实例化 1. 泛型编程 如何实现一个通…

Android vehicle车辆属性新增demo

目录 前言一、Vehicle模块1.1 简介1.2 Vehicle框架1.3 主要功能和特点1.4 重要服务CarService1.4.1 简介1.4.2 组成1.4.3 启动时序1.4.4 作用 二、车辆属性新增demo2.1 CarPropertyService2.1.1 简介2.1.2 架构2.1.3 车辆属性 API2.1.4 CarPropertyService 初始化流程 2.2 App …

鸿蒙ARKTS--简易的购物网站

目录 一、media 二、string.json文件 三、pages 3.1 登录页面&#xff1a;gouwuPage.ets 3.2 PageResource.ets 3.3 商品页面&#xff1a;shangpinPage.ets 3.4 我的页面&#xff1a;wodePage.ets 3.5 注册页面&#xff1a;zhucePage.ets 3. 购物网站主页面&#xff…

在GitHub上上传项目(Idea)

repository创建好后&#xff0c;GitHub会提示相应的命令 在Idea的终端执行这些命令&#xff0c;就OK了 在GitHub上查看&#xff0c;已经上传成功

设备树语法

设备树语法 1 Devicetree格式1.1 DTS文件格式1.2 node格式1.3 properties格式 2 dts文件包好desi文件3 常用的 属性 properties3.1 #address-cells、#size-cells3.2 compatible3.3 model3.4 status3.5 reg&#xff08;设备不同reg属性的含义就不同&#xff09;3.6 name、device…

企业知识库搭建不再是难题,靠这几个软件就可以了

在当今知识为王的时代&#xff0c;具备一套强大且实用的企业知识库&#xff08;Knowledge Base&#xff09;已成为提升工作效率、促进团队合作不可或缺的工具。那么&#xff0c;问题来了&#xff0c;我们该如何搭建一套属于自己的知识库呢&#xff1f;今天&#xff0c;我就给大…

软件工程 - 04 需求分析

文章目录 需求分析需求分析方法系统建模用例图类图对象图活动图时序图协作图构件图部署图 软件开发各个阶段的图 需求分析 软件开发中非常重要的一环&#xff1b;好的需求分析方法&#xff0c;可以帮助更好地理解用户需求&#xff0c;准确定义系统的功能和性能要求&#xff0c…

深入理解数据结构(3):栈和队列详解

文章主题&#xff1a;顺序表和链表详解&#x1f331;所属专栏&#xff1a;深入理解数据结构&#x1f4d8;作者简介&#xff1a;更新有关深入理解数据结构知识的博主一枚&#xff0c;记录分享自己对数据结构的深入解读。&#x1f604;个人主页&#xff1a;[₽]的个人主页&#x…

系统优化都没做过?看这篇就够了

目录 一、系统优化指标 二、系统优化简介 三、系统优化 3.1 CPU 高 3.2 内存占用高 业务引起的内存升高 程序自身引起的内存问题 3.3 磁盘I/O 3.4 网络 3.5 数据库优化 3.6 响应时间高 3.7 吞吐量 3.8 代码层面优化 3.9 业务优化 四、JVM优化 4.1 堆内存设置 4.2 选择何时的…