JDK1.5 新特性【泛型】

前言

        泛型在 JavaSE 阶段是学习过的,但是毕竟处理定义一些简单的集合就很少用到它了,至于最近 Flink 中遇到的 泛型方法,更是感觉闻所未闻,以及源码中加在接口、方法、类前的各种 <T,V> 让我实在自觉羞愧,于是今天就来专门深入学习一下泛型。为了和前几篇文章对应,这里就叫 JDK1.5 新特性吧。

        后续应该还会再去深入学习一些基础的东西,比如注解反射,不用它就学不会。

泛型

1、为什么要使用泛型

要回答这个问题,我们先看看现在我们是怎么使用泛型的。

/*** 泛型定义了这个 ArrayList 存储的类型都是 String 类型*/ArrayList<String> list = new ArrayList<>();list.add("name");list.add(10);    //编译报错

我们可以看到,当我们定义了一个泛型为 String 类型的 ArrayList 集合时,当我们往进添加一条 int 类型的数据就会报错,这是因为这里的泛型约束了我们的集合中的数据类型必须都是一致的 String 类型。

那么在 JDK1.5 之前,在没有引入泛型之前是怎么防止数据类型不一致的呢?

/*** 在 JDK1.5 之前是没有泛型的* 所以当时的 集合 存储的都是 Object 类型*/ArrayList list = new ArrayList();list.add("hello");list.add(10);

在 JDK1.5 之前,集合存储的都是 Object 类型的数据,所以不管是数值类型、String 还是别的引用类型,通通可以存进去而且不会报错。但是这也给我们后期处理数据造成了麻烦。

Iterator iterator = old_list.iterator();while (iterator.hasNext()){// 遍历到 10 会报错 java.lang.Integer cannot be cast to java.lang.StringString next = (String) iterator.next(); System.out.println(next);            
}

对于上面集合中的数据,集合中存在多种数据类型,当我们使用迭代器或者循环遍历时,必然会因为取出数据的类型不一致造成报错,因为我们没法判断下一个数据的类型。

那么当时的集合是怎么进行使用的呢?

对于我们上面的集合(包含 String 和 int 两种数据类型),放在当时是这样的:

/*** 所以在 JDK1.5 之前 使用集合需要判断元素类型*/Object object = iterator.next();if (object instanceof String){String next = (String) object;System.out.println(next);}else if (object instanceof Integer){int next = (int) object;System.out.println(next);}

我们需要判断每个数据的类型,防止因为数据类型不一致而转换失败。很明显这种方式是极其复杂繁琐的。

2、泛型概述

所以为什么要使用泛型?因为泛型提供了编译时的类型安全检测机制,它可以帮助我们程序员在编译时就检测到非法的类型。

泛型的本质是参数化类型,也就是说操作的数据类型被指定为一个参数。

泛型是一种把数据类型的明确工作推迟到创建对象或者调用方法的时候才去明确的特殊类型。

注意:泛型参数只能是引用类型,不能是原始类型(比如 int、double、float、char)。

泛型可以使用在 方法、接口、类,分别称作:泛型类、泛型方法和泛型接口。

3、泛型类

3.1、基本格式

基本格式:修饰符 class 类名<类型>{}

public class Student<T>{}

注意:这里的 T 可以随便写,但是我们常用的是 T(Type)、E(Element Java集合框架中经常使用)、K(Key)、V(Value) 等参数类型来表示泛型。

3.1、泛型类的使用

我们先定义一个普通的 Student 类,只有一个 id 属性:

public class Student {public String id;   // 学号public String getId() {return id;}public void setId(String id) {this.id = id;}
}

我们可以这样来得到它的属性。 

Student<String> student = new Student<>();student.setId("2023001");System.out.println(student.getId());

        但是,我们的学号可能是 String 类型,也可能是 int 类型,这该怎么办?难道把 id 设置为 Object 类型?设置为 Object 类型当然是可以的,但是当我们要对该学号进行一些处理的时候(比如学号+1 或者 学号拼接一个字符串)就有可能又涉及到强制转换了(Object -> String 或者 Object-> Integer)。

所以,这里我们这里就可以定义一个泛型类:

public class Student<T> {public T id;   // 学号public T getId() {return id;}public void setId(T id) {this.id = id;}
}
   Student<String> student = new Student<>();student.setId("2023001");System.out.println(student.getId());// 注意:泛型只能是引用类型Student<Integer> stu = new Student<>();stu.setId(2023001);System.out.println(stu.getId());Student<Double> st = new Student<>();st.setId(12.0);System.out.println(st.getId());

可以看到,我们定义泛型类后,每次创建 Student 对象时,我们可以随意地指定它参数的类型。

注意:我们虽然指定了泛型,但是我们依然可以创建一个没有指定泛型的对象,只不过此时我们类的属性类型以及 getter 方法的返回值都变成了 Object,setter 方法的参数也变成了 Object,所以这当我们要取值的时候,又要进行一个类型的判断。所以当我们既然使用了泛型类,就一定要指定泛型参数!

4、泛型方法

4.1、基本格式

基本格式:修饰符 <泛型参数> 返回值类型 方法名 (类型 变量名){}

public <T> void show(T t){...}

4.2、泛型方法的使用

我们继续定义一个 Person 类,我们需要定义一个 show 方法,你给它传什么类型的参数,它就返回什么类型的参数。因为参数类型不同,所有这里我们需要多次重载该 show 方法:

public class Person {public Integer show(int grade){return grade;}public String show(String grade){return grade;}public Double show(double grade){return grade;}
}

但是很明显,这样代码的冗余度很高。所以我们可以先使用泛型类来进行优化:

public class Person<T> {public T show(T grade) {return grade;}
}

这样明显我们类中的代码简洁了很多,但是我们在调用该方法的时候依然很复杂:

Person<String> a = new Person<>();String res1 = a.show("100");Person<Integer> b = new Person<>();int res2 = b.show(95);Person<Double> c = new Person<>();double res3 = c.show(95.8);

这就需要我们定义一个泛型方法:

public class Person {public<T> T show(T grade) {return grade;}
}

使用泛型方法: 

Person person = new Person();String res1 = student.show("s");int res2 = student.show(90);double res3 = student.show(96.5);

很明显,使用泛型方法后,不管是类的定义还是方法的调用都是最简洁的。

5、泛型接口

5.1、基本格式

基本格式:修饰符 interface 接口名<类型>{...}

public interface MyInterface<T>{...}

5.2、泛型接口的使用

5.2.1、普通方法

我们定义一个接口,并定义一个抽象方法,要求它的所有实现类的方法参数类型和返回值类型都是统一的类型。

public interface MyInterface <T>{T show(T t);
}

注意:这里接口中的方法并不是泛型方法,所以它的 T 指的就是泛型接口中的 T。 

public class Person implements MyInterface<String>{@Overridepublic String show(String grade) {return grade;}
}

可以看到,使用泛型接口,就相当于约束了其实现类的一些实现要求(比如上面 这个泛型接口,就要求了它的实现类必须实现 show 方法,并且该方法的参数类型和返回值类型都与接口的泛型类型必须保持一致)。

5.2.2、泛型方法

那,如果我们要实现上面 4.2 中的效果,也就是说这个show方法传入什么类型的参数,返回什么类型的结果,这又该怎么实现呢?我们可以在接口中定义一个泛型方法:

public interface MyInterface <T>{<M> M show(M m);
}

注意:泛型接口中的泛型类型是用来约束其 实现类 或者 非泛型方法的参数类型和返回值类型(比如上面 5.1.1)或者泛型方法的参数类型或者返回值类型之一(因为如果一个泛型方法的返回值类型和参数类型都是接口的泛型类型那么这个泛型方法就没有意义了),而泛型方法中的泛型类型是用来约束方法的返回值类型和参数类型的。

public class Person<T> implements MyInterface<T>{@Overridepublic <M> M show(M m) {return m;}
}

我们调用方法:

Person<String> person = new Person<>();String res1 = person.show("100");int res2 = person.show(96);double res3 = person.show(98.5);

可以看到我们这个Student类的泛型 String 是完全没有必要的,所以我们可以这样改造:

public interface MyInterface{<M> M show(M m);
}
public class Person implements MyInterface{@Overridepublic <M> M show(M m) {return m;}
}
Person person = new Person();String res1 = person.show("100");int res2 = person.show(96);double res3 = person.show(98.5);

6、泛型类型通配符

  • 类型通配符 <?> 一般用于接受使用,不能够做添加。
  • List <?> 表示元素类型未知的 list ,它的元素可以匹配任何类型。
  • 带通配符的 List 仅表示它是各种泛型 list 的父类,并不能把元素添加到其中(不能调用 add方法 )。

6.1、泛型类型通配符的使用

public class Test {public static void main(String[] args) {ArrayList<String> list1 = new ArrayList<>();list1.add("tom");list1.add("bob");list1.add("mike");ArrayList<Integer> list2 = new ArrayList<>();list2.add(10);list2.add(18);list2.add(20);printList(list1);printList(list2);}/*** 不知道接受到的list会是什么类型* @param list 不能调用 add*/public static void printList(List<?> list){list.forEach(System.out::println);}}

6.2、泛型类型通配符的上限和下限

类型通配符的上限:<? extends A> 表示类型必须是 A 或者 A 的子类。

类型通配符的下限:<? super B> 表示类型必须是 B 或者 B 的父类。

7、泛型擦除机制

泛型只在编译阶段限制参数类型的传递,在运行阶段都会被擦除,也就是说在运行时我们的 .class 文件中是没有泛型的!

我们可以通过反编译工具将我们的 .class 文件反编译为 .java 文件,完成后,我们就会发现,我们所定义的泛型 <T>、<K>... 其实在编译后都变成了 Object 。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC总结

SpringMVC简介 简介 SpringMVC是一款基于Servlet API构建的原始Web框架&#xff0c;从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称&#xff08; spring-webmvc &#xff09;&#xff0c;但它通常被称为“Spring MVC”。 调用流程 接收数…

3.6-Dockerfile语法梳理及最佳实践

WORKDIR是设置当前docker的工作目录 ADD 和 COPY 为了将本地的一些文件添加到docker image里面&#xff0c;ADD 和 COPY的作用特别像&#xff0c;但是ADD 和 COPY还有一些区别&#xff0c;ADD不仅可以添加本地文件到docker里面&#xff0c;还可以将文件在添加到docker image里面…

4.Pod详解【四】

文章目录 4. Pod详解4.1 Pod介绍4.1.1 Pod结构4.1.2 Pod定义 4.2 Pod配置4.2.1 基本配置4.2.2 镜像拉取4.2.3 启动命令4.2.4 环境变量4.2.5 端口设置4.2.6 资源配额 4.3 Pod生命周期4.3.1 创建和终止4.3.2 初始化容器4.3.3 钩子函数4.3.4 容器探测4.3.5 重启策略 4.4 Pod调度4.…

C++算法入门练习——树的带权路径长度

现有一棵n个结点的树&#xff08;结点编号为从0到n-1&#xff0c;根结点为0号结点&#xff09;&#xff0c;每个结点有各自的权值w。 结点的路径长度是指&#xff0c;从根结点到该结点的边数&#xff1b;结点的带权路径长度是指&#xff0c;结点权值乘以结点的路径长度&#x…

嵌入式开发--赛普拉斯cypress的铁电存储器FM25CL64B

嵌入式开发–赛普拉斯cypress的铁电存储器FM25CL64B 简介 FM25CL64B是赛普拉斯cypress出品的一款铁电存储器&#xff0c;这种存储器最大的优势是可以像RAM一样随机存储&#xff0c;和按字节写入&#xff0c;也可以像ROM一样掉电仍然可以保存数据&#xff0c;是一种相当优秀的…

Springboot 对于数据库字段加密方案(此方案是对字符串处理的方案)

背景:在erp开发中&#xff0c;有些用户比较敏感数据库里的数据比较敏感&#xff0c;系统给用户部署后&#xff0c;公司也不想让任何人看到数据&#xff0c;所以就有了数据库字段加密方案。 技术 spring boot mybatisplus 3.3.1 mybatisplus 实际提供了 字段加密方案 第一 他…

使用Spring Boot结合JustAuth实现支付宝、微信、微博扫码登录功能

使用Spring Boot结合JustAuth实现支付宝、微信、微博扫码登录功能 在使用Spring Boot结合JustAuth实现支付宝、微信、微博扫码登录功能之前&#xff0c;需要先确保已经配置好Spring Boot项目&#xff0c;并且添加了JustAuth的依赖。你可以在项目的pom.xml文件中添加如下依赖&a…

PostgreSQL 难搞的事系列 --- vacuum 的由来与PG16的命令的改进 (1)

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, Oceanbase, Sql Server等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友…

【Python从入门到进阶】42、使用requests的Cookie登录古诗文网站

接上篇《41、有关requests代理的使用》 上一篇我们介绍了requests代理的基本使用&#xff0c;本篇我们来学习如何利用requests的Cookie登录古诗文网。 一、登录网站及目的介绍 我们需要Cookie模拟登录的网站为&#xff1a;https://www.gushiwen.cn/&#xff08;古诗文网&…

许多网友可能还不知道,升级到Windows 11其实没那么复杂,只要符合几个条件可以了

如果你的Windows 10电脑可以升级Windows 11,现在怎么办?有几种方法可以免费安装新的操作系统。以下是你的选择。 如果你想升级到Windows 11,你可以随时购买一台已经安装了操作系统的新电脑。然而,如果你目前的Windows 10 PC满足所有必要的升级要求,那么在Windows 11免费的…

pm2在Windows环境中的使用

pm2 进程管理工具可以Windows操作系统上运行&#xff0c;当一台Windows电脑上需要运行多个进程时&#xff0c;或者运维时需要运行多个进程以提供服务时。可以使用pm2&#xff0c;而不再是使用脚本。 1. 使用PM2管理进程 1.1. 启动PM2项目 1.1.1. 直接启动项目 参数说明&…