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。 } } |
<script type="text/javascript"></script>那应该如何实现深层次的克隆,即修改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的老师不改变。 }
|
分享到:
相关推荐
【Java语言的26个细节】涵盖了Java编程中的一些易被忽视或误解的要点,以下是对其中几个细节的详细说明: 1. **位移运算越界处理**:Java中的位移运算符`和`>>`在处理整型数据时,会进行优化。对于左移运算`a,当...
9. **关键字**:列举了Java语言中的保留字,这些关键字具有特殊的含义,不能用作标识符。 10. **字面量** - **整数字面量**:介绍整数字面量的不同进制表示方法,包括十进制、八进制、十六进制等。 - **浮点数字面...
本文将探讨一些在实际开发中容易被忽视的Java语言细节,帮助开发者更好地理解和应用Java。 首先,我们来看位移运算的问题。在Java中,位移运算符`用于进行二进制位的左移操作。在处理`a时,Java会进行优化,将其...
### Java语言的26个细节解析 #### 一、数据类型自动提升与精度损失 Java中的数据类型在进行运算时遵循自动提升规则:`byte` -> `short` -> `int` -> `long` -> `float` -> `double`,以及 `char` -> `int`。这一...
本文探讨了Java语言研发中常见的问题,并针对这些问题提出了相应的解决措施。 首先,Java编程语言在研发过程中常常遇到中文字符显示和编码的问题。由于Java原生采用Unicode编码格式,而在实际应用中,计算机的本地...
在Java语言的学习中,以下几个核心知识点不容忽视: 1. **Java基础**:Java的基础语法是学习的起点,包括数据类型(如整型、浮点型、字符型和布尔型)、变量声明、运算符、流程控制(如if语句、switch语句、for循环...
异常处理是Java编程中不可忽视的一部分。Java使用try-catch-finally结构来捕获和处理运行时错误。这有助于编写健壮的代码,防止程序因未预期的错误而中断。 Java还提供了强大的集合框架,包括List、Set和Map接口,...
这本书通过一系列精心设计的编程谜题,揭示了Java语言中容易被忽视的陷阱和误解。这些谜题覆盖了类加载、内存模型、类型转换、异常处理、多线程等众多主题,旨在帮助开发者避开常见的编程误区,提升代码质量。在提供...
Java语言程序设计是计算机科学领域中的一个重要主题,它是一种广泛使用的高级编程语言,以其“一次编写,到处运行”的跨平台特性而闻名。本教程旨在为初学者和有经验的程序员提供一个全面的学习资源,帮助他们深入...
Java语言程序设计是计算机科学领域中的一个重要主题,尤其对于初学者和职业开发人员而言,它是构建强大、可移植和高效软件的基础。Java以其"一次编写,到处运行"的特性而闻名,这得益于其跨平台的Java虚拟机(JVM)...
根据提供的描述,本文将围绕 Java 语言的一些基础而重要的知识点进行深入探讨,特别是那些容易被忽略但又极其关键的细节。这些内容不仅涵盖了 Java 语言本身的特点,还涉及到 Java 虚拟机(JVM)的相关机制,对于理解...
在 java 语言中,Java 程序的基本单位是类,也就是说:一个 Java 程序是由多个类组成 的。定义一个类与定义一个数据类型是有区别的。在程序设计语言中,把定义数据类型的能 力作为一种很重要的能力来对待。在面向...
这份课件涵盖了从Java语言基础到实际应用开发的全方位内容。 在学习Java编程的过程中,首先会接触到的是Java的语法基础,包括数据类型(如整型、浮点型、字符型和布尔型)、变量、运算符以及控制结构(如if语句、...
本文将采用轻松幽默的方式,为大家揭示那些在开发中可能被忽略的Java语法细节,帮助大家更好地理解和掌握Java。 #### 十六进制的趣事 在计算机科学领域中,十六进制是一种常用的数字表示方法,它以16为基数,由0-9...
### Java语言编码规范详解 #### 一、引言 在软件开发过程中,编码规范的重要性不容忽视。据统计,软件在其整个生命周期中的维护成本大约占到总成本的80%。因此,建立并遵循一套统一的编码规范对于提高软件的可读性...
### Java编写中容易搞错的一些东西 #### 1....这些知识点涵盖了Java编程中一些容易被忽略或者理解错误的概念和细节,对于开发者来说非常重要。理解和掌握这些内容有助于编写更加健壮、高效的代码。
### JAVA中汉字字符转化为英文字符 #### 知识点概览 本文将详细介绍如何在Java中实现汉字到英文字符的转换。此技术主要用于提取汉字的首字母或进行其他基于字符编码的操作。通过以下两个核心方法:`toTureAsciiStr`...