【AICon】AI 大模型超全落地场景&最佳实践 了解详情
写点什么

Java 编程技巧:如何实现参数的输入输出?

  • 2020-09-23
  • 本文字数:16030 字

    阅读完需:约 53 分钟

Java编程技巧:如何实现参数的输入输出?

AI 大模型超全落地场景&金融应用实践,8 月 16 - 19 日 FCon x AICon 大会联诀来袭、干货翻倍!

前言

软件开发方法学的泰斗肯特·贝克(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<L, R>:MutablePair<L, R>,ImmutablePair<L, R>

  • Triple<L, M, R>:MutableTriple<L, M, R>、ImmutableTriple<L, M, R>

  • JavaTuples 提供的元组类:

  • Unit<A>

  • Pair<A,B>,KeyValue<A,B>

  • Triplet<A,B,C>

  • Quartet<A,B,C,D>

  • Quintet<A,B,C,D,E>

  • Sextet<A,B,C,D,E,F>

  • Septet<A,B,C,D,E,F,G>

  • Octet<A,B,C,D,E,F,G,H>

  • Ennead<A,B,C,D,E,F,G,H,I>

  • Decade<A,B,C,D,E,F,G,H,I,J>


随着元组的元数不断地增加,代码的阅读性也逐渐地下降。当元组的元数超过 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@AllArgsConstructorpublic 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.利用支撑类实现。

后记

《庄子·养生主》有言:


吾生也有涯,而知也无涯。以有涯随无涯,殆已!


意思是:我的生命是有限的,但知识却是无限的。用有限的生命去追求无限的知识,必然会失败的。


所以,知识并不是越多越好,而是“学而精之,精而深之,深而新之 ”。


本文转载自公众号高德技术(ID:amap_tech)。


原文链接


Java编程技巧:如何实现参数的输入输出?


2020-09-23 14:001863

评论

发布
暂无评论
发现更多内容

云桌面怎么选?一篇搞懂四大关键指标!

青椒云云电脑

云桌面 云桌面厂家 云桌面方案 云桌面系统

Postman POST请求教程:从入门到精通

Liam

Java 程序员 前端 后端 Postman

解锁加密经济领域的新篇章:Token Explorer 助您一臂之力

Footprint Analytics

区块链 加密货币 Token 公链

用C#实现简单的线性回归

EquatorCoco

Python C# 线性回归 开发语言

京东JD商品详情API:实时数据获取的实现

Noah

Mixtral 8X7B MoE模型基于PAI的微调部署实践

阿里云大数据AI技术

RetsCloud AppLink适用的场景有哪些?

RestCloud

零代码 自动化集成 适用场景

提高iOS App开发效率的方法

雪奈椰子

打通商城与ERP系统,实现物料自动同步

聚道云软件连接器

案例分享

2023年Gartner® DevOps平台魔力象限发布,Atlassian被评为“领导者”

龙智—DevSecOps解决方案

DevOps

快速搭建前端开发平台利器

高端章鱼哥

软件开发 前端开发 前端框架

电商新趋势:解析养号的必要性及海外云手机运用攻略

Ogcloud

云手机 海外云手机 跨境电商云手机

云桌面直接当电脑用?云桌面到底是何方神圣

青椒云云电脑

桌面云 云桌面 云桌面解决方案

腾讯云ES RAG最佳实践:百行代码轻松实现帮助文档的智能问答

腾讯云大数据

ES

实录分享 | 央企大数据平台架构发展趋势与应用场景的介绍

Alluxio

人工智能 大数据 构架 Alluxio 央企

IITO-IPQ6010 WIFI router support-What is the relationship between VAP-AP-STA?

wifi6-yiyi

iiot vap

铭文 LaunchPad 平台 Solmash 推出早鸟激励计划

长安区块链

Jenkins入门知识:什么是Jenkins?以及它的历史与发展

龙智—DevSecOps解决方案

PaaS服务的零代码开发平台——JNPF

这我可不懂

软件开发 低代码 JNPF

流式湖仓增强,Hologres + Flink 构建企业级实时数仓

Apache Flink

告别信息差!奇点云SimbaMetric打破协作壁垒,为指标管理提效

奇点云

奇点云 数据指标体系 指标工厂

虚幻引擎nDisplay教程:如何同步nDisplay节点与Switchboard + Helix Core

龙智—DevSecOps解决方案

软件测试/测试开发/全日制/测试管理丨Cypress测试框架

测试人

软件测试

软件测试/测试开发/全日制/测试管理丨Playwright测试框架

测试人

软件测试

云桌面如何推动建筑行业数字化转型?

青椒云云电脑

云桌面 云桌面厂家 云桌面解决方案

ByConity 社区回顾|ByConity 和开发者们一起展望未来,携手共进!

字节跳动开源

大数据 开源 字节跳动 社区 回顾

软件测试/测试开发全日制|Pytest conftest.py配置文件如何使用?

霍格沃兹测试开发学社

从工程化角度,详解鹏程·脑海大模型训练过程

华为云开发者联盟

人工智能 华为云 华为云开发者联盟 模型训练优化

云服务器网站搭建全攻略:技巧与窍门

一只扑棱蛾子

云服务器

海外云手机助力企业拓展海外市场

Ogcloud

云手机 海外云手机

Vue.js轻量级框架:快速搭建可扩展的管理系统

互联网工科生

Vue 表单 管理系统

Java编程技巧:如何实现参数的输入输出?_架构_高德技术_InfoQ精选文章