夯實Java基礎系列7:一文讀懂Java 代碼塊和執行順序

来源:https://www.cnblogs.com/AliCoder/archive/2019/09/27/11600918.html
-Advertisement-
Play Games

目錄 "Java中的構造方法" "構造方法簡介" "構造方法實例" "例 1" "例 2" "Java中的幾種構造方法詳解" "普通構造方法" "預設構造方法" "重載構造方法" "java子類構造方法調用父類構造方法" "Java中的代碼塊簡介" "Java代碼塊使用" "局部代碼塊" "構造代碼 ...


目錄

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫里查看

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star哈

文章首發於我的個人博客:

www.how2playlife.com

本文是微信公眾號【Java技術江湖】的《夯實Java基礎系列博文》其中一篇,本文部分內容來源於網路,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術博客內容,引用其中了一些比較好的博客文章,如有侵權,請聯繫作者。
該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接著瞭解每個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,形成自己的知識框架。為了更好地總結和檢驗你的學習成果,本系列文章也會提供每個知識點對應的面試題以及參考答案。

如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關註公眾號【Java技術江湖】聯繫作者,歡迎你參與本系列博文的創作和修訂。

Java中的構造方法

構造方法簡介

構造方法是類的一種特殊方法,用來初始化類的一個新的對象。Java 中的每個類都有一個預設的構造方法,它必須具有和類名相同的名稱,而且沒有返回類型。構造方法的預設返回類型就是對象類型本身,並且構造方法不能被 static、final、synchronized、abstract 和 native 修飾。

提示:構造方法用於初始化一個新對象,所以用 static 修飾沒有意義;構造方法不能被子類繼承,所以用 final 和 abstract 修飾沒有意義;多個線程不會同時創建記憶體地址相同的同一個對象,所以用 synchronized 修飾沒有必要。

構造方法的語法格式如下:

class class_name
{
    public class_name(){}    //預設無參構造方法
    public ciass_name([paramList]){}    //定義構造方法
    …
    //類主體
}

在一個類中,與類名相同的方法就是構造方法。每個類可以具有多個構造方法,但要求它們各自包含不同的方法參數。

構造方法實例

例 1

構造方法主要有無參構造方法和有參構造方法兩種,示例如下:

public class MyClass
{
    private int m;    //定義私有變數
    MyClass()
    {
        //定義無參的構造方法
        m=0;
    }
    MyCiass(int m)
    {
        //定義有參的構造方法
        this.m=m;
    }
}

該示例定義了兩個構造方法,分別是無參構造方法和有參構造方法。在一個類中定義多個具有不同參數的同名方法,這就是方法的重載。這兩個構造方法的名稱都與類名相同,均為 MyClass。在實例化該類時可以調用不同的構造方法進行初始化。

註意:類的構造方法不是要求必須定義的。如果在類中沒有定義任何一個構造方法,則 Java 會自動為該類生成一個預設的構造方法。預設的構造方法不包含任何參數,並且方法體為空。如果類中顯式地定義了一個或多個構造方法,則 Java 不再提供預設構造方法。

例 2

要在不同的條件下使用不同的初始化行為創建類的對象,這時候就需要在一個類中創建多個構造方法。下麵通過一個示例來演示構造方法的使用。

(1) 首先在員工類 Worker 中定義兩個構造方法,代碼如下:

public class Worker
{
    public String name;    //姓名
    private int age;    //年齡
    //定義帶有一個參數的構造方法
    public Worker(String name)
    {
        this.name=name;
    }
    //定義帶有兩個參數的構造方法
    public Worker(String name,int age)
    {
        this.name=name;
        this.age=age;
    }
    public String toString()
    {
        return"大家好!我是新來的員工,我叫"+name+",今年"+age+"歲。";
    }
}

在 Worker 類中定義了兩個屬性,其中 name 屬性不可改變。分別定義了帶有一個參數和帶有兩個參數的構造方法,並對其屬性進行初始化。最後定義了該類的 toString() 方法,返回一條新進員工的介紹語句。

提示:Object 類具有一個 toString() 方法,該方法是個特殊的方法,創建的每個類都會繼承該方法,它返回一個 String 類型的字元串。如果一個類中定義了該方法,則在調用該類對象時,將會自動調用該類對象的 toString() 方法返回一個字元串,然後使用“System.out.println(對象名)”就可以將返回的字元串內容列印出來。

(2) 在 TestWorker 類中創建 main() 方法作為程式的入口處,在 main() 方法中調用不同的構造方法實例化 Worker 對象,並對該對象中的屬性進行初始化,代碼如下:

public class TestWorker
{
    public static void main(String[] args)
    {
        System.out.println("-----------帶有一個參數的構造方法-----------");
        //調用帶有一個參數的構造方法,Staff類中的sex和age屬性值不變
        Worker worker1=new Worker("張強");
        System.out.println(worker1);
        System.out.println("-----------帶有兩個參數的構造方法------------");
        //調用帶有兩個參數的構造方法,Staff類中的sex屬性值不變
        Worker worker2=new Worker("李麗",25);
        System.out.println(worker2);
    }
}

在上述代碼中,創建了兩個不同的 Worker 對象:一個是姓名為張強的員工對象,一個是姓名為李麗、年齡為 25 的員工對象。對於第一個 Worker 對象 Worker1,並未指定 age 屬性值,因此程式會將其值採用預設值 0。對於第二個 Worker 對象 Worker2,分別對其指定了 name 屬性值和 age 屬性值,因此程式會將傳遞的參數值重新賦值給 Worker 類中的屬性值。

運行 TestWorker 類,輸出的結果如下:

-----------帶有一個參數的構造方法-----------
大家好!我是新來的員工,我叫張強,今年0歲。
-----------帶有兩個參數的構造方法------------
大家好!我是新來的員工,我叫李麗,今年25歲。

通過調用帶參數的構造方法,在創建對象時,一併完成了對象成員的初始化工作,簡化了對象初始化的代碼。

Java中的幾種構造方法詳解

普通構造方法

方法名與類名相同

無返回類型

子類不能繼承父類的構造方法

不能被static、final、abstract修飾(有final和static修飾的是不能被子類繼承的,abstract修飾的是抽象類,抽象類是不能實例化的,也就是不能new)

可以被private修飾,可以在本類裡面實例化,但是外部不能實例化對象(註意!!!)

public class A{
    int i=0;
    public A(){
        i=2;
    }
    public A(int i){
        this.i=i;
    }
}

預設構造方法

如果沒有任何的構造方法,編譯時系統會自動添加一個預設無參構造方法

隱含的預設構造方法

    public A(){}

顯示的預設構造方法

    public A(){
    System.out.print("顯示的預設構造方法")
    }

重載構造方法

比如原本的類里的構造方法是一個參數的,現在新建的對象是有三個參數,此時就要重載構造方法

當一個類中有多個構造方法,有可能會出現重覆性操作,這時可以用this語句調用其他的構造方法。

public class A{
    private int age;
    private String name;
    public A(int age,String name){
        this.age=age;
        this.name=name;
    }
    public A(int age){
        this(age,"無名氏");//調用 A(int age,String name)構造方法
    }
    public A(){
        this(1);//調用 A(int age)構造方法
    }
    public void setName(String name) {this.name=name;}
    public String getName() {return name;}
    public void setAge(int age) {this.age=age;}
    public int getAge() {return age;}
}

A a=new A(20,"周一");
A b=new A(20);
A c=new A();
String name = a.getName();
String name1 = b.getName();
int age = c.getAge();
System.out.println(name);
System.out.println(name1);
System.out.println(age);

java子類構造方法調用父類構造方法

首先父類構造方法是絕對不能被子類繼承的。

子類構造方法調用父類的構造方法重點是:子類構造方法無論如何都要調用父類的構造方法。

子類構造方法要麼調用父類無參構造方法(包括當父類沒有構造方法時。系統預設給的無參構造方法),要麼調用父類有參構造方法。當子類構造方法調用父類無參構造方法,一般都是預設不寫的,要寫的話就是super(),且要放在構造方法的第一句。當子類構造方法要調用父類有參數的構造方法,那麼子類的構造方法中必須要用super(參數)調用父類構造方法,且要放在構造方法的第一句。

當子類的構造方法是無參構造方法時,必須調用父類無參構造方法。因為系統會自動找父類有沒有無參構造方法,如果沒有的話系統會報錯:說父類沒有定義無參構造方法。

當子類構造方法是有參構造方法時,這時就會有兩種情況。
第一種:子類構造方法沒有寫super,也就是說你預設調用父類無參構造方法,這樣的話就和子類是無參構造方法一樣。

第二種:子類構造方法有super(參數)時,就是調用父類有參構造方法,系統會找父類有沒有參數一致(參數數量,且類型順序要相同)的有參構造方法,如果沒有的話,同樣也會報錯。

但是這裡會遇到和重載構造方法this一樣問題,一個參數的構造方法可以調用多個參數構造方法,沒有的參數給一個自己定義值也是可以的。

Java中的代碼塊簡介

在java中用{}括起來的稱為代碼塊,代碼塊可分為以下四種:

一.簡介

1.普通代碼塊:

類中方法的方法體

2.構造代碼塊

構造塊會在創建對象時被調用,每次創建時都會被調用,優先於類構造函數執行。

3.靜態代碼塊:

用static{}包裹起來的代碼片段,只會執行一次。靜態代碼塊優先於構造塊執行。

4.同步代碼塊:

使用synchronized(){}包裹起來的代碼塊,在多線程環境下,對共用數據的讀寫操作是需要互斥進行的,否則會導致數據的不一致性。同步代碼塊需要寫在方法中。

二.靜態代碼塊和構造代碼塊的異同點

相同點:都是JVM載入類後且在構造函數執行之前執行,在類中可定義多個,一般在代碼塊中對一些static變數進行賦值。

不同點:靜態代碼塊在非靜態代碼塊之前執行。靜態代碼塊只在第一次new時執行一次,之後不在執行。而非靜態代碼塊每new一次就執行一次。

Java代碼塊使用

局部代碼塊

位置:局部位置(方法內部)

作用:限定變數的生命周期,儘早釋放,節約記憶體

調用:調用其所在的方法時執行

 public class 局部代碼塊 {
@Test
public void test (){
    B b = new B();
    b.go();
}
}
class B {
    B(){}
    public void go() {
        //方法中的局部代碼塊,一般進行一次性地調用,調用完立刻釋放空間,避免在接下來的調用過程中占用棧空間
        //因為棧空間記憶體是有限的,方法調用可能會會生成很多局部變數導致棧記憶體不足。
        //使用局部代碼塊可以避免這樣的情況發生。
        {
            int i = 1;
            ArrayList<Integer> list = new ArrayList<>();
            while (i < 1000) {
                list.add(i ++);
            }
            for (Integer j : list) {
                System.out.println(j);
            }
            System.out.println("gogogo");
        }
        System.out.println("hello");
    }
}

構造代碼塊

位置:類成員的位置,就是類中方法之外的位置

作用:把多個構造方法共同的部分提取出來,共用構造代碼塊

調用:每次調用構造方法時,都會優先於構造方法執行,也就是每次new一個對象時自動調用,對 對象的初始化

class A{
    int i = 1;
    int initValue;//成員變數的初始化交給代碼塊來完成
    {
        //代碼塊的作用體現於此:在調用構造方法之前,用某段代碼對成員變數進行初始化。
        //而不是在構造方法調用時再進行。一般用於將構造方法的相同部分提取出來。
        //
        for (int i = 0;i < 100;i ++) {
            initValue += i;
        }
    }
    {
        System.out.println(initValue);
        System.out.println(i);//此時會列印1
        int i = 2;//代碼塊里的變數和成員變數不衝突,但會優先使用代碼塊的變數
        System.out.println(i);//此時列印2
        //System.out.println(j);//提示非法向後引用,因為此時j的的初始化還沒開始。
        //
    }
    {
        System.out.println("代碼塊運行");
    }
    int j = 2;
    {
        System.out.println(j);
        System.out.println(i);//代碼塊中的變數運行後自動釋放,不會影響代碼塊之外的代碼
    }
    A(){
        System.out.println("構造方法運行");
    }
}
public class 構造代碼塊 {
    @Test
    public void test() {
        A a = new A();
    }
}

靜態代碼塊

 位置:類成員位置,用static修飾的代碼塊

 作用:對類進行一些初始化  只載入一次,當new多個對象時,只有第一次會調用靜態代碼塊,因為,靜態代碼塊                  是屬於類的,所有對象共用一份

 調用: new 一個對象時自動調用

 public class 靜態代碼塊 {

@Test
public void test() {
    C c1 = new C();
    C c2 = new C();
    //結果,靜態代碼塊只會調用一次,類的所有對象共用該代碼塊
    //一般用於類的全局信息初始化
    //靜態代碼塊調用
    //代碼塊調用
    //構造方法調用
    //代碼塊調用
    //構造方法調用
}

}
class C{
    C(){
        System.out.println("構造方法調用");
    }
    {
        System.out.println("代碼塊調用");
    }
    static {
        System.out.println("靜態代碼塊調用");
    }
}

Java代碼塊、構造方法(包含繼承關係)的執行順序

這是一道常見的面試題,要回答這個問題,先看看這個實例吧。

一共3個類:A、B、C
其中A是B的父類,C無繼承僅作為輸出

A類:

public class A {

static {
    Log.i("HIDETAG", "A靜態代碼塊");
}

private static C c = new C("A靜態成員");
private  C c1 = new C("A成員");

{
    Log.i("HIDETAG", "A代碼塊");
}

static {
    Log.i("HIDETAG", "A靜態代碼塊2");
}

public A() {
    Log.i("HIDETAG", "A構造方法");
}

}

B類:

public class B extends A {

private static C c1 = new C("B靜態成員");

{
    Log.i("HIDETAG", "B代碼塊");
}

private C c = new C("B成員");

static {
    Log.i("HIDETAG", "B靜態代碼塊2");
}

static {
    Log.i("HIDETAG", "B靜態代碼塊");
}

public B() {
    Log.i("HIDETAG", "B構造方法");

}

}

C類:

public class C {

public C(String str) {
    Log.i("HIDETAG", str + "構造方法");
}
}

執行語句:new B();

輸出結果如下:

 I/HIDETAG: A靜態代碼塊
 I/HIDETAG: A靜態成員構造方法
 I/HIDETAG: A靜態代碼塊2
 I/HIDETAG: B靜態成員構造方法
 I/HIDETAG: B靜態代碼塊2
 I/HIDETAG: B靜態代碼塊
 I/HIDETAG: A成員構造方法
 I/HIDETAG: A代碼塊
 I/HIDETAG: A構造方法
 I/HIDETAG: B代碼塊
 I/HIDETAG: B成員構造方法
 I/HIDETAG: B構造方法

得出結論:

執行順序依次為:
父類的靜態成員和代碼塊
子類靜態成員和代碼塊
父類成員初始化和代碼快
父類構造方法
子類成員初始化和代碼塊
子類構造方法

註意:可以發現,同一級別的代碼塊和成員初始化是按照代碼順序從上到下依次執行

看完上面這個demo,再來看看下麵這道題,看看你搞得定嗎?

看下麵一段代碼,求執行順序:

class A {
    public A() {
        System.out.println("1A類的構造方法");
    }
    {
        System.out.println("2A類的構造快");
    }
    static {
        System.out.println("3A類的靜態塊");
    }
}
 
public class B extends A {
    public B() {
        System.out.println("4B類的構造方法");
    }
    {
        System.out.println("5B類的構造快");
    }
    static {
        System.out.println("6B類的靜態塊");
    }
    public static void main(String[] args) {
        System.out.println("7");
        new B();
        new B();
        System.out.println("8");
    }
}

執行順序結果為:367215421548

為什麼呢?

首先我們要知道下麵這5點:

每次new都會執行構造方法以及構造塊。
構造塊的內容會在構造方法之前執行。
非主類的靜態塊會在類載入時,構造方法和構造塊之前執行,切只執行一次。
主類(public class)里的靜態塊會先於main執行。
繼承中,子類實例化,會先執行父類的構造方法,產生父類對象,再調用子類構造方法。
所以題目里,由於主類B繼承A,所以會先載入A,所以第一個執行的是第3句。

從第4點我們知道6會在7之前執行,所以前三句是367。

之後實例化了B兩次,每次都會先實例化他的父類A,然後再實例化B,而根據第1、2、5點,知道順序為2154。

最後執行8

所以順序是367215421548

參考文章

https://blog.csdn.net/likunkun__/article/details/83066062
https://www.jianshu.com/p/6877aae403f7
https://www.jianshu.com/p/49e45af288ea
https://blog.csdn.net/du_du1/article/details/91383128
http://c.biancheng.net/view/976.html
https://blog.csdn.net/evilcry2012/article/details/79499786
https://www.jb51.net/article/129990.htm

微信公眾號

Java技術江湖

如果大家想要實時關註我更新的文章以及分享的乾貨的話,可以關註我的公眾號【Java技術江湖】一位阿裡 Java 工程師的技術小站,作者黃小斜,專註 Java 相關技術:SSM、SpringBoot、MySQL、分散式、中間件、集群、Linux、網路、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!

Java工程師必備學習資源: 一些Java工程師常用學習資源,關註公眾號後,後臺回覆關鍵字 “Java” 即可免費無套路獲取。

我的公眾號

個人公眾號:黃小斜

作者是 985 碩士,螞蟻金服 JAVA 工程師,專註於 JAVA 後端技術棧:SpringBoot、MySQL、分散式、中間件、微服務,同時也懂點投資理財,偶爾講點演算法和電腦理論基礎,堅持學習和寫作,相信終身學習的力量!

程式員3T技術學習資源: 一些程式員學習技術的資源大禮包,關註公眾號後,後臺回覆關鍵字 “資料” 即可免費無套路獲取。


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

-Advertisement-
Play Games
更多相關文章
  • 增加Security配置類 前面演示了一個簡單的登錄入門例子,使用springboot security預設的配置實現,雖然非常簡單,但是基本實現了登錄功能。不過在生產環境下,顯然不能僅僅使用如此簡單的登錄功能,我們還需要更多個性化的登錄配置,所以我們要使用配置類來代替預設配置。新建一個配置類 We ...
  • 前言:C++是博大精深的語言,特性複雜得跟北京二環一樣,繼承亂得跟亂倫似的。 不過它仍然是我最熟悉且必須用在游戲開發上的語言,這篇文章用於挑選出一些個人覺得重要的條款/經驗/技巧進行記錄總結。 文章最後列出一些我看過的C++書籍/博客等,方便參考。 其實以前也寫過相同的筆記博文,現在用markdow ...
  • Web應用安全管理 Web應用的安全管理,主要包括兩個方面的內容,一個是用戶身份的認證,即用戶登錄的設計,二是用戶授權,即一個用戶在一個應用系統中能夠執行哪些操作的許可權管理。許可權管理的設計一般使用角色來管理,即給一個用戶賦予哪些角色,這個用戶就具有哪些許可權。 Spring框架體系中,經典的安全體系框 ...
  • 一、協程 1.歷史進程: (1)3.4引入協程,用yield來實現 (2)3.5引入協程語法 (3)實現協程比較好的包有asyncio,tornado,gevent 2.定義:協程是為非搶占式多任務產生子程式的電腦程式組件,協程允許不同入口點在不同位置暫停或開始執行程式 3.從技術角度講,協程就是 ...
  • python面向函數式編程,模擬用戶登錄驗證、註冊的代碼實現。 主要有以下兩個文件: 1、user.txt文檔文件,相當於資料庫的用戶信息表,主要是記錄用戶名和密碼。 註意:1)此文檔需要與.py文件放在同一個路徑下。 2)用戶名、密碼在存儲時,是以$符號區別開。 2、模擬用戶登錄驗證、註冊的代碼實 ...
  • @[TOC]     下麵向大家介紹一下我在學習python課程的一些題目的解法,如果大家有什麼更好的解法請私信我。這裡只顯示題目與代碼。 1.快樂的數字     描述: 編寫一個演算法來確定一個數字是否“快樂”。 快樂的數字按照如下方式確定 ...
  • Spring框架主要包括IoC和AOP,這兩大功能都可以使用註解進行配置。 一、bean定義 二、依賴註入 三、使用Primary註解 四、Scope註解 五、方法註入 六、AOP註解 七、ComponentScan註解 ...
  • 在有道翻譯頁面中打開開發者工具,在Headers板塊找到Request URL以及相應的data。 上面這種很大可能被有道網頁給識別出來不是人工在訪問,而是代碼在訪問。 此時我們可以加個‘User-Agent’代理。通過設置User Agent來達到隱藏身份的目的,一般情況下瀏覽器是通過User-A ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...