中國新聞社級別桌子seo關鍵詞
目錄
1. 抽象類
1.1 抽象類概念
1.2 抽象類語法與特性
1.3 抽象類的作用
2. 接口
2.1 接口的概念
2.2 接口的語法規(guī)則與特性
2.3 實現多個接口(解決多繼承的問題)
2.4 接口間的繼承
2.5 抽象類和接口的區(qū)別
2.6 接口的使用實例
2.7 Clonable 接口和深拷貝
2.7.1 Cloneable接口
2.7.2 深拷貝
淺拷貝
深拷貝
3. Object類
3.1 獲取對象信息
3.2 對象比較equals方法
3.3 hashcode方法
1. 抽象類
1.1 抽象類概念
在面向對象的概念中,所有的對象都是通過類來描繪的,但是反過來,并不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。比如:
在打印圖形例子中, 我們發(fā)現, 父類 Shape 中的 draw 方法好像并沒有什么實際工作, 主要的繪制圖形都是由 Shape的各種子類的 draw 方法來完成的. 像這種沒有實際工作的方法, 我們可以把它設計成一個 抽象方法(abstract method), 包含抽象方法的類我們稱為 抽象類(abstract class).
Shape這個類不可以表述成一個具體的對象, 所以我們可以把它定義成一個抽象類. 而抽象類的定義只需在class
關鍵字前加上abstract
即可, 此時這個類就是抽象類.
abstract class Shape {//抽象方法public abstract void draw();
}
并且我們可以看到, Shape中的draw()有沒有具體的實現并不重要, 因為子類都重寫了該方法, 那么如果不想在Shape中實現它, 給它加上abstract關鍵字, 這樣就可以不寫具體的實現了.
注意, 一旦把方法定義成抽象方法, 就必須是在抽象類中.
當然, 在這個類中是可以有其他的字段和方法的.
abstract class Shape {public static int a = 10;//抽象方法public abstract void draw();public void func() {}
}
其實抽象類本身里面的成員和普通類沒有區(qū)別, 只不過它多了一個抽象方法.
1.2 抽象類語法與特性
1. 抽象類 使用abstract修飾類
2. 抽象類當中 可以包含普通類所能包含的成員
3. 抽象類和普通類不一樣的是, 抽象類當中可以包含抽象方法
4. 抽象方法是使用abstract修飾的。這個方法 沒有具體的實現
5. 不能實例化抽象類 new
6. 抽象類存在的最大的意義 就是為了被繼承
7. 如果一個普通類 繼承了一個抽象類 此時必須重寫抽象類當中的方法
運行以下代碼, 也能發(fā)生動態(tài)綁定.
abstract class Shape {//抽象方法public abstract void draw();
}class Rect extends Shape {@Overridepublic void draw() {System.out.println("矩形!");}
}class Cycle extends Shape {@Overridepublic void draw() {System.out.println("畫圓!");}
}class Triangle extends Shape {@Overridepublic void draw() {System.out.println("畫一個三角形!");}
}class Flower extends Shape {@Overridepublic void draw() {System.out.println("?!");}
}public class Test {public static void drawMap(Shape shape) {shape.draw();}public static void main(String[] args) {Rect rect = new Rect();drawMap(rect);drawMap(new Cycle());drawMap(new Triangle());drawMap(new Flower());}public static void main1(String[] args) {//Shape shape = new Shape(); //errShape shape = new Rect();}
}
8. 如果一個抽象類A 繼承了一個抽象類B,此時A當中 不需要重寫B(tài)中的抽象方法。但是如果A再被普通類繼承,就需要重寫。
abstract class A extends Shape {}
class B extends A {// 必須要重寫, 否則報錯@Overridepublic void draw() {System.out.println("dsfsasa");}
}
9. 抽象方法 不能是私有的. 也就是要滿足重寫的規(guī)則.
10. 抽象方法不能被final和static修飾, 因為抽象方法要被子類重寫
11. 抽象類當中 可以有構造方法。為了方便子類能夠調用,來初始化抽象類當中的成員
1.3 抽象類的作用
抽象類本身不能被實例化, 要想使用, 只能創(chuàng)建該抽象類的子類. 然后讓子類重寫抽象類中的抽象方法.
使用抽象類相當于多了一重編譯器的校驗, 如上面的代碼, 實際工作不應該由父類完成, 而應由子類完成. 那么此時如果不小心誤用成父類了, 使用普通類編譯器是不會報錯的. 但是父類是抽象類就會在實例化的時候提示錯誤, 讓我們盡早發(fā)現問題.
很多語法存在的意義都是為了 "預防出錯", 例如我們曾經用過的 final 也是類似. 創(chuàng)建的變量用戶不去修改, 不就相當于常量嘛? 但是加上 final 能夠在不小心誤修改的時候, 讓編譯器及時提醒我們.
充分利用編譯器的校驗, 在實際開發(fā)中是非常有意義的.
2. 接口
2.1 接口的概念
在現實生活中,接口的例子比比皆是,比如:筆記本上的USB口,電源插座等。
電腦的USB口上,可以插:U盤、鼠標、鍵盤...所有符合USB協(xié)議的設備
電源插座插孔上,可以插:電腦、電視機、電飯煲...所有符合規(guī)范的設備
通過上述例子可以看出:接口就是公共的行為規(guī)范標準,大家在實現時,只要符合規(guī)范標準,就可以通用。
在Java中,接口可以看成是:多個類的公共規(guī)范,是一種引用數據類型。
接口可以認為是 行為的標準/規(guī)范
2.2 接口的語法規(guī)則與特性
接口的定義格式與定義類的格式基本相同,將class關鍵字換成interface
關鍵字,就定義了一個接口。
1. 使用interface
來修飾 接口
2. 接口當中的成員方法,不能有具體的實現。[ public ]
(1) 抽象方法:默認是public abstract
的方法
(2) JDK1.8開始 允許有可以實現的方法,但是這個方法只能是由default
修飾的
(3) 可以實現 有一個靜態(tài)方法
3. 成員變量默認是public static final
修飾的
4. 接口不能被實例化。
interface ITest {int size = 10; // public static finalvoid draw(); // public abstract, 必須被重寫default public void func() {System.out.println("默認方法!");}public static void func2() {System.out.println("Fsafsaa");}
}
5. 類和接口之間采用 implements
來實現多個接口
class A implements ITest {@Overridepublic void draw() {System.out.println("必須重寫的!"); // 必須重寫抽象方法}/*@Overridepublic void func() {System.out.println("這個方法的重寫 是可以選擇的! ");}*/
}
6. 子類重寫抽象方法,必須加上public
.
7. 接口中不能有靜態(tài)代碼塊和構造方法
8.如果你不想實現接口的方法,那么就把這個類定義為抽象類。但是如果這個類 被其他類繼承 那么必須重寫
9.一個類 可以實現 多個接口。 使用implements
用逗號隔開[可以解決多繼承的問題]
我們把前面畫圖形的例子改成用接口實現.
class Rect implements IShape {@Overridepublic void draw() {System.out.println("矩形!");}
}class Flower implements IShape {@Overridepublic void draw() {System.out.println("花!");}
}class Cycle implements IShape {@Overridepublic void draw() {System.out.println("圓!");}
}public class Test2 {public static void drawMap(IShape iShape) {iShape.draw();}public static void main(String[] args) {Rect rect = new Rect();drawMap(rect);drawMap(new Cycle());drawMap(new Flower());//IShape iShape = new Cycle();}
}
注意:子類和父類之間是extends 繼承關系,類與接口之間是 implements 實現關系。
2.3 實現多個接口(解決多繼承的問題)
在Java中,類和類之間是單繼承的,一個類只能有一個父類,即Java中不支持多繼承,但是一個類可以實現多個接口。
interface IFLying {void flying();
}interface ISwimming {void swimming();
}interface IRunning {void running();
}class Animal {public String name;public int age;public Animal(String name, int age) {this.name = name;this.age = age;}public void eat() {System.out.println("吃飯!");}
}class Dog extends Animal implements IRunning, ISwimming {public Dog(String name, int age) {super(name, age);}@Overridepublic void swimming() {System.out.println(name + " 正在游泳!");}@Overridepublic void running() {System.out.println(name + " 正在跑!");}@Overridepublic void eat() {System.out.println(name + "正在吃狗糧!");}
}class Bird extends Animal implements IFLying {public Bird(String name, int age) {super(name, age);}@Overridepublic void flying() {System.out.println(name + "正在飛!");}@Overridepublic void eat() {System.out.println(name + "正在鳥糧!");}
}class Duck extends Animal implements IFLying, IRunning, ISwimming {public Duck(String name, int age) {super(name, age);}@Overridepublic void flying() {System.out.println(name + "正在飛!");}@Overridepublic void swimming() {System.out.println(name + " 正在游泳!");}@Overridepublic void running() {System.out.println(name + " 正在跑!");}@Overridepublic void eat() {System.out.println(name + "正在吃鴨糧!");}
}
注意:一個類實現多個接口時,每個接口中的抽象方法都要實現,否則類必須設置為抽象類。
上面的代碼展示了 Java 面向對象編程中最常見的用法: 一個類繼承一個父類, 同時實現多種接口.
2.4 接口間的繼承
在Java中,類和類之間是單繼承的,一個類可以實現多個接口,接口與接口之間可以多繼承。即:用接口可以達到多繼承的目的。
interface A1 {void func();
}interface B1 {void func2();
}interface D extends A1,B1 {void func3();
}
class E implements D {@Overridepublic void func() {}@Overridepublic void func2() {}@Overridepublic void func3() {}
}class C1 implements A1,B1 {@Overridepublic void func() {}@Overridepublic void func2() {}
}
接口間的繼承相當于把多個接口合并在一起.
2.5 抽象類和接口的區(qū)別
核心區(qū)別: 抽象類中可以包含普通方法和普通字段, 這樣的普通方法和字段可以被子類直接使用(不必重寫), 而接口中不能包含普通方法, 子類必須重寫所有的抽象方法.
No | 區(qū)別 | 抽象類(abstract) | 接口(interface) |
---|---|---|---|
1 | 結構組成 | 普通類+抽象方法 | 抽象方法+全局常量 |
2 | 權限 | 各種權限 | public |
3 | 子類使用 | 使用extends關鍵字繼承抽象類 | 使用implements關鍵字實現接口 |
4 | 關系 | 一個抽象類可以實現若干接口 | 接口不能繼承抽象類, 但是接口可以使用extends關鍵字繼承多個父接口 |
5 | 子類限制 | 一個子類只能繼承一個抽象類 | 一個子類可以實現多個接口 |
2.6 接口的使用實例
以下代碼實現了對數組的簡單的排序:
class Student {public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}public class Test {public static void main1(String[] args) {int[] array = {1, 4, 2, 7, 3, 8, 5};Arrays.sort(array);System.out.println(Arrays.toString(array));}
}
接下來我們需求變了, 我們不再排序簡單的數據.
public static void main(String[] args) {// 有一個學生數組, 數組中有三個學生Student[] students = new Student[3];students[0] = new Student("bit", 10);students[1] = new Student("hello", 40);students[2] = new Student("gbc", 5);// 現在想對數組排序Arrays.sort(students);System.out.println(Arrays.toString(students));}
根據年齡還是姓名進行排序?
可以看到,執(zhí)行上述代碼會直接報異常了, 這個異常是個類型轉換異常(ClassCastException
). 后面的意思是Student不能轉化為Comparable.
通過點擊灰色部分可以進到出錯的位置, 首先可以知道是第29行出錯, 再點擊其中的第320行的灰色部分.
可以看到, 它是把傳進來的數組強制轉化成Comparable, 也就是說它認為底層在給數組排序的時候, 需要把下標的元素強轉成Comparable. 那么又為什么要把好好的Student轉化成Comparable?
首先, 如果是一個自定義類型, 是需要我們自己指定比較的規(guī)則的. 因為目前的Student是根據什么比較的我們根本看不出來, 我們需要指定要根據什么來進行比較, 比如姓名, 年齡...也就是說, 我們自定義的學生類, 需要具備可以比較的功能? 怎么具備呢?
假設現在不是學生, 如果現在有一個字符串數組, 要對數組進行排序, Arrays.sort()
比較的是字符串:
public static void main(String[] args) {String[] strings = {"abc", "hello", "bcd"};Arrays.sort(strings);System.out.println(Arrays.toString(strings));}
可以看到, 此時是按照了字母順序排好序的. 那么代碼層面看, 它是如何排好序的? 可以看String的源碼.
會發(fā)現, String實現了Comparable<String>
接口, 那么我們就可以模仿這個來寫.
所以, Student要可比較, 就要實現 Comparable<Student>
接口, 只有實現這個接口, 才能讓Student具備可比較的功能.
要實現Comparable<String>
接口, 我們就要看它的源碼:
可以看到方法compareTo()
, 那么這個方法是接口中的方法, 所以需要重寫.
@Overridepublic int compareTo(Student o) {// 根據年齡比較 if (this.age - o.age > 0) {return 1;} else if (this.age - o.age < 0) {return -1;} else {return 0;}}
再次執(zhí)行前面的學生數組排序程序, 可以看到, 此時就根據年齡排成有序了.
實現Comparable接口使得Student可比較后, 我們可以使用compareTo()方法更直觀的感受到Student具備了的可比較的功能.
public static void main(String[] args) {Student student1 = new Student("bit", 10);Student student2 = new Student("hello", 40);if (student1.compareTo(student2) > 0) {System.out.println("student1 > student2");} else {System.out.println("student1 < student2");}}
總結: 如果我們以后 自定義的類型, 如果要比較大小, 那么必須要讓這個類具備可以比較的功能. 此時可以選擇實現Comparable接口.
但是我們會發(fā)現這樣寫有一個不好的地方, 更換了需求, 比如此時要根據姓名比較, 那么就意味著要修改重寫的compareTo()
的代碼邏輯, 但是假設代碼已經使用了很長一段時間, 此時貿然修改這部分代碼, 會引起非常大的風險, 在下次項目上線的時候, 就很有可能會出現很大的bug.
Java提供了另一個接口 Comparator
, 靈活性更強.
class AgeComparator implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.age - o2.age;}
}
可以看到, 我們重新寫了另外一個類, 實現了Comparator
接口, 而且并沒有在Student類上實現該接口.
那么我們可以通過Comparator源碼看到, 它有一個compare()
方法.
所以我們重寫compare()
方法即可.
Arrays.sort()
是可以傳兩個參數的, 此時第二個參數可以是實現了Comparator
接口的一個對象
public static void main(String[] args) {Student[] students = new Student[3];students[0] = new Student("bit", 10);students[1] = new Student("hello", 40);students[2] = new Student("gbc", 5);AgeComparator ageComparator = new AgeComparator();Arrays.sort(students, ageComparator);System.out.println(Arrays.toString(students));}
運行可以看到, 它就根據年齡從小到大進行排序了.
也可以更靈活一點, 如果要根據姓名進行比較:
class NameComparator implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.name.compareTo(o2.name);}
}
public static void main(String[] args) {Student[] students = new Student[3];students[0] = new Student("bit", 10);students[1] = new Student("hello", 40);students[2] = new Student("gbc", 5);NameComparator nameComparator = new NameComparator();Arrays.sort(students, nameComparator);System.out.println(Arrays.toString(students));}
可以看到, 此時就根據姓名進行比較了.
于是我們可以知道, 要通過什么來進行排序, 都可以建一個類來實現Comparator
接口.
那么我們會注意到, Comparable接口對類的侵入性非常強, 而Comparator對類的侵入性就比較弱.
Comparator又叫做比較器
public static void main(String[] args) {Student student1 = new Student("bit", 10);Student student2 = new Student("hello", 40);AgeComparator ageComparator = new AgeComparator();if (ageComparator.compare(student1, student2) > 0) {System.out.println("student1 > student2");} else {System.out.println("student1 < student2");}}
我們也可以利用比較器寫一個冒泡排序:
public static void bubbleSort(Comparable[] array) {for (int i = 0; i < array.length - 1; i++) {for (int j = 0; j < array.length - 1 - i; j++) {if (array[j].compareTo(array[j + 1]) > 0) {Comparable tmp = array[j];array[j] = array[j + 1];array[j + 1] = tmp;}}}}
public static void main(String[] args) {Student[] students = new Student[3];students[0] = new Student("bit", 10);students[1] = new Student("hello", 40);students[2] = new Student("gbc", 5);bubbleSort(students);System.out.println(Arrays.toString(students));}
2.7 Clonable 接口和深拷貝
2.7.1 Cloneable接口
class Person {public int id;@Overridepublic String toString() {return "Person{" +"id=" + id +'}';}
}public class Test2 {public static void main(String[] args){Person person = new Person();}
}
如上代碼所示, 有一個person對象, 現在假設需要克隆一下這個person對象. 也就是說, 有一個一模一樣的person對象. 那么此時上面代碼的內存圖如下所示.
現在要求把這個對象拷貝(克隆)一份, 就需要讓Person實現Cloneable
接口.
然后在Person中重寫Object.clone()
方法:
@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
最后在main上聲明異常, 并強轉person.clone的結果:
public class Test2 {public static void main(String[] args) throws CloneNotSupportedException {Person person = new Person();Person person2 = (Person) person.clone();System.out.println(person);System.out.println(person2);}
}
于是當以上代碼寫完之后, 內存分布如下:
運行結果:
2.7.2 深拷貝
深拷貝是什么? 與之相對應的, 淺拷貝又是什么?
淺拷貝
我們更改一下上面Cloneable的示例代碼,
class Money {public double m = 12.5;
}class Person implements Cloneable {public int id;public Money money = new Money();@Overridepublic String toString() {return "Person{" +"id=" + id +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
添加了Money類, 并在Person中加入成員屬性, 那么此時的內存分布如下.
在main中代碼不變的情況下, 其克隆的內存效果圖如下.
同時:
System.out.println("person:" + person.money.m);System.out.println("person2:" + person2.money.m);
運行結果:
那么可以看到, money是指向的同一個對象, 如果是這樣就會有一個問題:
person2.money.m = 1999;
可以看到, person和person2的money都被改成了1999, 這是我們不希望看到的, 我們預期的是把money也拷貝一份.
以上這就是淺拷貝, 并沒有把person中作為對象的成員屬性也進行拷貝. 我們希望能夠達到深拷貝, 如下圖, 把money也拷貝了過去.
深拷貝
可以看到, 此時修改person2.money.m
的值, 并不會影響原來person.money.m
的值, 這就是深拷貝.
class Money implements Cloneable {public double m = 12.5;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
注意此時如果只在Money中重寫clone(), 依然是不符合預期結果的, 顯然這個clone()并沒有被調用.
class Person implements Cloneable {public int id;public Money money = new Money();@Overrideprotected Object clone() throws CloneNotSupportedException {//return super.clone();Person tmp = (Person) super.clone(); // 克隆了Persontmp.money = (Money) this.money.clone(); // 克隆了Moneyreturn tmp;}@Overridepublic String toString() {return "Person{" +"id=" + id +'}';}
}
3. Object類
Object是Java默認提供的一個類。Java里面除了Object類,所有的類都是存在繼承關系的。默認會繼承Object父類。即所有類的對象都可以使用Object的引用進行接收。
范例:使用Object接收所有類的對象
class Person{
}class Student{
}public class Test {public static void main(String[] args) {function(new Person());function(new Student());}public static void function(Object obj) {System.out.println(obj);}
}
執(zhí)行結果:
Person@1b6d3586
Student@4554617c
所以在開發(fā)之中,Object類是參數的最高統(tǒng)一類型。但是Object類也存在有定義好的一些方法。如下:
對于整個Object類中的方法需要實現全部掌握。
在這里,我們主要熟悉這幾個方法:toString()
方法,equals()
方法,hashcode()
方法
3.1 獲取對象信息
在sout(對象)
的時候, 底層就是調用的Object.toString()
, 然后toString()
中有調用到hashCode()
那么hashCode()
是什么?
可以看到, hashCode()
返回的是一個int類型的值, 然后把它變成十六進制(toHexString
)
在public native int hashCode();
的注釋中可以看到:
Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by java.util.HashMap.
返回對象的 哈希代碼值。支持此方法是因為可以使用諸如java.util.HashMap提供的哈希表之類的哈希表。
3.2 對象比較equals方法
在Java中,==進行比較時:
a.如果==左右兩側是基本類型變量,比較的是變量中值是否相同
b.如果==左右兩側是引用類型變量,比較的是引用變量地址是否相同
c.如果要比較對象中內容,必須重寫Object中的equals方法,因為equals方法默認也是按照地址比較的
class Person {private String name;private int age;public Person(String name, int age) {this.age = age;this.name = name;}
}public class Test6 {public static void main(String[] args) {Person person1 = new Person("張三", 18);Person person2 = new Person("張三", 18);}
}
可以看到, 運行結果是false, 但是我們希望的結果的true.
進行調試:
那么怎么辦?
我們預期是認為person1和person2是兩個一樣的人, 那么就需要在Person中自己重寫equals()
方法.
@Overridepublic boolean equals(Object obj) {if (obj == null) {return false;}if (this == obj) {return true;}// 不是Person類對象 是不是同種類型if (!(obj instanceof Person)) {return false;}Person per = (Person) obj;if (this.name.equals(per.name) && this.age == per.age) {return true;}return false;}
再次運行程序:
如果以后寫了自定義類型, 那么就需要注意重寫 equals()
方法
結論:比較對象中內容是否相同的時候,一定要重寫equals方法。
3.3 hashcode方法
前面有提到過hashCode, 這個方法是幫我們計算一個具體的對象位置.
class Person {private String name ;private int age ;public Person(String name, int age) {this.age = age ;this.name = name ;}
}public class Test6 {public static void main(String[] args) {Person person1 = new Person("張三",18);Person person2 = new Person("張三",18);System.out.println(person1.hashCode());System.out.println(person2.hashCode());}
}
可以看到, 兩個person的hash數值不一樣. 但是在數據結構的哈希表中如果兩個人都叫 張三 的話, 這個時候就要看hashCode能否把它倆放到同一個位置上.
像重寫equals方法一樣,我們也可以重寫hashcode()方法。
@Overridepublic int hashCode() {//計算對象的位置return Objects.hash(name, age);}
1、hashcode方法用來確定對象在內存中存儲的位置是否相同
2、事實上hashCode() 在散列表中才有用,在其它情況下沒用。在散列表中hashCode() 的作用是獲取對象的散列碼,進而確定該對象在散列表中的位置。