在实际编码中,经常会遇到一个方法需要返回多个值的情况,你编写一个方法,需要同时返回某个操作的结果和一些相关的附加信息。使用传统的方式,你可能需要创建一个包含这些信息的自定义类或者使用集合(如 Map
)来存储这些值。然而,这往往使得代码变得臃肿,而且对于调用方来说,理解和提取这些值可能会显得有些繁琐。
这时使用org.apache.commons.lang3.tuple
下的Pair
或 Triple
及其子类是一种非常便捷的解决方案。这些类提供了一种清晰、简单的方式来组织和传递多个相关联的值,使得代码更加直观和易于理解。
使用 Pair
或 Triple
就能轻松解决这个问题。你可以在一个方法中返回一个 Pair
或 Triple
对象,其中包含你想要传递的多个值。这样,你可以清晰地表示这些值之间的关系,而且调用方可以轻松地访问和使用这些值,而无需繁琐的解包过程。
在接下来的部分,我们将深入研究如何在这类场景中使用 Pair
和 Triple
及其子类,以及它们如何简化我们在编码中常遇到的多值返回问题。
引入依赖:
xml
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
Pair 类介绍
Pair
类是org.apache.commons.lang3
库提供的一个简单的键值对容器,用于表示两个相关联的值。其主要作用是将两个值组织在一起,提供一种便捷的方式进行传递和处理。
首先我们来看一下Pair
的源码:
typescript
/**
* 抽象类,表示简单的键值对。
* 实现了 Map.Entry 接口,支持在各种集合中使用。
* 实现了 Comparable 接口,用于比较两个 Pair 对象的大小。
* 可序列化,支持对象的序列化和反序列化。
*
* @param <L> 左值的类型
* @param <R> 右值的类型
*/
public abstract class Pair<L, R> implements Map.Entry<L, R>, Comparable<Pair<L, R>>, Serializable {
private static final long serialVersionUID = 4954918890077093841L;
// 空数组,用于表示空的 Pair 对象数组
public static final Pair<?, ?>[] EMPTY_ARRAY = new PairAdapter[0];
/**
* 返回一个空的 Pair 数组。
*
* @return 空的 Pair 数组
*/
public static <L, R> Pair<L, R>[] emptyArray() {
return (Pair[]) EMPTY_ARRAY;
}
/**
* 静态工厂方法,创建一个新的 ImmutablePair 对象,表示给定的左右值的键值对。
*
* @param left 左值
* @param right 右值
* @param <L> 左值的类型
* @param <R> 右值的类型
* @return ImmutablePair 对象
*/
public static <L, R> Pair<L, R> of(L left, R right) {
return ImmutablePair.of(left, right);
}
/**
* 静态工厂方法,创建一个新的 ImmutablePair 对象,表示给定 Map.Entry 对象的键值对。
*
* @param pair Map.Entry 对象
* @param <L> 左值的类型
* @param <R> 右值的类型
* @return ImmutablePair 对象
*/
public static <L, R> Pair<L, R> of(Map.Entry<L, R> pair) {
return ImmutablePair.of(pair);
}
/**
* 返回左值,实现了 Map.Entry 接口。
*
* @return 左值
*/
public final L getKey() {
return this.getLeft();
}
/**
* 抽象方法,由子类实现,用于获取左值。
*
* @return 左值
*/
public abstract L getLeft();
/**
* 抽象方法,由子类实现,用于获取右值。
*
* @return 右值
*/
public abstract R getRight();
/**
* 返回右值,实现了 Map.Entry 接口。
*
* @return 右值
*/
public R getValue() {
return this.getRight();
}
}
Pair
类是一个抽象类,它有两个子类ImmutablePair
和MutablePair
。接下来我们介绍一下这两个子类,也是我们要使用的两个类。
MutablePair
MutablePair
是一个可变的。它允许在创建后动态修改键和值,提供了更大的灵活性。但是它是线程不安全的。
我们可以根据它提供的几个静态方法或者它的构造器去构造一个MutablePair
:
scss
// 静态工厂方法,返回一个空的 MutablePair
public static <L, R> MutablePair<L, R>[] emptyArray();
// 静态工厂方法,传入给定的左右值的键值对,创建并返回一个MutablePair 对象
public static <L, R> MutablePair<L, R> of(L left, R right);
// 静态工厂方法,传入给定 Map.Entry 对象的键值对,创建并返回一个新的MutablePair 对象
public static <L, R> MutablePair<L, R> of(Map.Entry<L, R> pair);
// 无参构造器
public MutablePair()
// 指定左右值的键值对的构造器
public MutablePair(L left, R right)
我们可以根据它的几个方法修改键和值:
arduino
// 修改左值
public void setLeft(L left);
// 修改右值
public void setRight(R right);
// 修改新的右值,并返回之前的右值。
public R setValue(R value);
我们可以根据它的几个方法获取键和值:
csharp
// 获取左值
public L getLeft();
// Pair中的方法 获取左值
public final L getKey();
// 获取右值
public R getRight();
// Pair中的方法 获取右值
public R getValue();
当然我们看到它的
left
以及right
都是public
的。所以我们也可以直接取值,不用使用它的get
方法。
ImmutablePair
ImmutablePair
是Pair
的一个不可变的子类。它在创建完成之后,不允许改变键和值。它是线程安全的。
我们可以看一下它如何进行构造的:
csharp
// 静态工厂方法,返回一个空的 ImmutablePair 数组。
public static <L, R> ImmutablePair<L, R>[] emptyArray();
// 静态工厂方法,返回一个包含 null 左值和 null 右值的 ImmutablePair 对象,表示空值。
public static <L, R> ImmutablePair<L, R> nullPair();
// 静态工厂方法,返回一个包含指定左值和 null 右值的 ImmutablePair 对象。
public static <L, R> Pair<L, R> left(L left);
// 静态工厂方法,返回一个包含 null 左值和指定右值的 ImmutablePair 对象。
public static <L, R> Pair<L, R> right(R right);
// 静态工厂方法,创建并返回一个新的 ImmutablePair 对象,表示给定的左右值的键值对。
public static <L, R> ImmutablePair<L, R> of(L left, R right);
// 静态工厂方法,创建并返回一个新的 ImmutablePair 对象,表示给定 Map.Entry 对象的键值对。
public static <L, R> ImmutablePair<L, R> of(Map.Entry<L, R> pair);
// 有参构造器 传入给定的左右值的键值对。
public ImmutablePair(L left, R right);
我们可以根据它的几个方法获取键和值:
csharp
// 获取左值
public L getLeft();
// Pair中的方法 获取左值
public final L getKey();
// 获取右值
public R getRight();
// Pair中的方法 获取右值
public R getValue();
当然我们看到它的
left
以及right
都是public
的。所以我们也可以直接取值,不用使用它的get
方法。
那我们再看一下为什么ImmutablePair
是不可变的,并且是线程安全的。
首先我们看一下它的左值以及右值都是final
的,不可更改的。并且调用它的setValue
会抛出UnsupportedOperationException
。
arduino
public final L left;
public final R right;
public R setValue(R value) {
throw new UnsupportedOperationException();
}
类中的 left
和 right
成员变量被声明为 final
,这意味着它们在对象创建后不能被修改,确保了线程安全性。ImmutablePair
被设计为不可变的键值对类,即一旦创建,其内容不可更改。这确保了在多线程环境中,不会有并发修改的问题。
使用示例
typescript
/**
* 返回MutablePair
* @param userDO
* @return
*/
private static MutablePair<String, Integer> handleUserInfo1(UserDO userDO){
return MutablePair.of(userDO.getUserId(), userDO.getAge());
}
/**
* 返回ImmutablePair
* @param userDO
* @return
*/
private static ImmutablePair<String, Integer> handleUserInfo2(UserDO userDO){
return ImmutablePair.of(userDO.getUserId(), userDO.getAge());
}
public static void main(String[] args) {
UserDO userDO = new UserDO();
userDO.setUserId("coderacademy");
userDO.setAge(35);
MutablePair<String, Integer> mutablePair = handleUserInfo1(userDO);
System.out.println(mutablePair.getLeft()+" MutablePair修改前:"+ mutablePair.right);
mutablePair.setRight(40);
System.out.println(mutablePair.getLeft()+" MutablePair修改后:"+ mutablePair.right);
ImmutablePair<String, Integer> immutablePair = handleUserInfo2(userDO);
System.out.println(mutablePair.getLeft()+" ImmutablePair修改前:"+ mutablePair.right);
immutablePair.setValue(50);
System.out.println(mutablePair.getLeft()+" ImmutablePair修改后:"+ mutablePair.right);
}
执行结果,我们发现ImmutablePair
在修改value时报错:
image.png
Pair
类及其子类 ImmutablePair
和 MutablePair
是用于表示键值对的实用工具类。ImmutablePair
是不可变的、线程安全的,适用于安全共享;MutablePair
允许动态修改值,但不具备线程安全性,适用于单线程环境。它们在方法返回多个值时提供了简便的解决方案,提高了代码的灵活性。
Triple介绍
Triple
是一个用于表示三元组的抽象类。三元组是由三个元素组成的有序集合,其中每个元素都有特定的位置,分别称为左值(Left)、中间值(Middle)和右值(Right)。Triple
类提供了一种便捷的方式来组织和处理这种具有固定顺序的数据。可以在不创建专门类的情况下轻松返回三个值。通过 Triple
,开发者可以更方便地处理包含三个元素的数据,减少了创建和维护多个变量的复杂性,使代码更加简洁。
我们来看一下Triple
的源码:
csharp
/**
* 表示包含三个元素的三元组的抽象类 Triple。
*
* 该类是一个抽象实现,定义了基本的 API,将元素分别称为 'left'、'middle' 和 'right'。
*
* 子类的实现可以是可变的或不可变的。对存储的对象类型没有限制。
* Triple 对象的可变性取决于其中存储的对象是否是可变的。如果存储的是可变对象,那么 Triple 本身也就变得可变,因为存储的对象状态可以被修改。
* 如果存储的是不可变对象,那么Triple 对象在创建后就保持不可变。
*
*/
public abstract class Triple<L, M, R> implements Comparable<Triple<L, M, R>>, Serializable {
/**
* 一个空数组。
*/
public static final Triple<?, ?, ?>[] EMPTY_ARRAY = new TripleAdapter[0];
/**
* 返回可分配而无需编译器警告的空数组单例。
*
*/
@SuppressWarnings("unchecked")
public static <L, M, R> Triple<L, M, R>[] emptyArray() {
return (Triple<L, M, R>[]) EMPTY_ARRAY;
}
/**
* 获取由三个对象组成的不可变三元组,推断出泛型类型。
*
* 此工厂方法允许使用推断类型来创建三元组以获取泛型类型。
*
* @param left 左元素,可以为 null
* @param middle 中间元素,可以为 null
* @param right 右元素,可以为 null
* @return 由三个参数形成的三元组,非 null
*/
public static <L, M, R> Triple<L, M, R> of(final L left, final M middle, final R right) {
return new ImmutableTriple<>(left, middle, right);
}
/**
* 获取此三元组的左元素。
*
* @return 左元素,可以为 null
*/
public abstract L getLeft();
/**
* 获取此三元组的中间元素。
*
* @return 中间元素,可以为 null
*/
public abstract M getMiddle();
/**
* 获取此三元组的右元素。
*
* @return 右元素,可以为 null
*/
public abstract R getRight();
}
Triple
是一个抽象类,它有两个子类:可变MutableTriple
以及不可变 ImmutableTriple
。
MutableTriple
MutableTriple
是可变的,原因在于它提供了公共的设置(set)方法,允许在创建后修改其内部值。具体来说,MutableTriple
提供了 setLeft
、setMiddle
和 setRight
方法,使得在对象创建后可以修改左、中、右元素的值。
arduino
/**
* 表示由三个 {@code Object} 元素组成的可变三元组。
*
* 非线程安全
*
*/
public class MutableTriple<L, M, R> extends Triple<L, M, R> {
/**
* 通过推断泛型类型获取三个对象的可变三元组。
*
* 该工厂允许通过推断泛型类型创建三元组。
*
*/
public static <L, M, R> MutableTriple<L, M, R> of(final L left, final M middle, final R right) {
return new MutableTriple<>(left, middle, right);
}
/** 左对象 */
public L left;
/** 中间对象 */
public M middle;
/** 右对象 */
public R right;
/**
* 创建一个新的三元组实例,包含三个 null 值。
*/
public MutableTriple() {
}
/**
* 创建一个新的三元组实例。
*
* @param left 左值,可以为 null
* @param middle 中间值,可以为 null
* @param right 右值,可以为 null
*/
public MutableTriple(final L left, final M middle, final R right) {
this.left = left;
this.middle = middle;
this.right = right;
}
/**
* 设置三元组的左元素。
*/
public void setLeft(final L left) {
this.left = left;
}
/**
* 设置三元组的中间元素。
*/
public void setMiddle(final M middle) {
this.middle = middle;
}
/**
* 设置三元组的右元素。
*/
public void setRight(final R right) {
this.right = right;
}
}
MutableTriple
被明确标记为非线程安全。
ImmutableTriple
ImmutableTriple
是一个不可变的三元组类,由三个泛型元素(left、middle、right)组成。不可变意味着一旦创建,其状态无法修改。该类被设计为线程安全的,但需要注意,如果存储在三元组中的对象是可变的,那么三元组本身实际上就不再是不可变的。
csharp
/**
* 一个由三个元素组成的不可变三元组。
*
* ImmutableTriple 是一个最终类,被设计成不可变的,即在实例化后其状态不可更改。
* 如果存储在三元组中的三个对象都是线程安全的,则该类是线程安全的。类的最终性防止了子类化,确保不会添加不希望的行为。
*
* 线程安全的 如果三个对象都是线程安全的
*
*/
public final class ImmutableTriple<L, M, R> extends Triple<L, M, R> {
/**
* 返回可以在不触发编译器警告的情况下分配的空数组单例。
* @return 可以在不触发编译器警告的情况下分配的空数组单例。
*/
@SuppressWarnings("unchecked")
public static <L, M, R> ImmutableTriple<L, M, R>[] emptyArray() {
return (ImmutableTriple<L, M, R>[]) EMPTY_ARRAY;
}
/**
* 返回一个由 null 组成的不可变三元组。
*
* @return 一个由 null 组成的不可变三元组。
*/
public static <L, M, R> ImmutableTriple<L, M, R> nullTriple() {
return NULL;
}
/**
* 通过推断泛型类型获得由三个对象组成的不可变三元组。
*
* 此工厂允许使用推断创建三元组以获得泛型类型。
*
* @return 由三个参数形成的不可变三元组,不为 null
*/
public static <L, M, R> ImmutableTriple<L, M, R> of(final L left, final M middle, final R right) {
return new ImmutableTriple<>(left, middle, right);
}
/** 左对象 */
public final L left;
/** 中间对象 */
public final M middle;
/** 右对象 */
public final R right;
/**
* 构造方法 创建一个新的三元组实例。
*
*/
public ImmutableTriple(final L left, final M middle, final R right) {
this.left = left;
this.middle = middle;
this.right = right;
}
}
ImmutableTriple
被声明为 final
,表示不可继承,确保不可变性。确保不会有子类添加或修改行为。然后类中的属性 left
、middle
、right
被声明为 final
,表示它们在实例化后无法被修改。类中没有提供修改元素的公共方法。ImmutableTriple
主张不可变性,不提供修改实例状态的方法。当然如果存储在三元组中的对象是可变的,则整个三元组就变得可变。这是因为虽然 ImmutableTriple
本身是不可变的,但如果存储的对象是可变的,它们的状态可能会发生变化。
类声明中使用 #ThreadSafe#
标记,表示在存储的三个对象都是线程安全的情况下,ImmutableTriple
是线程安全的。
示例
typescript
/**
* 返回可变Truple
* @param userDO
* @return
*/
private static MutableTriple<String, Integer, UserDO> handleUserInfo1(UserDO userDO){
return MutableTriple.of(userDO.getUserId(), userDO.getSex(), userDO);
}
/**
* 返回不可变Triple
* @param userDO
* @return
*/
private static ImmutableTriple<String, Integer, UserDO> handleUserInfo2(UserDO userDO){
return ImmutableTriple.of(userDO.getUserId(), userDO.getSex(), userDO);
}
public static void main(String[] args) {
UserDO userDO = new UserDO();
userDO.setUserId("coderacademy");
userDO.setUserName("码农Academy");
userDO.setSex(1);
MutableTriple<String, Integer, UserDO> mutableTriple = handleUserInfo1(userDO);
System.out.println("mutableTriple改变前的值:" + mutableTriple);
mutableTriple.setMiddle(2);
System.out.println("mutableTriple改变后的值:" + mutableTriple);
ImmutableTriple<String, Integer, UserDO> immutableTriple = handleUserInfo2(userDO);
System.out.println("ImmutableTriple改变前的值:" + immutableTriple);
UserDO userFromTriple = immutableTriple.right;
userFromTriple.setSex(2);
System.out.println("ImmutableTriple改Right键值对象的值:" + immutableTriple);
// 因ImmutableTriple 不可变,无法通过set方法修改键值。
}
总结
使用 Pair 和 Triple 类可以简化代码、提高可读性,使关联数据更清晰,保持类型安全,增强代码清晰度,提高扩展性,并提供丰富的功能,从而使开发人员更高效地处理相关数据,编写更简洁可读的代码,提升代码质量和开发效率。
本文已收录于我的个人博客:码农Academy的技术博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等