夜光:Java成神之路(二)擅长的语言

夜光序言:

命中注定的人,并不是注定与之相爱、结合、交配的人。
命中注定的人,是为了令你成为你注定成为的样子而出现的人。

 
 
正文:
 
                                              以道御术 / 以术识道



 

3.3.2 深拷贝和浅拷贝

 
上面的示例代码中,Person 中有两个成员变量,分别是 name 和 age, name 是 String 类型, age 是 int 类型。代码非常简单,如下所示:
 
public class Person implements Cloneable{
   privat int age ;
   private String name;
 public Person(int age, String name) {
  this.age = age;
  this.name = name;
  }
 public Person() {}
 public int getAge() {
  return age;
  }
  public String getName() {
return name;
  }
  @Override
  protected Object clone() throws CloneNotSupportedException {
  return (Person)super.clone();
 }
} 

 

 
 
 
由于 age 是基本数据类型, 那么对它的拷贝没有什么疑议,直接将一个 4 字节的整数值拷贝过来就行。
 
但是 name 是 String 类型的, 它只是一个引用, 指向一个真正的 String 对象,那么对它的拷贝有两种方式:
直接将原对象中的 name 的引用值拷贝给新对象的 name 字段,
或者是根据原 Person 对象中的 name 指向的字符串对象创建一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的 Person 对象的 name 字段。

 

 

这两种拷贝方式分别叫做浅拷贝和深拷贝。深拷贝和浅拷贝的原理如下图所示:

下面通过代码进行验证。

 
如果两个 Person 对象的 name 的地址值相同, 说明两个对象的 name 都指向同一个 String 对象,也就是浅拷贝, 而如果两个对象的 name 的地址值不同, 那么就说明指向不同的 String 对象, 也就 是在拷贝 Person 对象的时候, 同时拷贝了 name 引用的 String 对象, 也就是深拷贝。
 

验证代码如下:

 
 Person p = new Person(23, "zhang");
 Person p1 = (Person) p.clone();
 String result = p.getName() == p1.getName() ? "clone 是浅拷贝的" : "clone 是深拷贝的";
 System.out.println(result); 

 

 
 

打印结果为:


clone 是浅拷贝的 
 
所以,clone 方法执行的是浅拷贝, 在编写程序时要注意这个细节。

如何进行深拷贝:

由上一节的内容可以得出如下结论:如果想要深拷贝一个对象,这个对象必须要实现 Cloneable 接口,实现 clone方法,并且在 clone 方法内部, 把该对象引用的其他对象也要 clone 一份,这就要求这个被引用的对象必须也要实现 Cloneable 接口并且实现 clone 方法。
 
 
那么,按照上面的结论,实现以下代码 Body 类组合了 Head 类,要想深拷贝 Body 类,必须在 Body 类的 clone 方法中将 Head 类也要拷贝一份。
 

 

代码如下:

1.static class Body implements Cloneable{
2. public Head head;
3. public Body() {}
4. public Body(Head head) {this.head = head;}
5. @Override
6. protected Object clone() throws CloneNotSupportedException {
7. Body newBody = (Body) super.clone();
8. newBody.head = (Head) head.clone();
9. return newBody;
10. }
11.}
12.static class Head implements Cloneable{
13. public Face face;
14. public Head() {}
15. @Override
16. protected Object clone() throws CloneNotSupportedException {
17. return super.clone();
18. } }
19.public static void main(String[] args) throws CloneNotSupportedException {
20. Body body = new Body(new Head(new Face()));
21. Body body1 = (Body) body.clone();
22. System.out.println("body == body1 : " + (body == body1) );
23. System.out.println("body.head == body1.head : " + (body.head == body1.head));
24.} 

 

打印结果为:


1. body == body1 : false
2. body.head == body1.head : false

二、JavaSE 语法

1. Java 有没有 goto 语句?

goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。
 
根据 James Gosling(Java 之父)编写的《The Java Programming Language》一书的附录中给出了一个 Java 关键字列表
 
 
其中有 goto 和 const,但是这两个是目前 无法使用的关键字,因此有些地方将其称之为保留字,其实保留字这个词应该有更广泛的意义,因为熟悉 C 语言的程 序员都知道,在系统类库中使用过的有特殊意义的单词或单词的组合都被视为保留字

2. & 和 && 的区别

&运算符有两种用法:(1)按位与;(2)逻辑与。
 
&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true 整个表达式的值才是 true。
 
&&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。
 
 
 
很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是 null 而且不是空字符串,应 当写为 username != null &&!username.equals(""),二者的顺序不能交换,更不能用&运算符
 
 
因为第一个条件如 果不成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。

 

注意:逻辑或运算符(|) 和短路或运算符(||)的差别也是如此。


 

3. 在 Java 中,如何跳出当前的多重嵌套循环

 

在最外层循环前加一个标记如 A,然后用 break A;可以跳出多重循环。

 
(Java 中支持带标签的 break 和 continue语句,作用有点类似于 C 和 C++中的 goto 语句
 
但是就像要避免使用 goto 一样,应该避免使用带标签的 break 和 continue
 
因为它不会让你的程序变得更优雅,很多时候甚至有相反的作用)
 
 

4. 两个对象值相同 (x.equals(y) == true) ,但却可有不同的 hashCode,这句话对不对?

不对,如果两个对象 x 和 y 满足 x.equals(y) == true,它们的哈希码(hashCode)应当相同。
 
Java 对于 eqauls 方法和 hashCode 方法是这样规定的:
 
(1)如果两个对象相同(equals 方法返回 true),那么它们的 hashCode 值一定要相同;
 
(2)如果两个对象的 hashCode 相同,它们并不一定相同。
 
 
当然,你未必要按照 要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在 Set 集合中,同时增加新元素 的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。
 
 
关于 equals 和 hashCode 方法,很多 Java 程序员都知道,但很多人也就是仅仅知道而已,在 Joshua Bloch 的大作《Effective Java》(很多软件公司,《Effective Java》、《Java 编程思想》以及《重构:改善既有代码质量》

夜光:

是 Java 程序员必看书籍,如果你还没看过,那就赶紧去看电子书或者买............,中是这样介绍 equals 方法的。

 
首先 equals 方法必须满足自反性(x.equals(x)必须返回 true)、对称性(x.equals(y)返回 true 时,y.equals(x)也必须返回 true)、传递性(x.equals(y)和 y.equals(z)都返回 true 时,x.equals(z)也必须返回 true)和一致性(当 x 和 y 引用的对象信息没有被修改时,多次调用 x.equals(y)应该得到同样的返回值),而且对于任何非 null 值的引 用 x,x.equals(null)必须返回 false。
 
 

实现高质量的 equals 方法的诀窍包括:

 

1. 使用==操作符检查"参数是否为这个 对象的引用";
 
2. 使用 instanceof 操作符检查"参数是否为正确的类型";
 
3. 对于类中的关键属性,检查参数传入对象 的属性是否与之相匹配;
 
4. 编写完 equals 方法后,问自己它是否满足对称性、传递性、一致性;
 
5. 重写 equals 时 总是要重写 hashCode;
 
6. 不要将 equals 方法参数中的 Object 对象替换为其他的类型,在重写时不要忘掉 @Override 注解。

 

5. 是否可以继承 String

 

String 类是 final 类,不可以被继承

继承 String 本身就是一个错误的行为,对 String 类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)


 

6. 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

是值传递。

 
Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。
 
对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。
 
C++ 和 C#中可以通过传引用或传输出参数来改变传入的参数的值。
 
说明:Java 中没有传引用实在是非常的不方便,这一 点在 Java 8 中仍然没有得到改进,正是如此在 Java 编写的代码中才会出现大量的 Wrapper 类(将需要通过方法 调用修改的引用置于一个 Wrapper 类中,再将 Wrapper 对象传入方法)
 

 

这样的做法只会让代码变得臃肿,尤其是让从 C 和 C++转型为 Java 程序员的开发者无法容忍。


 

7. 重载(overload)和重写(override)的区别?重载的方法能否根据返回类型进行区分?

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
 
 
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为 重载;
 
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方 法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
 
重载对返回类型没有特殊的要求。
 

方法重载的规则:

 
1.方法名一致,参数列表中参数的顺序,类型,个数不同。
 
2.重载与方法的返回值无关,存在于父类和子类,同类中。
 
3.可以抛出不同的异常,可以有不同修饰符。
 
 
 
 
 

发布了1529 篇原创文章 · 获赞 305 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/weixin_41987706/article/details/105510044
今日推荐