共计 20232 个字符,预计需要花费 51 分钟才能阅读完成。
简介: 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 | |
@AllArgsConstructor | |
private 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 | |
@AllArgsConstructor | |
private 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. 利用撑持类实现。
后记
《庄子·养生主》有言:
吾生也有涯,而知也无涯。以有涯随无涯,殆已!
意思是:我的生命是无限的,但常识却是有限的。用无限的生命去谋求有限的常识,必然会失败的。
所以,常识并不是越多越好,而是“学而精之,精而深之,深而新之”。