<input id="0qass"><u id="0qass"></u></input>
  • <input id="0qass"><u id="0qass"></u></input>
  • <menu id="0qass"><u id="0qass"></u></menu>

    Java 基礎高頻面試題(2021年最新版)

    微信搜索【程序員囧輝】,關注這個堅持分享技術干貨的程序員。

    前言

    網上的 Java 基礎面試題文章有非常多,但是大部分都比較老了。

    很多題目早已不是當前的熱門題目,沒有必要在這些題目上花太多時間。

    很多答案放現在已經不準確,可能會誤導新人。

    因此,我花了幾天時間整理了一些時下高頻的 Java 基礎題目,并反復斟酌,給出符合當前版本的解析。

    ?

    我的最新文章:BAT 老兵的經驗之談,成長路上這個道理越早知道越好

    ?

    面試系列

    我自己前前后后加起來總共應該參加了不下四五十次的面試,拿到過幾乎所有一線大廠的 offer:阿里、字節、美團、快手、拼多多等等。

    每次面試后我都會將面試的題目進行記錄,并整理成自己的題庫,最近我將這些題目整理出來,并按大廠的標準給出自己的解析,希望在這金三銀四的季節里,能助你一臂之力。

    面試文章持續更新中... ...

    內容鏈接地址
    面試經驗分享921天,從小廠到入職阿里
    兩年Java開發工作經驗面試總結
    4 年 Java 經驗,阿里網易拼多多面試總結、心得體會
    5 年 Java 經驗,字節、美團、快手核心部門面試總結(真題解析)
    復習2個月拿下美團offer,我都做了些啥?
    如何準備好一場大廠面試?
    簡歷如何寫一份讓 HR 眼前一亮的簡歷(附模板)
    Offer 選擇跳槽,如何選擇一家公司
    Java 基礎Java 基礎高頻面試題(2021年最新版)
    一道有意思的“初始化”面試題
    集合(HashMap)Java 集合框架高頻面試題(2021年最新版)
    面試阿里,HashMap 這一篇就夠了
    并發編程面試必問的線程池,你懂了嗎?
    面試必問的CAS,你懂了嗎?
    MySQL面試必問的 MySQL,你懂了嗎?
    MySQL 8.0 MVCC 核心原理解析(核心源碼)
    Spring面試必問的 Spring,你懂了嗎?
    Mybatis面試題:mybatis 中的 DAO 接口和 XML 文件里的 SQL 是如何建立關系的?
    Redis面試必問的緩存使用:如何保證數據一致性、緩存設計模式
    面試必問的 Redis:Memcached VS Redis
    面試必問的 Redis:高可用解決方案哨兵、集群
    面試必問的 Redis:主從復制
    面試必問的 Redis:RDB、AOF、混合持久化
    面試必問的 Redis:數據結構和基礎概念
    JVMJava虛擬機面試題精選(二)
    Java虛擬機面試題精選(一)
    分布式面試必問的分布式鎖,你懂了嗎?
    算法位圖法:判斷一個數是否在40億個整數中?
    ??

    ?

    ?

    正文

    1、面向對象的三個基本特征?

    面向對象的三個基本特征是:封裝、繼承和多態。

    繼承:讓某個類型的對象獲得另一個類型的對象的屬性的方法。繼承就是子類繼承父類的特征和行為,使得子類對象(實例)具有父類的實例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。

    封裝:隱藏部分對象的屬性和實現細節,對數據的訪問只能通過外公開的接口。通過這種方式,對象對內部數據提供了不同級別的保護,以防止程序中無關的部分意外的改變或錯誤的使用了對象的私有部分。

    多態:對于同一個行為,不同的子類對象具有不同的表現形式。多態存在的3個條件:1)繼承;2)重寫;3)父類引用指向子類對象。

    舉個簡單的例子:英雄聯盟里面我們按下 Q 鍵這個動作:

    • 對于亞索,就是斬鋼閃
    • 對于提莫,就是致盲吹箭
    • 對于劍圣,就是阿爾法突襲

    同一個事件發生在不同的對象上會產生不同的結果。

    ?

    我再舉一個簡單的例子幫助大家理解,這個例子可能不是完全準確,但是我認為是有利于理解的。

    public class Animal { // 動物
        public void sleep() {
            System.out.println("躺著睡");
        }
    }
    class Horse extends Animal { // 馬 是一種動物
        public void sleep() {
            System.out.println("站著睡");
        }
    }
    class Cat extends Animal { // 貓 是一種動物
        private int age;
        public int getAge() {
            return age + 1;
        }
        @Override
        public void sleep() {
            System.out.println("四腳朝天的睡");
        }
    }

    ?

    在這個例子中:

    House 和 Cat 都是 Animal,所以他們都繼承了 Animal,同時也從 Animal 繼承了 sleep 這個行為。

    但是針對 sleep 這個行為,House 和 Cat 進行了重寫,有了不同的表現形式(實現),這個我們稱為多態。

    在 Cat 里,將 age 屬性定義為 private,外界無法直接訪問,要獲取 Cat 的 age 信息只能通過 getAge 方法,從而對外隱藏了 age 屬性,這個就叫做封裝。當然,這邊 age 只是個例子,實際使用中可能是一個復雜很多的對象。

    ?

    2、訪問修飾符public,private,protected,以及不寫時的區別?

    ?

    3、下面兩個代碼塊能正常編譯和執行嗎?

    ?

    // 代碼塊1
    short s1 = 1; s1 = s1 + 1;
    // 代碼塊2
    short s1 = 1; s1 += 1;

    代碼塊1編譯報錯,錯誤原因是:不兼容的類型: 從int轉換到short可能會有損失”。

    代碼塊2正常編譯和執行。

    我們將代碼塊2進行編譯,字節碼如下:

    public class com.joonwhee.open.demo.Convert {
      public com.joonwhee.open.demo.Convert();
        Code:
           0: aload_0
           1: invokespecial #1 // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: iconst_1 // 將int類型值1入(操作數)棧
           1: istore_1 // 將棧頂int類型值保存到局部變量1中
           2: iload_1 // 從局部變量1中裝載int類型值入棧
           3: iconst_1 // 將int類型值1入棧
           4: iadd // 將棧頂兩int類型數相加,結果入棧
           5: i2s // 將棧頂int類型值截斷成short類型值,后帶符號擴展成int類型值入棧。
           6: istore_1 // 將棧頂int類型值保存到局部變量1中
           7: return
    }

    可以看到字節碼中包含了 i2s 指令,該指令用于將 int 轉成 short。i2s 是 int to short 的縮寫。

    其實,s1 += 1 相當于 s1 = (short)(s1 + 1),有興趣的可以自己編譯下這兩行代碼的字節碼,你會發現是一摸一樣的。

    ?

    說好的 Java 基礎題,怎么又開始變態起來了???

    ?

    ?

    4、基礎考察,指出下題的輸出結果

    public static void main(String[] args) {
        Integer a = 128, b = 128, c = 127, d = 127;
        System.out.println(a == b);
        System.out.println(c == d);
    }

    答案是:false,true。

    執行 Integer a = 128,相當于執行:Integer a = Integer.valueOf(128),基本類型自動轉換為包裝類的過程稱為自動裝箱(autoboxing)。

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

    在 Integer 中引入了 IntegerCache 來緩存一定范圍的值,IntegerCache 默認情況下范圍為:-128~127。

    本題中的 127 命中了?IntegerCache,所以 c?和 d?是相同對象,而 128 則沒有命中,所以 a?和 b?是不同對象。

    ?

    但是這個緩存范圍時可以修改的,可能有些人不知道??梢酝ㄟ^JVM啟動參數:-XX:AutoBoxCacheMax=<size> 來修改上限值,如下圖所示:

    ?

    5、用最有效率的方法計算2乘以8?

    2 << 3。

    進階:通常情況下,可以認為位運算是性能最高的。但是,其實編譯器現在已經“非常聰明了”,很多指令編譯器都能自己做優化。所以在實際實用中,我們無需特意去追求實用位運算,這樣不僅會導致代碼可讀性很差,而且某些自作聰明的優化反而會誤導編譯器,使得編譯器無法進行更好的優化。

    這可能就是所謂的“豬隊友”吧。

    ?

    6、&和&&的區別?

    &&:邏輯與運算符。當運算符左右兩邊的表達式都為 true,才返回 true。同時具有短路性,如果第一個表達式為 false,則直接返回 false。

    &:邏輯與運算符、按位與運算符。

    按位與運算符:用于二進制的計算,只有對應的兩個二進位均為1時,結果位才為1 ,否則為0。

    邏輯與運算符:& 在用于邏輯與時,和 && 的區別是不具有短路性。所在通常使用邏輯與運算符都會使用 &&,而 & 更多的適用于位運算。

    ?

    7、String 是 Java 基本數據類型嗎?

    答:不是。Java 中的基本數據類型只有8個:byte、short、int、long、float、double、char、boolean;除了基本類型(primitive type),剩下的都是引用類型(reference type)。

    基本數據類型:數據直接存儲在棧上

    引用數據類型區別:數據存儲在堆上,棧上只存儲引用地址

    ?

    8、String 類可以繼承嗎?

    不行。String 類使用 final 修飾,無法被繼承。

    ?

    9、String和StringBuilder、StringBuffer的區別?

    String:String 的值被創建后不能修改,任何對 String 的修改都會引發新的 String 對象的生成。

    StringBuffer:跟 String 類似,但是值可以被修改,使用 synchronized 來保證線程安全。

    StringBuilder:StringBuffer 的非線程安全版本,沒有使用 synchronized,具有更高的性能,推薦優先使用。

    ?

    10、String s = new String("xyz") 創建了幾個字符串對象?

    一個或兩個。如果字符串常量池已經有“xyz”,則是一個;否則,兩個。

    當字符創常量池沒有 “xyz”,此時會創建如下兩個對象:

    一個是字符串字面量 "xyz" 所對應的、駐留(intern)在一個全局共享的字符串常量池中的實例,此時該實例也是在堆中,字符串常量池只放引用。

    另一個是通過 new String() 創建并初始化的,內容與"xyz"相同的實例,也是在堆中。

    ?

    11、String s = "xyz" 和 String s = new String("xyz") 區別?

    兩個語句都會先去字符串常量池中檢查是否已經存在 “xyz”,如果有則直接使用,如果沒有則會在常量池中創建 “xyz” 對象。

    另外,String s = new String("xyz") 還會通過 new String() 在堆里創建一個內容與 "xyz" 相同的對象實例。

    所以前者其實理解為被后者的所包含。

    ?

    12、== 和 equals 的區別是什么?

    ==:運算符,用于比較基礎類型變量和引用類型變量。

    對于基礎類型變量,比較的變量保存的值是否相同,類型不一定要相同。

    short s1 = 1; long l1 = 1;
    // 結果:true。類型不同,但是值相同
    System.out.println(s1 == l1);

    對于引用類型變量,比較的是兩個對象的地址是否相同。

    Integer i1 = new Integer(1);
    Integer i2 = new Integer(1);
    // 結果:false。通過new創建,在內存中指向兩個不同的對象
    System.out.println(i1 == i2);

    ?

    equals:Object 類中定義的方法,通常用于比較兩個對象的值是否相等。

    equals 在 Object 方法中其實等同于 ==,但是在實際的使用中,equals 通常被重寫用于比較兩個對象的值是否相同。

    Integer i1 = new Integer(1);
    Integer i2 = new Integer(1);
    // 結果:true。兩個不同的對象,但是具有相同的值
    System.out.println(i1.equals(i2));
    
    // Integer的equals重寫方法
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            // 比較對象中保存的值是否相同
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

    ?

    13、兩個對象的 hashCode() 相同,則 equals() 也一定為 true,對嗎?

    不對。hashCode() 和 equals() 之間的關系如下:

    當有 a.equals(b) == true 時,則 a.hashCode() == b.hashCode() 必然成立,

    反過來,當 a.hashCode() == b.hashCode() 時,a.equals(b) 不一定為 true。

    ?

    14、什么是反射

    反射是指在運行狀態中,對于任意一個類都能夠知道這個類所有的屬性和方法;并且對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取信息以及動態調用對象方法的功能稱為反射機制。

    ?

    15、深拷貝和淺拷貝區別是什么?

    數據分為基本數據類型和引用數據類型?;緮祿愋?#xff1a;數據直接存儲在棧中;引用數據類型:存儲在棧中的是對象的引用地址,真實的對象數據存放在堆內存里。

    淺拷貝:對于基礎數據類型:直接復制數據值;對于引用數據類型:只是復制了對象的引用地址,新舊對象指向同一個內存地址,修改其中一個對象的值,另一個對象的值隨之改變。

    深拷貝:對于基礎數據類型:直接復制數據值;對于引用數據類型:開辟新的內存空間,在新的內存空間里復制一個一模一樣的對象,新老對象不共享內存,修改其中一個對象的值,不會影響另一個對象。

    深拷貝相比于淺拷貝速度較慢并且花銷較大。

    ?

    16、并發和并行有什么區別?

    并發:兩個或多個事件在同一時間間隔發生。

    并行:兩個或者多個事件在同一時刻發生。

    并行是真正意義上,同一時刻做多件事情,而并發在同一時刻只會做一件事件,只是可以將時間切碎,交替做多件事情。

    ?

    網上有個例子挺形象的:

    你吃飯吃到一半,電話來了,你一直到吃完了以后才去接,這就說明你不支持并發也不支持并行。

    你吃飯吃到一半,電話來了,你停了下來接了電話,接完后繼續吃飯,這說明你支持并發。

    你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支持并行。

    ?

    17、構造器是否可被 重寫?

    Constructor?不能被 override(重寫),但是可以 overload(重載),所以你可以看到?個類中有多個構造函數的情況。

    ?

    18、當一個對象被當作參數傳遞到一個方法后,此方法可改變這個對象的屬性,并可返回變化后的結果,那么這里到底是值傳遞還是引用傳遞?

    值傳遞。Java 中只有值傳遞,對于對象參數,值的內容是對象的引用。

    ?

    19、Java 靜態變量和成員變量的區別。

    ?

    public class Demo {
        /**
         * 靜態變量:又稱類變量,static修飾
         */
        public static String STATIC_VARIABLE = "靜態變量";
        /**
         * 實例變量:又稱成員變量,沒有static修飾
         */
        public String INSTANCE_VARIABLE = "實例變量";
    }

    成員變量存在于堆內存中。靜態變量存在于方法區中。

    成員變量與對象共存亡,隨著對象創建而存在,隨著對象被回收而釋放。靜態變量與類共存亡,隨著類的加載而存在,隨著類的消失而消失。

    成員變量所屬于對象,所以也稱為實例變量。靜態變量所屬于類,所以也稱為類變量。

    成員變量只能被對象所調用 。靜態變量可以被對象調用,也可以被類名調用。

    ?

    20、是否可以從一個靜態(static)方法內部發出對非靜態(non-static)方法的調用?

    區分兩種情況,發出調用時是否顯示創建了對象實例。

    1)沒有顯示創建對象實例:不可以發起調用,非靜態方法只能被對象所調用,靜態方法可以通過對象調用,也可以通過類名調用,所以靜態方法被調用時,可能還沒有創建任何實例對象。因此通過靜態方法內部發出對非靜態方法的調用,此時可能無法知道非靜態方法屬于哪個對象。

    ?

    public class Demo {
        public static void staticMethod() {
            // 直接調用非靜態方法:編譯報錯
            instanceMethod();
        }
        public void instanceMethod() {
            System.out.println("非靜態方法");
        }
    }

    ?

    2)顯示創建對象實例:可以發起調用,在靜態方法中顯示的創建對象實例,則可以正常的調用。

    ?

    public class Demo {
        public static void staticMethod() {
            // 先創建實例對象,再調用非靜態方法:成功執行
            Demo demo = new Demo();
            demo.instanceMethod();
        }
        public void instanceMethod() {
            System.out.println("非靜態方法");
        }
    }

    ?

    21、初始化考察,請指出下面程序的運行結果。

    public class InitialTest {
        public static void main(String[] args) {
            A ab = new B();
            ab = new B();
        }
    }
    class A {
        static { // 父類靜態代碼塊
            System.out.print("A");
        }
        public A() { // 父類構造器
            System.out.print("a");
        }
    }
    class B extends A {
        static { // 子類靜態代碼塊
            System.out.print("B");
        }
        public B() { // 子類構造器
            System.out.print("b");
        }
    }

    執行結果:ABabab,兩個考察點:

    1)靜態變量只會初始化(執行)一次。

    2)當有父類時,完整的初始化順序為:父類靜態變量(靜態代碼塊)->子類靜態變量(靜態代碼塊)->父類非靜態變量(非靜態代碼塊)->父類構造器 ->子類非靜態變量(非靜態代碼塊)->子類構造器 。

    關于初始化,這題算入門題,我之前還寫過一道有(fei)點(chang)意(bian)思(tai)的進階題目,有興趣的可以看看:一道有意思的“初始化”面試題

    ?

    22、重載(Overload)和重寫(Override)的區別?

    方法的重載和重寫都是實現多態的方式,區別在于前者實現的是編譯時的多態性,而后者實現的是運行時的多態性。

    重載:一個類中有多個同名的方法,但是具有有不同的參數列表(參數類型不同、參數個數不同或者二者都不同)。

    重寫:發生在子類與父類之間,子類對父類的方法進行重寫,參數都不能改變,返回值類型可以不相同,但是必須是父類返回值的派生類。即外殼不變,核心重寫!重寫的好處在于子類可以根據需要,定義特定于自己的行為。

    ?

    23、為什么不能根據返回類型來區分重載?

    如果我們有兩個方法如下,當我們調用:test(1) 時,編譯器無法確認要調用的是哪個。

    // 方法1
    int test(int a);
    // 方法2
    long test(int a);

    方法的返回值只是作為方法運行之后的一個“狀態”,但是并不是所有調用都關注返回值,所以不能將返回值作為重載的唯一區分條件。

    ?

    24、抽象類(abstract class)和接口(interface)有什么區別?

    抽象類只能單繼承,接口可以多實現。

    抽象類可以有構造方法,接口中不能有構造方法。

    抽象類中可以有成員變量,接口中沒有成員變量,只能有常量(默認就是 public static final)

    抽象類中可以包含非抽象的方法,在 Java 7 之前接口中的所有方法都是抽象的,在 Java 8 之后,接口支持非抽象方法:default 方法、靜態方法等。Java 9 支持私有方法、私有靜態方法。

    抽象類中的抽象方法類型可以是任意修飾符,Java 8 之前接口中的方法只能是 public 類型,Java 9 支持 private 類型。

    ?

    設計思想的區別:

    接口是自上而下的抽象過程,接口規范了某些行為,是對某一行為的抽象。我需要這個行為,我就去實現某個接口,但是具體這個行為怎么實現,完全由自己決定。

    抽象類是自下而上的抽象過程,抽象類提供了通用實現,是對某一類事物的抽象。我們在寫實現類的時候,發現某些實現類具有幾乎相同的實現,因此我們將這些相同的實現抽取出來成為抽象類,然后如果有一些差異點,則可以提供抽象方法來支持自定義實現。

    ?

    我在網上看到有個說法,挺形象的:

    普通類像親爹 ,他有啥都是你的。

    抽象類像叔伯,有一部分會給你,還能指導你做事的方法。

    接口像干爹,可以給你指引方法,但是做成啥樣得你自己努力實現。

    ?

    25、Error 和 Exception 有什么區別?

    Error 和 Exception 都是 Throwable 的子類,用于表示程序出現了不正常的情況。區別在于:

    Error 表示系統級的錯誤和程序不必處理的異常,是恢復不是不可能但很困難的情況下的一種嚴重問題,比如內存溢出,不可能指望程序能處理這樣的情況。

    Exception 表示需要捕捉或者需要程序進行處理的異常,是一種設計或實現問題,也就是說,它表示如果程序運行正常,從不會發生的情況。

    ?

    26、Java 中的 final 關鍵字有哪些用法?

    修飾類:該類不能再派生出新的子類,不能作為父類被繼承。因此,一個類不能同時被聲明為abstract 和 final。

    修飾方法:該方法不能被子類重寫。

    修飾變量:該變量必須在聲明時給定初值,而在以后只能讀取,不可修改。 如果變量是對象,則指的是引用不可修改,但是對象的屬性還是可以修改的。

    ?

    public class FinalDemo {
        // 不可再修改該變量的值
        public static final int FINAL_VARIABLE = 0;
        // 不可再修改該變量的引用,但是可以直接修改屬性值
        public static final User USER = new User();
        public static void main(String[] args) {
            // 輸出:User(id=0, name=null, age=0)
            System.out.println(USER);
            // 直接修改屬性值
            USER.setName("test");
            // 輸出:User(id=0, name=test, age=0)
            System.out.println(USER);
        }
    }

    ?

    27、闡述 final、finally、finalize 的區別。

    其實是三個完全不相關的東西,只是長的有點像。。

    final 如上所示。

    finally:finally 是對 Java 異常處理機制的最佳補充,通常配合 try、catch 使用,用于存放那些無論是否出現異常都一定會執行的代碼。在實際使用中,通常用于釋放鎖、數據庫連接等資源,把資源釋放方法放到 finally 中,可以大大降低程序出錯的幾率。

    finalize:Object 中的方法,在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。finalize()方法僅作為了解即可,在 Java 9 中該方法已經被標記為廢棄,并添加新的 java.lang.ref.Cleaner,提供了更靈活和有效的方法來釋放資源。這也側面說明了,這個方法的設計是失敗的,因此更加不能去使用它。

    ?

    28、try、catch、finally 考察,請指出下面程序的運行結果。

    ?

    public class TryDemo {
        public static void main(String[] args) {
            System.out.println(test());
        }
        public static int test() {
            try {
                return 1;
            } catch (Exception e) {
                return 2;
            } finally {
                System.out.print("3");
            }
        }
    }

    執行結果:31。

    相信很多同學應該都做對了,try、catch。finally 的基礎用法,在 return 前會先執行 finally 語句塊,所以是先輸出 finally 里的 3,再輸出 return 的 1。

    ?

    29、try、catch、finally 考察2,請指出下面程序的運行結果。

    ?

    public class TryDemo {
        public static void main(String[] args) {
            System.out.println(test1());
        }
        public static int test1() {
            try {
                return 2;
            } finally {
                return 3;
            }
        }
    }

    執行結果:3。

    這題有點先將,但也不難,try 返回前先執行 finally,結果 finally 里不按套路出牌,直接 return 了,自然也就走不到 try 里面的 return 了。

    finally 里面使用 return 僅存在于面試題中,實際開發中千萬不要這么用。

    ?

    30、try、catch、finally 考察3,請指出下面程序的運行結果。

    ?

    public class TryDemo {
        public static void main(String[] args) {
            System.out.println(test1());
        }
        public static int test1() {
            int i = 0;
            try {
                i = 2;
                return i;
            } finally {
                i = 3;
            }
        }
    }

    執行結果:2。

    這邊估計有不少同學會以為結果應該是 3,因為我們知道在 return 前會執行 finally,而 i 在 finally 中被修改為 3 了,那最終返回 i 不是應該為 3 嗎?確實很容易這么想,我最初也是這么想的,當初的自己還是太年輕了啊。

    這邊的根本原因是,在執行 finally 之前,JVM 會先將 i 的結果暫存起來,然后 finally 執行完畢后,會返回之前暫存的結果,而不是返回 i,所以即使這邊 i 已經被修改為 3,最終返回的還是之前暫存起來的結果 2。

    這邊其實根據字節碼可以很容易看出來,在進入 finally 之前,JVM 會使用 iload、istore 兩個指令,將結果暫存,在最終返回時在通過 iload、ireturn 指令返回暫存的結果。

    為了避免氣氛再次變態起來,我這邊就不貼具體的字節碼程序了,有興趣的同學可以自己編譯查看下。

    ?

    ?

    31、JDK1.8之后有哪些新特性?

    接口默認方法:Java 8允許我們給接口添加一個非抽象的方法實現,只需要使用 default關鍵字即可

    Lambda 表達式和函數式接口:Lambda 表達式本質上是一段匿名內部類,也可以是一段可以傳遞的代碼。Lambda 允許把函數作為一個方法的參數(函數作為參數傳遞到方法中),使用 Lambda 表達式使代碼更加簡潔,但是也不要濫用,否則會有可讀性等問題,《Effective Java》作者 Josh Bloch 建議使用 Lambda 表達式最好不要超過3行。

    Stream API:用函數式編程方式在集合類上進行復雜操作的工具,配合Lambda表達式可以方便的對集合進行處理。Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常復雜的查找、過濾和映射數據等操作。使用Stream API 對集合數據進行操作,就類似于使用 SQL 執行的數據庫查詢。也可以使用 Stream API 來并行執行操作。簡而言之,Stream API 提供了一種高效且易于使用的處理數據的方式。

    方法引用:方法引用提供了非常有用的語法,可以直接引用已有Java類或對象(實例)的方法或構造器。與lambda聯合使用,方法引用可以使語言的構造更緊湊簡潔,減少冗余代碼。

    日期時間API:Java 8 引入了新的日期時間API改進了日期時間的管理。

    Optional 類:著名的 NullPointerException?是引起系統失敗最常見的原因。很久以前 Google Guava 項目引入了 Optional 作為解決空指針異常的一種方式,不贊成代碼被 null 檢查的代碼污染,期望程序員寫整潔的代碼。受Google Guava的鼓勵,Optional?現在是Java 8庫的一部分。

    新工具:新的編譯工具,如:Nashorn引擎 jjs、 類依賴分析器 jdeps。

    ?

    50、wait() 和 sleep() 方法的區別

    來源不同:sleep() 來自 Thread 類,wait() 來自 Object 類。

    對于同步鎖的影響不同:sleep() 不會該表同步鎖的行為,如果當前線程持有同步鎖,那么 sleep 是不會讓線程釋放同步鎖的。wait() 會釋放同步鎖,讓其他線程進入 synchronized 代碼塊執行。

    使用范圍不同:sleep() 可以在任何地方使用。wait() 只能在同步控制方法或者同步控制塊里面使用,否則會拋 IllegalMonitorStateException。

    恢復方式不同:兩者會暫停當前線程,但是在恢復上不太一樣。sleep() 在時間到了之后會重新恢復;wait() 則需要其他線程調用同一對象的 notify()/nofityAll() 才能重新恢復。

    ?

    51、線程的 sleep() 方法和 yield() 方法有什么區別?

    線程執行 sleep() 方法后進入超時等待(TIMED_WAITING)狀態,而執行 yield() 方法后進入就緒(READY)狀態。

    sleep() 方法給其他線程運行機會時不考慮線程的優先級,因此會給低優先級的線程運行的機會;yield() 方法只會給相同優先級或更高優先級的線程以運行的機會。

    ?

    52、線程的 join() 方法是干啥用的?

    用于等待當前線程終止。如果一個線程A執行了 threadB.join() 語句,其含義是:當前線程A等待 threadB 線程終止之后才從 threadB.join() 返回繼續往下執行自己的代碼。

    ?

    53、編寫多線程程序有幾種實現方式?

    通常來說,可以認為有三種方式:1)繼承 Thread 類;2)實現 Runnable 接口;3)實現 Callable 接口。

    其中,Thread 其實也是實現了 Runable 接口。Runnable 和?Callable 的主要區別在于是否有返回值。

    ?

    54、Thread 調用 start() 方法和調用 run() 方法的區別

    run():普通的方法調用,在主線程中執行,不會新建一個線程來執行。

    start():新啟動一個線程,這時此線程處于就緒(可運行)狀態,并沒有運行,一旦得到 CPU 時間片,就開始執行 run() 方法。

    ?

    55、線程的狀態流轉

    ?

    一個線程可以處于以下狀態之一:

    NEW:新建但是尚未啟動的線程處于此狀態,沒有調用 start() 方法。

    RUNNABLE:包含就緒(READY)和運行中(RUNNING)兩種狀態。線程調用 start() 方法會會進入就緒(READY)狀態,等待獲取 CPU 時間片。如果成功獲取到 CPU 時間片,則會進入運行中(RUNNING)狀態。

    BLOCKED:線程在進入同步方法/同步塊(synchronized)時被阻塞,等待同步鎖的線程處于此狀態。

    WAITING:無限期等待另一個線程執行特定操作的線程處于此狀態,需要被顯示的喚醒,否則會一直等待下去。例如對于 Object.wait(),需要等待另一個線程執行 Object.notify() 或 Object.notifyAll();對于 Thread.join(),則需要等待指定的線程終止。

    TIMED_WAITING:在指定的時間內等待另一個線程執行某項操作的線程處于此狀態。跟 WAITING 類似,區別在于該狀態有超時時間參數,在超時時間到了后會自動喚醒,避免了無期限的等待。

    TERMINATED:執行完畢已經退出的線程處于此狀態。

    線程在給定的時間點只能處于一種狀態。這些狀態是虛擬機狀態,不反映任何操作系統線程狀態。

    ?

    56、synchronized 和 Lock 的區別

    1)Lock 是一個接口;synchronized 是 Java 中的關鍵字,synchronized 是內置的語言實現;

    2)Lock 在發生異常時,如果沒有主動通過 unLock() 去釋放鎖,很可能會造成死鎖現象,因此使用 Lock 時需要在 finally 塊中釋放鎖;synchronized 不需要手動獲取鎖和釋放鎖,在發生異常時,會自動釋放鎖,因此不會導致死鎖現象發生;

    3)Lock 的使用更加靈活,可以有響應中斷、有超時時間等;而 synchronized 卻不行,使用 synchronized 時,等待的線程會一直等待下去,直到獲取到鎖;

    4)在性能上,隨著近些年 synchronized 的不斷優化,Lock 和 synchronized 在性能上已經沒有很明顯的差距了,所以性能不應該成為我們選擇兩者的主要原因。官方推薦盡量使用 synchronized,除非 synchronized 無法滿足需求時,則可以使用 Lock。

    ?

    57、synchronized 各種加鎖場景的作用范圍

    1.作用于非靜態方法,鎖住的是對象實例(this),每一個對象實例有一個鎖。

    public synchronized void method() {}

    2.作用于靜態方法,鎖住的是類的Class對象,因為Class的相關數據存儲在永久代元空間,元空間是全局共享的,因此靜態方法鎖相當于類的一個全局鎖,會鎖所有調用該方法的線程。

    public static synchronized void method() {}

    3.作用于 Lock.class,鎖住的是 Lock 的Class對象,也是全局只有一個。

    synchronized (Lock.class) {}

    4.作用于 this,鎖住的是對象實例,每一個對象實例有一個鎖。

    synchronized (this) {}

    5.作用于靜態成員變量,鎖住的是該靜態成員變量對象,由于是靜態變量,因此全局只有一個。

    public static Object monitor = new Object(); synchronized (monitor) {}

    ?

    58、如何檢測死鎖?

    死鎖的四個必要條件:

    1)互斥條件:進程對所分配到的資源進行排他性控制,即在一段時間內某資源僅為一個進程所占有。此時若有其他進程請求該資源,則請求進程只能等待。

    2)請求和保持條件:進程已經獲得了至少一個資源,但又對其他資源發出請求,而該資源已被其他進程占有,此時該進程的請求被阻塞,但又對自己獲得的資源保持不放。

    3)不可剝奪條件:進程已獲得的資源在未使用完畢之前,不可被其他進程強行剝奪,只能由自己釋放。

    4)環路等待條件:存在一種進程資源的循環等待鏈,鏈中每一個進程已獲得的資源同時被 鏈中下一個進程所請求。即存在一個處于等待狀態的進程集合{Pl, P2, …, pn},其中 Pi 等待的資源被 P(i+1) 占有(i=0, 1, …, n-1),Pn 等待的資源被 P0占 有,如下圖所示。

    ?

    59、怎么預防死鎖?

    預防死鎖的方式就是打破四個必要條件中的任意一個即可。

    1)打破互斥條件:在系統里取消互斥。若資源不被一個進程獨占使用,那么死鎖是肯定不會發生的。但一般來說在所列的四個條件中,“互斥”條件是無法破壞的。因此,在死鎖預防里主要是破壞其他幾個必要條件,而不去涉及破壞“互斥”條件。。

    2)打破請求和保持條件:1)采用資源預先分配策略,即進程運行前申請全部資源,滿足則運行,不然就等待。 2)每個進程提出新的資源申請前,必須先釋放它先前所占有的資源。

    3)打破不可剝奪條件:當進程占有某些資源后又進一步申請其他資源而無法滿足,則該進程必須釋放它原來占有的資源。

    4)打破環路等待條件:實現資源有序分配策略,將系統的所有資源統一編號,所有進程只能采用按序號遞增的形式申請資源。

    ?

    60、為什么要使用線程池?直接new個線程不是很舒服?

    如果我們在方法中直接new一個線程來處理,當這個方法被調用頻繁時就會創建很多線程,不僅會消耗系統資源,還會降低系統的穩定性,一不小心把系統搞崩了,就可以直接去財務那結帳了。

    如果我們合理的使用線程池,則可以避免把系統搞崩的窘境??偟脕碚f,使用線程池可以帶來以下幾個好處:

    • 降低資源消耗。通過重復利用已創建的線程,降低線程創建和銷毀造成的消耗。
    • 提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
    • 增加線程的可管理型。線程是稀缺資源,使用線程池可以進行統一分配,調優和監控。
      ?

    61、線程池的核心屬性有哪些?

    threadFactory(線程工廠):用于創建工作線程的工廠。

    corePoolSize(核心線程數):當線程池運行的線程少于 corePoolSize 時,將創建一個新線程來處理請求,即使其他工作線程處于空閑狀態。

    workQueue(隊列):用于保留任務并移交給工作線程的阻塞隊列。

    maximumPoolSize(最大線程數):線程池允許開啟的最大線程數。

    handler(拒絕策略):往線程池添加任務時,將在下面兩種情況觸發拒絕策略:1)線程池運行狀態不是 RUNNING;2)線程池已經達到最大線程數,并且阻塞隊列已滿時。

    keepAliveTime(保持存活時間):如果線程池當前線程數超過 corePoolSize,則多余的線程空閑時間超過 keepAliveTime 時會被終止。
    ?

    62、說下線程池的運作流程。

    ?

    63、線程池有哪些拒絕策略?

    AbortPolicy:中止策略。默認的拒絕策略,直接拋出 RejectedExecutionException。調用者可以捕獲這個異常,然后根據需求編寫自己的處理代碼。

    DiscardPolicy:拋棄策略。什么都不做,直接拋棄被拒絕的任務。

    DiscardOldestPolicy:拋棄最老策略。拋棄阻塞隊列中最老的任務,相當于就是隊列中下一個將要被執行的任務,然后重新提交被拒絕的任務。如果阻塞隊列是一個優先隊列,那么“拋棄最舊的”策略將導致拋棄優先級最高的任務,因此最好不要將該策略和優先級隊列放在一起使用。

    CallerRunsPolicy:調用者運行策略。在調用者線程中執行該任務。該策略實現了一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是將任務回退到調用者(調用線程池執行任務的主線程),由于執行任務需要一定時間,因此主線程至少在一段時間內不能提交任務,從而使得線程池有時間來處理完正在執行的任務。
    ?

    ?

    70、List、Set、Map三者的區別?

    List(對付順序的好幫手): List 接口存儲一組不唯一(可以有多個元素引用相同的對象)、有序的對象。

    Set(注重獨一無二的性質):不允許重復的集合,不會有多個元素引用相同的對象。

    Map(用Key來搜索的專業戶): 使用鍵值對存儲。Map 會維護與 Key 有關聯的值。兩個 Key可以引用相同的對象,但 Key 不能重復,典型的 Key 是String類型,但也可以是任何對象。

    ?

    71、ArrayList 和 LinkedList 的區別。

    ArrayList 底層基于動態數組實現,LinkedList 底層基于鏈表實現。

    對于按 index 索引數據(get/set方法):ArrayList 通過 index 直接定位到數組對應位置的節點,而 LinkedList需要從頭結點或尾節點開始遍歷,直到尋找到目標節點,因此在效率上 ArrayList 優于 LinkedList。

    對于隨機插入和刪除:ArrayList 需要移動目標節點后面的節點(使用System.arraycopy 方法移動節點),而 LinkedList 只需修改目標節點前后節點的 next 或 prev 屬性即可,因此在效率上 LinkedList 優于 ArrayList。

    對于順序插入和刪除:由于 ArrayList 不需要移動節點,因此在效率上比 LinkedList 更好。這也是為什么在實際使用中 ArrayList 更多,因為大部分情況下我們的使用都是順序插入。

    ?

    72、ArrayList 和 Vector 的區別。

    Vector 和 ArrayList 幾乎一致,唯一的區別是 Vector 在方法上使用了 synchronized 來保證線程安全,因此在性能上 ArrayList 具有更好的表現。

    有類似關系的還有:StringBuilder 和 StringBuffer、HashMap 和 Hashtable。

    ?

    73、介紹下 HashMap 的底層數據結構

    我們現在用的都是 JDK 1.8,底層是由“數組+鏈表+紅黑樹”組成,如下圖,而在 JDK 1.8 之前是由“數組+鏈表”組成。

    ?

    74、為什么要改成“數組+鏈表+紅黑樹”?

    主要是為了提升在 hash 沖突嚴重時(鏈表過長)的查找性能,使用鏈表的查找性能是 O(n),而使用紅黑樹是 O(logn)。

    ?

    75、那在什么時候用鏈表?什么時候用紅黑樹?

    對于插入,默認情況下是使用鏈表節點。當同一個索引位置的節點在新增后超過8個(閾值8):如果此時數組長度大于等于 64,則會觸發鏈表節點轉紅黑樹節點(treeifyBin);而如果數組長度小于64,則不會觸發鏈表轉紅黑樹,而是會進行擴容,因為此時的數據量還比較小。

    對于移除,當同一個索引位置的節點在移除后達到 6 個,并且該索引位置的節點為紅黑樹節點,會觸發紅黑樹節點轉鏈表節點(untreeify)。
    ?

    76、HashMap 的默認初始容量是多少?HashMap 的容量有什么限制嗎?

    默認初始容量是16。HashMap 的容量必須是2的N次方,HashMap 會根據我們傳入的容量計算一個大于等于該容量的最小的2的N次方,例如傳 9,容量為16。

    ?

    77、HashMap 的插入流程是怎么樣的?

    ?

    78、HashMap 的擴容(resize)流程是怎么樣的?

    ?

    79、除了 HashMap,還用過哪些 Map,在使用時怎么選擇?

    ?

    80、HashMap?和Hashtable?的區別?

    HashMap 允許 key 和 value 為 null,Hashtable 不允許。

    HashMap 的默認初始容量為 16,Hashtable 為 11。

    HashMap 的擴容為原來的 2 倍,Hashtable 的擴容為原來的 2 倍加 1。

    HashMap 是非線程安全的,Hashtable是線程安全的。

    HashMap 的 hash 值重新計算過,Hashtable 直接使用 hashCode。

    HashMap 去掉了 Hashtable 中的 contains 方法。

    HashMap 繼承自 AbstractMap 類,Hashtable 繼承自 Dictionary 類。

    ?

    90、Java 內存結構(運行時數據區)

    ?

    程序計數器:線程私有。一塊較小的內存空間,可以看作當前線程所執行的字節碼的行號指示器。如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Native方法,這個計數器值則為空。

    Java虛擬機棧:線程私有。它的生命周期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。

    本地方法棧:線程私有。本地方法棧與虛擬機棧所發揮的作用是非常相似的,它們之間的區別不過是虛擬機棧為虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用到的Native方法服務。

    Java堆:線程共享。對大多數應用來說,Java堆是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內存。

    方法區:與Java堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息(構造方法、接口定義)、常量、靜態變量、即時編譯器編譯后的代碼(字節碼)等數據。方法區是JVM規范中定義的一個概念,具體放在哪里,不同的實現可以放在不同的地方。

    運行時常量池:運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中存放。

    String str = new String("hello");

    上面的語句中變量 str 放在棧上,用 new 創建出來的字符串對象放在堆上,而"hello"這個字面量是放在堆中。

    ?

    91、什么是雙親委派模型?

    如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。

    ?

    92、Java虛擬機中有哪些類加載器?

    啟動類加載器(Bootstrap ClassLoader):

    這個類加載器負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,并且是虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。

    擴展類加載器(Extension ClassLoader):

    這個加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器。

    應用程序類加載器(Application ClassLoader):

    這個類加載器由sun.misc.Launcher$AppClassLoader實現。由于這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它為系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

    自定義類加載器:

    用戶自定義的類加載器。

    ?

    93、類加載的過程

    類加載的過程包括:加載、驗證、準備、解析、初始化,其中驗證、準備、解析統稱為連接。

    加載:通過一個類的全限定名來獲取定義此類的二進制字節流,在內存中生成一個代表這個類的java.lang.Class對象。

    驗證:確保Class文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。

    準備:為靜態變量分配內存并設置靜態變量初始值,這里所說的初始值“通常情況”下是數據類型的零值。

    解析:將常量池內的符號引用替換為直接引用。

    初始化:到了初始化階段,才真正開始執行類中定義的 Java 初始化程序代碼。主要是靜態變量賦值動作和靜態語句塊(static{})中的語句。

    ?

    94、介紹下垃圾收集機制(在什么時候,對什么,做了什么)?

    在什么時候?

    在觸發GC的時候,具體如下,這里只說常見的 Young GC 和 Full GC。

    觸發Young GC:當新生代中的 Eden 區沒有足夠空間進行分配時會觸發Young GC。

    觸發Full GC:

    • 當準備要觸發一次Young GC時,如果發現統計數據說之前Young GC的平均晉升大小比目前老年代剩余的空間大,則不會觸發Young GC而是轉為觸發Full GC。(通常情況)
    • 如果有永久代的話,在永久代需要分配空間但已經沒有足夠空間時,也要觸發一次Full GC。
    • System.gc()默認也是觸發Full GC。
    • heap dump帶GC默認也是觸發Full GC。
    • CMS GC時出現Concurrent Mode Failure會導致一次Full GC的產生。

    對什么?

    對那些JVM認為已經“死掉”的對象。即從GC Root開始搜索,搜索不到的,并且經過一次篩選標記沒有復活的對象。

    做了什么?

    對這些JVM認為已經“死掉”的對象進行垃圾收集,新生代使用復制算法,老年代使用標記-清除和標記-整理算法。

    ?

    95、GC Root有哪些?

    在Java語言中,可作為GC Roots的對象包括下面幾種:

    • 虛擬機棧(棧幀中的本地變量表)中引用的對象。
    • 方法區中類靜態屬性引用的對象。
    • 方法區中常量引用的對象。
    • 本地方法棧中JNI(即一般說的Native方法)引用的對象。

    ?

    96、垃圾收集有哪些算法,各自的特點?

    標記 - 清除算法

    首先標記出所有需要回收的對象,在標記完成后統一回收所有被標記的對象。它的主要不足有兩個:一個是效率問題,標記和清除兩個過程的效率都不高;另一個是空間問題,標記清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致以后在程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。

    復制算法

    為了解決效率問題,一種稱為“復制”(Copying)的收集算法出現了,它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。只是這種算法的代價是將內存縮小為了原來的一半,未免太高了一點。

    標記 - 整理算法

    復制收集算法在對象存活率較高時就要進行較多的復制操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。

    根據老年代的特點,有人提出了另外一種“標記-整理”(Mark-Compact)算法,標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存。

    分代收集算法

    當前商業虛擬機的垃圾收集都采用“分代收集”(Generational Collection)算法,這種算法并沒有什么新的思想,只是根據對象存活周期的不同將內存劃分為幾塊。

    一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。

    在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。

    在老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用標記—清理或者標記—整理算法來進行回收。

    ?

    最后

    金三銀四的季節,相信有不少同學正準備跳槽。?

    我將我最近的原創的文章進行了匯總:原創匯總,其中有不少面試高頻題目解析,很多都是我自己在面試大廠時遇到的,我在對每個題目解析時都會按較高的標準進行深入剖析,可能只看一遍并不能完全明白,但是相信反復閱讀,定能有所收獲。

    原創不易,如果你覺得本文寫的還不錯,對你有幫助,請通過【點贊】讓我知道,支持我寫出更好的文章。

    程序員囧輝 CSDN認證博客專家 Spring MySQL JVM
    微信搜索【程序員囧輝】,領取本人原創大廠高頻面試題 PDF、簡歷模板、進學習交流群。對技術有極致的追求,致力于寫出小白也能看得懂的文章。
    <p> <b><span style="font-size:14px;"></span><span style="font-size:14px;background-color:#FFE500;">【Java面試寶典】</span></b><br /> <span style="font-size:14px;">1、68講視頻課,500道大廠Java常見面試題+100個Java面試技巧與答題公式+10萬字核心知識解析+授課老師1對1面試指導+無限次回放</span><br /> <span style="font-size:14px;">2、這門課程基于胡書敏老師8Java面試經驗,調研近百家互聯網公司及面試官的問題打造而成,從篩選簡歷和面試官角度,給出能幫助候選人能面試成功的面試技巧。</span><br /> <span style="font-size:14px;">3、通過學習這門課程,你能系統掌握Java核心、數據庫、Java框架、分布式組件、Java簡歷準備、面試實戰技巧等面試必考知識點。</span><br /> <span style="font-size:14px;">4、知識點+項目經驗案例,每一個都能做為面試的作品展現。</span><br /> <span style="font-size:14px;">5、本課程已經在線下的培訓課程中經過實際檢驗,老師每次培訓結束后,都能幫助同學們運用面試技巧,成功找到更好的工作。</span><br /> <br /> <span style="font-size:14px;background-color:#FFE500;"><b>【超人氣講師】</b></span><br /> <span style="font-size:14px;">胡書敏 | 10大廠工作經驗,8Java面試經驗,5線下Java職業培訓經驗,5架構師經驗</span><br /> <br /> <span style="font-size:14px;background-color:#FFE500;"><b>【報名須知】</b></span><br /> <span style="font-size:14px;">上課模式是什么?</span><br /> <span style="font-size:14px;">課程采取錄播模式,課程永久有效,可無限次觀看</span><br /> <span style="font-size:14px;">課件、課程案例代碼完全開放給你,你可以根據所學知識,自行修改、優化</span><br /> <br /> <br /> <span style="font-size:14px;background-color:#FFE500;"><strong>如何開始學習?</strong></span><br /> <span style="font-size:14px;">PC端:報名成功后可以直接進入課程學習</span><br /> <span style="font-size:14px;">移動端:<span style="font-family:Helvetica;font-size:14px;background-color:#FFFFFF;">CSDN 學院APP注意不是CSDN APP哦</span></span> </p>
    相關推薦
    ??2020 CSDN 皮膚主題: 程序猿惹誰了 設計師:白松林 返回首頁
    多乐彩