文章目錄
- 一、Object類
- 1.獲取對象信息
- 2.對象比較:equals方法
- 二、再談接口
- 1.比較相關接口
- 2.Cloneable接口和深拷貝
- 三、內部類
- 1.匿名內部類
- 2.實例內部類
- 3.靜態內部類
- 4.局部內部類
在之前的學習中,我們已經了解了有關類以及接口的知識,在本章節中,我們繼續來探究類和接口相關的知識。
一、Object類
Object類是Java默認提供的一個類。在Java中,除了Object類,其余所有的類都存在繼承關系,默認繼承Object類。即所有類實例化出的對象都可以使用Object類的引用接收,如:
class A {}
class B {}
public class Test {public static void test(Object object) {System.out.println(object);}public static void main(String[] args) {test(new A());test(new B());}
}
Object類中也存在一些定義好的方法,在編寫代碼過程中經常使用,接下來我們就來學習一下。
1.獲取對象信息
如果想要打印對象中的內容,可以通過重寫toString的方式來完成,這一點在之前的學習中也已經介紹過,我們來看一下源碼即可:
查看源碼方式:雙擊Shift,因為toString方法在Object類中,所以輸入Object,再尋找toString方法即可。
2.對象比較:equals方法
在Java中,使用 == 進行比較時:
a.如果 == 兩邊為基本數據類型,則比較的是變量的值是否相同。
b.如果 == 兩邊為引用類型,則比較的是"地址"是否相同。
c.當 == 兩邊為引用類型時,如果想比較其內容是否相同,就可以重寫Object中的equals方法,使用equals進行比較(equals默認按照"地址"進行比較)。
舉個例子:
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}
}
public class Test {public static void main(String[] args) {Person person = new Person("小王", 20);Person person1 = new Person("小王", 20);System.out.println(person.equals(person1));}
}
定義一個Person類,實例化兩個對象,并將其姓名和年齡都進行了統一,沒重寫equals方法時,運行結果為"false"。
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age && Objects.equals(name, person.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}
}
public class Test {public static void main(String[] args) {Person person = new Person("小王", 20);Person person1 = new Person("小王", 20);System.out.println(person.equals(person1));}
}
當我們重寫了equals方法之后,此時運行結果就為"true"。
重寫equals方法也很簡單:在空行單擊鼠標右鍵選擇生成,再選擇equals() 和 hashCode(),再一直點擊下一步即可:
當涉及到需要比較對象中的內容是否相同時,一定要重寫equals方法。
對于什么是hashCode(),等我們學習完哈希表之后就會知道。
二、再談接口
1.比較相關接口
我們來看這樣一個例子:
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public class Test {public static void main(String[] args) {Person person = new Person("小王", 20);Person person1 = new Person("小李", 18);}
}
我們定義一個Person類,實例化兩個對象,現在我們想比較兩個對象的大小:
public static void main(String[] args) {Person person = new Person("小王", 20);Person person1 = new Person("小李", 18);System.out.println(person > person1); //error}
對于二元運算符,兩邊的操作數應該為基本數據類型,但此時比較的為引用類型的數據,并不能進行比較。如果我們想比較引用類型的數據,可以實現一個接口:
class Person implements Comparable<Person> {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Person o) {//原始語句//return 0;if (this.age > o.age) {return 1;} else if (this.age < o.age) {return -1;} else {return 0;}}
}
public class Test {public static void main(String[] args) {Person person = new Person("小王", 20);Person person1 = new Person("小李", 18);System.out.println(person.compareTo(person1)); //1}
}
我們實現Comparable接口,"<>"中的為類名,并重寫compareTo方法;在這個例子中,我們根據年齡的大小比較兩個數據。
重寫方法可以使用鼠標右鍵,生成的方式完成。
對于自定義類型,要想比較大小,就需要實現Comparable這個接口,實現這個接口就需要重寫compareTo方法:
對于compareTo方法中的實現邏輯,還可以寫為:
public int compareTo(Person o) {return this.age - o.age;}
返回值為兩個數據中年齡的差值。
equals和compareTo:equals比較的是 是否相等,返回值為布爾類型;compareTo比較的是 大小關系,返回值為整型。
使用Comparable這個接口雖然能解決比較大小的問題,但這個接口對類的侵入性較強:在上面這個例子中,我們通過年齡對 對象進行比較,但當我們想根據其他變量比較大小時,就需要重新編寫和比較相關的所有的代碼,修改成本過高。
我們可以通過Comparator這個接口來解決這個問題:
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}
//根據年齡比較
class AgeCom implements Comparator<Person> {@Overridepublic int compare(Person o1, Person o2) {//原始語句//return 0;return o1.age - o2.age;}
}
//根據姓名比較
class NameCom implements Comparator<Person> {@Overridepublic int compare(Person o1, Person o2) {//name為String類型,String中實現了Comparable接口,重寫了compareTo方法return o1.name.compareTo(o2.name);}
}
public class Test {public static void main(String[] args) {Person person = new Person("小王", 20);Person person1 = new Person("小李", 18);//實例化對象,通過對象調用類中的方法AgeCom ageCom = new AgeCom();System.out.println(ageCom.compare(person, person1));NameCom nameCom = new NameCom();System.out.println(nameCom.compare(person, person1));}
}
在實現Comparator接口時,我們需要根據比較的邏輯重寫compare方法。 實現Comparator接口后,代碼的修改和增加 效率更高,使用更方便。
2.Cloneable接口和深拷貝
class Person {public String name;public int age;public Person(int age) {this.age = age;}//重寫clone方法@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic String toString() {return "Person{" +"age=" + age +'}';}
}
public class Test {public static void main(String[] args) {Person person = new Person(20);Person person1 = person.clone();}
}
此時代碼并不能正常運行,會編譯報錯,我們需要解決三處錯誤才能使代碼正常運行:
第一處錯誤是有關異常的知識,我們之后會學習到。
第三處錯誤:
Cloneable接口的源碼為:
我們稱之為空接口/標記接口,其作用為:證明當前類是可以被克隆的。
我們還需要注意一點:
解決完這三處問題后,代碼就可以正常運行了:
class Person implements Cloneable {public String name;public int age;public Person(int age) {this.age = age;}//重寫clone方法@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic String toString() {return "Person{" +"age=" + age +'}';}
}
public class Test {public static void main(String[] args) throws CloneNotSupportedException {Person person = new Person(20);Person person1 = (Person) person.clone();System.out.println(person);System.out.println(person1);}
}
運行結果為:
執行圖為:
我們對代碼做出如下修改:
class Money {public double money = 66.6;
}
class Person implements Cloneable {public String name;public int age;//組合思想public Money m;public Person(int age) {this.age = age;this.m = new Money();}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic String toString() {return "Person{" +"age=" + age +'}';}
}
public class Test {public static void main(String[] args) throws CloneNotSupportedException {Person person = new Person(20);Person person1 = (Person) person.clone();System.out.println(person.m.money); //66.6System.out.println(person1.m.money); //66.6System.out.println("===============");person1.m.money = 88.8;System.out.println(person.m.money); //88.8System.out.println(person1.m.money); //88.8}
}
我們使用組合的思想增加一個成員變量,并在構造方法中為其實例化一個對象。我們想修改person1的值,但最后卻將person和person1的值都修改了。
在修改值的過程中,會發生如下邏輯:
此時發生的這種現象,我們就稱之為淺拷貝。淺拷貝并沒有將 對象中的對象進行克隆。
但我們只想修改person1的值,應該怎么辦呢?
我們對代碼進行修改:
//實現接口
class Money implements Cloneable {public double money = 66.6;//重寫方法@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
對于Person類中的clone方法的內容進行修改 (核心!!!):
@Overrideprotected Object clone() throws CloneNotSupportedException {//return super.clone();Person tmp = (Person) super.clone();tmp.m = (Money) this.m.clone();return tmp;}
修改完以上兩處,再次運行代碼,只有person1的值被修改了。
流程圖為:
此時發生的這種現象,我們就稱之為深拷貝。區分深淺拷貝主要取決于代碼的實現過程。
三、內部類
在Java中,可以將一個類定義在另一個類或者方法的內部,前者稱為內部類,后者稱為外部類。 內部類也是封裝思想的一種體現。
內部類可以分為:靜態內部類、實例內部類、匿名內部類、局部內部類(幾乎不用)。
class A {class B {//實例內部類}static class C {//靜態內部類}
}
interface D {void test();
}
public class Test {public static void main(String[] args) {new D() {@Overridepublic void test() {}}; //匿名內部類}
}
在編譯之后,內部類也會生成獨立的字節碼文件。一個類對應一個字節碼文件。
1.匿名內部類
對于匿名內部類,可以使用以下兩種方式調用類中的方法:
//方式一new D() {@Overridepublic void test() {System.out.println("嘻嘻");}}.test(); //匿名內部類
//方式二D d = new D() {@Overridepublic void test() {System.out.println("嘻嘻");}}; //匿名內部類d.test();
對于匿名內部類我們可以認為:有一個類實現了一個接口,并在類中重寫了接口中的方法。
public static void main(String[] args) {int ret = 100;D d = new D() {@Overridepublic void test() {System.out.println("值為:" + ret);}}; //匿名內部類d.test();}
在匿名內部類中可以使用初始化的變量,
public static void main(String[] args) {int ret = 100;ret = 200;D d = new D() {@Overridepublic void test() {System.out.println("值為:" + ret); //error}}; //匿名內部類d.test();}
但當變量的值被修改后,代碼就會報錯。在匿名內部類中,不能訪問被修改的數據。
2.實例內部類
我們再來看實例內部類:
class Outer {public int a = 1;private int b = 2;public static int c = 3;//實例內部類class Inner {public int x = 1;private int y = 2;public void test() {System.out.println("內部類");}}public void test() {System.out.println("外部類");}
}
public class Test2 {public static void main(String[] args) {//實例化對象//方式一Outer.Inner inner = new Outer().new Inner();//方式二Outer outer = new Outer();Outer.Inner inner1 = outer.new Inner();System.out.println(inner.x); //1inner.test(); //內部類}
}
在使用內部類時,內部類的實例化需要通過外部類才能實現。
class Inner {public int x = 1;private int y = 2;public static int y = 3;public void test() {System.out.println("內部類");}}
我們在實例內部類中添加一個靜態成員變量,會報錯,這是因為:static修飾的成員不依賴于對象,而內部類卻需要依賴于外部類對象。
public static final int z = 3;
使用final關鍵字修飾就可以解決這個錯誤,final修飾的為常量。
class Inner {public int a = 666;public int x = 1;private int y = 2;public static final int z = 3;public void test() {System.out.println("內部類");System.out.println(a); //666System.out.println(Outer.this.a); //1}}
當外部類和實例內部類中存在同名的成員變量時,優先訪問實例內部類中的成員變量;如果想要訪問外部類中的成員變量,需要使用 “外部類類名.this.成員變量” 的方式訪問。
實例內部類也受訪問修飾限定符的約束。
3.靜態內部類
class Outer1 {public int a = 1;private int b = 2;public static int c = 3;//靜態內部類static class Inner {public int x = 1;private int y = 2;public static int z = 3;public void test() {System.out.println(x); //1System.out.println(c); //3//a為非靜態System.out.println(a); //errorSystem.out.println("內部類");}}
}
public class Test3 {public static void main(String[] args) {//實例化Outer1.Inner inner = new Outer1.Inner();inner.test();}
}
靜態內部類的實例化也較為特殊,需注意。
在靜態內部類中不可以直接調用靜態內部類外的 非靜態的成員。如果想訪問,需要通過外部類對象進行訪問:
public void test() {System.out.println(x); //1System.out.println(c); //3//實例化外部類對象Outer1 outer1 = new Outer1();//通過外部類對象訪問System.out.println(outer1.a); //1System.out.println("內部類");}
4.局部內部類
public void test() {//局部內部類class Inner {public int num = 1;}Inner inner = new Inner();System.out.println(inner.num);}
局部內部類只能在當前定義的 方法體的 內部使用,不能被訪問修飾限定符 修飾,很少使用。
Ending。