接口
接口是一種與類相似的結構,只包含常量和抽象方法。它的目的是指明相關或者不相關的多個對象的共同行為。例如,使用正確的接口,可以指明這些對象是可比較的、可食用的以及可克隆的。接口是對類的一組需求描述,這些類要遵從接口描述的統一格式進行定義。
可以使用Edible接口來明確一個對象是否是可食用的。這需要使用implements關鍵字讓對象的類實現這個接口來完成
package edu.uestc.avatar; /** * 可食用接口 * 在jdk1.8以前,接口中只能包含常量和抽象方法。 * 定義的方法默認就是public abstract * */ public interface Ediable { /** * 所有可食用的都具有怎么吃共同的行為 * 接口中的所有方法自動地屬於public。因此public可以省略 */ String howToEat(); }
Animal類定義了sound方法。這是個抽象方法,將被具體的動物類所實現
package edu.uestc.avatar; public abstract class Animal { public abstract String sound(); }
Chicken類實現了Edible接口表明小雞是可食用的。當一個類實現一個接口時,該類用同樣的簽名和返回值類型實現定義在接口中的所有方法。小雞類也繼承Animal類並實現sound方法。
package edu.uestc.avatar; public class Chicken extends Animal implements Ediable{ @Override public String sound() { return "老雞罵小雞,你是個壞東西,教你咯咯咯,你偏嘰嘰嘰。。。。"; } @Override public String howToEat() { return "用啤酒炸雞......"; } }
Tiger是動物但不可食用。繼承自Animal類。
package edu.uestc.avatar; public class Tiger extends Animal{ @Override public String sound() { return "兩只老虎跑的快,跑得快"; } }
Fruit類實現Edible。因為它不實現howToEat方法,所以Fruit類必須為abstract的,Fruit的子類必須實現howToEat方法(Apple類和Orange類)
package edu.uestc.avatar; /** * 實現接口:implements,某個類實現了某個接口,就代表這個是滿足這個接口規范的這一類事物 * 要求實現該接口所有的抽象方法 */ public abstract class Fruit implements Ediable{ } package edu.uestc.avatar; public class Orange extends Fruit{ @Override public String howToEat() { return "將orange榨汁"; } } package edu.uestc.avatar; //這兒將Apple換為Banana,道理一樣 public class Banana extends Fruit{ @Override public String howToEat() { return "apple..."; } }
測試類
package edu.uestc.avatar; public class EidableTest { public static void main(String[] args) { Object[] instances = {new Tiger(),new Chicken(),new Orange(),new Banana()}; for(Object obj : instances) { if(obj instanceof Ediable) System.out.println(((Ediable)obj).howToEat()); if(obj instanceof Animal) System.out.println(((Animal)obj).sound()); } } }
Comparable接口
現在假設希望使用Arrays類的sort方法對Circle對象數組進行排序,Circle類就必須實現Comparable接口,需要實現里面的compareTo方法。假設希望根據圓的面積進行比較。如果第一個圓的面積小於第二個圓的面積就返回-1,如果相等就返回0,否則返回-1.
package edu.uestc.avatar.demo; public class Circle implements Comparable<Circle>,Cloneable{ private double radius; public Circle(double radius) { this.radius = radius; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } public double getArea() { return Math.PI * radius * radius; } @Override public String toString() { return "Circle [radius=" + radius + ",area: " +getArea()+ "]"; } /** * 比較規則:如果當前對象比circle小,返回小於0的整數,如果相等,返回0,如果大於circle,返回一個大於0的整數 */ @Override public int compareTo(Circle circle) { return this.radius == circle.radius ? 0 : this.radius < circle.radius ? -1 : 1; } /** * 覆蓋父類的clone方法,以便子類實例可供調用 */ @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
Cloneable接口
當拷貝一個變量時,原始變量和拷貝變量引用同一個對象,也就是說,改變一個變量所引用的對象將會對另一個變量產生影響;如果創建一個對象的新copy,它的初始狀態和原始對象一樣,但以后將可以各自改變各自的狀態,那就需要使用clone方法
Circle copy = circle.clone();
copy.getArea();
不過,事情並沒有這么簡單。clone方法是Object類的一個protected方法,也就是說,在用戶編寫的代碼中不能直接調用它。只有Employee類才能夠克隆Employee對象。這種限制有一定的道理。這里査看一下Object類實現的clone方法。由於這個類對具體的類對象一無所知,所以只能將各個域進行對應的拷貝。如果對象中的所有數據域都屬於數值或基本類型,這樣拷貝域沒有任何問題。但是,如果在對象中包含了子對象的引用,拷貝的結果會使得兩個域引用同一個子對象,因此原始對象與克隆對象共享這部分信息。
默認的克隆操作是淺拷貝,它並沒有克隆包含在對象中的內部對象。
如果進行淺拷貝會發生什么呢?這要根據具體情況而定。如果原始對象與淺克隆對象共享的子對象是不可變的,將不會產生任何問題。也確實存在這種情形。例如,子對象屬於像String類這樣的不允許改變的類,也有可能子對象在其生命周期內不會發生變化,既沒有更改它們的方法,也沒有創建對它引用的方法。
然而,更常見的情況是子對象可變,因此必須重新定義clone方法,以便實現克隆子對象的深拷貝。在列舉的示例中,hireDay域屬於Date類,這就是一個可變的子對象,
對於每一個類,都需要做出下列判斷:
-
默認的clone方法是否滿足要求。
-
默認的clone方法是否能夠通過調用可變子對象的clone得到修補。
-
是否不應該使用clone。
實際上,選項3是默認的。如果要選擇1或2,類必須:
-
實現Cloneable接口。
-
使用public訪問修飾符重新定義clone方法。
必須謹慎地實現子類的克隆。
接口與抽象類
接口 | 抽象類 |
不考慮java8中default方法的情況下,接口中是沒有實現代碼的實現 | 抽象類中可以有普通成員方法 ,並且可以定義變量 |
接口中的方法修飾符號 只能是public |
抽象類中的抽象方法可以有public ,protected ,default |
接口中沒有構造方法 | 可以有構造方法 |
選擇:
1、當我們需要一組規范的方法的時候,我們就可以用接口,在具體的業務中,來對接口進行實現,能達到以不變應對萬變,多變的需求的情況我們只需要改變對應的實現類 。
2、如果多個實現類中有者相同可以復用的代碼 這個時候就可以在實現類和接口之間,添加一個抽象類,把公共的代碼抽出在抽象類中。然后要求不同實現過程的 子類可以重寫抽象類中的方法,來完成各自的業務。
接口與回調
回調(callback)是一種常見的程序設計模式。在這種模式中,可以指出某個特定事件發生時應該采取的動作。如在java.swing包中有一個Timer類,可以使用它在給定的事件間隔時發出通告。
如程序中有一個時鍾,請求每秒鍾獲得一個通告,以便更新時鍾的畫面。定時器需要直到調用哪一個方法,並要求傳遞的對象實現了java.awt.ActionListner接口.
public interface ActionListener extends EventListener { //回調方法,ActionEvent提供了事件的相關信息 public void actionPerformed(ActionEvent e); }
當到達指定時間間隔時,定時器就調用actionPerformed方法。
案例:每10秒鍾打印一條信息”At the tone,the time is ...“
package edu.uestc.avatar.beep; import javax.swing.JOptionPane; import javax.swing.Timer; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.time.LocalDateTime; public class BeepDemo { public static void main(String[] args) { /** * 創建一個定時器 * 每10000毫秒觸發定時器,定時器就會調用該事件里的回調方法 */ Timer timer = new Timer(1000, new BeepActionListner()); //啟動定時器 timer.start(); JOptionPane.showMessageDialog(null, "退出定時器"); System.exit(0); } } class BeepActionListner implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println("At the tone,the time is " + LocalDateTime.now()); Toolkit.getDefaultToolkit().beep(); } }
內部類
內部類就是在類的內部定義的類,為什么需要使用內部類:
1.一個內部類的對象能夠訪問創建它的對象的實現,包括私有數據。
2. 對於同一個包中的其他類來說,內部類能夠隱藏起來。
3.匿名內部類可以很方便的定義回調。
4.使用內部類可以非常方便的編寫事件驅動程序
package edu.uestc.avatar; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.time.LocalDateTime; import javax.swing.Timer; public class Talking { private boolean beep; private int interval = 1000; public Talking(boolean beep, int interval) { this.beep = beep; this.interval = interval; } public void start() { TimerPrintActionListener listener = new TimerPrintActionListener(); Timer timer = new Timer(interval, listener); timer.start(); } /** * TimerPrintActionListner位於Talking內部----內部類 */ public class TimerPrintActionListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println("注意,當前時間:" + LocalDateTime.now()); //內部類可以直接訪問外部類的數據 if(beep) Toolkit.getDefaultToolkit().beep(); } } }
內部類的特殊語法規則
內部類有一個外圍類的引用,使用外圍類的引用語法:OuterClass.this
public void actionPerformed(ActionEvent e) { System.out.println("注意,當前時間:" + LocalDateTime.now()); if(Talking.this.beep) Toolkit.getDefaultToolkit().beep(); }
反過來,可以采用下列語法更加明確地編寫內部類對象的構造器:outerObject.new InnerClass(costruction params)
1)成員內部類:定義在類的內部,方法的外部,
A.特點:a.作為類的一個成員,有4個權限修飾符:public (default) protected private
b.作為一個類,可以用abstract、final修飾,也有構造器,也可以在類里定義屬性、方法
B.成員內部類的注意事項:
a.非靜態成員內部類:
1)創建對象的方式:先有外部類的對象,再通過外部類對象調用內部類的構造器,格式:外部類對象.new 內部類()
2)調內部類的屬性,可以用"this."來指明;
調外部類的不同名屬性,直接調用即可;
調外部類的同名屬性:外部類的類名.this.同名屬性:表示外部類的當前對象的屬性
3)不能有靜態的屬性和方法
b.靜態成員內部類:
1)創建對象的方式:調用構造器的方式:外部類類名.內部類()
2)可以有非靜態的屬性和方法
3)靜態內部類只能調用外部類的靜態屬性、方法,不能調用外部類的非靜態屬性、方法
package edu.uestc.avatar; /** * 只是為了把一個類隱藏在另外一個類的內部,並不需要內部類引用外圍對象,可以把內部類聲明為static的,以便取消產生的引用 * 查找數字中的最大值和最小值 * @author Adan * */ public class ArrayAlg { public static Pair getMinAndMax(int[] list) { int min = list[0], max = list[0]; for(int i = 1; i < list.length; i++) { if(min > list[i]) min = list[i]; if(max < list[i])max = list[i]; } return new Pair(min, max); } public static class Pair{ private int min; private int max; public Pair(int min,int max) { this.min = min; this.max = max; } public int getMin() { return min; } public int getMax() { return max; } } }
C.成員內部類的優勢:成員內部類作為外部類的成員,可以直接訪問外部類的私有屬性。
2)局部內部類:定義在方法的內部,對於局部內部類我們常常使用一個方法,得到一個接口實現類的對象。局部內部類的優勢:通過方法非常方便的得到一個接口實現類的對象。
package edu.uestc.avatar; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.time.LocalDateTime; import javax.swing.Timer; import edu.uestc.avatar.Talking.TimerPrintActionListener; /** * 局部內部類 * 發現:前面的TimerPrintActionListener類只是在Talking類的start()方法內部使用,可以使用局部內部類 * */ public class LocalInnerClassTalking { private boolean beep; private int interval = 1000; public LocalInnerClassTalking(boolean beep, int interval) { this.beep = beep; this.interval = interval; } public void start() { class TimerPrintActionListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println("注意,當前時間:" + LocalDateTime.now()); //內部類可以直接訪問外部類的數據 if(beep) Toolkit.getDefaultToolkit().beep(); } } TimerPrintActionListener listener = new TimerPrintActionListener(); Timer timer = new Timer(interval, listener); timer.start(); } }
注意:匿名內部類通過使用"new 接口(){}"的方式用其隱含實現一個接口或抽象類,實現的部分寫在大括號內。
package edu.uestc.avatar; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.time.LocalDateTime; import javax.swing.Timer; public class AnonymousInnerClassTalking { private int interval; private boolean beep; public AnonymousInnerClassTalking(int interval, boolean beep) { this.interval = interval; this.beep = beep; } public void start() { //匿名內部類:new的是實現了ActionListener接口類的實例,該類沒有名字 Timer timer = new Timer(interval, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("注意,當前時間:" + LocalDateTime.now()); //內部類可以直接訪問外部類的數據 if(beep) Toolkit.getDefaultToolkit().beep(); } }); timer.start(); } }
Java8改進的接口
在jdk1.8以前,接口里只能定義常量(public static final)和抽象方法(public abstract),試想,假設1萬個類實現了一個接口,這時候對接口進行了升級,按照jdk1.7的規則,加方法的話只能加
抽象方法,當加完抽象方法之后1萬個類瞬間編譯報錯。因為必須要重寫抽象方法,在jdk1.8提出接口更新。
有的時候我們希望1萬個類如果有類想升級那么重寫,有類的不想升級就別重寫了。這時候默認方法方法就來了,用default修飾,默認方法可提供方法實現,而實現該接口的類可以不用實現默認方法
接口中可以定義靜態方法,讓接口具備了功能, 讓接口來調用(通過接口名.方法名調用)
函數式接口
接口中有且僅有一個抽象方法的接口即為函數式接口,可以使用@FunctionalInterface檢查定義的接口是否是一個函數式接口。函數式接口可以采用lambda表達式。
示例:Rational類
package edu.uestc.avatar.demo; /** * 有理數:a/b,a表示為分子,b表示為分母,分母不能為0 * @author Adan * */ public class Rational extends Number implements Comparable<Rational>{ private static final long serialVersionUID = 1L; /* * 分子 */ private long numerator = 0; /** * 分母 */ private long denominator = 1; public Rational() { this(0,1); } public Rational(long numerator, long denominator) { this.numerator = numerator; this.denominator = denominator; } public long getNumerator() { return numerator; } public void setNumerator(long numerator) { this.numerator = numerator; } public long getDenominator() { return denominator; } public void setDenominator(long denominator) { this.denominator = denominator; } /** * 兩個有理數相加 * @param rational 另一個有理數 * @return 有理數 */ public Rational add(Rational rational) { long n = numerator * rational.denominator + denominator * rational.numerator; long d = denominator * rational.denominator; return new Rational(n, d); } /** * 兩個有理數相減 */ public Rational substract(Rational rational) { long n = numerator * rational.denominator - denominator * rational.numerator; long d = denominator * rational.denominator; return new Rational(n, d); } /** * 兩個有理數相乘 */ public Rational multiply(Rational rational) { long n = numerator * rational.numerator; long d = denominator * rational.denominator; return new Rational(n, d); } /** * 兩個有理數相除 */ public Rational divide(Rational rational) { return multiply(new Rational(denominator,rational.numerator)); } @Override public int compareTo(Rational o) { if(substract(o).numerator == 0) return 0; else if(substract(o).numerator < 0) return -1; else return 1; } @Override public int intValue() { return (int)doubleValue(); } @Override public long longValue() { return (long)doubleValue(); } @Override public float floatValue() { return (float)doubleValue(); } @Override public double doubleValue() { return this.numerator * 1.0 / this.denominator; } @Override public String toString() { if(denominator == 1) return numerator + ""; else if(numerator == 0) return 0 + ""; else if(numerator == denominator) return 1 + ""; else return numerator + "/" + denominator; } @Override public boolean equals(Object obj) { return substract((Rational)obj).getNumerator() == 0; } }
類的設計原則
- 內聚性
- 一致性
- 封裝性
- 清晰性
- 完整性
- 實例和靜態
- 繼承與聚合
- 接口和抽象類
練習:使用接口組裝電腦。