Java提高学习之Object类详解(1)
转自:http://www.importnew.com/10304.html
问:什么是Object类?
答:Object类存储在java.lang包中,是所有java类(Object类除外)的终极父类。当然,数组也继承了Object类。然而,接口是不继承Object类的,原因在这里指出:Section 9.6.3.4 of the Java Language Specification:“Object类不作为接口的父类”。Object类中声明了以下函数,我会在下文中作详细说明。
1 protected Object clone() 2 boolean equals(Object obj) 3 protected void finalize() 4 Class<?> getClass() 5 int hashCode() 6 void notify() 7 void notifyAll() 8 String toString() 9 void wait() 10 void wait(long timeout) 11 void wait(long timeout, int nanos)
java的任何类都继承了这些函数,并且可以覆盖不被final修饰的函数。例如,没有final修饰的toString()函数可以被覆盖,但是final wait()函数就不行。
问:可以声明要“继承Object类”吗?
答:可以。在代码中明确地写出继承Object类没有语法错误。参考代码清单1。
代码清单1:明确的继承Object
1 import java.lang.Object; 2 public class Employee extends Object { 3 private String name; 4 5 public Employee(String name) { 6 this.name = name; 7 } 8 public String getName() { 9 return name; 10 } 11 public static void main(String[] args) { 12 Employee emp = new Employee("John Doe"); 13 System.out.println(emp.getName()); 14 } 15 }
你可以试着编译代码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 public class Employee 2 { 3 private String name; 4 5 public Employee(String name) 6 { 7 this.name = name; 8 } 9 10 public String getName() 11 { 12 return name; 13 } 14 15 public static void main(String[] args) 16 { 17 Employee emp = new Employee("John Doe"); 18 System.out.println(emp.getName()); 19 } 20 }
就像代码清单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 public class CloneDemo implements Cloneable { 2 int x; 3 4 public static void main(String[] args) throws CloneNotSupportedException { 5 CloneDemo cd = new CloneDemo(); 6 cd.x = 5; 7 System.out.printf("cd.x = %d%n", cd.x); 8 CloneDemo cd2 = (CloneDemo) cd.clone(); 9 System.out.printf("cd2.x = %d%n", cd2.x); 10 } 11 }
代码清单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 = 5cd2.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 class Data implements Cloneable { 2 int x; 3 4 @Override 5 public Object clone() throws CloneNotSupportedException { 6 return super.clone(); 7 } 8 } 9 10 public class CloneDemo { 11 public static void main(String[] args) throws CloneNotSupportedException { 12 Data data = new Data(); 13 data.x = 5; 14 System.out.printf("data.x = %d%n", data.x); 15 Data data2 = (Data) data.clone(); 16 System.out.printf("data2.x = %d%n", data2.x); 17 } 18 }
代码清单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 = 5data2.x = 5 |
问:什么是浅克隆?
A:浅克隆(也叫做浅拷贝)仅仅复制了这个对象本身的成员变量,该对象如果引用了其他对象的话,也不对其复制。代码清单3和代码清单4演示了浅克隆。新的对象中的数据包含在了这个对象本身中,不涉及对别的对象的引用。如果一个对象中的所有成员变量都是原始类型,并且其引用了的对象都是不可改变的(大多情况下都是)时,使用浅克隆效果很好!但是,如果其引用了可变的对象,那么这些变化将会影响到该对象和它克隆出的所有对象!代码清单5给出一个示例。
代码清单5:演示浅克隆在复制引用了可变对象的对象时存在的问题
1 class Employee implements Cloneable { 2 private String name; 3 private int age; 4 private Address address; 5 6 Employee(String name, int age, Address address) { 7 this.name = name; 8 this.age = age; 9 this.address = address; 10 } 11 12 @Override 13 public Object clone() throws CloneNotSupportedException { 14 return super.clone(); 15 } 16 17 Address getAddress() { 18 return address; 19 } 20 21 String getName() { 22 return name; 23 } 24 25 int getAge() { 26 return age; 27 } 28 } 29 30 class Address { 31 private String city; 32 33 Address(String city) { 34 this.city = city; 35 } 36 37 String getCity() { 38 return city; 39 } 40 41 void setCity(String city) { 42 this.city = city; 43 } 44 } 45 46 public class CloneDemo { 47 public static void main(String[] args) throws CloneNotSupportedException { 48 Employee e = new Employee("John Doe", 49, new Address("Denver")); 49 System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), 50 e.getAddress().getCity()); 51 Employee e2 = (Employee) e.clone(); 52 System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), 53 e2.getAddress().getCity()); 54 e.getAddress().setCity("Chicago"); 55 System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), 56 e.getAddress().getCity()); 57 System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), 58 e2.getAddress().getCity()); 59 } 60 }
代码清单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: DenverJohn Doe: 49: DenverJohn Doe: 49: ChicagoJohn Doe: 49: Chicago |
问:什么是深克隆?
答:深克隆(也叫做深复制)会复制这个对象和它所引用的对象的成员变量,如果该对象引用了其他对象,深克隆也会对其复制。例如,代码清单6在代码清单5上稍作修改演示深克隆。同时,这段代码也演示了协变返回类型和一种更为灵活的克隆方式。
代码清单6:深克隆成员变量address
1 class Employee implements Cloneable 2 { 3 private String name; 4 private int age; 5 private Address address; 6 7 Employee(String name, int age, Address address) 8 { 9 this.name = name; 10 this.age = age; 11 this.address = address; 12 } 13 14 @Override 15 public Employee clone() throws CloneNotSupportedException 16 { 17 Employee e = (Employee) super.clone(); 18 e.address = address.clone(); 19 return e; 20 } 21 22 Address getAddress() 23 { 24 return address; 25 } 26 27 String getName() 28 { 29 return name; 30 } 31 32 int getAge() 33 { 34 return age; 35 } 36 } 37 38 class Address 39 { 40 private String city; 41 42 Address(String city) 43 { 44 this.city = city; 45 } 46 47 @Override 48 public Address clone() 49 { 50 return new Address(new String(city)); 51 } 52 53 String getCity() 54 { 55 return city; 56 } 57 58 void setCity(String city) 59 { 60 this.city = city; 61 } 62 } 63 64 public class CloneDemo 65 { 66 public static void main(String[] args) throws CloneNotSupportedException 67 { 68 Employee e = new Employee("John Doe", 49, new Address("Denver")); 69 System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), 70 e.getAddress().getCity()); 71 Employee e2 = (Employee) e.clone(); 72 System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), 73 e2.getAddress().getCity()); 74 e.getAddress().setCity("Chicago"); 75 System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), 76 e.getAddress().getCity()); 77 System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), 78 e2.getAddress().getCity()); 79 } 80 }
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: DenverJohn Doe: 49: DenverJohn Doe: 49: ChicagoJohn Doe: 49: Denver |
Q:如何克隆一个数组?
A:对数组类型进行浅克隆可以利用clone()方法。对数组使用clone()时,不必将clone()的返回值类型转换为数组类型,代码清单7示范了数组克隆。
代码清单7:对两个数组进行浅克隆
1 class City { 2 private String name; 3 4 City(String name) { 5 this.name = name; 6 } 7 8 String getName() { 9 return name; 10 } 11 12 void setName(String name) { 13 this.name = name; 14 } 15 } 16 17 public class CloneDemo { 18 public static void main(String[] args) { 19 double[] temps = { 98.6, 32.0, 100.0, 212.0, 53.5 }; 20 for (double temp : temps) 21 System.out.printf("%.1f ", temp); 22 System.out.println(); 23 double[] temps2 = temps.clone(); 24 for (double temp : temps2) 25 System.out.printf("%.1f ", temp); 26 System.out.println(); 27 28 System.out.println(); 29 30 City[] cities = { new City("Denver"), new City("Chicago") }; 31 for (City city : cities) 32 System.out.printf("%s ", city.getName()); 33 System.out.println(); 34 City[] cities2 = cities.clone(); 35 for (City city : cities2) 36 System.out.printf("%s ", city.getName()); 37 System.out.println(); 38 39 cities[0].setName("Dallas"); 40 for (City city : cities2) 41 System.out.printf("%s ", city.getName()); 42 System.out.println(); 43 } 44 }
代码清单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 |

浙公网安备 33010602011771号