Java的陷阱。 第一部分

你好 我想提请您注意一篇简短的文章。 本文适用于初学者。 但是,即使您是经验丰富的开发人员,也请不要下结论。
我希望该出版物不仅对初学者有用。

本出版物的目的:
向初学者展示最常见的错误以及一些纠正错误的技巧。 显然,某些错误可能很复杂,并且由于某种原因或另一种原因而发生。 该出版物的目的是在某种程度上分析它们并帮助及早识别。 我希望该出版物对初学者有用。

错误清单:

1.错别字。 烦人的错别字不会立即显示
2.条件分配而不是比较
3.条件中的逻辑错误
4.错误的字符串比较
5.基本类型变量的初始化不正确
6.双重使用不当
7.构造函数中返回值的类型错误。
8.除以零。 POSITIVE_INFINITY
9.不考虑类的初始化顺序
10.局部变量隐藏了一个类变量
11.忽略算术表达式中的自动转换
12.带字节的无限循环,难以检测。
13.类的名称与存储它的文件的名称不同。
14.作为数组元素的对象未初始化。
15.使用public修饰符一次将多个类放在一个文件中

Java陷阱


所有编程语言都有其优点和缺点。 这是由于许多原因。 Java也不例外。 我试图收集新手Java程序员遇到的一些明显的和不明显的困难。 我相信有经验的程序员也会在我的文章中找到有用的东西。 练习,专心和积累的编程经验将帮助您避免许多错误。 但是最好事先考虑一些错误和困难。 我将给出一些带有代码和解释的示例。 通过对代码的注释,许多解释将对您清晰明了。 由于一些规则不是很明显,实践给了很多。 有些位于表面上,有些则隐藏在语言库或Java虚拟机中。 请记住,java不仅是具有一组库的编程语言,还是Java虚拟机。

对于这篇文章,我专门编写了带有详细注释的工作代码。 为了编写带有代码示例的文章,使用了Java 8,为了进行测试,将Java代码放在单独的程序包中。

示例:“ packageunderwaterRocks.simple;”

初学者面临什么困难?

错别字


碰巧的是,新手程序员所做的错别字一目了然。


代码示例:

文件:“ Simple.java”

/*   ;     */ package underwaterRocks.simple; /** * * @author Ar20L80 */ public class Simple { public static void main(String[] args) { int ival = 10; if(ival>0); { System.out.println("     "); } } } 


说明 :“分号表示语句的结尾。 在这种情况下; 空语句的结尾。 这是一个逻辑错误。 这样的错误可能很难检测到。

编译器将认为一切正确。 条件(ival> 0); 在这种情况下没有意义。 因为这意味着:如果ival大于零,则不执行任何操作并继续。”

条件分配而不是比较


条件是变量分配。

这不是一个错误,但是应该证明使用这种技术是合理的。

  boolean myBool = false; if(myBool = true) System.out.println(myBool); 

在这段代码中,如果(myBool = true)表示:“将变量myBool设置为true,
如果表达式为真,则遵循括号后面的条件。”

在此代码中,条件将始终为true。 和System.out.println(myBool); 无论条件如何,都将始终执行。

==是相等性的比较。
=是一项作业,可以说a = 10; 例如:“但指定值为10”。

括号中的条件返回布尔值。
编写顺序无关紧要。 您可以像这样比较:(0 == a)或(5 == a)
如果忘记一个等号,例如(0 = a)或(5 = a),则编译器将通知您错误。 您分配一个值,而不是一个比较。
您也可以以可读的形式写一些时间间隔。
例如:您需要写:大于5小于10。
您可以这样写:(a> 4 && a <10),但是成功的话,您可以这样写:(4 <a && a <10),
现在您看到a在4到10之间,不包括这些值。 这更加明显。 显然,不包括这些值的a在4到10之间。

代码示例(间隔为3.9 []):
如果(3 <a && a <9)执行;

逻辑错误


if(condition){} if(condition){} else {}-else表示最接近的if。
这通常是初学者错误的原因。

无效的字符串比较

初学者经常使用==代替.equals来比较字符串。

变量初始化


考虑初始化基本类型的变量。

基元(字节,短整数,整数,长整数,字符,浮点数,双精度,布尔值)。

初始值。

 byte 0 short 0 int 0 long 0L float 0.0f double 0.0d char '\u0000' String (or any object) null boolean false (  jvm) 


注意事项:

局部变量略有不同。
编译器永远不会将默认值分配给未初始化的局部变量。

如果您无法在声明它的地方初始化本地变量,
请记住在尝试使用之前为其分配一个值。

访问未初始化的局部变量将导致编译时错误。

在代码中确认此注释:

文件:“ MyInitLocal.java”

 /*         */ package underwaterRocks.myInit; /** * * @author Ar20L80 */ public class MyInitLocal { float classes_f; int classes_gi; public static void main(String[] args) { float f; int i; MyInitLocal myInit = new MyInitLocal(); /*         .*/ System.out.println("myInit.classes_f = " + myInit.classes_f); System.out.println("myInit.classes_gi = " + myInit.classes_gi); // System.out.println("f = " + f); // .     // System.out.println("f = " + i); // .     } } 


值范围:

byte ( , 1 , [-128, 127])
short ( , 2 , [-32768, 32767])
int ( , 4 , [-2147483648, 2147483647])
long ( , 8 , [-922372036854775808,922372036854775807])
float ( , 4 )
double ( , 8 )
char ( Unicode, 2 , 16 , [0, 65535])
boolean ( /, int, JVM)

char:char数据类型是单个16位Unicode字符。 它的最小值为“ \ u0000”(或0),最大值为“ \ uffff”(或65,535(含))。


Oracle文档>>

让我们尝试初始化一个数字类型为long的变量:922372036854775807。
没有任何事情对我们有利。 因为它是int类型的整数文字。
使用长文字正确初始化:922372036854775807L;

代码示例:

文件:“ MyInitLocalLong.java”

 /*    long  */ package underwaterRocks.myInit; /** * * @author Ar20L80 */ public class MyInitLocalLong { public static void main(String[] args) { // long al = 922372036854775807; // integer number too large long bl = 922372036854775807L; //   } } 


初始化变量时要查找什么。

此类型的变量的值范围。 用某种类型的文字初始化变量的事实。 用于显式和隐式强制转换。 关于类型兼容性。

使用Integer类型的shell时,应注意这些类型的自动打包和自动拆包。

双重使用不当


在这里您需要澄清。 这与滥用double类型无关。
我们正确使用。 只有结果才能使新手程序员感到惊讶。
文件:“ MinusDouble.java”
 /*   */ package underwaterRocks.tstDouble; /** * * @author vvm */ public class MinusDouble { public static void main(String[] args) { double a = 4.64; double b = 2.64; System.out.println("ab = "+(ab)); } } /*   run: ab = 1.9999999999999996 */ 


注意有关双精度型。 浮点数可让您以给定的相对误差和很大的范围进行计数。 在科学计算中,经常需要相对误差。

无效的双重比较


考虑双重类型。

代码示例:

文件:“ MyDouble.java”

 /*    double  - double. */ package underwaterRocks.myDouble; /** * * @author Ar20L80 */ public class MyDouble { public static void main(String[] args) { double dx = 1.4 - 0.1 - 0.1 - 0.1 - 0.1; System.out.println("dx = " + dx); // dx = 0.9999999999999997 System.out.print(" (dx == 1.0):"); System.out.println(dx == 1.0); // false,   1.0   0.9999999999999997 /*   double*/ final double EPSILON = 1E-14; double xx = 1.4 - 0.1 - 0.1 - 0.1 - 0.1; double xy = 1.0; /*  xx c xy */ if (Math.abs(xx - xy) < EPSILON) System.out.println(xx + "    " + xy + " EPSILON = " + EPSILON); } } 

双精度型在不需要高精度的地方很方便。 对于金融交易,此类型不合适。 尽管有些公司不是很诚实,但使用double型将其舍入到所需的一侧。 对于财务操作,BigDecimal类用于财务计算,因为由于精度损失和舍入结果错误的原因,实际原始类型不适用于该目的。 但是,使用BigInteger类可以获得更准确的结果。

类构造器


类构造函数与类名称匹配,不返回任何内容,甚至不返回void。

代码示例:

文件:“ MyConstructor.java”

 /*      ,  void    void -    */ package underwaterRocks.myConstructor; /** * * @author Ar20L80 */ public class MyConstructor { public MyConstructor(){ System.out.println("   void"); } public void MyConstructor(){ System.out.println("  c void"); } public static void main(String[] args) { MyConstructor myconst = new MyConstructor(); myconst.MyConstructor(); //    } } 

正如我们在代码中看到的,两个具有相同名称的方法:MyConstructor()和MyConstructor()。 其中一种方法不返回任何内容。 这是我们类的构造函数。 另一个带有void的方法是常规类方法。 如果您没有创建构造函数,或者您认为创建的类的构造函数为void,则编译器将创建默认构造函数,您会惊讶为什么构造函数不起作用。

被零除


您认为将是执行此类代码的结果。

文件:“ DivisionByZero.java”

 /* */ package divisionByZero; import static java.lang.Double.POSITIVE_INFINITY; /** * * @author Ar20L80 */ public class DivisionByZero { public static void main(String[] args) { try{ float f = 12.2f; double d = 8098098.8790d; System.out.println(f/0); System.out.println(d/0); System.out.println(POSITIVE_INFINITY == f/0); System.out.println(POSITIVE_INFINITY == d/0); } catch (NumberFormatException ex) { System.out.println("NumberFormatException"); } catch (ArithmeticException ex) { System.out.println("ArithmeticException"); } } } 

执行代码将输出:

 Infinity Infinity true true 

将整数类型除以零将产生ArithmeticException。

java.lang.Double类定义常量POSITIVE_INFINITY;

 public static final float POSITIVE_INFINITY = 1.0d / 0.0d; 

它将转换为等于Infinity的字符串。

初始化顺序


文件:“ InitClass.java”

 /*     */ package myInitClass; /** * * @author Ar20L80 */ public class InitClass { InitClass(){ //   System.out.print(""); } { //   System.out.print("3 "); } public static void main(String[] args) { System.out.print("2"); new InitClass(); } static { //    System.out.print("1"); } } 

首先,执行所有静态块,然后执行初始化块,然后执行类构造函数。

它将显示:“ 123 Constructor”

局部变量隐藏了类变量
尽管现代的IDE可以轻松检测到此错误,但我想更详细地考虑这种错误。 让我们从构造函数中的经典变量赋值开始。 这个例子是正确的。 没有错
 public class MyClass { private int val = 0; public MyClass(int val) { this.val = val; } } 

但是,如果在方法中而不是在类构造函数中使用此技术,会发生什么? 在通常的方法中,不建议使用此技术。 问题与班级的正确设计有关。

一个简单的解释:在方法中,与类变量同名的变量是该方法的局部变量。 您可以使用this.val访问类变量。 但是,如果类的设计不当,则这种方法的吸引力只会导致副作用,并且可能会降低代码的可读性。

算术类型转换是自动完成的

这会导致烦人的错误。
 // byte a = 1; // byte b = 1; // byte  = a + b; //  // byte a = (byte) 1; // byte b = (byte) 1; // byte  = a + b; //  


 //     —     . byte a = 1; byte b = 1; byte c = (byte) (a + b); 


 //     —  final // final byte a = 1; // final byte b = 1; // byte c = a + b; //    ,  a  b final 


使用字符串时,一种可能的解决方案:
 byte bHundr = Byte.parseByte("100"); //      byte 


以下代码给出了另一个错误。
 for (byte i = 1; i <= 128; i++) { System.out.println(i); } 

在这种情况下,我们得到一个无限循环。

的解释。 键入字节[-128,127]。 128不再在此范围内。 发生溢出,并且循环重复。 在这种情况下使用字节的必要性令人怀疑。 尽管这种情况很少发生。 建议使用int而不是字节。 另一个建议是不要在算法中使用循环。

数组元素的对象未初始化
 int[] cats = new int[10]; for(int i=0; i<cats.length;i++){ System.out.println("cats " + i + " = " + cats[i]); } 


在这个例子中,我们有一个原始类型元素的数组。 如果不初始化它们,也不会发生任何不良情况。 它们将被分配默认值。 在这种情况下,值= 0。

让我们考虑另一个示例,该示例不在数组中包含基元,而是在数组中包含对象。
 public class ArrInitObj { public static void main(String[] args) { MyObj[] cats = new MyObj[10]; for(int i=0; i<cats.length;i++){ System.out.println("cats " + i + " = " + cats[i]); System.out.println("cats " + i + ".val = " + cats[i].val); //    java.lang.NullPointerException } } } class MyObj{ public int val; } 


解决此问题的方法是在使用它们之前初始化所有对象变量。 可以在MyObj类的构造函数中完成初始化。

类名与存储它的文件名不同
现代IDE可以轻松检测到此类错误。 但是,尽管很少遇到这种错误。 考虑到大小写字母名称的差异,这将有助于注意。

使用public修饰符一次将多个类放在一个文件中
该错误非常罕见。 IDE将立即向您发出警告。
文件名必须与公共类的名称匹配。

结论
乍看之下,许多错误并不明显。 即使是经验丰富的程序员也可以这样做,但数量较少。 细心,实际经验,使用调试器和阅读文档将帮助您避免许多错误。

我希望您喜欢这篇文章并发现对您有所帮助。 我很高兴您的意见,评论,建议和愿望。 待续。 相反,添加如下。

参考文献
Oracle Java代码设计指南>>>

PS。 我的朋友们 没有您的帮助,我无法继续发布。 也就是说,没有经济机会。 如果该出版物确实对您有所帮助,并且您想继续,请支持我。 某个地方有一个按钮:“支持作者”。
希望您的理解。 谢谢啦 感谢Habr发布的机会。

如果缺乏支持,作者将被迫删除自己的出版物或将其隐藏在草稿中。 这不是最后通.。 如果有机会,它会派上用场,您可以提供帮助,然后单击作者支持按钮。

Source: https://habr.com/ru/post/zh-CN439642/


All Articles