Java三大特性、抽象類以及介面相關
封裝
概念:物件屬性的封裝隱藏,方法的公開;屬性私有化後,則其他類不能直接使用物件名.屬性名訪問,必須通過提供的公開方法。控制在程式中屬性的讀和修改的訪問級別。
目的:增強安全性和簡化程式設計,使用者不必瞭解具體的實現細節,而只是要通過外部介面,一特定的訪問許可權來使用類的成員。
基本要求:把所有的屬性私有化,對每個屬性提供getter和setter方法
一個小例子:
//封裝之前
public class TestDemo {
public String name;
public String address;
public String age;
public TestDemo(String name, String address, String age) {
this.name = name;
this.address = address;
this.age = age;
}
TestDemo() {}
}
//呼叫是這樣的
public class Test {
public static void main(String[] args) {
TestDemo testDemo = new TestDemo();
String name = testDemo.name;
String address = testDemo.address;
String age = testDemo.age;
testDemo.name = "hello";
}
}
//封裝之後
public class TestDemo {
private String name;
private String address;
private String age; //構造私有屬性,並且不對外公開獲取,只能從建構函式設定
public TestDemo(String name, String address, String age) {
this.name = name;
this.address = address;
this.age = age;
}
TestDemo() {
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//使用是這樣的
public class Test {
public static void main(String[] args) {
TestDemo testDemo = new TestDemo();
TestDemo testDemo1 = new TestDemo("Hello", "world", "man");
String name = testDemo1.getName();
String address = testDemo1.getAddress();
//String age = testDemo1.getAge(); //無法獲取age
testDemo1.setName("world"); //呼叫公開方法進行屬性的修改
}
}
繼承
實現程式碼的複用,可以基於已經存在的類構造一個新類,繼承已存在的類就是複用這個類的方法和域,在此基礎上還可以新增一些新的方法和域,一個類只能繼承一個類
缺點:
- 父類變,子類就必須變。
- 繼承破壞了封裝,對於父類而言,它的實現細節對與子類來說都是透明的。
- 繼承是一種強耦合關係。
訪問修飾符:
- private:僅對本類可見
- protected:對本包和所有子類可見
- public:對所有類可見
- 預設(無修飾):對本包可見
繼承和許可權:
Public | 無修飾 | Private | Protected | final | abstract | static | |
類繼承 | 可繼承 | 同一包中的可繼承 | 不能修飾類 | 不能修飾類 | 不能派生子類(不可繼承) | 一般可繼承 | 不能修飾類 |
方法過載 | 可過載 | 可過載 | 不能過載 | 可過載 | 不可過載 | 可過載 | 可過載(修飾主函式就不能過載) |
成員變數 | 父類屬性被隱藏,使用呼叫super | 父類屬性被隱藏,使用呼叫super | 子類不能直接訪問父類的私有變數(通過父類構造器初始化父類私有變數) | 父類屬性被隱藏,使用呼叫super | 必須賦初值 | 不能修飾成員變數 | 每個例項共享這個類變數 |
類、超類、子類
引例:一家公司,有經理、普通員工等等,但是所有人都是老闆的僱員,以此設計它們之間的關係 分析:不管是經理、普通員工都是僱員的一類,但是各種身份又有一些不同的屬性、不同的功能
- 定義父類(超類) Employee
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 工資
*/
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
}
- 定義子類 Manager
public class Manager extends Employee {
/**
* 獎金資訊,特有的屬性
*/
private double bonus;
public Manager(String name, double salary) {
//Manager的構造器不能訪問父類的私有域,所以通過父類的構造器進行初始化
super(name, salary);
bonus = 0;
}
/**
* 重寫父類的方法
*/
@Override
public double getSalary() {
//呼叫父類的方法
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
}
繼承關係:
強制型別轉化:
- 只能在繼承層次內進行型別轉換
- 在將超類轉換成子類之前,應該使用instance檢查
Object:所有類的超類
Object類是Java中所有類的始祖,如果一個類沒有明確指出超類,那麼預設超類是Object。
-
euqals方法 用於檢測一個物件是否等於另一個物件,判斷兩個物件是都具有相同的引用
public boolean equals(Object obj) { return (this == obj); }
一般equals和==是不一樣的,但是在Object中兩者是一樣的
-
toString
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
-
getClass 獲得執行時型別
public final native Class<?> getClass();
-
hashCode
public native int hashCode();
該方法用於雜湊查詢,可以減少在查詢中使用equals的次數,重寫了equals方法一般都要重寫hashCode方法。這個方法在一些具有雜湊功能的Collection中用到。一般必須滿足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就滿足equals。不過為了提高效率,應該儘量使上面兩個條件接近等價。
-
notify 喚醒在該物件上等待的某個執行緒
public final native void notify();
-
notifyAll 喚醒在該物件上等待的所有執行緒
public final native void notifyAll();
-
wait(long timeout) 在規定時間內沒有獲得鎖就返回
public final native void wait(long timeout) throws InterruptedException;
-
wait(long timeout, int nanos) 在規定時間內沒有獲得鎖就返回,有一個附加時間
public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); }
-
wait() 一直等待,直到獲得鎖或者被中斷
public final void wait() throws InterruptedException { wait(0); }
-
finalize 用於釋放資源
protected void finalize() throws Throwable { }
-
clone
protected native Object clone() throws CloneNotSupportedException;
實現物件的淺複製,只有實現了Cloneable接口才可以呼叫該方法。主要是JAVA裡除了8種基本型別傳引數是值傳遞,其他的類物件傳引數都是引用傳遞,我們有時候不希望在方法裡講引數改變,這是就需要在類中複寫clone方法。
抽象類、介面
抽象類
類也可以繼承抽象類,抽象類可以繼承類,也可以繼承抽象類,可以實現介面。
-
abstract 使用abstract class 定義的類都是抽象類,可以含有、也可以不含有抽象方法 還是上面的例子,不管是經理還是僱員,都是人,所以人可以定義為一個抽象類
//抽象類Person abstract class Person { public abstract String getDescription(); private String name; public Person(String name) { this.name = name; } public String getName() { return name; } } //Employee繼承Person public class Employee extends Person { private double salary; public Employee(String name, double salary) { super(name); this.salary = salary; } @Override public String getDescription() { return "a employee"; } public double getSalary() { return salary; } } //定義新的Student類繼承Person public class Student extends Person { private String major; public Student(String name, String major) { super(name); this.major = major; } public String getMajor() { return major; } @Override public String getDescription() { return "a student" + major; } }
-
注意問題:
- 抽象類不能被例項化,例項化的工作應該交由它的子類來完成,它只需要有一個引用即可。
- 抽象方法必須由子類來進行重寫
- 只要包含一個抽象方法的抽象類,該方法必須要定義成抽象類,不管是否還包含有其他方法
- 抽象類中可以包含具體的方法,當然也可以不包含抽象方法
- 子類中的抽象方法不能與父類的抽象方法同名
- abstract不能與final並列修飾同一個類
- abstract 不能與private、static、final或native並列修飾同一個方法
介面
類可以實現介面,抽象類也可以實現介面,但是介面只能實現介面。類、抽象類、介面都可以實現多個介面
-
介面示例: Comparable介面
public interface Comparable { int compareTo(Object other); } //jdk 1.5後使用泛型定義 public interface Comparable<T> { int compareTo(T other); }
使用 implements關鍵字
public class Employee extends Person implements Comparable { ... public int compareTo(Object other) { Employee other = (Employee) other; //使用泛型後不用強轉了,比較的方式自定義 return Double.compare(salary, other.salary); } }
-
介面的預設方法 可以為介面提供一個預設實現,必須用default修飾符標記
public interface Comparable { default int compareTo(Object other) { return 0; } }
預設方法可能導致衝突,例如現在一個介面中將方法定義為預設方法,然後又在超類或另一個介面中定義了同樣的方法
-
介面的繼承 一個介面可以繼承另一個介面
public interface Work { public void work(); } public interface Study extends Work { public void study(); } //實現Study的時候會重寫兩個方法 public class Student extends Person implements Study { ... public void work() {} public void study() {} }
Study介面自己聲明瞭一個方法,又繼承了Work,所以實現Study介面的時候,一共有兩個方法需要重寫
-
注意事項:
- 一個Interface的所有方法訪問許可權自動被宣告為public。確切的說只能為public,當然你可以顯示的宣告為protected、private,但是編譯會出錯!
- 介面中可以定義“成員變數”,或者說是不可變的常量,因為介面中的“成員變數”會自動變為為public static final。可以通過類命名直接訪問:ImplementClass.name
- 介面中不存在實現的方法
- 實現介面的非抽象類必須要實現該介面的所有方法。抽象類可以不用實現
- 不能使用new操作符例項化一個介面,但可以宣告一個介面變數,該變數必須引用(refer to)一個實現該介面的類的物件。可以使用 instanceof 檢查一個物件是否實現了某個特定的介面。例如:if(anObject instanceof Comparable){}。
- 在實現多介面的時候一定要避免方法名的重複
多型
態性是指允許不同子型別的物件對同一訊息作出不同的響應。簡單的說就是用同樣的物件引用呼叫同樣的方法但是做了不同的事情。
例子:利用抽象類的例子寫一個測試類
public class Test {
public static void main(String[] args) {
Person person = new Employee("Employee", 5000); //向上轉型為Person,person指向Employee
System.out.println(person.getDescription()); //最終呼叫的是Employee重寫的方法
Employee employee = (Employee) person; //向下轉型,需要強制轉換
System.out.println(employee.getDescription()); //呼叫的是Employee重寫的方法
person = new Student("Student", "java"); //向上轉型,將person重新指向一個新的Student
System.out.println(person.getDescription()); //呼叫Student重寫的方法
}
}
/** 結果
* a employee
* a employee
* a studentjava
* /
Employee是Person的子類, Person person = new Employee(“Employee”, 5000) 編譯時變數和執行時變數不一樣,所以多型發生了。
(1)Person person作為一個引用型別資料,儲存在JVM棧的本地變量表中。 (2)new Employee(“Employee”, 5000)作為例項物件資料儲存在堆記憶體中 Employee的物件例項資料(介面、方法、field、物件型別等)的地址也儲存在堆中 Employee的物件的型別資料(物件例項資料的地址所執行的資料)儲存在方法區中,方法區中物件型別資料中有一個指向該類方法的方法表。
(3)Java虛擬機器規範中並未對引用型別訪問具體物件的方式做規定,目前主流的實現方式主要有兩種: 1. 通過控制代碼訪問 在這種方式中,JVM堆中會專門有一塊區域用來作為控制代碼池,儲存相關控制代碼所執行的例項資料地址(包括在堆中地址和在方法區中的地址)。這種實現方法由於用控制代碼表示地址,因此十分穩定。 2.通過直接指標訪問 通過直接指標訪問的方式中,reference中儲存的就是物件在堆中的實際地址,在堆中儲存的物件資訊中包含了在方法區中的相應型別資料。這種方法最大的優勢是速度快,在HotSpot虛擬機器中用的就是這種方式。
(4)實現過程 首先虛擬機器通過reference型別(Person的引用)查詢java棧中的本地變量表,得到堆中的 物件型別資料的地址,從而找到方法區中的物件型別資料(Employee的物件型別資料),然後查詢方法表定位到實際類(Employee類)的方法執行。
多型存在的三個必要條件
-
繼承 如例子中的Employee是Person的子類
-
重寫 Employee重寫了Person的getDescription方法
-
父類引用指向子類物件 Person person = new Employee(“Employee”, 5000);
多型的優點
- 消除型別之間的耦合關係
- 可替換性
- 可擴充性
- 介面性
- 靈活性
- 簡化性
多型性
多型性分為編譯時的多型性和執行時的多型性。
執行時的多型性 方法重寫(override)實現的是執行時的多型性(也稱為後繫結),執行時的多型是面向物件最精髓的東西,要實現多型需要做兩件事:1. 方法重寫(子類繼承父類並重寫父類中已有的或抽象的方法);2. 物件造型(用父型別引用引用子型別物件,這樣同樣的引用呼叫同樣的方法就會根據子類物件的不同而表現出不同的行為)。
編譯時的多型 方法過載(overload)實現的是編譯時的多型性(也稱為前繫結)。
-
方法呼叫繫結 將一個方法主體關聯起來被稱作繫結 (1)前期繫結(靜態繫結):在程式執行之前進行繫結(預設的繫結方式) (2)後期繫結(動態繫結或執行時繫結):在執行時根據物件的型別進行繫結
Java中除了static方法和final方法(private方法屬於final方法)之外,其他所有的方法都是後期繫結
-
方法的型構:指方法的組成結構,具體包括方法的名稱和引數,涵蓋引數的數量、型別以及出現的順序,但是不包括方法的返回值型別,訪問許可權修飾符,以及 abstract、static、final 等修飾符。
-
相同型構
public void method(String s) {} public String method(String s) {}
-
不同型構
public void method(int i, String s) {} public void method(String s, int i) []
-
-
過載 指在同一個類中定義了一個以上具有相同名稱,但是型構不同的方法。
-
重寫 指在繼承情況下,子類中定義了與其父類中方法具有相同型構的新方法,就稱為子類把父類的方法重寫了(子類必須重寫父類為抽象類的方法)。這是實現多型必須的步驟