怒肝倆月,新鮮出爐史上最有趣的Java小白手冊,初版,每一個 Java 初學者都應該收藏

這麼說吧,在我眼裏,Java 就是最流行的編程語言,沒有之一(PHP 往一邊站)。不只崗位多,容易找到工做,關鍵是薪資水平也到位,不學 Java 虧得慌,對吧?php

那可能零基礎學編程的小夥伴就會頭疼了,網上關於 Java 的大部分技術文章都不夠幽默,不夠風趣,不夠系列,急須要一份能看得進去的學習手冊,那我以爲我肝的這份手冊正好符合要求,而且會一直持續更新下去。html

初版的內容暫時包含兩方面,Java 基礎和 Java 面向對象編程。來吧,先上目錄,一睹爲快。java

0一、Java 基本語法簡介
0二、Java 基本數據類型簡介
0三、Java main() 方法簡介
0四、Java 的流程控制語句
0五、Java 包的簡介
0六、Java 究竟是值傳遞仍是引用傳遞
0七、Java 的類和對象
0八、Java 構造方法
0九、Java 抽象類
十、Java 接口
十一、Java 繼承
十二、this 關鍵字
1三、super 關鍵字
1四、重寫和重載
1五、static 關鍵字
1六、Java 枚舉
1七、final 關鍵字nginx

目錄欣賞完了,接下來就是拜讀精華內容的時間,搬個小板凳,認認真真好好學吧,學到就是賺到!程序員

1、Java 基本語法簡介

0一、數據類型

Java 有 2 種數據類型,一種是基本數據類型,一種是引用類型。web

基本數據類型用於存儲簡單類型的數據,好比說,int、long、byte、short 用於存儲整數,float、double 用於存儲浮點數,char 用於存儲字符,boolean 用於存儲布爾值。面試

不一樣的基本數據類型,有不一樣的默認值和大小,來個表格感覺下。sql

數據類型 默認值 大小
boolean false 1比特
char '\u0000' 2字節
byte 0 1字節
short 0 2字節
int 0 4字節
long 0L 8字節
float 0.0f 4字節
double 0.0 8字節

引用類型用於存儲對象(null 表示沒有值的對象)的引用,String 是引用類型的最佳表明,好比說 String cmower = "沉默王二"數據庫

0二、聲明變量

要聲明一個變量,必須指定它的名字和類型,來看一個簡單的示例:編程

int age;
String name;

count 和 name 在聲明後會獲得一個默認值,按照它們的數據類型——不能是局部變量(不然 Java 編譯器會在你使用變量的時候提醒要先賦值),必須是類成員變量。

public class SyntaxLocalVariable {
    int age;
    String name;

    public static void main(String[] args) {
        SyntaxLocalVariable syntax = new SyntaxLocalVariable();
        System.out.println(syntax.age); // 輸出 0
        System.out.println(syntax.name);  // 輸出 null
    }
}

也能夠在聲明一個變量後使用「=」操做符進行賦值,就像下面這樣:

int age = 18;
String name = "沉默王二";

咱們定義了 2 個變量,int 類型的 age 和 String 類型的 name,age 賦值 18,name 賦值爲「沉默王二」。

每行代碼後面都跟了一個「;」,表示當前語句結束了。

在 Java 中,變量最好遵照命名約定,這樣能提升代碼的可閱讀性。

  • 以字母、下劃線(_)或者美圓符號($)開頭
  • 不能使用 Java 的保留字,好比說 int 不能做爲變量名

0三、數組

數組在 Java 中佔據着重要的位置,它是不少集合類的底層實現。數組屬於引用類型,它用來存儲一系列指定類型的數據。

聲明數組的通常語法以下所示:

type[] identiier = new type[length];

type 能夠是任意的基本數據類型或者引用類型。來看下面這個例子:

public class ArraysDemo {
    public static void main(String[] args) {
        int [] nums = new int[10];
        nums[0] = 18;
        nums[1] = 19;
        System.out.println(nums[0]);
    }
}

數組的索引從 0 開始,第一個元素的索引爲 0,第二個元素的索引爲 1。爲何要這樣設計?感興趣的話,你能夠去探究一下。

經過變量名[索引]的方式能夠訪問數組指定索引處的元素,賦值或者取值是同樣的。

0四、關鍵字

關鍵字屬於保留字,在 Java 中具備特殊的含義,好比說 public、final、static、new 等等,它們不能用來做爲變量名。爲了便於你做爲參照,我列舉了 48 個經常使用的關鍵字,你能夠瞅一瞅。

  1. abstract: abstract 關鍵字用於聲明抽象類——能夠有抽象和非抽象方法。

  2. boolean: boolean 關鍵字用於將變量聲明爲布爾值類型,它只有 true 和 false 兩個值。

  3. break: break 關鍵字用於中斷循環或 switch 語句。

  4. byte: byte 關鍵字用於聲明一個能夠容納 8 個比特的變量。

  5. case: case 關鍵字用於在 switch 語句中標記條件的值。

  6. catch: catch 關鍵字用於捕獲 try 語句中的異常。

  7. char: char 關鍵字用於聲明一個能夠容納無符號 16 位比特的 Unicode 字符的變量。

  8. class: class 關鍵字用於聲明一個類。

  9. continue: continue 關鍵字用於繼續下一個循環。它能夠在指定條件下跳過其他代碼。

  10. default: default 關鍵字用於指定 switch 語句中除去 case 條件以外的默認代碼塊。

  11. do: do 關鍵字一般和 while 關鍵字配合使用,do 後緊跟循環體。

  12. double: double 關鍵字用於聲明一個能夠容納 64 位浮點數的變量。

  13. else: else 關鍵字用於指示 if 語句中的備用分支。

  14. enum: enum(枚舉)關鍵字用於定義一組固定的常量。

  15. extends: extends 關鍵字用於指示一個類是從另外一個類或接口繼承的。

  16. final: final 關鍵字用於指示該變量是不可更改的。

  17. finally: finally 關鍵字和 try-catch 配合使用,表示不管是否處理異常,老是執行 finally 塊中的代碼。

  18. float: float 關鍵字用於聲明一個能夠容納 32 位浮點數的變量。

  19. for: for 關鍵字用於啓動一個 for 循環,若是循環次數是固定的,建議使用 for 循環。

  20. if: if 關鍵字用於指定條件,若是條件爲真,則執行對應代碼。

  21. implements: implements 關鍵字用於實現接口。

  22. import: import 關鍵字用於導入對應的類或者接口。

  23. instanceof: instanceof 關鍵字用於判斷對象是否屬於某個類型(class)。

  24. int: int 關鍵字用於聲明一個能夠容納 32 位帶符號的整數變量。

  25. interface: interface 關鍵字用於聲明接口——只能具備抽象方法。

  26. long: long 關鍵字用於聲明一個能夠容納 64 位整數的變量。

  27. native: native 關鍵字用於指定一個方法是經過調用本機接口(非 Java)實現的。

  28. new: new 關鍵字用於建立一個新的對象。

  29. null: 若是一個變量是空的(什麼引用也沒有指向),就能夠將它賦值爲 null。

  30. package: package 關鍵字用於聲明類所在的包。

  31. private: private 關鍵字是一個訪問修飾符,表示方法或變量只對當前類可見。

  32. protected: protected 關鍵字也是一個訪問修飾符,表示方法或變量對同一包內的類和全部子類可見。

  33. public: public 關鍵字是另一個訪問修飾符,除了能夠聲明方法和變量(全部類可見),還能夠聲明類。main() 方法必須聲明爲 public。

  34. return: return 關鍵字用於在代碼執行完成後返回(一個值)。

  35. short: short 關鍵字用於聲明一個能夠容納 16 位整數的變量。

  36. static: static 關鍵字表示該變量或方法是靜態變量或靜態方法。

  37. strictfp: strictfp 關鍵字並不常見,一般用於修飾一個方法,確保方法體內的浮點數運算在每一個平臺上執行的結果相同。

  38. super: super 關鍵字可用於調用父類的方法或者變量。

  39. switch: switch 關鍵字一般用於三個(以上)的條件判斷。

  40. synchronized: synchronized 關鍵字用於指定多線程代碼中的同步方法、變量或者代碼塊。

  41. this: this 關鍵字可用於在方法或構造函數中引用當前對象。

  42. throw: throw 關鍵字主動拋出異常。

  43. throws: throws 關鍵字用於聲明異常。

  44. transient: transient 關鍵字在序列化的使用用到,它修飾的字段不會被序列化。

  45. try: try 關鍵字用於包裹要捕獲異常的代碼塊。

  46. void: void 關鍵字用於指定方法沒有返回值。

  47. volatile: volatile 關鍵字保證了不一樣線程對它修飾的變量進行操做時的可見性,即一個線程修改了某個變量的值,這新值對其餘線程來講是當即可見的。

  48. while: 若是循環次數不固定,建議使用 while 循環。

0五、操做符

除去「=」賦值操做符,Java 中還有不少其餘做用的操做符,咱們來大體看一下。

①、算術運算符

  • +(加號)
  • –(減號)
  • *(乘號)
  • /(除號)
  • %(取餘)

來看一個例子:

public class ArithmeticOperator {
    public static void main(String[] args) {
        int a = 10;
        int b = 5;

        System.out.println(a + b);//15  
        System.out.println(a - b);//5  
        System.out.println(a * b);//50  
        System.out.println(a / b);//2  
        System.out.println(a % b);//0  
    }
}

「+」號比較特殊,還能夠用於字符串拼接,來看一個例子:

String result = "沉默王二" + "一枚有趣的程序員";

②、邏輯運算符

邏輯運算符一般用於布爾表達式,常見的有:

  • &&(AND)多個條件中只要有一個爲 false 結果就爲 false
  • ||(OR)多個條件只要有一個爲 true 結果就爲 true
  • !(NOT)條件若是爲 true,加上「!」就爲 false,不然,反之。

來看一個例子:

public class LogicalOperator {
    public static void main(String[] args) {
        int a=10;
        int b=5;
        int c=20;
        System.out.println(a<b&&a<c);//false
        System.out.println(a>b||a<c);//true
        System.out.println(!(a<b)); // true
    }
}

③、比較運算符

  • &lt; (小於)
  • &lt;= (小於或者等於)
  • &gt; (大於)
  • &gt;= (大於或者等於)
  • == (相等)
  • != (不等)

0六、程序結構

Java 中最小的程序單元叫作類,一個類能夠有一個或者多個字段(也叫做成員變量),還能夠有一個或者多個方法,甚至還能夠有一些內部類。

若是一個類想要執行,就必須有一個 main 方法——程序運行的入口,就好像人的嘴同樣,嗯,能夠這麼牽強的理解一下。

public class StructureProgram {
    public static void main(String[] args) {
        System.out.println("沒有成員變量,只有一個 main 方法");
    }
}
  • 類名叫作 StructureProgram,在它裏面,只有一個 main 方法。
  • {} 之間的代碼稱之爲代碼塊。
  • 以上源代碼將會保存在一個後綴名爲 java 的文件中。

0七、編譯而後執行代碼

一般,一些教程在介紹這塊內容的時候,建議你經過命令行中先執行 javac 命令將源代碼編譯成字節碼文件,而後再執行 java 命令指定代碼。

但我不但願這個糟糕的局面再繼續下去了——新手安裝配置 JDK 真的蠻須要勇氣和耐心的,稍有不慎,沒入門就先放棄了。何況,在命令行中編譯源代碼會遇到不少莫名其妙的錯誤,這對新手是極其致命的——若是你再遇到這種老式的教程,能夠吐口水了。

好的方法,就是去下載 IntelliJ IDEA,簡稱 IDEA,它被業界公認爲最好的 Java 集成開發工具,尤爲在智能代碼助手、代碼自動提示、代碼重構、代碼版本管理(Git、SVN、Maven)、單元測試、代碼分析等方面有着亮眼的發揮。IDEA 產於捷克(位於東歐),開發人員以嚴謹著稱。IDEA 分爲社區版和付費版兩個版本,新手直接下載社區版就足夠用了。

安裝成功後,能夠開始敲代碼了,而後直接右鍵運行(連保存都省了),結果會在 Run 面板中顯示,以下圖所示。

想查看反編譯後的字節碼的話,能夠在 src 的同級目錄 target/classes 的包路徑下找到一個 StructureProgram.class 的文件(若是找不到的話,在目錄上右鍵選擇「Reload from Disk」)。

能夠雙擊打開它。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.cmower.baeldung.basic;

public class StructureProgram {
    public StructureProgram() {
    }

    public static void main(String[] args) {
        System.out.println("沒有成員變量,只有一個 main 方法");
    }
}

IDEA 默認會用 Fernflower 將 class 字節碼反編譯爲咱們能夠看得懂的 Java 代碼。實際上,class 字節碼(請安裝 show bytecode 插件)長下面這個樣子:

// class version 57.65535 (-65479)
// access flags 0x21
public class com/cmower/baeldung/basic/StructureProgram {

  // compiled from: StructureProgram.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/cmower/baeldung/basic/StructureProgram; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 5 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream
;
    LDC "\u6ca1\u6709\u6210\u5458\u53d8\u91cf\uff0c\u53ea\u6709\u4e00\u4e2a main \u65b9\u6cd5"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 6 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1
}

新手看起來仍是有些懵逼的,建議過過眼癮就好了。

2、Java 基本數據類型簡介

0一、布爾

布爾(boolean)僅用於存儲兩個值:true 和 false,也就是真和假,一般用於條件的判斷。代碼示例:

boolean flag = true;

0二、byte

byte 的取值範圍在 -128 和 127 之間,包含 127。最小值爲 -128,最大值爲 127,默認值爲 0。

在網絡傳輸的過程當中,爲了節省空間,經常使用字節來做爲數據的傳輸方式。代碼示例:

byte a = 10;
byte b = -10;

0三、short

short 的取值範圍在 -32,768 和 32,767 之間,包含 32,767。最小值爲 -32,768,最大值爲 32,767,默認值爲 0。代碼示例:

short s = 10000;
short r = -5000;

0四、int

int 的取值範圍在 -2,147,483,648(-2 ^ 31)和 2,147,483,647(2 ^ 31 -1)(含)之間,默認值爲 0。若是沒有特殊需求,整形數據就用 int。代碼示例:

int a = 100000;
int b = -200000;

0五、long

long 的取值範圍在 -9,223,372,036,854,775,808(-2^63) 和 9,223,372,036,854,775,807(2^63 -1)(含)之間,默認值爲 0。若是 int 存儲不下,就用 long,整形數據就用 int。代碼示例:

long a = 100000L
long b = -200000L;

爲了和 int 做區分,long 型變量在聲明的時候,末尾要帶上大寫的「L」。不用小寫的「l」,是由於小寫的「l」容易和數字「1」混淆。

0六、float

float 是單精度的浮點數,遵循 IEEE 754(二進制浮點數算術標準),取值範圍是無限的,默認值爲 0.0f。float 不適合用於精確的數值,好比說貨幣。代碼示例:

float f1 = 234.5f;

爲了和 double 做區分,float 型變量在聲明的時候,末尾要帶上小寫的「f」。不須要使用大寫的「F」,是由於小寫的「f」很容易辨別。

0七、double

double 是雙精度的浮點數,遵循 IEEE 754(二進制浮點數算術標準),取值範圍也是無限的,默認值爲 0.0。double 一樣不適合用於精確的數值,好比說貨幣。代碼示例:

double d1 = 12.3

那精確的數值用什麼表示呢?最好使用 BigDecimal,它能夠表示一個任意大小且精度徹底準確的浮點數。針對貨幣類型的數值,也能夠先乘以 100 轉成整形進行處理。

Tips:單精度是這樣的格式,1 位符號,8 位指數,23 位小數,有效位數爲 7 位。

雙精度是這樣的格式,1 位符號,11 位指數,52 爲小數,有效位數爲 16 位。

取值範圍取決於指數位,計算精度取決於小數位(尾數)。小數位越多,則能表示的數越大,那麼計算精度則越高。

一個數由若干位數字組成,其中影響測量精度的數字稱做有效數字,也稱有效數位。有效數字指科學計算中用以表示一個浮點數精度的那些數字。通常地,指一個用小數形式表示的浮點數中,從第一個非零的數字算起的全部數字。如 1.24 和 0.00124 的有效數字都有 3 位。

0八、char

char 能夠表示一個 16 位的 Unicode 字符,其值範圍在 '\u0000'(0)和 '\uffff'(65,535)(包含)之間。代碼示例:

char letterA = 'A'// 用英文的單引號包裹住。

3、Java main() 方法簡介

每一個程序都須要一個入口,對於 Java 程序來講,入口就是 main 方法。

public static void main(String[] args) {}

public、static、void 這 3 個關鍵字在前面的內容已經介紹過了,若是以爲回去找比較麻煩的話,這裏再貼一下:

  • public 關鍵字是另一個訪問修飾符,除了能夠聲明方法和變量(全部類可見),還能夠聲明類。main() 方法必須聲明爲 public。

  • static 關鍵字表示該變量或方法是靜態變量或靜態方法,能夠直接經過類訪問,不須要實例化對象來訪問。

  • void 關鍵字用於指定方法沒有返回值。

另外,main 關鍵字爲方法的名字,Java 虛擬機在執行程序時會尋找這個標識符;args 爲 main() 方法的參數名,它的類型爲一個 String 數組,也就是說,在使用 java 命令執行程序的時候,能夠給 main() 方法傳遞字符串數組做爲參數。

java HelloWorld 沉默王二 沉默王三

javac 命令用來編譯程序,java 命令用來執行程序,HelloWorld 爲這段程序的類名,沉默王二和沉默王三爲字符串數組,中間經過空格隔開,而後就能夠在 main() 方法中經過 args[0]args[1] 獲取傳遞的參數值了。

public class HelloWorld {
    public static void main(String[] args) {
        if ("沉默王二".equals(args[0])) {

        }

        if ("沉默王三".equals(args[1])) {

        }
    }
}

main() 方法的寫法並非惟一的,還有其餘幾種變體,儘管它們可能並不常見,能夠簡單來了解一下。

第二種,把方括號 [] 往 args 靠近而不是 String 靠近:

public static void main(String []args) { }

第三種,把方括號 [] 放在 args 的右側:

public static void main(String args[]) { }

第四種,還能夠把數組形式換成可變參數的形式:

public static void main(String...args) { }

第五種,在 main() 方法上添加另一個修飾符 strictfp,用於強調在處理浮點數時的兼容性:

public strictfp static void main(String[] args) { }

也能夠在 main() 方法上添加 final 關鍵字或者 synchronized 關鍵字。

第六種,還能夠爲 args 參數添加 final 關鍵字:

public static void main(final String[] args) { }

第七種,最複雜的一種,全部能夠添加的關鍵字通通添加上:

final static synchronized strictfp void main(final String[] args) { }

固然了,並不須要爲了裝逼特地把 main() 方法寫成上面提到的這些形式,使用 IDE 提供的默認形式就能夠了。

4、Java 的流程控制語句

在 Java 中,有三種類型的流程控制語句:

  • 條件分支,用於在兩個或者多個條件之間作出選擇,常見的有 if/else/else if、三元運算符和 switch 語句。

  • 循環或者遍歷,常見的有 for、while 和 do-while。

  • break 和 continue,用於跳出循環或者跳過進入下一輪循環。

if 語句

if 語句的格式以下:

if(布爾表達式){  
// 若是條件爲 true,則執行這塊代碼

畫個流程圖表示一下:

來寫個示例:

public class IfExample {
    public static void main(String[] args) {
        int age = 20;
        if (age < 30) {
            System.out.println("青春年華");
        }
    }
}

輸出:

青春年華

if-else 語句

if-else 語句的格式以下:

if(布爾表達式){  
// 條件爲 true 時執行的代碼塊
}else{  
// 條件爲 false  時執行的代碼塊
}  

畫個流程圖表示一下:

來寫個示例:

public class IfElseExample {
    public static void main(String[] args) {
        int age = 31;
        if (age < 30) {
            System.out.println("青春年華");
        } else {
            System.out.println("而立之年");
        }
    }
}

輸出:

而立之年

除了這個例子以外,還有一個判斷閏年(被 4 整除但不能被 100 整除或者被 400 整除)的例子:

public class LeapYear {
    public static void main(String[] args) {
        int year = 2020;
        if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
            System.out.println("閏年");
        } else {
            System.out.println("普通年份");
        }
    }
}

輸出:

閏年

若是執行語句比較簡單的話,能夠使用三元運算符來代替 if-else 語句,若是條件爲 true,返回 ? 後面 : 前面的值;若是條件爲 false,返回 : 後面的值。

public class IfElseTernaryExample {
    public static void main(String[] args) {
        int num = 13;
        String result = (num % 2 == 0) ? "偶數" : "奇數";
        System.out.println(result);
    }
}

輸出:

奇數

if-else-if 語句

if-else-if 語句的格式以下:

if(條件1){  
// 條件1 爲 true 時執行的代碼
}else if(條件2){  
// 條件2 爲 true 時執行的代碼
}  
else if(條件3){  
// 條件3 爲 true 時執行的代碼
}  
...  
else{  
// 以上條件均爲 false 時執行的代碼

畫個流程圖表示一下:

來寫個示例:

public class IfElseIfExample {
    public static void main(String[] args) {
        int age = 31;
        if (age < 30) {
            System.out.println("青春年華");
        } else if (age >= 30 && age < 40 ) {
            System.out.println("而立之年");
        } else if (age >= 40 && age < 50 ) {
            System.out.println("不惑之年");
        } else {
            System.out.println("知天命");
        }
    }
}

輸出:

而立之年

if 嵌套語句

if 嵌套語句的格式以下:

if(外側條件){    
     // 外側條件爲 true 時執行的代碼 
          if(內側條件){  
             // 內側條件爲 true 時執行的代碼
    }    
}  

畫個流程圖表示一下:

來寫個示例:

public class NestedIfExample {
    public static void main(String[] args) {
        int age = 20;
        boolean isGirl = true;
        if (age >= 20) {
            if (isGirl) {
                System.out.println("女生法定結婚年齡");
            }
        }
    }
}

輸出:

女生法定結婚年齡

switch 語句的格式:

switch(變量) {    
case 可選值1:    
 // 可選值1匹配後執行的代碼;    
 break;  // 該關鍵字是可選項
case 可選值2:    
 // 可選值2匹配後執行的代碼;    
 break;  // 該關鍵字是可選項
......    

default// 該關鍵字是可選項     
 // 全部可選值都不匹配後執行的代碼 
}    
  • 變量能夠有 1 個或者 N 個值。

  • 值類型必須和變量類型是一致的,而且值是肯定的。

  • 值必須是惟一的,不能重複,不然編譯會出錯。

  • break 關鍵字是可選的,若是沒有,則執行下一個 case,若是有,則跳出 switch 語句。

  • default 關鍵字也是可選的。

畫個流程圖:

來個示例:

public class Switch1 {
    public static void main(String[] args) {
        int age = 20;
        switch (age) {
            case 20 :
                System.out.println("上學");
                break;
            case 24 :
                System.out.println("蘇州工做");
                break;
            case 30 :
                System.out.println("洛陽工做");
                break;
            default:
                System.out.println("未知");
                break// 可省略
        }
    }
}

輸出:

上學

當兩個值要執行的代碼相同時,能夠把要執行的代碼寫在下一個 case 語句中,而上一個 case 語句中什麼也沒有,來看一下示例:

public class Switch2 {
    public static void main(String[] args) {
        String name = "沉默王二";
        switch (name) {
            case "詹姆斯":
                System.out.println("籃球運動員");
                break;
            case "穆里尼奧":
                System.out.println("足球教練");
                break;
            case "沉默王二":
            case "沉默王三":
                System.out.println("乒乓球愛好者");
                break;
            default:
                throw new IllegalArgumentException(
                        "名字沒有匹配項");

        }
    }
}

輸出:

乒乓球愛好者

枚舉做爲 switch 語句的變量也很常見,來看例子:

public class SwitchEnumDemo {
    public enum PlayerTypes {
        TENNIS,
        FOOTBALL,
        BASKETBALL,
        UNKNOWN
    }

    public static void main(String[] args) {
        System.out.println(createPlayer(PlayerTypes.BASKETBALL));
    }

    private static String createPlayer(PlayerTypes playerType) {
        switch (playerType) {
            case TENNIS:
                return "網球運動員費德勒";
            case FOOTBALL:
                return "足球運動員C羅";
            case BASKETBALL:
                return "籃球運動員詹姆斯";
            case UNKNOWN:
                throw new IllegalArgumentException("未知");
            default:
                throw new IllegalArgumentException(
                        "運動員類型: " + playerType);

        }
    }
}

輸出:

籃球運動員詹姆斯

循環語句比較

比較方式 for while do-while
簡介 for 循環的次數是固定的 while 循環的次數是不固定的,而且須要條件爲 true do-while 循環的次數也不固定,但會至少執行一次循環,無聊條件是否爲 true
什麼時候使用 循環次數固定的 循環次數是不固定的 循環次數不固定,而且循環體至少要執行一次
語法 for(init:condition;++/--) {// 要執行的代碼} while(condition){// 要執行的代碼} do{//要執行的代碼}while(condition);

普通的 for 循環

普通的 for 循環能夠分爲 4 個部分:

1)初始變量:循環開始執行時的初始條件。

2)條件:循環每次執行時要判斷的條件,若是爲 true,就執行循環體;若是爲 false,就跳出循環。固然了,條件是可選的,若是沒有條件,則會一直循環。

3)循環體:循環每次要執行的代碼塊,直到條件變爲 false。

4)自增/自減:初識變量變化的方式。

來看一下普通 for 循環的格式:

for(初識變量;條件;自增/自減){  
// 循環體
}  

畫個流程圖:

來個示例:

public class ForExample {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            System.out.println("沉默王二好帥啊");
        }
    }
}

輸出:

沉默王二好帥啊
沉默王二好帥啊
沉默王二好帥啊
沉默王二好帥啊
沉默王二好帥啊

循環語句還能夠嵌套呢,這樣就能夠打印出更好玩的呢。

public class PyramidForExample {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            for (int j = 0;j<= i;j++) {
                System.out.print("❤");
            }
            System.out.println();
        }
    }
}

打印出什麼玩意呢?


❤❤
❤❤❤
❤❤❤❤
❤❤❤❤❤

for-each

for-each 循環一般用於遍歷數組和集合,它的使用規則比普通的 for 循環還要簡單,不須要初始變量,不須要條件,不須要下標來自增或者自減。來看一下語法:

for(元素類型 元素 : 數組或集合){  
// 要執行的代碼
}  

來看一下示例:

public class ForEachExample {
    public static void main(String[] args) {
        String[] strs = {"沉默王二""一枚有趣的程序員"};

        for (String str : strs) {
            System.out.println(str);
        }
    }
}

輸出:

沉默王二
一枚有趣的程序員

無限 for 循環

想不想體驗一下無限 for 循環的威力,也就是死循環?

public class InfinitiveForExample {
    public static void main(String[] args) {
        for(;;){
            System.out.println("停不下來。。。。");
        }
    }
}

輸出:

停不下來。。。。
停不下來。。。。
停不下來。。。。
停不下來。。。。

一旦運行起來,就停不下來了,除非強制中止。

while 循環

while(條件){  
//循環體  
}  

畫個流程圖:

來個示例:

public class WhileExample {
    public static void main(String[] args) {
        int i = 0;
        while (true) {
            System.out.println("沉默王二");
            i++;
            if (i == 5) {
                break;
            }
        }
    }
}

猜猜會輸出幾回?

沉默王二
沉默王二
沉默王二
沉默王二
沉默王二

do-while 循環

do{  
// 循環體
}while(提交);  

畫個流程圖:

來個示例:

public class DoWhileExample {
    public static void main(String[] args) {
        int i = 0;
        do {
            System.out.println("沉默王二");
            i++;
            if (i == 5) {
                break;
            }
        } while (true);
    }
}

程序輸出結果以下所示:

沉默王二
沉默王二
沉默王二
沉默王二
沉默王二

break

break 關鍵字一般用於中斷循環或 switch 語句,它在指定條件下中斷程序的當前流程。若是是內部循環,則僅中斷內部循環。

能夠將 break 關鍵字用於全部類型循環語句中,好比說 for 循環while 循環,以及 do-while 循環

來畫個流程圖感覺一下:

用在 for 循環中的示例:

for (int i = 1; i <= 10; i++) {
    if (i == 5) {
        break;
    }
    System.out.println(i);
}

用在嵌套 for 循環中的示例:

for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        if (i == 2 && j == 2) {
            break;
        }
        System.out.println(i + " " + j);
    }
}

用在 while 循環中的示例:

int i = 1;
while (i <= 10) {
    if (i == 5) {
        i++;
        break;
    }
    System.out.println(i);
    i++;
}

用在 do-while 循環中的示例:

int j = 1;
do {
    if (j == 5) { 
        j++;
        break;
    }
    System.out.println(j);
    j++;
while (j <= 10);

continue

當咱們須要在 for 循環或者 (do)while 循環中當即跳轉到下一個循環時,就能夠使用 continue 關鍵字,一般用於跳過指定條件下的循環體,若是循環是嵌套的,僅跳過當前循環。

來個示例:

public class ContinueDemo {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            if (i == 5) {
                // 使用 continue 關鍵字
                continue;// 5 將會被跳過
            }
            System.out.println(i);
        }
    }
}

輸出:

1
2
3
4
6
7
8
9
10

5 真的被跳過了。

再來個循環嵌套的例子。

public class ContinueInnerDemo {
    public static void main(String[] args) {
        for (int i = 1; i <= 3; i++) {
            for (int j = 1; j <= 3; j++) {
                if (i == 2 && j == 2) {
                    //  當i=2,j=2時跳過
                    continue;
                }
                System.out.println(i + " " + j);
            }
        }
    }
}

打印出什麼玩意呢?

1 1
1 2
1 3
2 1
2 3
3 1
3 2
3 3

「2 2」 沒有輸出,被跳過了。

再來看一下 while 循環時 continue 的使用示例:

public class ContinueWhileDemo {
    public static void main(String[] args) {
        int i = 1;
        while (i <= 10) {
            if (i == 5) {
                i++;
                continue;
            }
            System.out.println(i);
            i++;
        }
    }
}

輸出:

1
2
3
4
6
7
8
9
10

注意:若是把 if 條件中的「i++」省略掉的話,程序就會進入死循環,一直在 continue。

最後,再來看一下 do-while 循環時 continue 的使用示例:

public class ContinueDoWhileDemo {
    public static void main(String[] args) {
        int i=1;
        do{
            if(i==5){
                i++;
                continue;
            }
            System.out.println(i);
            i++;
        }while(i<=10);
    }
}

輸出:

1
2
3
4
6
7
8
9
10

注意:一樣的,若是把 if 條件中的「i++」省略掉的話,程序就會進入死循環,一直在 continue。

5、Java 包的簡介

在 Java 中,咱們使用 package(包)對相關的類、接口和子包進行分組。這樣作的好處有:

  • 使相關類型更容易查找
  • 避免命名衝突,好比說 com.itwanger.Hello 和 com.itwangsan.Hello 不一樣
  • 經過包和訪問權限控制符來限定類的可見性

0一、建立一個包

package com.itwanger;

能夠使用 package 關鍵字來定義一個包名,須要注意的是,這行代碼必須處於一個類中的第一行。強烈建議在包中聲明類,不要缺省,不然就失去了包結構的帶來的好處。

包的命名應該遵照如下規則:

  • 應該所有是小寫字母
  • 能夠包含多個單詞,單詞之間使用「.」鏈接,好比說 java.lang
  • 名稱由公司名或者組織名肯定,採用倒序的方式,好比說,我我的博客的域名是 www.itwanger.com,因此我建立的包名是就是 com.itwanger.xxxx

每一個包或者子包都在磁盤上有本身的目錄結構,若是 Java 文件時在 com.itwanger.xxxx 包下,那麼該文件所在的目錄結構就應該是 com->itwanger->xxxx

0二、使用包

讓咱們在名爲 test 的子包裏新建一個 Cmower 類:

package com.itwanger.test;

public class Cmower {
    private String name;
    private int age;
}

若是須要在另一個包中使用 Cmower 類,就須要經過 import 關鍵字將其引入。有兩種方式可供選擇,第一種,使用 * 導入包下全部的類:

import com.itwanger.test.*;

第二種,使用類名導入該類:

import com.itwanger.test.Cmower;

Java 和第三方類庫提供了不少包可供使用,能夠經過上述的方式導入類庫使用。

package com.itwanger.test;

import java.util.ArrayList;
import java.util.List;

public class CmowerTest {
    public static void main(String[] args) {
        List<Cmower> list = new ArrayList<>();
        list.add(new Cmower());
    }
}

0三、全名

有時,咱們可能會使用來自不一樣包下的兩個具備相同名稱的類。例如,咱們可能同時使用 java.sql.Datejava.util.Date。當咱們遇到命名衝突時,咱們須要對至少一個類使用全名(包名+類名)。

List<com.itwanger.test.Cmower> list1 = new ArrayList<>();
list.add(new com.itwanger.test.Cmower());

6、Java 究竟是值傳遞仍是引用傳遞

將參數傳遞給方法有兩種常見的方式,一種是「值傳遞」,一種是「引用傳遞」。C 語言自己只支持值傳遞,它的衍生品 C++ 既支持值傳遞,也支持引用傳遞,而 Java 只支持值傳遞。

0一、值傳遞 VS 引用傳遞

首先,咱們必需要搞清楚,到底什麼是值傳遞,什麼是引用傳遞,不然,討論 Java 究竟是值傳遞仍是引用傳遞就顯得毫無心義。

當一個參數按照值的方式在兩個方法之間傳遞時,調用者和被調用者實際上是用的兩個不一樣的變量——被調用者中的變量(原始值)是調用者中變量的一份拷貝,對它們當中的任何一個變量修改都不會影響到另一個變量。

而當一個參數按照引用傳遞的方式在兩個方法之間傳遞時,調用者和被調用者其實用的是同一個變量,當該變量被修改時,雙方都是可見的。

Java 程序員之因此容易搞混值傳遞和引用傳遞,主要是由於 Java 有兩種數據類型,一種是基本類型,好比說 int,另一種是引用類型,好比說 String。

基本類型的變量存儲的都是實際的值,而引用類型的變量存儲的是對象的引用——指向了對象在內存中的地址。值和引用存儲在 stack(棧)中,而對象存儲在 heap(堆)中。

之因此有這個區別,是由於:

  • 棧的優點是,存取速度比堆要快,僅次於直接位於 CPU 中的寄存器。但缺點是,棧中的數據大小與生存週期必須是肯定的。
  • 堆的優點是能夠動態地分配內存大小,生存週期也沒必要事先告訴編譯器,Java 的垃圾回收器會自動收走那些再也不使用的數據。但因爲要在運行時動態分配內存,存取速度較慢。

0二、基本類型的參數傳遞

衆所周知,Java 有 8 種基本數據類型,分別是 int、long、byte、short、float、double 、char 和 boolean。它們的值直接存儲在棧中,每看成爲參數傳遞時,都會將原始值(實參)複製一份新的出來,給形參用。形參將會在被調用方法結束時從棧中清除。

來看下面這段代碼:

public class PrimitiveTypeDemo {
    public static void main(String[] args) {
        int age = 18;
        modify(age);
        System.out.println(age);
    }

    private static void modify(int age1) {
        age1 = 30;
    }
}

1)main 方法中的 age 是基本類型,因此它的值 18 直接存儲在棧中。

2)調用 modify() 方法的時候,將爲實參 age 建立一個副本(形參 age1),它的值也爲 18,不過是在棧中的其餘位置。

3)對形參 age 的任何修改都只會影響它自身而不會影響實參。

0三、引用類型的參數傳遞

來看一段建立引用類型變量的代碼:

Writer writer = new Writer(18"沉默王二");

writer 是對象嗎?仍是對象的引用?爲了搞清楚這個問題,咱們能夠把上面的代碼拆分爲兩行代碼:

Writer writer;
writer = new Writer(18"沉默王二");

假如 writer 是對象的話,就不須要經過 new 關鍵字建立對象了,對吧?那也就是說,writer 並非對象,在「=」操做符執行以前,它僅僅是一個變量。那誰是對象呢?new Writer(18, "沉默王二"),它是對象,存儲於堆中;而後,「=」操做符將對象的引用賦值給了 writer 變量,因而 writer 此時應該叫對象引用,它存儲在棧中,保存了對象在堆中的地址。

每當引用類型做爲參數傳遞時,都會建立一個對象引用(實參)的副本(形參),該形參保存的地址和實參同樣。

來看下面這段代碼:

public class ReferenceTypeDemo {
    public static void main(String[] args) {
        Writer a = new Writer(18);
        Writer b = new Writer(18);
        modify(a, b);

        System.out.println(a.getAge());
        System.out.println(b.getAge());
    }

    private static void modify(Writer a1, Writer b1) {
        a1.setAge(30);

        b1 = new Writer(18);
        b1.setAge(30);
    }
}

1)在調用 modify() 方法以前,實參 a 和 b 指向的對象是不同的,儘管 age 都爲 18。

2)在調用 modify() 方法時,實參 a 和 b 都在棧中建立了一個新的副本,分別是 a1 和 b1,但指向的對象是一致的(a 和 a1 指向對象 a,b 和 b1 指向對象 b)。

3)在 modify() 方法中,修改了形參 a1 的 age 爲 30,意味着對象 a 的 age 從 18 變成了 30,而實參 a 指向的也是對象 a,因此 a 的 age 也變成了 30;形參 b1 指向了一個新的對象,隨後 b1 的 age 被修改成 30。

修改 a1 的 age,意味着同時修改了 a 的 age,由於它們指向的對象是一個;修改 b1 的 age,對 b 卻沒有影響,由於它們指向的對象是兩個。

程序輸出的結果以下所示:

30
18

果真和咱們的分析是吻合的。

7、Java 的類和對象

類和對象是 Java 中最基本的兩個概念,能夠說撐起了面向對象編程(OOP)的一片天。對象能夠是現實中看得見的任何物體(一隻特立獨行的豬),也能夠是想象中的任何虛擬物體(能七十二變的孫悟空),Java 經過類(class)來定義這些物體,有什麼狀態(經過字段,或者叫成員變量定義,好比說豬的顏色是純色仍是花色),有什麼行爲(經過方法定義,好比說豬會吃,會睡覺)。

來,讓我來定義一個簡單的類給你看看。

public class Pig {
    private String color;

    public void eat() {
        System.out.println("吃");
    }
}

默認狀況下,每一個 Java 類都會有一個空的構造方法,儘管它在源代碼中是缺省的,但卻能夠經過反編譯字節碼看到它。

public class Pig {
    private String color;

    public Pig() {
    }

    public void eat() {
        System.out.println("吃");
    }
}

沒錯,就是多出來的那個 public Pig() {},參數是空的,方法體是空的。咱們能夠經過 new 關鍵字利用這個構造方法來建立一個對象,代碼以下所示:

 Pig pig = new Pig();

固然了,咱們也能夠主動添加帶參的構造方法。

public class Pig {
    private String color;

    public Pig(String color) {
        this.color = color;
    }

    public void eat() {
        System.out.println("吃");
    }
}

這時候,再查看反編譯後的字節碼時,你會發現缺省的無參構造方法消失了——和源代碼如出一轍。

public class Pig {
    private String color;

    public Pig(String color) {
        this.color = color;
    }

    public void eat() {
        System.out.println("吃");
    }
}

這意味着沒法經過 new Pig() 來建立對象了——編譯器會提醒你追加參數。

好比說你將代碼修改成 new Pig("純白色"),或者添加無參的構造方法。

public class Pig {
    private String color;

    public Pig(String color) {
        this.color = color;
    }

    public Pig() {
    }

    public void eat() {
        System.out.println("吃");
    }
}

使用無參構造方法建立的對象狀態默認值爲 null(color 字符串爲引用類型),若是是基本類型的話,默認值爲對應基本類型的默認值,好比說 int 爲 0,更詳細的見下圖。

(圖片中有一處錯誤,boolean 的默認值爲 false)

接下來,咱們來建立多個 Pig 對象,它的顏色各不相同。

public class PigTest {
    public static void main(String[] args) {
        Pig pigNoColor = new Pig();
        Pig pigWhite = new Pig("純白色");
        Pig pigBlack = new Pig("純黑色");
    }
}

你看,咱們建立了 3 個不一樣花色的 Pig 對象,所有來自於一個類,因而可知類的重要性,只須要定義一次,就能夠屢次使用。

那假如我想改變對象的狀態呢?該怎麼辦?目前毫無辦法,由於沒有任何能夠更改狀態的方法,直接修改 color 是行不通的,由於它的訪問權限修飾符是 private 的。

最好的辦法就是爲 Pig 類追加 getter/setter 方法,就像下面這樣:

public String getColor() {
    return color;
}

public void setColor(String color) {
    this.color = color;
}

經過 setColor() 方法來修改,經過 getColor() 方法獲取狀態,它們的權限修飾符是 public 的。

Pig pigNoColor = new Pig();
pigNoColor.setColor("花色");
System.out.println(pigNoColor.getColor()); // 花色

爲何要這樣設計呢?能夠直接將 color 字段的訪問權限修飾符換成是 public 的啊,不就和 getter/setter 同樣的效果了嗎?

由於有些狀況,某些字段是不容許被隨意修改的,它只有在對象建立的時候初始化一次,好比說豬的年齡,它只能每一年長一歲(舉個例子),沒有月光寶盒讓它變回去。

private int age;

public int getAge() {
    return age;
}

public void increaseAge() {
    this.age++;
}

你看,age 就沒有 setter 方法,只有一個每一年能夠調用一次的 increaseAge() 方法和 getter 方法。若是把 age 的訪問權限修飾符更改成 public,age 就徹底失去控制了,能夠隨意將其重置爲 0 或者負數。

訪問權限修飾符對於 Java 來講,很是重要,目前共有四種:public、private、protected 和 default(缺省)。

一個類只能使用 public 或者 default 修飾,public 修飾的類你以前已經見到過了,如今我來定義一個缺省權限修飾符的類給你欣賞一下。

class Dog {
}

哈哈,其實也沒啥能夠欣賞的。缺省意味着這個類能夠被同一個包下的其餘類進行訪問;而 public 意味着這個類能夠被全部包下的類進行訪問。

假如硬要經過 private 和 protected 來修飾類的話,編譯器會生氣的,它不一樣意。

private 能夠用來修飾類的構造方法、字段和方法,只能被當前類進行訪問。protected 也能夠用來修飾類的構造方法、字段和方法,但它的權限範圍更寬一些,能夠被同一個包中的類進行訪問,或者當前類的子類。

能夠經過下面這張圖來對比一下四個權限修飾符之間的差異:

  • 同一個類中,無論是哪一種權限修飾符,均可以訪問;
  • 同一個包下,private 修飾的沒法訪問;
  • 子類能夠訪問 public 和 protected 修飾的;
  • public 修飾符面向世界,哈哈,能夠被全部的地方訪問到。

8、Java 構造方法

假設如今有一個 Writer 類,它有兩個字段,姓名和年紀:

public class Writer {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Writer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

重寫了 toString() 方法,用於打印 Writer 類的詳情。因爲沒有構造方法,意味着當咱們建立 Writer 對象時,它的字段值並無初始化:

Writer writer = new Writer();
System.out.println(writer.toString());

輸出結果以下所示:

Writer{name='null', age=0}

name 是字符串類型,因此默認值爲 null,age 爲 int 類型,因此默認值爲 0。

讓咱們爲 Writer 類主動加一個無參的構造方法:

public Writer() {
    this.name = "";
    this.age = 0;
}

構造方法也是一個方法,只不過它沒有返回值,默認返回建立對象的類型。須要注意的是,當前構造方法沒有參數,它被稱爲無參構造方法。若是咱們沒有主動建立無參構造方法的話,編譯器會隱式地自動添加一個無參的構造方法。這就是爲何,一開始雖然沒有構造方法,卻能夠使用 new Writer() 建立對象的緣由,只不過,全部的字段都被初始化成了默認值。

接下來,讓咱們添加一個有參的構造方法:

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

如今,咱們建立 Writer 對象的時候就能夠經過對字段值初始化值了。

Writer writer1 = new Writer("沉默王二",18);
System.out.println(writer1.toString());

來看一下打印結果:

Writer{name='沉默王二', age=18}

能夠根據字段的數量添加不一樣參數數量的構造方法,好比說,咱們能夠單獨爲 name 字段添加一個構造方法:

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

爲了可以兼顧 age 字段,咱們能夠經過 this 關鍵字調用其餘的構造方法:

public Writer(String name) {
    this(name,18);
}

把做者的年齡都默認初始化爲 18。若是須要使用父類的構造方法,還能夠使用 super 關鍵字,手冊後面有詳細的介紹。

9、Java 抽象類

當咱們要完成的任務是肯定的,但具體的方式須要隨後開個會投票的話,Java 的抽象類就派上用場了。這句話怎麼理解呢?搬個小板凳坐好,聽我來給你講講。

0一、抽象類的 5 個關鍵點

1)定義抽象類的時候須要用到關鍵字 abstract,放在 class 關鍵字前。

public abstract class AbstractPlayer {
}

關於抽象類的命名,阿里出品的 Java 開發手冊上有強調,「抽象類命名要使用 Abstract 或 Base 開頭」,記住了哦。

2)抽象類不能被實例化,但能夠有子類。

嘗試經過 new 關鍵字實例化的話,編譯器會報錯,提示「類是抽象的,不能實例化」。

經過 extends 關鍵字能夠繼承抽象類,繼承後,BasketballPlayer 類就是 AbstractPlayer 的子類。

public class BasketballPlayer extends AbstractPlayer {
}

3)若是一個類定義了一個或多個抽象方法,那麼這個類必須是抽象類。

當在一個普通類(沒有使用 abstract 關鍵字修飾)中定義了抽象方法,編譯器就會有兩處錯誤提示。

第一處在類級別上,提醒你「這個類必須經過 abstract 關鍵字定義」,or 的那個信息不必,見下圖。

第二處在方法級別上,提醒你「抽象方法所在的類不是抽象的」,見下圖。

4)抽象類能夠同時聲明抽象方法和具體方法,也能夠什麼方法都沒有,但不必。就像下面這樣:

public abstract class AbstractPlayer {
    abstract void play();

    public void sleep() {
        System.out.println("運動員也要休息而不是挑戰極限");
    }
}

5)抽象類派生的子類必須實現父類中定義的抽象方法。好比說,抽象類中定義了 play() 方法,子類中就必須實現。

public class BasketballPlayer extends AbstractPlayer {
    @Override
    void play() {
        System.out.println("我是張伯倫,籃球場上得過 100 分");
    }
}

若是沒有實現的話,編譯器會提醒你「子類必須實現抽象方法」,見下圖。

0二、何時用抽象類

與抽象類息息相關的還有一個概念,就是接口,咱們留到下一篇文章中詳細說,由於要說的知識點仍是蠻多的。你如今只須要有這樣一個概念就好,接口是對行爲的抽象,抽象類是對整個類(包含成員變量和行爲)進行抽象。

(是否是有點明白又有點不明白,彆着急,翹首以盼地等下一篇文章出爐吧)

除了接口以外,還有一個概念就是具體的類,就是不經過 abstract 修飾的普通類,見下面這段代碼中的定義。

public class BasketballPlayer {
   public void play() {
        System.out.println("我是詹姆斯,現役第一人");
    }
}

有接口,有具體類,那何時該使用抽象類呢?

1)咱們但願一些通用的功能被多個子類複用。好比說,AbstractPlayer 抽象類中有一個普通的方法 sleep(),代表全部運動員都須要休息,那麼這個方法就能夠被子類複用。

public abstract class AbstractPlayer {
    public void sleep() {
        System.out.println("運動員也要休息而不是挑戰極限");
    }
}

雖然 AbstractPlayer 類能夠不是抽象類——把 abstract 修飾符去掉也能知足這種場景。但 AbstractPlayer 類可能還會有一個或者多個抽象方法。

BasketballPlayer 繼承了 AbstractPlayer 類,也就擁有了 sleep() 方法。

public class BasketballPlayer extends AbstractPlayer {
}

BasketballPlayer 對象能夠直接調用 sleep() 方法:

BasketballPlayer basketballPlayer = new BasketballPlayer();
basketballPlayer.sleep();

FootballPlayer 繼承了 AbstractPlayer 類,也就擁有了 sleep() 方法。

public class FootballPlayer extends AbstractPlayer {
}

FootballPlayer 對象也能夠直接調用 sleep() 方法:

FootballPlayer footballPlayer = new FootballPlayer();
footballPlayer.sleep();

2)咱們須要在抽象類中定義好 API,而後在子類中擴展實現。好比說,AbstractPlayer 抽象類中有一個抽象方法 play(),定義全部運動員均可以從事某項運動,但須要對應子類去擴展實現。

public abstract class AbstractPlayer {
    abstract void play();
}

BasketballPlayer 繼承了 AbstractPlayer 類,擴展實現了本身的 play() 方法。

public class BasketballPlayer extends AbstractPlayer {
    @Override
    void play() {
        System.out.println("我是張伯倫,我籃球場上得過 100 分,");
    }
}

FootballPlayer 繼承了 AbstractPlayer 類,擴展實現了本身的 play() 方法。

public class FootballPlayer extends AbstractPlayer {
    @Override
    void play() {
        System.out.println("我是C羅,我能接住任意高度的頭球");
    }
}

3)若是父類與子類之間的關係符合 is-a 的層次關係,就能夠使用抽象類,好比說籃球運動員是運動員,足球運動員是運動員。

0三、具體示例

爲了進一步展現抽象類的特性,咱們再來看一個具體的示例。假設如今有一個文件,裏面的內容很是簡單——「Hello World」,如今須要有一個讀取器將內容讀取出來,最好能按照大寫的方式,或者小寫的方式。

這時候,最好定義一個抽象類,好比說 BaseFileReader:

public abstract class BaseFileReader {
    protected Path filePath;

    protected BaseFileReader(Path filePath) {
        this.filePath = filePath;
    }

    public List<String> readFile() throws IOException {
        return Files.lines(filePath)
                .map(this::mapFileLine).collect(Collectors.toList());
    }

    protected abstract String mapFileLine(String line);
}

filePath 爲文件路徑,使用 protected 修飾,代表該成員變量能夠在須要時被子類訪問。

readFile() 方法用來讀取文件,方法體裏面調用了抽象方法 mapFileLine()——須要子類擴展實現大小寫的方式。

你看,BaseFileReader 設計的就很是合理,而且易於擴展,子類只須要專一於具體的大小寫實現方式就能夠了。

小寫的方式:

public class LowercaseFileReader extends BaseFileReader {
    protected LowercaseFileReader(Path filePath) {
        super(filePath);
    }

    @Override
    protected String mapFileLine(String line) {
        return line.toLowerCase();
    }
}

大寫的方式:

public class UppercaseFileReader extends BaseFileReader {
    protected UppercaseFileReader(Path filePath) {
        super(filePath);
    }

    @Override
    protected String mapFileLine(String line) {
        return line.toUpperCase();
    }
}

你看,從文件裏面一行一行讀取內容的代碼被子類複用了——抽象類 BaseFileReader 類中定義的普通方法 readFile()。與此同時,子類只須要專一於本身該作的工做,LowercaseFileReader 以小寫的方式讀取文件內容,UppercaseFileReader 以大寫的方式讀取文件內容。

接下來,咱們來新建一個測試類 FileReaderTest:

public class FileReaderTest {
    public static void main(String[] args) throws URISyntaxException, IOException {
        URL location = FileReaderTest.class.getClassLoader().getResource("helloworld.txt");
        Path path = Paths.get(location.toURI());
        BaseFileReader lowercaseFileReader = new LowercaseFileReader(path);
        BaseFileReader uppercaseFileReader = new UppercaseFileReader(path);
        System.out.println(lowercaseFileReader.readFile());
        System.out.println(uppercaseFileReader.readFile());
    }
}

項目的 resource 目錄下有一個文本文件,名字叫 helloworld.txt。

能夠經過 ClassLoader.getResource() 的方式獲取到該文件的 URI 路徑,而後就能夠使用 LowercaseFileReader 和 UppercaseFileReader 兩種方式讀取到文本內容了。

輸出結果以下所示:

[hello world]
[HELLO WORLD]

10、Java 接口

對於面向對象編程來講,抽象是一個極具魅力的特徵。若是一個程序員的抽象思惟不好,那他在編程中就會遇到不少困難,沒法把業務變成具體的代碼。在 Java 中,能夠經過兩種形式來達到抽象的目的,一種是抽象類,另一種就是接口。

若是你如今就想知道抽象類與接口之間的區別,我能夠提早給你說一個:

  • 一個類只能繼承一個抽象類,但卻能夠實現多個接口。

固然了,在沒有搞清楚接口究竟是什麼,它能夠作什麼以前,這個區別理解起來會有點難度。

0一、接口是什麼

接口是經過 interface 關鍵字定義的,它能夠包含一些常量和方法,來看下面這個示例。

public interface Electronic {
    // 常量
    String LED = "LED";

    // 抽象方法
    int getElectricityUse();

    // 靜態方法
    static boolean isEnergyEfficient(String electtronicType) {
        return electtronicType.equals(LED);
    }

    // 默認方法
    default void printDescription() {
        System.out.println("電子");
    }
}

1)接口中定義的變量會在編譯的時候自動加上 public static final 修飾符,也就是說 LED 變量實際上是一個常量。

Java 官方文檔上有這樣的聲明:

Every field declaration in the body of an interface is implicitly public, static, and final.

換句話說,接口能夠用來做爲常量類使用,還能省略掉 public static final,看似不錯的一種選擇,對吧?

不過,這種選擇並不可取。由於接口的本意是對方法進行抽象,而常量接口會對子類中的變量形成命名空間上的「污染」。

2)沒有使用 privatedefault 或者 static 關鍵字修飾的方法是隱式抽象的,在編譯的時候會自動加上 public abstract 修飾符。也就是說 getElectricityUse() 實際上是一個抽象方法,沒有方法體——這是定義接口的本意。

3)從 Java 8 開始,接口中容許有靜態方法,好比說 isEnergyEfficient() 方法。

靜態方法沒法由(實現了該接口的)類的對象調用,它只能經過接口的名字來調用,好比說 Electronic.isEnergyEfficient("LED")

接口中定義靜態方法的目的是爲了提供一種簡單的機制,使咱們沒必要建立對象就能調用方法,從而提升接口的競爭力。

4)接口中容許定義 default 方法也是從 Java 8 開始的,好比說 printDescription(),它始終由一個代碼塊組成,爲實現該接口而不覆蓋該方法的類提供默認實現,也就是說,沒法直接使用一個「;」號來結束默認方法——編譯器會報錯的。

容許在接口中定義默認方法的理由是很充分的,由於一個接口可能有多個實現類,這些類就必須實現接口中定義的抽象類,不然編譯器就會報錯。假如咱們須要在全部的實現類中追加某個具體的方法,在沒有 default 方法的幫助下,咱們就必須挨個對實現類進行修改。

來看一下 Electronic 接口反編譯後的字節碼吧,你會發現,接口中定義的全部變量或者方法,都會自動添加上 public 關鍵字——假如你想知道編譯器在背後都默默作了哪些輔助,記住反編譯字節碼就對了。

public interface Electronic
{

    public abstract int getElectricityUse();

    public static boolean isEnergyEfficient(String electtronicType)
    
{
        return electtronicType.equals("LED");
    }

    public void printDescription()
    
{
        System.out.println("\u7535\u5B50");
    }

    public static final String LED = "LED";
}

有些讀者可能會問,「二哥,爲何我反編譯後的字節碼和你的不同,你用了什麼反編譯工具?」其實沒有什麼祕密,微信搜「沉默王二」回覆關鍵字「JAD」就能夠免費獲取了,超級好用。

0二、定義接口的注意事項

由以前的例子咱們就能夠得出下面這些結論:

  • 接口中容許定義變量
  • 接口中容許定義抽象方法
  • 接口中容許定義靜態方法(Java 8 以後)
  • 接口中容許定義默認方法(Java 8 以後)

除此以外,咱們還應該知道:

1)接口不容許直接實例化。

須要定義一個類去實現接口,而後再實例化。

public class Computer implements Electronic {

    public static void main(String[] args) {
        new Computer();
    }

    @Override
    public int getElectricityUse() {
        return 0;
    }
}

2)接口能夠是空的,既不定義變量,也不定義方法。

public interface Serializable {
}

Serializable 是最典型的一個空的接口,我以前分享過一篇文章《Java Serializable:明明就一個空的接口嘛》,感興趣的讀者能夠去個人我的博客看一看,你就明白了空接口的意義。

http://www.itwanger.com/java/2019/11/14/java-serializable.html

3)不要在定義接口的時候使用 final 關鍵字,不然會報編譯錯誤,由於接口就是爲了讓子類實現的,而 final 阻止了這種行爲。

4)接口的抽象方法不能是 private、protected 或者 final。

5)接口的變量是隱式 public static final,因此其值沒法改變。

0三、接口能夠作什麼

1)使某些實現類具備咱們想要的功能,好比說,實現了 Cloneable 接口的類具備拷貝的功能,實現了 Comparable 或者 Comparator 的類具備比較功能。

Cloneable 和 Serializable 同樣,都屬於標記型接口,它們內部都是空的。實現了 Cloneable 接口的類能夠使用 Object.clone() 方法,不然會拋出 CloneNotSupportedException。

public class CloneableTest implements Cloneable {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        CloneableTest c1 = new CloneableTest();
        CloneableTest c2 = (CloneableTest) c1.clone();
    }
}

運行後沒有報錯。如今把 implements Cloneable 去掉。

public class CloneableTest {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        CloneableTest c1 = new CloneableTest();
        CloneableTest c2 = (CloneableTest) c1.clone();

    }
}

運行後拋出 CloneNotSupportedException:

Exception in thread "main" java.lang.CloneNotSupportedException: com.cmower.baeldung.interface1.CloneableTest
    at java.base/java.lang.Object.clone(Native Method)
    at com.cmower.baeldung.interface1.CloneableTest.clone(CloneableTest.java:6)
    at com.cmower.baeldung.interface1.CloneableTest.main(CloneableTest.java:11)

至於 Comparable 和 Comparator 的用法,感興趣的讀者能夠參照我以前寫的另一篇文章《來吧,一文完全搞懂Java中的Comparable和Comparator》。

http://www.itwanger.com/java/2020/01/04/java-comparable-comparator.html

2)Java 原則上只支持單一繼承,但經過接口能夠實現多重繼承的目的。

可能有些讀者會問,「二哥,爲何 Java 只支持單一繼承?」簡單來解釋一下。

若是有兩個類共同繼承(extends)一個有特定方法的父類,那麼該方法會被兩個子類重寫。而後,若是你決定同時繼承這兩個子類,那麼在你調用該重寫方法時,編譯器不能識別你要調用哪一個子類的方法。這也正是著名的菱形問題,見下圖。

ClassC 同時繼承了 ClassA 和 ClassB,ClassC 的對象在調用 ClassA 和 ClassB 中重載的方法時,就不知道該調用 ClassA 的方法,仍是 ClassB 的方法。

接口沒有這方面的困擾。來定義兩個接口,Fly 會飛,Run 會跑。

public interface Fly {
    void fly();
}
public interface Run {
    void run();
}

而後讓一個類同時實現這兩個接口。

public class Pig implements Fly,Run{
    @Override
    public void fly() {
        System.out.println("會飛的豬");
    }

    @Override
    public void run() {
        System.out.println("會跑的豬");
    }
}

這就在某種形式上達到了多重繼承的目的:現實世界裏,豬的確只會跑,但在雷軍的眼裏,站在風口的豬就會飛,這就須要賦予這隻豬更多的能力,經過抽象類是沒法實現的,只能經過接口。

3)實現多態。

什麼是多態呢?通俗的理解,就是同一個事件發生在不一樣的對象上會產生不一樣的結果,鼠標左鍵點擊窗口上的 X 號能夠關閉窗口,點擊超連接卻能夠打開新的網頁。

多態能夠經過繼承(extends)的關係實現,也能夠經過接口的形式實現。來看這樣一個例子。

Shape 是表示一個形狀。

public interface Shape {
    String name();
}

圓是一個形狀。

public class Circle implements Shape {
    @Override
    public String name() {
        return "圓";
    }
}

正方形也是一個形狀。

public class Square implements Shape {
    @Override
    public String name() {
        return "正方形";
    }
}

而後來看測試類。

List<Shape> shapes = new ArrayList<>();
Shape circleShape = new Circle();
Shape squareShape = new Square();

shapes.add(circleShape);
shapes.add(squareShape);

for (Shape shape : shapes) {
    System.out.println(shape.name());
}

多態的存在 3 個前提:

一、要有繼承關係,Circle 和 Square 都實現了 Shape 接口
二、子類要重寫父類的方法,Circle 和 Square 都重寫了 name() 方法
三、父類引用指向子類對象,circleShape 和 squareShape 的類型都爲 Shape,但前者指向的是 Circle 對象,後者指向的是 Square 對象。

而後,咱們來看一下測試結果:


正方形

也就意味着,儘管在 for 循環中,shape 的類型都爲 Shape,但在調用 name() 方法的時候,它知道 Circle 對象應該調用 Circle 類的 name() 方法,Square 對象應該調用 Square 類的 name() 方法。

0四、接口與抽象類的區別

好了,關於接口的一切,你應該都搞清楚了。如今回到讀者春夏秋冬的那條留言,「兄弟,說說抽象類和接口之間的區別?」

1)語法層面上

  • 接口中不能有 public 和 protected 修飾的方法,抽象類中能夠有。
  • 接口中的變量只能是隱式的常量,抽象類中能夠有任意類型的變量。
  • 一個類只能繼承一個抽象類,但卻能夠實現多個接口。

2)設計層面上

抽象類是對類的一種抽象,繼承抽象類的類和抽象類自己是一種 is-a 的關係。

接口是對類的某種行爲的一種抽象,接口和類之間並無很強的關聯關係,全部的類均可以實現 Serializable 接口,從而具備序列化的功能。

就這麼多吧,能說道這份上,我相信面試官就不會爲難你了。

11、Java 繼承

在 Java 中,一個類能夠繼承另一個類或者實現多個接口,我想這一點,大部分的讀者應該都知道了。還有一點,我不肯定你們是否知道,就是一個接口也能夠繼承另一個接口,就像下面這樣:

public interface OneInterface extends Cloneable {
}

這樣作有什麼好處呢?我想有一部分讀者應該已經猜出來了,就是實現了 OneInterface 接口的類,也能夠使用 Object.clone() 方法了。

public class TestInterface implements OneInterface {
    public static void main(String[] args) throws CloneNotSupportedException {
        TestInterface c1 = new TestInterface();
        TestInterface c2 = (TestInterface) c1.clone();
    }
}

除此以外,咱們還能夠在 OneInterface 接口中定義其餘一些抽象方法(好比說深拷貝),使該接口擁有 Cloneable 所不具備的功能。

public interface OneInterface extends Cloneable {
    void deepClone();
}

看到了吧?這就是繼承的好處:子接口擁有了父接口的方法,使得子接口具備了父接口相同的行爲;同時,子接口還能夠在此基礎上自由發揮,添加屬於本身的行爲

以上,把「接口」換成「類」,結論一樣成立。讓咱們來定義一個普通的父類 Wanger:

public class Wanger {
    int age;
    String name;
    void write() {
        System.out.println("我寫了本《基督山伯爵》");
    }
}

而後,咱們再來定義一個子類 Wangxiaoer,使用關鍵字 extends 來繼承父類 Wanger:

public class Wangxiaoer extends Wanger{
    @Override
    void write() {
        System.out.println("我寫了本《茶花女》");
    }
}

咱們能夠將通用的方法和成員變量放在父類中,達到代碼複用的目的;而後將特殊的方法和成員變量放在子類中,除此以外,子類還能夠覆蓋父類的方法(好比write() 方法)。這樣,子類也就煥發出了新的生命力。

Java 只支持單一繼承,這一點,我在上一篇接口的文章中已經提到過了。若是一個類在定義的時候沒有使用 extends 關鍵字,那麼它隱式地繼承了 java.lang.Object 類——在我看來,這恐怕就是 Java 號稱萬物皆對象的真正緣由了。

那究竟子類繼承了父類的什麼呢?

子類能夠繼承父類的非 private 成員變量,爲了驗證這一點,咱們來看下面這個示例。

public class Wanger {
    String defaultName;
    private String privateName;
    public String publicName;
    protected String protectedName;
}

父類 Wanger 定義了四種類型的成員變量,缺省的 defaultName、私有的 privateName、共有的 publicName、受保護的 protectedName。

在子類 Wangxiaoer 中定義一個測試方法 testVariable()

能夠確認,除了私有的 privateName,其餘三種類型的成員變量均可以繼承到。

同理,子類能夠繼承父類的非 private 方法,爲了驗證這一點,咱們來看下面這個示例。

public class Wanger {
    void write() {
    }

    private void privateWrite() {
    }

    public void publicWrite() {
    }

    protected void protectedWrite() {
    }
}

父類 Wanger 定義了四種類型的方法,缺省的 write、私有的 privateWrite()、共有的 publicWrite()、受保護的 protectedWrite()。

在子類 Wangxiaoer 中定義一個 main 方法,並使用 new 關鍵字新建一個子類對象:

能夠確認,除了私有的 privateWrite(),其餘三種類型的方法均可以繼承到。

不過,子類沒法繼承父類的構造方法。若是父類的構造方法是帶有參數的,代碼以下所示:

public class Wanger {
    int age;
    String name;

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

則必須在子類的構造器中顯式地經過 super 關鍵字進行調用,不然編譯器將提示如下錯誤:

修復後的代碼以下所示:

public class Wangxiaoer extends Wanger{
    public Wangxiaoer(int age, String name) {
        super(age, name);
    }
}

is-a 是繼承的一個明顯特徵,就是說子類的對象引用類型能夠是一個父類類型。

public class Wangxiaoer extends Wanger{
    public static void main(String[] args) {
        Wanger wangxiaoer = new Wangxiaoer();
    }
}

同理,子接口的實現類的對象引用類型也能夠是一個父接口類型。

public interface OneInterface extends Cloneable {
}
public class TestInterface implements OneInterface {
    public static void main(String[] args) {
        Cloneable c1 = new TestInterface();
    }
}

儘管一個類只能繼承一個類,但一個類卻能夠實現多個接口,這一點,我在上一篇文章也提到過了。另外,還有一點我也提到了,就是 Java 8 以後,接口中能夠定義 default 方法,這很方便,但也帶來了新的問題:

若是一個類實現了多個接口,而這些接口中定義了相同簽名的 default 方法,那麼這個類就要重寫該方法,不然編譯沒法經過。

FlyInterface 是一個會飛的接口,裏面有一個簽名爲 sleep() 的默認方法:

public interface FlyInterface {
    void fly();
    default void sleep() {
        System.out.println("睡着飛");
    }
}

RunInterface 是一個會跑的接口,裏面也有一個簽名爲 sleep() 的默認方法:

public interface RunInterface {
    void run();
    default void sleep() {
        System.out.println("睡着跑");
    }
}

Pig 類實現了 FlyInterface 和 RunInterface 兩個接口,但這時候編譯出錯了。

本來,default 方法就是爲實現該接口而不覆蓋該方法的類提供默認實現的,如今,相同方法簽名的 sleep() 方法把編譯器搞懵逼了,只能重寫了。

public class Pig implements FlyInterfaceRunInterface {

    @Override
    public void fly() {
        System.out.println("會飛的豬");
    }

    @Override
    public void sleep() {
        System.out.println("只能重寫了");
    }

    @Override
    public void run() {
        System.out.println("會跑的豬");
    }
}

類雖然不能繼承多個類,但接口卻能夠繼承多個接口,這一點,我不知道有沒有觸及到一些讀者的知識盲區。

public interface WalkInterface extends FlyInterface,RunInterface{
    void walk();
}

12、this 關鍵字

在 Java 中,this 關鍵字指的是當前對象(它的方法正在被調用)的引用,能理解吧,各位親?不理解的話,咱們繼續往下看。

看完再不明白,你過來捶爆我,我保證不還手,只要不打臉。

0一、消除字段歧義

我敢賭一毛錢,全部的讀者,無論男女老幼,應該都知道這種用法,畢竟寫構造方法的時候常常用啊。誰要不知道,過來,我給你發一毛錢紅包,只要你臉皮夠厚。

public class Writer {
    private int age;
    private String name;

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

Writer 類有兩個成員變量,分別是 age 和 name,在使用有參構造函數的時候,若是參數名和成員變量的名字相同,就須要使用 this 關鍵字消除歧義:this.age 是指成員變量,age 是指構造方法的參數。

0二、引用類的其餘構造方法

當一個類的構造方法有多個,而且它們之間有交集的話,就能夠使用 this 關鍵字來調用不一樣的構造方法,從而減小代碼量。

好比說,在無參構造方法中調用有參構造方法:

public class Writer {
    private int age;
    private String name;

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

    public Writer() {
        this(18"沉默王二");
    }
}

也能夠在有參構造方法中調用無參構造方法:

public class Writer {
    private int age;
    private String name;

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

    public Writer() {
    }
}

須要注意的是,this() 必須是構造方法中的第一條語句,不然就會報錯。

0三、做爲參數傳遞

在下例中,有一個無參的構造方法,裏面調用了 print() 方法,參數只有一個 this 關鍵字。

public class ThisTest {
    public ThisTest() {
        print(this);
    }

    private void print(ThisTest thisTest) {
        System.out.println("print " +thisTest);
    }

    public static void main(String[] args) {
        ThisTest test = new ThisTest();
        System.out.println("main " + test);
    }
}

來打印看一下結果:

print com.cmower.baeldung.this1.ThisTest@573fd745
main com.cmower.baeldung.this1.ThisTest@573fd745

從結果中能夠看得出來,this 就是咱們在 main() 方法中使用 new 關鍵字建立的 ThisTest 對象。

0四、鏈式調用

學過 JavaScript,或者 jQuery 的讀者可能對鏈式調用比較熟悉,相似於 a.b().c().d(),彷彿能無窮無盡調用下去。

在 Java 中,對應的專有名詞叫 Builder 模式,來看一個示例。

public class Writer {
    private int age;
    private String name;
    private String bookName;

    public Writer(WriterBuilder builder) {
        this.age = builder.age;
        this.name = builder.name;
        this.bookName = builder.bookName;
    }

    public static class WriterBuilder {
        public String bookName;
        private int age;
        private String name;

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

        public WriterBuilder writeBook(String bookName) {
            this.bookName = bookName;
            return this;
        }

        public Writer build() {
            return new Writer(this);
        }
    }
}

Writer 類有三個成員變量,分別是 age、name 和 bookName,還有它們仨對應的一個構造方法,參數是一個內部靜態類 WriterBuilder。

內部類 WriterBuilder 也有三個成員變量,和 Writer 類一致,不一樣的是,WriterBuilder 類的構造方法裏面只有 age 和 name 賦值了,另一個成員變量 bookName 經過單獨的方法 writeBook() 來賦值,注意,該方法的返回類型是 WriterBuilder,最後使用 return 返回了 this 關鍵字。

最後的 build() 方法用來建立一個 Writer 對象,參數爲 this 關鍵字,也就是當前的 WriterBuilder 對象。

這時候,建立 Writer 對象就能夠經過鏈式調用的方式。

Writer writer = new Writer.WriterBuilder(18,"沉默王二")
                .writeBook("《Web全棧開發進階之路》")
                .build();

0五、在內部類中訪問外部類對象

說實話,自從 Java 8 的函數式編程出現後,就不多用到 this 在內部類中訪問外部類對象了。來看一個示例:

public class ThisInnerTest {
    private String name;

    class InnerClass {
        public InnerClass() {
            ThisInnerTest thisInnerTest = ThisInnerTest.this;
            String outerName = thisInnerTest.name;
        }
    }
}

在內部類 InnerClass 的構造方法中,經過外部類.this 能夠獲取到外部類對象,而後就能夠使用外部類的成員變量了,好比說 name。

十3、super 關鍵字

簡而言之,super 關鍵字就是用來訪問父類的。

先來看父類:

public class SuperBase {
    String message = "父類";

    public SuperBase(String message) {
        this.message = message;
    }

    public SuperBase() {
    }

    public void printMessage() {
        System.out.println(message);
    }
}

再來看子類:

public class SuperSub extends SuperBase {
    String message = "子類";

    public SuperSub(String message) {
        super(message);
    }

    public SuperSub() {
        super.printMessage();
        printMessage();
    }

    public void getParentMessage() {
        System.out.println(super.message);
    }

    public void printMessage() {
        System.out.println(message);
    }
}

1)super 關鍵字可用於訪問父類的構造方法

你看,子類能夠經過 super(message) 來調用父類的構造方法。如今來新建一個 SuperSub 對象,看看輸出結果是什麼:

SuperSub superSub = new SuperSub("子類的message");

new 關鍵字在調用構造方法建立子類對象的時候,會經過 super 關鍵字初始化父類的 message,因此此此時父類的 message 會輸出「子類的message」。

2)super 關鍵字能夠訪問父類的變量

上述例子中的 SuperSub 類中就有,getParentMessage() 經過 super.message 方法父類的同名成員變量 message。

3)當方法發生重寫時,super 關鍵字能夠訪問父類的同名方法

上述例子中的 SuperSub 類中就有,無參的構造方法 SuperSub() 中就使用 super.printMessage() 調用了父類的同名方法。

十4、重寫和重載

先來看一段重寫的代碼吧。

class LaoWang{
    public void write() {
        System.out.println("老王寫了一本《基督山伯爵》");
    }
}
public class XiaoWang extends LaoWang {
    @Override
    public void write() {
        System.out.println("小王寫了一本《茶花女》");
    }
}

重寫的兩個方法名相同,方法參數的個數也相同;不過一個方法在父類中,另一個在子類中。就好像父類 LaoWang 有一個 write() 方法(無參),方法體是寫一本《基督山伯爵》;子類 XiaoWang 重寫了父類的 write() 方法(無參),但方法體是寫一本《茶花女》。

來寫一段測試代碼。

public class OverridingTest {
    public static void main(String[] args) {
        LaoWang wang = new XiaoWang();
        wang.write();
    }
}

你們猜結果是什麼?

小王寫了一本《茶花女》

在上面的代碼中,們聲明瞭一個類型爲 LaoWang 的變量 wang。在編譯期間,編譯器會檢查 LaoWang 類是否包含了 write() 方法,發現 LaoWang 類有,因而編譯經過。在運行期間,new 了一個 XiaoWang 對象,並將其賦值給 wang,此時 Java 虛擬機知道 wang 引用的是 XiaoWang 對象,因此調用的是子類 XiaoWang 中的 write() 方法而不是父類 LaoWang 中的 write() 方法,所以輸出結果爲「小王寫了一本《茶花女》」。

再來看一段重載的代碼吧。

class LaoWang{
    public void read() {
        System.out.println("老王讀了一本《Web全棧開發進階之路》");
    }

    public void read(String bookname) {
        System.out.println("老王讀了一本《" + bookname + "》");
    }
}

重載的兩個方法名相同,但方法參數的個數不一樣,另外也不涉及到繼承,兩個方法在同一個類中。就好像類 LaoWang 有兩個方法,名字都是 read(),但一個有參數(書名),另一個沒有(只能讀寫死的一本書)。

來寫一段測試代碼。

public class OverloadingTest {
    public static void main(String[] args) {
        LaoWang wang = new LaoWang();
        wang.read();
        wang.read("金");
    }
}

這結果就不用猜了。變量 wang 的類型爲 LaoWang,wang.read() 調用的是無參的 read() 方法,所以先輸出「老王讀了一本《Web全棧開發進階之路》」;wang.read("金") 調用的是有參的 read(bookname) 方法,所以後輸出「老王讀了一本《》」。在編譯期間,編譯器就知道這兩個 read() 方法時不一樣的,由於它們的方法簽名(=方法名稱+方法參數)不一樣。

簡單的來總結一下:

1)編譯器沒法決定調用哪一個重寫的方法,由於只從變量的類型上是沒法作出判斷的,要在運行時才能決定;但編譯器能夠明確地知道該調用哪一個重載的方法,由於引用類型是肯定的,參數個數決定了該調用哪一個方法。

2)多態針對的是重寫,而不是重載。

哎,後悔啊,早年我要是能把這道面試題吃透的話,也不用被老馬刁難了。吟一首詩感慨一下人生吧。

青青園中葵,朝露待日晞。
陽春佈德澤,萬物生光輝。
常恐秋節至,焜黃華葉衰。
百川東到海,什麼時候復西歸?
少壯不努力,老大徒傷悲

另外,我想要告訴你們的是,重寫(Override)和重載(Overload)是 Java 中兩個很是重要的概念,新手常常會被它們倆迷惑,由於它們倆的英文名字太像了,中文翻譯也只差一個字。難,太難了。

十5、static 關鍵字

先來個提綱挈領(唉呀媽呀,成語區博主上線了)吧:

static 關鍵字可用於變量、方法、代碼塊和內部類,表示某個特定的成員只屬於某個類自己,而不是該類的某個對象。

0一、靜態變量

靜態變量也叫類變量,它屬於一個類,而不是這個類的對象。

public class Writer {
    private String name;
    private int age;
    public static int countOfWriters;

    public Writer(String name, int age) {
        this.name = name;
        this.age = age;
        countOfWriters++;
    }

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

其中,countOfWriters 被稱爲靜態變量,它有別於 name 和 age 這兩個成員變量,由於它前面多了一個修飾符 static

這意味着不管這個類被初始化多少次,靜態變量的值都會在全部類的對象中共享。

Writer w1 = new Writer("沉默王二",18);
Writer w2 = new Writer("沉默王三",16);

System.out.println(Writer.countOfWriters);

按照上面的邏輯,你應該能推理得出,countOfWriters 的值此時應該爲 2 而不是 1。從內存的角度來看,靜態變量將會存儲在 Java 虛擬機中一個名叫「Metaspace」(元空間,Java 8 以後)的特定池中。

靜態變量和成員變量有着很大的不一樣,成員變量的值屬於某個對象,不一樣的對象之間,值是不共享的;但靜態變量不是的,它能夠用來統計對象的數量,由於它是共享的。就像上面例子中的 countOfWriters,建立一個對象的時候,它的值爲 1,建立兩個對象的時候,它的值就爲 2。

簡單小結一下:

1)因爲靜態變量屬於一個類,因此不要經過對象引用來訪問,而應該直接經過類名來訪問;

2)不須要初始化類就能夠訪問靜態變量。

public class WriterDemo {
    public static void main(String[] args) {
        System.out.println(Writer.countOfWriters); // 輸出 0
    }
}

0二、靜態方法

靜態方法也叫類方法,它和靜態變量相似,屬於一個類,而不是這個類的對象。

public static void setCountOfWriters(int countOfWriters) {
    Writer.countOfWriters = countOfWriters;
}

setCountOfWriters() 就是一個靜態方法,它由 static 關鍵字修飾。

若是你用過 java.lang.Math 類或者 Apache 的一些工具類(好比說 StringUtils)的話,對靜態方法必定不會感動陌生。

Math 類的幾乎全部方法都是靜態的,能夠直接經過類名來調用,不須要建立類的對象。

簡單小結一下:

1)Java 中的靜態方法在編譯時解析,由於靜態方法不能被重寫(方法重寫發生在運行時階段,爲了多態)。

2)抽象方法不能是靜態的。

3)靜態方法不能使用 this 和 super 關鍵字。

4)成員方法能夠直接訪問其餘成員方法和成員變量。

5)成員方法也能夠直接方法靜態方法和靜態變量。

6)靜態方法能夠訪問全部其餘靜態方法和靜態變量。

7)靜態方法沒法直接訪問成員方法和成員變量。

0三、靜態代碼塊

靜態代碼塊能夠用來初始化靜態變量,儘管靜態方法也能夠在聲明的時候直接初始化,但有些時候,咱們須要多行代碼來完成初始化。

public class StaticBlockDemo {
    public static List<String> writes = new ArrayList<>();

    static {
        writes.add("沉默王二");
        writes.add("沉默王三");
        writes.add("沉默王四");

        System.out.println("第一塊");
    }

    static {
        writes.add("沉默王五");
        writes.add("沉默王六");

        System.out.println("第二塊");
    }
}

writes 是一個靜態的 ArrayList,因此不太可能在聲明的時候完成初始化,所以須要在靜態代碼塊中完成初始化。

簡單小結一下:

1)一個類能夠有多個靜態代碼塊。

2)靜態代碼塊的解析和執行順序和它在類中的位置保持一致。爲了驗證這個結論,能夠在 StaticBlockDemo 類中加入空的 main 方法,執行完的結果以下所示:

第一塊
第二塊

0四、靜態內部類

Java 容許咱們在一個類中聲明一個內部類,它提供了一種使人信服的方式,容許咱們只在一個地方使用一些變量,使代碼更具備條理性和可讀性。

常見的內部類有四種,成員內部類、局部內部類、匿名內部類和靜態內部類,限於篇幅緣由,前三種不在咱們本次文章的討論範圍,之後有機會再細說。

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        public static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

以上這段代碼是否是特別熟悉,對,這就是建立單例的一種方式,第一次加載 Singleton 類時並不會初始化 instance,只有第一次調用 getInstance() 方法時 Java 虛擬機纔開始加載 SingletonHolder 並初始化 instance,這樣不只能確保線程安全也能保證 Singleton 類的惟一性。不過,建立單例更優雅的一種方式是使用枚舉。

簡單小結一下:

1)靜態內部類不能訪問外部類的全部成員變量。

2)靜態內部類能夠訪問外部類的全部靜態變量,包括私有靜態變量。

3)外部類不能聲明爲 static。

十6、Java 枚舉

開門見山地說吧,enum(枚舉)是 Java 1.5 時引入的關鍵字,它表示一種特殊類型的類,默認繼承自 java.lang.Enum。

爲了證實這一點,咱們來新建一個枚舉 PlayerType:

public enum PlayerType {
    TENNIS,
    FOOTBALL,
    BASKETBALL
}

兩個關鍵字帶一個類名,還有大括號,以及三個大寫的單詞,但沒看到繼承 Enum 類啊?彆着急,心急吃不了熱豆腐啊。使用 JAD 查看一下反編譯後的字節碼,就一清二楚了。

public final class PlayerType extends Enum
{

    public static PlayerType[] values()
    {
        return (PlayerType[])$VALUES.clone();
    }

    public static PlayerType valueOf(String name)
    
{
        return (PlayerType)Enum.valueOf(com/cmower/baeldung/enum1/PlayerType, name);
    }

    private PlayerType(String s, int i)
    
{
        super(s, i);
    }

    public static final PlayerType TENNIS;
    public static final PlayerType FOOTBALL;
    public static final PlayerType BASKETBALL;
    private static final PlayerType $VALUES[];

    static 
    {
        TENNIS = new PlayerType("TENNIS"0);
        FOOTBALL = new PlayerType("FOOTBALL"1);
        BASKETBALL = new PlayerType("BASKETBALL"2);
        $VALUES = (new PlayerType[] {
            TENNIS, FOOTBALL, BASKETBALL
        });
    }
}

看到沒?PlayerType 類是 final 的,而且繼承自 Enum 類。這些工做咱們程序員沒作,編譯器幫咱們悄悄地作了。此外,它還附帶幾個有用靜態方法,好比說 values()valueOf(String name)

0一、內部枚舉

好的,小夥伴們應該已經清楚枚舉長什麼樣子了吧?既然枚舉是一種特殊的類,那它實際上是能夠定義在一個類的內部的,這樣它的做用域就能夠限定於這個外部類中使用。

public class Player {
    private PlayerType type;
    public enum PlayerType {
        TENNIS,
        FOOTBALL,
        BASKETBALL
    }

    public boolean isBasketballPlayer() {
      return getType() == PlayerType.BASKETBALL;
    }

    public PlayerType getType() {
        return type;
    }

    public void setType(PlayerType type) {
        this.type = type;
    }
}

PlayerType 就至關於 Player 的內部類,isBasketballPlayer() 方法用來判斷運動員是不是一個籃球運動員。

因爲枚舉是 final 的,能夠確保在 Java 虛擬機中僅有一個常量對象(能夠參照反編譯後的靜態代碼塊「static 關鍵字帶大括號的那部分代碼」),因此咱們能夠很安全地使用「==」運算符來比較兩個枚舉是否相等,參照 isBasketballPlayer() 方法。

那爲何不使用 equals() 方法判斷呢?

if(player.getType().equals(Player.PlayerType.BASKETBALL)){};
if(player.getType() == Player.PlayerType.BASKETBALL){};

「==」運算符比較的時候,若是兩個對象都爲 null,並不會發生 NullPointerException,而 equals() 方法則會。

另外, 「==」運算符會在編譯時進行檢查,若是兩側的類型不匹配,會提示錯誤,而 equals() 方法則不會。

0二、枚舉可用於 switch 語句

這個我在以前的一篇我去的文章中詳細地說明過了,感興趣的小夥伴能夠點擊連接跳轉過去看一下。

switch (playerType) {
        case TENNIS:
            return "網球運動員費德勒";
        case FOOTBALL:
            return "足球運動員C羅";
        case BASKETBALL:
            return "籃球運動員詹姆斯";
        case UNKNOWN:
            throw new IllegalArgumentException("未知");
        default:
            throw new IllegalArgumentException(
                    "運動員類型: " + playerType);

    }

0三、枚舉能夠有構造方法

若是枚舉中須要包含更多信息的話,能夠爲其添加一些字段,好比下面示例中的 name,此時須要爲枚舉添加一個帶參的構造方法,這樣就能夠在定義枚舉時添加對應的名稱了。

public enum PlayerType {
    TENNIS("網球"),
    FOOTBALL("足球"),
    BASKETBALL("籃球");

    private String name;

    PlayerType(String name) {
        this.name = name;
    }
}

0四、EnumSet

EnumSet 是一個專門針對枚舉類型的 Set 接口的實現類,它是處理枚舉類型數據的一把利器,很是高效(內部實現是位向量,我也搞不懂)。

由於 EnumSet 是一個抽象類,因此建立 EnumSet 時不能使用 new 關鍵字。不過,EnumSet 提供了不少有用的靜態工廠方法:

下面的示例中使用 noneOf() 建立了一個空的 PlayerType 的 EnumSet;使用 allOf() 建立了一個包含全部 PlayerType 的 EnumSet。

public class EnumSetTest {
    public enum PlayerType {
        TENNIS,
        FOOTBALL,
        BASKETBALL
    }

    public static void main(String[] args) {
        EnumSet<PlayerType> enumSetNone = EnumSet.noneOf(PlayerType.class);
        System.out.println(enumSetNone);

        EnumSet<PlayerType> enumSetAll = EnumSet.allOf(PlayerType.class);
        System.out.println(enumSetAll);
    }
}

程序輸出結果以下所示:

[]
[TENNIS, FOOTBALL, BASKETBALL]

有了 EnumSet 後,就能夠使用 Set 的一些方法了:

0五、EnumMap

EnumMap 是一個專門針對枚舉類型的 Map 接口的實現類,它能夠將枚舉常量做爲鍵來使用。EnumMap 的效率比 HashMap 還要高,能夠直接經過數組下標(枚舉的 ordinal 值)訪問到元素。

和 EnumSet 不一樣,EnumMap 不是一個抽象類,因此建立 EnumMap 時能夠使用 new 關鍵字:

EnumMap<PlayerType, String> enumMap = new EnumMap<>(PlayerType.class);

有了 EnumMap 對象後就能夠使用 Map 的一些方法了:

和 HashMap 的使用方法大體相同,來看下面的例子:

EnumMap<PlayerType, String> enumMap = new EnumMap<>(PlayerType.class);
enumMap.put(PlayerType.BASKETBALL,"籃球運動員");
enumMap.put(PlayerType.FOOTBALL,"足球運動員");
enumMap.put(PlayerType.TENNIS,"網球運動員");
System.out.println(enumMap);

System.out.println(enumMap.get(PlayerType.BASKETBALL));
System.out.println(enumMap.containsKey(PlayerType.BASKETBALL));
System.out.println(enumMap.remove(PlayerType.BASKETBALL));

程序輸出結果以下所示:

{TENNIS=網球運動員, FOOTBALL=足球運動員, BASKETBALL=籃球運動員}
籃球運動員
true
籃球運動員

0六、單例

一般狀況下,實現一個單例並不是易事,不信,來看下面這段代碼

public class Singleton {  
    private volatile static Singleton singleton; 
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {
        synchronized (Singleton.class) { 
        if (singleton == null) {  
            singleton = new Singleton(); 
        }  
        }  
    }  
    return singleton;  
    }  
}

但枚舉的出現,讓代碼量減小到極致:

public enum EasySingleton{
    INSTANCE;
}

完事了,真的超級短,有沒有?枚舉默認實現了 Serializable 接口,所以 Java 虛擬機能夠保證該類爲單例,這與傳統的實現方式不大相同。傳統方式中,咱們必須確保單例在反序列化期間不能建立任何新實例。

0七、枚舉可與數據庫交互

咱們能夠配合 Mybatis 將數據庫字段轉換爲枚舉類型。如今假設有一個數據庫字段 check_type 的類型以下:

`check_type` int(1) DEFAULT NULL COMMENT '檢查類型(1:未經過、2:經過)',

它對應的枚舉類型爲 CheckType,代碼以下:

public enum CheckType {
    NO_PASS(0"未經過"), PASS(1"經過");
    private int key;

    private String text;

    private CheckType(int key, String text) {
        this.key = key;
        this.text = text;
    }

    public int getKey() {
        return key;
    }

    public String getText() {
        return text;
    }

    private static HashMap<Integer,CheckType> map = new HashMap<Integer,CheckType>();
    static {
        for(CheckType d : CheckType.values()){
            map.put(d.key, d);
        }
    }

    public static CheckType parse(Integer index) {
        if(map.containsKey(index)){
            return map.get(index);
        }
        return null;
    }
}

1)CheckType 添加了構造方法,還有兩個字段,key 爲 int 型,text 爲 String 型。

2)CheckType 中有一個public static CheckType parse(Integer index)方法,可將一個 Integer 經過 key 的匹配轉化爲枚舉類型。

那麼如今,咱們能夠在 Mybatis 的配置文件中使用 typeHandler 將數據庫字段轉化爲枚舉類型。

<resultMap id="CheckLog" type="com.entity.CheckLog">
  <id property="id" column="id"/>
  <result property="checkType" column="check_type" typeHandler="com.CheckTypeHandler"></result>
</resultMap>

其中 checkType 字段對應的類以下:

public class CheckLog implements Serializable {

    private String id;
    private CheckType checkType;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public CheckType getCheckType() {
        return checkType;
    }

    public void setCheckType(CheckType checkType) {
        this.checkType = checkType;
    }
}

CheckTypeHandler 轉換器的類源碼以下:

public class CheckTypeHandler extends BaseTypeHandler<CheckType{

    @Override
    public CheckType getNullableResult(ResultSet rs, String index) throws SQLException {
        return CheckType.parse(rs.getInt(index));
    }

    @Override
    public CheckType getNullableResult(ResultSet rs, int index) throws SQLException {
        return CheckType.parse(rs.getInt(index));
    }

    @Override
    public CheckType getNullableResult(CallableStatement cs, int index) throws SQLException {
        return CheckType.parse(cs.getInt(index));
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int index, CheckType val, JdbcType arg3) throws SQLException {
        ps.setInt(index, val.getKey());
    }
}

CheckTypeHandler 的核心功能就是調用 CheckType 枚舉類的 parse() 方法對數據庫字段進行轉換。

恕我直言,我以爲小夥伴們確定會用 Java 枚舉了,若是還不會,就過來砍我!

十7、final 關鍵字

儘管繼承可讓咱們重用現有代碼,但有時處於某些緣由,咱們確實須要對可擴展性進行限制,final 關鍵字能夠幫助咱們作到這一點。

0一、final 類

若是一個類使用了 final 關鍵字修飾,那麼它就沒法被繼承。若是小夥伴們細心觀察的話,Java 就有很多 final 類,好比說最多見的 String 類。

public final class String
    implements java.io.SerializableComparable<String>, CharSequence,
               ConstableConstantDesc 
{}

爲何 String 類要設計成 final 的呢?緣由大體有如下三個:

  • 爲了實現字符串常量池
  • 爲了線程安全
  • 爲了 HashCode 的不可變性

更詳細的緣由,能夠查看我以前寫的一篇文章

任未嘗試從 final 類繼承的行爲將會引起編譯錯誤,爲了驗證這一點,咱們來看下面這個例子,Writer 類是 final 的。

public final class Writer {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

嘗試去繼承它,編譯器會提示如下錯誤,Writer 類是 final 的,沒法繼承。

不過,類是 final 的,並不意味着該類的對象是不可變的。

Writer writer = new Writer();
writer.setName("沉默王二");
System.out.println(writer.getName()); // 沉默王二

Writer 的 name 字段的默認值是 null,但能夠經過 settter 方法將其更改成「沉默王二」。也就是說,若是一個類只是 final 的,那麼它並非不可變的所有條件。

若是,你想了解不可變類的所有真相,請查看我以前寫的文章此次要說不明白immutable類,我就怎麼地。忽然發現,寫系列文章真的妙啊,不少相關性的概念所有涉及到了。我真服了本身了。

把一個類設計成 final 的,有其安全方面的考慮,但不該該故意爲之,由於把一個類定義成 final 的,意味着它沒辦法繼承,假如這個類的一些方法存在一些問題的話,咱們就沒法經過重寫的方式去修復它。

0二、final 方法

被 final 修飾的方法不能被重寫。若是咱們在設計一個類的時候,認爲某些方法不該該被重寫,就應該把它設計成 final 的。

Thread 類就是一個例子,它自己不是 final 的,這意味着咱們能夠擴展它,但它的 isAlive() 方法是 final 的:

public class Thread implements Runnable {
    public final native boolean isAlive();
}

須要注意的是,該方法是一個本地(native)方法,用於確認線程是否處於活躍狀態。而本地方法是由操做系統決定的,所以重寫該方法並不容易實現。

Actor 類有一個 final 方法 show()

public class Actor {
    public final void show() {

    }
}

當咱們想要重寫該方法的話,就會出現編譯錯誤:

若是一個類中的某些方法要被其餘方法調用,則應考慮事被調用的方法稱爲 final 方法,不然,重寫該方法會影響到調用方法的使用。

一個類是 final 的,和一個類不是 final,但它全部的方法都是 final 的,考慮一下,它們之間有什麼區別?

我能想到的一點,就是前者不能被繼承,也就是說方法沒法被重寫;後者呢,能夠被繼承,而後追加一些非 final 的方法。沒毛病吧?看把我聰明的。

0三、final 變量

被 final 修飾的變量沒法從新賦值。換句話說,final 變量一旦初始化,就沒法更改。以前被一個小夥伴問過,什麼是 effective final,什麼是 final,這一點,我在以前的文章也有闡述過,因此這裏再貼一下地址:

http://www.itwanger.com/java/2020/02/14/java-final-effectively.html

1)final 修飾的基本數據類型

來聲明一個 final 修飾的 int 類型的變量:

final int age = 18;

嘗試將它修改成 30,結果編譯器生氣了:

2)final 修飾的引用類型

如今有一個普通的類 Pig,它有一個字段 name:

public class Pig {
   private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

在測試類中聲明一個 final 修飾的 Pig 對象:

 final Pig pig = new Pig();

若是嘗試將 pig 從新賦值的話,編譯器一樣會生氣:

但咱們仍然能夠去修改 Pig 的字段值:

final Pig pig = new Pig();
pig.setName("特立獨行");
System.out.println(pig.getName()); // 特立獨行

3)final 修飾的字段

final 修飾的字段能夠分爲兩種,一種是 static 的,另一種是沒有 static 的,就像下面這樣:

public class Pig {
   private final int age = 1;
   public static final double PRICE = 36.5;
}

非 static 的 final 字段必須有一個默認值,不然編譯器將會提醒沒有初始化:

static 的 final 字段也叫常量,它的名字應該爲大寫,能夠在聲明的時候初始化,也能夠經過 static [代碼塊初始化]()。

4) final 修飾的參數

final 關鍵字還能夠修飾參數,它意味着參數在方法體內不能被再修改:

public class ArgFinalTest {
    public void arg(final int age) {
    }

    public void arg1(final String name) {
    }
}

若是嘗試去修改它的話,編譯器會提示如下錯誤:

。。。。。。

後續還會繼續更新,但有些小夥伴可能就忍不住了,這份小白手冊有沒有 PDF 版能夠白嫖啊,那必須得有啊,直接「沉默王二」公衆號後臺回覆「小白」就能夠了,不要手軟,以爲不錯的,請多多分享——贈人玫瑰,手有餘香哦。

沒關注的話,掃描上面的二維碼就能夠了,而後回覆「小白」。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注便可提高學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

相關文章
相關標籤/搜索