封裝
把客觀事物封裝成抽象的類境肾,并且類可以把自己的數(shù)據(jù)和方法只讓可信的類或者對(duì)象操作剔难,對(duì)不可信的進(jìn)行信息隱藏。封裝是面向?qū)ο蟮奶卣髦话掠鳎菍?duì)象和類概念的主要特性偶宫。
通俗的說,一個(gè)類就是一個(gè)封裝了數(shù)據(jù)以及操作這些數(shù)據(jù)的代碼的邏輯實(shí)體环鲤。在一個(gè)對(duì)象內(nèi)部纯趋,某些代碼或某些數(shù)據(jù)可以是私有的,不能被外界訪問冷离。
通過這種方式吵冒,對(duì)象對(duì)內(nèi)部數(shù)據(jù)提供了不同級(jí)別的保護(hù),以防止程序中無(wú)關(guān)的部分意外的改變或錯(cuò)誤的使用了對(duì)象的私有部分西剥。但是如果?個(gè)類沒有提供給外界訪問的?法痹栖,那么這個(gè)類也沒有什么意義了。
我們來看一個(gè)常見的 類瞭空,比如:Student
public class Student implements Serializable {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
復(fù)制代碼
將對(duì)象中的成員變量進(jìn)行私有化揪阿,外部程序是無(wú)法訪問的疗我。對(duì)外提供了訪問的方式,就是set和get方法南捂。 而對(duì)于這樣一個(gè)實(shí)體對(duì)象吴裤,外部程序只有賦值和獲取值的權(quán)限,是無(wú)法對(duì)內(nèi)部進(jìn)行修改
繼承
繼承 就是子類繼承父類的特征和行為溺健,使得子類對(duì)象(實(shí)例)具有父類的實(shí)例域和方法麦牺,或子類從父類繼承方法,使得子類具有父類相同的行為矿瘦。 在 Java 中通過 extends 關(guān)鍵字可以申明一個(gè)類是從另外一個(gè)類繼承而來的枕面,一般形式如下:
class 父類 {
}
class 子類 extends 父類 {
}
復(fù)制代碼
繼承概念的實(shí)現(xiàn)方式有二類:實(shí)現(xiàn)繼承與接口繼承。
實(shí)現(xiàn)繼承是指直接使用基類的屬性和方法而無(wú)需額外編碼的能力
接口繼承是指僅使用屬性和方法的名稱缚去、但是子類必須提供實(shí)現(xiàn)的能力
一般我們繼承基本類和抽象類用 extends 關(guān)鍵字潮秘,實(shí)現(xiàn)接口類的繼承用 implements 關(guān)鍵字。
注意點(diǎn):
通過繼承創(chuàng)建的新類稱為“子類”或“派生類”易结,被繼承的類稱為“基類”枕荞、“父類”或“超類”。
繼承的過程搞动,就是從一般到特殊的過程躏精。要實(shí)現(xiàn)繼承,可以通過“繼承”(Inheritance)和“組合”(Composition)來實(shí)現(xiàn)鹦肿。
子類可以擁有父類的屬性和方法矗烛。
子類可以擁有自己的屬性和方法, 即?類可以對(duì)?類進(jìn)?擴(kuò)展。
子類可以重寫覆蓋父類的方法箩溃。
JAVA 只支持單繼承瞭吃,即一個(gè)子類只允許有一個(gè)父類,但是可以實(shí)現(xiàn)多級(jí)繼承涣旨,及子類擁有唯一的父類歪架,而父類還可以再繼承。
使用implements
關(guān)鍵字可以變相的使java具有多繼承的特性
霹陡,使用范圍為類繼承接口的情況和蚪,可以同時(shí)繼承多個(gè)接口(接口跟接口之間采用逗號(hào)分隔)。
# implements 關(guān)鍵字
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
復(fù)制代碼
值得留意的是: 關(guān)于父類私有屬性和私有方法的繼承 的討論 這個(gè)網(wǎng)上 有大量的爭(zhēng)論烹棉,我這邊以Java官方文檔為準(zhǔn): With the use of the extends keyword, the subclasses will be able to inherit all the properties of the superclass except for the private properties of the superclass.
子類不能繼承父類的私有屬性(事實(shí))攒霹,但是如果子類中公有的方法影響到了父類私有屬性,那么私有屬性是能夠被子類使用的浆洗。
官方文檔 明確說明: private和final不被繼承催束,但從內(nèi)存的角度看的話:父類private屬性是會(huì)存在于子類對(duì)象中的。
通過繼承的方法(比如辅髓,public方法)可以訪問到父類的private屬性
如果子類中存在與父類private方法簽名相同的方法泣崩,其實(shí)相當(dāng)于覆蓋
個(gè)人覺得文章里的一句話很贊,我們不可能完全繼承父母的一切(如性格等)洛口,但是父母的一些無(wú)法繼承的東西卻仍會(huì)深刻的影響著我們矫付。
多態(tài)
同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力就是 多態(tài)。網(wǎng)上的爭(zhēng)論很多第焰,筆者個(gè)人認(rèn)同網(wǎng)上的這個(gè)觀點(diǎn):重載也是多態(tài)的一種表現(xiàn)买优,不過多態(tài)主要指運(yùn)行時(shí)多態(tài)
Java 多態(tài)可以分為 重載式多態(tài)和重寫式多態(tài):
-重載式多態(tài),也叫編譯時(shí)多態(tài)挺举。編譯時(shí)多態(tài)是靜態(tài)的杀赢,主要是指方法的重載,它是根據(jù)參數(shù)列表的不同來區(qū)分不同的方法湘纵。通過編譯之后會(huì)變成兩個(gè)不同的方法脂崔,在運(yùn)行時(shí)談不上多態(tài)。也就是說這種多態(tài)再編譯時(shí)已經(jīng)確定好了梧喷。
-重寫式多態(tài)砌左,也叫運(yùn)行時(shí)多態(tài)。運(yùn)行時(shí)多態(tài)是動(dòng)態(tài)的,主要指繼承父類和實(shí)現(xiàn)接口時(shí)铺敌,可使用父類引用指向子類對(duì)象實(shí)現(xiàn)汇歹。這個(gè)就是大家通常所說的多態(tài)性。
這種多態(tài)通過動(dòng)態(tài)綁定(dynamic binding)技術(shù)來實(shí)現(xiàn)偿凭,是指在執(zhí)行期間判斷所引用對(duì)象的實(shí)際類型产弹,根據(jù)其實(shí)際的類型調(diào)用其相應(yīng)的方法。也就是說弯囊,只有程序運(yùn)行起來痰哨,你才知道調(diào)用的是哪個(gè)子類的方法。 這種多態(tài)可通過函數(shù)的重寫以及向上轉(zhuǎn)型來實(shí)現(xiàn)常挚。
多態(tài)存在的三個(gè)必要條件:
- 繼承
- 重寫
- 父類引用指向子類對(duì)象:Parent p = new Child();
我們一起來看個(gè)例子作谭,仔細(xì)品讀代碼,就明白了:
@SpringBootTest
class Demo2021ApplicationTests {
class Animal {
public void eat(){
System.out.println("動(dòng)物吃飯奄毡!");
}
public void work(){
System.out.println("動(dòng)物可以幫助人類干活折欠!");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("吃魚");
}
public void sleep() {
System.out.println("貓會(huì)睡懶覺");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨頭");
}
}
@Test
void contextLoads() {
//part1
Cat cat_ = new Cat();
cat_.eat();
cat_.sleep();
cat_.work();
//part2
Animal cat=new Cat();
cat.eat();
cat.work();
cat.sleep();//此處編譯會(huì)報(bào)錯(cuò)。
Animal dog=new Dog();
dog.eat();//結(jié)果為:吃骨頭吼过。此處調(diào)用子類的同名方法锐秦。
//part3
//如果想要調(diào)用父類中沒有的方法,則要向下轉(zhuǎn)型盗忱,這個(gè)非常像"強(qiáng)轉(zhuǎn)"
Cat cat222 = (Cat)cat; // 向下轉(zhuǎn)型(注意酱床,如果是(Cat)dog 則會(huì)報(bào)錯(cuò))
cat222.sleep(); //結(jié)果為:貓會(huì)睡懶覺。 可以調(diào)用 Cat 的 sleep()
}
}
復(fù)制代碼
我們來看上面part1部分:
Cat cat_ = new Cat();
cat_.eat();
cat_.sleep();
cat_.work();
復(fù)制代碼
結(jié)果:
吃魚 貓會(huì)睡懶覺趟佃。 動(dòng)物可以幫助人類干活扇谣!
cat_.work();
這處繼承了父類Animal的方法昧捷,還是很好理解的 我們接著看part2:
Animal cat=new Cat();
cat.eat();
cat.work();
cat.sleep();//此處編譯會(huì)報(bào)錯(cuò)。
Animal dog=new Dog();
dog.eat();//結(jié)果為:吃骨頭罐寨。此處調(diào)用子類的同名方法靡挥。
復(fù)制代碼
這塊就比較特殊了,我們一句句來看
Animal cat=new Cat();
像這種這個(gè) 父類引用指向子類對(duì)象鸯绿,這種現(xiàn)象叫做:"向上轉(zhuǎn)型",也被稱為多態(tài)的引用跋破。
cat.sleep();
這句 編譯器會(huì)提示 編譯報(bào)錯(cuò)。 表明:當(dāng)我們當(dāng)子類的對(duì)象作為父類的引用使用時(shí)瓶蝴,只能訪問子類中和父類中都有的方法毒返,而無(wú)法去訪問子類中特有的方法
值得注意的是:向上轉(zhuǎn)型是安全的。但是缺點(diǎn)是:一旦向上轉(zhuǎn)型舷手,子類會(huì)丟失的子類的擴(kuò)展方法拧簸,其實(shí)就是 子類中原本特有的方法就不能再被調(diào)用了。所以cat.sleep()這句會(huì)編譯報(bào)錯(cuò)男窟。
cat.eat();
這句的結(jié)果打咏铺瘛:吃魚。程序這塊調(diào)用我們Cat定義的方法蝎宇。
cat.work();
這句的結(jié)果打拥芫ⅰ:動(dòng)物可以幫助人類干活! 我們上面Cat類沒有定義work方法姥芥,但是卻使用了父類的方法兔乞,這是不是很神奇。其實(shí)此處調(diào)的是父類的同名方法
Animal dog=new Dog();dog.eat();
這句的結(jié)果打印為:吃骨頭凉唐。此處調(diào)用子類的同名方法庸追。
由此我們可以知道當(dāng)發(fā)生向上轉(zhuǎn)型,去調(diào)用方法時(shí)台囱,首先檢查父類中是否有該方法淡溯,如果沒有,則編譯錯(cuò)誤簿训;如果有咱娶,再去調(diào)用子類的同名方法。如果子類沒有同名方法强品,會(huì)再次去調(diào)父類中的該方法
我們現(xiàn)在知道了 向上轉(zhuǎn)型時(shí)會(huì)丟失子類的擴(kuò)展方法膘侮,哎,但我們就是想找回來的榛,這可咋辦琼了? 向下轉(zhuǎn)型可以幫助我們,找回曾經(jīng)失去的
我們來看part3:
Cat cat_real = (Cat)cat; //注意 此處的cat 對(duì)應(yīng)上面父類Animal夫晌,可不是子類
cat_real.sleep();
復(fù)制代碼
Cat cat = (Cat)cat; cat222.sleep();
這個(gè)向下轉(zhuǎn)型非常像"強(qiáng)轉(zhuǎn)"雕薪。
打印的結(jié)果:貓會(huì)睡懶覺昧诱。此處又能調(diào)用了 子類Cat 的 sleep()方法了。
一道簡(jiǎn)單的面試題
我們?cè)賮砜匆坏烙幸馑嫉念}所袁,來強(qiáng)化理解
public class Main {
static class Animal{
int weight = 10;
public void print() {
System.out.println("this Animal Print:" + weight);
}
public Animal() {
print();
}
}
static class Dog extends Animal {
int weight = 20;
@Override
public void print() {
System.out.println("this Dog Print:" + weight);
}
public Dog() {
print();
}
}
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println("---------------");
Animal dog222 = new Dog();
Dog dog333 = (Dog)dog222;
System.out.println("---------------");
Dog dog444 = (Dog)new Animal();
}
}
復(fù)制代碼
執(zhí)行結(jié)果:
this Dog Print:0 this Dog Print:20
this Dog Print:0
this Dog Print:20
this Animal Print:10 Exception in thread "main" java.lang.ClassCastException: com.zj.Main Animalcannotbecasttocom.zj.MainDog at com.zj.Main.main(Main.java:15)
做對(duì)了嘛鳄哭,不對(duì)的話,復(fù)制代碼去idea中debug看看 [圖片上傳失敗...(image-f7f8e1-1666835336011)]
我們先看第一部分
Dog dog = new Dog();
程序內(nèi)部的執(zhí)行順序:
- 先 初始化 父類Animal 的屬性 int weight=10
- 然后 調(diào)用父類Animal的構(gòu)造方法,執(zhí)行print()
- 實(shí)際調(diào)用子類Dog的print()方法,打痈傺:this Dog Print:0,由于此時(shí)的子類屬性weight 并未初始化
- 初始化 子類Dog 的屬性 int weight=20
- 調(diào)用 子類Dog的構(gòu)造方法锄俄,執(zhí)行print()
- 實(shí)際調(diào)用當(dāng)前類的print()方法局劲,打印this Dog Print:20
其中有幾處我們需要注意一下:實(shí)例化子類dog,程序會(huì)去默認(rèn)優(yōu)先實(shí)例化父類奶赠,即子類實(shí)例化時(shí)會(huì)隱式傳遞Dog的this調(diào)用父類構(gòu)造器進(jìn)行初始化工作鱼填,這個(gè)和JVM的雙親委派機(jī)制有關(guān),這里就不展開講了毅戈,先挖個(gè)坑苹丸,以后再來填??。
當(dāng)程序調(diào)用父類Animal的構(gòu)造方法時(shí)苇经,會(huì)隱式傳遞Dog的this,類似于:
Dog dog = new Dog(this);
static class Animal{
public Animal(this) {
print(this);//子類又把print()給重寫了
}
}
static class Dog extends Animal {
public Dog(this) {
print(this);//此時(shí)子類的 屬int weight 被沒有初始化默認(rèn)為0
}
}
復(fù)制代碼
這塊其實(shí)和JVM的虛方法表有關(guān)赘理,這又是一個(gè)大坑,以后慢慢填??扇单。
我們接著看第2部分 Animal dog222 = new Dog();
這句是向上轉(zhuǎn)型商模,程序加載順序和第一部分Dog dog = new Dog();是一樣的,都是實(shí)例化類的過程 Dog dog333 = (Dog)dog222;
這個(gè)是向下轉(zhuǎn)型蜘澜,并沒有調(diào)用類構(gòu)造器施流,這塊等會(huì)和第3部分結(jié)合講
最后我們來看下第3部分Dog dog444 = (Dog)new Animal();
這句先實(shí)例化Andimal類,它沒有父類鄙信,就直接實(shí)例化當(dāng)前類瞪醋,打印this Animal Print:10。然后(Dog)表示向下轉(zhuǎn)型装诡,但是為啥運(yùn)行會(huì)報(bào)ClassCastException 異常呢?且第2部分Dog dog333 = (Dog)dog222;
卻沒有問題?
我們可以發(fā)現(xiàn)银受,向下轉(zhuǎn)型可以讓子類找回其獨(dú)有的方法 但是向下轉(zhuǎn)型是不安全的,實(shí)現(xiàn) 向下轉(zhuǎn)型 前需要先實(shí)現(xiàn) 向上轉(zhuǎn)型鸦采。