Java 问答:终极父类(第一部分)

Java的一些特性会让初学者感到困惑,但在有经验的开发者眼中,倒是合情合理的。例如,新手可能不会理解Object类。这篇文章分红三个部分讲跟Object类及其方法有关的问题。 html

上帝类

问:什么是Object类? java

答:Object类存储在java.lang包中,是全部java类(Object类除外)的终极父类。固然,数组也继承了Object类。然而,接口是不继承Object类的,缘由在这里指出:Section 9.6.3.4 of the Java Language Specification:“Object类不做为接口的父类”。
Object类中声明了如下函数,我会在下文中做详细说明。 数组

  • protected Object clone() oracle

  • boolean equals(Object obj) ide

  • protected void finalize() 函数

  • Class<?> getClass() this

  • int hashCode() spa

  • void notify() htm

  • void notifyAll() 对象

  • String toString()

  • void wait()

  • void wait(long timeout)

  • void wait(long timeout, int nanos)

java的任何类都继承了这些函数,而且能够覆盖不被final修饰的函数。例如,没有final修饰的toString()函数能够被覆盖,可是final wait()函数就不行。

问:能够声明要“继承Object类”吗?

答:能够。在代码中明确地写出继承Object类没有语法错误。参考代码清单1。

代码清单1:明确的继承Object类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
importjava.lang.Object;
publicclassEmployeeextendsObject {
   privateString name;
   publicEmployee(String name) {
       this.name = name;
   }
   publicString getName() {
       returnname;
   }
   publicstaticvoidmain(String[] args) {
       Employee emp =newEmployee("John Doe");
       System.out.println(emp.getName());
   }
}

你能够试着编译代码1(javac Employee.java),而后运行Employee.class(java Employee),能够看到John Doe 成功的输出了。

由于编译器会自动引入java.lang包中的类型,即import java.lang.Object; 不必声明出来。Java也没有强制声明“继承Object类”。若是这样的话,就不能继承除Object类以外别的类了,由于java不支持多继承。然而,即便不声明出来,也会默认继承了Object类,参考代码清单2。

代码清单2:默认继承Object类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
publicclassEmployee
{
 privateString name;
 publicEmployee(String name)
 {
     this.name = name;
 }
 publicString getName()
 {
     returnname;
 }
 publicstaticvoidmain(String[] args)
 {
     Employee emp =newEmployee("John Doe");
     System.out.println(emp.getName());
 }
}

就像代码清单1同样,这里的Employee类继承了Object,因此可使用它的函数。

克隆Object类

问:clone()函数是用来作什么的?

答:clone()能够产生一个相同的类而且返回给调用者。

问:clone()是如何工做的?

答:Object将clone()做为一个本地方法来实现,这意味着它的代码存放在本地的库中。当代码执行的时候,将会检查调用对象的类(或者父类)是否实现了java.lang.Cloneable接口(Object类不实现Cloneable)。若是没有实现这个接口,clone()将会抛出一个检查异常()——java.lang.CloneNotSupportedException,若是实现了这个接口,clone()会建立一个新的对象,并将原来对象的内容复制到新对象,最后返回这个新对象的引用。

问:怎样调用clone()来克隆一个对象?

答:用想要克隆的对象来调用clone(),将返回的对象从Object类转换到克隆的对象所属的类,赋给对象的引用。这里用代码清单3做一个示例。

代码清单3:克隆一个对象

1
2
3
4
5
6
7
8
9
10
11
publicclassCloneDemoimplementsCloneable {
   intx;
   publicstaticvoidmain(String[] args)throwsCloneNotSupportedException {
       CloneDemo cd =newCloneDemo();
       cd.x =5;
       System.out.printf("cd.x = %d%n", cd.x);
       CloneDemo cd2 = (CloneDemo) cd.clone();
       System.out.printf("cd2.x = %d%n", cd2.x);
   }
}

代码清单3声明了一个继承Cloneable接口的CloneDemo类。这个接口必须实现,不然,调用Object的clone()时将会致使抛出异常CloneNotSupportedException。

CloneDemo声明了一个int型变量x和主函数main()来演示这个类。其中,main()声明可能会向外抛出CloneNotSupportedException异常。

Main()先实例化CloneDemo并将x的值初始化为5。而后输出x的值,紧接着调用clone(),将克隆的对象传回CloneDemo。最后,输出了克隆的x的值。

编译代码清单3(javac CloneDemo.java)而后运行(java CloneDemo)。你能够看到如下运行结果:

1
2
cd.x = 5
cd2.x = 5

问:什么状况下须要覆盖clone()方法呢?

答:上面的例子中,调用clone()的代码是位于被克隆的类(即CloneDemo类)里面的,因此就不须要覆盖clone()了。可是,若是调用别的类中的clone(),就须要覆盖clone()了。不然,将会看到“clone在Object中是被保护的”提示,由于clone()在Object中的权限是protected。(译者注:protected权限的成员在不一样的包中,只有子类对象能够访问。代码清单3的CloneDemo类和代码清单4的Data类是Object类的子类,因此能够调用clone(),可是代码清单4中的CloneDemo类就不能直接调用Data父类的clone())。代码清单4在代码清单3上稍做修改来演示覆盖clone()。

代码清单4:从别的类中克隆对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classDataimplementsCloneable {
   intx;
   @Override
   publicObject clone()throwsCloneNotSupportedException {
       returnsuper.clone();
   }
}
publicclassCloneDemo {
   publicstaticvoidmain(String[] args)throwsCloneNotSupportedException {
       Data data =newData();
       data.x =5;
       System.out.printf("data.x = %d%n", data.x);
       Data data2 = (Data) data.clone();
       System.out.printf("data2.x = %d%n", data2.x);
   }
}

代码清单4声明了一个待克隆的Data类。这个类实现了Cloneable接口来防止调用clone()的时候抛出异常CloneNotSupportedException,声明了int型变量x,覆盖了clone()方法。这个方法经过执行super.clone()来调用父类的clone()(这个例子中是Object的)。经过覆盖来避免抛出CloneNotSupportedException异常。

代码清单4也声明了一个CloneDemo类来实例化Data,并将其初始化,输出示例的值。而后克隆Data的对象,一样将其值输出。

编译代码清单4(javac CloneDemo.java)并运行(java CloneDemo),你将看到如下运行结果:

1
2
data.x = 5
data2.x = 5

问:什么是浅克隆?

A:浅克隆(也叫作浅拷贝)仅仅复制了这个对象自己的成员变量,该对象若是引用了其余对象的话,也不对其复制。代码清单3和代码清单4演示了浅克隆。新的对象中的数据包含在了这个对象自己中,不涉及对别的对象的引用。

若是一个对象中的全部成员变量都是原始类型,而且其引用了的对象都是不可改变的(大多状况下都是)时,使用浅克隆效果很好!可是,若是其引用了可变的对象,那么这些变化将会影响到该对象和它克隆出的全部对象!代码清单5给出一个示例。

代码清单5:演示浅克隆在复制引用了可变对象的对象时存在的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
classEmployeeimplementsCloneable {
   privateString name;
   privateintage;
   privateAddress address;
   Employee(String name,intage, Address address) {
       this.name = name;
       this.age = age;
       this.address = address;
   }
   @Override
   publicObject clone()throwsCloneNotSupportedException {
       returnsuper.clone();
   }
   Address getAddress() {
       returnaddress;
   }
   String getName() {
       returnname;
   }
   intgetAge() {
       returnage;
   }
}
classAddress {
   privateString city;
   Address(String city) {
       this.city = city;
   }
   String getCity() {
       returncity;
   }
   voidsetCity(String city) {
       this.city = city;
   }
}
publicclassCloneDemo {
   publicstaticvoidmain(String[] args)throwsCloneNotSupportedException {
       Employee e =newEmployee("John Doe",49,newAddress("Denver"));
       System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(),
                         e.getAddress().getCity());
       Employee e2 = (Employee) e.clone();
       System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(),
                         e2.getAddress().getCity());
       e.getAddress().setCity("Chicago");
       System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(),
                         e.getAddress().getCity());
       System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(),
                         e2.getAddress().getCity());
   }
}

代码清单5给出了Employee、Address和CloneDemo类。Employee声明了name、age、address成员变量,是能够被克隆的类;Address声明了一个城市的地址而且其值是可变的。CloneDemo类驱动这个程序。

CloneDemo的主函数main()建立了一个Employee对象而且对其进行克隆,而后,改变了原来的Employee对象中address值城市的名字。由于原来的Employee对象和其克隆出来的对象引用了相同的Address对象,因此二者都会提现出这个变化。

编译 (javac CloneDemo.java) 并运行 (java CloneDemo)代码清单5,你将会看到以下输出结果:

1
2
3
4
John Doe: 49: Denver
John Doe: 49: Denver
John Doe: 49: Chicago
John Doe: 49: Chicago

问:什么是深克隆?

答:深克隆(也叫作深复制)会复制这个对象和它所引用的对象的成员变量,若是该对象引用了其余对象,深克隆也会对其复制。例如,代码清单6在代码清单5上稍做修改演示深克隆。同时,这段代码也演示了协变返回类型和一种更为灵活的克隆方式。

代码清单6:深克隆成员变量address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
classEmployeeimplementsCloneable
{
 privateStringname;
 privateintage;
 privateAddress address;
 Employee(Stringname,intage, Address address)
 {
     this.name = name;
     this.age = age;
     this.address = address;
 }
 @Override
 publicEmployee clone() throws CloneNotSupportedException
 {
     Employee e = (Employee)super.clone();
     e.address = address.clone();
     returne;
 }
 Address getAddress()
 {
     returnaddress;
 }
 StringgetName()
 {
     returnname;
 }
 intgetAge()
 {
     returnage;
 }
}
classAddress
{
 privateStringcity;
 Address(Stringcity)
 {
     this.city = city;
 }
 @Override
 publicAddress clone()
 {
     returnnewAddress(newString(city));
 }
 StringgetCity()
 {
     returncity;
 }
 voidsetCity(Stringcity)
 {
     this.city = city;
 }
}
publicclassCloneDemo
{
 publicstaticvoidmain(String[] args) throws CloneNotSupportedException
 {
     Employee e =newEmployee("John Doe",49,newAddress("Denver"));
     System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(),
                       e.getAddress().getCity());
     Employee e2 = (Employee) e.clone();
     System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(),
                       e2.getAddress().getCity());
     e.getAddress().setCity("Chicago");
     System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(),
                       e.getAddress().getCity());
     System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(),
                       e2.getAddress().getCity());
 }
}

Java支持协变返回类型,代码清单6利用这个特性,在Employee类中覆盖父类clone()方法时,将返回类型从Object类的对象改成Employee类型。这样作的好处就是,Employee类以外的代码能够不用将这个类转换为Employee类型就能够对其进行复制。

Employee类的clone()方法首先调用super().clone(),对name,age,address这些成员变量进行浅克隆。而后,调用成员变量Address对象的clone()来对其引用Address对象进行克隆。

从Address类中的clone()函数能够看出,这个clone()和咱们以前写的clone()有些不一样:

  • Address类没有实现Cloneable接口。由于只有在Object类中的clone()被调用时才须要实现,而Address是不会调用clone()的,因此没有实现Cloneable()的必要。

  • 这个clone()函数没有声明抛出CloneNotSupportedException。这个检查异常只可能在调用Object类clone()的时候抛出。clone()是不会被调用的,所以这个异常也就没有被处理或者传回调用处的必要了。

  • Object类的clone()没有被调用(这里没有调用super.clone())。由于这不是对Address的对象进行浅克隆——只是一个成员变量复制而已。

为了克隆Address的对象,须要建立一个新的Address对象并对其成员进行初始化操做。最后将新建立的Address对象返回。

编译(javac CloneDemo.java)代码清单6而且运行这个程序,你将会看到以下输出结果(java CloneDemo):

1
2
3
4
John Doe: 49: Denver
John Doe: 49: Denver
John Doe: 49: Chicago
John Doe: 49: Denver

Q:如何克隆一个数组?

A:对数组类型进行浅克隆能够利用clone()方法。对数组使用clone()时,没必要将clone()的返回值类型转换为数组类型,代码清单7示范了数组克隆。

代码清单7:对两个数组进行浅克隆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
classCity {
   privateString name;
   City(String name) {
       this.name = name;
   }
   String getName() {
       returnname;
   }
   voidsetName(String name) {
       this.name = name;
   }
}
publicclassCloneDemo {
   publicstaticvoidmain(String[] args) {
       double[] temps = {98.6,32.0,100.0,212.0,53.5};
       for(doubletemp : temps)
           System.out.printf("%.1f ", temp);
       System.out.println();
       double[] temps2 = temps.clone();
       for(doubletemp : temps2)
           System.out.printf("%.1f ", temp);
       System.out.println();
       System.out.println();
       City[] cities = {newCity("Denver"),newCity("Chicago") };
       for(City city : cities)
           System.out.printf("%s ", city.getName());
       System.out.println();
       City[] cities2 = cities.clone();
       for(City city : cities2)
           System.out.printf("%s ", city.getName());
       System.out.println();
       cities[0].setName("Dallas");
       for(City city : cities2)
           System.out.printf("%s ", city.getName());
       System.out.println();
   }
}

代码清单7声明了一个City类存储名字,还有一些有关城市的数据(好比人口)。CloneDemo类提供了主函数main()来演示数组克隆。

main()函数首先声明了一个双精度浮点型数组来表示温度。在输出数组的值以后,克隆这个数组——注意没有运算符。以后,输出克隆的彻底相同的数据。

紧接着,main()声明了一个City对象的数组,输出城市的名字,克隆这个数组,输出克隆的这个数组中城市的名字。为了证实浅克隆完成(好比,这两个数组引用了相同的City对象),main()最后改变了原来的数组中第一个城市的名字,输出第二个数组中全部城市的名字。咱们立刻就能够看到,第二个数组中的名字也改变了。

编译 (javac CloneDemo.java)并运行 (java CloneDemo)代码清单7,你将会看到以下输出结果:

1
2
3
4
5
6
98.6 32.0 100.0 212.0 53.5
98.6 32.0 100.0 212.0 53.5
Denver Chicago
Denver Chicago
Dallas Chicago
相关文章
相关标签/搜索