Java中的图元
图元它们是不可改变的、有序的元素序列。在这方面,它们类似于不可变的列表--然而,在软件工程中通常使用图元来表示对。值得注意的是,它们并不局限于成对,可以是 n-length.Java对不可变列表(和其他集合)有很好的支持,但对成对却没有很好的支持。
对子既可以是两个元素之间的key-value 映射,也可以是两个元素的对子,从一个方法中返回。
当你想从一个方法中返回多个值时,对子就变得非常有用。比如说。
mean, std = getStats()
从技术上讲,我们可以返回元素之间的key-value 映射,或者在Java中用Map 或List 实现的2个元素的序列,但是在图元和对的背景下,这些都是很难操作的。
Map<Float, Float> meanStdMap = getStatsMap();
List<Float> meanStdList = getStatsList();
无论是Map ,还是List ,简单地说,都不是为了这个目的。你可以通过让getStats_() 方法返回不可修改的集合来强制执行不可更改性,以获得图元的效果。
public static Map<Float, Float> getStatsMap() {
return Collections.unmodifiableMap(new HashMap<Float, Float>());
}
public static List<Float> getStatsList() {
return Collections.unmodifiableList(new ArrayList<>());
}
但这最终让人感觉是对语言中固有的缺失功能的一种变通!不幸的是,从写作的角度来看,所有的解决方案都是对一个固有的缺失特性的变通,尽管有些解决方案比其他的更笨拙。
在本指南中,我们将看看如何在Java中创建和使用Tuples--一个没有内置的功能。我们将探讨核心包和类,如
Pair和AbstractMap.SimpleImmutableEntry,第三方库编码一个简单的自定义类。
javafx.util.Pair
**注意:**从JDK 11开始,JavaFX并不随默认的JDK下载一起提供,而是成为一个独立的包。它必须作为一个依赖项单独下载/导入。这使得作为Tuple的Pair 的使用更加麻烦。
Tuple 类缺失的核心解决方案是Pair 。它驻留在javafx.util 包中,被添加来表示name-value 对,这在桌面和移动软件开发中很常见。尽管它最初是为了用于JavaFX应用程序 - 该类在其他领域具有高度的通用性
它简单得不能再简单了。
import javafx.util.Pair;
// public class Pair<K,V> implements Serializable {...}
Pair<String, Integer> pair = new Pair<>("Mean Value", 25);
K (键)和V (值)都是通用的,所以你可以给它们分配任何类型。它们都可以通过各自的getters来访问。
System.out.printf("Key: %s, Value: %s%n", pair.getKey(), pair.getValue());
// Key: Mean Value, Value: 25
System.out.println(pair);
// Mean Value=25
Java本来就不能从一个方法中返回两个值,但可以返回一个Pair ,它是这两个值的一个封装器。
public static Pair<String, Integer> getStats() {
return new Pair<>("Mean Value", 25);
}
Pair 是不可变的,就像Tuple一样,所以没有setter函数。
AbstractMap.SimpleImmutableEntry
另一个你可以使用的核心类是AbstractMap.SimpleImmutableEntry 类,尽管,这比前一个更像是一个变通的方法,而且一般不会被广泛使用。它是为了帮助创建自定义的地图实现,但在紧要关头,可以作为一个元组。
使用SimpleImmutableEntry 而不是Pair 的主要好处是,它在所有当前版本的JDK中都有提供,所以你不必下载外部依赖或降低你的JDK版本。
**注意:**虽然确实存在一个SimpleEntry ,但它是可变的,所以我们将使用不可变的版本。
我们可以将之前的方法改写为。
public static AbstractMap.SimpleImmutableEntry<String, Integer> getStats() {
return new AbstractMap.SimpleImmutableEntry<>("Mean Value", 25);
}
然后可以获得并解析为。
AbstractMap.SimpleImmutableEntry<String, Integer> stats = getStats();
System.out.printf("Key: %s, Value: %s%n", stats.getKey(), stats.getValue());
// Key: Mean Value, Value: 25
System.out.println(stats);
// Mean Value=25
Apache Commons
Apache Commons是一个广泛使用的库,存在于许多Java项目中,主要用于帮助/方便的方法和扩展Java官方能力的类。这些类中的一个是Pair ,来自lang3 包。
使用Maven,你可以用以下方式添加依赖关系。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${version}</version>
</dependency>
或者,如果你使用的是Gradle。
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
值得注意的是,Apache的Pair 类是基于Map.Entry<K, V>!它实现了该接口,并以左右元素的形式提供了一个类似地图的条目。
public abstract class Pair<L, R> implements Map.Entry<L, R> ... Serializable {...}
一个Pair 是通过其of() 方法创建的。
public static Pair<String, Integer> getStats() {
return Pair.of("Mean Value", 25);
}
访问这些元素可以通过getLeft() 和getRight() getters实现,这意味着它不是一个key-value 映射,而是两个元素的一对类似元组。
Pair<String, Integer> stats = getStats();
System.out.printf("Key: %s, Value: %s%n", stats.getLeft(), stats.getRight());
// Left: Mean Value, Right: 25
System.out.println(stats);
// (Mean Value,25)
如果你使用过Python等语言,这段代码返回的输出结果与你可能习惯的输出结果更相似。
**注意:**尽管Apache Common的实现看起来不像是元素之间的key-value 映射,但它确实有一个有趣的setValue() 方法,它最终设置了Pair的R (右边的元素),就好像它是一个value ,对应于一个key 。
还值得注意的是,这个Pair 默认是一个ImmutablePair ,尽管setValue() 方法是公开存在的--它导致了一个UnsupportedOperationException 。
Pair<String, Integer> stats = getStats();
System.out.println(stats);
stats.setValue(15);
System.out.println(stats);
(Mean Value,25)
Exception in thread "main" java.lang.UnsupportedOperationException
at org.apache.commons.lang3.tuple.ImmutablePair.setValue(ImmutablePair.java:202)
at Main.main(Main.java:31)
然而,如果你使用一个MutablePair ,操作会成功。
Javatuples
Javatuples是一个较早的库,它在2011年进行了最新的更新。它不再被维护,但作为一个轻量级的库,它确实工作得相当好,可以让你解决Java中缺乏图元的问题。
它可以作为依赖项通过Maven导入。
<dependency>
<groupId>org.javatuples</groupId>
<artifactId>javatuples</artifactId>
<version>1.2</version>
</dependency>
或者Gradle。
implementation 'org.javatuples:javatuples:1.2'
**注意:**通过Maven导入依赖关系时可能会遇到问题。在这种情况下,请手动下载JAR文件。
该库提供了比成对图元更多的东西!它提供长度为1到10的图元--Unit,Pair,Triplet, ...Decade 。所有这些类都是类型安全的、不可变的、可序列化的和可迭代的,所以你在所有方面都得到了保障。
不出所料,它们的工作方式与我们使用其他Tuple变体的方式基本相同。
public static Pair<String, Integer> getStats() {
return Pair.with("Mean Value", 25);
}
Tuple 中的值最终被存储在List 中,通过各种封装方法,你可以像在Tuple 中一样单独访问它们。
Pair<String, Integer> stats = getStats();
System.out.printf("Element_1: %s, Element_2: %s%n", stats.getValue(0), stats.getValue(1));
// Element_1: Mean Value, Element_2: 25
System.out.println(stats);
// ["Mean Value", 25]
自定义类
最后,你可以选择实现你自己的类来表示图元。第三方库可能不适合你,或者你根本不想麻烦地下载任何库。
Pair类工作得相当好,而AbstractMap.SimpleImmutableEntry使用起来很奇怪,因为它并不打算作为一个类似于对的元组使用。
值得庆幸的是,实现这样的东西并不难,你可以用一个简单的封装类,也可以用更复杂的。最简单的解决方案是创建。
public class Tuple {
private Object element1;
private Object element2;
}
然而,这个解决方案的可塑性并不强。如果你确定知道返回类型,或者你要创建一个专门的对子作为持有者对象--这种方法会有效。然而,如果要在Java中创建一个更通用的类似于对的Tuple ,我们要做的是沿着这样的思路。
public class Tuple<E1, E2> {
private final E1 e1;
private final E2 e2;
public Tuple(E1 e1, E2 e2){
this.e1 = e1;
this.e2 = e2;
}
public E1 getE1() {
return e1;
}
public E2 getE2() {
return e2;
}
public String toString() {
return String.format("(%s, %s)", e1, e2);
}
}
两个通用的最终元素(不可变),有一个构造函数,接受任何类型的两个元素,并为它们提供获取器。然后这个类可以被用作。
public static Tuple<String, Integer> getStats() {
return new Tuple("Mean Value", 25);
}
而我们可以从元组中提取数据,如。
Tuple<String, Integer> stats = getStats();
System.out.printf("E1: %s, E2: %s%n", stats.getE1(), stats.getE2());
// E1: Mean Value, E2: 25
System.out.println(stats);
// (Mean Value, 25)
结论
元组是不可变的、有序的元素序列。它们通常被用来表示对--两个元素的图元。Java对不可变的列表(和其他集合)有很好的支持,但对成对的支持却不多。
在本指南中,我们已经看了什么是元组,以及成对是元组的一种特殊类型。我们看了一下Java中可以用来表示一对数据点的核心包,并探索了提供相同基本功能的第三方库。
最后,我们实现了我们自己的通用Tuple 类,它可以用来表示任何两种类型的对。