元组:javatuples
1. 问题
有时你会有类似如下的需求:
- 逻辑上,方法需要返回两个甚至更多的值( 通常它们的类型并不一致,因此无法使用数组 )。形如:
return "tom", 18; // 有些语言支持这种语法,但是 Java 并没有这个语法特性。
- 有时,你需要向方法传入类似于多个学生的姓名和年龄:
demo("tom", 19, "jerry", 18, ...) // 当然这里可以使用不定参/可变参语法,但是可读性并不是很好,不直观。
对于上述的情况,你可以采用这样的解决办法:
- 将多个数据封装到一个 Map 中,或者定义一个类( 例如 Student ),将数据封装到对象中:
map.put("name", "tom");
map.put("age", 18);
return map;
// 或
return new Student("name", 18);
- 对于第二个问题,可以使用数组
names[0] = "tom";
ages[0] = 19;
names[1] = "jerry";
ages[1] = 18;
demo(names, ages);
不过,在使用上述方案实现相关需求后,你可能会有如下想法:
-
使用 Map 或自定义类有点「杀鸡用牛刀」的感觉;
-
使用两个数组的时候,将一个人的两个信息分开存放,感觉又有点怪怪的。
2. tuples
数据结构领域中有一个较少提及的数据结构:「元组」(tuple) 。有些语言中,天生就有 tuple 类型的变量,但是 Java 中没有(其实,常见的编程语言中,大多数都没有)。
tuple 结构可以和数组做对比:
-
相同点在于:tuple 和数组一样,作为容器,其中可以存放多个值。并且,它和数组一样有下标索引的概念。
-
不同点在于:tuple 不强求其中的各个数据的类型必须一致。
当然,我们可以自己实现 tuple 数据结果(相较于 List、Set 它其实简单很多)。不过,很显然有现成的:javatuples
<dependency>
<groupId>org.javatuples</groupId>
<artifactId>javatuples</artifactId>
<version>1.2</version>
</dependency>
由于 tuple 数据结构的功能/作用实在是比较简单(有时可能项目中就有程序员自己随手就实现了它),所以这个库,在 2011 年就「彻底」完成了所有功能,不再升级更新了。因此,最后一个版本就是 1.2 。
2.1 The tuple classes
该工具库提供了以下不同容量的 tuple 类:
| 类 | 容量 |
|---|---|
Unit<A> |
1 element |
Pair<A,B> |
2 elements |
Triplet<A,B,C> |
3 elements |
Quartet<A,B,C,D> |
4 elements |
Quintet<A,B,C,D,E> |
5 elements |
Sextet<A,B,C,D,E,F> |
6 elements |
Septet<A,B,C,D,E,F,G> |
7 elements |
Octet<A,B,C,D,E,F,G,H> |
8 elements |
Ennead<A,B,C,D,E,F,G,H,I> |
9 elements |
Decade<A,B,C,D,E,F,G,H,I,J> |
10 elements |
[!cite] 提示
我也是很服气作者能为每一种容量的 tuple 类都单独起了个名字!
由于上述 tuple 类并没有什么语义,所以,作者额外地为两个常见的情况提供了单独的 tuple 类。
-
KeyValue<A, B>
-
LabelValue<A, B>
实际上,它们俩就是 Pair 的别名。
2.2 Creating tuples
所有类型的 tuple 都可以通过 new 来创建:
Pair<Integer, Integer> pair = new Pair<>(10, 20);
Triplet<String, Integer, Date> triplet = new Triplet<>("hello", 10, new Date());
...
于此同时,tuple 还提供了静态方法 .with() 来创建各种 ruple 类的实例:
Pair<Integer, Integer> pair = Pair.with(10, 20);
Triplet<String, Integer, Date> triplet = Triplet.with("hello", 10, new Date());
2.3 Getting/Setting values
tuple 数据结构在概念上是下标索引的,但是你不能想当然地对其使用下标运算符 [] 。
从一个 tuple 容器中取值,有 2 种方式:
通过 .getValueN() 方法
System.out.println( pair.getValue0() );
System.out.println( pair.getValue1() );
System.out.println( triplet.getValue0() );
System.out.println( triplet.getValue1() );
System.out.println( triplet.getValue2() );
通过 .getValue(index) 方法
System.out.println( pair.getValue(0) );
System.out.println( pair.getValue(1) );
System.out.println( triplet.getValue(0) );
System.out.println( triplet.getValue(1) );
System.out.println( triplet.getValue(2) );
不过 .getValue(index) 方法取出来的值统一都是 Object 类型,后续使用时需要做类型转换。
优先考虑使用 .getValueN() 方法 。
另外,大家都能猜到,既然有 get 方法,这里也自然有 set 方法:
pair.setAt0(xxx);
pair.setAt1(xxx);
triplet.setAt0(xxx);
triplet.setAt1(xxx);
triplet.setAt2(xxx);
"KeyValue" 和 "LabelValue" 这 2 种「额外」的 tuple 类型中,它们的 getting/setting 方法是叫:getKey() / getValue() 和 getLabel() / getValue() 。
2.4 Adding or removing elements
当你向一个 Pair 对象中添加元素时,你将获得一个 Triplet 对象;当你从一个 Triplet 对象中移除一个元素时,你将获得一个 Pair 对象。
也就是说,任何一种 tuple 类的容量是不可改变的。
Pair<Integer, Integer> pair = Pair.with(10, 20);
Triplet<Integer, Integer, Integer> triplet = pair.add(30);
System.out.println( triplet ); // 10, 20, 30
调用 .add() 方法时,添加的元素将被添加到末尾。
另外,tuple 还提供 .addAtN() 方法。将要添加的元素添加到指定位置,而原位置( 及后续内容 )依次后移。
triplet = pair.addAt1(30);
System.out.println( triplet ); // 10, 30, 20
从 tuple 中移除元素使用 .removeFromN() 方法。
pair = triplet.removeFrom0();
2.5 Converting to/from collections or arrays
任何一种 tuple 都可以转换成 List 或数组:
Object[] array = triplet.toArray();
List<Object> list = triplet.toList();
反向的操作有:
String[] array = ...
...
Quartet<String,String,String,String> quartet = Quartet.fromArray(array);
这里需要注意的是,由于 Array 和 List 是要求其中元素类型是一致的。所以,从 tuple 转成数组和 List 时,会失去元素的具体类型,从而得到一个 Object 的数组和 List 。如果这样处理,那么就失去了 tuple 的使用价值。
同样,对于一个一致的某种类型的数组和 List,转换成 tuple 时,其元素类型必然也都是一样的,这样也就没有必要去使用 tuple 了,为什么不直接使用这个数组和 List 呢。
2.6 Iterating
由于所有类型的 tuple 都是"可循环"对象,所以可以直接用便捷 for 循环对其进行遍历:
for (Object value : triplet) {
...
}
不过,这里失去了每个元素的具体类型,只能将它们统一当作 Object 来看待。
Checking contents
tuple 提供了方法用来判断 tuple 中是否包含某个/某些对象:
if (quartet.contains(value)) {
...
}
if (quartet.containsAll(valueCollection)) {
...
}
浙公网安备 33010602011771号