【一起讀原始碼】1. Java 中元組 Tuple
1.1 問題描述
使用 Java 做資料分析、機器學習的時候,常常需要對批量的資料進行處理,如果需要處理的資料的維度不超過10時,可以考慮使用 org.javatuples 提供的 Tuple 類工具。支援 1 - 10 的多維資料處理,支援資料泛型。除了常見的 Integer / String / Long / Double 還可以是自定義的資料型別。
1.2 總體分析
如圖所示,總共 13 個類,13個介面。

先看介面非常簡單,附帶自己的泛型 X,然後提供一個自己的方法 X getValue0() … X getValue9() … getValue … getKey … getLabel.
所有的類的父類為抽象類 Tuple,注意該方法並沒有包含任何泛型,而是實現了常見的三個介面:
- Iterable
- Serializable
- Comparable
接下來要出場的子類就根據實際情況實現多個介面,返回對應的值。具體內容如下表所示:
| 類名 | 引數數目 |
|---|---|
| Unit | 1 |
| Pair | 2 |
| KeyValue | 2 |
| LabelValue | 2 |
| Triplet | 3 |
| Quartet | 4 |
| Quintet | 5 |
| Sextet | 6 |
| Septet | 7 |
| Octet | 8 |
| Ennead | 9 |
| Decade | 10 |
1.3 父類 Tuple
Tuple 是一個抽象類,具有兩個成員變數,如下:
private final Object[] valueArray;
private final List<Object> valueList;
私有變數外部不能直接訪問,這兩個變數的主要作用應該是用於 Iterable 遍歷。
接著看 Tuple 對這個介面的實現:
public final Iterator<Object> iterator() {
return this.valueList.iterator();
}
比較容易理解,把 Tuple 的 iterator 轉移給它內部變數完成。
同樣地看一下 contains 方法,這個同樣容易理解:如果待查變數為 null 則直接返回 false,否則逐個遍歷看看是不是相等。
public final boolean contains(final Object value) {
for (final Object val : this.valueList) {
if (val == null) {
if (value == null) {
return true;
}
} else {
if (val.equals(value)) {
return true;
}
}
}
return false;
}
接下來看一下其中的 indexOf 方法,也就是找到資料對應的索引:
public final int indexOf(final Object value) {
int i = 0;
for (final Object val : this.valueList) {
if (val == null) {
if (value == null) {
return i;
}
} else {
if (val.equals(value)) {
return i;
}
}
i++;
}
return -1;
}
這個我認為不合理的地方就是最後的返回 -1 ,應該定義一個常量,NOT_FOUNT = -1 更加合適。其他的沒有什麼可以解釋的。
類似地看一下 lastIndexOf ,這個只是倒過來遍歷一次。
到目前為止還不知道成員變數 valueArray 的作用,看下原始碼發現,主要是用於Tuple轉換為 Array 和 getValue 時使用(遍歷連結串列時間複雜度為 O ( N ) O(N) O(N) 而此時根據索引讀資料時間複雜度為 O ( 1 ) O(1) O(1))。
public final Object[] toArray() {
return this.valueArray.clone();
}
public final Object getValue(final int pos) {
if (pos >= getSize()) {
throw new IllegalArgumentException(
"Cannot retrieve position " + pos + " in " + this.getClass().getSimpleName() +
". Positions for this class start with 0 and end with " + (getSize() - 1));
}
return this.valueArray[pos];
}
1.4 子類以 Pair 為例
建構函式只提供了一個 ,即全引數建構函式
public Pair(
final A value0,
final B value1) {
super(value0, value1);
this.val0 = value0;
this.val1 = value1;
}
內部變數 final A value1 和 final B value2 是 final 型別的,因此如果希望更新它們的值不能直接 set 而是需要重新 new Pair,當然在 Pair 類中已經提供了。
public <X> Pair<X,B> setAt0(final X value) {
return new Pair<X,B>(
value, this.val1);
}
public <X> Pair<A,X> setAt1(final X value) {
return new Pair<A,X>(
this.val0, value);
}
而其他的方法比較有意思的是從 Pair 到 Triplet 等等更高維度的,都有提供這些方法。方法同樣是 return new Triplet … 這類的。
另外還有經常使用的 with 方法建立Pair物件,比如 Pair.with(value1, value2);
對應的原始碼實現如下:
public static <A,B> Pair<A,B> with(final A value0, final B value1) {
return new Pair<A,B>(value0,value1);
}
這些都是非常容易理解的內容,也可以藉此學習一下 java 的泛型。
1.5 面向物件思維
所有這些類繼承於一個父類 Tuple,該父類為抽象類,實現 Iterable 與 Comparable 介面
public abstract class Tuple implements Iterable<Object>, Serializable, Comparable<Tuple> {
// 兩個基本的資料結構
private final Object[] valueArray;
private final List<Object> valueList;
... 包括迭代器、遍歷方法等
}
接下來的12個類均繼承於 Tuple 類,並根據引數多少對相應的方法做了修改。
public final class Unit<A> extends Tuple implements IValue0<A> {
}
而 該介面也比較簡單:
public interface IValue0<X> {
public X getValue0();
}
類似地,其他介面只是更改了方法的名字,以便於不同的類的使用。比如 Quartet 類有四個物件,則需要實現四個介面,分別實現 getValue0、getValue1、getValue2、getValue3 四個介面。
非常簡單,但是也非常好用。
1.6 靈活應用
對於一個具有 m 個屬性, 共 n 行的資料集,現在需要使用 List 和 Tuple 組合儲存。
- 按行儲存:儲存後結果是 n 行,每一行是 一個 Tuple 物件,每個 Tuple 物件共包含 m 個屬性。也就是說,先打包成行,再儲存為一個 List。
- 按列儲存:儲存後結果是 m 列,然後把每一列對應的 List 物件儲存為 Tuple。也就是說,先基於列進行打包,然後每一列對應一個 List 物件,然後使用 Tuple 進行合併。
這個應該要根據實際情況而定,一般情況下第二種情況可能更加多一些,基於屬性進行打包,方便每次讀取單個屬性時返回一個 list。
1.7 總結
作為讀原始碼的開端,特別選了一個簡單的、也挺好用的。希望能夠幫到大家~~
Smileyan
2022.7.28 19:56
