新聞中心
Java沒有直接的輸入輸出參數(shù)機(jī)制,無法簡單地實(shí)現(xiàn)參數(shù)的輸入輸出功能,因此需要借助其它方法來實(shí)現(xiàn)。本文作者通過實(shí)踐總結(jié),分享利用方法參數(shù)、方法返回值、類字段等方法來實(shí)現(xiàn)參數(shù)的輸入輸出,并對比總結(jié)各自的優(yōu)缺點(diǎn)及使用場景。較長,可收藏后再看。

創(chuàng)新互聯(lián)于2013年成立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢想脫穎而出為使命,1280元垣曲做網(wǎng)站,已為上家服務(wù),為垣曲各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18980820575
前言
軟件開發(fā)方法學(xué)的泰斗肯特·貝克(Kent Beck)曾說過:
我不是一個(gè)偉大的程序員,我只是一個(gè)具有良好習(xí)慣的優(yōu)秀程序員。
養(yǎng)成良好的習(xí)慣,尤其是不斷重構(gòu)的習(xí)慣,是每一個(gè)優(yōu)秀程序員都應(yīng)該具備的素質(zhì)。重構(gòu)(Refactoring)就是在不改變軟件現(xiàn)有功能的基礎(chǔ)上,通過調(diào)整程序的結(jié)構(gòu)、提高程序的質(zhì)量、優(yōu)化程序的性能……使其程序的設(shè)計(jì)模式和架構(gòu)更趨合理,從而提高軟件的穩(wěn)定性、擴(kuò)展性和維護(hù)性。
一 一個(gè)需要重構(gòu)的方法
需求描述:
需要把一個(gè)線串(一組經(jīng)緯度坐標(biāo)串),按照指定分段長度數(shù)組進(jìn)行按比例劃分(由于指定線串的長度較小,可以近似地認(rèn)為在幾何平面上,無需進(jìn)行球面距離換算)。
代碼實(shí)現(xiàn):
/**
* 幾何輔助類
*/
public final class GeometryHelper {
/** 常量相關(guān) */
/** 小數(shù)位數(shù) */
private static final int DIGIT_SCALE = 8;
/** 放大比例 */
private static final double ZOOM_SCALE = 10000000000L;
/** 幾何工廠 */
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING));
/**
* 構(gòu)造方法
*/
private GeometryHelper() {
throw new UnsupportedOperationException();
}
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 檢查分段數(shù)量
if (Objects.isNull(segmentLengthes) || segmentLengthes.length < 1) {
return new LineString[] {lineString};
}
// 計(jì)算總共長度
double totalLength = Arrays.stream(segmentLengthes)
.map(segmentLength -> Math.max(segmentLength, 0.0D))
.sum();
// 計(jì)算目標(biāo)長度
double lineLength = lineString.getLength();
long[] targetLengthes = Arrays.stream(segmentLengthes)
.mapToLong(segmentLength -> getTargetLength(lineLength, totalLength, segmentLength))
.toArray();
// 初始化參數(shù)值
int index = 1;
Coordinate[] coordinates = lineString.getCoordinates();
Coordinate coordinate = coordinates[0];
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
// 添加線串坐標(biāo)
long addupLength = 0L;
ListcoordinateList = new ArrayList<>();
coordinateList.add(coordinate);
for (; index < coordinates.length; index++) {
// 計(jì)算分段長度
long segmentLength = Math.round(coordinate.distance(coordinates[index]) * ZOOM_SCALE);
// 根據(jù)長度處理
boolean isBreak = true;
int compareResult = Long.compare(addupLength + segmentLength, targetLengthes[i]);
// 根據(jù)長度處理: 未達(dá)目標(biāo)長度
if (compareResult < 0) {
addupLength += segmentLength;
coordinate = coordinates[index];
coordinateList.add(coordinate);
isBreak = false;
}
// 根據(jù)長度處理: 超過目標(biāo)長度
else if (compareResult > 0) {
long deltaLength = targetLengthes[i] - addupLength;
coordinate = buildMiddleCoordinate(coordinate, coordinates[index], segmentLength, deltaLength);
}
// 根據(jù)長度處理: 等于目標(biāo)長度
else {
index++;
coordinate = coordinates[index];
}
// 是否跳出循環(huán)
if (isBreak) {
break;
}
}
coordinateList.add(coordinate);
// 設(shè)置線串對象
lineStrings[i] = buildLineString(coordinateList);
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, index, coordinate);
// 返回線串?dāng)?shù)組
return lineStrings;
}
/**
* 構(gòu)建線串
*
* @param coordinates 坐標(biāo)數(shù)組
* @param index 當(dāng)前序號(hào)
* @param coordinate 當(dāng)前坐標(biāo)
* @return 線串
*/
private static LineString buildLineString(Coordinate[] coordinates, int index, Coordinate coordinate) {
ListcoordinateList = new ArrayList<>();
coordinateList.add(coordinate);
coordinateList.addAll(Arrays.asList(ArrayUtils.subarray(coordinates, index, coordinates.length)));
return buildLineString(coordinateList);
}
/**
* 構(gòu)建線串
*
* @param coordinateList 坐標(biāo)列表
* @return 線串
*/
private static LineString buildLineString(ListcoordinateList) {
return GEOMETRY_FACTORY.createLineString(coordinateList.toArray(new Coordinate[0]));
}
/**
* 構(gòu)建中間坐標(biāo)
*
* @param coordinate1 坐標(biāo)1
* @param coordinate2 坐標(biāo)2
* @param segmentLength 分段長度
* @param deltaLength 增量長度
* @return 中間坐標(biāo)
*/
private static Coordinate buildMiddleCoordinate(Coordinate coordinate1, Coordinate coordinate2,
long segmentLength, long deltaLength) {
double deltaScale = deltaLength * 1.0D / segmentLength;
double middleX = round(coordinate1.x + (coordinate2.x - coordinate1.x) * deltaScale, DIGIT_SCALE);
double middleY = round(coordinate1.y + (coordinate2.y - coordinate1.y) * deltaScale, DIGIT_SCALE);
return new Coordinate(middleX, middleY);
}
/**
* 獲取目標(biāo)長度
*
* @param lineLength 線路長度
* @param totalLength 總共長度
* @param segmentLength 段長度
* @return 目標(biāo)長度
*/
private static long getTargetLength(double lineLength, double totalLength, double segmentLength) {
return Math.round(Math.max(segmentLength, 0.0D) * ZOOM_SCALE * lineLength / totalLength);
}
/**
* 四舍五入
*
* @param value 雙精度浮點(diǎn)值
* @param scale 保留小數(shù)位數(shù)
* @return 四舍五入值
*/
private static double round(double value, int scale) {
return BigDecimal.valueOf(value).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}
備注說明:
在超過目標(biāo)長度時(shí),獲取了一個(gè)中間坐標(biāo),由于數(shù)據(jù)精度的關(guān)系,這個(gè)坐標(biāo)可能跟上一坐標(biāo)或下一坐標(biāo)重合。這里為了降低這塊邏輯的復(fù)雜度,沒有進(jìn)行前后坐標(biāo)的去重處理。
存在問題:
在方法splitLineString(劃分線串)中,存在一個(gè)兩層循環(huán),導(dǎo)致了方法邏輯復(fù)雜、層級較深、代碼量大。如果把外層循環(huán)體提煉為一個(gè)方法,就能夠使代碼更簡潔、更清晰、更容易維護(hù)。
二 一次失敗的重構(gòu)經(jīng)歷
理論依據(jù):
當(dāng)看到一個(gè)方法定義過長或者這段方法需要很多注釋才能讓人理解的時(shí)候,這時(shí)候就要考慮是不是把這個(gè)方法的部分代碼提取出來,形成一個(gè)新的方法,方便調(diào)用和理解,同時(shí)也減小方法的粒度。我們把這種方法叫做提煉函數(shù)(Extract Function),在Java語言中可叫做提煉方法(Extract Method)。
重構(gòu)步驟:
- 創(chuàng)建一個(gè)新方法,并根據(jù)這個(gè)方法的意圖來命名;
- 將待提煉的代碼段從原方法中拷貝到新方法中;
- 檢查提煉的代碼段,把缺少的變量添加到方法的參數(shù)中;
- 如果部分參數(shù)成對出現(xiàn),可以把這些參數(shù)合并為一個(gè)參數(shù);
- 如果方法需要有返回值,確定返回值的類型,并在合適的位置返回;
- 在原方法中,刪除被提煉的代碼段,替換為新方法的調(diào)用。
代碼實(shí)現(xiàn):
/**
* 幾何輔助類
*/
public final class GeometryHelper {
/** 原有靜態(tài)常量 */
......
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 原有計(jì)算邏輯
......
// 初始化參數(shù)值
int index = 1;
Coordinate[] coordinates = lineString.getCoordinates();
Coordinate coordinate = coordinates[0];
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
lineStrings[i] = combineLineString(coordinates, index, coordinate, targetLengthes[i]);
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, index, coordinate);
// 返回線串?dāng)?shù)組
return lineStrings;
}
/**
* 組裝線串
*
* @param coordinates 坐標(biāo)數(shù)組
* @param index 當(dāng)前序號(hào)
* @param coordinate 當(dāng)前坐標(biāo)
* @param targetLength 目標(biāo)長度
* @return 線串
*/
private static LineString combineLineString(Coordinate[] coordinates, int index, Coordinate coordinate, long targetLength) {
// 添加線串坐標(biāo)
long addupLength = 0L;
ListcoordinateList = new ArrayList<>();
coordinateList.add(coordinate);
for (; index < coordinates.length; index++) {
// 計(jì)算分段長度
long segmentLength = Math.round(coordinate.distance(coordinates[index]) * ZOOM_SCALE);
// 根據(jù)長度處理
boolean isBreak = true;
int compareResult = Long.compare(addupLength + segmentLength, targetLength);
// 根據(jù)長度處理: 未達(dá)目標(biāo)長度
if (compareResult < 0) {
addupLength += segmentLength;
coordinate = coordinates[index];
coordinateList.add(coordinate);
isBreak = false;
}
// 根據(jù)長度處理: 超過目標(biāo)長度
else if (compareResult > 0) {
long deltaLength = targetLength - addupLength;
coordinate = buildMiddleCoordinate(coordinate, coordinates[index], segmentLength, deltaLength);
}
// 根據(jù)長度處理: 等于目標(biāo)長度
else {
index++;
coordinate = coordinates[index];
}
// 是否跳出循環(huán)
if (isBreak) {
break;
}
}
coordinateList.add(coordinate);
// 返回線串對象
return buildLineString(coordinateList);
}
/** 原有靜態(tài)方法 */
......
}
存在問題:
粗看這段代碼,似乎沒有什么問題。但是,通過測試發(fā)現(xiàn),并沒有得到正確的結(jié)果。
分析原因:
在《Thinking in Java》中有這樣一段話:
When you’re passing primitives into a method,you get a distinct copy of the primitive. When you’re passing a reference into a method, you get a copy of the reference.
當(dāng)您將基本類型傳遞到方法中時(shí),您將得到該基本類型的副本。當(dāng)您將對象引用傳遞到方法中時(shí),您將得到該對象引用的副本。
原來參數(shù)index(當(dāng)前序號(hào))和coordinate(當(dāng)前坐標(biāo))在方法combineLineString(組裝線串)中的修改,只是對該方法中的參數(shù)副本進(jìn)行修改,并沒有體現(xiàn)到調(diào)用方法splitLineString(劃分線串)中,從而導(dǎo)致每次調(diào)用都在使用參數(shù)的初始化值。其實(shí),這是在提取方法的過程中,沒有考慮到參數(shù)的作用域。
檢查技巧:
這里給出一個(gè)作者屢試不爽的檢查技巧——把提取方法的所有參數(shù)添加上final關(guān)鍵字,編譯后觀察到哪個(gè)參數(shù)出現(xiàn)編譯錯(cuò)誤,就說明這個(gè)參數(shù)是一個(gè)輸入輸出參數(shù)(Inout Parameter)。
解決方案:
在Java語言中,沒有直接的輸入輸出參數(shù)機(jī)制,無法簡單地實(shí)現(xiàn)參數(shù)的輸入輸出功能。所以,需要借助其它解決方案,來實(shí)現(xiàn)參數(shù)的輸入輸出功能。在這里,作者通過實(shí)踐總結(jié),給出了以下幾種解決方案。
三 利用方法參數(shù)實(shí)現(xiàn)
本章將從方法參數(shù)入手,實(shí)現(xiàn)參數(shù)的輸入輸出功能。
3.1 利用參數(shù)類實(shí)現(xiàn)
理論依據(jù):
引入?yún)?shù)對象(Introduce Parameter Object):當(dāng)一個(gè)方法的參數(shù)超過3個(gè)時(shí),就可以考慮將參數(shù)封裝成一個(gè)對象類。將參數(shù)封裝成對象類后,提高了代碼的可讀性,并且該參數(shù)對象類也可以重用。以后,如果增加或刪除參數(shù),方法本身不需要修改,只需要修改參數(shù)對象類就可以了。
這里,可以利用引入?yún)?shù)對象重構(gòu)方法,定義一個(gè)輸入輸出參數(shù)類,來實(shí)現(xiàn)參數(shù)的輸入輸出功能。
代碼實(shí)現(xiàn):
/**
* 幾何輔助類
*/
public final class GeometryHelper {
/** 原有靜態(tài)常量 */
......
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 原有計(jì)算邏輯
......
// 初始化參數(shù)值
Coordinate[] coordinates = lineString.getCoordinates();
InoutParameter inoutParameter = new InoutParameter(1, coordinates[0]);
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
lineStrings[i] = combineLineString(coordinates, inoutParameter, targetLengthes[i]);
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, inoutParameter.getIndex(), inoutParameter.getCoordinate());
// 返回線串?dāng)?shù)組
return lineStrings;
}
/**
* 組裝線串
*
* @param coordinates 坐標(biāo)數(shù)組
* @param inoutParameter 輸入輸出參數(shù)
* @param targetLength 目標(biāo)長度
* @return 線串
*/
private static LineString combineLineString(Coordinate[] coordinates, InoutParameter inoutParameter, long targetLength) {
// 獲取輸入?yún)?shù)
int index = inoutParameter.getIndex();
Coordinate coordinate = inoutParameter.getCoordinate();
// 添加線串坐標(biāo)
......
// 設(shè)置輸出參數(shù)
inoutParameter.setIndex(index);
inoutParameter.setCoordinate(coordinate);
// 返回線串對象
return buildLineString(coordinateList);
}
/** 原有靜態(tài)方法 */
......
/**
* 輸入輸出參數(shù)類
*/
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
private static class InoutParameter {
/** 當(dāng)前序號(hào) */
private int index;
/** 當(dāng)前坐標(biāo) */
private Coordinate coordinate;
}
}
3.2 利用單值數(shù)組實(shí)現(xiàn)
理論依據(jù):
當(dāng)您將對象引用傳遞到方法中時(shí),您將得到該對象引用的副本。也就是說,當(dāng)您將數(shù)組引用傳遞到方法中時(shí),您將得到該數(shù)組引用的副本。但是,這兩個(gè)數(shù)組引用都指向同一個(gè)數(shù)組,當(dāng)修改副本數(shù)組引用中的值時(shí),也能體現(xiàn)到原有數(shù)組引用中。
利用數(shù)組引用的這個(gè)特性,可以實(shí)現(xiàn)參數(shù)的輸入輸出功能。這里,引入了單值數(shù)組的概念,即一個(gè)數(shù)組只有一個(gè)值,用于傳遞輸入輸出參數(shù)值。
代碼實(shí)現(xiàn):
/**
* 幾何輔助類
*/
public final class GeometryHelper {
/** 原有靜態(tài)常量 */
......
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 原有計(jì)算邏輯
......
// 初始化參數(shù)值
int[] indexHolder = new int[] {1};
Coordinate[] coordinates = lineString.getCoordinates();
Coordinate[] coordinateHolder = new Coordinate[] {coordinates[0]};
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
lineStrings[i] = combineLineString(coordinates, indexHolder, coordinateHolder, targetLengthes[i]);
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, indexHolder[0], coordinateHolder[0]);
// 返回線串?dāng)?shù)組
return lineStrings;
}
/**
* 組裝線串
*
* @param coordinates 坐標(biāo)數(shù)組
* @param indexHolder 序號(hào)支撐
* @param coordinateHolder 坐標(biāo)支撐
* @param targetLength 目標(biāo)長度
* @return 線串
*/
private static LineString combineLineString(Coordinate[] coordinates, int[] indexHolder, Coordinate[] coordinateHolder, long targetLength) {
// 獲取支撐取值
int index = indexHolder[0];
Coordinate coordinate = coordinateHolder[0];
// 添加線串坐標(biāo)
......
// 設(shè)置支撐取值
indexHolder[0] = index;
coordinateHolder[0] = coordinate;
// 返回線串對象
return buildLineString(coordinateList);
}
/** 原有靜態(tài)方法 */
......
}
3.3 利用元組類實(shí)現(xiàn)
理論依據(jù):
元組(Tuple):Java中的元組(Tuple)是一種數(shù)據(jù)結(jié)構(gòu),可以存放多個(gè)元素,并且每個(gè)元素的數(shù)據(jù)類型可以不同。Tuple與List類似,但是不同的是,List只能存儲(chǔ)一種數(shù)據(jù)類型,而Tuple可存儲(chǔ)多種數(shù)據(jù)類型。
可能你會(huì)質(zhì)疑,Object類型的List實(shí)際也是可以存儲(chǔ)多種類型的啊?但是,在創(chuàng)建List時(shí),需要指定元素?cái)?shù)據(jù)類型,只能指定為Object類型;在獲取的元素時(shí),只能獲取到Object類型的值,需要強(qiáng)制轉(zhuǎn)化為對應(yīng)的數(shù)據(jù)類型。而Tuple在創(chuàng)建時(shí),可以直接指定多個(gè)元素?cái)?shù)據(jù)類型;在獲取元素時(shí),無需進(jìn)行數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)化。
常用的元組工具包有:
- Apache的commons-lang3提供的元組類:
- Pair
- Triple
- JavaTuples提供的元組類:
??
??
隨著元組的元數(shù)不斷地增加,代碼的閱讀性也逐漸地下降。當(dāng)元組的元數(shù)超過3個(gè)時(shí),不如直接創(chuàng)建對象類,并給予合適類名和字段名,便于代碼的理解和維護(hù)。所以,不建議使用JavaTuples中的元組類,而推薦使用Apache的commons-lang3提供的元組類。
代碼實(shí)現(xiàn):
/**
* 幾何輔助類
*/
public final class GeometryHelper {
/** 原有靜態(tài)常量 */
......
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 原有計(jì)算邏輯
......
// 初始化參數(shù)值
Coordinate[] coordinates = lineString.getCoordinates();
MutablePairmutablePair = MutablePair.of(1, coordinates[0]);
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
lineStrings[i] = combineLineString(coordinates, mutablePair, targetLengthes[i]);
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, mutablePair.getLeft(), mutablePair.getRight());
// 返回線串?dāng)?shù)組
return lineStrings;
}
/**
* 組裝線串
*
* @param coordinates 坐標(biāo)數(shù)組
* @param mutablePair 當(dāng)前配對
* @param targetLength 目標(biāo)長度
* @return 線串
*/
private static LineString combineLineString(Coordinate[] coordinates, MutablePairmutablePair,
long targetLength) {
// 獲取配對取值
int index = mutablePair.getLeft();
Coordinate coordinate = mutablePair.getRight();
// 添加線串坐標(biāo)
......
// 設(shè)置配對取值
mutablePair.setLeft(index);
mutablePair.setRight(coordinate);
// 返回線串對象
return buildLineString(coordinateList);
}
/** 原有靜態(tài)方法 */
......
}
3.4 利用支撐類實(shí)現(xiàn)
理論依據(jù):
在上一節(jié)里,把所有輸入輸出參數(shù)放入到一個(gè)元組里,每一個(gè)輸入輸出參數(shù)沒有一個(gè)具體的命名,造成了代碼的理解和維護(hù)困難。如果每一個(gè)輸入輸出參數(shù)都定義一個(gè)元組,可以讓代碼維護(hù)者輕松地知道每一個(gè)參數(shù)的具體含義。所以,這里定義了自己的一元元組類——ObjectHolder(對象支撐類,也可以使用javatuples的Unit類),用于傳遞輸入輸出參數(shù)值。
代碼實(shí)現(xiàn):
/**
* 幾何輔助類
*/
public final class GeometryHelper {
/** 原有靜態(tài)常量 */
......
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 原有計(jì)算邏輯
......
// 初始化參數(shù)值
Coordinate[] coordinates = lineString.getCoordinates();
ObjectHolderindexHolder = new ObjectHolder<>(1);
ObjectHoldercoordinateHolder = new ObjectHolder<>(coordinates[0]);
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
lineStrings[i] = combineLineString(coordinates, indexHolder, coordinateHolder, targetLengthes[i]);
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, indexHolder.getValue(), coordinateHolder.getValue());
// 返回線串?dāng)?shù)組
return lineStrings;
}
/**
* 組裝線串
*
* @param coordinates 坐標(biāo)數(shù)組
* @param indexHolder 序號(hào)支撐
* @param coordinateHolder 坐標(biāo)支撐
* @param targetLength 目標(biāo)長度
* @return 線串
*/
private static LineString combineLineString(Coordinate[] coordinates, ObjectHolderindexHolder, ObjectHolder coordinateHolder, long targetLength) {
// 獲取支撐取值
int index = indexHolder.getValue();
Coordinate coordinate = coordinateHolder.getValue();
// 添加線串坐標(biāo)
......
// 設(shè)置支撐取值
indexHolder.setValue(index);
coordinateHolder.setValue(coordinate);
// 返回線串對象
return buildLineString(coordinateList);
}
/** 原有靜態(tài)方法 */
......
}
/**
* 對象支撐類
*/
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class ObjectHolder{
/** 對象取值 */
private T value;
}
3.5 利用其它方法實(shí)現(xiàn)
除此之外,還可以利用其它參數(shù)方法實(shí)現(xiàn)參數(shù)的輸入輸出功能:
利用數(shù)組實(shí)現(xiàn)
首先,在調(diào)用函數(shù)中,定義一個(gè)對象數(shù)組,把所有輸入輸出參數(shù)存入對象數(shù)組中;其次,在被調(diào)用函數(shù)中,把這些參數(shù)從對象數(shù)組中取出來使用;再次,在被調(diào)用函數(shù)中,再把這些參數(shù)值存入對象數(shù)組中;最后,在調(diào)用函數(shù)中,把這些參數(shù)值從對象數(shù)組中取出來使用。
利用對象數(shù)組的問題是——代碼可讀性太差,而且在參數(shù)的存入和取出過程中,需要進(jìn)行數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)化。如果所有輸入輸出參數(shù)的類型一致,可以直接定義該類型的數(shù)組,從而避免了數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)化。
利用Map實(shí)現(xiàn)
首先,在調(diào)用函數(shù)中,定義一個(gè)對象Map,把所有輸入輸出參數(shù)存入對象Map中;其次,在被調(diào)用函數(shù)中,把這些參數(shù)從對象Map中取出來使用;再次,在被調(diào)用函數(shù)中,再把這些參數(shù)值存入對象Map中;最后,在調(diào)用函數(shù)中,把這些參數(shù)值從對象Map中取出來使用。
利用對象Map實(shí)現(xiàn),代碼的可讀性比利用對象數(shù)組實(shí)現(xiàn)更強(qiáng),但是也存在同樣的問題——在參數(shù)的存入和取出過程中,需要進(jìn)行數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)化。如果所有輸入輸出參數(shù)的類型一致,可以直接定義該類型的Map,從而避免了數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)化。但是,利用對象Map實(shí)現(xiàn),還不如定義一個(gè)參數(shù)類更實(shí)用。
利用原子類實(shí)現(xiàn)
JDK中,提供了一套原子類——AtomicInteger、AtomicLong、AtomicDouble等,可用于對應(yīng)的基礎(chǔ)類型和包裝類型,實(shí)現(xiàn)對應(yīng)參數(shù)的輸入輸出功能。實(shí)現(xiàn)方法跟ObjectHolder一樣,這里不再累述。
四 利用方法返回值實(shí)現(xiàn)
本章將從方法返回值入手,實(shí)現(xiàn)參數(shù)的輸入輸出功能。
4.1 利用結(jié)果類實(shí)現(xiàn)
理論依據(jù):
引入返回值對象(Introduce Return Object):當(dāng)一個(gè)方法的需要返回多個(gè)值時(shí),就可以考慮將返回值封裝成一個(gè)對象類。將返回值封裝成對象類后,提高了代碼的可讀性,并且該返回值對象類也可以重用。以后,如果增加或刪除返回值,方法本身不需要修改,只需要修改返回值對象類就可以了。
這里,可以利用引入返回值對象重構(gòu)方法,定義一個(gè)返回值對象類,來實(shí)現(xiàn)參數(shù)的輸入輸出功能。
代碼實(shí)現(xiàn):
/**
* 幾何輔助類
*/
public final class GeometryHelper {
/** 原有靜態(tài)常量 */
......
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 原有計(jì)算邏輯
......
// 初始化參數(shù)值
int index = 1;
Coordinate[] coordinates = lineString.getCoordinates();
Coordinate coordinate = coordinates[0];
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
ReturnResult result = combineLineString(coordinates, index, coordinate, targetLengthes[i]);
index = result.getIndex();
coordinate = result.getCoordinate();
lineStrings[i] = result.getLineString();
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, index, coordinate);
// 返回線串?dāng)?shù)組
return lineStrings;
}
/**
* 組裝線串
*
* @param coordinates 坐標(biāo)數(shù)組
* @param index 當(dāng)前序號(hào)
* @param coordinate 當(dāng)前坐標(biāo)
* @param targetLength 目標(biāo)長度
* @return 返回值
*/
private static ReturnResult combineLineString(Coordinate[] coordinates, int index, Coordinate coordinate, long targetLength) {
// 添加線串坐標(biāo)
......
// 返回輸出結(jié)果
return new ReturnResult(index, coordinate, buildLineString(coordinateList));
}
/** 原有靜態(tài)方法 */
......
/**
* 返回值類
*/
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
private static class ReturnResult {
/** 當(dāng)前序號(hào) */
private int index;
/** 當(dāng)前坐標(biāo) */
private Coordinate coordinate;
/** 線串對象 */
private LineString lineString;
}
}
4.2 利用元組類實(shí)現(xiàn)
理論依據(jù):
參考3.3章節(jié)的元組(Tuple)的定義和特性。元組(Tuple)可以用于方法的參數(shù)值,也可以用于方法的返回值。當(dāng)一個(gè)方法需要返回多個(gè)值時(shí),又不愿意定義自己的結(jié)果類時(shí),可以采用元組(Tuple)實(shí)現(xiàn)多個(gè)值的返回。
代碼實(shí)現(xiàn):
/**
* 幾何輔助類
*/
public final class GeometryHelper {
/** 原有靜態(tài)常量 */
......
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 原有計(jì)算邏輯
......
// 初始化參數(shù)值
int index = 1;
Coordinate[] coordinates = lineString.getCoordinates();
Coordinate coordinate = coordinates[0];
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
Tripletriple = combineLineString(coordinates, index, coordinate, targetLengthes[i]);
index = triple.getLeft();
coordinate = triple.getMiddle();
lineStrings[i] = triple.getRight();
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, index, coordinate);
// 返回線串?dāng)?shù)組
return lineStrings;
}
/**
* 組裝線串
*
* @param coordinates 坐標(biāo)數(shù)組
* @param index 當(dāng)前序號(hào)
* @param coordinate 當(dāng)前坐標(biāo)
* @param targetLength 目標(biāo)長度
* @return 返回值
*/
private static TriplecombineLineString(Coordinate[] coordinates, int index, Coordinate coordinate, long targetLength) {
// 添加線串坐標(biāo)
......
// 返回輸出結(jié)果
return ImmutableTriple.of(index, coordinate, buildLineString(coordinateList));
}
/** 原有靜態(tài)方法 */
......
}
4.3 利用其它方法實(shí)現(xiàn)
除此之外,還可以利用其它返回值方法實(shí)現(xiàn)參數(shù)的輸入輸出功能:
利用數(shù)組實(shí)現(xiàn)
首先,在被調(diào)用方法中,定義一個(gè)對象數(shù)組,把多個(gè)返回值放入到對象數(shù)組中;最后,在調(diào)用函數(shù)中,把這些參數(shù)值從對象數(shù)組中取出來,并強(qiáng)制轉(zhuǎn)化為對應(yīng)的數(shù)據(jù)類型。
利用對象數(shù)組的問題是——代碼可讀性太差,而且在返回值的存入和取出過程中,需要進(jìn)行數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)化。如果所有返回值的數(shù)據(jù)類型一致,可以直接定義該類型的數(shù)組,從而避免了數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)化。
利用Map實(shí)現(xiàn)
首先,在被調(diào)用方法中,定義一個(gè)對象Map,把多個(gè)返回值放入到對象Map中;最后,在調(diào)用函數(shù)中,把這些參數(shù)值從對象Map中取出來,并強(qiáng)制轉(zhuǎn)化為對應(yīng)的數(shù)據(jù)類型。
利用對象Map實(shí)現(xiàn),代碼的可讀性比利用對象數(shù)組實(shí)現(xiàn)更強(qiáng),但是也存在同樣的問題——在返回值的存入和取出過程中,需要進(jìn)行數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)化。如果所有返回值的類型一致,可以直接定義該類型的Map,從而避免了數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)化。但是,利用對象Map實(shí)現(xiàn),還不如定義一個(gè)返回值類更實(shí)用。
五 利用類字段實(shí)現(xiàn)
本章將從類字段入手,實(shí)現(xiàn)參數(shù)的輸入輸出功能。
5.1 利用線程本地變量實(shí)現(xiàn)
理論依據(jù):
線程本地變量(ThreadLocal):線程本地變量不同于它們的普通變量,因?yàn)樵L問某個(gè)變量的每個(gè)線程都有自己的局部變量,且獨(dú)立于變量的初始化副本。線程本地變量實(shí)例通常是類中的私有靜態(tài)字段,它希望將變量狀態(tài)與某一個(gè)線程關(guān)聯(lián)起來。
要用類字段解決參數(shù)的輸入輸出問題,就必須考慮方法的線程安全性。這里,利用線程本地變量(ThreadLocal)來實(shí)現(xiàn)線程中輸入輸出參數(shù)值共享。
代碼實(shí)現(xiàn):
/**
* 幾何輔助類
*/
public final class GeometryHelper {
/** 屬性相關(guān) */
/** 當(dāng)前序號(hào)支撐 */
private static final ThreadLocalINDEX_HOLDER = new ThreadLocal<>();
/** 當(dāng)前坐標(biāo)支撐 */
private static final ThreadLocalCOORDINATE_HOLDER = new ThreadLocal<>();
/** 原有靜態(tài)常量 */
......
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 原有計(jì)算邏輯
......
// 初始化參數(shù)值
INDEX_HOLDER.set(1);
Coordinate[] coordinates = lineString.getCoordinates();
COORDINATE_HOLDER.set(coordinates[0]);
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
lineStrings[i] = combineLineString(coordinates, targetLengthes[i]);
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, INDEX_HOLDER.get(), COORDINATE_HOLDER.get());
// 清除支撐類
INDEX_HOLDER.remove();
COORDINATE_HOLDER.remove();
// 返回線串?dāng)?shù)組
return lineStrings;
}
/**
* 組裝線串
*
* @param coordinates 坐標(biāo)數(shù)組
* @param targetLength 目標(biāo)長度
* @return 線串
*/
private static LineString combineLineString(Coordinate[] coordinates, long targetLength) {
// 獲取支撐取值
int index = INDEX_HOLDER.get();
Coordinate coordinate = COORDINATE_HOLDER.get();
// 添加線串坐標(biāo)
......
// 設(shè)置支持取值
INDEX_HOLDER.set(index);
COORDINATE_HOLDER.set(coordinate);
// 返回線串對象
return buildLineString(coordinateList);
}
/** 原有靜態(tài)方法 */
......
}
5.2 利用類成員變量實(shí)現(xiàn)
理論依據(jù):
在上一章節(jié)中,利用線程本地變量(ThreadLocal)來實(shí)現(xiàn)線程中輸入輸出參數(shù)值共享,讓方法的封裝更復(fù)雜——需要從線程本地變量(ThreadLocal)讀取和存儲(chǔ)輸入輸出參數(shù)值。有沒有一種更簡單的方法,直接利用類成員變量實(shí)現(xiàn)輸入輸出參數(shù)值的共享呢?
答案是肯定的,可以把方法的封裝和變量的定義封裝到一個(gè)類中。這樣,在每一個(gè)類實(shí)例中,都可以利用類成員變量來實(shí)現(xiàn)輸入輸出參數(shù)值的共享。但是,這個(gè)類是線程非安全的,必須在單線程中使用。
代碼實(shí)現(xiàn):
/**
* 幾何輔助類
*/
public final class GeometryHelper {
// 原有構(gòu)造方法
......
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
SplitLineStringAlgorithm algorithm = new SplitLineStringAlgorithm();
return algorithm.splitLineString(lineString, segmentLengthes);
}
}
/**
* 劃分線串算法類
*/
public class SplitLineStringAlgorithm {
/** 屬性相關(guān) */
/** 當(dāng)前序號(hào) */
private int index;
/** 當(dāng)前坐標(biāo) */
private Coordinate coordinate;
/** 原有靜態(tài)常量 */
......
/**
* 劃分線串
*
* @param lineString 原始線串
* @param segmentLengthes 分段長度數(shù)組
* @return 線串?dāng)?shù)組
*/
public LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 原有計(jì)算邏輯
......
// 初始化參數(shù)值
index = 1;
Coordinate[] coordinates = lineString.getCoordinates();
coordinate = coordinates[0];
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
lineStrings[i] = combineLineString(coordinates, targetLengthes[i]);
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, index, coordinate);
// 返回線串?dāng)?shù)組
return lineStrings;
}
/**
* 組裝線串
*
* @param coordinates 坐標(biāo)數(shù)組
* @param targetLength 目標(biāo)長度
* @return 線串
*/
private LineString combineLineString(Coordinate[] coordinates, long targetLength) {
// 添加線串坐標(biāo)
......
// 返回線串對象
return buildLineString(coordinateList);
}
/** 原有靜態(tài)方法 */
......
}
六 各種方法綜合點(diǎn)評
下面,針對以上各種實(shí)現(xiàn)方法進(jìn)行一個(gè)綜合點(diǎn)評:
??
??
總結(jié)如下:
各種實(shí)現(xiàn)方法有利有弊,應(yīng)當(dāng)根據(jù)具體的使用場景,來選擇最適合的實(shí)現(xiàn)方法。
根據(jù)參數(shù)和返回值的類型選擇實(shí)現(xiàn)方法:輸入輸出參數(shù)盡量使用方法參數(shù)實(shí)現(xiàn),返回值盡量使用返回值實(shí)現(xiàn)。
根據(jù)參數(shù)和返回值的數(shù)量選擇實(shí)現(xiàn)方法:數(shù)量少的盡量使用支撐類和元組類,數(shù)量多的盡量使用自定義類。
不建議使用一些取巧的實(shí)現(xiàn)方法,比如:3.2.利用單值數(shù)組實(shí)現(xiàn)、5.1.利用線程本地變量實(shí)現(xiàn)。
不推薦使用對象數(shù)組和對象Map,Java是強(qiáng)類型定義語言,不建議使用強(qiáng)制數(shù)據(jù)類型轉(zhuǎn)化。
最適合本文中案例的實(shí)現(xiàn)方法是——3.4.利用支撐類實(shí)現(xiàn)。
后記
《莊子·養(yǎng)生主》有言:
吾生也有涯,而知也無涯。以有涯隨無涯,殆已!
意思是:我的生命是有限的,但知識(shí)卻是無限的。用有限的生命去追求無限的知識(shí),必然會(huì)失敗的。
所以,知識(shí)并不是越多越好,而是“學(xué)而精之,精而深之,深而新之 ”。
【本文為專欄作者“阿里巴巴官方技術(shù)”原創(chuàng)稿件,轉(zhuǎn)載請聯(lián)系原作者】
??戳這里,看該作者更多好文??
標(biāo)題名稱:Java編程技巧:如何實(shí)現(xiàn)參數(shù)的輸入輸出?
文章轉(zhuǎn)載:http://fisionsoft.com.cn/article/dhjsodh.html


咨詢
建站咨詢
