Java作为一门优秀的面向对象的程序设计语言,正在被越来越多的人使用。本文试图列出作者在实际开发中碰到的一些Java语言的容易被人忽视的细节,希望能给正在学习Java语言的人有所帮助。
1,位移运算越界怎么处理
考察下面的代码输出结果是多少?
int a=5;
System.out.println(a < <33);
按照常理推测,把a左移33位应该将a的所有有效位都移出去了,那剩下的都是零啊,所以输出结果应该是0才对啊,可是执行后发现输出结果是10,为什么呢?因为Java语言对位移运算作了优化处理,Java语言对a < <b转化为a < <(b%32)来处理,所以当要移位的位数b超过32时,实际上移位的位数是b%32的值,那么上面的代码中a < <33相当于a < <1,所以输出结果是10。
2,可以让i!=i吗?
当你看到这个命题的时候一定会以为我疯了,或者Java语言疯了。这看起来是绝对不可能的,一个数怎么可能不等于它自己呢?或许就真的是Java语言疯了,不信看下面的代码输出什么?
double i=0.0/0.0;
if(i==i){
System.out.println("Yes i==i");
}else{
System.out.println("No i!=i");
}
上面的代码输出"No i!=i",为什么会这样呢?关键在0.0/0.0这个值,在IEEE 754浮点算术规则里保留了一个特殊的值用来表示一个不是数字的数量。这个值就是NaN("Not a Number"的缩写),对于所有没有良好定义的浮点计算都将得到这个值,比如:0.0/0.0;其实我们还可以直接使用Double.NaN来得到这个值。在IEEE 754规范里面规定NaN不等于任何值,包括它自己。所以就有了i!=i的代码。
3,怎样的equals才安全?
我们都知道在Java规范里定义了equals方法覆盖的5大原则:reflexive(反身性),symmetric(对称性),transitive(传递性),consistent(一致性),non-null(非空性)。那么考察下面的代码:
public class Student{
private String name;
private int age;
public Student(String name,int age){
this.name=name;
this.age=age;
}
public boolean equals(Object obj){
if(obj instanceof Student){
Student s=(Student)obj;
if(s.name.equals(this.name) && s.age==this.age){
return true;
}
}
return super.equals(obj);
}
}
你认为上面的代码equals方法的覆盖安全吗?表面看起来好像没什么问题,这样写也确实满足了以上的五大原则。但其实这样的覆盖并不很安全,假如Student类还有一个子类CollegeStudent,如果我拿一个Student对象和一个CollegeStudent对象equals,只要这两个对象有相同的name和age,它们就会被认为相等,但实际上它们是两个不同类型的对象啊。问题就出在instanceof这个运算符上,因为这个运算符是向下兼容的,也就是说一个CollegeStudent对象也被认为是一个Student的实例。怎样去解决这个问题呢?那就只有不用instanceof运算符,而使用对象的getClass()方法来判断两个对象是否属于同一种类型,例如,将上面的equals()方法修改为:
public boolean equals(Object obj){
if(obj.getClass()==Student.class){
Student s=(Student)obj;
if(s.name.equals(this.name) && s.age==this.age){
return true;
}
}
return super.equals(obj);
}
这样才能保证obj对象一定是Student的实例,而不会是Student的任何子类的实例。
4,浅复制与深复制
1)浅复制与深复制概念
⑴浅复制(浅克隆)
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
⑵深复制(深克隆)
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
2)Java的clone()方法
⑴clone方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:
①对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象
②对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样
③如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
⑵Java中对象的克隆
①为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
②在派生类中覆盖基类的clone()方法,并声明为public。
③在派生类的clone()方法中,调用super.clone()。
④在派生类中实现Cloneable接口。
请看如下代码:
class Student implements Cloneable{
String name;
int age;
Student(String name,int age){
this.name=name;
this.age=age;
}
public Object clone(){
Object obj=null;
try{
obj=(Student)super.clone();
//Object中的clone()识别出你要复制的是哪一个对象。
}
catch(CloneNotSupportedException e){
e.printStackTrace();
}
return obj;
}
}
public static void main(String[] args){
Student s1=new Student("zhangsan",18);
Student s2=(Student)s1.clone();
s2.name="lisi";
s2.age=20;
System.out.println("name="+s1.name+","+"age="+s1.age);//修改学生2
//后,不影响学生1的值。
}
说明:
①为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
②继承自java.lang.Object类的clone()方法是浅复制。以下代码可以证明之。
class Teacher{
String name;
int age;
Teacher(String name,int age){
this.name=name;
this.age=age;
}
}
class Student implements Cloneable{
String name;
int age;
Teacher t;//学生1和学生2的引用值都是一样的。
Student(String name,int age,Teacher t){
this.name=name;
this.age=age;
this.t=t;
}
public Object clone(){
Student stu=null;
try{
stu=(Student)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
stu.t=(Teacher)t.clone();
return stu;
}
public static void main(String[] args){
Teacher t=new Teacher("tangliang",30);
Student s1=new Student("zhangsan",18,t);
Student s2=(Student)s1.clone();
s2.t.name="tony";
s2.t.age=40;
System.out.println("name="+s1.t.name+","+"age="+s1.t.age);
//学生1的老师成为tony,age为40。
}
}
那应该如何实现深层次的克隆,即修改s2的老师不会影响s1的老师?代码改进如下。
class Teacher implements Cloneable{
String name;
int age;
Teacher(String name,int age){
this.name=name;
this.age=age;
}
public Object clone(){
Object obj=null;
try{
obj=super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return obj;
}
}
class Student implements Cloneable{
String name;
int age;
Teacher t;
Student(String name,int age,Teacher t){
this.name=name;
this.age=age;
this.t=t;
}
public Object clone(){
Student stu=null;
try{
stu=(Student)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
stu.t=(Teacher)t.clone();
return stu;
}
}
public static void main(String[] args){
Teacher t=new Teacher("tangliang",30);
Student s1=new Student("zhangsan",18,t);
Student s2=(Student)s1.clone();
s2.t.name="tony";
s2.t.age=40;
System.out.println("name="+s1.t.name+","+"age="+s1.t.age);
//学生1的老师不改变。
}
3)利用串行化来做深复制
把对象写到流里的过程是串行化(Serilization)过程,Java程序员又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
如下为深复制源代码。
public Object deepClone(){
//将对象写到流里
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//从流里读出来
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将之排除在复制过程之外。上例代码改进如下。
class Teacher implements Serializable{
String name;
int age;
Teacher(String name,int age){
this.name=name;
this.age=age;
}
}
class Student implements Serializable
{
String name;//常量对象。
int age;
Teacher t;//学生1和学生2的引用值都是一样的。
Student(String name,int age,Teacher t){
this.name=name;
this.age=age;
this.p=p;
}
public Object deepClone() throws IOException,
OptionalDataException,ClassNotFoundException
{
//将对象写到流里
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//从流里读出来
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
}
public static void main(String[] args){
Teacher t=new Teacher("tangliang",30);
Student s1=new Student("zhangsan",18,t);
Student s2=(Student)s1.deepClone();
s2.t.name="tony";
s2.t.age=40;
System.out.println("name="+s1.t.name+","+"age="+s1.t.age);
//学生1的老师不改变。
}
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/dichyzhu/archive/2009/05/22/4208156.aspx
分享到:
相关推荐
Java编程语言以其强大的跨平台能力和丰富的库而广受开发者...以上就是Java编程中一些值得注意的细节,熟练掌握这些知识点,能够让你的Java代码更加健壮、高效。在实践中不断学习和总结,你会成为更优秀的Java开发者。
在深入探讨Java开发注意事项之前,我们首先应当澄清,给定的部分内容似乎包含了非文本或乱码信息...以上注意事项涵盖了从编码规范到系统架构的多个方面,对于任何Java开发者而言,都是值得深入了解和实践的关键知识点。
值得注意的是,一旦字符串的编码被正确转换并存储,就不应该再对其进行重复的编码转换。例如,不应将已转换为`gbk`编码的字符串再次尝试转换为`gbk`,这可能会引入不必要的复杂性和潜在的错误。 #### 5. 优化编码...
值得注意的是,`Collator`的排序规则是基于特定地区的,所以如果你需要处理其他语言,如英文,可能需要改变`Locale`参数。此外,`setStrength()`的参数可以调整为`SECONDARY`或`TERTIARY`,以控制排序的精确度。 在...
### 解决Java_heap_space问题:深入理解与策略 在Java应用程序开发与运行过程中,经常会遇到一...然而,值得注意的是,内存配置并非一劳永逸,应根据应用的实际运行情况和服务器资源动态调整,以达到最佳的性能平衡。
在JAVA编程中,有许多重要的细节值得开发者关注。本文将根据提供的部分描述,深入解析JAVA开发过程中需要注意的关键知识点,并针对这些知识点进行详细说明。 #### 1. String 对象的理解与操作 - **String 的不可...
值得注意的是,`java.lang`包总是默认导入,无需显式使用`import`语句。此包包含了构成Java编程基础的类和接口,如`Object`、`String`、基本数据类型包装类等,是Java程序不可或缺的一部分。理解并熟练运用包机制,...
值得注意的是,`C:\Program Files\Broadcom\Broadcom802.11NetworkAdapter\Driver`这样的路径出现在`Path`变量中可能是因为某些硬件驱动或应用软件的特殊需求,与Java环境配置关系不大。在进行Java环境配置时,应...
值得注意的是,`regionId`属性除了有`set`和`get`方法外,还存在一个`regionIdSpecified`布尔属性,其作用在于指示在SOAP消息中是否应该包含`regionId`字段。这是因为某些WebServices在处理请求时,会根据字段是否被...
值得注意的是:自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如 int a = 3;这里的 a 是一个指向 int 类型的引用,指向 3这个字面值。这些字面值的数据,由于大小可知,生存期可知,...
值得注意的是,JDIC的使用可能受到操作系统和Java版本的影响,因为它依赖于操作系统的原生浏览器引擎(例如Windows上的IE,Mac OS上的WebKit)。因此,在某些系统上可能需要额外的配置或者可能会遇到兼容性问题。 ...
然而,值得注意的是,并非所有任务都会从并行化中受益,在某些情况下,由于管理多个线程的开销,它甚至可能会降低性能。 11. 如何在 Stream 中应用所需的逻辑和操作? 过滤方法。享受 Java 8 流之旅,并不断探索更...
值得注意的是,该示例中的`catch`块没有包含任何处理异常的逻辑,这意味着如果确实发生了异常,程序将不会采取任何措施直接继续执行。这种做法在实际开发中并不常见,通常我们会利用`catch`块来处理异常,比如向用户...
Java简历是展示求职者技能、经验和技术能力的重要文档,尤其对于Java开发者来说,一份精心制作的简历能够有效地吸引潜在雇主的注意。以下是一些从标题和描述中提取的关键知识点,以及如何利用这些知识点来创建一个...
值得注意的是,从Java 9开始,JavaFX被移出JDK的核心部分,成为一个独立的模块,但在Java 8中,它还是内置的,这正是"jfx"标签所指的内容。 在压缩包中,"java"文件可能包含了Java 1.8的完整安装程序或者特定组件,...
值得注意的是,转换过程中可能会遇到一些问题,比如内存限制、PDF加密或者包含复杂的图形和字体。对于内存限制,你可以考虑分页转换或者调整图像质量以减小内存占用。对于加密的PDF,你需要提供正确的密码才能进行...
然而,值得注意的是,Java2Pas可能无法完美地处理所有Java特性,比如泛型、反射、线程等高级特性,这些在转换后可能需要额外的代码调整。此外,由于Delphi的接口(interface)和Java的接口概念有所不同,转换后可能...
值得注意的是,反编译他人软件的源代码可能涉及到版权问题,所以在使用反编译工具时应确保遵循合法合规的原则。 总之,Java反编译工具是Java开发者的重要辅助工具,它可以帮助我们理解字节码,提高代码分析和调试的...
值得注意的是,由于C语言的复杂性和灵活性,以及Java的一些特性(如自动内存管理),并非所有的C代码都能被完美地转换。比如,C语言中的位运算、inline函数、预处理器宏等可能需要人工干预才能在Java中实现相同的...