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. 消费操作:处理不同类型的消费者,使得消费者更加通用和灵活。

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

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

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

相关文章

一线大厂java面试题

String 为什么要设计成不可变的 String被设计成不可变的有以下几个原因: 线程安全:由于String是不可变的&#xff0c;多个线程可以同时访问同一个String对象而无需担心数据被修改。这使得String在多线程环境下是线程安全1. 的。 2.缓存Hash值:由于String是不可变的&#xf…

接口性能优化思路

前言 日常开发中设计接口&#xff0c;响应时间是衡量一个接口质量的重要指标。 接口响应时间这里粗糙地分为三种&#xff1a; 即时响应&#xff1a;毫秒级&#xff0c;小于500毫秒快速响应&#xff1a;秒级&#xff0c;大于500毫秒且小于2秒长时间操作&#xff1a;大于2秒&a…

【Python其他检查字符串占字节数的方法】

在Python中&#xff0c;检查字符串在特定编码下占用的字节数&#xff0c;最标准且常用的方法是通过字符串的.encode()方法将字符串转换为字节串&#xff0c;然后使用len()函数来获取这个字节串的长度。这是因为字符串&#xff08;在Python 3中&#xff09;是以Unicode形式存储的…

力扣题解(一和零)

474. 一和零 给你一个二进制字符串数组 strs 和两个整数 m 和 n 。 请你找出并返回 strs 的最大子集的长度&#xff0c;该子集中 最多 有 m 个 0 和 n 个 1 。 如果 x 的所有元素也是 y 的元素&#xff0c;集合 x 是集合 y 的 子集 。 思路&#xff1a; 本题可以看成有两个限…

51单片机嵌入式开发:15、STC89C52RC操作蜂鸣器实现一个music音乐播放器的音乐盒

STC89C52RC操作蜂鸣器实现一个music音乐播放器的音乐盒 1 概述2 蜂鸣器操作方法3 蜂鸣器发出音声4 硬件电路5 软件实现6 整体工程&#xff1a;7 总结 1 概述 要实现一个基于STC89C52RC单片机的音乐盒&#xff0c;可以按照以下步骤进行&#xff1a; &#xff08;1&#xff09;硬…

STM32项目分享:智能宠物喂食系统

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 1.PCB图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; https://www.bilibili.com/video/BV1zy411z7…

关于对CSDN的谴责

关于对CSDN的谴责 如果不是心血来潮登了一次旧帐号我是万万不会想到&#xff0c;所有的文章都被设置成了仅VIP可见。 CSDN你的VIP有多不受人待见您不知道吗&#xff1f;为什么要把我用于你开通VIP刷绩效的工具&#xff1f; 这种东西不应该首先经过同意再开启吗&#xff1f;默认…

JavaWeb day01-HTML入门

Web前端 课程安排 HTML、CSS简介 HTML快速入门 实现标题排版 新闻标题样式

深度学习程序环境配置

深度学习环境配置 因为之前轻薄本没有显卡跑不起来&#xff0c;所以换了台电脑重新跑程序&#xff0c;故记录一下配置环境的步骤及常见错误 本人数学系&#xff0c;计算机部分知识比较匮乏&#xff0c;计算机专业同学可以略过部分内容 深度学习环境配置 深度学习环境配置 CUD…

MATLAB算法实战应用案例精讲-【数模应用】多元方差分析MANOVA(附MATLAB、python和R语言代码实现)

目录 知识储备 方差分析 一、单因素方差分析 二、双因素方差分析 三、多因素方差分析 四、事后多重比较 五、重复测量方差 六、协方差分析 七、多元方差分析 算法原理 多元方差分析的特点 多元方差分析的使用条件 应用案例 代码实现 MATLAB python R语言 知…

StringBuilder, Stringbuffer,StringJoiner

StringBuilder StringBuilder 代表可变字符串对象&#xff0c;相当于是一个容器&#xff0c;里面装的字符串是可以改变的&#xff0c;就是用来操作字符串的。 StringBuilder 比String更适合做字符串的修改操作&#xff0c;效率更高&#xff0c;代码更加的简洁。 public clas…

在结束的地方重新开始:十指之梢与新的轮回

一、前述 如果你想感受人潮汹涌&#xff0c;那么就请到大城市繁华街区的十字路口去看一看&#xff0c;尤其是节假日。 所以&#xff0c;交警对于城市交通的通畅&#xff0c;人们出行顺利的保障&#xff0c;是异常重要的。 交警&#xff0c;指挥交通有很多工具和方法&#xff0…

vscode 文件颜色变绿色

解决&#xff1a;关闭git功能 在设置中搜索Git:Enabled&#xff0c;取消Decorations: Enabled的勾选

速盾:cdn能防御ddos吗?

CDN&#xff08;内容分发网络&#xff09;是一种广泛应用于互联网中的技术&#xff0c;它通过将内容分发到全球各地的服务器上&#xff0c;以提高用户在访问网站时的加载速度和稳定性。然而&#xff0c;CDN是否能够有效防御DDoS&#xff08;分布式拒绝服务&#xff09;攻击是一…

UE5 C++在Cesium上给定一个坐标垂直地面射线正交的地形高度

一.方法一&#xff1a;需要一个Actor里&#xff0c;向下垂直射线。自身高度-射线距离。 #include "CesiumGlobeAnchorComponent.h" #include "CesiumOriginShiftComponent.h" #include "GeoTransforms.h" #include "CesiumGeoreference.h&…

OMOST 作画能力的硬核解析[C#]

1. 简介 1.1 什么是 OMOST&#xff1f; OMOST 是一款利用深度学习和生成对抗网络&#xff08;GAN&#xff09;技术的 AI 作画工具&#xff0c;能够自动生成高质量的绘画作品。它广泛应用于艺术创作、广告设计和教育培训等领域。 1.2 OMOST 的核心功能 自动生成绘画风格转换…

【Python】探索 Python 中的 callable 方法

为何每次早餐 仍然魂离魄散 原来 那朝分手都要啜泣中上班 明明能够过得这关 赢回旁人盛赞 原来 顽强自爱这样难 难得的激情总枉费 残忍的好人都美丽 别怕 你将无人会代替 &#x1f3b5; 陈慧娴《情意结》 在 Python 编程中&#xff0c;理解对象的可调用性…

04-用户画像+sqoop使用

优点 sqoop的作用是实现数据的导入和导出&#xff0c;主要是对数据库和数据仓库之间的操作 只要是支持jdbc连接的数据库都可以使用sqoop操作 添加Sqoop到环境变量中 export SQOOP_HOME/export/server/sqoop export PATH$PATH:$SQOOP_HOME/bin:$SQOOP_HOME/sbinsource /etc/…

内联汇编清楚变量指定位

static inline void clear_bit(int nr, volatile unsigned long *addr) {__asm__ __volatile__("lock ; ""btrl %1,%0": "m" (*addr): "Ir" (nr)); } 这段代码是一个内联函数&#xff0c;用于在特定地址的位图中清除一个位。它使用了…

Bootstrap 表单

Bootstrap 表单 Bootstrap 是一个流行的前端框架&#xff0c;用于快速开发响应式和移动设备优先的网页。它包含了一套丰富的组件&#xff0c;其中包括表单元素&#xff0c;这些元素可以帮助开发者创建风格统一、易于使用的表单界面。本文将详细介绍 Bootstrap 表单的基础知识&…