爲了更好的總結Java面試中的系統知識結構,本文根據如下資料整理學習筆記。java
在 Java 語言中,當實例化對象時,對象所在類的全部成員變量首先要進行初始化,只有當全部類成員完成初始化後,纔會調用對象所在類的構造函數建立象。mysql
初始化通常遵循3個原則:c++
加載順序git
實例程序員
class Base { // 1.父類靜態代碼塊 static { System.out.println("Base static block!"); } // 3.父類非靜態代碼塊 { System.out.println("Base block"); } // 4.父類構造器 public Base() { System.out.println("Base constructor!"); } } public class Derived extends Base { // 2.子類靜態代碼塊 static{ System.out.println("Derived static block!"); } // 5.子類非靜態代碼塊 { System.out.println("Derived block!"); } // 6.子類構造器 public Derived() { System.out.println("Derived constructor!"); } public static void main(String[] args) { new Derived(); } }
結果是:github
Base static block! Derived static block! Base block Base constructor! Derived block! Derived constructor!
參考資料:web
首先看一個在知乎上的優秀回答吧:面試
反射是什麼呢?當咱們的程序在運行時,須要動態的加載一些類這些類可能以前用不到因此不用加載到 JVM,而是在運行時根據須要才加載,這樣的好處對於服務器來講不言而喻。算法
舉個例子咱們的項目底層有時是用 mysql,有時用 oracle,須要動態地根據實際狀況加載驅動類,這個時候反射就有用了,假設 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection 這兩個類咱們要用,這時候咱們的程序就寫得比較動態化,經過Class tc = Class.forName("com.java.dbtest.TestConnection"); 經過類的全類名讓 JVM 在服務器中找到並加載這個類,而若是是 oracle 則傳入的參數就變成另外一個了。這時候就能夠看到反射的好處了,這個動態性就體現出 Java 的特性了!
舉多個例子,你們若是接觸過spring,會發現當你配置各類各樣的bean時,是以配置文件的形式配置的,你須要用到哪些bean就配哪些,spring容器就會根據你的需求去動態加載,你的程序就能健壯地運行。
反射 (Reflection) 是 Java 程序開發語言的特徵之一,它容許運行中的 Java 程序獲取自身的信息,而且能夠操做類或對象的內部屬性。經過 Class 獲取 class 信息稱之爲反射(Reflection)
簡而言之,經過反射,咱們能夠在運行時得到程序或程序集中每個類型的成員和成員的信息。
程序中通常的對象的類型都是在編譯期就肯定下來的,而 Java 反射機制能夠動態地建立對象並調用其屬性,這樣的對象的類型在編譯期是未知的。因此咱們能夠經過反射機制直接建立對象,即便這個對象的類型在編譯期是未知的。
反射的核心是 JVM 在運行時才動態加載類或調用方法/訪問屬性,它不須要事先(寫代碼的時候或編譯期)知道運行對象是誰。
Java 反射框架主要提供如下功能:
重點:是運行時而不是編譯時
不少人都認爲反射在實際的Java開發應用中並不普遍,其實否則。
當咱們在使用IDE(如Eclipse,IDEA)時,當咱們輸入一個對象或類並想調用它的屬性或方法時,一按點號,編譯器就會自動列出它的屬性或方法,這裏就會用到反射。
反射最重要的用途就是開發各類通用框架
不少框架(好比Spring)都是配置化的(好比經過XML文件配置JavaBean,Action之類的),爲了保證框架的通用性,它們可能須要根據配置文件加載不一樣的對象或類,調用不一樣的方法,這個時候就必須用到反射——運行時動態加載須要加載的對象。
對與框架開發人員來講,反射雖小但做用很是大,它是各類容器實現的核心。而對於通常的開發者來講,不深刻框架開發則用反射用的就會少一點,不過了解一下框架的底層機制有助於豐富本身的編程思想,也是頗有益的。
.class
屬性Class clazz1 = Person.class; System.out.println(clazz1.getName());
getClass();
Person p = new Person(); Class clazz3 = p.getClass(); System.out.println(clazz3.getName());
forName
靜態方法public static Class<?> forName(String className) // 在JDBC開發中經常使用此方法加載數據庫驅動: Class.forName(driver);
ClassLoader classLoader = this.getClass().getClassLoader(); Class clazz5 = classLoader.loadClass(className); System.out.println(clazz5.getName());
參考資料:
Annontation 是 Java5 開始引入的新特徵,中文名稱叫註解。它提供了一種安全的相似註釋的機制,用來將任何的信息或元數據(metadata)與程序元素(類、方法、成員變量等)進行關聯。爲程序的元素(類、方法、成員變量)加上更直觀更明瞭的說明,這些說明信息是與程序的業務邏輯無關,而且供指定的工具或框架使用。Annontation 像一種修飾符同樣,應用於包、類型、構造方法、方法、成員變量、參數及本地變量的聲明語句中。
Java 註解是附加在代碼中的一些元信息,用於一些工具在編譯、運行時進行解析和使用,起到說明、配置的功能。註解不會也不能影響代碼的實際邏輯,僅僅起到輔助性的做用。包含在 java.lang.annotation
包中。
簡單來講:註解其實就是代碼中的特殊標記,這些標記能夠在編譯、類加載、運行時被讀取,並執行相對應的處理。
傳統的方式,咱們是經過配置文件 .xml
來告訴類是如何運行的。
有了註解技術之後,咱們就能夠經過註解告訴類如何運行
例如:咱們之前編寫 Servlet 的時候,須要在 web.xml 文件配置具體的信息。咱們使用了註解之後,能夠直接在 Servlet 源代碼上,增長註解...Servlet 就被配置到 Tomcat 上了。也就是說,註解能夠給類、方法上注入信息。
明顯地能夠看出,這樣是很是直觀的,而且 Servlet 規範是推崇這種配置方式的。
在 java.lang 包下存在着5個基本的 Annotation,重點掌握前三個。
@Deprecated public String toLocaleString() { DateFormat formatter = DateFormat.getDateTimeInstance(); return formatter.format(this); }
import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 水果名稱註解 */ @Target(FIELD) @Retention(RUNTIME) @Documented public @interface FruitName { String value() default ""; }
通俗的講,泛型就是操做類型的 佔位符,即:假設佔位符爲 T,那麼這次聲明的數據結構操做的數據類型爲T類型。
假定咱們有這樣一個需求:寫一個排序方法,可以對整型數組、字符串數組甚至其餘任何類型的數組進行排序,該如何實現?答案是能夠使用 Java 泛型。
使用 Java 泛型的概念,咱們能夠寫一個泛型方法來對一個對象數組排序。而後,調用該泛型方法來對整型數組、浮點數數組、字符串數組等進行排序。
你能夠寫一個泛型方法,該方法在調用時能夠接收不一樣類型的參數。根據傳遞給泛型方法的參數類型,編譯器適當地處理每個方法調用。
下面是定義泛型方法的規則:
public class GenericMethodTest { // 泛型方法 printArray public static < E > void printArray( E[] inputArray ) { // 輸出數組元素 for ( E element : inputArray ){ System.out.printf( "%s ", element ); } System.out.println(); } public static void main( String args[] ) { // 建立不一樣類型數組: Integer, Double 和 Character Integer[] intArray = { 1, 2, 3, 4, 5 }; Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 }; Character[] charArray = { 'H', 'E', 'L', 'L', 'O' }; System.out.println( "整型數組元素爲:" ); printArray( intArray ); // 傳遞一個整型數組 System.out.println( "\n雙精度型數組元素爲:" ); printArray( doubleArray ); // 傳遞一個雙精度型數組 System.out.println( "\n字符型數組元素爲:" ); printArray( charArray ); // 傳遞一個字符型數組 } }
泛型類的聲明和非泛型類的聲明相似,除了在類名後面添加了類型參數聲明部分。
和泛型方法同樣,泛型類的類型參數聲明部分也包含一個或多個類型參數,參數間用逗號隔開。一個泛型參數,也被稱爲一個類型變量,是用於指定一個泛型類型名稱的標識符。由於他們接受一個或多個參數,這些類被稱爲參數化的類或參數化的類型。
public class Box<T> { private T t; public void add(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { Box<Integer> integerBox = new Box<Integer>(); Box<String> stringBox = new Box<String>(); integerBox.add(new Integer(10)); stringBox.add(new String("菜鳥教程")); System.out.printf("整型值爲 :%d\n\n", integerBox.get()); System.out.printf("字符串爲 :%s\n", stringBox.get()); } }
?
代替具體的類型參數。例如 List<?>
在邏輯上是 List<String>
,List<Integer>
等全部 List <具體類型實參>
的父類。參考資料:
理解編碼的關鍵,是要把字符的概念和字節的概念理解準確。這兩個概念容易混淆,咱們在此作一下區分:
類型 | 概念描述 | 舉例 |
---|---|---|
字符 | 人們使用的記號,抽象意義上的一個符號。 | '1', '中', 'a', '$', '¥', …… |
字節 | 計算機中存儲數據的單元,一個 8 位的二進制數,是一個很具體的存儲空間。 | 0x01, 0x45, 0xFA, …… |
ANSI 字符串 | 在內存中,若是「字符」是以 ANSI 編碼形式存在的,一個字符可能使用一個字節或多個字節來表示,那麼咱們稱這種字符串爲 ANSI 字符串或者多字節字符串。 | "中文123" (佔7字節) |
UNICODE 字符串 | 在內存中,若是「字符」是以在 UNICODE 中的序號存在的,那麼咱們稱這種字符串爲 UNICODE 字符串或者寬字節字符串。 | L"中文123" (佔10字節) |
字節與字符區別
它們徹底不是一個位面的概念,因此二者之間沒有「區別」這個說法。不一樣編碼裏,字符和字節的對應關係不一樣:
類型 | 概念描述 |
---|---|
ASCII | 一個英文字母(不分大小寫)佔一個字節的空間,一箇中文漢字佔兩個字節的空間。一個二進制數字序列,在計算機中做爲一個數字單元,通常爲8位二進制數,換算爲十進制。最小值0,最大值255。 |
UTF-8 | 一個英文字符等於一個字節,一箇中文(含繁體)等於三個字節 |
Unicode | 一個英文等於兩個字節,一箇中文(含繁體)等於兩個字節。符號:英文標點佔一個字節,中文標點佔兩個字節。舉例:英文句號「.」佔1個字節的大小,中文句號「。」佔2個字節的大小。 |
UTF-16 | 一個英文字母字符或一個漢字字符存儲都須要2個字節(Unicode擴展區的一些漢字存儲須要4個字節) |
UTF-32 | 世界上任何字符的存儲都須要4個字節 |
參考資料:
Java面向對象的基本思想之一是封裝細節而且公開接口。Java語言採用訪問控制修飾符來控制類及類的方法和變量的訪問權限,從而向使用者暴露接口,但隱藏實現細節。訪問控制分爲四種級別:
修飾符 | 當前類 | 同 包 | 子 類 | 其餘包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
參考資料:
跟上Java8 - 瞭解lambda
https://zhuanlan.zhihu.com/p/28093333
Java 中字符串對象建立有兩種形式,一種爲字面量形式,如 String str = "droid";
,另外一種就是使用 new 這種標準的構造對象的方法,如 String str = new String("droid");
,這兩種方式咱們在代碼編寫時都常用,尤爲是字面量的方式。然而這兩種實現其實存在着一些性能和內存佔用的差異。這一切都是源於 JVM 爲了減小字符串對象的重複建立,其維護了一個特殊的內存,這段內存被成爲字符串常量池或者字符串字面量池。
工做原理
當代碼中出現字面量形式建立字符串對象時,JVM首先會對這個字面量進行檢查,若是字符串常量池中存在相同內容的字符串對象的引用,則將這個引用返回,不然新的字符串對象被建立,而後將這個引用放入字符串常量池,並返回該引用。
public class Test { public static void main(String[] args) { String s1 = "abc"; String s2 = "abc"; // 以上兩個局部變量都存在了常量池中 System.out.println(s1 == s2); // true // new出來的對象不會放到常量池中,內存地址是不一樣的 String s3 = new String(); String s4 = new String(); /** * 字符串的比較不能夠使用雙等號,這樣會比較內存地址 * 字符串比較應當用equals,可見String重寫了equals */ System.out.println(s3 == s4); // false System.out.println(s3.equals(s4)); // true } }
能夠將一個類的定義放在另外一個類的定義內部,這就是內部類。
在 Java 中內部類主要分爲成員內部類、局部內部類、匿名內部類、靜態內部類
成員內部類也是最普通的內部類,它是外圍類的一個成員,因此他是能夠無限制的訪問外圍類的全部成員屬性和方法,儘管是private的,可是外圍類要訪問內部類的成員屬性和方法則須要經過內部類實例來訪問。
public class OuterClass { private String str; public void outerDisplay(){ System.out.println("outerClass..."); } public class InnerClass{ public void innerDisplay(){ str = "chenssy..."; //使用外圍內的屬性 System.out.println(str); outerDisplay(); //使用外圍內的方法 } } // 推薦使用getxxx()來獲取成員內部類,尤爲是該內部類的構造函數無參數時 public InnerClass getInnerClass(){ return new InnerClass(); } public static void main(String[] args) { OuterClass outer = new OuterClass(); OuterClass.InnerClass inner = outer.getInnerClass(); inner.innerDisplay(); } } -------------------- chenssy... outerClass...
在成員內部類中要注意兩點:
有這樣一種內部類,它是嵌套在方法和做用於內的,對於這個類的使用主要是應用與解決比較複雜的問題,想建立一個類來輔助咱們的解決方案,到那時又不但願這個類是公共可用的,因此就產生了局部內部類,局部內部類和成員內部類同樣被編譯,只是它的做用域發生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會失效。
//定義在方法裏: public class Parcel5 { public Destionation destionation(String str){ class PDestionation implements Destionation{ private String label; private PDestionation(String whereTo){ label = whereTo; } public String readLabel(){ return label; } } return new PDestionation(str); } public static void main(String[] args) { Parcel5 parcel5 = new Parcel5(); Destionation d = parcel5.destionation("chenssy"); } } //定義在做用域內: public class Parcel6 { private void internalTracking(boolean b){ if(b){ class TrackingSlip{ private String id; TrackingSlip(String s) { id = s; } String getSlip(){ return id; } } TrackingSlip ts = new TrackingSlip("chenssy"); String string = ts.getSlip(); } } public void track(){ internalTracking(true); } public static void main(String[] args) { Parcel6 parcel6 = new Parcel6(); parcel6.track(); } }
匿名內部類也就是沒有名字的內部類。正由於沒有名字,因此匿名內部類只能使用一次,它一般用來簡化代碼編寫。但使用匿名內部類還有個前提條件:必須繼承一個父類或實現一個接口
實例1:不使用匿名內部類來實現抽象方法
abstract class Person { public abstract void eat(); } class Child extends Person { public void eat() { System.out.println("eat something"); } } public class Demo { public static void main(String[] args) { Person p = new Child(); p.eat(); } }
運行結果:eat something
能夠看到,咱們用 Child 繼承了 Person 類,而後實現了 Child 的一個實例,將其向上轉型爲 Person 類的引用
可是,若是此處的 Child 類只使用一次,那麼將其編寫爲獨立的一個類豈不是很麻煩?
這個時候就引入了匿名內部類
實例2:匿名內部類的基本實現
abstract class Person { public abstract void eat(); } public class Demo { public static void main(String[] args) { Person p = new Person() { public void eat() { System.out.println("eat something"); } }; p.eat(); } }
運行結果:eat something
能夠看到,咱們直接將抽象類 Person 中的方法在大括號中實現了,這樣即可以省略一個類的書寫,而且,匿名內部類還能用於接口上。
實例3:在接口上使用匿名內部類
interface Person { public void eat(); } public class Demo { public static void main(String[] args) { Person p = new Person() { public void eat() { System.out.println("eat something"); } }; p.eat(); } }
運行結果:eat something
由上面的例子能夠看出,只要一個類是抽象的或是一個接口,那麼其子類中的方法均可以使用匿名內部類來實現
最經常使用的狀況就是在多線程的實現上,由於要實現多線程必須繼承 Thread 類或是繼承 Runnable 接口
實例4:Thread類的匿名內部類實現
public class Demo { public static void main(String[] args) { Thread t = new Thread() { public void run() { for (int i = 1; i <= 5; i++) { System.out.print(i + " "); } } }; t.start(); } }
運行結果:1 2 3 4 5
實例5:Runnable接口的匿名內部類實現
public class Demo { public static void main(String[] args) { Runnable r = new Runnable() { public void run() { for (int i = 1; i <= 5; i++) { System.out.print(i + " "); } } }; Thread t = new Thread(r); t.start(); } }
運行結果:1 2 3 4 5
關鍵字 static 中提到 static 能夠修飾成員變量、方法、代碼塊,其餘它還能夠修飾內部類,使用 static 修飾的內部類咱們稱之爲靜態內部類,不過咱們更喜歡稱之爲嵌套內部類。靜態內部類與非靜態內部類之間存在一個最大的區別,咱們知道非靜態內部類在編譯完成以後會隱含地保存着一個引用,該引用是指向建立它的外圍內,可是靜態內部類卻沒有。
public class OuterClass { private String sex; public static String name = "chenssy"; // 靜態內部類 static class InnerClass1{ // 在靜態內部類中能夠存在靜態成員 public static String _name1 = "chenssy_static"; public void display(){ // 靜態內部類只能訪問外圍類的靜態成員變量和方法 // 不能訪問外圍類的非靜態成員變量和方法 System.out.println("OutClass name :" + name); } } // 非靜態內部類 class InnerClass2{ // 非靜態內部類中不能存在靜態成員 public String _name2 = "chenssy_inner"; // 非靜態內部類中能夠調用外圍類的任何成員,無論是靜態的仍是非靜態的 public void display(){ System.out.println("OuterClass name:" + name); } } // 外圍類方法 public void display(){ // 外圍類訪問靜態內部類:內部類 System.out.println(InnerClass1._name1); // 靜態內部類 能夠直接建立實例不須要依賴於外圍類 new InnerClass1().display(); // 非靜態內部的建立須要依賴於外圍類 OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2(); // 方位非靜態內部類的成員須要使用非靜態內部類的實例 System.out.println(inner2._name2); inner2.display(); } public static void main(String[] args) { OuterClass outer = new OuterClass(); outer.display(); } } ---------------- Output: chenssy_static OutClass name :chenssy chenssy_inner OuterClass name:chenssy
組合:各部件之間沒什麼關係,只須要組合便可。例如組裝電腦,須要 new CPU(),new RAM(),new Disk()
public class Computer { public Computer() { CPU cpu=new CPU(); RAM ram=new RAM(); Disk disk=new Disk(); } } class CPU{ } class RAM{ } class Disk{ }
繼承:子類須要具備父類的功能,各子類之間有所差別。例如 Shape 類做爲父類,子類有 Rectangle,CirCle,Triangle……代碼不寫了,你們都常常用。
代理:飛機控制類,我不想暴露太多飛機控制的功能,只需部分前進左右轉的控制(而不須要暴露發射導彈功能)。經過在代理類中 new 一個飛機控制對象,而後在方法中添加飛機控制類的各個須要暴露的功能。
public class PlaneDelegation{ private PlaneControl planeControl; //private外部不可訪問 // 飛行員權限代理類,普通飛行員不能夠開火 PlaneDelegation(){ planeControl = new PlaneControl(); } public void speed(){ planeControl.speed(); } public void left(){ planeControl.left(); } public void right(){ planeControl.right(); } } final class PlaneControl {// final表示不可繼承,控制器都能繼承那還得了 protected void speed() {} protected void fire() {} protected void left() {} protected void right() {} }
說明:
構造函數是函數的一種特殊形式。特殊在哪裏?構造函數中不須要定義返回類型(void 是無需返回值的意思,請注意區分二者),且構造函數的名稱與所在的類名徹底一致,其他的與函數的特性相同,能夠帶有參數列表,能夠存在函數的重載現象。
通常用來初始化一些成員變量,當要生成一個類的對象(實例)的時候就會調用類的構造函數。若是不顯示聲明類的構造方法,會自動生成一個默認的不帶參數的空的構造函數。
public class Demo{ private int num=0; //無參構造函數 Demo() { System.out.println("constractor_run"); } //有參構造函數 Demo(int num) { System.out.println("constractor_args_run"); } //普通成員函數 public void demoFunction() { System.out.println("function_run"); } }
在這裏要說明一點,若是在類中咱們不聲明構造函數,JVM 會幫咱們默認生成一個空參數的構造函數;若是在類中咱們聲明瞭帶參數列表的構造函數,JVM 就不會幫咱們默認生成一個空參數的構造函數,咱們想要使用空參數的構造函數就必須本身去顯式的聲明一個空參的構造函數。
構造函數的做用
經過開頭的介紹,構造函數的輪廓已經漸漸清晰,那麼爲何會有構造函數呢?構造函數有什麼做用?構造函數是面向對象編程思想所需求的,它的主要做用有如下兩個:
父類引用能指向子類對象,子類引用不能指向父類對象;
向上造型
父類引用指向子類對象,例如:
Father f1 = new Son();
向下造型
把指向子類對象的父類引用賦給子類引用,須要強制轉換,例如:
Father f1 = new Son(); Son s1 = (Son)f1;
但有運行出錯的狀況:
Father f2 = new Father(); Son s2 = (Son)f2; //編譯無錯但運行會出現錯誤
在不肯定父類引用是否指向子類對象時,能夠用 instanceof 來判斷:
if(f3 instanceof Son){ Son s3 = (Son)f3; }
final int x = 1; // x = 2; // cannot assign value to final variable 'x' final A y = new A(); y.a = 1;
1. 靜態變量
靜態變量在內存中只存在一份,只在類初始化時賦值一次。
public class A { private int x; // 實例變量 public static int y; // 靜態變量 }
注意:不能再成員函數內部定義static變量。
2. 靜態方法
靜態方法在類加載的時候就存在了,它不依賴於任何實例,因此靜態方法必須有實現,也就是說它不能是抽象方法(abstract)。
3. 靜態語句塊
靜態語句塊在類初始化時運行一次。
4. 靜態內部類
內部類的一種,靜態內部類不依賴外部類,且不能訪問外部類的非靜態的變量和方法。
5. 靜態導包
import static com.xxx.ClassName.*
在使用靜態變量和方法時不用再指明 ClassName,從而簡化代碼,但可讀性大大下降。
6. 變量賦值順序
靜態變量的賦值和靜態語句塊的運行優先於實例變量的賦值和普通語句塊的運行,靜態變量的賦值和靜態語句塊的運行哪一個先執行取決於它們在代碼中的順序。
public static String staticField = "靜態變量";
static { System.out.println("靜態語句塊"); }
public String field = "實例變量";
{ System.out.println("普通語句塊"); }
最後才運行構造函數
public InitialOrderTest() { System.out.println("構造函數"); }
存在繼承的狀況下,初始化順序爲:
跳出當前循環;可是若是是嵌套循環,則只能跳出當前的這一層循環,只有逐層 break 才能跳出全部循環。
for (int i = 0; i < 10; i++) { // 在執行i==6時強制終止循環,i==6不會被執行 if (i == 6) break; System.out.println(i); } 輸出結果爲0 1 2 3 4 5 ;6之後的都不會輸出
終止當前循環,可是不跳出循環(在循環中 continue 後面的語句是不會執行了),繼續往下根據循環條件執行循環。
for (int i = 0; i < 10; i++) { // i==6不會被執行,而是被中斷了 if (i == 6) continue; System.out.println(i); } 輸出結果爲0 1 2 3 4 5 7 8 9; 只有6沒有輸出
特別注意:返回值爲 void 的方法,從某個判斷中跳出,必須用 return。
final 用於聲明屬性、方法和類,分別表示屬性不可變、方法不可覆蓋和類不可被繼承。
在異常處理的時候,提供 finally 塊來執行任何的清除操做。若是拋出一個異常,那麼相匹配的 catch 字句就會執行,而後控制就會進入 finally 塊,前提是有 finally 塊。例如:數據庫鏈接關閉操做上
finally 做爲異常處理的一部分,它只能用在 try/catch 語句中,而且附帶一個語句塊,表示這段語句最終必定會被執行(無論有沒有拋出異常),常常被用在須要釋放資源的狀況下。(×)(這句話其實存在必定的問題,尚未深刻了解,歡迎你們在 issue 中提出本身的看法)
finalize() 是 Object 中的方法,當垃圾回收器將要回收對象所佔內存以前被調用,即當一個對象被虛擬機宣告死亡時會先調用它 finalize() 方法,讓此對象處理它生前的最後事情(這個對象能夠趁這個時機掙脫死亡的命運)。要明白這個問題,先看一下虛擬機是如何判斷一個對象該死的。
能夠覆蓋此方法來實現對其餘資源的回收,例如關閉文件。
Java 採用可達性分析算法來斷定一個對象是否死期已到。Java中以一系列 "GC Roots" 對象做爲起點,若是一個對象的引用鏈能夠最終追溯到 "GC Roots" 對象,那就天下太平。
不然若是隻是A對象引用B,B對象又引用A,A B引用鏈均未能達到 "GC Roots" 的話,那它倆將會被虛擬機宣判符合死亡條件,具備被垃圾回收器回收的資格。
上面提到了判斷死亡的依據,但被判斷死亡後,還有生還的機會。
如何自我救贖:
須要注意:
finalize() 只會在對象內存回收前被調用一次 (The finalize method is never invoked more than once by a Java virtual machine for any given object. )
finalize() 的調用具備不肯定性,只保證方法會調用,但不保證方法裏的任務會被執行完(好比一個對象手腳不夠利索,磨磨嘰嘰,還在自救的過程當中,被殺死回收了)。
雖然以上以對象救贖舉例,但 finalize() 的做用每每被認爲是用來作最後的資源回收。
基於在自我救贖中的表現來看,此方法有很大的不肯定性(不保證方法中的任務執行完)並且運行代價較高。因此用來回收資源也不會有什麼好的表現。
綜上:finalize() 方法並無什麼鳥用。
至於爲何會存在一個雞肋的方法:書中說 「它不是 C/C++ 中的析構函數,而是 Java 剛誕生時爲了使 C/C++ 程序員更容易接受它所作出的一個妥協」。
參考資料:
斷言(assert)做爲一種軟件調試的方法,提供了一種在代碼中進行正確性檢查的機制,目前不少開發語言都支持這種機制。
在實現中,assertion 就是在程序中的一條語句,它對一個 boolean 表達式進行檢查,一個正確程序必須保證這個 boolean 表達式的值爲 true;若是該值爲 false,說明程序已經處於不正確的狀態下,系統將給出警告而且退出。通常來講,assertion 用於保證程序最基本、關鍵的正確性。assertion 檢查一般在開發和測試時開啓。爲了提升性能,在軟件發佈後,assertion 檢查一般是關閉的。下面簡單介紹一下 Java 中 assertion 的實現。
在語法上,爲了支持 assertion,Java 增長了一個關鍵字 assert。它包括兩種表達式,分別以下:
assert
若是
若是爲 false,則程序拋出 AssertionError,並終止執行。
assert
若是
若是爲 false,則程序拋出 java.lang.AssertionError,並輸入 <錯誤信息表達式> 。
public static void main(String[] args) { System.out.println("123"); int a = 0; int b = 1; assert a == b; //需顯示開啓,默認爲不開啓狀態 assert a == b : "執行失敗!"; System.out.println("1234"); }
assert 的應用範圍不少,主要包括:
每次都讀錯,美式發音:volatile /'vɑlətl/ adj. [化學] 揮發性的;不穩定的;爆炸性的;反覆無常的
volatile 是一個類型修飾符(type specifier),它是被設計用來修飾被不一樣線程訪問和修改的變量。在使用 volatile 修飾成員變量後,全部線程在任什麼時候間所看到變量的值都是相同的。此外,使用 volatile 會組織編譯器對代碼的優化,所以會下降程序的執行效率。因此,除非無可奈何,不然,能不使用 volatile 就儘可能不要使用 volatile。
參考資料:
instanceof 是 Java 的一個二元操做符,相似於 ==,>,< 等操做符。
instanceof 是 Java 的保留關鍵字。它的做用是測試它左邊的對象是不是它右邊的類的實例,返回 boolean 的數據類型。
public class Main { public static void main(String[] args) { Object testObject = new ArrayList(); displayObjectClass(testObject); } public static void displayObjectClass(Object o) { if (o instanceof Vector) System.out.println("對象是 java.util.Vector 類的實例"); else if (o instanceof ArrayList) System.out.println("對象是 java.util.ArrayList 類的實例"); else System.out.println("對象是 " + o.getClass() + " 類的實例"); } }
strictfp,即 strict float point (精確浮點)。
strictfp 關鍵字可應用於類、接口或方法。使用 strictfp 關鍵字聲明一個方法時,該方法中全部的 float 和 double 表達式都嚴格遵照 FP-strict 的限制,符合 IEEE-754 規範。當對一個類或接口使用 strictfp 關鍵字時,該類中的全部代碼,包括嵌套類型中的初始設定值和代碼,都將嚴格地進行計算。嚴格約束意味着全部表達式的結果都必須是 IEEE 754 算法對操做數預期的結果,以單精度和雙精度格式表示。
若是你想讓你的浮點運算更加精確,並且不會由於不一樣的硬件平臺所執行的結果不一致的話,能夠用關鍵字strictfp.
transient 英 /'trænzɪənt/ adj. 短暫的;路過的 n. 瞬變現象;過往旅客;候鳥
咱們都知道一個對象只要實現了 Serilizable 接口,這個對象就能夠被序列化,Java 的這種序列化模式爲開發者提供了不少便利,咱們能夠沒必要關係具體序列化的過程,只要這個類實現了 Serilizable 接口,這個類的全部屬性和方法都會自動序列化。
然而在實際開發過程當中,咱們經常會遇到這樣的問題,這個類的有些屬性須要序列化,而其餘屬性不須要被序列化,打個比方,若是一個用戶有一些敏感信息(如密碼,銀行卡號等),爲了安全起見,不但願在網絡操做(主要涉及到序列化操做,本地序列化緩存也適用)中被傳輸,這些信息對應的變量就能夠加上 transient 關鍵字。換句話說,這個字段的生命週期僅存於調用者的內存中而不會寫到磁盤裏持久化。
總之,Java 的 transient 關鍵字爲咱們提供了便利,你只須要實現 Serilizable 接口,將不須要序列化的屬性前添加關鍵字transient,序列化對象的時候,這個屬性就不會序列化到指定的目的地中。
參考資料:
native(即 JNI,Java Native Interface),凡是一種語言,都但願是純。好比解決某一個方案都喜歡就單單這個語言來寫便可。Java 平臺有個用戶和本地 C 代碼進行互操做的 API,稱爲 Java Native Interface (Java本地接口)。
參考資料:
類型 | 存儲 | 取值範圍 | 默認值 | 包裝類 |
---|---|---|---|---|
整數型 | ||||
byte | 8 | 最大存儲數據量是 255,最小 -27,最大 27-1, [-128~127] |
(byte) 0 | Byte |
short | 16 | 最大數據存儲量是 65536,[-215,215-1], [-32768,32767],±3萬 |
(short) 0 | Short |
int | 32 | 最大數據存儲容量是 231-1, [-231,231-1],±21億,[ -2147483648, 2147483647] |
0 | Integer |
long | 64 | 最大數據存儲容量是 264-1, [-263,263-1], ±922億億(±(922+16個零)) |
0L | Long |
浮點型 | ||||
float | 32 | 數據範圍在 3.4e-45~1.4e38,直接賦值時必須在數字後加上 f 或 F | 0.0f | Float |
double | 64 | 數據範圍在 4.9e-324~1.8e308,賦值時能夠加 d 或 D 也能夠不加 | 0.0d | Double |
布爾型 | ||||
boolean | 1 | true / flase | false | Boolean |
字符型 | ||||
char | 16 | 存儲 Unicode 碼,用單引號賦值 | '\u0000' (null) | Character |
jdk5.0
提供的新特特性,它能夠自動實現類型的轉換// jdk 1.5 public class TestDemo { public static void main(String[] args) { Integer m =10; int i = m; } }
上面的代碼在 jdk1.4 之後的版本都不會報錯,它實現了自動拆裝箱的功能,若是是 jdk1.4,就得這樣寫了
// jdk 1.4 public class TestDemo { public static void main(String[] args) { Integer b = new Integer(210); int c = b.intValue(); } }
new Integer(123) 與 Integer.valueOf(123) 的區別在於,new Integer(123) 每次都會新建一個對象,而 Integer.valueOf(123) 可能會使用緩存對象,所以屢次使用 Integer.valueOf(123) 會取得同一個對象的引用。
Integer x = new Integer(123); Integer y = new Integer(123); System.out.println(x == y); // false Integer z = Integer.valueOf(123); Integer k = Integer.valueOf(123); System.out.println(z == k); // true
編譯器會在自動裝箱過程調用 valueOf() 方法,所以多個 Integer 實例使用自動裝箱來建立而且值相同,那麼就會引用相同的對象。
Integer m = 123; Integer n = 123; System.out.println(m == n); // true
valueOf() 方法的實現比較簡單,就是先判斷值是否在緩存池中,若是在的話就直接使用緩存池的內容。
// valueOf 源碼實現 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
在 Java 8 中,Integer 緩存池的大小默認爲 -128~127。
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; }
Java 還將一些其它基本類型的值放在緩衝池中,包含如下這些:
所以在使用這些基本類型對應的包裝類型時,就能夠直接使用緩衝池中的對象。
參考資料:
i++ 是在程序執行完畢後進行自增,而 ++i 是在程序開始執行前進行自增。
i++ 的操做分三步
三個階段:內存到寄存器,寄存器自增,寫回內存(這三個階段中間均可以被中斷分離開)
因此 i++ 不是原子操做,上面的三個步驟中任何一個步驟同時操做,均可能致使 i 的值不正確自增
在多核的機器上,CPU 在讀取內存 i 時也會可能發生同時讀取到同一值,這就致使兩次自增,實際只增長了一次。
i++ 和 ++i 都不是原子操做
原子性:指的是一個操做是不可中斷的。即便是在多個線程一塊兒執行的時候,一個操做一旦開始,就不會被其餘線程打斷。
JMM 三大特性:原子性,可見性,有序性。詳情請閱讀 Github 倉庫:Java 併發編程 一文。
Java 定義了位運算符,應用於整數類型 (int),長整型 (long),短整型 (short),字符型 (char),和字節型 (byte)等類型。
下表列出了位運算符的基本運算,假設整數變量A的值爲60和變量B的值爲13
A(60):0011 1100
B(13):0000 1101
操做符 | 名稱 | 描述 | 例子 |
---|---|---|---|
& | 與 | 若是相對應位都是 1,則結果爲 1,不然爲 0 | (A&B)獲得 12,即 0000 1100 |
| | 或 | 若是相對應位都是 0,則結果爲 0,不然爲 1 | (A|B)獲得 61,即 0011 1101 |
^ | 異或 | 若是相對應位值相同,則結果爲 0,不然爲 1 | (A^B)獲得49,即 0011 0001 |
〜 | 非 | 按位取反運算符翻轉操做數的每一位,即 0 變成 1,1 變成 0 | (〜A)獲得-61,即1100 0011 |
<< | 左移 | (左移一位乘2)按位左移運算符。左操做數按位左移右操做數指定的位數。左移 n 位表示原來的值乘 2n | A << 2獲得240,即 1111 0000 |
>> | (右移一位除2)有符號右移,按位右移運算符。左操做數按位右移右操做數指定的位數 | A >> 2獲得15即 1111 | |
>>> | 無符號右移 | 無符號右移,按位右移補零操做符。左操做數的值按右操做數指定的位數右移,移動獲得的空位以零填充 | A>>>2獲得15即0000 1111 |
一個數在計算機中的二進制表示形式,叫作這個數的機器數。機器數是帶符號的,在計算機用一個數的最高位存放符號,正數爲 0,負數爲 1。
好比,十進制中的數 +3 ,計算機字長爲 8 位,轉換成二進制就是 00000011。若是是 -3 ,就是 10000011 。那麼,這裏的 00000011 和 10000011 就是機器數。
由於第一位是符號位,因此機器數的形式值就不等於真正的數值。例如上面的有符號數 10000011,其最高位 1 表明負,其真正數值是 -3 而不是形式值 131(10000011 轉換成十進制等於 131)。因此,爲區別起見,將帶符號位的機器數對應的真正數值稱爲機器數的真值。
例:0000 0001 的真值 = +000 0001 = +1,1000 0001 的真值 = –000 0001 = –1
原碼就是符號位加上真值的絕對值,即用第一位表示符號,其他位表示值。好比若是是 8 位二進制:
[+1]原 = 0000 0001
[-1]原 = 1000 0001
第一位是符號位。由於第一位是符號位,因此 8 位二進制數的取值範圍就是:[1111 1111 , 0111 1111],即:[-127 , 127]
原碼是人腦最容易理解和計算的表示方式
反碼的表示方法是:
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原= [11111110]反
可見若是一個反碼錶示的是負數, 人腦沒法直觀的看出來它的數值. 一般要將其轉換成原碼再計算。
補碼的表示方法是:
[+1] = [0000 0001]原 = [0000 0001]反 = [0000 0001]補
[-1] = [1000 0001]原 = [1111 1110]反 = [1111 1111]補
對於負數,補碼錶示方式也是人腦沒法直觀看出其數值的。 一般也須要轉換成原碼在計算其數值。
參考資料:
若是給定整數 a 和 b,用如下三行代碼便可交換 a 和b 的值
a = a ^ b; b = a ^ b; a = a ^ b;
說明:位運算的題目基本上都帶有靠經驗積累纔會作的特徵,也就是準備階段須要作足夠多的題,面試時纔會有良好的感受。
#include <stdio.h> int add(int a, int b) { int c = a & b; int r = a ^ b; if(c == 0){ return r; } else{ return add(r, c << 1); } } int main(int argn, char *argv[]) { printf("sum = %d\n", add(-10000, 56789)); return 0; }
(1)&& 和 & 都是表示與,區別是 && 只要第一個條件不知足,後面條件就再也不判斷。而 & 要對全部的條件都進行判斷。
// 例如: public static void main(String[] args) { if((23!=23) && (100/0==0)){ System.out.println("運算沒有問題。"); }else{ System.out.println("沒有報錯"); } } // 輸出的是「沒有報錯」。而將 && 改成 & 就會以下錯誤: // Exception in thread "main" java.lang.ArithmeticException: / by zero
public static void main(String[] args) { if((23==23)||(100/0==0)){ System.out.println("運算沒有問題。"); }else{ System.out.println("沒有報錯"); } } // 此時輸出「運算沒有問題」。若將||改成|則會報錯。
在編程語言中,字面量(literal)指的是在源代碼中直接表示的一個固定的值。
八進制是用在整數字面量以前添加 「0」 來表示的。
十六進制用在整數字面量以前添加 「0x」 或者 「0X」 來表示的
Java 7 中新增了二進制:用在整數字面量以前添加 「0b」 或者 「0B」 來表示的。
在數值字面量中使用下劃線
在 Java7 中,數值字面量,無論是整數仍是浮點數都容許在數字之間插入任意多個下劃線。而且不會對數值產生影響,目的是方便閱讀,規則只能在數字之間使用。
public class BinaryIntegralLiteral { public static void main(String[] args) { System.out.println(0b010101); System.out.println(0B010101); System.out.println(0x15A); System.out.println(0X15A); System.out.println(077); System.out.println(5_000); /** * 輸出結果 * 21 * 21 * 346 * 346 * 63 * 5000 */ } }
如下爲 Object 中的通用方法
public final native Class<?> getClass() public native int hashCode() public boolean equals(Object obj) protected native Object clone() throws CloneNotSupportedException public String toString() public final native void notify() public final native void notifyAll() public final native void wait(long timeout) throws InterruptedException public final void wait(long timeout, int nanos) throws InterruptedException public final void wait() throws InterruptedException protected void finalize() throws Throwable {} // JVM內存回收之finalize()方法
1. equals() 與 == 的區別
Integer x = new Integer(1); Integer y = new Integer(1); System.out.println(x.equals(y)); // true System.out.println(x == y); // false
2. 等價關係
(一)自反性
x.equals(x); // true
(二)對稱性
x.equals(y) == y.equals(x); // true
(三)傳遞性
if (x.equals(y) && y.equals(z)) x.equals(z); // true;
(四)一致性
屢次調用 equals() 方法結果不變
x.equals(y) == x.equals(y); // true
(五)與 null 的比較
對任何不是 null 的對象 x 調用 x.equals(null) 結果都爲 false
x.euqals(null); // false;
3. 實現
public class EqualExample { private int x; private int y; private int z; public EqualExample(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; EqualExample that = (EqualExample) o; if (x != that.x) return false; if (y != that.y) return false; return z == that.z; } }
hasCode() 返回散列值,而 equals() 是用來判斷兩個實例是否等價。等價的兩個實例散列值必定要相同,可是散列值相同的兩個實例不必定等價。
在覆蓋 equals() 方法時應當老是覆蓋 hashCode() 方法,保證等價的兩個實例散列值也相等。
下面的代碼中,新建了兩個等價的實例,並將它們添加到 HashSet 中。咱們但願將這兩個實例當成同樣的,只在集合中添加一個實例,可是由於 EqualExample 沒有實現 hasCode() 方法,所以這兩個實例的散列值是不一樣的,最終致使集合添加了兩個等價的實例。
EqualExample e1 = new EqualExample(1, 1, 1); EqualExample e2 = new EqualExample(1, 1, 1); System.out.println(e1.equals(e2)); // true HashSet<EqualExample> set = new HashSet<>(); set.add(e1); set.add(e2); System.out.println(set.size()); // 2
理想的散列函數應當具備均勻性,即不相等的實例應當均勻分佈到全部可能的散列值上。這就要求了散列函數要把全部域的值都考慮進來,能夠將每一個域都當成 R 進制的某一位,而後組成一個 R 進制的整數。R 通常取 31,由於它是一個奇素數,若是是偶數的話,當出現乘法溢出,信息就會丟失,由於與 2 相乘至關於向左移一位。
一個數與 31 相乘能夠轉換成移位和減法:31\*x == (x<<5)-x
,編譯器會自動進行這個優化。
@Override public int hashCode() { int result = 17; result = 31 * result + x; result = 31 * result + y; result = 31 * result + z; return result; }
默認返回 ToStringExample@4554617c 這種形式,其中 @ 後面的數值爲散列碼的無符號十六進制表示。
public class ToStringExample { private int number; public ToStringExample(int number) { this.number = number; } }
ToStringExample example = new ToStringExample(123); System.out.println(example.toString());
ToStringExample@4554617c
1. cloneable
clone() 是 Object 的 protect 方法,它不是 public,一個類不顯式去重寫 clone(),其它類就不能直接去調用該類實例的 clone() 方法。
public class CloneExample { private int a; private int b; }
CloneExample e1 = new CloneExample(); // CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'
重寫 clone() 獲得如下實現:
public class CloneExample { private int a; private int b; @Override protected CloneExample clone() throws CloneNotSupportedException { return (CloneExample)super.clone(); } }
CloneExample e1 = new CloneExample(); try { CloneExample e2 = e1.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
java.lang.CloneNotSupportedException: CloneTest
以上拋出了 CloneNotSupportedException,這是由於 CloneTest 沒有實現 Cloneable 接口。
public class CloneExample implements Cloneable { private int a; private int b; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
應該注意的是,clone() 方法並非 Cloneable 接口的方法,而是 Object 的一個 protected 方法。Cloneable 接口只是規定,若是一個類沒有實現 Cloneable 接口又調用了 clone() 方法,就會拋出 CloneNotSupportedException。
參考資料: