普开数据Java学习心得之易犯错误.docx
- 文档编号:3618573
- 上传时间:2022-11-24
- 格式:DOCX
- 页数:39
- 大小:41.33KB
普开数据Java学习心得之易犯错误.docx
《普开数据Java学习心得之易犯错误.docx》由会员分享,可在线阅读,更多相关《普开数据Java学习心得之易犯错误.docx(39页珍藏版)》请在冰豆网上搜索。
普开数据Java学习心得之易犯错误
第1部分语法
程序员们总是被层出不穷的复杂问题所困扰假如我们最基本的开放工具——设计和编写程序的语言本身就是复杂的那么这个语言自己也会成为这些复杂问题的一部分而非它们的解决方案了
——C.A.R.Hoare,TheEmperor’sOldClothes Java语言从C++派生而来并借鉴了ObjectiveCEiffelSmalltalkMesa和Lisp这些
语言的一些特性当使用其他语言的程序员转用Java来编程时他们很快就会发现Java的一些特性和自己以前所熟悉的语言非常相似因此这些程序员通常会认为这些特性在Java中和在以前所使用的语言中表现一致其实完全不是这样这些想法在C++程序员中尤其普遍 这一部分重点强调了Java语言上经常会绊倒新手的陷阱和语言行为
本部分包括以下10个单元
Item1什么时候被覆盖的方法并非真的被覆盖了本单元解释了调用子类的实例方法和静态方法之间的微妙差别
Item2String.equals()方法与==运算符的用法比较”本单元解释了这两种方法比较字符串的不同之处并指出了常量池是如何混淆这两种用法的。
第1部分语法
2t
Item3Java是强类型语言本单元解释了基本类型的转换和提升的规则这对从C++ 转到Java的程序员尤为重要
Item4那是构造函数吗本单元给出了一个经典的然而又非常简单的语言陷阱当我们培训新的Java学员时这个陷阱总是让学员们问出这样的问题编译器怎么会没发现它
Item5不能访问被覆盖的方法本单元又一次讨论了Java语言中的方法调用读完以后你将完全理解这个知识点
Item6避免落入隐藏变量成员的陷阱本单元讨论了这一最常见的陷阱所有Java语言的入门课程都应该提及这个问题并且和this引用一起讨论
Item7提前引用这一较短的单元向我们演示了什么是提前引用以及如何去避免它
Item8设计可继承的构造函数本单元是取自来之不易的实践经验对于每一个想开发可重用Java类的程序员来说这个单元是必读的
Item9通过引用传递基本类型本单元对从C++转换到Java的程序员特别有价值它解答了在Java中传递引用的相关问题
Item10布尔运算符与短路运算符本单元解释了Java编程中另一个常见的陷阱使用逻辑运算符单元中也举了一个使用短路short-circuit运算符的清晰例子
Item1:
什么时候被覆盖的方法并非真的被覆盖了好吧我承认本单元的标题确实带有一定的欺骗性虽然它的本意并非欺骗你而是
帮助你理解方法覆盖的概念想必你已经阅读了一两本这样的Java书籍它们在开头都指出了面向对象编程的3个主要概念封装继承和多态理解这3个概念对于领会Java语言来
说至关重要而搞懂方法的覆盖又是理解继承概念的关键部分覆盖实例方法会在Item5谈到本单元介绍静态方法的覆盖如果你还不明白两者的区
别那么Item1和Item5正适合你假如你已经急不可待地喊出不能覆盖静态方法那么你也许需要放松片刻再继续往下看不过在此之前先看看你是否能够猜出下面例子的
输出结果
Item1:
什么时候被覆盖的方法并非真的被覆盖了 u3
这个例子摘自Java语言规范8.4.8.5节
01:
classSuper
02:
{
03:
staticStringgreeting()
04:
{
05:
return"Goodnight";
06:
}
07:
08:
Stringname()
09:
{
10:
return"Richard";
11:
}
12:
}
01:
classSubextendsSuper
02:
{
03:
staticStringgreeting()
04:
{
05:
return"Hello";
06:
}
07:
08:
Stringname()
09:
{
10:
return"Dick";
11:
}
12:
}
01:
classTest
02:
{
03:
publicstaticvoidmain(String[]args)
04:
{
05:
Supers=newSub();
06:
System.out.println(s.greeting()+","+s.name());
07:
}
08:
}
运行Test类的结果如下
Goodnight,Dick
要是你得出了同样的输出结果那么你或许对方法的覆盖有了较好的理解如果你的结果和答案不一致那就让我们一起找出原因我们先分析一下各个类Super类由方法greeting和name组成Sub类继承了Super类而且同样含有greeting和name方法Test类只有一个main方法
第1部分语法
4t
在Test类的第5行中我们创建了一个Sub类的实例在这里你必须明白的是虽然变量s的数据类型为Super类但是它仍旧是Sub类的一个实例如果你对此有些迷惑那么可以这样理解变量s是一个被强制转换为Super型的Sub类的实例下一行(第6行)显示了s.greeting()返回的值加上一个字符串紧随其后的是s.name()的返回值关键问题
就在这里我们调用的到底是Super类的方法还是Sub类的方法让我们首先判断调用的是哪个类的name()方法两个类中的name()方法都不是静态方法而是实例方法因为 Sub类继承了Super类而且有一个和它父类同样标识的name()方法所以Sub类中的name()方法覆盖了Super类中的name()方法那么前面提到的变量s又是Sub类的一个实例这样一来s.name()的返回值就是Dick了
至此我们解决了问题的一半现在我们需要判断被调用的greeting()方法究竟是Super类的还是Sub类的需要注意的是两个类中的greeting()方法都是静态方法也称为类方法 尽管事实上Sub类的greeting()方法具有相同的返回类型相同的方法名以及相同的方法参
数无然而它并不覆盖Super类的greeting()方法由于变量s被强制转换为Super型并 且Sub类的greeting()方法没有覆盖Super类的greeting()方法因此s.greeting()的返回值为 Goodnight还是很迷惑请记住这条规则“实例方法被覆盖静态方法被隐藏”假如
你就是刚才大喊不能覆盖静态方法的读者之一那么你完全正确 现在你可能会问“隐藏和覆盖有什么区别”你也许还未理解这点然而实际上我们刚刚在这个Super/Sub类的例子中已经解释了两者的不同使用类的全局名可以访问被隐藏的方法即使变量s是Sub类的一个实例而且Sub类的greeting()方法隐藏了Super类的同
名方法我们仍旧能够将s强制转换为Super型以便访问被隐藏的greeting()方法与被隐藏的方法不同对被覆盖的方法而言除了覆盖它们的类之外其他任何类都无法访问它们这就是为何变量s调用的是Sub类的而非Super类的name()方法
本单元简要解释了Java语言中一个不时引起混淆的问题也许对你来说理解隐藏静态方法和覆盖实例方法的区别的最佳方式就是自己创建几个类似于Sub/Super的类再重复一次规则实例方法被覆盖而静态方法被隐藏被覆盖的方法只有覆盖它们的类才能访问它们而访问被隐藏的方法的途径是提供该方法的全局名现在你终于明白标题里问题的答案了吧什么时候“被覆盖的”方法并非真地被覆盖了呢答案就是“永远不会”另外我还有几个要点告诉大家请谨记
Item2:
String.equals()方法与==运算符的用法比较u5
l试图用子类的静态方法隐藏父类中同样标识的实例方法是不合法的编译器将会报错
l试图用子类的实例方法覆盖父类中同样标识的静态方法也是不合法的编译器同样会报错
l静态方法和最终方法带关键字final的方法不能被覆盖l实例方法能够被覆盖
l抽象方法必须在具体类1中被覆盖 Item2:
String.equals()方法与==运算符的用法比较
具有C++经验的读者们肯定会对何时使用等于运算符==以及何时使用string类感到困惑这种困惑主要是String.equals(...)方法和==运算符的混淆尽管它们有时产生的结果一样然而实际上它们的用法是不同的看看下面的例子
01:
publicclassStringExample
02:
{
03:
publicstaticvoidmain(Stringargs[])
04:
{
05:
Strings0="Programming";
06:
Strings1=newString("Programming");
07:
Strings2="Program"+"ming";
08:
09:
System.out.println("s0.equals(s1):
"+(s0.equals(s1)));
10:
System.out.println("s0.equals(s2):
"+(s0.equals(s2)));
11:
System.out.println("s0==s1:
"+(s0==s1));
12:
System.out.println("s0==s2:
"+(s0==s2));
13:
}
14:
}
这个例子包含了3个String型变量其中两个被赋值以常量表达式“Programming”另一个被赋值以一个新建的值为“Programming”的String类的实例使用equals(...)方法和“==”运算符进行比较产生了下列结果
s0.equals(s1):
true
s0.equals(s2):
true
s0==s1:
false
s0==s2:
true
1具体类也就是抽象方法所属抽象类的非抽象子类
第1部分语法
6t
String.equals()方法比较的是字符串的内容使用equals(...)方法会对字符串中的所有字符一个接一个地进行比较如果完全相等那么返回true在这种情况下全部个字符串都是相同的所以当字符串s0与s1或s2比较时我们得到的返回值均为true运算符“==” 比较的是String实例的引用在这种情况下很明显s0和s1并不是同一个String实例但s0和s2却是同一个读者也许会问s0和s2怎么是同一个对象呢这个问题的答案来自于
Java语言规范中关于字符串常量StringLiterals的章节本例中“Programming”“Program”和“ming”都是字符串常量1它们在编译期就被确定了当一个字符串由多个字符串常量连接而成时例如s2它同样在编译期就被确定为一个字符串常量Java确保一个字符串常量只有一份拷贝所以当“Programming”和“Program”+“ming”被确定为值相等时Java会设置两个变量的引用为同一个常量的引用在常量池constantpool中Java会跟踪所有的字符串常量
常量池指的是在编译期被确定并被保存在已编译的.class文件中的一些数据它包含了关于方法类接口等等当然还有字符串常量的信息当JVM装载了这个.class文件变量s0和s2被确定JVM执行了一项名为常量池解析constantpoolresolution的操作该项操作针对字符串的处理过程包括下列3个步骤摘自JVM规范5.4节n如果另一个常量池入口constantpoolentry被标记为CONSTANT_String2并且指出同样的Unicode字符序列已经被确定那么这项操作的结果就是为之前的常量池入口创建的String实例的引用n否则如果intern()方法已经被这个常量池描述的一个包含同样Unicode字符序列的String实例调用过了那么这项操作的结果就是那个相同String实例的引用n否则一个新的String实例会被创建它包含了CONSTANT_String入口描述的Unicode字符序列这个String实例就是该项操作的结果也就是说当常量池第一次确定一个字符串在Java内存栈中就创建一个String实例在常量池中后来的所有针对同样字符串内容的引用都会得到之前创建的String实例当JVM处理到第6行时它创建了字符串常量Programming的一份拷贝到另一个String实例中所以对s0和s1的引用的比较结果是false因为它们不是同一个对象这就是为何 s0==s1的操作在某些情况下与s0.equals(s1)不同s0==s1比较的是对象引用的值而s0.equals(s1)实际上执行的是字符串内容的比较
1摘自Java语言规范字符串常量由0个或多个包含在双引号之内的字符组成每个字符都可以用一个换码序列escapesequence表示
2它在.class内部使用用来识别字符串常量 Item2:
String.equals()方法与==运算符的用法比较
u7存在于.class文件中的常量池在运行期被JVM装载并且可以扩充此前提到的intern()方法针对String实例的这个意图提供服务当针对一个String实例调用了intern()方法intern()方法遵守前面概括的第3步以外的常量池解析规则因为实例已经存在而不需要另外创建一个新的所以已存在的实例的引用被加入到该常量池来看看另一个例子
01:
importjava.io.*;
02:
03:
publicclassStringExample2
04:
{
05:
publicstaticvoidmain(Stringargs[])
06:
{
07:
StringsFileName="test.txt";
08:
Strings0=readStringFromFile(sFileName);
09:
Strings1=readStringFromFile(sFileName);
10:
11:
System.out.println("s0==s1:
"+(s0==s1));
12:
System.out.println("s0.equals(s1):
"+(s0.equals(s1)));
13:
14:
s0.intern();
15:
s1.intern();
16:
17:
System.out.println("s0==s1:
"+(s0==s1));
18:
System.out.println("s0==s1.intern():
"+
19:
(s0==s1.intern()));
20:
}
21:
22:
privatestaticStringreadStringFromFile(StringsFileName)
23:
{
24:
//…readstringfromfile…
25:
}
26:
}
这个例子没有设置s0和s1的值为字符串常量取而代之的是在运行期它从一个文件中读取字符串并把值分配给readStringFromFile(...)方法创建的String实例从第9行开始程序对两个被新创建为具有同样字符值的String实例进行处理当你看到从第11行到12行的输出结果时你会再次注意到这两个对象并不是同一个但它们的内容是相同的输出结果如下
第1部分语法
8t
s0==s1:
false
s0.equals(s1):
true
s0==s1:
false
s0==s1.intern():
true
第14行所做的是将String实例的引用s0存入常量池当第15行被处理时对s1.intern()方法的调用会简单地返回引用s0这样一来第17行和18行的输出结果正是我们所期望的s0与s1仍旧是截然不同的两个String实例因此s0==s1的结果是false而s1.intern()返回的是常量池中的引用值即s0所以表达式s0==s1.intern()的结果是true假如我们希望将实例s1存入常量池中我们必须首先设置s0为null然后请求垃圾回收器garbage collector回收被指向s0的String实例在s0被回收后s1.intern()方法的调用将会把s1存入常量池 总的来说在执行等式比较equalitycomparison时应该始终使用String.equals(...)方法而不是==运算符如果你还是习惯性地使用==运算符那么intern()方法可以帮助你得到正确的答案因为当n和m均为String实例的引用时语句n.equals(m)与n.intern()==
m.intern()得到的结果是一致的假如你打算充分利用常量池的优势那么你就应该选择 String.intern()方法
Item3:
Java是强类型语言
每个Java开发人员都需要很好地理解Java所支持的基本数据类型primitivetype那么什么是陷阱呢它们与你以前所使用的语言有什么不同呢和大多数语言一样Java是强类型的stronglytype它支持8种基本数据类型这些基本数据类型算得上是构造对象的积木buildingblocks了通过对这些基本数据类型用法的严格检查Java编译器能够及时地在开发过程中捕捉到许多简单细微的错误大部分的开发人员都很熟悉基本数据类型以及与它们相关的值和操作不过你需要了解的是在Java中仍然有一些微妙之处与使用其它语言不同的是Java的基本数据类型始终被描述在JVM中因此开发人员就可以编写出不影响移植的代码这就使得位运算得以较安全地执行同时boolean型是不可转换的不像C或C++Java不允许你编写在boolean型和非boolean型之间转换的代码假如你曾经使用过上述语言那么你可能编写过一些诸如0等于false或非0等于true的“优雅”的代码
Item3:
Java是强类型语言u9在C语言中你可以像下面这样编写代码检查一个函数的返回值
value=get_value();
if(value)do_something;
然而类似的代码在Java中是不能编译的条件语句只能接收boolean型的值所以你
必须给它一个这样的值
value=getValue();
if(value!
=null)doSomething;
类型转换
在Java语言中由于基本数据类型的转换可以隐性地发生所以你需要理解类型转换何时会发生以及如何工作非boolean型数据之间的转换是合理的而且一般来说当你的代码可能会导致精度损失lossofprecision时编译器会向你发出警告Java的算术运算arithmeticoperation也经常会导致与其它语言一样的潜在问题大多数开发人员都曾经写过导致数据意外切断accidentaltruncation的代码举个例子下面的 Truncation类的第10行将会输出“2.0”而不是11行与12行输出的“2.4”
01:
publicclassTruncation
02:
{
03:
staticvoidprintFloat(floatf)
04:
{
05:
System.out.println("f="+f);
06:
}
07:
08:
publicstaticvoidmain(String[]args)
09:
{
10:
printFloat(12/5);//datalost!
11:
printFloat((float)12/5);
12:
printFloat(12/5.0f);
13:
}
14:
}
因为12和5都是integer型第10行中表达式的结果类型就是integer型因此小数部分丢失了事实上当intFloat方法并没有得到期望的float型参数时切断就已经发生了修复很简单只要表达式中的任何一个值是float型那么另一个也会被提升promote为float型所以第11行和12行操作正常扩展当基本类型的值能够在不损失数值的情况下被转换时转换操作会自动发生在这些情况下的转换被称作扩展之所以这么称呼是因为它们都会被转换成能够存储较大数据的类型例如可以为int变量分配一个byte值因为这不会引起数值或精度的损失图1.1展示了扩展转换括号内的数字是用来存储每种类型所需的比特数当一个类型被转换为具有更多比特数的类型时它不会损失任何信息
图1.1扩展转换
注意假如把一个int型或long型转换成为float型或者把一个long型转换成为double型可能会损失一些精度换句话说就是一些最不重要的位可能会丢失这种提升是隐性发生的下面这个例子的输出结果-46显示了在编译器没有任何警告的情况下发生的精度损失
publicclassLostPrecision
{
publicstaticvoidmain(String[]args)
{
intorig=1234567890;
floatapprox=orig;
introunded=(int)approx;//lostprecision
System.out.println(orig-rounded);
}
}
Java语言规范5.1.2节中对扩展转换有更详细的描述“尽管事实上精度损失可能会发生但是基本类型之间的扩展转换永远不会导致运行期异常”
窄化
窄化narrowing转换即任何不同于图1.1中从左到右顺序的转换能够导致信息损失例如如果将浮点型包括float型和double型数据转换成整型包括shortint和long型或者冒着溢出overflow的危险将long型转换为short型都会得到编译期错误通过加入显性的强制转换cast可以避免这种错误它会告诉编译器“我清楚我在干什么并且愿意承担可能产生的风险”
Item3:
Java是强类型语言
u11
隐性类型转换
顾名思义隐性类型转换不需要显性的强制转换运算符而是自动发生的它仅仅发生在扩展转换的情况下为了方便当变量是bytesh
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据 Java 学习心得 犯错误