夯實Java基礎系列4:一文了解final關鍵字的特性咱旱、使用方法确丢,以及實現(xiàn)原理

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

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

喜歡的話麻煩點下Star哈

文章首發(fā)于我的個人博客:

www.how2playlife.com

本文是微信公眾號【Java技術江湖】的《夯實Java基礎系列博文》其中一篇吐限,本文部分內容來源于網(wǎng)絡鲜侥,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術博客內容诸典,引用其中了一些比較好的博客文章剃毒,如有侵權,請聯(lián)系作者。

該系列博文會告訴你如何從入門到進階赘阀,一步步地學習Java基礎知識益缠,并上手進行實戰(zhàn),接著了解每個Java知識點背后的實現(xiàn)原理基公,更完整地了解整個Java技術體系幅慌,形成自己的知識框架。為了更好地總結和檢驗你的學習成果轰豆,本系列文章也會提供部分知識點對應的面試題以及參考答案胰伍。

如果對本系列文章有什么建議,或者是有什么疑問的話酸休,也可以關注公眾號【Java技術江湖】聯(lián)系作者骂租,歡迎你參與本系列博文的創(chuàng)作和修訂。

final關鍵字特性

final關鍵字在java中使用非常廣泛斑司,可以申明成員變量渗饮、方法、類宿刮、本地變量互站。一旦將引用聲明為final,將無法再改變這個引用僵缺。final關鍵字還能保證內存同步胡桃,本博客將會從final關鍵字的特性到從java內存層面保證同步講解。這個內容在面試中也有可能會出現(xiàn)磕潮。

final使用

final變量

final變量有成員變量或者是本地變量(方法內的局部變量)翠胰,在類成員中final經(jīng)常和static一起使用,作為類常量使用自脯。其中類常量必須在聲明時初始化之景,final成員常量可以在構造函數(shù)初始化。

public class Main {
    public static final int i; //報錯冤今,必須初始化 因為常量在常量池中就存在了闺兢,調用時不需要類的初始化,所以必須在聲明時初始化
    public static final int j;
    Main() {
        i = 2;
        j = 3;
    }
}

就如上所說的戏罢,對于類常量屋谭,JVM會緩存在常量池中,在讀取該變量時不會加載這個類龟糕。


public class Main {
    public static final int i = 2;
    Main() {
        System.out.println("調用構造函數(shù)"); // 該方法不會調用
    }
    public static void main(String[] args) {
        System.out.println(Main.i);
    }
}

final修飾基本數(shù)據(jù)類型變量和引用

@Test
public void final修飾基本類型變量和引用() {
    final int a = 1;
    final int[] b = {1};
    final int[] c = {1};
//  b = c;報錯
    b[0] = 1;
    final String aa = "a";
    final Fi f = new Fi();
    //aa = "b";報錯
    // f = null;//報錯
    f.a = 1;
}

final方法表示該方法不能被子類的方法重寫桐磁,將方法聲明為final,在編譯的時候就已經(jīng)靜態(tài)綁定了讲岁,不需要在運行時動態(tài)綁定我擂。final方法調用時使用的是invokespecial指令衬以。

class PersonalLoan{
    public final String getName(){
        return"personal loan”;
    }
}

class CheapPersonalLoan extends PersonalLoan{
    @Override
    public final String getName(){
        return"cheap personal loan";//編譯錯誤,無法被重載
    }

    public String test() {
        return getName(); //可以調用校摩,因為是public方法
    }
}

final類

final類不能被繼承看峻,final類中的方法默認也會是final類型的,java中的String類和Integer類都是final類型的衙吩。

class Si{
    //一般情況下final修飾的變量一定要被初始化互妓。
    //只有下面這種情況例外,要求該變量必須在構造方法中被初始化坤塞。
    //并且不能有空參數(shù)的構造方法冯勉。
    //這樣就可以讓每個實例都有一個不同的變量,并且這個變量在每個實例中只會被初始化一次
    //于是這個變量在單個實例里就是常量了摹芙。
    final int s ;
    Si(int s) {
        this.s = s;
    }
}
class Bi {
    final int a = 1;
    final void go() {
        //final修飾方法無法被繼承
    }
}
class Ci extends Bi {
    final int a = 1;
//        void go() {
//            //final修飾方法無法被繼承
//        }
}
final char[]a = {'a'};
final int[]b = {1};
final class PersonalLoan{}

class CheapPersonalLoan extends PersonalLoan {  //編譯錯誤灼狰,無法被繼承 
}

@Test
public void final修飾類() {
    //引用沒有被final修飾,所以是可變的浮禾。
    //final只修飾了Fi類型交胚,即Fi實例化的對象在堆中內存地址是不可變的。
    //雖然內存地址不可變伐厌,但是可以對內部的數(shù)據(jù)做改變承绸。
    Fi f = new Fi();
    f.a = 1;
    System.out.println(f);
    f.a = 2;
    System.out.println(f);
    //改變實例中的值并不改變內存地址裸影。

    Fi ff = f;
    //讓引用指向新的Fi對象挣轨,原來的f對象由新的引用ff持有。
    //引用的指向改變也不會改變原來對象的地址
    f = new Fi();
    System.out.println(f);
    System.out.println(ff);
}

final關鍵字的知識點

  1. final成員變量必須在聲明的時候初始化或者在構造器中初始化轩猩,否則就會報編譯錯誤卷扮。final變量一旦被初始化后不能再次賦值。
  2. 本地變量必須在聲明時賦值均践。 因為沒有初始化的過程
  3. 在匿名類中所有變量都必須是final變量晤锹。
  4. final方法不能被重寫, final類不能被繼承
  5. 接口中聲明的所有變量本身是final的。類似于匿名類
  6. final和abstract這兩個關鍵字是反相關的彤委,final類就不可能是abstract的鞭铆。
  7. final方法在編譯階段綁定,稱為靜態(tài)綁定(static binding)焦影。
  8. 將類车遂、方法、變量聲明為final能夠提高性能斯辰,這樣JVM就有機會進行估計舶担,然后優(yōu)化。

final方法的好處:

  1. 提高了性能彬呻,JVM在常量池中會緩存final變量
  2. final變量在多線程中并發(fā)安全衣陶,無需額外的同步開銷
  3. final方法是靜態(tài)編譯的柄瑰,提高了調用速度
  4. final類創(chuàng)建的對象是只可讀的,在多線程可以安全共享

final關鍵字的最佳實踐

final的用法

1剪况、final 對于常量來說教沾,意味著值不能改變屑柔,例如 final int i=100瘫证。這個i的值永遠都是100霸妹。
但是對于變量來說又不一樣推沸,只是標識這個引用不可被改變棍掐,例如 final File f=new File("c:\test.txt");

那么這個f一定是不能被改變的廊鸥,如果f本身有方法修改其中的成員變量乖篷,例如是否可讀凉蜂,是允許修改的该贾。有個形象的比喻:一個女子定義了一個final的老公羔杨,這個老公的職業(yè)和收入都是允許改變的,只是這個女人不會換老公而已杨蛋。

關于空白final

final修飾的變量有三種:靜態(tài)變量兜材、實例變量和局部變量,分別表示三種類型的常量逞力。
 另外曙寡,final變量定義的時候,可以先聲明寇荧,而不給初值举庶,這中變量也稱為final空白,無論什么情況揩抡,編譯器都確被Ы模空白final在使用之前必須被初始化。
 
但是峦嗤,final空白在final關鍵字final的使用上提供了更大的靈活性蕊唐,為此,一個類中的final數(shù)據(jù)成員就可以實現(xiàn)依對象而有所不同烁设,卻有保持其恒定不變的特征替梨。

public class FinalTest { 
final int p; 
final int q=3; 
FinalTest(){ 
p=1; 
} 
FinalTest(int i){ 
p=i;//可以賦值,相當于直接定義p 
q=i;//不能為一個final變量賦值 
} 
} 

final內存分配

剛提到了內嵌機制装黑,現(xiàn)在詳細展開副瀑。
要知道調用一個函數(shù)除了函數(shù)本身的執(zhí)行時間之外,還需要額外的時間去尋找這個函數(shù)(類內部有一個函數(shù)簽名和函數(shù)地址的映射表)曹体。所以減少函數(shù)調用次數(shù)就等于降低了性能消耗俗扇。

final修飾的函數(shù)會被編譯器優(yōu)化,優(yōu)化的結果是減少了函數(shù)調用的次數(shù)箕别。如何實現(xiàn)的铜幽,舉個例子給你看:

public class Test{ 
final void func(){System.out.println("g");}; 
public void main(String[] args){ 
for(int j=0;j<1000;j++)   
func(); 
}} 
經(jīng)過編譯器優(yōu)化之后滞谢,這個類變成了相當于這樣寫: 
public class Test{ 
final void func(){System.out.println("g");}; 
public void main(String[] args){ 
for(int j=0;j<1000;j++)  
{System.out.println("g");} 
}} 

看出來區(qū)別了吧?編譯器直接將func的函數(shù)體內嵌到了調用函數(shù)的地方除抛,這樣的結果是節(jié)省了1000次函數(shù)調用狮杨,當然編譯器處理成字節(jié)碼,只是我們可以想象成這樣到忽,看個明白橄教。

不過,當函數(shù)體太長的話喘漏,用final可能適得其反护蝶,因為經(jīng)過編譯器內嵌之后代碼長度大大增加,于是就增加了jvm解釋字節(jié)碼的時間翩迈。

在使用final修飾方法的時候持灰,編譯器會將被final修飾過的方法插入到調用者代碼處,提高運行速度和效率负饲,但被final修飾的方法體不能過大堤魁,編譯器可能會放棄內聯(lián),但究竟多大的方法會放棄返十,我還沒有做測試來計算過妥泉。

下面這些內容是通過兩個疑問來繼續(xù)闡述的

使用final修飾方法會提高速度和效率嗎

見下面的測試代碼,我會執(zhí)行五次:

public class Test   
{   
    public static void getJava()   
    {   
        String str1 = "Java ";   
        String str2 = "final ";   
        for (int i = 0; i < 10000; i++)   
        {   
            str1 += str2;   
        }   
    }   
    public static final void getJava_Final()   
    {   
        String str1 = "Java ";   
        String str2 = "final ";   
        for (int i = 0; i < 10000; i++)   
        {   
            str1 += str2;   
        }   
    }   
    public static void main(String[] args)   
    {   
        long start = System.currentTimeMillis();   
        getJava();   
        System.out.println("調用不帶final修飾的方法執(zhí)行時間為:" + (System.currentTimeMillis() - start) + "毫秒時間");   
        start = System.currentTimeMillis();   
        String str1 = "Java ";   
        String str2 = "final ";   
        for (int i = 0; i < 10000; i++)   
        {   
            str1 += str2;   
        }   
        System.out.println("正常的執(zhí)行時間為:" + (System.currentTimeMillis() - start) + "毫秒時間");   
        start = System.currentTimeMillis();   
        getJava_Final();   
        System.out.println("調用final修飾的方法執(zhí)行時間為:" + (System.currentTimeMillis() - start) + "毫秒時間");   
    }   
}  


結果為: 
第一次: 
調用不帶final修飾的方法執(zhí)行時間為:1732毫秒時間 
正常的執(zhí)行時間為:1498毫秒時間 
調用final修飾的方法執(zhí)行時間為:1593毫秒時間 
第二次: 
調用不帶final修飾的方法執(zhí)行時間為:1217毫秒時間 
正常的執(zhí)行時間為:1031毫秒時間 
調用final修飾的方法執(zhí)行時間為:1124毫秒時間 
第三次: 
調用不帶final修飾的方法執(zhí)行時間為:1154毫秒時間 
正常的執(zhí)行時間為:1140毫秒時間 
調用final修飾的方法執(zhí)行時間為:1202毫秒時間 
第四次: 
調用不帶final修飾的方法執(zhí)行時間為:1139毫秒時間 
正常的執(zhí)行時間為:999毫秒時間 
調用final修飾的方法執(zhí)行時間為:1092毫秒時間 
第五次: 
調用不帶final修飾的方法執(zhí)行時間為:1186毫秒時間 
正常的執(zhí)行時間為:1030毫秒時間 
調用final修飾的方法執(zhí)行時間為:1109毫秒時間 

由以上運行結果不難看出洞坑,執(zhí)行最快的是“正常的執(zhí)行”即代碼直接編寫盲链,而使用final修飾的方法,不像有些書上或者文章上所說的那樣检诗,速度與效率與“正常的執(zhí)行”無異匈仗,而是位于第二位瓢剿,最差的是調用不加final修飾的方法逢慌。 

觀點:加了比不加好一點。

使用final修飾變量會讓變量的值不能被改變嗎间狂;

見代碼:

public class Final   
{   
    public static void main(String[] args)   
    {   
        Color.color[3] = "white";   
        for (String color : Color.color)   
            System.out.print(color+" ");   
    }   
}   
  
class Color   
{   
    public static final String[] color = { "red", "blue", "yellow", "black" };   
}  


執(zhí)行結果: 
red blue yellow white 
看攻泼!,黑色變成了白色鉴象。 


在使用findbugs插件時忙菠,就會提示public static String[] color = { "red", "blue", "yellow", "black" };這行代碼不安全,但加上final修飾纺弊,這行代碼仍然是不安全的牛欢,因為final沒有做到保證變量的值不會被修改!

原因是:final關鍵字只能保證變量本身不能被賦與新值淆游,而不能保證變量的內部結構不被修改傍睹。例如在main方法有如下代碼Color.color = new String[]{""};就會報錯了隔盛。

如何保證數(shù)組內部不被修改

那可能有的同學就會問了,加上final關鍵字不能保證數(shù)組不會被外部修改拾稳,那有什么方法能夠保證呢吮炕?答案就是降低訪問級別,把數(shù)組設為private访得。這樣的話龙亲,就解決了數(shù)組在外部被修改的不安全性,但也產(chǎn)生了另一個問題悍抑,那就是這個數(shù)組要被外部使用的鳄炉。 

解決這個問題見代碼:

import java.util.AbstractList;   
import java.util.List;   

public class Final   
{   
    public static void main(String[] args)   
    {   
        for (String color : Color.color)   
            System.out.print(color + " ");   
        Color.color.set(3, "white");   
    }   
}   
  
class Color   
{   
    private static String[] _color = { "red", "blue", "yellow", "black" };   
    public static List<String> color = new AbstractList<String>()   
    {   
        @Override  
        public String get(int index)   
        {   
            return _color[index];   
        }   
        @Override  
        public String set(int index, String value)   
        {   
            throw new RuntimeException("為了代碼安全,不能修改數(shù)組");   
        }   
        @Override  
        public int size()   
        {   
            return _color.length;   
        }   
    };  


}

這樣就OK了,既保證了代碼安全搜骡,又能讓數(shù)組中的元素被訪問了迎膜。

final方法的三條規(guī)則

規(guī)則1:final修飾的方法不可以被重寫。

規(guī)則2:final修飾的方法僅僅是不能重寫浆兰,但它完全可以被重載磕仅。

規(guī)則3:父類中private final方法,子類可以重新定義簸呈,這種情況不是重寫榕订。

代碼示例

規(guī)則1代碼

public class FinalMethodTest
{
    public final void test(){}
}
class Sub extends FinalMethodTest
{
    // 下面方法定義將出現(xiàn)編譯錯誤,不能重寫final方法
    public void test(){}
}

規(guī)則2代碼

public class Finaloverload {
    //final 修飾的方法只是不能重寫蜕便,完全可以重載
    public final void test(){}
    public final void test(String arg){}
}

規(guī)則3代碼

public class PrivateFinalMethodTest
{
    private final void test(){}
}
class Sub extends PrivateFinalMethodTest
{
    // 下面方法定義將不會出現(xiàn)問題
    public void test(){}
}

final 和 jvm的關系

與前面介紹的鎖和 volatile 相比較劫恒,對 final 域的讀和寫更像是普通的變量訪問。對于 final 域轿腺,編譯器和處理器要遵守兩個重排序規(guī)則:

  1. 在構造函數(shù)內對一個 final 域的寫入两嘴,與隨后把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序族壳。
  2. 初次讀一個包含 final 域的對象的引用憔辫,與隨后初次讀這個 final 域,這兩個操作之間不能重排序仿荆。

下面贰您,我們通過一些示例性的代碼來分別說明這兩個規(guī)則:

<pre>public class FinalExample {
int i; // 普通變量
final int j; //final 變量
static FinalExample obj;

public void FinalExample () {     // 構造函數(shù) 
    i = 1;                        // 寫普通域 
    j = 2;                        // 寫 final 域 
}

public static void writer () {    // 寫線程 A 執(zhí)行 
    obj = new FinalExample ();
}

public static void reader () {       // 讀線程 B 執(zhí)行 
    FinalExample object = obj;       // 讀對象引用 
    int a = object.i;                // 讀普通域 
    int b = object.j;                // 讀 final 域 
}

}
</pre>

這里假設一個線程 A 執(zhí)行 writer () 方法,隨后另一個線程 B 執(zhí)行 reader () 方法拢操。下面我們通過這兩個線程的交互來說明這兩個規(guī)則锦亦。

寫 final 域的重排序規(guī)則

寫 final 域的重排序規(guī)則禁止把 final 域的寫重排序到構造函數(shù)之外。這個規(guī)則的實現(xiàn)包含下面 2 個方面:

  • JMM 禁止編譯器把 final 域的寫重排序到構造函數(shù)之外令境。
  • 編譯器會在 final 域的寫之后杠园,構造函數(shù) return 之前,插入一個 StoreStore 屏障舔庶。這個屏障禁止處理器把 final 域的寫重排序到構造函數(shù)之外抛蚁。

現(xiàn)在讓我們分析 writer () 方法玲昧。writer () 方法只包含一行代碼:finalExample = new FinalExample ()。這行代碼包含兩個步驟:

  1. 構造一個 FinalExample 類型的對象篮绿;
  2. 把這個對象的引用賦值給引用變量 obj孵延。

假設線程 B 讀對象引用與讀對象的成員域之間沒有重排序(馬上會說明為什么需要這個假設),下圖是一種可能的執(zhí)行時序:

在上圖中亲配,寫普通域的操作被編譯器重排序到了構造函數(shù)之外尘应,讀線程 B 錯誤的讀取了普通變量 i 初始化之前的值。而寫 final 域的操作吼虎,被寫 final 域的重排序規(guī)則“限定”在了構造函數(shù)之內犬钢,讀線程 B 正確的讀取了 final 變量初始化之后的值。

寫 final 域的重排序規(guī)則可以確保:在對象引用為任意線程可見之前思灰,對象的 final 域已經(jīng)被正確初始化過了玷犹,而普通域不具有這個保障。以上圖為例洒疚,在讀線程 B“看到”對象引用 obj 時歹颓,很可能 obj 對象還沒有構造完成(對普通域 i 的寫操作被重排序到構造函數(shù)外,此時初始值 2 還沒有寫入普通域 i)油湖。

讀 final 域的重排序規(guī)則

讀 final 域的重排序規(guī)則如下:

  • 在一個線程中巍扛,初次讀對象引用與初次讀該對象包含的 final 域,JMM 禁止處理器重排序這兩個操作(注意乏德,這個規(guī)則僅僅針對處理器)撤奸。編譯器會在讀 final 域操作的前面插入一個 LoadLoad 屏障。

初次讀對象引用與初次讀該對象包含的 final 域喊括,這兩個操作之間存在間接依賴關系胧瓜。由于編譯器遵守間接依賴關系,因此編譯器不會重排序這兩個操作郑什。大多數(shù)處理器也會遵守間接依賴府喳,大多數(shù)處理器也不會重排序這兩個操作。但有少數(shù)處理器允許對存在間接依賴關系的操作做重排序(比如 alpha 處理器)蹦误,這個規(guī)則就是專門用來針對這種處理器劫拢。

reader() 方法包含三個操作:

  1. 初次讀引用變量 obj;
  2. 初次讀引用變量 obj 指向對象的普通域 j。
  3. 初次讀引用變量 obj 指向對象的 final 域 i强胰。

現(xiàn)在我們假設寫線程 A 沒有發(fā)生任何重排序,同時程序在不遵守間接依賴的處理器上執(zhí)行妹沙,下面是一種可能的執(zhí)行時序:

在上圖中偶洋,讀對象的普通域的操作被處理器重排序到讀對象引用之前。讀普通域時距糖,該域還沒有被寫線程 A 寫入玄窝,這是一個錯誤的讀取操作牵寺。而讀 final 域的重排序規(guī)則會把讀對象 final 域的操作“限定”在讀對象引用之后,此時該 final 域已經(jīng)被 A 線程初始化過了恩脂,這是一個正確的讀取操作帽氓。

讀 final 域的重排序規(guī)則可以確保:在讀一個對象的 final 域之前,一定會先讀包含這個 final 域的對象的引用俩块。在這個示例程序中黎休,如果該引用不為 null,那么引用對象的 final 域一定已經(jīng)被 A 線程初始化過了玉凯。

如果 final 域是引用類型

上面我們看到的 final 域是基礎數(shù)據(jù)類型势腮,下面讓我們看看如果 final 域是引用類型,將會有什么效果漫仆?

請看下列示例代碼:

<pre>public class FinalReferenceExample {
final int[] intArray; //final 是引用類型
static FinalReferenceExample obj;

public FinalReferenceExample () { // 構造函數(shù)
intArray = new int[1]; //1
intArray[0] = 1; //2
}

public static void writerOne () { // 寫線程 A 執(zhí)行
obj = new FinalReferenceExample (); //3
}

public static void writerTwo () { // 寫線程 B 執(zhí)行
obj.intArray[0] = 2; //4
}

public static void reader () { // 讀線程 C 執(zhí)行
if (obj != null) { //5
int temp1 = obj.intArray[0]; //6
}
}
}
</pre>

這里 final 域為一個引用類型捎拯,它引用一個 int 型的數(shù)組對象。對于引用類型盲厌,寫 final 域的重排序規(guī)則對編譯器和處理器增加了如下約束:

  1. 在構造函數(shù)內對一個 final 引用的對象的成員域的寫入署照,與隨后在構造函數(shù)外把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序吗浩。

對上面的示例程序藤树,我們假設首先線程 A 執(zhí)行 writerOne() 方法,執(zhí)行完后線程 B 執(zhí)行 writerTwo() 方法拓萌,執(zhí)行完后線程 C 執(zhí)行 reader () 方法岁钓。下面是一種可能的線程執(zhí)行時序:

在上圖中,1 是對 final 域的寫入微王,2 是對這個 final 域引用的對象的成員域的寫入屡限,3 是把被構造的對象的引用賦值給某個引用變量。這里除了前面提到的 1 不能和 3 重排序外炕倘,2 和 3 也不能重排序钧大。

JMM 可以確保讀線程 C 至少能看到寫線程 A 在構造函數(shù)中對 final 引用對象的成員域的寫入。即 C 至少能看到數(shù)組下標 0 的值為 1罩旋。而寫線程 B 對數(shù)組元素的寫入啊央,讀線程 C 可能看的到,也可能看不到涨醋。JMM 不保證線程 B 的寫入對讀線程 C 可見瓜饥,因為寫線程 B 和讀線程 C 之間存在數(shù)據(jù)競爭,此時的執(zhí)行結果不可預知浴骂。

如果想要確保讀線程 C 看到寫線程 B 對數(shù)組元素的寫入乓土,寫線程 B 和讀線程 C 之間需要使用同步原語(lock 或 volatile)來確保內存可見性。

參考文章

https://www.infoq.cn/article/java-memory-model-6
http://www.reibang.com/p/067b6c89875a
http://www.reibang.com/p/f68d6ef2dcf0
https://www.cnblogs.com/xiaoxi/p/6392154.html
https://www.iteye.com/blog/cakin24-2334965
https://blog.csdn.net/chengqiuming/article/details/70139503
https://blog.csdn.net/hupuxiang/article/details/7362267

微信公眾號

個人公眾號:黃小斜

黃小斜是跨考軟件工程的 985 碩士,自學 Java 兩年趣苏,拿到了 BAT 等近十家大廠 offer狡相,從技術小白成長為阿里工程師。

作者專注于 JAVA 后端技術棧食磕,熱衷于分享程序員干貨尽棕、學習經(jīng)驗、求職心得和程序人生彬伦,目前黃小斜的CSDN博客有百萬+訪問量滔悉,知乎粉絲2W+,全網(wǎng)已有10W+讀者媚朦。

黃小斜是一個斜杠青年氧敢,堅持學習和寫作,相信終身學習的力量询张,希望和更多的程序員交朋友孙乖,一起進步和成長!

原創(chuàng)電子書:
關注微信公眾號【黃小斜】后回復【原創(chuàng)電子書】即可領取我原創(chuàng)的電子書《菜鳥程序員修煉手冊:從技術小白到阿里巴巴Java工程師》這份電子書總結了我2年的Java學習之路份氧,包括學習方法唯袄、技術總結、求職經(jīng)驗和面試技巧等內容蜗帜,已經(jīng)幫助很多的程序員拿到了心儀的offer恋拷!

程序員3T技術學習資源: 一些程序員學習技術的資源大禮包,關注公眾號后厅缺,后臺回復關鍵字 “資料” 即可免費無套路獲取蔬顾,包括Java、python湘捎、C++诀豁、大數(shù)據(jù)、機器學習窥妇、前端舷胜、移動端等方向的技術資料。

技術公眾號:Java技術江湖

如果大家想要實時關注我更新的文章以及分享的干貨的話活翩,可以關注我的微信公眾號【Java技術江湖】

這是一位阿里 Java 工程師的技術小站烹骨。作者黃小斜,專注 Java 相關技術:SSM材泄、SpringBoot沮焕、MySQL、分布式脸爱、中間件遇汞、集群、Linux簿废、網(wǎng)絡空入、多線程,偶爾講點Docker族檬、ELK歪赢,同時也分享技術干貨和學習經(jīng)驗,致力于Java全棧開發(fā)单料!

(關注公眾號后回復”Java“即可領取 Java基礎埋凯、進階、項目和架構師等免費學習資料扫尖,更有數(shù)據(jù)庫白对、分布式、微服務等熱門技術學習視頻换怖,內容豐富甩恼,兼顧原理和實踐,另外也將贈送作者原創(chuàng)的Java學習指南沉颂、Java程序員面試指南等干貨資源)

Java工程師必備學習資源: 一些Java工程師常用學習資源条摸,關注公眾號后,后臺回復關鍵字 “Java” 即可免費無套路獲取铸屉。

我的公眾號

?

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末钉蒲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子彻坛,更是在濱河造成了極大的恐慌顷啼,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昌屉,死亡現(xiàn)場離奇詭異钙蒙,居然都是意外死亡,警方通過查閱死者的電腦和手機怠益,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門仪搔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜻牢,你說我怎么就攤上這事烤咧。” “怎么了抢呆?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵煮嫌,是天一觀的道長。 經(jīng)常有香客問我抱虐,道長昌阿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮懦冰,結果婚禮上灶轰,老公的妹妹穿的比我還像新娘。我一直安慰自己刷钢,他們只是感情好笋颤,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著内地,像睡著了一般伴澄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阱缓,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天非凌,我揣著相機與錄音,去河邊找鬼荆针。 笑死敞嗡,一個胖子當著我的面吹牛,可吹牛的內容都是我干的祭犯。 我是一名探鬼主播秸妥,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沃粗!你這毒婦竟也來了粥惧?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤最盅,失蹤者是張志新(化名)和其女友劉穎突雪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涡贱,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡咏删,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了问词。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片督函。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖激挪,靈堂內的尸體忽然破棺而出辰狡,到底是詐尸還是另有隱情,我是刑警寧澤垄分,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布宛篇,位于F島的核電站,受9級特大地震影響薄湿,放射性物質發(fā)生泄漏叫倍。R本人自食惡果不足惜偷卧,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吆倦。 院中可真熱鬧听诸,春花似錦、人聲如沸逼庞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赛糟。三九已至,卻和暖如春砸逊,著一層夾襖步出監(jiān)牢的瞬間璧南,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工师逸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留司倚,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓篓像,卻偏偏與公主長得像动知,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子员辩,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內容