北京网站搭建服务商,门头沟高端网站建设,制作一个企业网站过程,做的好看的网站转载自达内论坛
作者#xff1a;达内唐亮 Java作为一门优秀的面向对象的程序设计语言,正在被越来越多的人使用。本文试图列出作者在实际开发中碰到的一些Java语言的容易被人忽视的细节,希望能给正在学习Java语言的人有所帮助。 1#xff0c;位移运算越界怎么处理 考察下…转载自达内论坛
作者达内唐亮 Java作为一门优秀的面向对象的程序设计语言,正在被越来越多的人使用。本文试图列出作者在实际开发中碰到的一些Java语言的容易被人忽视的细节,希望能给正在学习Java语言的人有所帮助。 1位移运算越界怎么处理 考察下面的代码输出结果是多少? int a5; System.out.println(a33); 按照常理推测,把a左移33位应该将a的所有有效位都移出去了那剩下的都是零啊所以输出结果应该是0才对啊可是执行后发现输出结果是10为什么呢因为Java语言对位移运算作了优化处理Java语言对ab转化为a(b%32)来处理所以当要移位的位数b超过32时实际上移位的位数是b%32的值那么上面的代码中a33相当于a1所以输出结果是10。 2可以让i!i吗 当你看到这个命题的时候一定会以为我疯了或者Java语言疯了。这看起来是绝对不可能的一个数怎么可能不等于它自己呢或许就真的是Java语言疯了不信看下面的代码输出什么 double i0.0/0.0; if(ii){ System.out.println(Yes ii); }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不等于任何值包括它自己。所以就有了ii的代码。 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.namename; this.ageage; } public boolean equals(Object obj){ if(obj instanceof Student){ Student s(Student)obj; if(s.name.equals(this.name) s.agethis.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.agethis.age){ return true; } } return super.equals(obj); } 这样才能保证obj对象一定是Student的实例而不会是Student的任何子类的实例。 4浅复制与深复制 1浅复制与深复制概念 ⑴浅复制浅克隆 被复制对象的所有变量都含有与原来的对象相同的值而所有的对其他对象的引用仍然指向原来的对象。换言之浅复制仅仅复制所考虑的对象而不复制它所引用的对象。 ⑵深复制深克隆 被复制对象的所有变量都含有与原来的对象相同的值除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象而不再是原有的那些被引用的对象。换言之深复制把要复制的对象所引用的对象都复制了一遍。 2Java的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.namename; this.ageage; } public Object clone(){ Object objnull; try{ obj(Student)super.clone(); //Object中的clone()识别出你要复制的是哪一个对象。 } catch(CloneNotSupportedException e){ e.printStackTrace(); } return obj; } } public static void main(String[] args){ Student s1new Student(zhangsan,18); Student s2(Student)s1.clone(); s2.namelisi; s2.age20; System.out.println(names1.name,ages1.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.namename; this.ageage; } } class Student implements Cloneable{ String name; int age; Teacher t;//学生1和学生2的引用值都是一样的。 Student(String name,int age,Teacher t){ this.namename; this.ageage; this.tt; } public Object clone(){ Student stunull; try{ stu(Student)super.clone(); }catch(CloneNotSupportedException e){ e.printStackTrace(); } stu.t(Teacher)t.clone(); return stu; } public static void main(String[] args){ Teacher tnew Teacher(tangliang,30); Student s1new Student(zhangsan,18,t); Student s2(Student)s1.clone(); s2.t.nametony; s2.t.age40; System.out.println(names1.t.name,ages1.t.age); //学生1的老师成为tony,age为40。 } } 那应该如何实现深层次的克隆即修改s2的老师不会影响s1的老师代码改进如下。 class Teacher implements Cloneable{ String name; int age; Teacher(String name,int age){ this.namename; this.ageage; } public Object clone(){ Object objnull; try{ objsuper.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.namename; this.ageage; this.tt; } public Object clone(){ Student stunull; try{ stu(Student)super.clone(); }catch(CloneNotSupportedException e){ e.printStackTrace(); } stu.t(Teacher)t.clone(); return stu; } } public static void main(String[] args){ Teacher tnew Teacher(tangliang,30); Student s1new Student(zhangsan,18,t); Student s2(Student)s1.clone(); s2.t.nametony; s2.t.age40; System.out.println(names1.t.name,ages1.t.age); //学生1的老师不改变。 } 3利用串行化来做深复制 把对象写到流里的过程是串行化Serilization过程Java程序员又非常形象地称为“冷冻”或者“腌咸菜picking”过程而把对象从流中读出来的并行化Deserialization过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出的是写在流里的是对象的一个拷贝而原对象仍然存在于JVM里面因此“腌成咸菜”的只是对象的一个拷贝Java咸菜还可以回鲜。 在Java语言里深复制一个对象常常可以先使对象实现Serializable接口然后把对象实际上只是对象的一个拷贝写到一个流里腌成咸菜再从流里读出来把咸菜回鲜便可以重建对象。 如下为深复制源代码。 public Object deepClone(){ //将对象写到流里 ByteArrayOutoutStream bonew ByteArrayOutputStream(); ObjectOutputStream oonew ObjectOutputStream(bo); oo.writeObject(this); //从流里读出来 ByteArrayInputStream binew ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oinew ObjectInputStream(bi); return(oi.readObject()); } 这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的否则就需要仔细考察那些不可串行化的对象可否设成transient从而将之排除在复制过程之外。上例代码改进如下。 class Teacher implements Serializable{ String name; int age; Teacher(String name,int age){ this.namename; this.ageage; } } class Student implements Serializable { String name;//常量对象。 int age; Teacher t;//学生1和学生2的引用值都是一样的。 Student(String name,int age,Teacher t){ this.namename; this.ageage; this.pp; } public Object deepClone() throws IOException, OptionalDataException,ClassNotFoundException { //将对象写到流里 ByteArrayOutoutStream bonew ByteArrayOutputStream(); ObjectOutputStream oonew ObjectOutputStream(bo); oo.writeObject(this); //从流里读出来 ByteArrayInputStream binew ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oinew ObjectInputStream(bi); return(oi.readObject()); } } public static void main(String[] args){ Teacher tnew Teacher(tangliang,30); Student s1new Student(zhangsan,18,t); Student s2(Student)s1.deepClone(); s2.t.nametony; s2.t.age40; System.out.println(names1.t.name,ages1.t.age); //学生1的老师不改变。 } 5String类和对象池 我们知道得到String对象有两种办法 String str1hello; String str2new String(hello); 这两种创建String对象的方法有什么差异吗当然有差异差异就在于第一种方法在对象池中拿对象第二种方法直接生成新的对象。在JDK5.0里面Java虚拟机在启动的时候会实例化9个对象池这9个对象池分别用来存储8种基本类型的包装类对象和String对象。当我们在程序中直接用双引号括起来一个字符串时JVM就到String的对象池里面去找看是否有一个值相同的对象如果有就拿现成的对象如果没有就在对象池里面创建一个对象并返回。所以我们发现下面的代码输出true String str1hello; String str2hello; System.out.println(str1str2); 这说明str1和str2指向同一个对象因为它们都是在对象池中拿到的而下面的代码输出为false: String str3hello String str4new String(hello); System.out.println(str3str4); 因为在任何情况下只要你去new一个String对象那都是创建了新的对象。 与此类似的在JDK5.0里面8种基本类型的包装类也有这样的差异 Integer i15;//在对象池中拿 Integer i2 5;//所以i1i2 Integer i3new Integer(5);//重新创建新对象所以i2!i3 对象池的存在是为了避免频繁的创建和销毁对象而影响系统性能那我们自己写的类是否也可以使用对象池呢当然可以考察以下代码 class Student{ private String name; private int age; private static HashSetStudent poolnew HashSetStudent();//对象池 public Student(String name,int age){ this.namename; this.ageage; } //使用对象池来得到对象的方法 public static Student newInstance(String name,int age){ //循环遍历对象池 for(Student stu:pool){ if(stu.name.equals(name) stu.ageage){ return stu; } } //如果找不到值相同的Student对象则创建一个Student对象 //并把它加到对象池中然后返回该对象。 Student stunew Student(name,age); pool.add(stu); return stu; } } public class Test{ public static void main(String[] args){ Student stu1Student.newInstance(tangliang,30);//对象池中拿 Student stu2Student.newInstance(tangliang,30);//所以stu1stu2 Student stu3new Student(tangliang,30);//重新创建所以stu1!stu3 System.out.println(stu1stu2); System.out.println(stu1stu3); } } _____________________________________________________________ 62.0-1.10.9吗? 考察下面的代码 double a2.0b1.1c0.9 if(a-bc){ System.out.println(YES!); }else{ System.out.println(NO!); } 以上代码输出的结果是多少呢你认为是“YES”吗那么很遗憾的告诉你不对Java语言再一次cheat了你以上代码会输出“NO”。为什么会这样呢其实这是由实型数据的存储方式决定的。我们知道实型数据在内存空间中是近似存储的所以2.0-1.1的结果不是0.9而是0.88888888889。所以在做实型数据是否相等的判断时要非常的谨慎。一般来说我们不建议在代码中直接判断两个实型数据是否相等如果一定要比较是否相等的话我们也采用以下方式来判断 if(Math.abs(a-b)1e-5){ //相等 }else{ //不相等 } 上面的代码判断a与b之差的绝对值是否小于一个足够小的数字如果是则认为a与b相等否则不相等。 7判断奇数 以下的方法判断某个整数是否是奇数考察是否正确 public boolean isOdd(int n){ return (n%21); } 很多人认为上面的代码没问题但实际上这段代码隐藏着一个非常大的BUG当n的值是正整数时以上的代码能够得到正确结果但当n的值是负整数时以上方法不能做出正确判断。例如当n-3时以上方法返回false。因为根据Java语言规范的定义Java语言里的求余运算符%得到的结果与运算符左边的值符号相同所以-3%2的结果是-1而不是1。那么上面的方法正确的写法应该是 public boolean isOdd(int n){ return (n%2!0); }