Object类的常用方法
- clone()
Object类中的clone()
方法用来复制自定义类的实例对象,clone()
方法会返回一个拷贝的新对象,而不是原对象的引用,这个新对象中已经包含了一些原来对象的信息。
继承自Object类的自定义类都会继承该方法,要clone的类还要实现Clonebla接口才能使用clone()
方法。Cloneable接口不包含任何方法,它是针对Object类中clone()
方法的一个标识,如果要clone的类没有实现Cloneable接口,并调用了Object的clone()
方法(super.clone()
),那么Object的clone()
方法会抛出CloneNotSupportedException
。
package clone;
public class Student {
private String name;
private int age;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) {
Student std = new Student();
std.setAge(12);
std.setName("Shirley");
try {
std.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
//output:
//java.lang.CloneNotSupportedException: clone.Student
// at java.lang.Object.clone(Native Method)
// at clone.Student.clone(Student.java:25)
// at clone.Student.main(Student.java:33)
克隆的实现方式有两种:
-
浅克隆(shallow clone):对于要克隆的对象,对于其基本数据类型的属性,复制一份给新产生的对象,对于引用类型的属性,复制一份引用给新产生的对象。也就是说,新产生的对象和原始对象中的引用类型的属性指向都是同一个对象。这就会出现一个问题,当在原始对象中修改引用类型的属性时,新产生对象的对应属性也会发生改变,反之亦然。
如何实现浅克隆:实现
java.lang.Cloneable
接口,重写java.lang.Object.clone()
方法。(注意:Object中的clone()
是一个protected属性的方法,重写之后要把clone()
方法的属性设置为public.public class Teacher { private String name; private String subject; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getSubject() { return this.subject; } public void setSubject(String subject) { this.subject = subject; } @Override public String toString() { return "Teacher [name: " + this.name + ", subject: " + this.subject + "]"; } } public class Student implements Cloneable { private String name; private int age; private Teacher teacher; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } public Teacher getTeacher() { return this.teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Student [name: " + this.name + ", age: " + this.age + ", teacher: " + this.teacher.toString() + "]"; } public static void main(String[] args) { Teacher teacher = new Teacher(); teacher.setName("Jack"); teacher.setSubject("Math"); Student std = new Student(); std.setAge(12); std.setName("Shirley"); std.setTeacher(teacher); try { Student clonedStd = (Student) std.clone(); System.out.println("std == clonedStd: " + (std == clonedStd)); System.out.println("std.getTeacher() == clonedStd.getTeacher(): " + (std.getTeacher() == clonedStd.getTeacher())); System.out.println("std:" + std.toString()); System.out.println("clonedStd" + clonedStd.toString()); Teacher clonedStdTeacher = clonedStd.getTeacher(); clonedStdTeacher.setName("Ann"); clonedStdTeacher.setSubject("Music"); clonedStd.setName("Sue"); clonedStd.setAge(14); System.out.println("After-->std:" + std.toString()); System.out.println("After-->clonedStd" + clonedStd.toString()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } //output //std == clonedStd: false //std.getTeacher() == clonedStd.getTeacher(): true //std:Student [name: Shirley, age: 12, teacher: Teacher [name: Jack, subject: Math]] //clonedStdStudent [name: Shirley, age: 12, teacher: Teacher [name: Jack, subject: Math]] //After-->std:Student [name: Shirley, age: 12, teacher: Teacher [name: Ann, subject: Music]] //After-->clonedStdStudent [name: Sue, age: 14, teacher: Teacher [name: Ann, subject: Music]]
由代码输出可以看出:
- 通过
clone()
方法得到的拷贝的对象与原对象不是同一个对象。 - 原对象和拷贝对象的teacher属性指向了同一个引用地址。
- 拷贝对象得到了原对象的属性值(使用
clone()
方法后打印原对象和拷贝对象,二者属性值是相同的)。 - 修改拷贝对象的基本数据类型的属性后,与之对应的原对象的属性值并没有发生改变,然而修改值为引用类型的属性值后(teacher属性),原对象和拷贝对象的teacher属性都发生了改变(指向相同的引用地址)。这是我们在实际应用当中很棘手的一个问题,因为通常情况下,我们希望对拷贝对像和原对象的属性值是各自独立的,对拷贝对象中属性值的修改不会影响其对应的原对象的值。这时就需要使用深克隆了。
- 通过
-
深克隆(deep clone):在浅克隆的基础上,对于克隆的对象中的引用类型的属性对应的类,也实现克隆。这样一来对于引用类型的属性,复制的就不再是一份引用,也就是说,拷贝对象和原对象的引用类型的属性不再指向同一个对象。
如何实现深克隆:对于要克隆的类和类中所有非基本数据类型的属性对应的类都实现
java.lang.Cloneable
接口,都重写java.lang.Object.clone()
方法。//我们将浅克隆的改写为深克隆 //1.Teacher类实现Cloneable接口,重写clone()方法 public class Teacher implements Cloneable{ //... @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } //2.修改Student类的clone()方法 public class Student implements Cloneable { //... @Override protected Object clone() throws CloneNotSupportedException { Student newStudent = (Student) super.clone(); newStudent.teacher = (Teacher) this.teacher.clone(); return newStudent; } } //再次运行main方法,输出为: //std == clonedStd: false //std.getTeacher() == clonedStd.getTeacher(): false //std:Student [name: Shirley, age: 12, teacher: Teacher [name: Jack, subject: Math]] //clonedStdStudent [name: Shirley, age: 12, teacher: Teacher [name: Jack, subject: Math]] //After-->std:Student [name: Shirley, age: 12, teacher: Teacher [name: Jack, subject: Math]] //After-->clonedStdStudent [name: Sue, age: 14, teacher: Teacher [name: Ann, subject: Music]]
Student类修改了
clone()
方法的实现。通过调用Student类的super.clone()
实现对于基本数据类型数据的拷贝,再通过调用引用类型属性teacher的值(Teacher对象)的super.clone()
返回了一个新的Teacher对象,并将其赋值给了拷贝对象的teacher属性,这样一来,原对象和拷贝对象的teacher属性就不再指向同一个对象了。通过观察代码输出可以看出,通过深克隆得到的拷贝对象的属性和原对象中的属性是相互独立,不会相互影响的。注意:
- 以上这种方法可以实现大部分属性值为对象时的深克隆。但是当属性值为一些特殊数据结构,例如HashMap时,直接调用
super.clone()
可能仍然无法实现深克隆,这时可能需要使用循环赋值的方法去复制该属性。 - 虽然以上这种方法可以实现深克隆,但在遇到嵌套很深的对象时,这种方法实现起来就会非常复杂。这种情况下使用对象序列化和反序列化实现深克隆,代码会简化很多。
- 以上这种方法可以实现大部分属性值为对象时的深克隆。但是当属性值为一些特殊数据结构,例如HashMap时,直接调用
-equals()
equals()
方法返回一个布尔值,用于比较两个非空对象是否相等。默认情况下,equals()
方法比较的是两个对象的引用是否指向同一个内存地址,也就是说,equals()
方法的返回值和==
是一致的。
public class Test {
public static void main(String[] args) {
Test t1 = new Test();
Test t2 = new Test();
Test t3 = t1;
System.out.println("t1 == t2: " + (t1 ==t2));
System.out.println("t1 equals t2: " + (t1.equals(t2)));
System.out.println("t1 == t3: " + (t1 ==t3));
System.out.println("t1 equals t3: " + (t1.equals(t3)));
}
}
//output
//t1 == t2: false
//t1 equals t2: false
//t1 == t3: true
//t1 equals t3: true
equals()
方法是Object的方法,我们创建的所有对象都拥有这个方法,并且可以重写这个方法。例如,String类就对其equals()
方法进行了重写,用于比较两个字符串的内容是否相等。这种情况下,equals()
方法和==
就会返回不同的值。
public class Test {
public static void main(String[] args) {
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println("str1 == str2: " + (str1 == str2));
System.out.println("str1.equals(str2: " + (str1.equals(str2)));
}
}
//output
//str1 == str2: false
//str1.equals(str2: true
在实际应用中,我们会根据需要对某些类的equals()
方法进行重写,在重写的时候,需要遵循以下原则:
Reflexive(自反性):for any non-null reference value
x
,x.equals(x)
should returntrue
.Symmetric(对称性):for any non-null reference values
x
andy
,x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
.Transitive(传递性): for any non-null reference values
x
,y
, andz
, ifx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
should returntrue
.Consistent(一致性):for any non-null reference values
x
andy
, multiple invocations ofx.equals(y)
consistently returntrue
or consistently returnfalse
, provided no information used inequals
comparisons on the objects is modified.For any non-null reference value
x
,x.equals(null)
should returnfalse
.
注意:通常情况下,当某个类当equals()
方法被重写时,也需要同时重写该类的hashCode()
方法。这是因为,通常情况下,调用equals()
方法返回为true
的两个对象,在调用hashCode()
方法时也应返回相同的数值。
-hashCode()
hashCode()
方法返回一个int型数值。hashCode()
方法其实就是一种hash算法,自定义类可以根据需要对其进行重写。java文档中对于hashCode的一般约定如下:
运行期间多次调用的返回值应该保持一致:
Whenever it is invoked on the same object more than once during an execution of a Java application, the
hashCode
method must consistently return the same integer, provided no information used inequals
comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.根据
equals()
方法比较结果为相等的两个对象在调用hashCode()
时应返回相同的整数:If two objects are equal according to the
equals(Object)
method, then calling thehashCode
method on each of the two objects must produce the same integer result.Java并不要求根据
equals()
方法比较结果为不相等的两个对象在调用hashCode()
时必须返回不同的结果,但是这样做可以提高hash表的性能:It is not required that if two objects are unequal according to the
equals(java.lang.Object)
method, then calling thehashCode
method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
注意:
- 在重写
equals()
方法后通常需要重写hashCode()
方法 - 如果两个对象调用
hashCode()
的返回值相同,调用equals()
的结果不一定为true(哈希碰撞)
-getClass()
getClass()
是Object类中的一个方法,对象实例调用这个方法返回代表实例运行时的类型的Class类。
-
什么是Class类呢?
Java除了基本类型外其他都是class(包括interface)。JVM每加载一个class,就为其创建一个Class类型的实例,关联起来,并在实例中保存该class的完整信息(例如,类名,package名称,它的super class,它实现的interface,它所有的字段和方法等等)。如果获取了某个Class实例,则可以获取到该实力对应的class的所有信息。通过Class实例获取class信息的方法称为反射(Reflection),也可以理解为在运行期获取对象类型信息的操作。
如何获取Class实例?
-
.class
:当我们知道数据类型(类名)缺没有实例对象时,可以通过类名.class
这种形式来获取Class实例。这也是基本数据类型获取Class实例最方便的一种方法。.class
语句也可以被用来获取多维数组对应的Class实例。Class cls = String.class; Class c = int[][][].class;
-
Object.getClass()
:这是实例对象拥有的一个方法,只能用于继承自Object的引用类型数据(不能用于基本数据类型)。boolean b; Class c = b.getClass();//编译错误 Class c = boolean.class;//正确 String str = "hello"; Class cls = str.getClass();
-
Class.forName()
:如果可以获取完整类名,还可以使用Class类的静态方法Class.forName()
,将完整类名作为参数传入该方法来获得Class实例。该方法可以用于引用类型和基本数据类型。使用该方法还可以利用JVM动态加载class的特性在运行期根据条件加载不同的实现类。Class cls = Class.forName("java.lang.String"); boolean isClassPresent(String name){ try{ Class.forName(name); return true; }catch(Exception e){ return false; } }
调用Class实例中的方法可以让我们获得class相关的信息,判断class的类型,并且创建class实例对象。
-
Class实例在JVM中时唯一的,可以用
==
比较两个Class实例Class cls1 = String.class; String s = "hello"; Class cls2 = s.getClass(); Class cls3 = Class.forName("java.lang.String"); boolean b1 = (cls1 == cls2); //true boolean b2 = (cls2 == cls3); //true
-
Class实例比较和
instanceof
的差别:-
instanceof
不但匹配当前类型,还匹配当前类型的子类 - 用
==
判断Class实例时,只能精确判断数据类型,不能做子类的比较
-
-toString()
Object的toString()
方法返回一个代表该对象实例的字符串。一般情况下,返回的字符串应该能准确得,完善得表达该对象的内容,并且易于阅读。推荐所有子类都重写这个方法。
默认的toString()
方法返回一个由类名,‘@’符号,和转换为十六进制的hash code组成:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
当我们创建一个新类时,可以重写toString()
方法在控制台中显示有关于类的有用信息。
package test;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//不重写toString()方法
public static void main(String[] args) {
Person p = new Person("jack", 28);
System.out.println(p.toString());
//output:test.Person@61bbe9ba
//对于表示对象的内容没有任何意义
}
}
//重写toString()方法
@Override
public String toString() {
return "Person: [name: " + this.name + ", age: " + this.age + "]";
}
//再次打印
System.out.println(p.toString());
//Person: [name: jack, age: 28]
//显示出了该实例对象包含的信息。
String类的常用方法
-compareTo()
String类实现了Compareable
接口,而compareTo()
方法是Compareable
接口唯一需要实现的方法。compareTo()
方法按照字典顺序比较两个字符串,返回一个int
值。比较是基于字符串中每一个字符的Unicode值。按照字典顺序排列,当该String对象位于参数字符串之前时,返回一个负整数;当该String对象位于参数字符串之后时,返回一个正整数;当二者相同时,返回0。只有当equals(Object)
返回true
时,compareTo()
返回0.
字典顺序的定义如下:当两个字符串不同时,它们要么在相同索引处有不同字符(索引位置在两个字符串中都有效),要么两个字符串的长度不同,或者以上两种情况同时存在。
-
当两个字符串在一个或多个索引处有不同的字符时,我们定义一个变量
k
的值为出现不同字符的最小索引的值。哪个字符串在索引k处的字符数值越小,按字典顺序该字符串就排在另一个字符串前面。在这种情况下,compareTo()
返回两个字符串在索引k处的字符值的差值:this.charAt(k) - anotherString.charAt(k);
-
当两个字符串在有效索引范围内,相同的索引处没有出现不同的字符时,按字典顺序长度较短的字符串排在长度较长的字符串前面。在这种情况下,
compareTo()
返回两个字符串长度的差值:this.length() - anotherString.length();
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abcd";
String str3 = "abc";
String str4 = "dcba";
//compareTo()可以传入String字面量
System.out.println(str1.compareTo(str2));//-1:字符串长度不同(结果为长度差)
System.out.println(str1.compareTo(str3));//0: 两字符串相同
System.out.println( str1.equals(str3));//true
System.out.println(str1 == str3);//true
System.out.println(str1.compareTo(str4));//-3: 字符不同(结果为字符值的差)
System.out.println(str1.compareTo("abc"));//0:两字符串相同
//也可以传入String对象
String s1 = new String("await");
String s2 = new String("await");
System.out.println(s1 == s2);//false:二者没有指向同一个对象
System.out.println(s1.equals(s2));//true:二者的内容相同
System.out.println(s1.compareTo(s2));//0:两字符串相同
}
注意:compareTo()
是大小写敏感的,如果需要忽略大小写,可以使用compareToIgnoreCase()
。
-concat()
concat()
可以在给定字符串的末尾拼接一个字符串,例如:"Hello".concat("World")
会返回"HelloWorld"。concat()
方法的规则如下:
当传入的字符串参数长度为0时,
concat()
会返回原字符串对象。当传入的字符串参数长度不为0时,调用
concat()
方法会返回一个由原字符串和传入的参数字符串拼接而成的新字符串。-
可以进行链式操作,调用
concat()
不会改变原字符串的内容//在给定字符串末尾拼接字符串 String str1 = "abc"; String str2 = "abcd"; String test = str1.concat(str2); System.out.println(test);//“abcabcd" System.out.println(str1);//"abc"->不改变原字符串 //链式操作 String welcome = "Welcome ".concat("to ").concat("my place."); System.out.println(welcome);//“Welcome to my place.“ //在给定字符串前面拼接字符串 String s = ".com"; String webSite = "www.google".concat(s); System.out.println(webSite);//"www.google.com"
-equals()
equals()
比较当前字符串与作为参数的字符串是否相同。如果参数是不为null的String对象,并且字符串中字符的序列与当前字符串完全一致,equals()
返回true。其他情况下返回false。equals()
方法大小写敏感,如果需要忽略大小写进行比较,需要调用equalsIgnoreCase()
。
String str1 = "abc";
str1.equals("abc");//true
str1.equals("ABC");//false
str1.equalsIgnoreCase("ABC");//true
contentEquals()
与equals()
功能相似。contentEquals()
只比较两者的内容是否相同,不检查比较对象的类型。
String str1 = "hello";
StringBuffer sb1 = "hello";
System.out.println(str1.equals(sb1));//false:因为被比较对象sb1不是String类型
System.out.println(str1.contentEquals(sb1));//true:内容相同即返回true,不检查比较对象的类型
-join()
join()
是Java 8中新引入String class的一个方法。它可以用来拼接字符串并返回拼接后的新字符串,也可以用来将可迭代的字符串的集合拼接成一个新的字符串。语法如下:
public static String join(CharSequence delimiter,
CharSequence... elements)
public static String join(CharSequence delimiter,
Iterable<? extends CharSequence> elements)
在使用join()
拼接多个字符串时,join()
的第一个参数为标点符号(用于分隔/拼接),后面紧跟着需要拼接的字符串,字符串之间以逗号分隔。
String str = String.join("-","hello","world");
System.out.println(str);//"hello-world"
join()
可以用来拼接字符串数组:
String[] strs = {"a", "b", "c", "d"};
String str = String.join(",", strs);
System.out.println(str);//"a,b,c,d"
join()
还可以用来拼接可迭代的字符串的集合:
import java.util.ArrayList;
import java.util.List;
public class Test{
public static void main(String[] args) {
List<String> names = new ArrayList();
names.add("Ann");
names.add("Joe");
names.add("Caroline");
names.add("Kris");
String name = String.join("|", names);
System.out.println(name);//"Ann|Joe|Caroline|Kris"
}
}
除了join()
方法外,StringJoiner
类也可以实现拼接字符串的功能。了解更多