当前位置: 首页 > news >正文

重学Java (一) 泛型

1. 前言

泛型编程自从 Java 5.0 中引入后已经超过15个年头了。对于现在的 Java 码农来说熟练使用泛型编程已经是家常便饭的事情了。所以本文就在不对泛型的基础使用在做说明了。 如果你还不会使用泛型的话,可以参考下面两个链接

  • Java 泛型详解
  • The Java™ Tutorials (Lesson: Generics)

这篇文章就简答聊一下,我实际在开发工作中很少用的到泛型方法这个知识点,以及在实际项目中有哪些东西会使用到泛型。

2. 泛型方法

在阅读代码的时候我们经常会看到下面这样的方法 (这段代码摘自 java.util.AbstractCollection)

 public <T> T[] toArray(T[] a) {// Estimate size of array; be prepared to see more or fewer elementsint size = size();T[] r = a.length >= size ? a :(T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);Iterator<E> it = iterator();for (int i = 0; i < r.length; i++) {if (! it.hasNext()) { // fewer elements than expectedif (a == r) {r[i] = null; // null-terminate} else if (a.length < i) {return Arrays.copyOf(r, i);} else {System.arraycopy(r, 0, a, 0, i);if (a.length > i) {a[i] = null;}}return a;}r[i] = (T)it.next();}// more elements than expectedreturn it.hasNext() ? finishToArray(r, it) : r;
}

那么 pulic 关键字后面的那个 <T> 就是用来标记这个方法是一个泛型方法。 那什么是泛型方法呢。

官方的解释是这样的

Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter's scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors.

通俗点来将就是将一个方法泛型化,让一个普通的类的某一个方法具有泛型功能。 如果在一个泛型类中增加一个泛型方法,那这个泛型方法就可以有一套独立于这个类的泛型类型。

通过一个简单的例子, 我们来看看

/*** GenericClass 这个泛型类是一个简单的套皮的 HashMap*/
public class GenericClass<K, V> {private Map<K, V> map = new HashMap<>();public V put(K key, V value) {return map.put(key, value);}public V get(K key) {return map.get(key);}// 泛型方法 genericMethod 可以接受一个全新的、作用域只限本函数的泛型类型Tpublic <T> T genericMethod(T t) {return t;}}

实际使用起来

GenericClass<String, Integer> map = new GenericClass<>();
// put 和 get 方法的参数必须使用定义时指定的 String 和 Integer
System.out.println(map.put("One", 1));
System.out.println(map.get("One"));
// 泛型方法 genericMethod 就可以接受一个 String 和 Integer 以外的类型
System.out.println(map.genericMethod(new Double(1.0)).getClass());

我们再来看看 JDK 中使用到泛型方法的例子。我们最常使用的泛型容器 ArrayList 中有个 toArray 方法。JDK 在它的实现中就提供了两个版本,其中一个就是泛型方法的版本

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable 
{// 这是一个普通版本,返回一个Object的数组public Object[] toArray() {return Arrays.copyOf(elementData, size);}// 这是一个泛型方法的版本,将容器里存储的元素输出到 T[] 数组中。 其中 T 必须是 E 的父类,否则 System.arraycopy 会抛出 ArrayStoreException 异常      public <T> T[] toArray(T[] a) {if (a.length < size)// Make a new array of a's runtime type, but my contents:return (T[]) Arrays.copyOf(elementData, size, a.getClass());System.arraycopy(elementData, 0, a, 0, size);if (a.length > size)a[size] = null;return a;}
}

泛型方法总体上来说就是可以给与现有的方法实现上,增加一个更加灵活的实现可能。

3. 实战应用

在实际的项目中,对于泛型的使用,除了像倾倒垃圾一样往泛型容易里塞各种 java bean 和其他泛型对象。还能怎么使用泛型呢?

我们在实际的一些项目中,会对数据库中的一些表(多数时候是全部)先实现 CRUD (Create, Read, Update, Delete)的操作,并从这些操作中延伸出一些简单的 REST 风格的 WebAPI 接口,然后才会根据实际业务需要实现一些更复杂的业务接口。

大体上会是下面这个样子。


// 这是一个简单的 Entity 对象
// 通常现在的 Java 应用都会使用到 Lombok 和 Spring Boot
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Entity
@Table(name = "user")
public class User {@Idprivate Long id;private String username;private String password;
}// 然后这个是 DAO 接口继承自 spring-data 的 JpaRepository
public interface UserDao extends JpaRepository<User, Long> {
}// 在来是一个访问 User 资源的 Service 和他的实现
public interface UserService {List<User> findAll();Optional<User> findById(Long id);User save (User user)void deleteById(Long id);
}@Service
public class UserSerivceImpl implements UserService {private UserDao userDao;public UserServiceImpl(UserDao userDao) {this.userDao = userDao;}@Overridepublic List<User> findAll() {return this.dao.findAll();}@Overridepublic Optional<User> findById(Long id) {return this.dao.findById(id);}@Overridepublic User save(User user) {return this.dao.save(user);}@Overridepublic void deleteById(Long id) {this.dao.deleteById(id);}
}// 最后就是 WebAPI 的接口了
@RestController
@RequestMapping("/user/")
public class UserController{private UserService userService;public UserController(userService userService) {this.userService = userService;}@GetMapping@ResponseBodypublic List<User> fetch() {return this.userService.findAll();}@GetMapping("{id}")@ResponseBodypublic User get(@PathVariable("id") Long id) {// 由于是示例这里就不考虑没有数据的情况了return this.userService.findById(id).get();}@PostMapping@ResponseBodypublic User create(@RequestBody User user) {return this.userService.save(user);}@PutMapping("{id}")@ResponseBodypublic User update(@RequestBody User user) {return this.userService.save(user);}@DeleteMapping("{id}")@ResponseBodypublic User delete(@PathVariable("id") Long id) {User user = this.userService.findById(id);this.userService.deleteById(id);return user;}
}

大致一个表的一套相关接口就是这个样子的。如果你的数据库中有大量表的话,而且每个表都需要提供 REST 风格的 WebAPI 接口的话,那么这将是一个相当枯燥的而又及其容易出错的工作。

为了不让这项枯燥而又容易犯错的工作占去我们宝贵的私人时间,我们可以通过泛型和继承的技巧来重构从 Service 层到 Controller 的这段代码(感谢 spring-data 提供了 JpaRepository, 让我们不至于从 DAO 层重构)

3.1 Service 层的重构

首先是 Service 接口的重构,我们 Service 层接口就是定义了一组 CRUD 的操作,我们可以将这组 CRUD 操作抽象到一个父接口,然后所有 Service 层的接口都将继承自这个父接口。而接口中出现的 Entity 和主键的类型(上例中 User 的主键 id 的类型是 Long)就可以用泛型来展现。

// 这里泛型表示 E 来指代 Entity, ID 用来指代 Entity 主键的类型
public interface ICrudService<E, ID> {List<E> findAll();Optional<E> findById(ID id);E save(E e);void deleteById(ID id);
}// 然后 Service 层的接口,就可以简化成这样
public interface UserService extends ICrudService<User, Long> {
}

同样 Service 层的实现也可以使用相似的方法具体实现可以抽象到一个基类中。

// 相比 ICrudService 这里有多了一个泛型 T 来代表 Entity 对应的 DAO, 我们的每一个 DAO 都继承自
// spring-data 的 JpaRepository 所以,这里可以使用到泛型的边界
public abstract class AbstractCrudService<T extends JpaRepository<E, ID>, E, ID> {private T dao;public AbstractCrudService(T dao) {this.dao = dao;}public List<E> findAll() {return this.dao.findAll();}public Optional<E> findById(ID id) {return this.dao.findById(id);}public E save(E e) {return this.dao.save(e);}public void deleteById(ID id) {this.dao.deleteById(id);}
}// 那 Service 的实现类可以简化成这样
@Service
public class UserServiceImpl extends AbstractCrudService<UserDao, User, Long> implements UserService {public UserServiceImpl(UserDao dao) {supper(dao);}
}

同样我们可以通过相同的方法来对 Controller 层进行重构

// Controller 层的基类
public abstract class AbstractCrudController<T extends ICrudService<E, ID>, E, ID> {private T service;public AbstractCrudController(T service) {this.service = service;}@GetMapping@ResponseBodypublic List<E> fetch() {return this.service.findAll();}@GetMapping("{id}")@ResponseBodypublic E get(@PathVariable("id") ID id) {// 由于是示例这里就不考虑没有数据的情况了return this.service.findById(id).get();}@PostMapping@ResponseBodypublic E create(@RequestBody E e) {return this.service.save(e);}@PutMapping("{id}")@ResponseBodypublic E update(@RequestBody E e) {return this.service.save(e);}@DeleteMapping("{id}")@ResponseBodypublic E delete(@PathVariable("id") ID id) {E e = this.service.findById(id).get();this.service.deleteById(id);return e;}
}// 具体的 WebAPI
@RestController
@RequestMapping("/user/")
public class UserController extends AbstractCrudController<UserService, User, Long> {public UserController(UserService service) {super(service);}
}

经过重构可以消减掉 Servcie 和 Controller 中的大量重复代码,使代码更容易维护了。

4. 结尾

关于泛型就简单的说这些了,泛型作为 Java 日常开发中一个常用的知识点,其实还有很多知识点可以供我们挖掘,奈何本人才疏学浅,这么多年工作下来,只积累出来这么点内容。

文末放上示例代码的代码库:

  • GitHub入口
  • gitee入口

相关文章:

重学Java (一) 泛型

1. 前言 泛型编程自从 Java 5.0 中引入后已经超过15个年头了。对于现在的 Java 码农来说熟练使用泛型编程已经是家常便饭的事情了。所以本文就在不对泛型的基础使用在做说明了。 如果你还不会使用泛型的话&#xff0c;可以参考下面两个链接 Java 泛型详解The Java™ Tutorial…...

Docker 部署 Redis 服务

拉取最新版本的 Redis 镜像: $ sudo docker pull redis:latest在本地预先创建好 data 目录和 conf/redis.conf 文件。 使用以下命令来运行 Redis 容器: $ sudo docker run -itd --name redis --privilegedtrue -p 6379:6379 -v /home/ubuntu/docker/redis/data:/data -v /ho…...

阿里云产品试用系列-负载均衡 SLB

阿里云负载均衡&#xff08;Server Load Balancer&#xff0c;简称SLB&#xff09;是云原生时代应用高可用的基本要素。通过将流量分发到不同的后端服务来扩展应用系统的服务吞吐能力&#xff0c;消除单点故障并提升应用系统的可用性。阿里云SLB包含面向4层的网络型负载均衡NLB…...

drf 对象级权限

drf 对象级权限 Django REST Framework&#xff08;DRF&#xff09;提供了对象级别权限&#xff08;Object-level permissions&#xff09;来控制特定对象的访问权限。 简单来说&#xff1a;通过视图类中的self.get_object(pk)得到一个obj对象(视图对象)&#xff0c;在与requ…...

八大排序(二)--------冒泡排序

本专栏内容为&#xff1a;八大排序汇总 通过本专栏的深入学习&#xff0c;你可以了解并掌握八大排序以及相关的排序算法。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;八大排序汇总 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库…...

SmartSQL 一款开源的数据库文档管理工具

建议直接蓝奏云下载安装 蓝奏云下载&#xff1a;https://wwoc.lanzoum.com/b04dpvcxe 蓝奏云密码&#xff1a;123 项目介绍 SmartSQL 是一款方便、快捷的数据库文档查询、导出工具&#xff01;从最初仅支持 数据库、CHM文档格式开始&#xff0c;通过不断地探索开发、集思广…...

代码随想录算法训练营第56天 | ● 583. 两个字符串的删除操作 ● 72. 编辑距离 ● 动态规划之编辑距离总结篇

文章目录 前言一、583. 两个字符串的删除操作二、72. 编辑距离三、动态规划之编辑距离总结篇总结 前言 一、583. 两个字符串的删除操作 两种思路&#xff1a;1.直接动态规划&#xff0c;求两个字符串需要删除的最小次数 2.采用子序列的和-最长公共子序列。思路一分析如下&#…...

矩阵 m * M = c

文章目录 题1题2 题1 (2023江苏领航杯-prng) 题目来源&#xff1a;https://dexterjie.github.io/2023/09/12/%E8%B5%9B%E9%A2%98%E5%A4%8D%E7%8E%B0/2023%E9%A2%86%E8%88%AA%E6%9D%AF/ 题目描述&#xff1a; (没有原数据&#xff0c;自己生成的数据) from Crypto.Util.number…...

Linux——IO

✅<1>主页&#xff1a;&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;Linux——文件系统 ☂️<3>开发环境&#xff1a;Centos7 &#x1f4ac;<4>前言&#xff1a;是不是只有C/C有文件操作呢&#xff1f;python&#xff0c;java&…...

svn(乌龟svn)和SVN-VS2022插件(visualsvn) 下载

下载地址: https://www.visualsvn.com/visualsvn/download/...

开源日报 0824 | 构建UI组件和页面的前端工作坊

Storybook 是一个用于构建 UI 组件和页面的前端工作坊&#xff0c;支持多种主流框架&#xff0c;提供丰富的插件&#xff0c;具有可配置性强和扩展性好的特点。 storybookjs/storybook Stars: 79.9k License: MIT Storybook 是一个用于构建 UI 组件和页面的前端工作坊&#x…...

福建三明大型工程机械3D扫描工程零件三维建模逆向抄数-CASAIM中科广电

高精度3D扫描技术已经在大型工件制造领域发挥着重要作用&#xff0c;可以高精度高效率实现全尺寸三维测量&#xff0c;本期&#xff0c;我们要分享的应用是大型工程机械3D扫描案例。 铣轮是深基础施工领域内工法先进、技术复杂程度高、高附加值的地连墙设备&#xff0c;具有成…...

使用香橙派学习 Linux的守护进程

Q&#xff1a;什么是守护进程 A&#xff1a;Linux Daemon&#xff08;守护进程&#xff09;是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行 某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务&#xff0c;不是对整个系统就是对某个…...

数据治理-数据仓库和商务智能

数据仓库的作用 减少数据冗余&#xff0c;提高信息一致性&#xff0c;让企业能够利用数据做出更优决策的方法&#xff0c;数据仓库是企业数据管理的核心。 业务驱动因素 运营支持职能、合规需求&#xff08;历史数据响应&#xff09;和商务智能活动&#xff08;主因&#xff1…...

CH2--x86系统架构概览

2.1 OVERVIEW OF THE SYSTEM-LEVEL ARCHITECTURE 图中的实线箭头表示线性地址&#xff0c;虚线表示段选择器&#xff0c;虚线箭头表示物理地址 2.1.1 Global and Local Descriptor Tables 全局描述符表 (GDT) GDT是一个全局的段描述符表&#xff0c;它存储在系统内存中的一个固…...

Immutable.js API 简介

Immutable-js 这个库的实现是深拷贝还是浅拷贝&#xff1f;immutable 来源immutable.js三大特性&#xff1a; 持久化数据结构结构共享惰性操作 Immutable.js 的几种数据类型 immutable 使用 使用 npm 安装 immutable&#xff1a; 常用API介绍 MapListList.isList() 和 Map.isMa…...

HLSL 入门(一)

HLSL High Level Shader Language 高级着色语言&#xff0c;是Direct3D中用来编写Shader的语言。其语法类似于C语言。 虽然其主要作用是用来编写例如顶点着色器&#xff0c;像素着色器。但本质是对图形并行管线进行编程&#xff0c;因此也能用来编写用于计算的着色器&#xff…...

【Docker】挂载数据卷

一、Docker数据卷说明及操作 在Docker中挂载数据卷是一种将数据持久化保存的方法&#xff0c;以便容器之间或容器与主机之间共享数据。以下是如何在Docker中挂载数据卷的步骤&#xff1a; 1、创建数据卷 首先&#xff0c;您需要创建一个数据卷。可以使用以下命令创建一个数据卷…...

[技术干货]spring 和spring boot区别

Spring 和 Spring Boot 都是 Java 框架&#xff0c;用于构建企业级应用程序。Spring 是一个完整的框架&#xff0c;提供各种功能&#xff0c;包括依赖注入、事务管理、数据访问、Web 开发等。Spring Boot 是一个基于 Spring 的框架&#xff0c;旨在简化 Spring 应用程序的开发和…...

【hudi】数据湖客户端运维工具Hudi-Cli实战

数据湖客户端运维工具Hudi-Cli实战 help hudi:student_mysql_cdc_hudi_fl->help AVAILABLE COMMANDSArchived Commits Commandtrigger archival: trigger archivalshow archived commits: Read commits from archived files and show detailsshow archived commit stats: …...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...

nnUNet V2修改网络——暴力替换网络为UNet++

更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅

目录 前言 操作系统与驱动程序 是什么&#xff0c;为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中&#xff0c;我们在使用电子设备时&#xff0c;我们所输入执行的每一条指令最终大多都会作用到硬件上&#xff0c;比如下载一款软件最终会下载到硬盘上&am…...