Java基礎(chǔ)之面向?qū)ο髾C制(多態(tài)、繼承)底層實現(xiàn)
一、Java的前世
為什么會產(chǎn)生Java?Java的特點是什么?
從C語言開始講,C語言是一種結(jié)構(gòu)化語言,模塊化編程,便于程序的調(diào)試,依靠非常全面的運算符和多樣的數(shù)據(jù)類型,可以輕易完成各種數(shù)據(jù)結(jié)構(gòu)的構(gòu)建,通過指針類型更可對內(nèi)存直接尋址以及對硬件進行直接操作,因此既能夠用于開發(fā)系統(tǒng)程序,也可用于開發(fā)應(yīng)用軟件。其缺點就是封裝性弱,程序的安全性上不是很好。C語言的異常處理一般使用setjmp()與longjmp(),在捕獲到異常時進行跳轉(zhuǎn);或者使用abort()和exit()兩個函數(shù),強行終止程序的運行。如果要實現(xiàn)多線程,應(yīng)該要直接操作底層操作系統(tǒng),語言本身沒有封裝該機制。
C語言是一門面向過程的語言,所謂面向過程指的是以“事件過程”為中心的編程思想,即按照事件的解決步驟,在函數(shù)中一步一步實現(xiàn)。其實,生活中大部分事情都可以用面向過程的思想來解決。然而,當問題的規(guī)模變大,且問題中有多個部分是共同的特征時,我們?nèi)匀恍枰獙@件事情建立一步一步操作,此時面向過程就顯得繁重冗余,因此產(chǎn)生了面向?qū)ο蟮乃枷搿?/p>
面向?qū)ο蟮乃枷胧菍⑹录械囊恍┕餐卣鞒橄蟪鰜碜鳛橐粋€對象,一個對象包括屬性和方法,比如說一個班級中的同學,大家都擁有姓名、成績、年齡、興趣愛好,在操作這些數(shù)據(jù)的時候,我們只需要將共同的部分抽象出來,然后給每個同學一個對象的實例。如果是面向過程的方法,我們需要為每一個同學定義屬性變量,執(zhí)行某個動作需要定義獨立的方法。因此,產(chǎn)生了C++語言。
C++繼承自C語言,可以進行面向過程的程序設(shè)計,也可以抽象化出對象進行基于對象的程序設(shè)計,也可以進行繼承、多態(tài)為特點的面向?qū)ο蟮某绦蛟O(shè)計。在C++的面向?qū)ο笤O(shè)計中,將數(shù)據(jù)和相關(guān)操作封裝在一個類中,類的實例為一個對象。支持面向?qū)ο箝_發(fā)的四個特性:封裝、抽象、繼承、多態(tài)。在C++語言中,內(nèi)存分為堆(程序運行時分配內(nèi)存)和棧(函數(shù)內(nèi)部聲明的變量)兩部分,往往需要手動管理內(nèi)存,通過new,delete動態(tài)劃分內(nèi)存并進行內(nèi)存的回收;類中包含構(gòu)造函數(shù)和析構(gòu)函數(shù),分別為建立對象和刪除對象釋放資源。
Java也是一門面向?qū)ο蟮恼Z言,不僅吸收了C++的各種優(yōu)點,同時摒棄了C++種難以理解的多繼承、指針等概念,功能更加強大且簡單易上手。其特點:簡單、OOP、平臺無關(guān)性(JVM的功勞);相比于面向?qū)ο蟮恼Z言C++而言,Java JVM的動態(tài)內(nèi)存管理非常優(yōu)秀。在發(fā)展的過程中,逐漸更新了更多強大的功能:XML支持、安全套接字soket支持、全新的I/O API、正則表達式、日志與斷言;泛型、基本類型的自動裝箱、改進的循環(huán)、枚舉類型、格式化I/O及可變參數(shù)。
在C語言、C++、Java的演化過程中,并不會導致新語言取代舊語言,每種語言按照自身的特點有了自己適合的領(lǐng)域。如追求程序的性能和執(zhí)行效率,如系統(tǒng)底層開發(fā),就需要使用C++,甚至C語言;安卓開發(fā)、網(wǎng)站、嵌入式、大數(shù)據(jù)技術(shù)等領(lǐng)域,一般使用Java語言,由于其安全性、便攜性、可移植性、可維護性等,很容易實現(xiàn)多線程,代碼的可讀性高。
C語言和C++是編譯型的語言,而Java是解釋型的語言:
- 編譯型語言:程序在執(zhí)行之前需要一個專門的編譯過程,把程序編譯成為機器語言的文件。
程序執(zhí)行效率高,依賴編譯器,跨平臺性差
- 解釋型語言:程序不需要編譯,程序運行時才翻譯成機器語言,每執(zhí)行一次都要翻譯一次。
效率比較低,依賴解釋器,跨平臺性好
Java是靜態(tài)-強類型語言。
二、多態(tài)
多態(tài)是同一個行為具有多個不同表現(xiàn)形式或形態(tài)的能力。多態(tài)就是同一個接口,使用不同的實例而執(zhí)行不同操作。
一般實現(xiàn)形式:
- 重載
@Overload:同一類種方法名相同,參數(shù)不同;返回類型不要求 - 重寫
@Override:子類繼承自父類的方法重寫實現(xiàn)過程,返回值、形參不能變、只能重寫函數(shù)體內(nèi)的語句,便于子類根據(jù)自身需要定義自己的行為。Animal b = new Dog(); - 接口
- 抽象類、抽象方法
重寫規(guī)則:
final,static方法不可被重寫
參數(shù)列表:與被重寫方法的參數(shù)列表必須完全相同
返回類型:與被重寫方法的返回類型可以不相同,但是必須是父類返回值的派生類
訪問權(quán)限:不能比父類中被重寫的方法的訪問權(quán)限更低(public > protected > private > )
拋異常:如果父類方法拋異常,子類不能拋更廣泛的異常
同包:除了private final可以重寫所有父類方法
不同包:只能重寫public 或者 protected的非final方法
子類中調(diào)用父類被重寫方法可以用super.method()
三、Java中多態(tài)的底層實現(xiàn)
多態(tài)性特征的最基本體現(xiàn)有“重載”和“重寫”,其實這兩個體現(xiàn)在Java虛擬機中時分派的作用。分派又分為靜態(tài)分派和動態(tài)分派,靜態(tài)分派是指所有依賴靜態(tài)類型來定位方法執(zhí)行版本的分派動作,動態(tài)分派是指在運行期根據(jù)實際類型確定方法執(zhí)行版本的分派過程。
Animal animal = new Bird();
在上面代碼中Animal是父類,Bird是繼承Animal的子類;那么在定義animal對象時前面的“Animal”稱為變量的靜態(tài)類型(Static Type),或者叫外觀類型(Apparent Type),后面的“Bird”則稱為變量的實際類型(Actual Type),靜態(tài)類型和實際類型在程序中都可以發(fā)生一些變化,區(qū)別是靜態(tài)類型的變化僅僅在使用時發(fā)生,變量本身的靜態(tài)類型不會被改變,并且最終的靜態(tài)類型是在編譯器可知的;而實際類型變化的結(jié)果在運行期才可以確定,編譯器在編譯程序的時候并不知道一個對象的實際對象是什么。
//實際類型變化 Animal bird = new Bird(); Animal eagle = new Eagle(); //靜態(tài)類型變化 sd.sayHello((Bird)bird); sd.sayHello((Eagle)eagle);
animal對象的靜態(tài)類型是Animal,實際類型是Bird。靜態(tài)類型在編譯期可知,實際類型在運行時可知。
四、重載
同一個類中相同方法名的不同方法。
重載,使用哪個重載版本,就完全取決于傳入參數(shù)的數(shù)量和數(shù)據(jù)類型。
方法重載是通過靜態(tài)分派實現(xiàn)的,靜態(tài)分派是發(fā)生在編譯階段,因此匹配到靜態(tài)類型Animal。
例如下面代碼:
package test;
/**
* @Description: 方法靜態(tài)分派演示
* @version: v1.0.0
*/
public class StaticDispatch {
static abstract class Animal{ }
static class Bird extends Animal{ }
static class Eagle extends Animal{ }
public void sayHello(Animal animal) {
System.out.println("hello,animal");
}
public void sayHello(Bird bird) {
System.out.println("hello,I'm bird");
}
public void sayHello(Eagle eagle) {
System.out.println("hello,I'm eagle");
}
public static void main(String[] args){
Animal bird = new Bird(); // 靜態(tài)類型Animal(編譯期可知)--實際類型Bird(運行期可知)
Animal eagle = new Eagle(); // 靜態(tài)類型Animal(編譯期可知)--實際類型Eagle(運行期可知)
StaticDispatch sd = new StaticDispatch();
sd.sayHello(bird);
sd.sayHello(eagle);
}
}
/* 結(jié)果:
hello,animal
hello,animal
*/
代碼中刻意地定義了兩個靜態(tài)類型相同Animal,實際類型不同的變量,但虛擬機(準確的是編譯器)在重載時是通過參數(shù)的靜態(tài)類型而不是實際類型來作為判斷依據(jù)的。并且靜態(tài)類型是編譯期可知的,因此,在編譯階段,Javac編譯器會根據(jù)參數(shù)的靜態(tài)類型決定使用哪個重載版本,因此選擇了sayHello(Animal)作為調(diào)用目標。
方法重載是通過靜態(tài)分派實現(xiàn)的,并且靜態(tài)分派是發(fā)生在編譯階段,所以確定靜態(tài)分派的動作實際上不是由虛擬機來執(zhí)行的;另外,編譯器雖然能確定出方法重載的版本,但在很多情況下這個版本并不是“唯一的”,往往只能確定一個“更加適合”的版本。
五、重寫
方法的重寫:與虛擬機中動態(tài)分派的過程有著密切聯(lián)系。
package test;
/**
* @Description: 方法動態(tài)分派演示
* @version: v1.0.0
*/
public class DynamicDispatch {
static abstract class Animal{
protected abstract void sayHello();
}
static class Bird extends Animal{
@Override
protected void sayHello() {
System.out.println("Bird say hello");
}
}
static class Eagle extends Animal{
@Override
protected void sayHello() {
System.out.println("Eagle say hello");
}
}
public static void main(String[] args){
Animal bird = new Bird();
Animal eagle = new Eagle();
bird.sayHello();
eagle.sayHello();
bird = new Eagle();
bird.sayHello();
}
}
/* 結(jié)果:
Bird say hello
Eagle say hello
Eagle say hello
*/
通過javap反編譯命令來看一段該代碼的字節(jié)碼:
>javap -c DynamicDispatch.class
Compiled from "DynamicDispatch.java"
public class com.carmall.DynamicDispatch {
public com.carmall.DynamicDispatch();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/carmall/DynamicDispatch$Bird
3: dup
4: invokespecial #3 // Method com/carmall/DynamicDispatch$Bir
d."<init>":()V
7: astore_1
8: new #4 // class com/carmall/DynamicDispatch$Eagl
e
11: dup
12: invokespecial #5 // Method com/carmall/DynamicDispatch$Eag
le."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #6 // Method com/carmall/DynamicDispatch$Ani
mal.sayHello:()V
20: aload_2
21: invokevirtual #6 // Method com/carmall/DynamicDispatch$Animal.sayHello:()V
24: new #4 // class com/carmall/DynamicDispatch$Eagle
27: dup
28: invokespecial #5 // Method com/carmall/DynamicDispatch$Eagle."<init>":()V
31: astore_1
32: aload_1
33: invokevirtual #6 // Method com/carmall/DynamicDispatch$Animal.sayHello:()V
36: return
}
上面的指令,invokevirtual表示運行時按照對象的類來調(diào)用實例方法;invokespecial根據(jù)編譯時類型來調(diào)用實例方法,也就是會調(diào)用父類。
0~15行的字節(jié)碼是準備動作,作用時建立bird和eagle的內(nèi)存空間、調(diào)用Bird和Eagle類型的實例構(gòu)造器,將這兩個實例的引用存放在第1、2個局部變量表Slot之中。
接下來的16~21行時關(guān)鍵部分;這部分把剛剛創(chuàng)建的兩個對象的引用壓到棧頂,這兩個對象是將要執(zhí)行的sayHello()方法的所有者,稱為接收者(Receiver);17和21句是方法調(diào)用命令,這兩條調(diào)用命令單從字節(jié)碼角度來看,無論是指令(invokevirtual)還是參數(shù)(都是常量池中第6項的常量,注釋顯示了這個常量是sayHello方法的符號引用)完全一樣的,但是這兩句執(zhí)行的目標方法并不同,這是因為invokevirtual指令的多態(tài)查找過程引起的,該指令運行時的解析過程可分為以下幾個步驟:
- 找到操作數(shù)棧第一個元素所指向的對象的實際類型,記為C。如果在類型C中找到了與常量中描述符和簡單名稱都一樣的方法,則進行訪問權(quán)限校驗,如果通過則返回該方法的的直接引用,查找過程結(jié)束;如果不通過,則返回
java.lang.IllegalAccessError異常。 - 否則,按照繼承關(guān)系從下往上一次對C的各個父類進行第二步的搜索和驗證過程。
- 如果始終都沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常。
- 由于
invokevirtual指令執(zhí)行的第一步就是在運行期確定接收者的實際類型,所以兩次調(diào)用的invokevirtual指令把常量池中的類方法符號引用解析到了不同的直接引用上,這個過程就是Java語言中重寫的本質(zhì)。
package java.lang;
import java.lang.annotation.*;
/**
* Indicates that a method declaration is intended to override a
* method declaration in a supertype. If a method is annotated with
* this annotation type compilers are required to generate an error
* message unless at least one of the following conditions hold:
*
* <ul><li>
* The method does override or implement a method declared in a
* supertype.
* </li><li>
* The method has a signature that is override-equivalent to that of
* any public method declared in {@linkplain Object}.
* </li></ul>
*
* @author Peter von der Ahé
* @author Joshua Bloch
* @jls 9.6.1.4 @Override
* @since 1.5
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
到此這篇關(guān)于Java基礎(chǔ)之面向?qū)ο髾C制(多態(tài)、繼承)底層實現(xiàn)的文章就介紹到這了,更多相關(guān)Java面向?qū)ο髾C制底層實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA 2023創(chuàng)建JSP項目的完整步驟教程
這篇文章主要介紹了IDEA 2023創(chuàng)建JSP項目的完整步驟教程,創(chuàng)建項目需要經(jīng)過新建項目、設(shè)置項目名稱和路徑、選擇JDK版本、添加模塊和工件、配置Tomcat服務(wù)器等步驟,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2024-10-10
java中BigDecimal的介紹及使用教程BigDecimal格式化及BigDecimal常見問題
BigDecimal是Java在java.math包中提供的線程安全的API類,用來對超過16位有效位的數(shù)進行精確的運算,這篇文章主要介紹了java中BigDecimal的介紹及使用,BigDecimal格式化,BigDecimal常見問題,需要的朋友可以參考下2023-08-08
spring-shiro權(quán)限控制realm實戰(zhàn)教程
這篇文章主要介紹了spring-shiro權(quán)限控制realm實戰(zhàn)教程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
java.io.IOException:?UT010029:?Stream?is?closed異常分析及解決
這篇文章主要給大家介紹了關(guān)于java.io.IOException:?UT010029:?Stream?is?closed異常分析及解決辦法,文中通過代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-02-02

