[Java]深度剖析面向对象编程
?
專欄簡介 :java語法
創作目標:從不一樣的角度,用通俗易懂的方式,總結歸納java語法知識.
希望在提升自己的同時,幫助他人,與大家一起共同進步,互相成長.
學歷代表過去,能力代表現在,學習能力代表未來!
目錄
前言
一.封裝
1.封裝的思想
?2.封裝的應用
二.this與super的區別
?注意事項:
三.static總結
1)靜態變量在內存中的布局
2)靜態變量的含義?
?3)靜態變量的作用
1.修飾成員變量
2.修飾方法
3.修飾代碼塊
四.重載與重寫的區別
五.繼承與組合的區別
六.多態
1.向上轉型
1.直接賦值?
2.方法傳參?
3.方法返回?
2.動態綁定
3.理解多態
4.多態的優缺點:??
5.向下轉型
七.抽象類
八.接口
1.Compareable與Compareor區別
1)基本區別:
?2)Compareable的實現:
3)Compareor的實現:
4)比較總結:Compareable與Compareor
2.Cloneable接口
3.深拷貝與淺拷貝的區別
總結
前言
? ? ? ? 初學 java 這類面向對象的語言, 難免對知識理解的不夠深入, 為了解決這類問題,這篇文章專門對初學 java 時理解起來較為抽象的概念做了通俗易懂的描述,?希望在歸納總結知識的同時也可以對各位有所幫助與啟發.
一.封裝
1.封裝的思想
? ? ? ? 封裝的思想在我們日常生活中應用廣泛,其目的是1.提高程序的安全性 2.提高代碼的復用效率.在日常生活中許多事物的實現細節和繁瑣的操作流程不需要讓使用者知道,這時封裝的思想就體現了很大的作用.例如:1.銀行卡的賬戶和金額顧客不能修改,但密碼可以修改. 2.啟動汽車時不需要知道發動機,火花塞各個系統之間是如何配合使用的,只需按下點火按鈕即可.
- 1.private修飾成員變量,成員方法,?其作用是隱藏內部的實現細節且只能在本類中調用.
- 2.private修飾構造方法,那么該類的對象就不能實例化,其目的是通常是實現單例模式因為單例模式通常有一個私有且靜態的本類對象,一般提供公有且靜態的方法訪問該對象.
- 3.private修飾類,如果修飾的不是內部類那么這種操作一般是毫無意義的且編譯器會報錯,外部類存在的意義就是被實例化調用,如果對外不可見就失去了其存在的意義.private修飾的內部類也要很苛刻的要求內部類和內部類的方法聲明都必須是靜態的,不推薦內部類的寫法,但有必要知道.
?2.封裝的應用
class Bank{private String account;private String password;private int balance;public String getAccount() {//銀行卡賬戶可獲得但不可修改return account;}public String getPassword() {//銀行卡密碼可獲得也可修改return password;}public void setPassword(String password) {this.password = password;}public int getBalance() {//賬戶余額用戶可獲得但不能修改return balance;} } class Ignite{//點火private void spark_plug(){//火花塞工作System.out.println("火花塞工作");}private void separator(){//分離器工作System.out.println("分離器工作");}private void igniter(){//點火器工作System.out.println("點火器工作");}public void ignite(){//發動separator();spark_plug();igniter();System.out.println("汽車發動完畢");}}阿里編程規范:
Java中所有的成員變量一律使用private封裝,并且根據屬性的實際情況對外提供getter和setter方法。
二.this與super的區別
?注意事項:
1.當一個子類繼承自父類時,那么子類的構造方法中第一行會有一個默認的隱式super()語句,優先執行父類的構造方法.
2.this與super在構造方法中不能同時出現.有以下兩點原因:
- this與super調用構造方法時,都必須置于構造方法的首行, 同時出現則相互矛盾.
- this()中調用的構造方法會優先默認執行父類的構造方法, 如果再調用super()就會重復執行父類構造方法,編譯器無法通過.
小明可以從父親手里繼承錢和車, 此時this與super的關系是;
三.static總結
1)靜態變量在內存中的布局
? ? ? ? 學習static關鍵字之前首先我們要知道,靜態變量儲存在哪里?在java中靜態變量存儲在方法區,很多同學以為方法區是堆區的一部分其實不然,《Java虛擬機規范》中明確說明:“盡管所有的方法區在邏輯上是屬于堆的一部分,但一些簡單的實現可能不會選擇去進行垃圾收集或者進行壓縮。”但對于HotSpotJVM而言,方法區還有一個別名叫做Non-Heap(非堆),目的就是要和堆分開。
2)靜態變量的含義?
? ? ? ? 了解static的內存布局之后, 我們從static的含義出發初步深入了解static關鍵字?.?靜態的反義詞是動態, 動態時指java程序在JVM上運行時,?JVM會根據對象的需要動態開辟內存, 而內存回收由JVM統一管理并分配給其他新建對象. 那么相反靜態就是指程序在運行之前, JVM就會為static所修飾的內容分配內存. 當java程序運行到JVM時, JVM就會把類及類的成員變量儲存在方法區, 而方法區的線程是共享的所以static關鍵字所修飾的內容也是全局共享的, 所以如果類的某些內容可以共享時, 我們可以考慮使用static關鍵字修飾.
?3)靜態變量的作用
1.修飾成員變量
? ? ? ? 當我們在 int age 之前加上static關鍵字修飾后,age就不再屬于對象,age屬性統一交給Person類去管理(age屬于Person的所有對象),此時age達到了全局共享的效果,但弊端是一個對象對age作出更改其他對象都會受影響.例如:p1.age=10,p2.age=20.這段代碼執行的結果是p1和p2的age都為20.
public class Person {String name;static int age;public String toString() {return "Name:" + name + ", Age:" + age;}public static void main(String[] args) {Person p1 = new Person();p1.name = "zhangsan";p1.age = 10;Person p2 = new Person();p2.name = "lisi";p2.age = 12;System.out.println(p1);System.out.println(p2);} }2.修飾方法
? ? ? ? 相比于修飾成員變量,static關鍵字修飾的方法并沒有在數據的存儲上有區別,因為方法本身就存放在類的定義當中,其最大的作用就是可以直接使用類名調用方法,省去了實例化對象性的繁瑣過程.但其弊端是只能調用靜態的成員變量和方法,因為static所修飾的方法是全局共享的,所以如果調用非靜態的方法或成員變量,它會不知道使用哪一個對象的方法或屬性.注意:對象也可以調用static所修飾的內容但編譯器會報警告.
3.修飾代碼塊
? ? ? ? static所修飾的代碼塊為靜態代碼塊.
? ? ? ? 無論是方法體還是普通變量都依賴于實例化對象的運行,而靜態代碼塊在類加載時已經執行所以2和3都不成立.
class Book{public Book(String msg) {System.out.println(msg);} }public class Person {Book book1 = new Book("book1成員變量初始化");static Book book2;static {book2 = new Book("static成員book2成員變量初始化");book4 = new Book("static成員book4成員變量初始化");}public Person(String msg) {System.out.println(msg);}Book book3 = new Book("book3成員變量初始化");static Book book4;public static void funStatic() {System.out.println("static修飾的funStatic方法");}public static void main(String[] args) {Person.funStatic();System.out.println("****************");Person p1 = new Person("p1初始化");}四.重載與重寫的區別
注意事項:?
- 聲明為final的方法不能被重寫,也不能被繼承.
- 聲明為static的方法不能被重寫,但可以被繼承.
- 構造方法不能被重寫,修改構造方法相當于修改父類.
????????了解上述區別之后我們會有疑問為什么子類的重寫方法的權限要大于父類??我們可以從相反的角度來考慮. 假如子類對象的權限小于父類. A a = new B();這是一個典型的向上轉型的案例, a的類型為A所以a能使用什么方法取決于A類, A告訴a:你可以使用m方法在任何地方. 但a的執行取決于B類. B告訴a:你可以使用m方法但僅限于本類.很明顯是矛盾的.
class A{public void m(){}}class B extends A{private void m(){}}五.繼承與組合的區別
語義區別:組合有has_a語義, 例如鏈表有節點. 繼承有is_a語義,例如貓是動物.
? ? ? ? 繼承與組合本質都是為了提高代碼的復用效率, 不同的是繼承父類的實現細節可見, 會破壞代碼的封裝性. 而組合被包括的對象內部細節對外不可見, 封裝性好 .繼承與封裝最本質的區別是由各自的優缺點來決定到底使用哪個.
? ? ? ?繼承的優點:
? ? ? ? 繼承的缺點:
? ? ? ? 組合的優點:
????????組合的缺點:
- 創建對象需實現考慮好所有的組合對象,會使系統有很多對象.
? ? ? ? 如何抉擇二者的使用:
- 如果沒有明顯的is_a關系推薦使用組合.
- 不要為了多態而使用繼承,組合之間通過接口也可以達到同樣的效果,而且比繼承有更高的可拓展性.
六.多態
1.向上轉型
? ? ? ? 通俗易懂的來講,?多態就是不同對象對同一事物或時間做出的不同反應.例如:父類是熊,子類是熊貓和灰熊.同樣是熊, 但熊貓餓了吃竹子, 狗熊餓了吃肉.如果用父類熊去調用子類對象,僅僅調用"吃"這個方法就會有不同的情況.這就是多態中的向上轉型.
向上轉型發生時有三種不同的時機:
1.直接賦值?
class Bear{public void eat(){System.out.println("吃東西");} } class Grizzlize extends Bear{@Overridepublic void eat() {System.out.println("吃肉");}} class Panda extends Bear{@Overridepublic void eat() {System.out.println("吃竹子");} } public class Polymorphism {public static void main(String[] args) {Bear bear1 = new Grizzlize();Bear bear2 = new Panda();bear1.eat();bear2.eat();} }2.方法傳參?
將不同的對象作為參數傳遞給同一方法,該方法的參數類型為父類的引用,結果產生了不同的狀態.
class Bear{public void eat(){System.out.println("吃東西");} } class Grizzlize extends Bear{@Overridepublic void eat() {System.out.println("吃肉");}} class Panda extends Bear{@Overridepublic void eat() {System.out.println("吃竹子");} } public class Polymorphism {public static void Eat(Bear bear){bear.eat();}public static void main(String[] args) {Eat(new Grizzlize());/*Grizzlize grizzlize = new Grizzlize();等價于Eat(grizzlize);*/Eat(new Panda());} }3.方法返回?
父類引用接收返回子類對象的方法 class Bear{public void eat(){System.out.println("吃東西");} } class Grizzlize extends Bear{@Overridepublic void eat() {System.out.println("吃肉");}} class Panda extends Bear{@Overridepublic void eat() {System.out.println("吃竹子");} } public class Polymorphism {public static Panda findMyBear1(){Panda panda = new Panda();return panda;}public static Grizzlize findMyBear2(){Grizzlize grizzlize = new Grizzlize();return grizzlize;}public static void main(String[] args) {Bear bear1 = findMyBear1();Bear bear2 = findMyBear2();bear1.eat();bear2.eat();} }2.動態綁定
? ? ? ? 在了解動態綁定之前我們首先要知道什么是靜態綁定?靜態綁定就是根據你傳入的參數可以確定在編譯時期就能確定你所要調用的方法,這就叫做靜態綁定.而動態綁定則是在編譯時期看到的方法調用并不一定是正確的,只有程序運行時才能確定.
//靜態綁定 public class Polymorphism { public static void main(String[] args) {function();function(1);function(1,5);}public static void function(){}public static void function(int a){}public static void function(int a,int b){} } //動態綁定 class Dogs extends Animal{public Dogs(String name) {super(name);}@Overridepublic void eat() {System.out.println(name+"吃骨頭");} } public class Polymorphism {public static void main(String[] args) {Animal animal = new Dogs("小狗");animal.eat();}? ? ? ? 調用java的.class文件的反匯編,可以看出在編譯時期調用的時Animal的eat()方法,等到運行時才能知曉到底調用哪個方法??
3.理解多態
? ? ? ? 了解向上轉型,動態綁定和方法重寫后就可以用多態的思想來實現程序了,只用關注父類的代碼就可以兼容多個子類的對象.
class Bear{public void eat(){//無任何動作} } class Grizzlize extends Bear{@Overridepublic void eat() {System.out.println("灰熊吃肉");}} class Panda extends Bear{@Overridepublic void eat() {System.out.print("熊貓吃竹子");} } class Raccon extends Bear{@Overridepublic void eat() {System.out.println("浣熊吃蟲子");} } public class Polymorphism {public static void eats(Bear bear){bear.eat();}public static void main(String[] args) {Bear bear1 = new Grizzlize();Bear bear2 = new Panda();Bear bear3 = new Raccon();eats(bear1);eats(bear2);eats(bear3);}4.多態的優缺點:??
1.類調用者使用類的成本降低:- 封裝使類的調用者不必過多的關注類內部的實現細節.
- 多態使類的調用者連類的類型都不需要考慮,只需要知道這個對象具有某個方法即可.???
2.降低代碼的圈復雜度,避免重復使用if_else:
- 例如我們需要知道多個熊他們要吃的事物,代碼如下:
3.可擴展能力強:
- 未來如果新增一種熊,只需增加這個熊的實例對象即可,不需像if_else一樣改變代碼的結構.
4.代碼運行效率低:
- 屬性無多態性,構造方法無多態性.
5.向下轉型
? ? ? ? 全面了解多態以后,我們會發現多態有一個局限性,即父類不能調用子類特有的方法,而向下轉型雖然能解決這個問題但十分的不安全一般不推薦使用.如果非要使用我們可以使用instanceof這個方法來保障向下轉型的安全性.例如:浣熊特有的技能為預知,如果想要使用這個方法必須將父類強轉為子類.(instanceof的作用是測試左邊對象是否是它右邊類的實例)
public static void main(String[] args) {Bear bear = new Raccon();if (bear instanceof Raccon){Raccon raccon = (Raccon) bear;raccon.preception();}}七.抽象類
? ? ? ? 抽象類就是將子類的共性抽取出來,最大的意義就是被繼承方便后續向上轉型.抽象類中可以含有多種不同的成員,但抽象類不能被實例化,只能去創建該抽象類的子類重寫父類的方法,這些功能看似普通的類也能做,為什么非要使用抽象方法呢?其實抽象方法相當于多了一層檢驗效果,普通類在調用子類對象的方法時可能會調用到父類的,這是普通編譯器不會報錯,但是如果父類是抽象類就會早早報錯及時發現問題.
抽象方法的特點:
- 抽象類和抽象方法必須使用abstract修飾.
- 如果一個普通類繼承了抽象類就必須重寫抽象類中的抽象方法.
- 如果一個抽象類A繼承自抽象類B,那么抽象類A無需重寫抽象類B的抽象方法,但出來混遲早要還,最后繼承的普通類一定要重寫所有抽象類的抽象方法.
- 抽象方法不能被private修飾,必須滿足重寫的規則.
- 抽象方法不能被static和final修飾.
? ? ? ? final修飾的類不能被繼承與抽象類的初衷相矛盾.static修飾抽象類會直接報錯.
- 抽象類最大的作用就是為了讓程序員更早的發現錯誤.
八.接口
1.Compareable與Compareor區別
1)基本區別:
? ? ? ? Compareable為自然排序位于java.lang包底下,Compareor為定制排序位于java.util包底下.顧名思義一個類只要實現了Compareable接口就認為該類擁有了排序的功能,那么該類對象的List列表和數組可以通過Collection.sort()或者Arrays.sort()排序.而Compareor則是在外部指定排序規則,作為參數傳遞給某些類.
?2)Compareable的實現:
?????????Compareable接口只包含一個compareTo()函數,所以如果想實現自然排序只需重寫compareTo()函數即可.其比較規則如下:
- e1.compareTo(e2)>0即e1>e2.
- e1.compareTo(e2)<0即e1<e2.
- e1.compareT0(e2)=0即e1=e2.
? ? ? ? 如果我們不實現Compareable接口,那么就會報類型捕獲異常(classCastException).
? ? ? ? 但自然排序有一定的缺陷,即對類的侵入性較強,如果一個月后我們要把比較年齡改為比較姓名,那么對整個程序的結構都會有較大的改動,十分的不方便這時定制排序Compareor就派上了用場.?
3)Compareor的實現:
????????Compareor位于java.util包底下,需要重寫int compare(T o1, T o2)方法.但是重寫時我們會發現Compareor作為一個接口有很多的方法,那么按照接口的定義我們應當重寫所有方法才對,但為什么重寫int compare(T o1, T o2)方法就可以呢?
? ? ? ? 其實自JDK1.8開始接口中就可以定義三類方法:1.抽象方法 2.default修飾的普通方法 3.static所修飾的方法.除了int compare(T o1, T o2)和equals屬于第一類,其余都是第二或第三類自然無需重寫,又因為接口的實現類都默認繼承Object類,java代碼在編譯為字節碼文件時,equals方法已經實現了,所以編譯器不會認為Compareor接口的equals方法沒有實現.
內部實現:
class Student implements Comparator<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 +'}';} @Overridepublic int compare(Student o1, Student o2) {return o1.name.compareTo(o2.name);} @Overridepublic int compare(Student o1, Student o2) {return o1.age-o2.age;} }外部實現:
????????此時需要借助Arrays.sort()的重載方法,即根據指定比較器引發的順序對指定的數組對象進行排序.
class NameCompare implements Comparator<Student>{@Overridepublic int compare(Student o1, Student o2) {return o1.name.compareTo(o2.name);} } class AgeCompare implements Comparator<Student>{@Overridepublic int compare(Student o1, Student o2) {return o1.age-o2.age;} } public class TestInterface{public static void main(String[] args) {Student student1 = new Student("zhangsan", 18);Student student2 = new Student("lisi", 21);Student student3 = new Student("wangwu", 19);Student[] students = {student1, student2, student3};AgeCompare ageCompare = new AgeCompare();NameCompare nameCompare = new NameCompare();Arrays.sort(students,ageCompare);//按年齡比較Arrays.sort(students,nameCompare);//按姓名比較System.out.println(Arrays.toString(students));} }4)比較總結:Compareable與Compareor
- Compareable接口比較固定像是類擁有該功能與一個具體的類綁定,Compareor接口更加的靈活,實現一個就可以為各個需要比較功能的類使用.
- Compareable接口用戶可以可以實現它來完成自己的特定的比較,Compareor接口可以看成是一種算法的實現,隨用隨取,相當于設計模式中的算法與數據分離.
2.Cloneable接口
? ? ? ? java中有三種實例化對象的方法分別是 new關鍵字 反射 以及clone().當我們傳遞一個對象的引用時,為了防止該對象被修改,可以克隆一份再去傳遞.
? ? ? ? clone()方法存在于Object類中,調用這個方法就可以創建一個對象的拷貝,但在使用這個方法之前必須實現Cloneable接口,否則會拋出異常CloneNotSupportedException.實現步驟如下:
3.深拷貝與淺拷貝的區別
? ? ? ? 初學者可能有一個誤區,認為深拷貝還是淺拷貝取決于某個確定的函數或接口,其實不然,真正決定深淺拷貝的是我們具體的實現方式.例如Cloneable接口,如果我們按如下方式寫代碼就是淺拷貝.這樣寫會導致克隆出的副本部分變量的引用與主體指向同一個對象,一但副本改變主體也會跟著改變.
class Money {public double m = 12.5;} class Person1 implements Cloneable{public int id;Money money = new Money();@Overridepublic String toString() {return "Person1{" +"id=" + id +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone;} } class Test{public static void main(String[] args) throws CloneNotSupportedException {Person1 person1 = new Person1();Person1 person2 = (Person1) person1.clone();person2.money.m = 199;System.out.println(person1.money.m);System.out.println(person2.money.m);} }? ? ? ? 解決方式就是將要克隆的所有對象所涉及到的類都實現Cloneable接口.
class Money implements Cloneable{public double m = 12.5;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();} } class Person1 implements Cloneable{public int id;Money money = new Money();@Overridepublic String toString() {return "Person1{" +"id=" + id +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {/*return super.clone();*/Person1 tmp = (Person1) super.clone();tmp.money = (Money)this.money.clone();return tmp;} } class Test{public static void main(String[] args) throws CloneNotSupportedException {Person1 person1 = new Person1();Person1 person2 = (Person1) person1.clone();person2.money.m = 199;System.out.println(person1.money.m);System.out.println(person2.money.m);} }總結
????????以上就是面向對象編程的全部內容了,通過對比易混淆概念與知識延拓,相信我們對面向對象已經有了一個深入的認識,如果我的文章對你有億點點幫助和啟發,麻煩不要忘記三連哦!
總結
以上是生活随笔為你收集整理的[Java]深度剖析面向对象编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [搬家from qzone] 读书笔记
- 下一篇: 等错误率EER