Java编程的利器:Pair和Triple无缝解决多值返回问题,编写高效代码

在实际编码中,经常会遇到一个方法需要返回多个值的情况,你编写一个方法,需要同时返回某个操作的结果和一些相关的附加信息。使用传统的方式,你可能需要创建一个包含这些信息的自定义类或者使用集合(如 Map)来存储这些值。然而,这往往使得代码变得臃肿,而且对于调用方来说,理解和提取这些值可能会显得有些繁琐。

这时使用org.apache.commons.lang3.tuple下的PairTriple 及其子类是一种非常便捷的解决方案。这些类提供了一种清晰、简单的方式来组织和传递多个相关联的值,使得代码更加直观和易于理解。

使用 PairTriple 就能轻松解决这个问题。你可以在一个方法中返回一个 PairTriple 对象,其中包含你想要传递的多个值。这样,你可以清晰地表示这些值之间的关系,而且调用方可以轻松地访问和使用这些值,而无需繁琐的解包过程。

在接下来的部分,我们将深入研究如何在这类场景中使用 PairTriple 及其子类,以及它们如何简化我们在编码中常遇到的多值返回问题。

引入依赖:

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类是一个抽象类,它有两个子类ImmutablePairMutablePair。接下来我们介绍一下这两个子类,也是我们要使用的两个类。

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

ImmutablePairPair的一个不可变的子类。它在创建完成之后,不允许改变键和值。它是线程安全的。

我们可以看一下它如何进行构造的:

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();  
}

类中的 leftright 成员变量被声明为 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

image.png

Pair 类及其子类 ImmutablePairMutablePair 是用于表示键值对的实用工具类。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 提供了 setLeftsetMiddlesetRight 方法,使得在对象创建后可以修改左、中、右元素的值。

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,表示不可继承,确保不可变性。确保不会有子类添加或修改行为。然后类中的属性 leftmiddleright 被声明为 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、中间件、架构设计、面试题、程序员攻略等

相关推荐
陈平安Java and C4 小时前
MyBatisPlus
java
秋野酱4 小时前
如何在 Spring Boot 中实现自定义属性
java·数据库·spring boot
安的列斯凯奇5 小时前
SpringBoot篇 单元测试 理论篇
spring boot·后端·单元测试
Bunny02125 小时前
SpringMVC笔记
java·redis·笔记
架构文摘JGWZ5 小时前
FastJson很快,有什么用?
后端·学习
BinaryBardC5 小时前
Swift语言的网络编程
开发语言·后端·golang
feng_blog66885 小时前
【docker-1】快速入门docker
java·docker·eureka
邓熙榆6 小时前
Haskell语言的正则表达式
开发语言·后端·golang
枫叶落雨2227 小时前
04JavaWeb——Maven-SpringBootWeb入门
java·maven
m0_748232397 小时前
SpringMVC新版本踩坑[已解决]
java