当前位置: 首页 > 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: …...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

Qt 事件处理中 return 的深入解析

Qt 事件处理中 return 的深入解析 在 Qt 事件处理中&#xff0c;return 语句的使用是另一个关键概念&#xff0c;它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别&#xff1a;不同层级的事件处理 方…...