Object類的常用方法
- clone()
? Object類中的clone()
方法用來復(fù)制自定義類的實例對象涡驮,clone()
方法會返回一個拷貝的新對象,而不是原對象的引用刻盐,這個新對象中已經(jīng)包含了一些原來對象的信息上忍。
? 繼承自O(shè)bject類的自定義類都會繼承該方法,要clone的類還要實現(xiàn)Clonebla接口才能使用clone()
方法俗慈。Cloneable接口不包含任何方法成玫,它是針對Object類中clone()
方法的一個標(biāo)識加酵,如果要clone的類沒有實現(xiàn)Cloneable接口,并調(diào)用了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)
? 克隆的實現(xiàn)方式有兩種:
-
淺克隆(shallow clone):對于要克隆的對象,對于其基本數(shù)據(jù)類型的屬性钦勘,復(fù)制一份給新產(chǎn)生的對象陋葡,對于引用類型的屬性,復(fù)制一份引用給新產(chǎn)生的對象彻采。也就是說腐缤,新產(chǎn)生的對象和原始對象中的引用類型的屬性指向都是同一個對象。這就會出現(xiàn)一個問題肛响,當(dāng)在原始對象中修改引用類型的屬性時岭粤,新產(chǎn)生對象的對應(yīng)屬性也會發(fā)生改變,反之亦然特笋。
如何實現(xiàn)淺克绿杲健:實現(xiàn)
java.lang.Cloneable
接口,重寫java.lang.Object.clone()
方法。(注意:Object中的clone()
是一個protected屬性的方法虎囚,重寫之后要把clone()
方法的屬性設(shè)置為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()
方法后打印原對象和拷貝對象淘讥,二者屬性值是相同的)圃伶。 - 修改拷貝對象的基本數(shù)據(jù)類型的屬性后,與之對應(yīng)的原對象的屬性值并沒有發(fā)生改變蒲列,然而修改值為引用類型的屬性值后(teacher屬性)窒朋,原對象和拷貝對象的teacher屬性都發(fā)生了改變(指向相同的引用地址)。這是我們在實際應(yīng)用當(dāng)中很棘手的一個問題蝗岖,因為通常情況下炼邀,我們希望對拷貝對像和原對象的屬性值是各自獨立的,對拷貝對象中屬性值的修改不會影響其對應(yīng)的原對象的值剪侮。這時就需要使用深克隆了。
- 通過
-
深克隆(deep clone):在淺克隆的基礎(chǔ)上洛退,對于克隆的對象中的引用類型的屬性對應(yīng)的類瓣俯,也實現(xiàn)克隆。這樣一來對于引用類型的屬性兵怯,復(fù)制的就不再是一份引用彩匕,也就是說,拷貝對象和原對象的引用類型的屬性不再指向同一個對象媒区。
如何實現(xiàn)深克峦找恰:對于要克隆的類和類中所有非基本數(shù)據(jù)類型的屬性對應(yīng)的類都實現(xiàn)
java.lang.Cloneable
接口,都重寫java.lang.Object.clone()
方法袜漩。//我們將淺克隆的改寫為深克隆 //1.Teacher類實現(xiàn)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()
方法的實現(xiàn)宙攻。通過調(diào)用Student類的super.clone()
實現(xiàn)對于基本數(shù)據(jù)類型數(shù)據(jù)的拷貝奠货,再通過調(diào)用引用類型屬性teacher的值(Teacher對象)的super.clone()
返回了一個新的Teacher對象,并將其賦值給了拷貝對象的teacher屬性座掘,這樣一來递惋,原對象和拷貝對象的teacher屬性就不再指向同一個對象了。通過觀察代碼輸出可以看出溢陪,通過深克隆得到的拷貝對象的屬性和原對象中的屬性是相互獨立萍虽,不會相互影響的。注意:
- 以上這種方法可以實現(xiàn)大部分屬性值為對象時的深克隆形真。但是當(dāng)屬性值為一些特殊數(shù)據(jù)結(jié)構(gòu)杉编,例如HashMap時,直接調(diào)用
super.clone()
可能仍然無法實現(xiàn)深克隆,這時可能需要使用循環(huán)賦值的方法去復(fù)制該屬性王财。 - 雖然以上這種方法可以實現(xiàn)深克隆卵迂,但在遇到嵌套很深的對象時,這種方法實現(xiàn)起來就會非常復(fù)雜绒净。這種情況下使用對象序列化和反序列化實現(xiàn)深克隆见咒,代碼會簡化很多。
- 以上這種方法可以實現(xiàn)大部分屬性值為對象時的深克隆形真。但是當(dāng)屬性值為一些特殊數(shù)據(jù)結(jié)構(gòu)杉编,例如HashMap時,直接調(diào)用
-equals()
? equals()
方法返回一個布爾值挂疆,用于比較兩個非空對象是否相等改览。默認(rèn)情況下,equals()
方法比較的是兩個對象的引用是否指向同一個內(nèi)存地址缤言,也就是說宝当,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的方法胆萧,我們創(chuàng)建的所有對象都擁有這個方法庆揩,并且可以重寫這個方法。例如跌穗,String類就對其equals()
方法進(jìn)行了重寫订晌,用于比較兩個字符串的內(nèi)容是否相等。這種情況下蚌吸,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
? 在實際應(yīng)用中,我們會根據(jù)需要對某些類的equals()
方法進(jìn)行重寫羹唠,在重寫的時候奕枢,需要遵循以下原則:
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
.
注意:通常情況下,當(dāng)某個類當(dāng)equals()
方法被重寫時佩微,也需要同時重寫該類的hashCode()
方法缝彬。這是因為,通常情況下喊衫,調(diào)用equals()
方法返回為true
的兩個對象跌造,在調(diào)用hashCode()
方法時也應(yīng)返回相同的數(shù)值。
-hashCode()
? hashCode()
方法返回一個int型數(shù)值族购。hashCode()
方法其實就是一種hash算法壳贪,自定義類可以根據(jù)需要對其進(jìn)行重寫。java文檔中對于hashCode的一般約定如下:
運行期間多次調(diào)用的返回值應(yīng)該保持一致:
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.根據(jù)
equals()
方法比較結(jié)果為相等的兩個對象在調(diào)用hashCode()
時應(yīng)返回相同的整數(shù):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并不要求根據(jù)
equals()
方法比較結(jié)果為不相等的兩個對象在調(diào)用hashCode()
時必須返回不同的結(jié)果寝杖,但是這樣做可以提高h(yuǎn)ash表的性能: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()
方法 - 如果兩個對象調(diào)用
hashCode()
的返回值相同违施,調(diào)用equals()
的結(jié)果不一定為true(哈希碰撞)
-getClass()
? getClass()
是Object類中的一個方法,對象實例調(diào)用這個方法返回代表實例運行時的類型的Class類瑟幕。
-
什么是Class類呢磕蒲?
Java除了基本類型外其他都是class(包括interface)留潦。JVM每加載一個class,就為其創(chuàng)建一個Class類型的實例辣往,關(guān)聯(lián)起來兔院,并在實例中保存該class的完整信息(例如,類名站削,package名稱坊萝,它的super class,它實現(xiàn)的interface许起,它所有的字段和方法等等)十偶。如果獲取了某個Class實例,則可以獲取到該實力對應(yīng)的class的所有信息园细。通過Class實例獲取class信息的方法稱為反射(Reflection)惦积,也可以理解為在運行期獲取對象類型信息的操作。
如何獲取Class實例猛频?
-
.class
:當(dāng)我們知道數(shù)據(jù)類型(類名)缺沒有實例對象時狮崩,可以通過類名.class
這種形式來獲取Class實例。這也是基本數(shù)據(jù)類型獲取Class實例最方便的一種方法鹿寻。.class
語句也可以被用來獲取多維數(shù)組對應(yīng)的Class實例厉亏。Class cls = String.class; Class c = int[][][].class;
-
Object.getClass()
:這是實例對象擁有的一個方法,只能用于繼承自O(shè)bject的引用類型數(shù)據(jù)(不能用于基本數(shù)據(jù)類型)烈和。boolean b; Class c = b.getClass();//編譯錯誤 Class c = boolean.class;//正確 String str = "hello"; Class cls = str.getClass();
-
Class.forName()
:如果可以獲取完整類名莱找,還可以使用Class類的靜態(tài)方法Class.forName()
执解,將完整類名作為參數(shù)傳入該方法來獲得Class實例。該方法可以用于引用類型和基本數(shù)據(jù)類型审丘。使用該方法還可以利用JVM動態(tài)加載class的特性在運行期根據(jù)條件加載不同的實現(xiàn)類窝趣。Class cls = Class.forName("java.lang.String")疯暑; boolean isClassPresent(String name){ try{ Class.forName(name); return true; }catch(Exception e){ return false; } }
調(diào)用Class實例中的方法可以讓我們獲得class相關(guān)的信息,判斷class的類型哑舒,并且創(chuàng)建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
不但匹配當(dāng)前類型洗鸵,還匹配當(dāng)前類型的子類 - 用
==
判斷Class實例時越锈,只能精確判斷數(shù)據(jù)類型,不能做子類的比較
-
-toString()
? Object的toString()
方法返回一個代表該對象實例的字符串膘滨。一般情況下甘凭,返回的字符串應(yīng)該能準(zhǔn)確得,完善得表達(dá)該對象的內(nèi)容火邓,并且易于閱讀丹弱。推薦所有子類都重寫這個方法德撬。
默認(rèn)的toString()
方法返回一個由類名,‘@’符號躲胳,和轉(zhuǎn)換為十六進(jìn)制的hash code組成:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
當(dāng)我們創(chuàng)建一個新類時蜓洪,可以重寫toString()
方法在控制臺中顯示有關(guān)于類的有用信息。
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
//對于表示對象的內(nèi)容沒有任何意義
}
}
//重寫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類實現(xiàn)了Compareable
接口隆檀,而compareTo()
方法是Compareable
接口唯一需要實現(xiàn)的方法。compareTo()
方法按照字典順序比較兩個字符串北滥,返回一個int
值刚操。比較是基于字符串中每一個字符的Unicode值。按照字典順序排列再芋,當(dāng)該String對象位于參數(shù)字符串之前時菊霜,返回一個負(fù)整數(shù);當(dāng)該String對象位于參數(shù)字符串之后時济赎,返回一個正整數(shù)鉴逞;當(dāng)二者相同時,返回0司训。只有當(dāng)equals(Object)
返回true
時构捡,compareTo()
返回0.
字典順序的定義如下:當(dāng)兩個字符串不同時,它們要么在相同索引處有不同字符(索引位置在兩個字符串中都有效)壳猜,要么兩個字符串的長度不同勾徽,或者以上兩種情況同時存在。
-
當(dāng)兩個字符串在一個或多個索引處有不同的字符時统扳,我們定義一個變量
k
的值為出現(xiàn)不同字符的最小索引的值喘帚。哪個字符串在索引k處的字符數(shù)值越小,按字典順序該字符串就排在另一個字符串前面咒钟。在這種情況下吹由,compareTo()
返回兩個字符串在索引k處的字符值的差值:this.charAt(k) - anotherString.charAt(k);
-
當(dāng)兩個字符串在有效索引范圍內(nèi),相同的索引處沒有出現(xiàn)不同的字符時朱嘴,按字典順序長度較短的字符串排在長度較長的字符串前面倾鲫。在這種情況下,
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:字符串長度不同(結(jié)果為長度差)
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: 字符不同(結(jié)果為字符值的差)
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:二者的內(nèi)容相同
System.out.println(s1.compareTo(s2));//0:兩字符串相同
}
注意:compareTo()
是大小寫敏感的萍嬉,如果需要忽略大小寫乌昔,可以使用compareToIgnoreCase()
。
-concat()
? concat()
可以在給定字符串的末尾拼接一個字符串壤追,例如:"Hello".concat("World")
會返回"HelloWorld"玫荣。concat()
方法的規(guī)則如下:
當(dāng)傳入的字符串參數(shù)長度為0時,
concat()
會返回原字符串對象大诸。當(dāng)傳入的字符串參數(shù)長度不為0時捅厂,調(diào)用
concat()
方法會返回一個由原字符串和傳入的參數(shù)字符串拼接而成的新字符串贯卦。-
可以進(jìn)行鏈?zhǔn)讲僮鳎{(diào)用
concat()
不會改變原字符串的內(nèi)容//在給定字符串末尾拼接字符串 String str1 = "abc"; String str2 = "abcd"; String test = str1.concat(str2); System.out.println(test);//“abcabcd" System.out.println(str1);//"abc"->不改變原字符串 //鏈?zhǔn)讲僮?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()
比較當(dāng)前字符串與作為參數(shù)的字符串是否相同撵割。如果參數(shù)是不為null的String對象,并且字符串中字符的序列與當(dāng)前字符串完全一致辙芍,equals()
返回true啡彬。其他情況下返回false。equals()
方法大小寫敏感故硅,如果需要忽略大小寫進(jìn)行比較庶灿,需要調(diào)用equalsIgnoreCase()
。
String str1 = "abc";
str1.equals("abc");//true
str1.equals("ABC");//false
str1.equalsIgnoreCase("ABC");//true
? contentEquals()
與equals()
功能相似吃衅。contentEquals()
只比較兩者的內(nèi)容是否相同往踢,不檢查比較對象的類型。
String str1 = "hello";
StringBuffer sb1 = "hello";
System.out.println(str1.equals(sb1));//false:因為被比較對象sb1不是String類型
System.out.println(str1.contentEquals(sb1));//true:內(nèi)容相同即返回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()
的第一個參數(shù)為標(biāo)點符號(用于分隔/拼接),后面緊跟著需要拼接的字符串跷敬,字符串之間以逗號分隔讯私。
String str = String.join("-","hello","world");
System.out.println(str);//"hello-world"
join()
可以用來拼接字符串?dāng)?shù)組:
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
類也可以實現(xiàn)拼接字符串的功能西傀。了解更多