抽象類與介面
- 抽象類
- 定義和語法
- 理解抽象類
- 作用
- 抽象類總結:
- 介面
- 概念
- 介面特性
- 注意事項:
- 實作多個介面
- 介面使用實體
- Clonable 介面和深拷貝
- 抽象類和介面的區別?
抽象類
定義和語法
包含抽象方法的類,叫做抽象類 需要用abstract修飾這個類
在Java中,一個類如果被 abstract 修飾類稱為抽象類,抽象類中被 abstract 修飾的方法稱為抽象方法,抽象方法可以沒有具體的實作
抽象類中可以包含其他非抽象方法,也可以包含欄位,非抽象方法和普通方法的規則是一樣的,可以被重寫,也可以被子類直接呼叫
//抽象類
abstract class Shape2{
// 抽象方法
public abstract void draw();
//抽象類可以增加屬性
public int a;
// 抽象類可以增加方法
public void func(){
}
}
理解抽象類
1.不能實體化物件
/* abstract class Shape2{ public abstract void draw(); } */ Shape2 shape = new Shape(); 編譯出錯 error:shape 是抽象的,無法實體化
2.抽象方法不能為 private的
抽象方法沒有加訪問限定符時,默認是public
abstract class Shape2{ private abstract void draw(); } 編譯出錯 error:非法的修飾限定符組合:private + abstract
3.抽象方法不能被 fina l和 static 修飾
因為抽象方法要被子類重寫
abstract class Shape { abstract final void methodA(); abstract public static void methodB(); } 編譯出錯 error:非法的修飾限定符組合:final + abstract 非法的修飾限定符組合:static + abstract
4.抽象類必須被繼承
且繼承后子類要重寫父類中的抽象方法,否則子類也是抽象類,就要使用 abstract修飾
abstract class Shape2{ //如果一個方法沒有具體實作,那么這個方法可以是一個抽象方法 public abstract void draw(); //一個方法是抽象方法,那么存放它的類一定要宣告成 抽象類 } class Cycle extends Shape2{ @Override public void draw() { System.out.println("畫一個?"); } } //如果一個類繼承了抽象類,那么該類一定要實作抽象類里的方法 class Triangle extends Shape2{ @Override public void draw() { System.out.println("畫一個▲"); } } class Flower extends Shape2{ @Override public void draw() { System.out.println("畫一個?"); } }
作用
抽象類存在最大的意義:就是為了被繼承
抽象類本身不能被實體化,要想使用,必須創建該抽象類的子類,然后讓子類重寫抽象類中的抽象方法
抽象類總結:
- 抽象類不可以被實體化,即 不能:Shape2 shape = new Shape();
- 類內的資料成員和普通類沒有區別,即:抽象類內部可以包含普通方法和屬性,甚至構造方法
唯一的就是其成員不能被實體化 - 抽象類生來就是被繼承的
- 若一個類繼承了抽象類,那么該類必須重寫抽象類當中的抽象方法
- 抽象類 / 抽象方法一定不能被 final 修飾
- 一個方法是抽象方法,那么存放它的類一定要宣告成 抽象類
抽象類中不一定包含抽象方法,但是有抽象方法的類一定是抽象類 - 當抽象類A 繼承抽象類B,A可以不重寫B的方法,但一旦A再被繼承,那么一定還要重寫抽象方法
- 抽象方法一定不能被 final 和 static 修飾
介面
概念
在現實生活中,介面的例子很多,比如電腦的USB介面,電源插座…
電腦的USB口上,可以插:U盤、滑鼠、鍵盤…所有符合USB協議的設備
電源插座插孔上,可以插:電腦、電視機、電飯煲…所有符合規范的設備
通過上述例子可以看出:介面就是公共的行為規范標準,大家在實作時,只要符合規范標準,就可以通用,在Java中,介面可以看成是:多個類的公共規范,是一種參考資料型別
介面是抽象類的更進一步,抽象類中還可以包含非抽象方法,和欄位,而介面中包含的方法都是抽象方法,欄位只能包含靜態常量
介面特性
1.介面當中的方法,都是抽象方法
2.介面中可以有具體實作的方法,需要用default修飾,JDK1.8 加入的
.
3.介面中定義的成員變數,默認是常量
.
4.介面是不可以用來實體化的
.
5.介面和類的關系:implements
一旦一個類實作了介面,那么一定要重寫介面當中的方法
.
6.可解決Java單繼承問題
.
7.可發生向上轉型
.
前提是:把一個物件賦值給介面型別之前,一定要保證這個類實作了這個介面
例如下邊代碼:Cycle實作了Shape,故可以發生向上轉型
.
介面中的方法是不能在介面中實作的,只能由實作介面的類來實作
.
注意事項:
- 使用 interface 定義一個介面
- 介面中的方法一定是抽象方法,故 abstract 可以省略
介面中的方法一定格式 public,故 public 可以省略 - 類使用 implements 繼承介面,此處的含義是"實作"
- 介面不能被實體化
- 只要這個類實作了該介面,那么就可發生向上轉型
- 一旦一個類實作了介面,那么一定要重寫介面當中的方法
介面中的方法是不能在介面中實作的,只能由實作介面的類來實作
實作多個介面
有時,我們需要一個類同時繼承多個父類,在有些編程語言中可通過多繼承方式來實作,但Java中,只支持"單繼承",一個類只能 extends 一個父類,但可同時實作多個介面,也能達到類似多繼承的效果
Java 面向物件編程中最常見的用法:一個類繼承一個父類,同時實作多種介面
代碼理解:
class Animal{
protected String name;
//提供構造方法
public Animal(String name){
this.name = name;
}
}
//定義介面
interface IFlying{
void fly();
}
interface IRunning{
void run();
}
interface ISwimming{
void swim();
}
//貓
// 先 extends 后 implements
class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在用四條腿跑");
}
}
//魚
class Fish extends Animal implements ISwimming {
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name + "正在用尾巴游泳");
}
}
//青蛙
class Frog extends Animal implements IRunning, ISwimming {
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在往前跳");
}
@Override
public void swim() {
System.out.println(this.name + "正在蹬腿游泳");
}
}
//鴨子
class Duck extends Animal implements IRunning, ISwimming,IFlying{
public Duck(String name){
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在飛飛飛");
}
@Override
public void run() {
System.out.println(this.name + "正在跑啊跑");
}
@Override
public void swim() {
System.out.println(this.name + "正在游啊泳");
}
}
貓🐱:具有跑能力
魚🐟:具有游泳能力
青蛙🐸:具有跑、游泳能力
鴨子🦆:具有飛、跑、游泳能力
.
可見,把 run,swim,fly 寫在Animal里是不合適的,因為不是每一種動物都具備這些能力
介面主要是對方法的一個抽象,使人時刻牢記多型的好處,讓程式猿忘記型別,有了介面之后, 類的使用者就不必關注具體型別,而只關注某個類是否具備某種能力
再看一個例子:
//機器人
class Robot implements IRunning{
@Override
public void run() {
System.out.println("我是機器人,看誰跑得快!");
}
}
public class InterfaceDemo2 {
public static void walk(IRunning running) {
System.out.println("dudududdududu");
running.run();
}
public static void main(String[] args) {
IRunning iRunning = new Robot();
//iRunning.run();
walk(iRunning);
}
}
輸出結果:

在上述 walk 方法內部,我們并不關注到底是哪種動物,只要引數是會跑的就可以,甚至引數可以不是 “動物”,只要會跑即可
介面使用實體
給一個陣列排序,直接使用Array.sort即可
public static void main(String[] args) { int[] array = {8,6,2,3,4}; Arrays.sort(array); System.out.println(Arrays.toString(array)); }
假設給定一個學生類 Student
class Student {
public String name;
public int age;
public int score;
public Student(String name,int age,int score){
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +'}';
}
}
再創建一個學生陣列,對這個陣列中的元素進行排序
public static void main(String[] args) {
Student student1 = new Student("花",18,100);
Student student2 = new Student("A",20,95);
Student student3 = new Student("B",23,88);
Student[] students = new Student[3];
students[0] = student1;
students[1] = student2;
students[2] = student3;
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
思考:如果使用 Array.sort 方法能否成功對 student 陣列排序?
顯然,是不可以的 ,型別轉換例外

普通陣列,是可以使用 sort 方法進行比較陣列成員的,但上述比較的是兩個學生物件,就不能直接進行比較了,需要我們實作 Comparable 介面,并實作其中的 compareTo 方法
滑鼠放在 implements 后按 Alt + Enter

選擇 Implement methods

選定之后,會出現如下代碼,只需自己重新實作即可

以年齡比較為例:
class Student implements Comparable<Student> {
public String name;
public int age;
public int score;
public Student(String name,int age,int score){
this.name = name;
this.age = age;
this.score = score;
}
@Override
public int compareTo(Student o) {
if (this.age > o.age){
return 1;
}
else if(this.age < o.age){
return -1;
}
else{
return 0;
}
}
}
public static void main(String[] args) {
Student student1 = new Student("花",18,100);
Student student2 = new Student("A",20,95);
Student student3 = new Student("B",17,88);
if(student1.compareTo(student2) < 0) {
System.out.println("student1的年齡 < student2的年齡");
}
}
這樣便可以根據年齡對 Student 進行比較了
此時,再加上 sort 方法,也可實作
public static void main(String[] args) {
Student student1 = new Student("花",18,100);
Student student2 = new Student("A",20,95);
Student student3 = new Student("B",17,88);
Student[] students = new Student[3];
students[0] = student1;
students[1] = student2;
students[2] = student3;
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
會發現輸出結果,是以年齡由小到大排列的

在 sort 方法中會自動呼叫 compareTo 方法,compareTo 的引數是 Object,其實傳入的就是 Student 型別的物件
自定義型別要可比較,必須實作 comparable 或 comparator 介面

Clonable 介面和深拷貝
Clonable 是 Java 內置的一個介面
Object 類中存在一個 clone 方法,呼叫這個方法可以創建一個物件的 “拷貝”,但是要想合法呼叫 clone 方法,必須要先實作 Clonable 介面,否則就會拋出 CloneNotSupportedException 例外
陣列的拷貝:
public static void main(String[] args) {
int[] array = {8,5,2,7,4,6,9};
int[] array2 = array.clone();
array2[0] = 66;
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(array2));
}
由輸出結果,可得,上述拷貝為深拷貝

理解淺拷貝:
兩個參考同時指向一個物件
通過array2[0] ,把person里的某一個資料修改了,那么array[0]去訪問這個資料時,也被修改了!

要想達到深拷貝,不僅要克隆陣列本身,還要把物件也克隆一份

舉例:

而上述的 array 可使用 clone 方法,由于 array 的父類是 object,即相當于 array 是默認繼承于 object,故 array就有clone方法
當前的person也是默認繼承于object,但 person 是自定義型別,不能直接使用 clone 方法
若想克隆自定義型別:
1.實作介面 Cloneable
class Person implements Cloneable {
public int age;
}
但會發現,Cloneable 是一個空介面

空介面 也叫做:標記介面
含義:只要一個類實作了這個介面,那么就標記這個類是可以進行clone的
2.重寫 clone 方法,默認的即可
class Person implements Cloneable {
public int age;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
此時person就擁有了clone方法

選中之后會有報錯,滑鼠放在clone后,按Alt + Enter,選例外(此處隨便選一個,后面會詳細講解例外)

再次報錯!!!
分析:clone 方法的回傳值是 object,因此需要進行強制轉換


此時就可以驗證深拷貝了:
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person) person1.clone();
System.out.println(person1.age);
System.out.println(person2.age);
System.out.println("=======修改========");
person2.age = 52;
System.out.println(person1.age);
System.out.println(person2.age);
}
輸出結果:

可以發現,拷貝后,沒有發生改變,考慮 age是簡單型別的原因,

再新寫一個 Money類
Cloneable 拷貝出的物件是一份 “淺拷貝”
class Money{
double money = 13.14;
}
class Person implements Cloneable {
public int age;
Money m = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

驗證淺拷貝:
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person) person1.clone();
System.out.println(person1.m.money);
System.out.println(person2.m.money);
System.out.println("=======修改========");
person2.m.money = 99.99;
System.out.println(person1.m.money);
System.out.println(person2.m.money);
}

我們可以看到,通過clone,我們只是拷貝了Person物件,但 Person 物件中的 Money 物件,并沒有拷貝,通過 person2 這個參考修改了 m 的值后,person1這個參考訪問m的時候,值也發生了改變,即發生了淺拷貝
那么如何使它達到一個深拷貝?
這里只貼代碼,不做文字決議,大家下去自行理解~
class Money implements Cloneable {
double money = 13.14;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable {
public int age;
Money m = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
//return super.clone();
//1.克隆 person
Person p = (Person) super.clone();
//2.克隆當前 Money物件
p.m = (Money) this.m.clone();
return p;
}
}

注意: clone 方法本身就是一個淺拷貝,若物件里還有物件,只克隆最外邊一層是不能實作克隆的,里邊的也需要被克隆,才能實作深拷貝
抽象類和介面的區別?
| 引數 | 抽象類 | 介面 |
|---|---|---|
| 結構組成 | 普通類 + 抽象方法 | 抽象方法 + 全域變數 |
| 子類使用 | 使用extends關鍵字繼承抽象類 | 使用implements關鍵字實作介面,它需要提供介面中所有宣告的方法的實作 |
| 訪問修飾符 | 抽象方法可以有public、protected和default這些修飾符 | 介面方法默認修飾符是public,不可以使用其它修飾符 |
| 多繼承 | 抽象方法可以繼承一個類和實作多個介面 | 介面不能繼承抽象類,但介面可以使用extends關鍵字繼承一個或多個其它介面 |
| 速度 | 比介面速度快 | 介面有點慢,因為介面要花費時間去尋找在類中實作的方法 |
| 普通方法 | 抽象類中可以包含普通方法和普通欄位,這些普通方法、欄位可以被子類直接使用(不必重寫) | 介面中不能包含普通方法,子類必須重寫所有的抽象方法 |
該吃飯了~你們加油!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/298650.html
標籤:java









