基礎面試題: final,static,this,super詳解

final 關鍵字

final關鍵字主要用在三個地方:變量、方法、類。

  1. 對於一個final變量,如果是基本數據類型的變量,則其數值一旦在初始化之後便不能更改;如果是引用類型的變量,則在對其初始化之後便不能再讓其指向另一個對象。
  2. 當用final修飾一個類時,表明這個類不能被繼承。final類中的所有成員方法都會被隱式地指定爲final方法。
  3. 使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的Java實現版本中,會將final方法轉爲內嵌調用。但是如果方法過於龐大,可能看不到內嵌調用帶來的任何性能提升(現在的Java版本已經不需要使用final方法進行這些優化了)。類中所有的private方法都隱式地指定爲final。

static 關鍵字

static 關鍵字主要有以下四種使用場景:

  1. 修飾成員變量和成員方法: 被 static 修飾的成員屬於類,不屬於單個這個類的某個對象,被類中所有對象共享,可以並且建議通過類名調用。被static 聲明的成員變量屬於靜態成員變量,靜態變量 存放在 Java 內存區域的方法區。調用格式:類名.靜態變量名 類名.靜態方法名()
  2. 靜態代碼塊: 靜態代碼塊定義在類中方法外, 靜態代碼塊在非靜態代碼塊之前執行(靜態代碼塊—>非靜態代碼塊—>構造方法)。 該類不管創建多少對象,靜態代碼塊只執行一次.
  3. 靜態內部類(static修飾類的話只能修飾內部類): 靜態內部類與非靜態內部類之間存在一個最大的區別: 非靜態內部類在編譯完成之後會隱含地保存着一個引用,該引用是指向創建它的外圍類,但是靜態內部類卻沒有。沒有這個引用就意味着:1. 它的創建是不需要依賴外圍類的創建。2. 它不能使用任何外圍類的非static成員變量和方法。
  4. 靜態導包(用來導入類中的靜態資源,1.5之後的新特性): 格式爲:import static 這兩個關鍵字連用可以指定導入某個類中的指定靜態資源,並且不需要使用類名調用類中靜態成員,可以直接使用類中靜態成員變量和成員方法。

static 關鍵字詳解

static 關鍵字主要有以下四種使用場景

  1. 修飾成員變量和成員方法
  2. 靜態代碼塊
  3. 修飾類(只能修飾內部類)
  4. 靜態導包(用來導入類中的靜態資源,1.5之後的新特性)

修飾成員變量和成員方法(常用)

被 static 修飾的成員屬於類,不屬於單個這個類的某個對象,被類中所有對象共享,可以並且建議通過類名調用。被static 聲明的成員變量屬於靜態成員變量,靜態變量 存放在 Java 內存區域的方法區。

方法區與 Java 堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆),目的應該是與 Java 堆區分開來。

HotSpot 虛擬機中方法區也常被稱爲 “永久代”,本質上兩者並不等價。僅僅是因爲 HotSpot 虛擬機設計團隊用永久代來實現方法區而已,這樣 HotSpot 虛擬機的垃圾收集器就可以像管理 Java 堆一樣管理這部分內存了。但是這並不是一個好主意,因爲這樣更容易遇到內存溢出問題。

調用格式:

  • 類名.靜態變量名
  • 類名.靜態方法名()

如果變量或者方法被 private 則代表該屬性或者該方法只能在類的內部被訪問而不能在類的外部被訪問。

測試方法:

public class StaticBean {

    String name;
    //靜態變量
    static int age;

    public StaticBean(String name) {
        this.name = name;
    }
    //靜態方法
    static void SayHello() {
        System.out.println("Hello i am java");
    }
    @Override
    public String toString() {
        return StaticBean{ +
                name=' + name + ''' + age + age +
                '}';
    }
}Copy to clipboardErrorCopied
public class StaticDemo {

    public static void main(String[] args) {
        StaticBean staticBean = new StaticBean(1);
        StaticBean staticBean2 = new StaticBean(2);
        StaticBean staticBean3 = new StaticBean(3);
        StaticBean staticBean4 = new StaticBean(4);
        StaticBean.age = 33;
        StaticBean{name='1'age33} StaticBean{name='2'age33} StaticBean{name='3'age33} StaticBean{name='4'age33}
        System.out.println(staticBean+ +staticBean2+ +staticBean3+ +staticBean4);
        StaticBean.SayHello();Hello i am java
    }

}Copy to clipboardErrorCopied

靜態代碼塊

靜態代碼塊定義在類中方法外, 靜態代碼塊在非靜態代碼塊之前執行(靜態代碼塊—非靜態代碼塊—構造方法)。 該類不管創建多少對象,靜態代碼塊只執行一次.

靜態代碼塊的格式是

static {    
語句體;   
}Copy to clipboardErrorCopied

一個類中的靜態代碼塊可以有多個,位置可以隨便放,它不在任何的方法體內,JVM加載類時會執行這些靜態的代碼塊,如果靜態代碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,每個代碼塊只會被執行一次。

靜態代碼塊對於定義在它之後的靜態變量,可以賦值,但是不能訪問.

靜態內部類

靜態內部類與非靜態內部類之間存在一個最大的區別,我們知道非靜態內部類在編譯完成之後會隱含地保存着一個引用,該引用是指向創建它的外圍類,但是靜態內部類卻沒有。沒有這個引用就意味着:

  1. 它的創建是不需要依賴外圍類的創建。
  2. 它不能使用任何外圍類的非static成員變量和方法。

Example(靜態內部類實現單例模式)

public class Singleton {

    //聲明爲 private 避免調用默認構造方法創建對象
    private Singleton() {
    }

   // 聲明爲 private 表明靜態內部該類只能在該 Singleton 類中被訪問
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    }
}Copy to clipboardErrorCopied

當 Singleton 類加載時,靜態內部類 SingletonHolder 沒有被加載進內存。只有當調用 getUniqueInstance()方法從而觸發 SingletonHolder.INSTANCE 時 SingletonHolder 纔會被加載,此時初始化 INSTANCE 實例,並且 JVM 能確保 INSTANCE 只被實例化一次。

這種方式不僅具有延遲初始化的好處,而且由 JVM 提供了對線程安全的支持。

靜態導包

格式爲:import static

這兩個關鍵字連用可以指定導入某個類中的指定靜態資源,並且不需要使用類名調用類中靜態成員,可以直接使用類中靜態成員變量和成員方法

 //將Math中的所有靜態資源導入,這時候可以直接使用裏面的靜態方法,而不用通過類名進行調用
 //如果只想導入單一某個靜態方法,只需要將換成對應的方法名即可

import static java.lang.Math.;//換成import static java.lang.Math.max;具有一樣的效果

public class Demo {
  public static void main(String[] args) {

    int max = max(1,2);
    System.out.println(max);
  }
}
Copy to clipboardErrorCopied

補充內容

靜態方法與非靜態方法

靜態方法屬於類本身,非靜態方法屬於從該類生成的每個對象。 如果您的方法執行的操作不依賴於其類的各個變量和方法,請將其設置爲靜態(這將使程序的佔用空間更小)。 否則,它應該是非靜態的。

Example

class Foo {
    int i;
    public Foo(int i) { 
       this.i = i;
    }

    public static String method1() {
       return An example string that doesn't depend on i (an instance variable);

    }

    public int method2() {
       return this.i + 1;  Depends on i
    }

}Copy to clipboardErrorCopied

你可以像這樣調用靜態方法:Foo.method1()。 如果您嘗試使用這種方法調用 method2 將失敗。 但這樣可行:Foo bar = new Foo(1);bar.method2();

總結:

  • 在外部調用靜態方法時,可以使用”類名.方法名”的方式,也可以使用”對象名.方法名”的方式。而實例方法只有後面這種方式。也就是說,調用靜態方法可以無需創建對象。
  • 靜態方法在訪問本類的成員時,只允許訪問靜態成員(即靜態成員變量和靜態方法),而不允許訪問實例成員變量和實例方法;實例方法則無此限制

static{}靜態代碼塊與{}非靜態代碼塊(構造代碼塊)

相同點: 都是在JVM加載類時且在構造方法執行之前執行,在類中都可以定義多個,定義多個時按定義的順序執行,一般在代碼塊中對一些static變量進行賦值。

不同點: 靜態代碼塊在非靜態代碼塊之前執行(靜態代碼塊—非靜態代碼塊—構造方法)。靜態代碼塊只在第一次new執行一次,之後不再執行,而非靜態代碼塊在每new一次就執行一次。 非靜態代碼塊可在普通方法中定義(不過作用不大);而靜態代碼塊不行。

修正 issue #677:靜態代碼塊可能在第一次new的時候執行,但不一定只在第一次new的時候執行。比如通過 Class.forName("ClassDemo")創建 Class 對象的時候也會執行。

一般情況下,如果有些代碼比如一些項目最常用的變量或對象必須在項目啓動的時候就執行的時候,需要使用靜態代碼塊,這種代碼是主動執行的。如果我們想要設計不需要創建對象就可以調用類中的方法,例如:Arrays類,Character類,String類等,就需要使用靜態方法, 兩者的區別是 靜態代碼塊是自動執行的而靜態方法是被調用的時候才執行的.

Example:

public class Test {
    public Test() {
        System.out.print("默認構造方法!--");
    }

    //非靜態代碼塊
    {
        System.out.print("非靜態代碼塊!--");
    }

    //靜態代碼塊
    static {
        System.out.print("靜態代碼塊!--");
    }

    private static void test() {
        System.out.print("靜態方法中的內容! --");
        {
            System.out.print("靜態方法中的代碼塊!--");
        }

    }

    public static void main(String[] args) {
        Test test = new Test();
        Test.test();//靜態代碼塊!--靜態方法中的內容! --靜態方法中的代碼塊!--
    }
}

非靜態代碼塊與構造函數的區別是: 非靜態代碼塊是給所有對象進行統一初始化,而構造函數是給對應的對象初始化,因爲構造函數是可以多個的,運行哪個構造函數就會建立什麼樣的對象,但無論建立哪個對象,都會先執行相同的構造代碼塊。也就是說,構造代碼塊中定義的是不同對象共性的初始化內容。

this關鍵字詳解

​ 1.每個類的每個非靜態方法(沒有被static修飾)都會隱含一個this關鍵字,它指向調用這個方法的對象;當在方法中使用本類屬性時,都會隱含地使用this關鍵字,當然也可以明確使用。

​ a.this可以看成是一個變量,它的值就是當前對象的引用

​ b.this關鍵字只能在方法內部使用,表示對“調用方法的那個對象”的引用如果是在同一類中調用另外一個方法,則可以不用寫this,直接調用

​ 2.爲了區分屬性和局部變量,可以通過this關鍵字來調用

​ 3.this關鍵字的用法

​ a.當類中非靜態方法的參數名與類的某個成員變量名相同時,爲了避免參數作用範圍覆蓋了成員變量的作用範圍,必須明確使用this關鍵字來指定

​ b.如果某個構造方法的第一條語句具有形式this(…),那麼這個構造方法將調用本類中的其他構造方法

​ c.如果某個方法需要傳入當前對象,則可以將當前的對象作爲參數傳遞給它

public class pra {

                     private String  name;

	                 private String  sex;

                     private Integer age;

           public pra(String name, String sex, Integer age) {

                     super();

                     this.name = name;

                     this.sex = sex;

                     this.age = age;

         }

          public pra() {

           this("by", "女", 15);  //調到了有參的構造方法

        }

}

super關鍵字詳解

​ super代表了父類空間的引用

​ 1 super的作用:

​ a.子父類存在着同名的成員時,在子類中默認時訪問子類的成員,可以通過super關鍵字指定訪問父類的成員

​ b.創建子類對象時,默認會先調用父類無參的構造方法,可以通過super關鍵字指定調用父類的構造方法

2 super的用法

​ super可以用來引用直接父類的實例變量。

​ super可以用來調用直接父類方法。

​ super()可以用於調用直接父類構造函數

class C {
	String name = "A";

	public void work() {
		System.out.println("A工作!");
	}

}

class B extends C {
	String name = "B";

	public B() {
		super(); // 調用父類構造方法
	}
	 
	public void work() {
		System.out.println("B工作!");
	}
	 
	public void pint() {
		System.out.println(name);
		System.out.println(super.name); // 調用父類的實例變量
		super.work(); // 調用父類的方法
	}

}

public class A {
	public static void main(String[] args) {
		B b = new B();
		b.pint();
	}
}

this和super注意事項

1.如果在子類的構造方法上沒有指定調用父類的構造方法,java編譯器會在子類的構造器裏面加上super()語句

2.super關鍵字調用父類的構造函數時,該語句必須要是子類構造函數的第一個語句

3.super和this不能同時出現在同一個構造函數中調用其他的構造函數,因爲兩個語句都要是第一個語句

this和super的區別

1.代表的事物不同

​ super代表的是父類空間的引用

​ this代表的是所屬函數的調用者對象

2.使用前提不同

​ super必須要有繼承關係才能使用

​ this不需要繼承關係也能使用

3調用的構造函數不同

​ super:調用父類的構造函數

​ this:調用所屬類的構造函數

如果大家對java架構相關感興趣,可以關注下面公衆號,會持續更新java基礎面試題, netty, spring boot,spring cloud等系列文章,一系列乾貨隨時送達, 超神之路從此展開, BTAJ不再是夢想!

架構殿堂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章