Java基礎(chǔ)知識最詳細版(面試必備)

剛剛經(jīng)歷過秋招,看了大量的面經(jīng),順便將常見的Java??贾R點總結(jié)了一下,并根據(jù)被問到的頻率大致做了一個標注。一顆星表示知識點需要了解,被問到的頻率不高,面試時起碼能說個差不多。兩顆星表示被問到的頻率較高或?qū)斫釰ava有著重要的作用,建議熟練掌握。三顆星表示被問到的頻率非常高,建議深入理解并熟練掌握其相關(guān)知識,方便面試時拓展(方便裝逼),給面試官留下個好印象。

微信搜索公眾號路人zhang,回復(fù)面試手冊,領(lǐng)取本文檔PDF版及更多面試資料。

推薦閱讀:一文搞懂所有HashMap面試題

JVM、JRE及JDK的關(guān)系 **

JDK(Java Development Kit)是針對Java開發(fā)員的產(chǎn)品,是整個Java的核心,包括了Java運行環(huán)境JRE、Java工具和Java基礎(chǔ)類庫。

Java Runtime Environment(JRE)是運行JAVA程序所必須的環(huán)境的集合,包含JVM標準實現(xiàn)及Java核心類庫。

JVM是Java Virtual Machine(Java虛擬機)的縮寫,是整個java實現(xiàn)跨平臺的最核心的部分,能夠運行以Java語言寫作的軟件程序。

**簡單來說就是JDK是Java的開發(fā)工具,JRE是Java程序運行所需的環(huán)境,JVM是Java虛擬機.它們之間的關(guān)系是JDK包含JRE和JVM,JRE包含JVM.**

JAVA語言特點 **

  • Java是一種面向?qū)ο蟮恼Z言
  • Java通過Java虛擬機實現(xiàn)了平臺無關(guān)性,一次編譯,到處運行
  • 支持多線程
  • 支持網(wǎng)絡(luò)編程
  • 具有較高的安全性和可靠性

JAVA和C++的區(qū)別?。?/h3>

面試時記住前四個就行了

  • Java 通過虛擬機從而實現(xiàn)跨平臺特性,但是 C++ 依賴于特定的平臺。
  • Java 沒有指針,它的引用可以理解為安全指針,而 C++ 具有和 C 一樣的指針。
  • Java 支持自動垃圾回收,而 C++ 需要手動回收。
  • Java 不支持多重繼承,只能通過實現(xiàn)多個接口來達到相同目的,而 C++ 支持多重繼承。
  • Java 不支持操作符重載,雖然可以對兩個 String 對象執(zhí)行加法運算,但是這是語言內(nèi)置支持的操作,不屬于操
    作符重載,而 C++ 可以。
  • Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。

Java的基本數(shù)據(jù)類型 ?。?/h3>

注意String不是基本數(shù)據(jù)類型

類型 關(guān)鍵字 包裝器類型 占用內(nèi)存(字節(jié))(重要) 取值范圍 默認值
字節(jié)型 byte Byte 1 -128(-2^7) ~ 127(2^7-1) 0
短整型 short Short 2 -2^15 ~ 2^15-1 0
整型 int Integer 4 -2^31 ~ 2^31-1 0
長整型 long Long 8 -2^63 ~ 2^63-1 0L
單精度浮點型 float Float 4 3.4e-45 ~ 1.4e38 0.0F
雙精度浮點型 double Double 8 4.9e-324 ~ 1.8e308 0.0D
字符型 char Character 2 '\u0000'
布爾型 boolean Boolean 1 true/flase flase

隱式(自動)類型轉(zhuǎn)換和顯示(強制)類型轉(zhuǎn)換?。?/h3>
  • 隱式(自動)類型轉(zhuǎn)換:從存儲范圍小的類型到存儲范圍大的類型。byteshort(char)intlongfloatdouble
  • 顯示(強制)類型轉(zhuǎn)換:從存儲范圍大的類型到存儲范圍小的類型。doublefloatlongintshort(char)byte。該類類型轉(zhuǎn)換很可能存在精度的損失。

看一個經(jīng)典的代碼

short s = 1;
s = s + 1;

這是會報錯的,因為1是int型,s+1會自動轉(zhuǎn)換為int型,將int型直接賦值給short型會報錯。

做一下修改即可避免報錯

short s = 1;
s = (short)(s + 1);

或這樣寫,因為s += 1會自動進行強制類型轉(zhuǎn)換

short s = 1;
s += 1;

自動裝箱與拆箱 **

  • 裝箱:將基本類型用包裝器類型包裝起來

  • 拆箱:將包裝器類型轉(zhuǎn)換為基本類型

    這個地方有很多易混淆的地方,但在面試中問到的頻率一般,筆試的選擇題中經(jīng)常出現(xiàn),還有一個String創(chuàng)建對象和這個比較像,很容易混淆,在下文可以看到

  • 下面這段代碼的輸出結(jié)果是什么?

    public class Main {
        public static void main(String[] args) {
            
                Integer a = 100;
                Integer b = 100;
                Integer c = 128;
                Integer d = 128;
    
                System.out.println(a==b);
                System.out.println(c==d);
        }
    }
    
    true
    false
    

    很多人看到這個結(jié)果會很疑惑,為什么會是一個true一個flase.其實從源碼中可以很容易找到原因.首先找到Integer方法中的valueOf方法

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

    可以看到當(dāng)不滿足if語句中的條件,就會重新創(chuàng)建一個對象返回,那結(jié)果必然不相等。繼續(xù)打開IntegerCache可以看到

        private static class IntegerCache {
              static final int low = -128;
            static final int high;
              static final Integer cache[];
      
              static {
                  // high value may be configured by property
                  int h = 127;
                  String integerCacheHighPropValue =
                      sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                  if (integerCacheHighPropValue != null) {
                      try {
                          int i = parseInt(integerCacheHighPropValue);
                          i = Math.max(i, 127);
                          // Maximum array size is Integer.MAX_VALUE
                          h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                      } catch( NumberFormatException nfe) {
                          // If the property cannot be parsed into an int, ignore it.
                      }
                  }
                  high = h;
      
                  cache = new Integer[(high - low) + 1];
                  int j = low;
                  for(int k = 0; k < cache.length; k++)
                      cache[k] = new Integer(j++);
      
                  // range [-128, 127] must be interned (JLS7 5.1.7)
                  assert IntegerCache.high >= 127;
              }
      
              private IntegerCache() {}
          }
    

    代碼挺長,大概說的就是在通過valueOf方法創(chuàng)建Integer對象的時候,如果數(shù)值在[-128,127]之間,便返回指向IntegerCache.cache中已經(jīng)存在的對象的引用;否則創(chuàng)建一個新的Integer對象。所以上面代碼中ab相等,cd不相等。

  • 在看下面的代碼會輸出什么

    public class Main {
        public static void main(String[] args) {
    
                Double a = 1.0;
                Double b = 1.0;
                Double c = 2.0;
                Double d = 2.0;
    
                System.out.println(a==b);
                System.out.println(c==d);
    
        }
    }
    
    
    flase
    flase
    

    采用同樣的方法,可以看到DoublevalueOf方法,每次返回都是重新new一個新的對象,所以上面代碼中的結(jié)果都不想等。

    public static Double valueOf(double d) {
              return new Double(d);
    }
    
  • 最后再看這段代碼的輸出結(jié)果

    public class Main {
        public static void main(String[] args) {
    
            Boolean a = false;
            Boolean b = false;
            Boolean c = true;
            Boolean d = true;
    
            System.out.println(a==b);
            System.out.println(c==d);
        }
    }
    
    true
    true
    

    老方法繼續(xù)看valueOf方法

    public static Boolean valueOf(boolean b) {
            return (b ? TRUE : FALSE);
        }
    

    再看看TRUEFALSE是個什么東西,是兩個靜態(tài)成員屬性。

    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);
    

**說下結(jié)論 **:Integer、Short、Byte、Character、Long這幾個類的valueOf方法的實現(xiàn)是類似的。Double、FloatvalueOf方法的實現(xiàn)是類似的。然后是BooleanvalueOf方法是單獨一組的。

  • Integer i = new Integer(xxx)Integer i =xxx的區(qū)別

    這兩者的區(qū)別主要是第一種會觸發(fā)自動裝箱,第二者不會

    最后看看下面這段程序的輸出結(jié)果

    public class Main {
        public static void main(String[] args) {
            Integer a = 1;
            Integer b = 2;
            Integer c = 3;
            Long g = 3L;
            int int1 = 12;
            int int2 = 12;
            Integer integer1 = new Integer(12);
            Integer integer2 = new Integer(12);
            Integer integer3 = new Integer(1);
    
            System.out.println("c==(a+b) ->"+ (c==(a+b)));
            System.out.println("g==(a+b) ->" + (g==(a+b)));
            System.out.println( "c.equals(a+b) ->" + (c.equals(a+b)));
            System.out.println( "g.equals(a+b) ->" + (g.equals(a+b)));
            System.out.println("int1 == int2 -> " + (int1 == int2));
            System.out.println("int1 == integer1 -> " + (int1 == integer1));
            System.out.println("integer1 == integer2 -> " + (integer1 == integer2));
            System.out.println("integer3 == a1 -> " + (integer3 == a));
        }
    }
    
    c==(a+b) ->true
    g==(a+b) ->true
    c.equals(a+b) ->true
    g.equals(a+b) ->false
    int1 == int2 -> true
    int1 == integer1 -> true
    integer1 == integer2 -> false
    integer3 == a1 -> false
    

    下面簡單解釋這些結(jié)果。

    1.當(dāng) "=="運算符的兩個操作數(shù)都是包裝器類型的引用,則是比較指向的是否是同一個對象,而如果其中有一個操作數(shù)是表達式(即包含算術(shù)運算)則比較的是數(shù)值(即會觸發(fā)自動拆箱的過程)。所以c==a+b,g==a+btrue。

    2.而對于equals方法會先觸發(fā)自動拆箱過程,再觸發(fā)自動裝箱過程。也就是說a+b,會先各自調(diào)用intValue方法,得到了加法運算后的數(shù)值之后,便調(diào)用Integer.valueOf方法,再進行equals比較。所以c.equals(a+b)true。而對于g.equals(a+b),a+b會先拆箱進行相加運算,在裝箱進行equals比較,但是裝箱后為Integer,gLong,所以g.equals(a+b)false。

    3.int1 == int2true無需解釋,int1 == integer1,在進行比較時,integer1會先進行一個拆箱操作變成int型在進行比較,所以int1 == integer1true。

    4.integer1 == integer2->false。integer1integer2都是通過new關(guān)鍵字創(chuàng)建的,可以看成兩個對象,所以integer1 == integer2false。integer3 == a1 -> false , integer3是一個對象類型,而a1是一個常量它們存放內(nèi)存的位置不一樣,所以integer3 == a1false,具體原因可學(xué)習(xí)下java的內(nèi)存模型。

String(不是基本數(shù)據(jù)類型)

String的不可變性 ***

在 Java 8 中,String 內(nèi)部使用 char 數(shù)組存儲數(shù)據(jù)。并且被聲明為final,因此它不可被繼承。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {   
    private final char value[];
}

為什么String`要設(shè)計成不可變的呢(不可變性的好處):

1.可以緩存 hash 值()

    因為 `String` 的` hash `值經(jīng)常被使用,例如` String` 用做 `HashMap` 的 `key`。不可變的特性可以使得 `hash `值也不可變,

因此只需要進行一次計算。

2.常量池優(yōu)化

    `String` 對象創(chuàng)建之后,會在字符串常量池中進行緩存,如果下次創(chuàng)建同樣的對象時,會直接返回緩存的引用。

3.線程安全

    `String` 不可變性天生具備線程安全,可以在多個線程中安全地使用。

字符型常量和字符串常量的區(qū)別 *

  1. 形式上: 字符常量是單引號引起的一個字符 字符串常量是雙引號引起的若干個字符
  2. 含義上: 字符常量相當(dāng)于一個整形值(ASCII值),可以參加表達式運算 字符串常量代表一個地址值(該字符串在內(nèi)存中存放位置)
  3. 占內(nèi)存大小 字符常量占兩個字節(jié) 字符串常量占若干個字節(jié)(至少一個字符結(jié)束標志)

什么是字符串常量池?*

字符串常量池位于堆內(nèi)存中,專門用來存儲字符串常量,可以提高內(nèi)存的使用率,避免開辟多塊空間存儲相同的字符串,在創(chuàng)建字符串時 JVM 會首先檢查字符串常量池,如果該字符串已經(jīng)存在池中,則返回它的引用,如果不存在,則實例化一個字符串放到池中,并返回其引用。

String 類的常用方法都有那些?**

面試時一般不會問,但面試或筆試寫字符串相關(guān)的算法題經(jīng)常會涉及到,還是得背一背(以下大致是按使用頻率優(yōu)先級排序)

  • length():返回字符串長度
  • charAt():返回指定索引處的字符
  • substring():截取字符串
  • trim():去除字符串兩端空白
  • split():分割字符串,返回一個分割后的字符串?dāng)?shù)組。
  • replace():字符串替換。
  • indexOf():返回指定字符的索引。
  • toLowerCase():將字符串轉(zhuǎn)成小寫字母。
  • toUpperCase():將字符串轉(zhuǎn)成大寫字符。

String和StringBuffer、StringBuilder的區(qū)別是什么?***

1.可變性

    `String`不可變,`StringBuilder`和`StringBuffer`是可變的

2.線程安全性

    `String`由于是不可變的,所以線程安全。`StringBuffer`對方法加了同步鎖或者對調(diào)用的方法加了同步鎖,所以是線程安全的。    `StringBuilder`并沒有對方法進行加同步鎖,所以是非線程安全的。

3.性能

`StringBuilder` > `StringBuffer` > `String`

為了方便記憶,總結(jié)如下

是否可變 是否安全 性能
String 不可變 安全
StringBuilder 可變 不安全
StringBuffer 可變 安全 較高

switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上 *

`switch`可以作用于`char` `byte` `short` `int`及它們對應(yīng)的包裝類型,`switch`不可作用于`long` `double` `float` `boolean`及他們的包裝類型。在 JDK1.5之后可以作用于枚舉類型,在JDK1.7之后可作用于`String`類型。

Java語言采用何種編碼方案?有何特點?*

Java語言采用Unicode編碼標準,它為每個字符制訂了一個唯一的數(shù)值,因此在任何的語言,平臺,程序都可以放心的使用。

訪問修飾符 **

在Java編程語言中有四種權(quán)限訪問控制符,這四種訪問權(quán)限的控制符能夠控制類中成員的可見性。其中類有兩種`public`、`default`。而方法和變量有 4 種:`public`、`default`、`protected`、`private`。
  • public : 對所有類可見。使用對象:類、接口、變量、方法

  • protected : 對同一包內(nèi)的類和所有子類可見。使用對象:變量、方法。 注意:不能修飾類(外部類)。

  • default : 在同一包內(nèi)可見,不使用任何修飾符。使用對象:類、接口、變量、方法。

  • private : 在同一類內(nèi)可見。使用對象:變量、方法。 注意:不能修飾類(外部類)

    修飾符 當(dāng)前類 同包內(nèi) 子類(同包) 其他包
    public Y Y Y Y
    protected Y Y Y N
    default Y Y Y N
    private Y N N N

運算符?。?/h3>
  • &&和&

    &&&都可以表示邏輯與,但他們是有區(qū)別的,共同點是他們兩邊的條件都成立的時候最終結(jié)果才是true;不同點是&&只要是第一個條件不成立為false,就不會再去判斷第二個條件,最終結(jié)果直接為false,而&判斷的是所有的條件。

  • ||和|

    |||都表示邏輯或,共同點是只要兩個判斷條件其中有一個成立最終的結(jié)果就是true,區(qū)別是||只要滿足第一個條件,后面的條件就不再判斷,而|要對所有的條件進行判斷。

關(guān)鍵字

static關(guān)鍵字 ***

`static`關(guān)鍵字的主要用途**就是方便在沒有創(chuàng)建對象時調(diào)用方法和變量和優(yōu)化程序性能**

**1.static變量(靜態(tài)變量)**

用`static`修飾的變量被稱為靜態(tài)變量,也被稱為類變量,可以直接通過類名來訪問它。靜態(tài)變量被所有的對象共享,在內(nèi)存中只有一個副本,僅當(dāng)在類初次加載時會被初始化,而非靜態(tài)變量在創(chuàng)建對象的時候被初始化,并且存在多個副本,各個對象擁有的副本互不影響。

**2.static方法(靜態(tài)方法)**

`static`方法不依賴于任何對象就可以進行訪問,在`static`方法中不能訪問類的非靜態(tài)成員變量和非靜態(tài)成員方法,因為非靜態(tài)成員方法/變量都是必須依賴具體的對象才能夠被調(diào)用,但是在非靜態(tài)成員方法中是可以訪問靜態(tài)成員方法/變量的。
public class Main {
    public static String s1 = "s1";//靜態(tài)變量
    String s2  = "s2";
    public void fun1(){
        System.out.println(s1);
        System.out.println(s2);
    }
    
    public static void fun2(){
        System.out.println(s1);
        System.out.println(s2);//此處報錯,靜態(tài)方法不能調(diào)用非靜態(tài)變量
    }
}
**3.static代碼塊(靜態(tài)代碼塊)**

靜態(tài)代碼塊的主要用途是可以用來優(yōu)化程序的性能,因為它只會在類加載時加載一次,很多時候會將一些只需要進行一次的初始化操作都放在`static`代碼塊中進行。如果程序中有多個`static`塊,在類初次被加載的時候,會按照`static`塊的順序來執(zhí)行每個`static`塊。
public class Main {
    static {
        System.out.println("hello,word");
    }
    public static void main(String[] args) {
        Main m = new Main();
    }
}
**4.可以通過this訪問靜態(tài)成員變量嗎?(可以)**

`this`代表當(dāng)前對象,可以訪問靜態(tài)變量,而靜態(tài)方法中是不能訪問非靜態(tài)變量,也不能使用`this`引用。

**5.初始化順序**

靜態(tài)變量和靜態(tài)語句塊優(yōu)先于實例變量和普通語句塊,靜態(tài)變量和靜態(tài)語句塊的初始化順序取決于它們在代碼中的順序。如果存在繼承關(guān)系的話,初始化順序為**父類中的靜態(tài)變量和靜態(tài)代碼塊——子類中的靜態(tài)變量和靜態(tài)代碼塊——父類中的實例變量和普通代碼塊——父類的構(gòu)造函數(shù)——子類的實例變量和普通代碼塊——子類的構(gòu)造函數(shù)**

final 關(guān)鍵字?。?/h4>
`final`關(guān)鍵字主要用于修飾類,變量,方法。
  1. 類:被final修飾的類不可以被繼承
  2. 方法:被final修飾的方法不可以被重寫
  3. 變量:被final修飾的變量是基本類型,變量的數(shù)值不能改變;被修飾的變量是引用類型,變量便不能在引用其他對象,但是變量所引用的對象本身是可以改變的。
public class Main {
    int a = 1;
    public static void main(String[] args) {
        final int b = 1;
        b = 2;//報錯
        final Main m = new Main();
        m.a = 2;//不報錯,可以改變引用類型變量所指向的對象
    }
}   

final finally finalize區(qū)別 ***

  • final主要用于修飾類,變量,方法
  • finally一般作用在try-catch代碼塊中,在處理異常的時候,通常我們將一定要執(zhí)行的代碼方法finally代碼塊
    中,表示不管是否出現(xiàn)異常,該代碼塊都會執(zhí)行,一般用來存放一些關(guān)閉資源的代碼。
  • finalize是一個屬于Object類的一個方法,該方法一般由垃圾回收器來調(diào)用,當(dāng)我們調(diào)用System.gc()方法的時候,由垃圾回收器調(diào)用finalize(),回收垃圾,但Java語言規(guī)范并不保證inalize方法會被及時地執(zhí)行、而且根本不會保證它們會被執(zhí)行。

this關(guān)鍵字?。?/h4>

重點掌握前三種即可

1.`this`關(guān)鍵字可用來引用當(dāng)前類的實例變量。主要用于形參與成員名字重名,用`this`來區(qū)分。
public Person(String name, int age) {
    this.name = name;
    this.age = age;
}
2.`this`關(guān)鍵字可用于調(diào)用當(dāng)前類方法。
public class Main {
    public void fun1(){
        System.out.println("hello,word");
    }
    public void fun2(){
        this.fun1();//this可省略
    }

    public static void main(String[] args) {
        Main m = new Main();
        m.fun2();
    }
}
3.`this()`可以用來調(diào)用當(dāng)前類的構(gòu)造函數(shù)。(注意:`this()`一定要放在構(gòu)造函數(shù)的第一行,否則編譯不通過)
class Person{
    private String name;
    private int age;
    
    public Person() {
    }
 
    public Person(String name) {
        this.name = name;
    }
    public Person(String name, int age) {
        this(name);
        this.age = age;
    }
}
4.`this`關(guān)鍵字可作為調(diào)用方法中的參數(shù)傳遞。

5.`this`關(guān)鍵字可作為參數(shù)在構(gòu)造函數(shù)調(diào)用中傳遞。

6.`this`關(guān)鍵字可用于從方法返回當(dāng)前類的實例。super

super關(guān)鍵字 **

1.`super`可以用來引用直接父類的實例變量。和`this`類似,主要用于區(qū)分父類和子類中相同的字段

2.`super`可以用來調(diào)用直接父類構(gòu)造函數(shù)。(注意:`super()`一定要放在構(gòu)造函數(shù)的第一行,否則編譯不通過)

3.`super`可以用來調(diào)用直接父類方法。
public class Main {
    public static void main(String[] args) {
        Child child = new Child("Father","Child");
        child.test();
    }
}

class Father{
    protected String name;

    public Father(String name) {
        this.name = name;
    }

    public void Say(){
        System.out.println("hello,child");
    }

}

class Child extends Father{
    private String name;

    public Child(String name1, String name2) {
        super(name1);      //調(diào)用直接父類構(gòu)造函數(shù)
        this.name = name2;
    }

    public void test(){
        System.out.println(this.name);
        System.out.println(super.name);  //引用直接父類的實例變量
        super.Say();      //調(diào)用直接父類方法
    }
}

this與super的區(qū)別 **

  • 相同點:

    1. super()this()都必須在構(gòu)造函數(shù)的第一行進行調(diào)用,否則就是錯誤的
    2. this()super()都指的是對象,所以,均不可以在static環(huán)境中使用。
  • 不同點:

    1. super()主要是對父類構(gòu)造函數(shù)的調(diào)用,this()是對重載構(gòu)造函數(shù)的調(diào)用
    2. super()主要是在繼承了父類的子類的構(gòu)造函數(shù)中使用,是在不同類中的使用;this()主要是在同一類的不同構(gòu)造函數(shù)中的使用

break ,continue ,return 的區(qū)別及作用?。?/h4>
  • break結(jié)束當(dāng)前的循環(huán)體
  • continue結(jié)束本次循環(huán),進入下一次循環(huán)
  • return結(jié)束當(dāng)前方法

面向?qū)ο蠛兔嫦蜻^程的區(qū)別?。?/h3>
  • 面向過程

    優(yōu)點:性能比面向?qū)ο蟾?,因為類調(diào)用時需要實例化,開銷比較大,比較消耗資源。

    缺點:沒有面向?qū)ο笠拙S護、易復(fù)用、易擴展

  • 面向?qū)ο?/p>

    優(yōu)點:易維護、易復(fù)用、易擴展,由于面向?qū)ο笥蟹庋b、繼承、多態(tài)性的特性,可以設(shè)計出低耦合的系統(tǒng),使系統(tǒng)更加靈活、更加易于維護

    缺點:性能比面向過程低

面向?qū)ο笕筇匦?封裝、繼承、多態(tài))?。?/h3>
  • 封裝

    封裝就是隱藏對象的屬性和實現(xiàn)細節(jié),僅對外公開接口,控制在程序中屬性的讀和修改的訪問級別。

  • 繼承

    繼承就是子類繼承父類的特征和行為,使得子類對象(實例)具有父類的實例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。

  • 多態(tài)(重要)

    多態(tài)是同一個行為具有多個不同表現(xiàn)形式或形態(tài)的能力。這句話不是很好理解,可以看這個解釋,在Java語言中,多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時并不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發(fā)出的方法調(diào)用到底是哪個類中實現(xiàn)的方法,必須在由程序運行期間才能決定。

    在Java中實現(xiàn)多態(tài)的三個必要條件:繼承、重寫、向上轉(zhuǎn)型。繼承和重寫很好理解,向上轉(zhuǎn)型是指在多態(tài)中需要將子類的引用賦給父類對象。

    public class Main {
        public static void main(String[] args) {
              Person person = new Student(); //向上轉(zhuǎn)型
              person.run();
        }
    }
    
    class Person {
        public void run() {
            System.out.println("Person");
        }
    }
    
    class Student extends Person {   //繼承
        @Override 
        public void run() {         //重載
            System.out.println("Student");
        }
    }
    

    運行結(jié)果為

    Student
    

面向?qū)ο笪宕蠡驹瓌t是什么?。?/h3>
  • 單一職責(zé)原則(Single-Resposibility Principle)

    一個類,最好只做一件事,只有一個引起它的變化。單一職責(zé)原則可以看做是低耦合、高內(nèi)聚在面向?qū)ο笤瓌t上的引申,將職責(zé)定義為引起變化的原因,以提高內(nèi)聚性來減少引起變化的原因。

  • 開放封閉原則(Open-Closed principle)

    軟件實體應(yīng)該是可擴展的,而不可修改的。也就是,對擴展開放,對修改封閉的。

  • 里氏替換原則 (Liskov-Substituion Principle)

    子類必須能夠替換其基類。這一思想體現(xiàn)為對繼承機制的約束規(guī)范,只有子類能夠替換基類時,才能保證系統(tǒng)在運行期內(nèi)識別子類,這是保證繼承復(fù)用的基礎(chǔ)。在父類和子類的具體行為中,必須嚴格把握繼承層次中的關(guān)系和特征,將基類替換為子類,程序的行為不會發(fā)生任何變化。同時,這一約束反過來則是不成立的,子類可以替換基類,但是基類不一定能替換子類。

  • 依賴倒置原則(Dependecy-Inversion Principle)

    依賴于抽象。具體而言就是高層模塊不依賴于底層模塊,二者都同依賴于抽象;抽象不依賴于具體,具體依賴于抽象。

  • 接口隔離原則(Interface-Segregation Principle)

    使用多個小的專門的接口,而不要使用一個大的總接口。

抽象類和接口的對比?。?/h3>
在Java語言中,`abstract class`和`interface`是支持抽象類定義的兩種機制。抽象類:用來捕捉子類的通用特性的。接口:抽象方法的集合。

相同點:

  • 接口和抽象類都不能實例化
  • 都包含抽象方法,其子類都必須覆寫這些抽象方法

不同點:

類型 抽象類 接口
定義 abstract class Interface
實現(xiàn) extends(需要提供抽象類中所有聲明的方法的實現(xiàn)) implements(需要提供接口中所有聲明的方法的實現(xiàn))
繼承 抽象類可以繼承一個類和實現(xiàn)多個接口;子類只可以繼承一個抽象類 接口只可以繼承接口(一個或多個);子類可以實現(xiàn)多個接口
訪問修飾符 抽象方法可以有public、protected和default這些修飾符 接口方法默認修飾符是public。你不可以使用其它修飾符
構(gòu)造器 抽象類可以有構(gòu)造器 接口不能有構(gòu)造器
字段聲明 抽象類的字段聲明可以是任意的 接口的字段默認都是 static 和 final 的

在Java中定義一個不做事且沒有參數(shù)的構(gòu)造方法的作用?。?/h3>
Java程序存在繼承,在執(zhí)行子類的構(gòu)造方法時,如果沒有用`super()`來調(diào)用父類特定的構(gòu)造方法,則會調(diào)用父類中“沒有參數(shù)的構(gòu)造方法”。如果父類只定義了有參數(shù)的構(gòu)造函數(shù),而子類的構(gòu)造函數(shù)沒有用`super`調(diào)用父類那個特定的構(gòu)造函數(shù),就會出錯。

在調(diào)用子類構(gòu)造方法之前會先調(diào)用父類沒有參數(shù)的構(gòu)造方法,其目的是 *

幫助子類做初始化工作。

一個類的構(gòu)造方法的作用是什么?若一個類沒有聲明構(gòu)造方法,改程序能正確執(zhí)行嗎?為什么? *

主要作用是完成對類對象的初始化工作??梢詧?zhí)行。因為一個類即使沒有聲明構(gòu)造方法也會有默認的不帶參數(shù)的構(gòu)造方法。

構(gòu)造方法有哪些特性??。?/h3>
  • 方法名稱和類同名
  • 不用定義返回值類型
  • 不可以寫retrun語句
  • 構(gòu)造方法可以被重載

變量?。?/h3>
  • 類變量:獨立于方法之外的變量,用static修飾。

  • 實例變量:獨立于方法之外的變量,不過沒有 static 修飾。

  • 局部變量:類的方法中的變量。

  • 成員變量:成員變量又稱全局變量,可分為類變量和實例變量,有static修飾為類變量,沒有static修飾為實例變量。

    類變量 實例變量 局部變量
    定義位置 類中,方法外 類中,方法外 方法中
    初始值 有默認初始值 有默認初始值 無默認初始值
    存儲位置 方法區(qū)
    生命周期 類何時被加載和卸載 實例何時被創(chuàng)建及銷毀 方法何時被調(diào)用及結(jié)束調(diào)用

內(nèi)部類?。?/h3>
內(nèi)部類包括這四種:成員內(nèi)部類、局部內(nèi)部類、匿名內(nèi)部類和靜態(tài)內(nèi)部類
  • 成員內(nèi)部類

    1.成員內(nèi)部類定義為位于另一個類的內(nèi)部,成員內(nèi)部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態(tài)成員)。

    class Outer{
        private double a = 0;
        public static int b =1;
        public Outer(double a) {
            this.a = a;
        }
    
        class Inner {     //內(nèi)部類
            public void fun() {
                System.out.println(a);
                System.out.println(b);
            }
        }
    }
    

    2.當(dāng)成員內(nèi)部類擁有和外部類同名的成員變量或者方法時,即默認情況下訪問的是成員內(nèi)部類的成員。如果要訪問外部類的同名成員,需要以下面的形式進行訪問:外部類.this.成員變量

    3.在外部類中如果要訪問成員內(nèi)部類的成員,必須先創(chuàng)建一個成員內(nèi)部類的對象,再通過指向這個對象的引用來訪問。

    4.成員內(nèi)部類是依附外部類而存在的,如果要創(chuàng)建成員內(nèi)部類的對象,前提是必須存在一個外部類的對象。創(chuàng)建成員內(nèi)部類對象的一般方式如下:

    class Outter{
        private double a = 0;
        public static int b =1;
        public Outter(){}
        public Outter(double a) {
            this.a = a;
            Inner inner = new Inner();
            inner.fun();     //調(diào)用內(nèi)部類的方法
        }
    
    
        class Inner {     //內(nèi)部類
            int b = 2;
            public void fun() {
                System.out.println(a);
                System.out.println(b);            //訪問內(nèi)部類的b
                System.out.println(Outter.this.b);//訪問外部類的b
            }
        }
    }
    public class Main{
        public static void main(String[] args) {
            Outter outter = new Outter();
            Outter.Inner inner = outter.new Inner(); //創(chuàng)建內(nèi)部類的對象
        }
    }
    
  • 局部內(nèi)部類

    局部內(nèi)部類是定義在一個方法或者一個作用域里面的類,它和成員內(nèi)部類的區(qū)別在于局部內(nèi)部類的訪問僅限于方法內(nèi)或者該作用域內(nèi)。定義在實例方法中的局部類可以訪問外部類的所有變量和方法,定義在靜態(tài)方法中的局部類只能訪問外部類的靜態(tài)變量和方法。

    class Outter {
    
        private int outter_a = 1;
        private static int static_b = 2;
    
        public void test1(){
            int inner_c =3;
            class Inner {
                private void fun(){
                    System.out.println(outter_a);
                    System.out.println(static_b);
                    System.out.println(inner_c);
                }
            }
            Inner  inner = new Inner(); //創(chuàng)建局部內(nèi)部類
            inner.fun();
        }
        public static void test2(){
            int inner_d =3;
            class Inner {
                private void fun(){
                     System.out.println(outter_a); //編譯錯誤,定義在靜態(tài)方法中的局部類不可以訪問外部類的實例變量
                    System.out.println(static_b);
                    System.out.println(inner_d);
                }
            }
            Inner  inner = new Inner();
            inner.fun();
        }
    }
    
  • 匿名內(nèi)部類

    匿名內(nèi)部類只沒有名字的內(nèi)部類,在日常開發(fā)中使用較多。使用匿名內(nèi)部類的前提條件:必須繼承一個父類或?qū)崿F(xiàn)一個接口。

    interface Person {
        public void fun();
    }
    class Demo {
        public static void main(String[] args) {
             new Person() {
                public void fun() {
                    System.out.println("hello,word");
                }
            }.fun();
        }
    }
    
  • 靜態(tài)內(nèi)部類

    靜態(tài)內(nèi)部類也是定義在另一個類里面的類,只不過在類的前面多了一個關(guān)鍵字static。靜態(tài)內(nèi)部類是不需要依賴于外部類的,并且它不能使用外部類的非static成員變量或者方法,這點很好理解,因為在沒有外部類的對象的情況下,可以創(chuàng)建靜態(tài)內(nèi)部類的對象,如果允許訪問外部類的非static成員就會產(chǎn)生矛盾,因為外部類的非static成員必須依附于具體的對象。

    class Outter {
        int a = 1;
        static int b = 2;
        public Outter() {
    
        }
    
        static class Inner {
            public Inner() {
                System.out.println(a);//報錯,靜態(tài)內(nèi)部類不能訪問非靜態(tài)變量
                System.out.println(b);
            }
        }
    
    }
    public class Main{
        public static void main(String[] args) {
            Outter.Inner inner = new Outter.Inner();
        }
    }
    
    
  • 內(nèi)部類的優(yōu)點:

    1. 內(nèi)部類不為同一包的其他類所見,具有很好的封裝性;
    2. 匿名內(nèi)部類可以很方便的定義回調(diào)。
    3. 每個內(nèi)部類都能獨立的繼承一個接口的實現(xiàn),所以無論外部類是否已經(jīng)繼承了某個(接口的)實現(xiàn),對于內(nèi)部類都沒有影響。
    4. 內(nèi)部類有效實現(xiàn)了“多重繼承”,優(yōu)化 java 單繼承的缺陷。
  • 局部內(nèi)部類和匿名內(nèi)部類訪問局部變量的時候,為什么變量必須要加上final?

    public class Main {
        public static void main(String[] args)  {
             
        }
         
        public void fun(final int b) {
            final int a = 10;
            new Thread(){
                public void run() {
                    System.out.println(a);
                    System.out.println(b);
                };
            }.start();
        }
    }
    

    對于變量a可以從生命周期的角度理解,局部變量直接存儲在棧中,當(dāng)方法執(zhí)行結(jié)束后,非final的局部變量就被銷毀,而局部內(nèi)部類對局部變量的引用依然存在,如果局部內(nèi)部類要調(diào)用沒有final修飾的局部變量時,就會造成生命周期不一致出錯。

    對于變量b,其實是將fun方法中的變量b以參數(shù)的形式對匿名內(nèi)部類中的拷貝(變量b的拷貝)進行賦值初始化。在run方法中訪問的變量b根本就不是test方法中的局部變量b,而是一個拷貝值,所以不存在生命周期不一致的問題,但如果在run方法中修改變量b的值會導(dǎo)致數(shù)據(jù)不一致,所以需要加final修飾。

重寫與重載?。?/h3>

重載和重寫的區(qū)別

  • 重載:發(fā)生在同一個類中,方法名相同參數(shù)列表不同(參數(shù)類型不同、個數(shù)不同、順序不同),與方法返回值和訪問修飾符無關(guān),即重載的方法不能根據(jù)返回類型進行區(qū)分。
  • 重寫:發(fā)生在父子類中,方法名、參數(shù)列表必須相同,返回值小于等于父類,拋出的異常小于等于父類,訪問修飾符大于等于父類(里氏代換原則);如果父類方法訪問修飾符為private則子類中就不是重寫。

構(gòu)造器(constructor)是否可被重寫(override)

構(gòu)造器可以被重載,不能被重寫

重載的方法能否根據(jù)返回類型進行區(qū)分?為什么?

不能,因為調(diào)用時不能指定類型信息,編譯器不知道你要調(diào)用哪個函數(shù)。

== 和 equals 的區(qū)別?。?/h3>
  • ==

    對于基本數(shù)據(jù)類型,==比較的是值;對于引用數(shù)據(jù)類型,==比較的是內(nèi)存地址。

  • eauals

    對于沒有重寫equals方法的類,equals方法和==作用類似;對于重寫過equals方法的類,equals比較的是值。

hashCode 與 equals(為什么重寫equals方法后,hashCode方法也必須重寫)?。?/h3>
  • equals

    先看下String類中重寫的equals方法。

        public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }
    

    從源碼中可以看到:

    1. equals方法首先比較的是內(nèi)存地址,如果內(nèi)存地址相同,直接返回true;如果內(nèi)存地址不同,再比較對象的類型,類型不同直接返回false;類型相同,再比較值是否相同;值相同返回true,值不同返回false。總結(jié)一下,equals會比較內(nèi)存地址、對象類型、以及值,內(nèi)存地址相同,equals一定返回true;對象類型和值相同,equals方法一定返回true
    2. 如果沒有重寫equals方法,那么equals==的作用相同,比較的是對象的地址值。
  • hashCode

    hashCode方法返回對象的散列碼,返回值是int類型的散列碼。散列碼的作用是確定該對象在哈希表中的索引位置。

    關(guān)于hashCode有一些約定:

    1. 兩個對象相等,則hashCode一定相同。
    2. 兩個對象有相同的hashCode值,它們不一定相等。
    3. hashCode()方法默認是對堆上的對象產(chǎn)生獨特值,如果沒有重寫hashCode()方法,則該類的兩個對象的hashCode值肯定不同
  • 為什么重寫equals方法后,hashCode方法也必須重寫

    1. 根據(jù)規(guī)定,兩個對象相等,hashCode值也許相同,所以重寫equals方法后,hashCode方法也必須重寫(面試官肯定不是想聽這個答案)
    2. hashCode在具有哈希機制的集合中起著非常關(guān)鍵的作用,比如HashMap、HashSet等。以HashSet為例,HashSet的特點是存儲元素時無序且唯一,在向HashSet中添加對象時,首相會計算對象的HashCode值來確定對象的存儲位置,如果該位置沒有其他對象,直接將該對象添加到該位置;如果該存儲位置有存儲其他對象(新添加的對象和該存儲位置的對象的HashCode值相同),調(diào)用equals方法判斷兩個對象是否相同,如果相同,則添加對象失敗,如果不相同,則會將該對象重新散列到其他位置。所以重寫equals方法后,hashCode方法不重寫的話,會導(dǎo)致所有對象的HashCode值都不相同,都能添加成功,那么HashSet中會出現(xiàn)很多重復(fù)元素,HashMap也是同理(因為HashSet的底層就是通過HashMap實現(xiàn)的),會出現(xiàn)大量相同的KeyHashMap中的key是唯一的,但不同的key可以對應(yīng)相同的value)。所以重寫equals方法后,hashCode方法也必須重寫。同時因為兩個對象的hashCode值不同,則它們一定不相等,所以先計算對象的hashCode值可以在一定程度上判斷兩個對象是否相等,提高了集合的效率。總結(jié)一下,一共兩點:第一,在HashSet等集合中,不重寫hashCode方法會導(dǎo)致其功能出現(xiàn)問題;第二,可以提高集合效率。

Java 中是值傳遞還是引用傳遞,還是兩者共存 **

這是一個很容易搞混又很難解釋清楚的問題,先說結(jié)論,Java中只有值傳遞

先看這樣一段代碼

public class Main{
    public static void main(String[] args) {
        int a = 1;
        printValue(a);
        System.out.println("a:" + a);
    }
    public static void printValue(int b){
        b = 2;
        System.out.println("b:"+ b);
    }
}

輸出

b:2
a:1

可以看到將a的值傳到printValue方法中,并將其值改為2。但方法調(diào)用結(jié)束后,a的值還是1,并未發(fā)生改變,所以這種情況下為值傳遞。

再看這段代碼

public class Main{
    public static void main(String[] args) {
        Preson p = new Preson();
        p.name = "zhangsan";
        printValue(p);
        System.out.println("p.name: " + p.name);
    }
    public static void printValue(Preson q){
        q.name = "lisi";
        System.out.println("q.name: "+ q.name);
    }
}
class Preson{
    public String name;
}

輸出結(jié)果

q.name: lisi
p.name: lisi

在將p傳入printValue方法后,方法調(diào)用結(jié)束,pname屬性竟然被改變了!所以得出結(jié)論,參數(shù)為基本類型為值傳遞,參數(shù)為引用類型為時為引用傳遞。這個結(jié)論是錯誤的,下面來看看判斷是值傳遞還是值傳遞的關(guān)鍵是什么,先看定義

  • 值傳遞:是指在調(diào)用函數(shù)時將實際參數(shù)復(fù)制一份傳遞到函數(shù)中,這樣在函數(shù)中如果對參數(shù)進行修改,將不會影響到實際參數(shù)。
  • 引用傳遞:是指在調(diào)用函數(shù)時將實際參數(shù)的地址直接傳遞到函數(shù)中,那么在函數(shù)中對參數(shù)所進行的修改,將影響到實際參數(shù)。

從定義中可以明顯看出,區(qū)分是值傳遞還是引用傳遞主要是看向方法中傳遞的是實際參數(shù)的副本還是實際參數(shù)的地址。上面第一個例子很明顯是值傳遞,其實第二個例子中向printValue方法中傳遞的是一個引用的副本,只是這個副本引用和原始的引用指向的同一個對象,所以副本引用修改過對象屬性后,通過原始引用查看對象屬性肯定也是被修改過的。換句話說,printValue方法中修改的是副本引用指向的對象的屬性,不是引用本身,如果修改的是引用本身,那么原始引用肯定不受影響??聪旅孢@個例子

public class Main{
    public static void main(String[] args) {
        Preson p = new Preson();
        p.name = "zhangsan";
        printValue(p);
        System.out.println("p.name: " + p.name);
    }
    public static void printValue(Preson q){
        q = new Preson();
        q.name = "lisi";
        System.out.println("q.name: "+ q.name);
    }
}
class Preson{
    public String name;
}

輸出結(jié)果

q.name: lisi
p.name: zhangsan

可以看到將p傳入printValue方法后,printValue方法調(diào)用結(jié)束后,p的屬性name沒有改變,這是因為printValue方法中并沒有改變副本引用q所指向的對象,而是改變了副本引用q本身,將副本引用q指向了另一個對象并對這個對象的屬性進行修改,所以原始引用p所指向的對象不受影響。所以證明Java中只存在值傳遞。

IO流?。?/h3>

Java IO流主要可以分為輸入流和輸出流。按照照操作單元劃分,可以劃分為字節(jié)流和字符流。按照流的角色劃分為節(jié)點流和處理流。

Java I0流的40多個類都是從4個抽象類基類中派生出來的。

  • InputStream:字節(jié)輸入流
  • Reader:字符輸入流
  • OutputStream:字節(jié)輸出流
  • Writer:字符輸出流

BIO,NIO,AIO 有什么區(qū)別??。?/h3>
  • BIO (Blocking I/O):服務(wù)器實現(xiàn)模式為一個連接一個線程,即客戶端有連接請求時服務(wù)器就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,可以通過線程池機制來改善。BIO方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對服務(wù)端資源要求比較高,并發(fā)局限于應(yīng)用中,在jdk1.4以前是唯一的io

  • NIO (New I/O):服務(wù)器實現(xiàn)模式為一個請求一個線程,即客戶端發(fā)送的連接請求都會注冊到多路復(fù)用器上,多路復(fù)用器輪詢到連接有IO請求時才啟動一個線程進行處理。NIO方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu),比如聊天服務(wù)器,并發(fā)局限于應(yīng)用中,編程比較復(fù)雜,jdk1,4開始支持

  • AIO (Asynchronous I/O):服務(wù)器實現(xiàn)模式為一個有效請求一個線程,客戶端的IO請求都是由操作系統(tǒng)先完成了再通知服務(wù)器用其啟動線程進行處理。AIO方式適用于連接數(shù)目多且連接比較長(重操作)的架構(gòu),比如相冊服務(wù)器,充分調(diào)用OS參與并發(fā)操作,編程比較復(fù)雜,jdk1.7開始支持。

    這些概念看著比較枯燥,可以從這個經(jīng)典的燒開水的例子去理解

    **BIO **:來到廚房,開始燒水NIO,并坐在水壺面前一直等著水燒開。

    NIO:來到廚房,開AIO始燒水,但是我們不一直坐在水壺前面等,而是做些其他事,然后每隔幾分鐘到廚房看一下水有沒有燒開。

    AIO:來到廚房,開始燒水,我們不一直坐在水壺前面等,而是在水壺上面裝個開關(guān),水燒開之后它會通知我。

反射?。?/h3>

JAVA反射機制是在運行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意一個方法和屬性;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制。

Java獲取Class對象的三種方式

class Person {
    public String name = "zhangsan";
    public Person() {
    }
}
public class Main{
    public static void main(String[] args) throws ClassNotFoundException {
    //方式1
     Person p1 = new Person();
     Class c1 = p1.getClass();
     //方式2
    Class c2 = Person.class;
    //方式3可能會拋出ClassNotFoundException異常
    Class c3 = Class.forName("com.company");
    }
}

因為在一個類在 JVM 中只會有一個 Class 實例,所以對c1c2、c3進行equals比較時返回的都是true。

反射優(yōu)缺點:

  • 優(yōu)點:運行期類型的判斷,動態(tài)加載類,提高代碼靈活度。
  • 缺點:性能比直接的java代碼要慢很多。

反射應(yīng)用場景:

  1. Java的很多框架都用到了反射,例如Spring中的xml的配置模式等
  2. 動態(tài)代理設(shè)計模式也采用了反射機制

JAVA異?!。?/h3>

異常主要分為ErrorException兩種

  • Error:Error類以及他的子類的實例,代表了JVM本身的錯誤。錯誤不能被程序員通過代碼處理。
  • EXception:Exception以及他的子類,代表程序運行時發(fā)送的各種不期望發(fā)生的事件??梢员籎ava異常處理機制使用,是異常處理的核心。

異??驁D


在這里插入圖片描述

除了以上的分類,異常還能分為非檢查異常和檢查異常

  • 非檢查異常(unckecked exception):該類異常包括運行時異常(RuntimeException極其子類)和錯誤(Error)。編譯器不會進行檢查并且不要求必須處理的異常,也就說當(dāng)程序中出現(xiàn)此類異常時,即使我們沒有try-catch捕獲它,也沒有使用throws拋出該異常,編譯也會正常通過。因為這樣的異常發(fā)生的原因很可能是代碼寫的有問題。
  • 檢查異常(checked exception):除了ErrorRuntimeException的其它異常。這是編譯器要求必須處理的異常。這樣的異常一般是由程序的運行環(huán)境導(dǎo)致的。因為程序可能被運行在各種未知的環(huán)境下,而程序員無法干預(yù)用戶如何使用他編寫的程序,所以必須處理這些異常。

下面來看下try{}catch(){}finally{}return之間的“恩恩怨怨”,這里有些亂,面試時問的也不是很多,實在記不住就算啦。還是先看代碼猜結(jié)果。

public class Main{
    public static void main(String[] args) {
        int a = test1();
        System.out.println(a);
        int b = test2();
        System.out.println(b);
        int c = test3();
        System.out.println(c);
        int d = test4();
        System.out.println(d);
        int e = test5();
        System.out.println(e);
    }
    public static int test1(){
        int a = 1;
        try{
            a = 2;
            return a;
        }catch(Exception e){
            System.out.println("hello,test1");
            a = 3;
        }finally{
            a = 4;
        }
        return a;
    }
    //輸出 2
    
    
    public static int test2(){
        int a = 1;
        try{
            a = 2;
            return a;
        }catch(Exception e){
            System.out.println("hello,test2");
            a = 3;
            return a;
        }finally{
            a = 4;
        }
    }
    //輸出 2
    
    
    public static int test3(){
        int a = 1;
        try{
            a = 2/0;
            return a;
        }catch(Exception e){
            System.out.println("hello,test3");
            a = 3;
        }finally{
            a = 4;
        }
        return a;
    }
    //輸出 hello,test3 
    // 4
    
    
    public static int test4(){
        int a = 1;
        try{
            a = 2/0;
            return a;
        }catch(Exception e){
            System.out.println("hello,test4");
            a = 3;
            return a;
        }finally{
            a = 4;
        }
    }
    //輸出 hello,test4
    // 3
    
    public static int test5(){
        int a = 1;
        try{
            a = 2/0;
            return a;
        }catch(Exception e){
            a = 3;
            return a;
        }finally{
            a = 4;
            return a;
        }
    }
    
    //輸出 4
}

如果沒有仔細的研究過,應(yīng)該好多會猜錯,下面總結(jié)下規(guī)律。

  1. 從前三個例子可以看出如果try{}中的代碼沒有異常,catch(){}代碼塊中的代碼不會執(zhí)行。所以如果try{}catch(){}都含有return時,無異常執(zhí)行try{}中的return,存在異常執(zhí)行catch(){}return。
  2. 不管任何情況,就算try{}catch(){}中含有return,finally{}中的代碼一定會執(zhí)行,那么為什么test1、test2、test3中的結(jié)果不是4呢,因為雖然finally是在return后面的表達式運算之后執(zhí)行的,但此時并沒有返回運算之后的值,而是把值保存起來,不管finally對該值做任何的改變,返回的值都不會改變,依然返回保存起來的值。也就是說方法的返回值是在finally運算之前就確定了的。
  3. 如果return的數(shù)據(jù)是引用數(shù)據(jù)類型,而在finally中對該引用數(shù)據(jù)類型的屬性值的改變起作用,try中的return語句返回的就是在finally中改變后的該屬性的值。這個不理解可以看看上面提到的Java的值傳遞的問題。
  4. 如果finally{}中含有return,會導(dǎo)致程序提前退出,不在執(zhí)行try{}catch(){}中的return。所以test5返回的值是4。

最后總計一下try{}catch(){}finally{}的執(zhí)行順序。

  1. 先執(zhí)行try中的語句,包括return后面的表達式;
  2. 有異常時,執(zhí)行catch中的語句,包括return后面的表達式,無異常跳過catch語句;
  3. 然后執(zhí)行finally中的語句,如果finally里面有return語句,執(zhí)行return語句,程序結(jié)束;
  4. finally{}中沒有return時,無異常執(zhí)行try中的return,如果有異常時則執(zhí)行catch中的return。前兩步執(zhí)行的return只是確定返回的值,程序并未結(jié)束,finally{}執(zhí)行之后,最后將前兩步確定的return的返回值返回。

JAVA注解?。?/h3>

面試問的不多,但是在使用框架開發(fā)時會經(jīng)常使用,但東西太多了,這里只是簡單介紹下概念。

Annotation注解可以看成是java中的一種標記記號,用來給java中的類,成員,方法,參數(shù)等任何程序元素添加一些額外的說明信息,同時不改變程序語義。注解可以分為三類:基本注解,元注解,自定義注解

  • 標準注解

    1. @Deprecated:該注解用來說明程序中的某個元素(類,方法,成員變量等)已經(jīng)不再使用,如果使用的話的編譯器會給出警告。
    2. @SuppressWarnings(value=“”):用來抑制各種可能出現(xiàn)的警告。
    3. @Override:用來說明子類方法覆蓋了父類的方法,保護覆蓋方法的正確使用
  • 元注解(元注解也稱為元數(shù)據(jù)注解,是對注解進行標注的注解,元注解更像是一種對注解的規(guī)范說明,用來對定義的注解進行行為的限定。例如說明注解的生存周期,注解的作用范圍等)

    1. @Target(value=“ ”):該注解是用來限制注解的使用范圍的,即該注解可以用于哪些程序元素。
    2. @Retention(value=“ ”):用于說明注解的生存周期
    3. @Documnent:用來說明指定被修飾的注解可以被javadoc.exe工具提取進入文檔中,所有使用了該注解進行標注的類在生成API文檔時都在包含該注解的說明。
    4. @Inherited:用來說明使用了該注解的父類,其子類會自動繼承該注解。
    5. @Repeatable:java1.8新出的元注解,如果需要在給程序元素使用相同類型的注解,則需將該注解標注上。
  • 自定義注解:用@Interface來聲明注解。

JAVA泛型?。?/h3>

Java 泛型是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個參數(shù)。

  • 泛型擦除(這是面試考察泛型時經(jīng)常問到的問題)

    Java的泛型基本上都是在編譯器這個層次上實現(xiàn)的,在生成的字節(jié)碼中是不包含泛型中的類型信息的,使用泛型的時候加上類型參數(shù),在編譯器編譯的時候會去掉,這個過程成為類型擦除。看下面代碼

    public class Main{
        public static void main(String[] args) {
            ArrayList<Integer> arrayList1 = new ArrayList<>();
            ArrayList<String> arrayList2 = new ArrayList<>();
    
            System.out.println(arrayList1.getClass() == arrayList2.getClass());
        }
    }
    

    輸出結(jié)果

    true
    

    可以看到ArrayList<Integer>ArrayList<String>的原始類型是相同,在編譯成字節(jié)碼文件后都會變成List,JVM看到的只有List,看不到泛型信息,這就是泛型的類型擦除。在看下面這段代碼

    public class Main{
        public static void main(String[] args) throws Exception {
            ArrayList<Integer> arrayList = new ArrayList<>();
            arrayList.add(1);
            arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, "a");
            System.out.println(arrayList.get(0));
            System.out.println(arrayList.get(1));
        }
    }
    

    輸出

    1
    a
    

    可以看到通過反射進行add操作,ArrayList<Integer>竟然可以存儲字符串,這是因為在反射就是在運行期調(diào)用的add方法,在運行期泛型信息已經(jīng)被擦除。

  • 既然存在類型擦除,那么Java是如何保證在ArrayList<Integer>添加字符串會報錯呢?

    Java編譯器是通過先檢查代碼中泛型的類型,然后在進行類型擦除,再進行編譯。

JAVA序列化?。?/h3>
  • 序列化:將對象寫入到IO流中

  • 反序列化:從IO流中恢復(fù)對象

  • 序列化的意義:將Java對象轉(zhuǎn)換成字節(jié)序列,這些字節(jié)序列更加便于通過網(wǎng)絡(luò)傳輸或存儲在磁盤上,在需要時可以通過反序列化恢復(fù)成原來的對象。

  • 實現(xiàn)方式:

    1. 實現(xiàn)Serializable接口
    2. 實現(xiàn)Externalizable接口
  • 序列化的注意事項:

    1. 對象的類名、實例變量會被序列化;方法、類變量、transient實例變量都不會被序列化。
    2. 某個變量不被序列化,可以使用transient修飾。
    3. 序列化對象的引用類型成員變量,也必須是可序列化的,否則,會報錯。
    4. 反序列化時必須有序列化對象的class文件。

深拷貝與淺拷貝?。?/h3>
  • 深拷貝:對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型,創(chuàng)建一個新的對象,并復(fù)制其內(nèi)容,兩個引用指向兩個對象,但對象內(nèi)容相同。

  • 淺拷貝:對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型復(fù)制一個引用指向原始引用的對象,就是復(fù)制的引用和原始引用指向同一個對象。

    具體區(qū)別看下圖


    在這里插入圖片描述
  • 深拷貝的實現(xiàn)方式

    1. 重載clone方法

       public class Main{
          public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, CloneNotSupportedException {
              Address s = new Address("天津");
              Person p = new Person("張三", 23, s);
              System.out.println("克隆前的地址:" + p.getAddress().getName());
              Person cloneP = (Person) p.clone();
              cloneP.getAddress().setName("北京");
              System.out.println("克隆后的地址:" + cloneP.getAddress().getName());
          }
      }
      
      class Address implements Cloneable {
          private String city;
          public Address(String name){
              this.city = name;
      }
          @Override
          protected Object clone() throws CloneNotSupportedException {
                  return super.clone();
          }
          public String getName() {
              return city;
      
          }
          public void setName(String name) {
              this.city = name;
      
          }
      }
      class Person implements Cloneable{
          private String name;
          private int age;
          private Address address;
          public Person(String name, int age, Address address){
              this.name = name;
              this.age = age;
              this.address = address;
      
          }
          @Override
          public Object clone() throws CloneNotSupportedException {
                Person  person = (Person) super.clone();
                person.address = (Address)address.clone();
              return person;
          }
          public String getName() {
              return name;
          }
          public void setName(String name) {
              this.name = name;
          }
          public int getAge() {
              return age;
          }
          public void setAge(int age) {
              this.age = age;
          }
          public Address getAddress() {
              return address;
          }
          public void setAddress(Address address) {
              this.address = address;
          }
      }
      

      輸出

      克隆前的地址:天津
      克隆后的地址:北京
      

      其實就是Person類和Address類都要重寫clone方法,這里面需要注意的一點是super.clone()為淺克隆,所以在在Person類中重寫clone方法時,address對象需要調(diào)用address.clone()重新賦值,因為address類型為引用類型。

    2. 序列化

      public class Main{
          public static void main(String[] args) throws IOException, ClassNotFoundException {
              Address s = new Address("天津");
              Person p = new Person("張三", 23, s);
              System.out.println("克隆前的地址:" + p.getAddress().getName());
              Person cloneP = (Person) p.deepClone();
              cloneP.getAddress().setName("北京");
              System.out.println("克隆后的地址:" + cloneP.getAddress().getName());
          }
      }
      
      class Address implements Serializable{
          private String city;
          public Address(String name){
              this.city = name;
          }
      
          public String getName() {
              return city;
      
          }
          public void setName(String name) {
              this.city = name;
      
          }
      }
      class Person implements Serializable{
          private String name;
          private int age;
          private Address address;
          public Person(String name, int age, Address address){
              this.name = name;
              this.age = age;
              this.address = address;
      
          }
      
          public Object deepClone() throws IOException, ClassNotFoundException {        
              ByteArrayOutputStream bos = new ByteArrayOutputStream();
              ObjectOutputStream oos = new ObjectOutputStream(bos);
              oos.writeObject(this);        
              ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
              ObjectInputStream ois = new ObjectInputStream(bis);
              return ois.readObject();
          }
      
          public String getName() {
              return name;
          }
          public void setName(String name) {
              this.name = name;
          }
          public int getAge() {
              return age;
          }
          public void setAge(int age) {
              this.age = age;
          }
          public Address getAddress() {
              return address;
          }
          public void setAddress(Address address) {
              this.address = address;
          }
      }
      

      輸出

      克隆前的地址:天津
      克隆后的地址:北京
      

常見的Object方法?。?/h3>

這些方法都很重要,面試經(jīng)常會問到,要結(jié)合其他知識將這些方法理解透徹

  • Object clone():創(chuàng)建與該對象的類相同的新對象
  • boolean equals(Object):比較兩對象是否相等
  • void finalize():當(dāng)垃圾回收器確定不存在對該對象的更多引用時,對象垃圾回收器調(diào)用該方法
  • Class getClass():返回一個對象運行時的實例類
  • int hashCode():返回該對象的散列碼值
  • void notify():喚醒等待在該對象的監(jiān)視器上的一個線程
  • void notifyAll():喚醒等待在該對象的監(jiān)視器上的全部線程
  • String toString():返回該對象的字符串表示
  • void wait():在其他線程調(diào)用此對象的 notify() 方法或 notifyAll()方法前,導(dǎo)致當(dāng)前線程等待

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容