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

removeIf 方法设计理念及泛型界限限定

ArrayList 中的 removeIf 方法是 Java 8 中引入的集合操作方法之一。它使用了 Predicate 接口作为参数,以便根据指定的条件移除集合中的元素。以下是对 removeIf 方法入参设计的详细解释:

Predicate 接口

Predicate 是一个函数式接口,定义了一个 test 方法,用于接收一个参数并返回一个布尔值。它的签名如下:

@FunctionalInterface
public interface Predicate<T> {boolean test(T t);
}

Predicate 接口通常用于对输入参数进行条件测试。结合 removeIf 方法,这个接口被用来判断集合中的元素是否应该被移除。

removeIf 方法的签名

ArrayList 类中,removeIf 方法的签名如下:

public boolean removeIf(Predicate<? super E> filter)

其中,E 是集合的泛型类型参数,而 Predicate<? super E> 表示可以接受 E 类型或其父类型的参数。

设计理由

  1. 灵活性: 使用 Predicate<? super E> 作为参数,使得 removeIf 方法可以接受针对元素类型及其父类型的条件。这种设计提供了更大的灵活性。例如,如果有一个 ArrayList<Number>,可以传入 Predicate<Object>,因为 ObjectNumber 的父类型。

  2. 函数式编程: Java 8 引入了函数式编程的概念,允许使用 lambda 表达式和方法引用。这使得编写条件测试的代码变得更加简洁和直观。例如,可以使用 lambda 表达式来移除所有负数:

    ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
    list.removeIf(n -> n < 0);
    
  3. 类型安全: 泛型确保了 Predicate 的参数类型与集合元素类型一致,从而在编译时提供类型安全。这样可以避免传递错误类型的条件测试,导致运行时错误。

removeIf 方法的实现

removeIf 方法的内部实现使用了迭代器来遍历集合,并应用 Predicate 进行条件测试。以下是简化的实现:

public boolean removeIf(Predicate<? super E> filter) {Objects.requireNonNull(filter);boolean removed = false;final Iterator<E> each = iterator();while (each.hasNext()) {if (filter.test(each.next())) {each.remove();removed = true;}}return removed;
}

在这个实现中:

  1. 检查 filter 是否为 null
  2. 使用迭代器遍历集合。
  3. 对每个元素应用 Predicate 进行测试。
  4. 如果 Predicate 返回 true,则移除该元素。

使用示例

以下是一些使用 removeIf 方法的示例:

移除负数
ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
numbers.removeIf(n -> n < 0);
System.out.println(numbers); // 输出:[1, 3, 5]
移除空字符串
ArrayList<String> strings = new ArrayList<>(Arrays.asList("apple", "", "banana", " ", "cherry"));
strings.removeIf(String::isEmpty);
System.out.println(strings); // 输出:[apple,  , banana, cherry]

细品一下 removeIf() 方法入参设计

ArrayListremoveIf 方法中,使用 Predicate<? super E> 而不是 Predicate<E> 是为了增加方法的灵活性和通用性。具体来说,这与 Java 泛型的协变和逆变有关。以下是详细的解释:

泛型的协变和逆变

  • 协变(Covariance):允许使用某种类型及其子类型。用 <? extends E> 表示。
  • 逆变(Contravariance):允许使用某种类型及其超类型。用 <? super E> 表示。
  • 不变(Invariance):只能使用某种特定类型。用 <E> 表示。

removeIf 方法中,Predicate<? super E> 表示这个 Predicate 可以接受类型 EE 的任何超类型。这是一种逆变,用于增加灵活性。

为什么使用 <? super E> 而不是 <E>

  1. 灵活性: 使用 <? super E> 可以让 Predicate 接受类型 EE 的父类型的对象,从而增加了方法的灵活性。例如,如果 EInteger,那么 Predicate<? super Integer> 可以接受 Integer 及其父类 NumberObjectPredicate

    这意味着你可以传入更多种类的 Predicate,而不仅仅是严格匹配 EPredicate。例如:

    List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
    Predicate<Object> isEven = obj -> obj instanceof Integer && (Integer) obj % 2 == 0;
    list.removeIf(isEven); // 使用 Predicate<Object> 也可以
    
  2. 兼容性: 假设你有一个处理父类 NumberPredicate,比如:

    Predicate<Number> isPositive = num -> num.doubleValue() > 0;
    

    这个 Predicate 可以传递给 ArrayList<Integer>removeIf 方法,因为 NumberInteger 的超类型。如果 removeIf 方法只接受 Predicate<E>,那么你只能传入 Predicate<Integer>

  3. 类型安全: 使用 <? super E> 仍然确保了类型安全,因为在调用 removeIf 方法时,传入的 Predicate 需要能够处理类型 E。例如,对于 ArrayList<Integer>,传入的 Predicate 需要能够处理 Integer 及其超类型。

注意: ? 无界通配符主要用于不关心元素类型的操作,基本都是公共类操作。

例子

使用 Predicate<E>

如果 removeIf 只接受 Predicate<E>,那么只能传入 Predicate<Integer>

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
Predicate<Integer> isEven = n -> n % 2 == 0;
list.removeIf(isEven); // 正常工作

但是不能传入 Predicate<Number>

Predicate<Number> isPositive = num -> num.doubleValue() > 0;
// list.removeIf(isPositive); // 编译错误
使用 Predicate<? super E>

使用 Predicate<? super E>,可以传入 Predicate<Integer>Predicate<Number>

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Number> isPositive = num -> num.doubleValue() > 0;
list.removeIf(isEven); // 正常工作
list.removeIf(isPositive); // 也能正常工作

简而言之:

ArrayListremoveIf 方法使用 Predicate<? super E> 而不是 Predicate<E> 是为了增加方法的灵活性和通用性。通过允许传入类型 E 或其超类型的 PredicateremoveIf 方法变得更加通用和灵活,同时仍然保持类型安全。这样的设计使得在处理集合元素时,可以应用更多种类的条件,增加了代码的可重用性和灵活性。

再举例说明逆变的用法

逆变(Contravariance)在 Java 泛型中通过下界通配符(<? super T>)来实现。下界通配符允许使用某个类型及其父类型,这对于需要向集合中添加元素或处理泛型对象的写操作特别有用。以下是一些具体的例子来说明逆变的用法。

1. 使用逆变来添加元素到集合中

逆变可以确保能够向集合中添加类型 TT 的子类型的元素。

例子 1:向集合中添加元素
import java.util.List;
import java.util.ArrayList;public class CollectionUtils {public static void addAnimals(List<? super Dog> list) {list.add(new Dog());list.add(new Puppy());// list.add(new Animal()); // 编译错误,Animal 不是 Dog 的子类}public static void main(String[] args) {List<Animal> animalList = new ArrayList<>();addAnimals(animalList);for (Object obj : animalList) {System.out.println(obj.getClass().getSimpleName());}}
}class Animal {}
class Dog extends Animal {}
class Puppy extends Dog {}

在这个例子中,addAnimals 方法接受一个 List<? super Dog> 类型的参数,这意味着它可以接受 Dog 类型及其超类型(如 AnimalObject)的列表,并向其中添加 Dog 及其子类型(如 Puppy)的元素。

2. 处理通用的数据结构

逆变可以用于处理具有多种元素类型的数据结构,比如在比较器中。

例子 2:使用逆变来编写通用比较器
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;public class SortingUtils {public static <T> void sortList(List<T> list, Comparator<? super T> comparator) {Collections.sort(list, comparator);}public static void main(String[] args) {List<Integer> intList = new ArrayList<>();intList.add(3);intList.add(1);intList.add(4);intList.add(1);intList.add(5);Comparator<Number> numberComparator = (Number n1, Number n2) -> Double.compare(n1.doubleValue(), n2.doubleValue());sortList(intList, numberComparator);System.out.println(intList); // 输出: [1, 1, 3, 4, 5]}
}

在这个例子中,sortList 方法接受一个 Comparator<? super T> 类型的参数,这意味着它可以接受 T 类型及其超类型的比较器,从而使得比较器更加通用和灵活。

3. 使用逆变来编写通用的消费操作

逆变也可以用于消费操作,例如在处理不同类型的消费者时。

例子 3:使用逆变来编写通用的消费者
import java.util.List;
import java.util.ArrayList;
import java.util.function.Consumer;public class ConsumerUtils {public static <T> void processElements(List<T> list, Consumer<? super T> consumer) {for (T element : list) {consumer.accept(element);}}public static void main(String[] args) {List<Dog> dogList = new ArrayList<>();dogList.add(new Dog());dogList.add(new Puppy());Consumer<Animal> animalConsumer = animal -> System.out.println(animal.getClass().getSimpleName());processElements(dogList, animalConsumer); // 输出: Dog Puppy}
}class Animal {}
class Dog extends Animal {}
class Puppy extends Dog {}

在这个例子中,processElements 方法接受一个 Consumer<? super T> 类型的参数,这意味着它可以接受 T 类型及其超类型的消费者,从而使得消费者更加通用和灵活。

总结(添加、通用/公用、消费类型参数使用逆变)

逆变通过下界通配符(<? super T>)允许使用某个类型 T 及其父类型,主要用于写操作。使用逆变可以:

  1. 添加元素:确保可以向集合中添加类型 T 及其子类型的元素。
  2. 通用操作:处理具有多种元素类型的数据结构,使得操作更加灵活和通用。
  3. 消费操作:处理不同类型的消费者,使得消费者更加通用和灵活。

通过这些例子,可以看到逆变在编写通用和灵活的代码中发挥了重要作用,确保类型安全的同时增加了代码的灵活性。

相关文章:

removeIf 方法设计理念及泛型界限限定

ArrayList 中的 removeIf 方法是 Java 8 中引入的集合操作方法之一。它使用了 Predicate 接口作为参数&#xff0c;以便根据指定的条件移除集合中的元素。以下是对 removeIf 方法入参设计的详细解释&#xff1a; Predicate 接口 Predicate 是一个函数式接口&#xff0c;定义了…...

kubernetes集群部署elasticsearch集群,包含无认证和有认证模式

1、背景&#xff1a; 因公司业务需要&#xff0c;需要在测试、生产kubernetes集群中部署elasticsearch集群&#xff0c;因不同环境要求&#xff0c;需要部署不同模式的elasticsearch集群&#xff0c; 1、测试环境因安全性要求不高&#xff0c;是部署一套默认配置&#xff1b; 2…...

Java 随笔记: 集合与泛型

文章目录 1. 集合框架概述2. 集合接口2.1 Collection 接口2.2 List 接口2.3 Set 接口2.4 Map 接口 3. 集合的常用操作3.1 添加元素3.2 删除元素3.3 遍历元素3.4 判断大小3.5 判断是否为空 4. 迭代器4.1 迭代器的作用4.2 迭代器的使用4.3 迭代器与增强 for 循环4.4 迭代器的注意…...

SurrealDB:高效构建实时Web应用的数据库

SurrealDB&#xff1a;数据驱动&#xff0c;实时协同。用SurrealDB简化你的开发流程- 精选真开源&#xff0c;释放新价值。 概览 SurrealDB&#xff0c;一款专为现代Web应用设计的云原生数据库&#xff0c;以其创新的架构和功能&#xff0c;为开发者提供了一个强大的工具。它整…...

SQL Server查询计划阅读及分析

​​​​​​6.4.5. 查询计划阅读及分析 SQL Server中,SQL语句的查询计划可能会包含多个节点,每个节点除了包含和对应一个操作符外,还包含节点及操作符相关的其他信息,其细节与具体的操作符相关。SQL Server查询计划与Oracle执行计划中,虽然每个节点所包含内容的具体称谓…...

SAP Fiori 实战课程(二):新建页面

课程回顾 上一课中,利用Visual studio Code 新建、并运行了一个Demo工程。可以实现对项目的启动,启动后进入一个List清单。 那么本次课程的目前就是在上一节Demo的基础上,从零开始新建一个完整的页面。实现从首页清单,选择行后,鼠标点击,进入下一个页面。 准备工作 在开…...

【Rust光年纪】超越ORM:探索Rust语言多款数据库客户端库的核心功能和使用场景

数据库操作新选择&#xff1a;从异步操作到连接管理&#xff0c;掌握Rust语言数据库客户端库的全貌 前言 在现代软件开发中&#xff0c;与数据库进行交互是一个常见的任务。Rust语言作为一种高性能、内存安全的编程语言&#xff0c;拥有丰富的生态系统来支持各种数据库操作。…...

解决:事件监听器 addEventListener 被多次调用

背景&#xff1a; 给一个元素添加了事件监听&#xff0c;click 会触发 然而在实际场景中&#xff0c;点击一次&#xff0c;事件会被触发两次 阻止冒泡也没有用 解决&#xff1a; 使用API&#xff1a;event.stopImmediatePropagation() stopImmediatePropagation() 方法可防止…...

配置RIPv2的认证

目录 一、配置IP地址、默认网关、启用端口 1. 路由器R1 2. 路由器R2 3. 路由器R3 4. Server1 5. Server2 二、搭建RIPv2网络 1. R1配置RIPv2 2. R2配置RIPv2 3. Server1 ping Server2 4. Server2 ping Server1 三、模拟网络攻击&#xff0c;为R3配置RIPv2 四、在R…...

前端调试技巧:动态高亮渲染区域

效果&#xff1a; 前端界面的渲染过程、次数&#xff0c;会通过高亮变化来显示&#xff0c;通过这种效果排除一些BUG 高亮 打开方式 F12进入后点击ESC&#xff0c;进入rendering&#xff0c;选择前三个即可&#xff08;如果没有rendering&#xff0c;点击橘色部分勾选上&…...

深克隆与浅克隆的区别与实现

在软件开发中&#xff0c;克隆对象是一个常见需求。克隆的方式主要有两种&#xff1a;深克隆&#xff08;Deep Clone&#xff09;和浅克隆&#xff08;Shallow Clone&#xff09;。了解它们的区别及其实现方法&#xff0c;对于编写高效、安全的代码非常重要。 深克隆与浅克隆的…...

【学习笔记】无人机系统(UAS)的连接、识别和跟踪(六)-无人机直接C2通信

目录 引言 5.4 直接C2通信 5.4.1 概述 5.4.2 A2X直接C2通信服务的授权策略 5.4.3 USS使用A2X直接C2通信服务的C2授权程序 5.4.4 直接C2通信建立程序 引言 3GPP TS 23.256 技术规范&#xff0c;主要定义了3GPP系统对无人机&#xff08;UAV&#xff09;的连接性、身份识别…...

认识和安装R的扩展包,什么是模糊搜索安装,工作目录和空间的区别与设置

R语言以其强大的功能和灵活的扩展性,成为了无数数据分析师和研究者的首选工具。R的丰富功能和海量扩展包直接相关,但如何高效管理这些扩展包,进而充分发挥R的强大潜力?本文将为您揭示这些问题的答案。 一、R的扩展包 R的包(packages)是由R函数、数据和预编译代码组成的一…...

解决STM32开启定时器时立即进入一次中断程序问题

转自 解决STM32开启定时器时立即进入一次中断程序问题_stm32f407定时器初始化自动进入一次-CSDN博客 配置STM32定时器时&#xff0c;定时器中断使能、定时器使能、清除更新中断标志位&#xff0c;三者不同顺序程序执行时有不同效果&#xff0c;具体如下&#xff1a; TIM_Clea…...

Unity UGUI 之EventSystem

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 1.EventSystem是什么&#xff1f; 有需要请查看手册&#xff1a;Unity - 手册&#xff1…...

USB转多路UART - USB 基础

一、 前言 断断续续做了不少USB相关开发&#xff0c;但是没有系统去了解过&#xff0c;遇到问题就很被动了。做这个USB转UART的项目就是&#xff0c;于是专门花了一天的时间学习USB及CDC相关&#xff0c;到写这文章时估计也忘得差不多了&#xff0c;趁项目收尾阶段记录一下&am…...

接近50个实用编程相关学习资源网站

Date: 2024.07.17 09:45:10 author: lijianzhan 编程语言以及编程相关工具等实用性官方文档网站 C语言文档&#xff1a;https://learn.microsoft.com/zh-cn/cpp/c-languageMicrosoft C、C和汇编程序文档&#xff1a;https://learn.microsoft.com/zh-cn/cppJAVA官方文档&#…...

在数据操作中使用SELECT子句

目录 一、INSERT 语句中使用 SELECT子句 二、UPDATE 语句中使用 SELECT子句 三、DELETE 语句中使用 SELECT子句 一、INSERT 语句中使用 SELECT子句 在 INSERT 语句中使用 SELECT子句&#xff0c;可以将一个或多个表或视图中的数据添加到另外一个表中。使用 SELECT 子句还可以…...

Golang | Leetcode Golang题解之第274题H指数

题目&#xff1a; 题解&#xff1a; func hIndex(citations []int) int {// 答案最多只能到数组长度left,right:0,len(citations)var mid intfor left<right{// 1 防止死循环mid(leftright1)>>1cnt:0for _,v:range citations{if v>mid{cnt}}if cnt>mid{// 要找…...

区块链技术在智能家居中的创新应用探索

随着物联网技术的发展和智能家居市场的蓬勃发展&#xff0c;区块链技术作为一种去中心化的数据管理和安全保障技术&#xff0c;正在逐渐引入智能家居领域&#xff0c;并为其带来了新的创新应用。本文将探讨区块链技术在智能家居中的具体应用场景、优势以及未来发展方向。 智能家…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)

LeetCode 3309. 连接二进制表示可形成的最大数值&#xff08;中等&#xff09; 题目描述解题思路Java代码 题目描述 题目链接&#xff1a;LeetCode 3309. 连接二进制表示可形成的最大数值&#xff08;中等&#xff09; 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...

Python实现简单音频数据压缩与解压算法

Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中&#xff0c;压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言&#xff0c;提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...