Java编程技巧:如何实现参数的输入输出?
前言
軟件開發方法學的泰斗肯特·貝克(Kent Beck)曾說過:
我不是一個偉大的程序員,我只是一個具有良好習慣的優秀程序員。
養成良好的習慣,尤其是不斷重構的習慣,是每一個優秀程序員都應該具備的素質。重構(Refactoring)就是在不改變軟件現有功能的基礎上,通過調整程序的結構、提高程序的質量、優化程序的性能……使其程序的設計模式和架構更趨合理,從而提高軟件的穩定性、擴展性和維護性。
一 一個需要重構的方法
需求描述:
需要把一個線串(一組經緯度坐標串),按照指定分段長度數組進行按比例劃分(由于指定線串的長度較小,可以近似地認為在幾何平面上,無需進行球面距離換算)。
代碼實現:
/*** 幾何輔助類*/ public final class GeometryHelper {/** 常量相關 *//** 小數位數 */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));/*** 構造方法*/private GeometryHelper() {throw new UnsupportedOperationException();}/*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {// 檢查分段數量if (Objects.isNull(segmentLengthes) || segmentLengthes.length < 1) {return new LineString[] {lineString};}// 計算總共長度double totalLength = Arrays.stream(segmentLengthes).map(segmentLength -> Math.max(segmentLength, 0.0D)).sum();// 計算目標長度double lineLength = lineString.getLength();long[] targetLengthes = Arrays.stream(segmentLengthes).mapToLong(segmentLength -> getTargetLength(lineLength, totalLength, segmentLength)).toArray();// 初始化參數值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++) {// 添加線串坐標long addupLength = 0L;List<Coordinate> coordinateList = new ArrayList<>();coordinateList.add(coordinate);for (; index < coordinates.length; index++) {// 計算分段長度long segmentLength = Math.round(coordinate.distance(coordinates[index]) * ZOOM_SCALE);// 根據長度處理boolean isBreak = true;int compareResult = Long.compare(addupLength + segmentLength, targetLengthes[i]);// 根據長度處理: 未達目標長度if (compareResult < 0) {addupLength += segmentLength;coordinate = coordinates[index];coordinateList.add(coordinate);isBreak = false;}// 根據長度處理: 超過目標長度else if (compareResult > 0) {long deltaLength = targetLengthes[i] - addupLength;coordinate = buildMiddleCoordinate(coordinate, coordinates[index], segmentLength, deltaLength);}// 根據長度處理: 等于目標長度else {index++;coordinate = coordinates[index];}// 是否跳出循環if (isBreak) {break;}}coordinateList.add(coordinate);// 設置線串對象lineStrings[i] = buildLineString(coordinateList);}// 添加最后一段lineStrings[length - 1] = buildLineString(coordinates, index, coordinate);// 返回線串數組return lineStrings;}/*** 構建線串* * @param coordinates 坐標數組* @param index 當前序號* @param coordinate 當前坐標* @return 線串*/private static LineString buildLineString(Coordinate[] coordinates, int index, Coordinate coordinate) {List<Coordinate> coordinateList = new ArrayList<>();coordinateList.add(coordinate);coordinateList.addAll(Arrays.asList(ArrayUtils.subarray(coordinates, index, coordinates.length)));return buildLineString(coordinateList);}/*** 構建線串* * @param coordinateList 坐標列表* @return 線串*/private static LineString buildLineString(List<Coordinate> coordinateList) {return GEOMETRY_FACTORY.createLineString(coordinateList.toArray(new Coordinate[0]));}/*** 構建中間坐標* * @param coordinate1 坐標1* @param coordinate2 坐標2* @param segmentLength 分段長度* @param deltaLength 增量長度* @return 中間坐標*/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);}/*** 獲取目標長度* * @param lineLength 線路長度* @param totalLength 總共長度* @param segmentLength 段長度* @return 目標長度*/private static long getTargetLength(double lineLength, double totalLength, double segmentLength) {return Math.round(Math.max(segmentLength, 0.0D) * ZOOM_SCALE * lineLength / totalLength);}/*** 四舍五入* * @param value 雙精度浮點值* @param scale 保留小數位數* @return 四舍五入值*/private static double round(double value, int scale) {return BigDecimal.valueOf(value).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();} }備注說明:
在超過目標長度時,獲取了一個中間坐標,由于數據精度的關系,這個坐標可能跟上一坐標或下一坐標重合。這里為了降低這塊邏輯的復雜度,沒有進行前后坐標的去重處理。
存在問題:
在方法splitLineString(劃分線串)中,存在一個兩層循環,導致了方法邏輯復雜、層級較深、代碼量大。如果把外層循環體提煉為一個方法,就能夠使代碼更簡潔、更清晰、更容易維護。
二 一次失敗的重構經歷
理論依據:
當看到一個方法定義過長或者這段方法需要很多注釋才能讓人理解的時候,這時候就要考慮是不是把這個方法的部分代碼提取出來,形成一個新的方法,方便調用和理解,同時也減小方法的粒度。我們把這種方法叫做提煉函數(Extract Function),在Java語言中可叫做提煉方法(Extract Method)。
重構步驟:
- 創建一個新方法,并根據這個方法的意圖來命名;
- 將待提煉的代碼段從原方法中拷貝到新方法中;
- 檢查提煉的代碼段,把缺少的變量添加到方法的參數中;
- 如果部分參數成對出現,可以把這些參數合并為一個參數;
- 如果方法需要有返回值,確定返回值的類型,并在合適的位置返回;
- 在原方法中,刪除被提煉的代碼段,替換為新方法的調用。
代碼實現:
/*** 幾何輔助類*/ public final class GeometryHelper {/** 原有靜態常量 */....../*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {// 原有計算邏輯......// 初始化參數值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);// 返回線串數組return lineStrings;}/*** 組裝線串* * @param coordinates 坐標數組* @param index 當前序號* @param coordinate 當前坐標* @param targetLength 目標長度* @return 線串*/private static LineString combineLineString(Coordinate[] coordinates, int index, Coordinate coordinate, long targetLength) {// 添加線串坐標long addupLength = 0L;List<Coordinate> coordinateList = new ArrayList<>();coordinateList.add(coordinate);for (; index < coordinates.length; index++) {// 計算分段長度long segmentLength = Math.round(coordinate.distance(coordinates[index]) * ZOOM_SCALE);// 根據長度處理boolean isBreak = true;int compareResult = Long.compare(addupLength + segmentLength, targetLength);// 根據長度處理: 未達目標長度if (compareResult < 0) {addupLength += segmentLength;coordinate = coordinates[index];coordinateList.add(coordinate);isBreak = false;}// 根據長度處理: 超過目標長度else if (compareResult > 0) {long deltaLength = targetLength - addupLength;coordinate = buildMiddleCoordinate(coordinate, coordinates[index], segmentLength, deltaLength);}// 根據長度處理: 等于目標長度else {index++;coordinate = coordinates[index];}// 是否跳出循環if (isBreak) {break;}}coordinateList.add(coordinate);// 返回線串對象return buildLineString(coordinateList);}/** 原有靜態方法 */......}存在問題:
粗看這段代碼,似乎沒有什么問題。但是,通過測試發現,并沒有得到正確的結果。
分析原因:
在《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.
當您將基本類型傳遞到方法中時,您將得到該基本類型的副本。當您將對象引用傳遞到方法中時,您將得到該對象引用的副本。
原來參數index(當前序號)和coordinate(當前坐標)在方法combineLineString(組裝線串)中的修改,只是對該方法中的參數副本進行修改,并沒有體現到調用方法splitLineString(劃分線串)中,從而導致每次調用都在使用參數的初始化值。其實,這是在提取方法的過程中,沒有考慮到參數的作用域。
檢查技巧:
這里給出一個作者屢試不爽的檢查技巧——把提取方法的所有參數添加上final關鍵字,編譯后觀察到哪個參數出現編譯錯誤,就說明這個參數是一個輸入輸出參數(Inout Parameter)。
解決方案:
在Java語言中,沒有直接的輸入輸出參數機制,無法簡單地實現參數的輸入輸出功能。所以,需要借助其它解決方案,來實現參數的輸入輸出功能。在這里,作者通過實踐總結,給出了以下幾種解決方案。
三 利用方法參數實現
本章將從方法參數入手,實現參數的輸入輸出功能。
3.1 利用參數類實現
理論依據:
引入參數對象(Introduce Parameter Object):當一個方法的參數超過3個時,就可以考慮將參數封裝成一個對象類。將參數封裝成對象類后,提高了代碼的可讀性,并且該參數對象類也可以重用。以后,如果增加或刪除參數,方法本身不需要修改,只需要修改參數對象類就可以了。
這里,可以利用引入參數對象重構方法,定義一個輸入輸出參數類,來實現參數的輸入輸出功能。
代碼實現:
/*** 幾何輔助類*/ public final class GeometryHelper {/** 原有靜態常量 */....../*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {// 原有計算邏輯......// 初始化參數值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());// 返回線串數組return lineStrings;}/*** 組裝線串* * @param coordinates 坐標數組* @param inoutParameter 輸入輸出參數* @param targetLength 目標長度* @return 線串*/private static LineString combineLineString(Coordinate[] coordinates, InoutParameter inoutParameter, long targetLength) {// 獲取輸入參數int index = inoutParameter.getIndex();Coordinate coordinate = inoutParameter.getCoordinate();// 添加線串坐標......// 設置輸出參數inoutParameter.setIndex(index);inoutParameter.setCoordinate(coordinate);// 返回線串對象return buildLineString(coordinateList);}/** 原有靜態方法 */....../*** 輸入輸出參數類*/@Getter@Setter@ToString@NoArgsConstructor@AllArgsConstructorprivate static class InoutParameter {/** 當前序號 */private int index;/** 當前坐標 */private Coordinate coordinate;}}3.2 利用單值數組實現
理論依據:
當您將對象引用傳遞到方法中時,您將得到該對象引用的副本。也就是說,當您將數組引用傳遞到方法中時,您將得到該數組引用的副本。但是,這兩個數組引用都指向同一個數組,當修改副本數組引用中的值時,也能體現到原有數組引用中。
利用數組引用的這個特性,可以實現參數的輸入輸出功能。這里,引入了單值數組的概念,即一個數組只有一個值,用于傳遞輸入輸出參數值。
代碼實現:
/*** 幾何輔助類*/ public final class GeometryHelper {/** 原有靜態常量 */....../*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {// 原有計算邏輯......// 初始化參數值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]);// 返回線串數組return lineStrings;}/*** 組裝線串* * @param coordinates 坐標數組* @param indexHolder 序號支撐* @param coordinateHolder 坐標支撐* @param targetLength 目標長度* @return 線串*/private static LineString combineLineString(Coordinate[] coordinates, int[] indexHolder, Coordinate[] coordinateHolder, long targetLength) {// 獲取支撐取值int index = indexHolder[0];Coordinate coordinate = coordinateHolder[0];// 添加線串坐標......// 設置支撐取值indexHolder[0] = index;coordinateHolder[0] = coordinate;// 返回線串對象return buildLineString(coordinateList);}/** 原有靜態方法 */......}3.3 利用元組類實現
理論依據:
元組(Tuple):Java中的元組(Tuple)是一種數據結構,可以存放多個元素,并且每個元素的數據類型可以不同。Tuple與List類似,但是不同的是,List只能存儲一種數據類型,而Tuple可存儲多種數據類型。
可能你會質疑,Object類型的List實際也是可以存儲多種類型的啊?但是,在創建List時,需要指定元素數據類型,只能指定為Object類型;在獲取的元素時,只能獲取到Object類型的值,需要強制轉化為對應的數據類型。而Tuple在創建時,可以直接指定多個元素數據類型;在獲取元素時,無需進行數據類型的強制轉化。
常用的元組工具包有:
- Apache的commons-lang3提供的元組類:
- Pair:MutablePair,ImmutablePair
- Triple:MutableTriple、ImmutableTriple
- JavaTuples提供的元組類:
- Unit
- Pair,KeyValue
- Triplet
- Quartet
- Quintet
- Sextet
- Septet
- Octet
- Ennead
- Decade
隨著元組的元數不斷地增加,代碼的閱讀性也逐漸地下降。當元組的元數超過3個時,不如直接創建對象類,并給予合適類名和字段名,便于代碼的理解和維護。所以,不建議使用JavaTuples中的元組類,而推薦使用Apache的commons-lang3提供的元組類。
代碼實現:
/*** 幾何輔助類*/ public final class GeometryHelper {/** 原有靜態常量 */....../*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {// 原有計算邏輯......// 初始化參數值Coordinate[] coordinates = lineString.getCoordinates();MutablePair<Integer, Coordinate> mutablePair = 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());// 返回線串數組return lineStrings;}/*** 組裝線串* * @param coordinates 坐標數組* @param mutablePair 當前配對* @param targetLength 目標長度* @return 線串*/private static LineString combineLineString(Coordinate[] coordinates, MutablePair<Integer, Coordinate> mutablePair,long targetLength) {// 獲取配對取值int index = mutablePair.getLeft();Coordinate coordinate = mutablePair.getRight();// 添加線串坐標......// 設置配對取值mutablePair.setLeft(index);mutablePair.setRight(coordinate);// 返回線串對象return buildLineString(coordinateList);}/** 原有靜態方法 */......}3.4 利用支撐類實現
理論依據:
在上一節里,把所有輸入輸出參數放入到一個元組里,每一個輸入輸出參數沒有一個具體的命名,造成了代碼的理解和維護困難。如果每一個輸入輸出參數都定義一個元組,可以讓代碼維護者輕松地知道每一個參數的具體含義。所以,這里定義了自己的一元元組類——ObjectHolder(對象支撐類,也可以使用javatuples的Unit類),用于傳遞輸入輸出參數值。
代碼實現:
/*** 幾何輔助類*/ public final class GeometryHelper {/** 原有靜態常量 */....../*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {// 原有計算邏輯......// 初始化參數值Coordinate[] coordinates = lineString.getCoordinates();ObjectHolder<Integer> indexHolder = new ObjectHolder<>(1);ObjectHolder<Coordinate> coordinateHolder = 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());// 返回線串數組return lineStrings;}/*** 組裝線串* * @param coordinates 坐標數組* @param indexHolder 序號支撐* @param coordinateHolder 坐標支撐* @param targetLength 目標長度* @return 線串*/private static LineString combineLineString(Coordinate[] coordinates, ObjectHolder<Integer> indexHolder, ObjectHolder<Coordinate> coordinateHolder, long targetLength) {// 獲取支撐取值int index = indexHolder.getValue();Coordinate coordinate = coordinateHolder.getValue();// 添加線串坐標......// 設置支撐取值indexHolder.setValue(index);coordinateHolder.setValue(coordinate);// 返回線串對象return buildLineString(coordinateList);}/** 原有靜態方法 */......}/*** 對象支撐類*/ @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor public class ObjectHolder<T> {/** 對象取值 */private T value;}3.5 利用其它方法實現
除此之外,還可以利用其它參數方法實現參數的輸入輸出功能:
利用數組實現
首先,在調用函數中,定義一個對象數組,把所有輸入輸出參數存入對象數組中;其次,在被調用函數中,把這些參數從對象數組中取出來使用;再次,在被調用函數中,再把這些參數值存入對象數組中;最后,在調用函數中,把這些參數值從對象數組中取出來使用。
利用對象數組的問題是——代碼可讀性太差,而且在參數的存入和取出過程中,需要進行數據類型的強制轉化。如果所有輸入輸出參數的類型一致,可以直接定義該類型的數組,從而避免了數據類型的強制轉化。
利用Map實現
首先,在調用函數中,定義一個對象Map,把所有輸入輸出參數存入對象Map中;其次,在被調用函數中,把這些參數從對象Map中取出來使用;再次,在被調用函數中,再把這些參數值存入對象Map中;最后,在調用函數中,把這些參數值從對象Map中取出來使用。
利用對象Map實現,代碼的可讀性比利用對象數組實現更強,但是也存在同樣的問題——在參數的存入和取出過程中,需要進行數據類型的強制轉化。如果所有輸入輸出參數的類型一致,可以直接定義該類型的Map,從而避免了數據類型的強制轉化。但是,利用對象Map實現,還不如定義一個參數類更實用。
利用原子類實現
JDK中,提供了一套原子類——AtomicInteger、AtomicLong、AtomicDouble等,可用于對應的基礎類型和包裝類型,實現對應參數的輸入輸出功能。實現方法跟ObjectHolder一樣,這里不再累述。
四 利用方法返回值實現
本章將從方法返回值入手,實現參數的輸入輸出功能。
4.1 利用結果類實現
理論依據:
引入返回值對象(Introduce Return Object):當一個方法的需要返回多個值時,就可以考慮將返回值封裝成一個對象類。將返回值封裝成對象類后,提高了代碼的可讀性,并且該返回值對象類也可以重用。以后,如果增加或刪除返回值,方法本身不需要修改,只需要修改返回值對象類就可以了。
這里,可以利用引入返回值對象重構方法,定義一個返回值對象類,來實現參數的輸入輸出功能。
代碼實現:
/*** 幾何輔助類*/ public final class GeometryHelper {/** 原有靜態常量 */....../*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {// 原有計算邏輯......// 初始化參數值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);// 返回線串數組return lineStrings;}/*** 組裝線串* * @param coordinates 坐標數組* @param index 當前序號* @param coordinate 當前坐標* @param targetLength 目標長度* @return 返回值*/private static ReturnResult combineLineString(Coordinate[] coordinates, int index, Coordinate coordinate, long targetLength) {// 添加線串坐標......// 返回輸出結果return new ReturnResult(index, coordinate, buildLineString(coordinateList));}/** 原有靜態方法 */....../*** 返回值類*/@Getter@Setter@ToString@NoArgsConstructor@AllArgsConstructorprivate static class ReturnResult {/** 當前序號 */private int index;/** 當前坐標 */private Coordinate coordinate;/** 線串對象 */private LineString lineString;}}4.2 利用元組類實現
理論依據:
參考3.3章節的元組(Tuple)的定義和特性。元組(Tuple)可以用于方法的參數值,也可以用于方法的返回值。當一個方法需要返回多個值時,又不愿意定義自己的結果類時,可以采用元組(Tuple)實現多個值的返回。
代碼實現:
/*** 幾何輔助類*/ public final class GeometryHelper {/** 原有靜態常量 */....../*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {// 原有計算邏輯......// 初始化參數值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++) {Triple<Integer, Coordinate, LineString> triple = combineLineString(coordinates, index, coordinate, targetLengthes[i]);index = triple.getLeft();coordinate = triple.getMiddle();lineStrings[i] = triple.getRight();}// 添加最后一段lineStrings[length - 1] = buildLineString(coordinates, index, coordinate);// 返回線串數組return lineStrings;}/*** 組裝線串* * @param coordinates 坐標數組* @param index 當前序號* @param coordinate 當前坐標* @param targetLength 目標長度* @return 返回值*/private static Triple<Integer, Coordinate, LineString> combineLineString(Coordinate[] coordinates, int index, Coordinate coordinate, long targetLength) {// 添加線串坐標......// 返回輸出結果return ImmutableTriple.of(index, coordinate, buildLineString(coordinateList));}/** 原有靜態方法 */......}4.3 利用其它方法實現
除此之外,還可以利用其它返回值方法實現參數的輸入輸出功能:
利用數組實現
首先,在被調用方法中,定義一個對象數組,把多個返回值放入到對象數組中;最后,在調用函數中,把這些參數值從對象數組中取出來,并強制轉化為對應的數據類型。
利用對象數組的問題是——代碼可讀性太差,而且在返回值的存入和取出過程中,需要進行數據類型的強制轉化。如果所有返回值的數據類型一致,可以直接定義該類型的數組,從而避免了數據類型的強制轉化。
利用Map實現
首先,在被調用方法中,定義一個對象Map,把多個返回值放入到對象Map中;最后,在調用函數中,把這些參數值從對象Map中取出來,并強制轉化為對應的數據類型。
利用對象Map實現,代碼的可讀性比利用對象數組實現更強,但是也存在同樣的問題——在返回值的存入和取出過程中,需要進行數據類型的強制轉化。如果所有返回值的類型一致,可以直接定義該類型的Map,從而避免了數據類型的強制轉化。但是,利用對象Map實現,還不如定義一個返回值類更實用。
五 利用類字段實現
本章將從類字段入手,實現參數的輸入輸出功能。
5.1 利用線程本地變量實現
理論依據:
線程本地變量(ThreadLocal):線程本地變量不同于它們的普通變量,因為訪問某個變量的每個線程都有自己的局部變量,且獨立于變量的初始化副本。線程本地變量實例通常是類中的私有靜態字段,它希望將變量狀態與某一個線程關聯起來。
要用類字段解決參數的輸入輸出問題,就必須考慮方法的線程安全性。這里,利用線程本地變量(ThreadLocal)來實現線程中輸入輸出參數值共享。
代碼實現:
/*** 幾何輔助類*/ public final class GeometryHelper {/** 屬性相關 *//** 當前序號支撐 */private static final ThreadLocal<Integer> INDEX_HOLDER = new ThreadLocal<>();/** 當前坐標支撐 */private static final ThreadLocal<Coordinate> COORDINATE_HOLDER = new ThreadLocal<>();/** 原有靜態常量 */....../*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {// 原有計算邏輯......// 初始化參數值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();// 返回線串數組return lineStrings;}/*** 組裝線串* * @param coordinates 坐標數組* @param targetLength 目標長度* @return 線串*/private static LineString combineLineString(Coordinate[] coordinates, long targetLength) {// 獲取支撐取值int index = INDEX_HOLDER.get();Coordinate coordinate = COORDINATE_HOLDER.get();// 添加線串坐標......// 設置支持取值INDEX_HOLDER.set(index);COORDINATE_HOLDER.set(coordinate);// 返回線串對象return buildLineString(coordinateList);}/** 原有靜態方法 */......}5.2 利用類成員變量實現
理論依據:
在上一章節中,利用線程本地變量(ThreadLocal)來實現線程中輸入輸出參數值共享,讓方法的封裝更復雜——需要從線程本地變量(ThreadLocal)讀取和存儲輸入輸出參數值。有沒有一種更簡單的方法,直接利用類成員變量實現輸入輸出參數值的共享呢?
答案是肯定的,可以把方法的封裝和變量的定義封裝到一個類中。這樣,在每一個類實例中,都可以利用類成員變量來實現輸入輸出參數值的共享。但是,這個類是線程非安全的,必須在單線程中使用。
代碼實現:
/*** 幾何輔助類*/ public final class GeometryHelper {// 原有構造方法....../*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {SplitLineStringAlgorithm algorithm = new SplitLineStringAlgorithm();return algorithm.splitLineString(lineString, segmentLengthes);}}/*** 劃分線串算法類*/ public class SplitLineStringAlgorithm {/** 屬性相關 *//** 當前序號 */private int index;/** 當前坐標 */private Coordinate coordinate;/** 原有靜態常量 */....../*** 劃分線串* * @param lineString 原始線串* @param segmentLengthes 分段長度數組* @return 線串數組*/public LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {// 原有計算邏輯......// 初始化參數值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);// 返回線串數組return lineStrings;}/*** 組裝線串* * @param coordinates 坐標數組* @param targetLength 目標長度* @return 線串*/private LineString combineLineString(Coordinate[] coordinates, long targetLength) {// 添加線串坐標......// 返回線串對象return buildLineString(coordinateList);}/** 原有靜態方法 */......}六 各種方法綜合點評
下面,針對以上各種實現方法進行一個綜合點評:
總結如下:
- 各種實現方法有利有弊,應當根據具體的使用場景,來選擇最適合的實現方法。
- 根據參數和返回值的類型選擇實現方法:輸入輸出參數盡量使用方法參數實現,返回值盡量使用返回值實現。
- 根據參數和返回值的數量選擇實現方法:數量少的盡量使用支撐類和元組類,數量多的盡量使用自定義類。
- 不建議使用一些取巧的實現方法,比如:3.2.利用單值數組實現、5.1.利用線程本地變量實現。
- 不推薦使用對象數組和對象Map,Java是強類型定義語言,不建議使用強制數據類型轉化。
- 最適合本文中案例的實現方法是——3.4.利用支撐類實現。
后記
《莊子·養生主》有言:
吾生也有涯,而知也無涯。以有涯隨無涯,殆已!
意思是:我的生命是有限的,但知識卻是無限的。用有限的生命去追求無限的知識,必然會失敗的。
所以,知識并不是越多越好,而是“學而精之,精而深之,深而新之 ”。
原文鏈接:https://developer.aliyun.com/article/771556?
版權聲明:本文內容由阿里云實名注冊用戶自發貢獻,版權歸原作者所有,阿里云開發者社區不擁有其著作權,亦不承擔相應法律責任。具體規則請查看《阿里云開發者社區用戶服務協議》和《阿里云開發者社區知識產權保護指引》。如果您發現本社區中有涉嫌抄襲的內容,填寫侵權投訴表單進行舉報,一經查實,本社區將立刻刪除涉嫌侵權內容。總結
以上是生活随笔為你收集整理的Java编程技巧:如何实现参数的输入输出?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: @所有人 Flink Forward A
- 下一篇: 对人工智能的应用、发展及其影响的思考