Java學習之旅(一):探索extends

来源:https://www.cnblogs.com/Azir-s-soldier/archive/2019/09/02/11449549.html
-Advertisement-
Play Games

鄙人為興趣愛好,0基礎入門學習Java,有些心得想法,記錄於此,與君分享。 然畢竟新手,學識尚淺,錯誤之處,希望多多指正批評,也是對我最大的幫助! 前言:本篇文章,主要討論在子類繼承父類之後,各屬性成員和方法的特征規律和差異,this和super的異同,以及一些繼承在記憶體中構建的過程。 文章內所有內 ...


鄙人為興趣愛好,0基礎入門學習Java,有些心得想法,記錄於此,與君分享。

然畢竟新手,學識尚淺,錯誤之處,希望多多指正批評,也是對我最大的幫助!

 

前言:本篇文章,主要討論在子類繼承父類之後,一些繼承在記憶體中構建的過程,以及this和super的特點和異同

文章內所有內容均為個人猜測和想法,不代表任何學科結論。

 

一、我是孫子!

  既然有孫子,那肯定是指三代傳承,所以,我準備了三個類,A是爺爺,B是爸爸,C是孫子(也就是我,一下均以孫子代替)。代碼如下:

類A(爺爺):

 1 public class A {
 2     //屬性部分
 3     public int i_A;
 4     public String str_A = "我是類A里str屬性的初始值";
 5     //一般方法部分
 6     public void function (){
 7         System.out.println("我是類A里的“function”方法,我不接受參數");
 8     }
 9     public void function_A(){
10         System.out.println("我是類A里的“function_A”方法,我不接收參數");
11     }
12     public void function_A(int n){
13         System.out.println("我是類A里的“function_A”方法,我接收一個值為"+n+"的int類型參數");
14     }
15     //構造方法部分
16     public A(){
17         System.out.println("我是類A的無參數構造方法");
18     }
19     public A(int n){
20         System.out.println("我是類A的帶參數構造方法,我接收一個值為"+n+"的int類型參數");
21     }
22     //代碼塊部分
23     {
24         System.out.println("我是類A的代碼塊,我受過嚴格的訓練");
25     }
26 }
Class A

類B(爸爸):

 1 public class B extends A {
 2     //屬性部分
 3     public int i_B;
 4     public String str_B = "我是類B里str屬性的初始值";
 5     //一般方法部分
 6     public void function (){//父類A里一般方法的重寫
 7         System.out.println("我是類B里的“function”方法,是對父類A方法的重寫,我不接受參數");
 8     }
 9     public void function_B(){
10         System.out.println("我是類B里的“function_B”方法,我不接收參數");
11     }
12     public void function_B(int n){
13         System.out.println("我是類B里的“function_B”方法,我接收一個值為"+n+"的int類型參數");
14     }
15     //構造方法部分
16     public B(){
17         System.out.println("我是類B的無參數構造方法");
18     }
19     public B(int n){
20         System.out.println("我是類B的帶參數構造方法,我接收一個值為"+n+"的int類型參數");
21     }
22     //代碼塊部分
23     {
24         System.out.println("我是類B的代碼塊,無論多好笑,我都不會笑");
25     }
26 }
Class B

類C(孫子):

public class C extends B{
    //屬性部分
    public int i_C;
    public String str_C = "我是類C里str屬性的初始值";
    //一般方法部分
    public void function (){//父類A里一般方法的重寫
        System.out.println("我是類C里的“function”方法,是對父類B方法的重寫,我不接受參數");
    }
    public void function_C(){
        System.out.println("我是類C里的“function_C”方法,我不接收參數");
    }
    public void function_C(int n){
        System.out.println("我是類C里的“function_C”方法,我接收一個值為"+n+"的int類型參數");
    }
    //構造方法部分
    public C(){
        System.out.println("我是類C的無參數構造方法");
    }
    public C(int n){
        System.out.println("我是類C的帶參數構造方法,我接收一個值為"+n+"的int類型參數");
    }
    //代碼塊部分
    {
        System.out.println("我是類C的代碼塊,除非忍不住");
    }
}
Class C

  準備好了三個類,那我就要開始生成一個孫子。首先,我們不帶參數new一個C類對象c_new,在main中的代碼如下:

1 public class Relation {
2     public static void main(String[] args){
3         C new_c = new C();
4     }
5 }
main方法

  執行結果如下圖:

分析一:

  通過結果來看

  第一、給我的直觀感受是,java在new一個孫子對象的過程中,最先是new了一個爺爺類,再在爺爺類的後面new了一個爸爸類,最後在爸爸類的後面,new了一個孫子類。又由於object是所有類的父類,object也是爺爺類的父類,所以爺爺類其實是new在object類之後。

  第二、我認為,所有的“new”操作,一定都是在記憶體中開闢地址連續的空間(如果new出來的空間不連續,我估計整個java體系就會坍塌),而能100%保證正確申請到所需空間的步驟應該都是:先確定大小,再開闢空間,這個開闢的過程我覺得應該是JVM做的,而且開闢的空間必須等於或者大於所需要的空間。(我要是設計者,我會設計會大於所需的空間,多餘的空間可以存儲一些代碼信息,用於其他的用途,比如底層的一些保護或者回收機制)

  所以,我產生了一個想法:

  這個開闢空間的過程並非動態的(先開闢A大小空間,再接著記憶體地址開闢B大小空間,再……),而是在編譯的過程中,JVM會把A extends Object(隱式) 、B extends A、C extends B這個關係額外記錄成一條信息,這條信息告訴電腦,如果我需要生成一個C類對象,你需要給我對應大小空間的連續記憶體塊。於是,在得到指令需要生成一個C類對象後,記憶體會被開闢出一個能裝得下從object到C類所有大小的地址連續的記憶體空間。

  但是又會存在一個問題,如果我把main中“ C new_c = new C(); ”這條語句註釋掉,再編譯執行,能通過,既然main裡面什麼都沒做也可以執行通過,那此時ABC和object這四個類到底在不在記憶體中呢?還是說只有new出現了,才會產生我上述的編譯過程?

  為了證明這一點,我在main()里設計了一個while(true)的方法,可以無限選擇生成三種類中的任意一種類對象,我發現,在已經編譯後的程式執行過程中,我可以隨意選擇生成A、B或者C類的對象,這說明,類A,B,C甚至object肯定還是在記憶體中存在的,但是存在的空間是否連續,我無法確定,而且沒有一個具體的媒介可以用到他們,而且我猜想,很大可能性,這個記憶體狀態下,各個類所占用的空間也僅僅是類裡面代碼描述內容所需占用的記憶體空間,並不是像生成的具體對象那樣的記憶體空間。

  這個確定存在的推論讓我又得到一條信息,new的過程肯定都是複製的某塊記憶體的數據,而這塊記憶體正好是所需要生成對象的類存在的記憶體塊,不然怎麼可能在不操作記憶體數據的情況下,把可能不連續的記憶體塊,變成一定連續的記憶體塊呢?(因為我覺得如果通過操作記憶體數據來使記憶體連續實在太費力了,不符合java的特性)。

  綜上所述,以我的代碼為例,我覺得整個 “C new_c = new C();” 的過程應該是下麵這個步驟:

  1)記憶體中已經有幾塊區域存放了類Object、類A、類B和類C的主體。假如這四個類所占空間分別是50位元組,100位元組,100位元組,100位元組。合計:350位元組。

  2)在記憶體的另外一塊足夠大的地方,開闢出一個350位元組的連續地址空間,從object類到C類,依次將其記憶體的內容賦值到這塊新的記憶體中,這塊新記憶體是連續的,並產生一個代表這塊記憶體的地址值。

  3)將這個地址值,存到另外一塊名為c_new的記憶體里。

  至此,我覺得整個new的過程就完成了,以後對於c_new的操作,都是在c_new的值指向的新記憶體裡面進行的反覆讀寫與計算。

 

  

分析二:

  通過執行順序來看:

  眾所周知,構造一個類的具體對象是通過類裡面的構造方法實現的,那麼我們按照這個原則,開始從C();分析代碼的執行順序。

  進入C();第一行,直接就是輸出字元串 "我是類C的無參數構造方法" ,這和結果完全不一樣。從教程中,我瞭解到,其實構造函數的第一行,再未顯式調用的情況下,永遠是隱式調用了一個方法,super(),即調用當前類的父類的構造方法。所以我寫的構造函數,實際上是這樣的:

public C(){
        super();
        System.out.println("我是類C的無參數構造方法");
    }
C();
public C(int n){
        super();
        System.out.println("我是類C的帶參數構造方法,我接收一個值為"+n+"的int類型參數");
    }
C(int n);

  依次類推,B和A類的構造方法應該均是如此。所以,整個執行過程應該是C();→B();→A();→object();,這是幾個方法的嵌套使用(object有沒有構造函數我不確定,目前只討論ABC類,姑且這樣寫吧),在object();執行完畢之後,要執行的理應是A();里的下一條語句,System.out.plintln();,但是,結果顯示的卻是先執行了代碼塊的部分,而且,B、C類也是如此,我覺得這應該是java設計的一種類的規則,並不是某種機制產生的客觀結果(也可能是,只是我還不懂),而且代碼塊的調用是在super()和輸出之間發生,只有這種解釋,才能將輸出結果與構造過程一一對應。可以new一個帶參數的C類對象佐證。

public class Relation {
    public static void main(String[] args){
        C new_c = new C(5);//帶參數
    }
}
main()

結果如下:

  完美!

 

 

二、我到底是誰!

  是的,我又變成了this,我是一個指代關鍵字,在類的代碼裡面,指代當前類的具體對象,用來調用當前類的各種屬性和方法。比如,在類C裡面,我是代表類C的對象,在類B裡面,我代表B的對象,在A類里,我代表A的對象。。。但是,真的如此嗎?我到底是誰?

  為了查明真想,首先,我們在C類的function_C方法裡加入一行代碼,

public void function_C(){
        this.function();
        System.out.println("我是類C里的“function_C”方法,我不接收參數");
    }
function_C()

  通過main里生成的new_c調用function_C方法,執行後結果如下

 

  function_C方法正確調用到了this所在C類的function方法。

  但是,如果在B類的function_B方法裡加入調用方法,那this到底調用的哪一個function方法?讓我來測試一下。

1 public void function_B(){
2         this.function();
3         System.out.println("我是類B里的“function_B”方法,我不接收參數");
4     }
function_b()

  結果如下

  沒錯,function_B里的this.function()調用的卻是C類的function方法。如果在A類裡面測試,結果一樣。

  看上去,this並不是嚴格指代當前類的對象,起碼在父類里通過this調用重寫的方法時不符合這個說法。那麼如果測試的重寫的方法,而是重寫的屬性(這個說法可能並不專業和準確)呢?在三個類裡面設置一個同名同類型的變數,經過同上步驟的測試,this指代的又變成了當前類的對象。

  考慮到function方法實際上是子類對父類的重寫,情況可能有些特殊,所以關於this的指代性,可以做一個一般歸納:

  在繼承中,this確實在一般情況下,指代的是一個當前類的對象,但是如果在父類或者父父類(父類的父類)中通過this調用重寫的方法(同名同參數),那麼實際調用的就是new出來的具體對象的重寫方法,不會調用到父類,或者父父類。(這個結論的佐證就是,在父類中輸入this.後,IDE不會提示到子類的各種屬性和方法,除了重寫的方法)

  接下來,我們討論一下this();,通過this調用構造方法。

  首先,我們在C類的C(int n)方法里,第一行加入this(),然後帶參數運行,結果如下:

 

  通過追蹤代碼的執行順序,我們不難得出一個結論,this()把原來的隱藏的super()真正的屏蔽了起來,而如果顯式調用super(),編譯不會通過,這說明,this()和super()不能同時存在。其實這也很好理解,如果同時存在,那這個類的生成後就會非常混亂。所以這也應該是Java設定的特性。

 

 

 

三、我就是你爸爸!

 

  super就是爸爸,他比this的理解簡單多了,在任何情況下,super都指代當前類的父類的對象,沒有其他特例。就算是在父類里寫super.function(),調用的也是父父類的function()方法,測試如下:

  在B類的function_B方法的程式塊第一行寫super.function();,然後main中用生成的C類對象調用到function_B查看結果。

 

1 public void function_B(){
2         super.function();
3         System.out.println("我是類B里的“function_B”方法,我不接收參數");
4     }
function_B()

 

  結果如下:

 

  從結果不難看出,即使實際調用的是C類對象,但是super是寫在B類的function_B方法里的,所以會找到B類的父類里的對應的方法或者屬性。

  而super()就更不用討論什麼了,因為在任何子類的構造函數裡面的第一行預設都是super(),不管顯式或者隱式,他都必須在那裡。

  this()和super()的共同點就是:

    ①必須在構造函數里存在。在其他位置沒有實際意義,編譯也不會通過。

    ②都必須在構造函數的第一行。作為用來生成對象的方法,肯定是先有了對象你才能使用對象。

    ③由於第二條的存在,那麼this()和super()肯定不能同時存在。如果構造函數內是this(),那編譯器應該會預設註釋掉隱式的super()。

 

 

四、結尾

  到這裡,已經寫完了我所理解的繼承時記憶體構建、this和super特性的大部分內容,其實這裡面應該有很多不正確的地方或者沒有考慮到的地方,但是作為心得體會,留下記錄,可便於日後重新審視,發現自己的不足,從而提升自己,更歡迎歡迎大家多多批評指正,讓我更快速的進步!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 使用一個polygon矢量提取某個文件夾中所有的tif格式柵格數據 (要確保先安裝好arcpy包) 2019-09-02 23:53:36 ...
  • Java練習——撲克牌發牌器聲明:學習自其他博主,感謝分享,這裡自己也寫了一下。實現思路 - 構建一張撲克牌 - 構建一套撲克牌 - 測試 構建一張撲克牌 構建一套撲克牌 測試 結果: 玩家1紅桃6 方塊4 方塊A 黑桃4 草花2 紅桃Q 紅桃J 紅桃K 方塊3 黑桃K 方塊8 黑桃7 黑桃5 玩家 ...
  • 資料庫連接池原理-傳統方式 當有多個線程,每個線程都需要連接資料庫執行SQL語句的話,那麼每個線程都會創建一個連接,並且在使用完畢後,關閉連接。創建連接和關閉連接的過程也是比較消耗時間的,當多線程併發的時候,系統就會變得很卡頓。同時,一個資料庫同時支持的連接總數也是有限的,如果多線程併發量很大,那麼 ...
  • 在使用Spring整合MyBatis的時候遇到控制台報錯:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.xyfer.dao.UserDao.findById 詳細信息如下: 這 ...
  • 一.編程語言介紹與分類 1.什麼是編程語言 編程語言本質就是人類的語言,主要用於溝通交流。我們通過編程語言與電腦進行互動交流,從而使電腦來幫助我們實現一些特定的功能和一些複雜的工作。 2.編程語言的分類 編程語言可以分為機器語言、彙編語言、高級語言。機器語言是電腦底層的語言,直接與硬體打交道, ...
  • 字元集分為:ASCII碼 GBK Unicode(萬國碼) utf-8; ASCII碼是國外版,僅支持英文編碼,一個英文占用一位(byte); GBK 是國人補充的編碼,支持中文的書寫,一個字元占用兩位(byte); Unicode 是將多個國家的編碼進行統一整合,但是一個英文占用兩位(byte); ...
  • Spring Security 解析(四) —— 簡訊登錄開發   在學習Spring Cloud 時,遇到了授權服務oauth 相關內容時,總是一知半解,因此決定先把Spring Security 、Spring Security Oauth2 等許可權、認證相關的內容、原理及設 ...
  • 今天是Python基礎數據類型的最後一課,今天主要學習的知識是文件。文件主要分為讀文件,寫文件,讀寫文件,寫讀文件..... 首先我們來說只讀文件 我們先看下絕對路徑下的讀文件: open(1,2,3):打開文件,open()裡面的第一個參數是路徑的意思,你存儲的文件絕對路徑,裡面第二個參數是模式, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...