主頁 > 後端開發 > Java基礎知識面試題(最詳細版)

Java基礎知識面試題(最詳細版)

2020-11-23 00:39:12 後端開發

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

目錄
  • JVM、JRE及JDK的關系 **
  • JAVA語言特點 **
  • JAVA和C++的區別 **
  • Java的基本資料型別  **
  • 隱式(自動)型別轉換和顯示(強制)型別轉換 **
  • 自動裝箱與拆箱 **
  • String(不是基本資料型別)
    • String的不可變性 ***
    • 字符型常量和字串常量的區別 *
    • 什么是字串常量池?*
    • String 類的常用方法都有那些?**
    • String和StringBuffer、StringBuilder的區別是什么?***
  • switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上 *
  • Java語言采用何種編碼方案?有何特點?*
  • 訪問修飾符 **
  • 運算子 *
  • 關鍵字
    • static關鍵字 ***
    • final 關鍵字 ***
    • final finally finalize區別 ***
    • this關鍵字 **
    • super關鍵字 **
    • this與super的區別 **
    • break ,continue ,return 的區別及作用 **
  • 面向物件和面向程序的區別 **
  • 面向物件三大特性(封裝、繼承、多型) ***
  • 面向物件五大基本原則是什么 **
  • 抽象類和介面的對比 ***
  • 在Java中定義一個不做事且沒有引數的構造方法的作用 *
  • 在呼叫子類構造方法之前會先呼叫父類沒有引數的構造方法,其目的是 *
  • 一個類的構造方法的作用是什么?若一個類沒有宣告構造方法,改程式能正確執行嗎?為什么? *
  • 構造方法有哪些特性? **
  • 變數 **
  • 內部類 **
  • 重寫與多載 ***
    • 多載和重寫的區別
    • 構造器(constructor)是否可被重寫(override)
    • 多載的方法能否根據回傳型別進行區分?為什么?
  • == 和 equals 的區別 ***
  • hashCode 與 equals(為什么重寫equals方法后,hashCode方法也必須重寫) ***
  • Java 中是值傳遞還是參考傳遞,還是兩者共存 **
  • IO流 *
  • BIO,NIO,AIO 有什么區別? **
  • 反射 ***
  • JAVA例外 ***
  • JAVA注解 **
  • JAVA泛型 ***
  • JAVA序列化 **
  • 深拷貝與淺拷貝 ***
  • 常見的Object方法 ***

JVM、JRE及JDK的關系 **

? JDK(Java Development Kit)是針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類別庫,

? Java Runtime Environment(JRE)是運行JAVA程式所必須的環境的集合,包含JVM標準實作及Java核心類別庫,

? JVM是Java Virtual Machine(Java虛擬機)的縮寫,是整個java實作跨平臺的最核心的部分,能夠運行以Java語言寫作的軟體程式,

? 簡單來說就是JDK是Java的開發工具,JRE是Java程式運行所需的環境,JVM是Java虛擬機.它們之間的關系是JDK包含JRE和JVM,JRE包含JVM.

JAVA語言特點 **

  • Java是一種面向物件的語言
  • Java通過Java虛擬機實作了平臺無關性,一次編譯,到處運行
  • 支持多執行緒
  • 支持網路編程
  • 具有較高的安全性和可靠性

JAVA和C++的區別 **

面試時記住前四個就行了

  • Java 通過虛擬機從而實作跨平臺特性,但是 C++ 依賴于特定的平臺,
  • Java 沒有指標,它的參考可以理解為安全指標,而 C++ 具有和 C 一樣的指標,
  • Java 支持自動垃圾回收,而 C++ 需要手動回收,
  • Java 不支持多重繼承,只能通過實作多個介面來達到相同目的,而 C++ 支持多重繼承,
  • Java 不支持運算子多載,雖然可以對兩個 String 物件執行加法運算,但是這是語言內置支持的操作,不屬于操
    作符多載,而 C++ 可以,
  • Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto,

Java的基本資料型別  **

注意String不是基本資料型別

型別 關鍵字 包裝器型別 占用記憶體(位元組)(重要) 取值范圍 默認值
位元組型 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

隱式(自動)型別轉換和顯示(強制)型別轉換 **

  • 隱式(自動)型別轉換:從存盤范圍小的型別到存盤范圍大的型別,byteshort(char)intlongfloatdouble
  • 顯示(強制)型別轉換:從存盤范圍大的型別到存盤范圍小的型別,doublefloatlongintshort(char)byte,該型別別轉換很可能存在精度的損失,

看一個經典的代碼

short s = 1;
s = s + 1;

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

做一下修改即可避免報錯

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

或這樣寫,因為s += 1會自動進行強制型別轉換

short s = 1;
s += 1;

自動裝箱與拆箱 **

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

  • 拆箱:將包裝器型別轉換為基本型別

    這個地方有很多易混淆的地方,但在面試中問到的頻率一般,筆試的選擇題中經常出現,還有一個String創建物件和這個比較像,很容易混淆,在下文可以看到

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

    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
    

    很多人看到這個結果會很疑惑,為什么會是一個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);
      }
    

    可以看到當不滿足if陳述句中的條件,就會重新創建一個物件回傳,那結果必然不相等,繼續打開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 =
                      https://www.cnblogs.com/zydybaby/archive/2020/11/23/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方法創建Integer物件的時候,如果數值在[-128,127]之間,便回傳指向IntegerCache.cache中已經存在的物件的參考;否則創建一個新的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 一個新的物件,所以上面代碼中的結果都不想等,

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

    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
    

    老方法繼續看valueOf方法

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

    再看看TRUEFALSE是個什么東西,是兩個靜態成員屬性,

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

**說下結論 **:IntegerShortByteCharacterLong這幾個類的valueOf方法的實作是類似的,DoubleFloatvalueOf方法的實作是類似的,然后是BooleanvalueOf方法是單獨一組的,

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

    這兩者的區別主要是第一種會觸發自動裝箱,第二者不會

    最后看看下面這段程式的輸出結果

    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
    

    下面簡單解釋這些結果,

    1.當 "=="運算子的兩個運算元都是包裝器型別的參考,則是比較指向的是否是同一個物件,而如果其中有一個運算元是運算式(即包含算術運算)則比較的是數值(即會觸發自動拆箱的程序),所以c==a+bg==a+btrue

    2.而對于equals方法會先觸發自動拆箱程序,再觸發自動裝箱程序,也就是說a+b,會先各自呼叫intValue方法,得到了加法運算后的數值之后,便呼叫Integer.valueOf方法,再進行equals比較,所以c.equals(a+b)true,而對于g.equals(a+b)a+b會先拆箱進行相加運算,在裝箱進行equals比較,但是裝箱后為IntegergLong,所以g.equals(a+b)false

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

    4.integer1 == integer2 -> falseinteger1integer2都是通過new關鍵字創建的,可以看成兩個物件,所以integer1 == integer2falseinteger3 == a1 -> false , integer3是一個物件型別,而a1是一個常量它們存放記憶體的位置不一樣,所以integer3 == a1false,具體原因可學習下java的記憶體模型,

String(不是基本資料型別)

String的不可變性 ***

在 Java 8 中,String 內部使用 char 陣列存盤資料,并且被宣告為final,因此它不可被繼承,

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

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

1.可以快取 hash 值()

? 因為 Stringhash值經常被使用,例如 String 用做 HashMapkey,不可變的特性可以使得 hash 值也不可變,
因此只需要進行一次計算,

2.常量池優化

? String 物件創建之后,會在字串常量池中進行快取,如果下次創建同樣的物件時,會直接回傳快取的參考,

3.執行緒安全

? String 不可變性天生具備執行緒安全,可以在多個執行緒中安全地使用,

字符型常量和字串常量的區別 *

  1. 形式上: 字符常量是單引號引起的一個字符 字串常量是雙引號引起的若干個字符
  2. 含義上: 字符常量相當于一個整形值(ASCII值),可以參加運算式運算 字串常量代表一個地址值(該字串在記憶體中存放位置)
  3. 占記憶體大小 字符常量占兩個位元組 字串常量占若干個位元組(至少一個字符結束標志)

什么是字串常量池?*

? 字串常量池位于堆記憶體中,專門用來存盤字串常量,可以提高記憶體的使用率,避免開辟多塊空間存盤相同的字串,在創建字串時 JVM 會首先檢查字串常量池,如果該字串已經存在池中,則回傳它的參考,如果不存在,則實體化一個字串放到池中,并回傳其參考,

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

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

  • length():回傳字串長度
  • charAt():回傳指定索引處的字符
  • substring():截取字串
  • trim():去除字串兩端空白
  • split():分割字串,回傳一個分割后的字串陣列,
  • replace():字串替換,
  • indexOf():回傳指定字符的索引,
  • toLowerCase():將字串轉成小寫字母,
  • toUpperCase():將字串轉成大寫字符,

String和StringBuffer、StringBuilder的區別是什么?***

? 1.可變性

? String不可變,StringBuilderStringBuffer是可變的

? 2.執行緒安全性

? String由于是不可變的,所以執行緒安全,StringBuffer對方法加了同步鎖或者對呼叫的方法加了同步鎖,所以是執行緒安全的, StringBuilder并沒有對方法進行加同步鎖,所以是非執行緒安全的,

? 3.性能

? StringBuilder > StringBuffer > String

為了方便記憶,總結如下

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

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

? switch可以作用于char byte short int及它們對應的包裝型別,switch不可作用于long double float boolean及他們的包裝型別,在 JDK1.5之后可以作用于列舉型別,在JDK1.7之后可作用于String型別,

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

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

訪問修飾符 **

? 在Java編程語言中有四種權限訪問控制符,這四種訪問權限的控制符能夠控制類中成員的可見性,其中類有兩種publicdefault,而方法和變數有 4 種:publicdefaultprotectedprivate

  • public : 對所有類可見,使用物件:類、介面、變數、方法

  • protected : 對同一包內的類和所有子類可見,使用物件:變數、方法, 注意:不能修飾類(外部類)

  • default : 在同一包內可見,不使用任何修飾符,使用物件:類、介面、變數、方法,

  • private : 在同一類內可見,使用物件:變數、方法, 注意:不能修飾類(外部類)

    修飾符 當前類 同包內 子類(同包) 其他包
    public Y Y Y Y
    protected Y Y Y N
    default Y Y Y N
    private Y N N N

運算子 *

  • &&和&

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

  • ||和|

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

關鍵字

static關鍵字 ***

? static關鍵字的主要用途就是方便在沒有創建物件時呼叫方法和變數和優化程式性能

? 1.static變數(靜態變數)

? 用static修飾的變數被稱為靜態變數,也被稱為類變數,可以直接通過類名來訪問它,靜態變數被所有的物件共享,在記憶體中只有一個副本,僅當在類初次加載時會被初始化,而非靜態變數在創建物件的時候被初始化,并且存在多個副本,各個物件擁有的副本互不影響,

? 2.static方法(靜態方法)

? static方法不依賴于任何物件就可以進行訪問,在static方法中不能訪問類的非靜態成員變數和非靜態成員方法,因為非靜態成員方法/變數都是必須依賴具體的物件才能夠被呼叫,但是在非靜態成員方法中是可以訪問靜態成員方法/變數的,

public class Main {
    public static String s1 = "s1";//靜態變數
    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);//此處報錯,靜態方法不能呼叫非靜態變數
    }
}

? 3.static代碼塊(靜態代碼塊)

? 靜態代碼塊的主要用途是可以用來優化程式的性能,因為它只會在類加載時加載一次,很多時候會將一些只需要進行一次的初始化操作都放在static代碼塊中進行,如果程式中有多個static塊,在類初次被加載的時候,會按照static塊的順序來執行每個static塊,

public class Main {
    static {
        System.out.println("hello,word");
    }
    public static void main(String[] args) {
        Main m = new Main();
    }
}

? 4.可以通過this訪問靜態成員變數嗎?(可以)

? this代表當前物件,可以訪問靜態變數,而靜態方法中是不能訪問非靜態變數,也不能使用this參考,

? 5.初始化順序

? 靜態變數和靜態陳述句塊優先于實體變數和普通陳述句塊,靜態變數和靜態陳述句塊的初始化順序取決于它們在代碼中的順序,如果存在繼承關系的話,初始化順序為父類中的靜態變數和靜態代碼塊——子類中的靜態變數和靜態代碼塊——父類中的實體變數和普通代碼塊——父類的建構式——子類的實體變數和普通代碼塊——子類的建構式

final 關鍵字 ***

? final關鍵字主要用于修飾類,變數,方法,

  1. 類:被final修飾的類不可以被繼承
  2. 方法:被final修飾的方法不可以被重寫
  3. 變數:被final修飾的變數是基本型別,變數的數值不能改變;被修飾的變數是參考型別,變數便不能在參考其他物件,但是變數所參考的物件本身是可以改變的,
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區別 ***

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

this關鍵字 **

? 重點掌握前三種即可

? 1.this關鍵字可用來參考當前類的實體變數,主要用于形參與成員名字重名,用this來區分,

public Person(String name, int age) {
    this.name = name;
    this.age = age;
}

? 2.this關鍵字可用于呼叫當前類方法,

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()可以用來呼叫當前類的建構式,(注意:this()一定要放在建構式的第一行,否則編譯不通過)

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關鍵字可作為呼叫方法中的引數傳遞,

? 5.this關鍵字可作為引數在建構式呼叫中傳遞,

? 6.this關鍵字可用于從方法回傳當前類的實體,super

super關鍵字 **

? 1.super可以用來參考直接父類的實體變數,和this類似,主要用于區分父類和子類中相同的欄位

? 2.super可以用來呼叫直接父類建構式,(注意:super()一定要放在建構式的第一行,否則編譯不通過)

? 3.super可以用來呼叫直接父類方法,

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);      //呼叫直接父類建構式
        this.name = name2;
    }

    public void test(){
        System.out.println(this.name);
        System.out.println(super.name);  //參考直接父類的實體變數
        super.Say();      //呼叫直接父類方法
    }
}

this與super的區別 **

  • 相同點:

    1. super()this()都必須在建構式的第一行進行呼叫,否則就是錯誤的
    2. this()super()都指的是物件,所以,均不可以在static環境中使用,
  • 不同點:

    1. super()主要是對父類建構式的呼叫,this()是對多載建構式的呼叫
    2. super()主要是在繼承了父類的子類的建構式中使用,是在不同類中的使用;this()主要是在同一類的不同建構式中的使用

break ,continue ,return 的區別及作用 **

  • break結束當前的回圈體
  • continue結束本次回圈,進入下一次回圈
  • return結束當前方法

面向物件和面向程序的區別 **

  • 面向程序

    優點:性能比面向物件高,因為類呼叫時需要實體化,開銷比較大,比較消耗資源,

    缺點:沒有面向物件易維護、易復用、易擴展

  • 面向物件

    優點:易維護、易復用、易擴展,由于面向物件有封裝、繼承、多型性的特性,可以設計出低耦合的系統,使系統更加靈活、更加易于維護

    缺點:性能比面向程序低

面向物件三大特性(封裝、繼承、多型) ***

  • 封裝

    封裝就是隱藏物件的屬性和實作細節,僅對外公開介面,控制在程式中屬性的讀和修改的訪問級別,

  • 繼承

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

  • 多型(重要)

    多型是同一個行為具有多個不同表現形式或形態的能力,這句話不是很好理解,可以看這個解釋,在Java語言中,多型就是指程式中定義的參考變數所指向的具體型別和通過該參考變數發出的方法呼叫在編程時并不確定,而是在程式運行期間才確定,即一個參考變數倒底會指向哪個類的實體物件,該參考變數發出的方法呼叫到底是哪個類中實作的方法,必須在由程式運行期間才能決定,

    在Java中實作多型的三個必要條件:繼承、重寫、向上轉型,繼承和重寫很好理解,向上轉型是指在多型中需要將子類的參考賦給父類物件,

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

    運行結果為

    Student
    

面向物件五大基本原則是什么 **

  • 單一職責原則(Single-Resposibility Principle)

    一個類,最好只做一件事,只有一個引起它的變化,單一職責原則可以看做是低耦合、高內聚在面向物件原則上的引申,將職責定義為引起變化的原因,以提高內聚性來減少引起變化的原因,

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

    軟體物體應該是可擴展的,而不可修改的,也就是,對擴展開放,對修改封閉的,

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

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

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

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

  • 介面隔離原則(Interface-Segregation Principle)

    使用多個小的專門的介面,而不要使用一個大的總介面,

抽象類和介面的對比 ***

? 在Java語言中,abstract classinterface是支持抽象類定義的兩種機制,抽象類:用來捕捉子類的通用特性的,介面:抽象方法的集合,

相同點:

  • 介面和抽象類都不能實體化
  • 都包含抽象方法,其子類都必須覆寫這些抽象方法

不同點:

型別 抽象類 介面
定義 abstract class Interface
實作 extends(需要提供抽象類中所有宣告的方法的實作) implements(需要提供介面中所有宣告的方法的實作)
繼承 抽象類可以繼承一個類和實作多個介面;子類只可以繼承一個抽象類 介面只可以繼承介面(一個或多個);子類可以實作多個介面
訪問修飾符 抽象方法可以有public、protected和default這些修飾符 介面方法默認修飾符是public,你不可以使用其它修飾符
構造器 抽象類可以有構造器 介面不能有構造器
欄位宣告 抽象類的欄位宣告可以是任意的 介面的欄位默認都是 static 和 final 的

在Java中定義一個不做事且沒有引數的構造方法的作用 *

? Java程式存在繼承,在執行子類的構造方法時,如果沒有用super()來呼叫父類特定的構造方法,則會呼叫父類中“沒有引數的構造方法”,如果父類只定義了有引數的建構式,而子類的建構式沒有用super呼叫父類那個特定的建構式,就會出錯,

在呼叫子類構造方法之前會先呼叫父類沒有引數的構造方法,其目的是 *

? 幫助子類做初始化作業,

一個類的構造方法的作用是什么?若一個類沒有宣告構造方法,改程式能正確執行嗎?為什么? *

? 主要作用是完成對類物件的初始化作業,可以執行,因為一個類即使沒有宣告構造方法也會有默認的不帶引數的構造方法,

構造方法有哪些特性? **

  • 方法名稱和類同名
  • 不用定義回傳值型別
  • 不可以寫retrun陳述句
  • 構造方法可以被多載

變數 **

  • 類變數:獨立于方法之外的變數,用static修飾,

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

  • 區域變數:類的方法中的變數,

  • 成員變數:成員變數又稱全域變數,可分為類變數和實體變數,有static修飾為類變數,沒有static修飾為實體變數,

    類變數 實體變數 區域變數
    定義位置 類中,方法外 類中,方法外 方法中
    初始值 有默認初始值 有默認初始值 無默認初始值
    存盤位置 方法區 堆疊
    生命周期 類何時被加載和卸載 實體何時被創建及銷毀 方法何時被呼叫及結束呼叫

內部類 **

? 內部類包括這四種:成員內部類、區域內部類、匿名內部類和靜態內部類

  • 成員內部類

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

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

    2.當成員內部類擁有和外部類同名的成員變數或者方法時,即默認情況下訪問的是成員內部類的成員,如果要訪問外部類的同名成員,需要以下面的形式進行訪問:外部類.this.成員變數

    3.在外部類中如果要訪問成員內部類的成員,必須先創建一個成員內部類的物件,再通過指向這個物件的參考來訪問,

    4.成員內部類是依附外部類而存在的,如果要創建成員內部類的物件,前提是必須存在一個外部類的物件,創建成員內部類物件的一般方式如下:

    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();     //呼叫內部類的方法
        }
    
    
        class Inner {     //內部類
            int b = 2;
            public void fun() {
                System.out.println(a);
                System.out.println(b);            //訪問內部類的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(); //創建內部類的物件
        }
    }
    
  • 區域內部類

    區域內部類是定義在一個方法或者一個作用域里面的類,它和成員內部類的區別在于區域內部類的訪問僅限于方法內或者該作用域內,定義在實體方法中的區域類可以訪問外部類的所有變數和方法,定義在靜態方法中的區域類只能訪問外部類的靜態變數和方法,

    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(); //創建區域內部類
            inner.fun();
        }
        public static void test2(){
            int inner_d =3;
            class Inner {
                private void fun(){
                     System.out.println(outter_a); //編譯錯誤,定義在靜態方法中的區域類不可以訪問外部類的實體變數
                    System.out.println(static_b);
                    System.out.println(inner_d);
                }
            }
            Inner  inner = new Inner();
            inner.fun();
        }
    }
    
  • 匿名內部類

    匿名內部類只沒有名字的內部類,在日常開發中使用較多,使用匿名內部類的前提條件:必須繼承一個父類或實作一個介面,

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

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

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

    1. 內部類不為同一包的其他類所見,具有很好的封裝性;
    2. 匿名內部類可以很方便的定義回呼,
    3. 每個內部類都能獨立的繼承一個介面的實作,所以無論外部類是否已經繼承了某個(介面的)實作,對于內部類都沒有影響,
    4. 內部類有效實作了“多重繼承”,優化 java 單繼承的缺陷,
  • 區域內部類和匿名內部類訪問區域變數的時候,為什么變數必須要加上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可以從生命周期的角度理解,區域變數直接存盤在堆疊中,當方法執行結束后,非final的區域變數就被銷毀,而區域內部類對區域變數的參考依然存在,如果區域內部類要呼叫沒有final修飾的區域變數時,就會造成生命周期不一致出錯,

    對于變數b,其實是將fun方法中的變數b以引數的形式對匿名內部類中的拷貝(變數b的拷貝)進行賦值初始化,在run方法中訪問的變數b根本就不是test方法中的區域變數b,而是一個拷貝值,所以不存在生命周期不一致的問題,但如果在run方法中修改變數b的值會導致資料不一致,所以需要加final修飾,

重寫與多載 ***

多載和重寫的區別

  • 多載:發生在同一個類中,方法名相同引數串列不同(引數型別不同、個數不同、順序不同),與方法回傳值和訪問修飾符無關,即多載的方法不能根據回傳型別進行區分,
  • 重寫:發生在父子類中,方法名、引數串列必須相同,回傳值小于等于父類,拋出的例外小于等于父類,訪問修飾符大于等于父類(里氏代換原則);如果父類方法訪問修飾符為private則子類中就不是重寫,

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

構造器可以被多載,不能被重寫

多載的方法能否根據回傳型別進行區分?為什么?

不能,因為呼叫時不能指定型別資訊,編譯器不知道你要呼叫哪個函式,

== 和 equals 的區別 ***

  • ==

    對于基本資料型別,==比較的是值;對于參考資料型別,==比較的是記憶體地址,

  • eauals

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

hashCode 與 equals(為什么重寫equals方法后,hashCode方法也必須重寫) ***

  • 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方法首先比較的是記憶體地址,如果記憶體地址相同,直接回傳true;如果記憶體地址不同,再比較物件的型別,型別不同直接回傳false;型別相同,再比較值是否相同;值相同回傳true,值不同回傳false,總結一下,equals會比較記憶體地址、物件型別、以及值,記憶體地址相同,equals一定回傳true;物件型別和值相同,equals方法一定回傳true
    2. 如果沒有重寫equals方法,那么equals==的作用相同,比較的是物件的地址值,
  • hashCode

    hashCode方法回傳物件的散列碼,回傳值是int型別的散列碼,散列碼的作用是確定該物件在哈希表中的索引位置,

    關于hashCode有一些約定:

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

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

Java 中是值傳遞還是參考傳遞,還是兩者共存 **

這是一個很容易搞混又很難解釋清楚的問題,先說結論,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,但方法呼叫結束后,a的值還是1,并未發生改變,所以這種情況下為值傳遞,

再看這段代碼

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;
}

輸出結果

q.name: lisi
p.name: lisi

在將p傳入printValue方法后,方法呼叫結束,pname屬性竟然被改變了!所以得出結論,引數為基本型別為值傳遞,引數為參考型別為時為參考傳遞,這個結論是錯誤的,下面來看看判斷是值傳遞還是值傳遞的關鍵是什么,先看定義

  • 值傳遞:是指在呼叫函式時將實際引數復制一份傳遞到函式中,這樣在函式中如果對引數進行修改,將不會影響到實際引數,
  • 參考傳遞:是指在呼叫函式時將實際引數的地址直接傳遞到函式中,那么在函式中對引數所進行的修改,將影響到實際引數,

從定義中可以明顯看出,區分是值傳遞還是參考傳遞主要是看向方法中傳遞的是實際引數的副本還是實際引數的地址,上面第一個例子很明顯是值傳遞,其實第二個例子中向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;
}

輸出結果

q.name: lisi
p.name: zhangsan

可以看到將p傳入printValue方法后,printValue方法呼叫結束后,p的屬性name沒有改變,這是因為printValue方法中并沒有改變副本參考q所指向的物件,而是改變了副本參考q本身,將副本參考q指向了另一個物件并對這個物件的屬性進行修改,所以原始參考p所指向的物件不受影響,所以證明Java中只存在值傳遞,

IO流 *

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

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

  • InputStream:位元組輸入流
  • Reader:字符輸入流
  • OutputStream:位元組輸出流
  • Writer:字符輸出流

BIO,NIO,AIO 有什么區別? **

  • BIO (Blocking I/O):服務器實作模式為一個連接一個執行緒,即客戶端有連接請求時服務器就需要啟動一個執行緒進行處理,如果這個連接不做任何事情會造成不必要的執行緒開銷,可以通過執行緒池機制來改善,BIO方式適用于連接數目比較小且固定的架構,這種方式對服務端資源要求比較高,并發局限于應用中,在jdk1.4以前是唯一的io

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

  • AIO (Asynchronous I/O):服務器實作模式為一個有效請求一個執行緒,客戶端的IO請求都是由作業系統先完成了再通知服務器用其啟動執行緒進行處理,AIO方式適用于連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分呼叫OS參與并發操作,編程比較復雜,jdk1.7開始支持,

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

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

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

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

反射 ***

JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為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 實體,所以對c1c2c3進行equals比較時回傳的都是true

反射優缺點:

  • 優點:運行期型別的判斷,動態加載類,提高代碼靈活度,
  • 缺點:性能比直接的java代碼要慢很多,

反射應用場景:

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

JAVA例外 ***

例外主要分為ErrorException兩種

  • Error:Error類以及他的子類的實體,代表了JVM本身的錯誤,錯誤不能被程式員通過代碼處理,
  • EXception:Exception以及他的子類,代表程式運行時發送的各種不期望發生的事件,可以被Java例外處理機制使用,是例外處理的核心,

例外框圖
在這里插入圖片描述

除了以上的分類,例外還能分為非檢查例外和檢查例外

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

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

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
}

如果沒有仔細的研究過,應該好多會猜錯,下面總結下規律,

  1. 從前三個例子可以看出如果try{}中的代碼沒有例外,catch(){}代碼塊中的代碼不會執行,所以如果try{}catch(){}都含有return時,無例外執行try{}中的return,存在例外執行catch(){}return
  2. 不管任何情況,就算try{}catch(){}中含有returnfinally{}中的代碼一定會執行,那么為什么test1test2test3中的結果不是4呢,因為雖然finally是在return后面的運算式運算之后執行的,但此時并沒有回傳運算之后的值,而是把值保存起來,不管finally對該值做任何的改變,回傳的值都不會改變,依然回傳保存起來的值,也就是說方法的回傳值是在finally運算之前就確定了的,
  3. 如果return的資料是參考資料型別,而在finally中對該參考資料型別的屬性值的改變起作用,try中的return陳述句回傳的就是在finally中改變后的該屬性的值,這個不理解可以看看上面提到的Java的值傳遞的問題,
  4. 如果finally{}中含有return,會導致程式提前退出,不在執行try{}catch(){}中的return,所以test5回傳的值是4,

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

  1. 先執行try中的陳述句,包括return后面的運算式;
  2. 有例外時,執行catch中的陳述句,包括return后面的運算式,無例外跳過catch陳述句;
  3. 然后執行finally中的陳述句,如果finally里面有return陳述句,執行return陳述句,程式結束;
  4. finally{}中沒有return時,無例外執行try中的return,如果有例外時則執行catch中的return,前兩步執行的return只是確定回傳的值,程式并未結束,finally{}執行之后,最后將前兩步確定的return的回傳值回傳,

JAVA注解 **

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

Annotation 注解可以看成是java中的一種標記記號,用來給java中的類,成員,方法,引數等任何程式元素添加一些額外的說明資訊,同時不改變程式語意,注解可以分為三類:基本注解,元注解,自定義注解

  • 標準注解

    1. @Deprecated:該注解用來說明程式中的某個元素(類,方法,成員變數等)已經不再使用,如果使用的話的編譯器會給出警告,
    2. @SuppressWarnings(value=https://www.cnblogs.com/zydybaby/archive/2020/11/23/“”):用來抑制各種可能出現的警告,
    3. @Override:用來說明子類方法覆寫了父類的方法,保護覆寫方法的正確使用
  • 元注解(元注解也稱為元資料注解,是對注解進行標注的注解,元注解更像是一種對注解的規范說明,用來對定義的注解進行行為的限定,例如說明注解的生存周期,注解的作用范圍等)

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

JAVA泛型 ***

Java 泛型是 JDK 5 中引入的一個新特性, 泛型提供了編譯時型別安全檢測機制,該機制允許程式員在編譯時檢測到非法的型別,泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數,

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

    Java的泛型基本上都是在編譯器這個層次上實作的,在生成的位元組碼中是不包含泛型中的型別資訊的,使用泛型的時候加上型別引數,在編譯器編譯的時候會去掉,這個程序成為型別擦除,看下面代碼

    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());
        }
    }
    

    輸出結果

    true
    

    可以看到ArrayList<Integer> ArrayList<String>的原始型別是相同,在編譯成位元組碼檔案后都會變成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>竟然可以存盤字串,這是因為在反射就是在運行期呼叫的add方法,在運行期泛型資訊已經被擦除,

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

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

JAVA序列化 **

  • 序列化:將物件寫入到IO流中

  • 反序列化:從IO流中恢復物件

  • 序列化的意義:將Java物件轉換成位元組序列,這些位元組序列更加便于通過網路傳輸或存盤在磁盤上,在需要時可以通過反序列化恢復成原來的物件,

  • 實作方式:

    1. 實作Serializable介面
    2. 實作Externalizable介面
  • 序列化的注意事項:

    1. 物件的類名、實體變數會被序列化;方法、類變數、transient實體變數都不會被序列化,
    2. 某個變數不被序列化,可以使用transient修飾,
    3. 序列化物件的參考型別成員變數,也必須是可序列化的,否則,會報錯,
    4. 反序列化時必須有序列化物件的class檔案,

深拷貝與淺拷貝 ***

  • 深拷貝:對基本資料型別進行值傳遞,對參考資料型別,創建一個新的物件,并復制其內容,兩個參考指向兩個物件,但物件內容相同,

  • 淺拷貝:對基本資料型別進行值傳遞,對參考資料型別復制一個參考指向原始參考的物件,就是復制的參考和原始參考指向同一個物件,

    具體區別看下圖
    在這里插入圖片描述

  • 深拷貝的實作方式

    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物件需要呼叫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方法 ***

這些方法都很重要,面試經常會問到,要結合其他知識將這些方法理解透徹

  • Object clone():創建與該物件的類相同的新物件
  • boolean equals(Object):比較兩物件是否相等
  • void finalize():當垃圾回收器確定不存在對該物件的更多參考時,物件垃圾回收器呼叫該方法
  • Class getClass():回傳一個物件運行時的實體類
  • int hashCode():回傳該物件的散列碼值
  • void notify():喚醒等待在該物件的監視器上的一個執行緒
  • void notifyAll():喚醒等待在該物件的監視器上的全部執行緒
  • String toString():回傳該物件的字串表示
  • void wait():在其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法前,導致當前執行緒等待

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/226505.html

標籤:其他

上一篇:請教一個關于執行緒訪問共享變數的問題

下一篇:前端-CSS

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more