我想在任何方面都提供建议,但请记住重点是健壮性。
import java.util.*;
import java.io.*;
public class average {
public static void main (String[] args) throws IOException{
double[] nums = new double[4];
nums = inputHandler();
System.out.println("Average " + findAverage(nums));
System.out.println("End Program.");
}
public static double findAverage(double[] nums){
double average = 0.0;
final double N = 4.0;
for(int i = 0; i < N; i++){
average += nums[i] / N;
}
return average;
}
public static double[] inputHandler() throws IOException{
Scanner input = new Scanner(System.in);
double[] nums = new double[4];
double[] fileNums = new double[3];
// get first three numbers from file
System.out.print("Enter the name of the file which contains the first three numbers: ");
fileNums = fileInput(input.nextLine());
System.out.println();
// copy numbers from fileNums[] to nums[]
for(int i = 0; i < 3; i++){
nums[i] = fileNums[i];
}
//get last number from user input
System.out.print("Enter the fourth number: ");
nums[3] = userInput(input.nextLine());
System.out.println();
input.close();
return nums;
}
public static double[] fileInput(String fileName) throws IOException{
double[] nums = new double[3];
System.out.println();
File file = new File(fileName);
if(!file.isFile()){
System.err.println("ERROR: File does not exist.");
System.exit(-1);
}
System.out.println(fileName + ":");
Scanner input = new Scanner(file);
String cur = "";
for(int i = 0; i < 3; i++){
if(!input.hasNext()){
System.err.println("ERROR: File does not contain enough numbers.");
System.exit(-1);
}
cur = input.next();
System.out.println("Number " + (i + 1) + ": " + cur);
if(cur.matches("-?\d+(\.\d+)?")){ //regex ensures numeric input
if(Double.parseDouble(cur) < 1.7e308){
nums[i] = Double.parseDouble(cur);
}
else{
System.err.println("ERROR: Number too large");
System.exit(-1);
}
}
else{
System.err.println("ERROR: Non numeric input. Please check your file and try again.");
System.exit(-1);
}
}
input.close();
return nums;
}
public static double userInput(String userInput){
double num = 0;
if(userInput.matches("-?\d+(\.\d+)?")){ //regex ensures input is numeric
num = Double.parseDouble(userInput);
}
else{
System.err.println("ERROR: Non numeric input. Please check your file and try again.");
System.exit(-1);
}
return num;
}
}
#1 楼
首先,在可能引发异常的零件周围使用try
/ catch
块。例如,当文件抛出IOException时会发生什么情况,也许是因为数据已损坏并且文件存在但无法打开?在我的书中,关闭应用程序失败是失败:if(!file.isFile()){
System.err.println("ERROR: File does not exist.");
System.exit(-1);
}
第三,@ SirPython是正确的-使用内置方法读取您的电话号码。无论您的程序需要什么,都提供了用于读取
double
和int
的内置函数。第四,这不一定有助于提高鲁棒性,将您的方法逻辑拆分开来。您正在使用fileInput()
方法进行大量打印。用专用方法进行打印。用专用方法进行输入。发生异常行为时,请随时引发异常,但请务必处理该异常。将您的逻辑拆分成小块大小的组件并分别实现-这将使重用变得容易。第五,从错误中恢复。如果需要,请重新开始整个输入过程,但不要关闭程序。由于错误而关闭程序可能会被视为错误,并且在许多程序中是不可接受的。
#2 楼
该程序不能完全坚不可摧。for(int i = 0; i < N; i++){
average += nums[i] / N;
}
for(int i = 0; i < N; i++){
total += nums[i];
}
average = total / N;
有两种方法可以计算数字列表的平均值,这两种方法均可被破坏:
使用的方法:将每个数字除以列表的大小,然后将其添加到运行总计中。这不受溢出错误的影响,但是比方法2更容易遭受舍入错误的影响,特别是在数字非常小的情况下(如果教授输入
Double.MIN_VALUE
的四个副本,则代码应在返回0
时返回Double.MIN_VALUE
)。将所有数字相加,然后将总数除以列表的大小。这可以抵抗舍入错误,但很容易发生溢出错误(如果教授输入Double.MAX_VALUE
的四个副本,则当应返回Double.MAX_VALUE
时,它将溢出)。它也比方法1更快,但是在大多数情况下,速度差异并不重要。选择正确的方法取决于溢出错误还是舍入错误更重要。
评论
\ $ \ begingroup \ $
如果您确实想要,可以检查所有输入的幅度,并确定如何进行。但是代码看起来要复杂得多。
\ $ \ endgroup \ $
– JS1
2015年9月17日在9:16
\ $ \ begingroup \ $
avr = nums [i] + i /(i + 1。)*(avr-nums [i])怎么办?应该足够容易,并且没有这种精度问题。
\ $ \ endgroup \ $
–TonioElGringo
2015年9月17日在11:47
\ $ \ begingroup \ $
为了获得更多乐趣,请注意Java中的浮点加法不是关联的,因此“平均值”的定义不明确。找出平均0.0、0.1、0.2和0.3时会发生什么,然后对0.3、0.2、0.1和0.0尝试相同的事情。
\ $ \ endgroup \ $
–James_pic
2015年9月17日下午13:33
\ $ \ begingroup \ $
这不是完美的方法,但是用方法1进行的Kahan求和将得出非常接近正确的结果。 Double.MIN_VALUE问题并不可怕,因为结果与正确值仅1 ulp。
\ $ \ endgroup \ $
–恢复莫妮卡
2015年9月17日下午16:44
\ $ \ begingroup \ $
您可以避免这两个问题:首先,对数字求和并计数,直到列表用尽或溢出为止。如果发生溢出,请使用先前的总和计算部分平均值,然后继续。最后计算出完整的平均值。
\ $ \ endgroup \ $
–l0b0
2015年9月17日19:25在
#3 楼
public static double userInput(String userInput){
// ...
if(userInput.matches("-?\d+(\.\d+)?")){
// ...
} else {
System.err.println("ERROR: Non numeric input.Please check your file and try again.");
System.exit(-1);
}
// ...
}
提示我输入一个数字!我可能输入错误,但是为什么它要求我检查文件?
这是复制粘贴代码的问题...如果您有一种方法可以接受来自一个
double
实例和一个自定义错误消息,您将可以回避此问题。例如:/**
* Gets the number of double values from the scanner.
*
* @param scanner the {@link Scanner} instance to take from
* @param times the number of values required
* @param errorMessage the error message to use if not enough values
* @return a double array of the required size
* @throws {@link IllegalArgumentException} if not enough values
*/
private static double[] getInputs(Scanner scanner, int times, String errorMessage) {
double[] results = new double[times];
int i = 0;
for (; i < times && scanner.hasNextDouble(); i++) {
results[i] = scanner.nextDouble();
}
if (i != times) {
throw new IllegalArgumentException(errorMessage);
}
return results;
}
另外,要呼应@ Hosch250的回答,程序的健壮性是其从错误中恢复的能力...您在这里所做的只是为了使用错误消息处理它们,但是您没有尝试恢复。这也许也值得研究,例如您是否希望继续提示输入第四个数字,直到接受有效的
Scanner
值?#4 楼
没用的代码在代码的第一行,我已经看到一个问题:
double[] nums = new double[4];
nums = inputHandler();
为什么要为
nums
分配一个数组,然后立即将其重新分配给还有什么吗第一行完全没有用。应该是: double[] nums = inputHandler();
您在
inputHandler()
中使用fileNums
犯了同样的错误。 /> public static double findAverage(double[] nums){
double average = 0.0;
final double N = 4.0;
for(int i = 0; i < N; i++){
average += nums[i] / N;
}
return average;
}
将
double
,N
用作循环限制。每个循环除一次,而不是一次。 br />这就是我写的方式。请注意,我的函数假定数组将包含至少一个元素。如果这不一定是正确的,也可以检查是否为空或空数组。
public static double findAverage(double[] nums){
double sum = 0.0;
final int numElements = nums.length;
for(int i = 0; i < numElements; i++) {
sum += nums[i];
}
return sum / numElements;
}
评论
\ $ \ begingroup \ $
不过,OP的平均值+ = nums [i] / N的方式确实可以防止溢出。
\ $ \ endgroup \ $
– h.j.k.
2015年9月17日下午5:27
\ $ \ begingroup \ $
@ h.j.k。那么下溢呢?如果nums [0]是最小的可表示双精度数怎么办?无论哪种方式都会有问题。
\ $ \ endgroup \ $
– JS1
2015年9月17日上午9:08
\ $ \ begingroup \ $
没错,@ Mark的答案都涵盖了这两点... :)
\ $ \ endgroup \ $
– h.j.k.
15年9月17日在9:12
#5 楼
您正在做太多工作来验证输入。Scanner
已经有一种读取双精度数据的方法:java.util.Scanner.nextDouble
。与其做所有疯狂的正则表达式工作,不如直接在
java.util.Scanner.hasNextDouble
旁边使用它来接收输入。这样可以确保收到的所有输入都是有效的,并且可以有效地接收到。因此,您的教授很难破坏该应用程序。无需实际进行任何处理。实际上,如果您使用的是IDE,则您的IDE很可能会发脾气。处理它。只需在方法中插入一个
IOException
并让该方法处理异常,而不是使用该方法的任何对象。 如果这是一个常量值,则更好的做法是将其移至类的属性并像这样声明它:
final double N = 4.0;
您的代码
IOException
出现在代码的很多地方。坐在那里看起来有点尴尬。它应该是一个常量,类似于上面。评论
\ $ \ begingroup \ $
只需在方法中插入try / catch,然后在catch中抛出IOException,这会有什么好处?您会发现常量。我的个人指导原则是,除了0、1和''(空字符串)以及较小范围内的2之外的所有文字都是可疑的,几乎应始终移至常量...然后我也有此个人指导原则常量本身是可疑的,应该几乎总是将其移动到具有合理默认值的可配置设置... :)
\ $ \ endgroup \ $
– Stijn de Witt
2015年9月17日在7:25
\ $ \ begingroup \ $
@StijndeWitt我已经根据你的第一句话对答案进行了编辑。
\ $ \ endgroup \ $
– SirPython
15年9月17日在21:30
#6 楼
如果用户将不包含换行符的字符流传送到stdin,则扫描程序可能只会继续阅读。 (我测试了一个9 mb的文件,它读取了整个内容。)当您尝试从数据文件中读取下一行时,Scanner.next()会执行相同的操作。读取文件系统的技巧时,也有可能使扫描仪挂起。例如,如果在调用File.isFile()和尝试读取文件之间替换了该文件,则可以用刚刚挂起的管道替换该文件。
评论
许多人提到了有关上溢/下溢的问题。但是,可以使用BigDecimal避免此类错误。这样,实际上就可以制作出坚如磐石,牢不可破的程序。