Java核心技術(shù)卷Ⅰ 讀書分享 3、4泪酱、6章

第三章Java的基本程序設(shè)計(jì)結(jié)構(gòu)

數(shù)據(jù)類型

Java 是一種強(qiáng)類型語言派殷,必須為每個(gè)變量聲明一種類型。在 Java 中有 8 種基本類型:4 種整型墓阀,2種浮點(diǎn)型毡惜,1 種用于表示 Unicode 編碼的字符單元的字符類型 char,和一種用于表示真值的 boolean 類型(String 和 數(shù)組都是對(duì)象)斯撮。

整型:

用于表示沒有小數(shù)部分的數(shù)值经伙,它允許是負(fù)數(shù)
取值范圍 -2^(位-1) -----2^(位-1)-1 一個(gè)字節(jié)代表8位

類型 存儲(chǔ)需求 取值范圍 具體值
int 4 字節(jié) -2的32次方~2的32次方-1 -2147483646-2147483647(正好超過20億)
short 2 字節(jié) -2的16次方~2的16次方-1 -32768~32767
long 8 字節(jié) -2的64次方~2的64次方-1 -9223372036854775808~9223372036854775807
byte 1 字節(jié) -2的8次方~2的8次方-1 -128~127

在 Java 種,整型的范圍于運(yùn)行 Java 代碼的機(jī)器無關(guān)勿锅。
長整型數(shù)值后面有一個(gè) L 后綴帕膜,從 Java7 開始,加上前綴 0b 或 0B 就可以寫二進(jìn)制數(shù)溢十。如 0b1001 就代表 9垮刹,還可以為數(shù)字字面量加下劃線,如用 1_000_000 表示一百萬茶宵,更加可讀危纫,編譯器會(huì)去掉這些下劃線。

System.out.println(0b1001); //  result : 9
System.out.println(1_000_000); // result :1000000

浮點(diǎn)類型

用于表示有小數(shù)部分的數(shù)值乌庶。Java 中有兩種浮點(diǎn)類型种蝶。

類型 存儲(chǔ)需求 取值范圍
float 4 字節(jié) -1.7乘以10的38次方~1.7乘以10的38次方
double 8 字節(jié) -3.4乘以10的308次方~3.4乘以10的308次方

double 表示這種類型的數(shù)值精度是 float 類型的兩倍(也有人稱之為雙精度數(shù)值),float 類型的數(shù)值后有一個(gè)后綴 F 或 f瞒大,沒有后綴的默認(rèn)為 double 類型螃征,double 類型也可以加后綴 D 或 d 。
注意浮點(diǎn)類型計(jì)算存在誤差透敌,是因?yàn)楦↑c(diǎn)數(shù)值采用二進(jìn)制系統(tǒng)表示盯滚,在二進(jìn)制系統(tǒng)中無法精確地表示分?jǐn)?shù) 1/10 ,可以使用 BigDecimal 類代替實(shí)現(xiàn)酗电。

System.out.println(2.0-1.1);
result : 0.8999999999999999

char 類型

char 類型用于表示單個(gè)字符魄藕,不過現(xiàn)在有些變化,有些 Unicode 字符可以用一個(gè) char 值描述撵术,另外一些 Unicode 字符則需要兩個(gè) char 值背率。'A' 與 "A" 不同,前者是編碼值為 65 所對(duì)應(yīng)的字符常量,后者是包含一個(gè)字符 A 的字符串寝姿。char 類型的值可以表示為十六進(jìn)制值交排,其范圍從 \u0000 到 \Uffff 。
下面這行代碼是符合語法標(biāo)準(zhǔn)的饵筑,\u005B 表示 [ 埃篓,\u005D 表示 ]。Unicode 轉(zhuǎn)義序列會(huì)在解析代碼之前得到處理根资。

public static void main(String\u005B\u005D args) {}
image.png
微信截圖_20190423100013.png

在IDEA中
image.png

Java中可以使用 \u + Unicode編碼來進(jìn)行轉(zhuǎn)義架专,如 \u0022,除了這個(gè)以外玄帕,還有一些特殊的轉(zhuǎn)義序列

轉(zhuǎn)義序列 名稱 Unicode值
\b 退格 \u0008
\t 制表 \u0009
\n 換行 \u000a
\r 回車 \u000d
\" 雙引號(hào) \u0022
\' 單引號(hào) \u0027
\\ 反斜杠 \u005c

boolean 類型

boolean(布爾) 類型有兩個(gè)值:false 和 true胶征,用來判定邏輯條件。整型值和布爾值之間不能進(jìn)行相互轉(zhuǎn)換桨仿。

變量

在 Java 中睛低,每個(gè)變量都一個(gè)類型(type)。在聲明變量時(shí)服傍,變量的類型位于變量名之前钱雷,例如:

double salary;
int vacationDays;
boolean done;

變量名必須要以字母開頭,并由字母或數(shù)字組成的序列吹零,不過這里的“字母”的概念不單指英文字母罩抗,字母包括 A~Za~z灿椅,_套蒂,$,或在某種語言中表示字母的任何 Unicode 字符茫蛹,比如德國人就可以在變量名中使用字母 ? (讀音為:ei)操刀。

常量

在 Java 中,利用關(guān)鍵字 final 指示常量婴洼,例如
final int cout = 3 ;
關(guān)鍵字 final 表示這個(gè)變量只能被賦值一次骨坑,一旦被賦值之后,就不能夠再修改了柬采,習(xí)慣上欢唾,常量名使用全大寫。聲明在類中粉捻,用 static final 聲明的變量礁遣,也被稱為類常量

運(yùn)算符

在 Java 中,使用算術(shù)運(yùn)算符 +肩刃,-祟霍,*押搪,/,表示加減乘除運(yùn)算浅碾,當(dāng)參與/運(yùn)算的兩個(gè)操作數(shù)都是整數(shù)時(shí),表示整數(shù)觸發(fā)续语;否則表示浮點(diǎn)除法垂谢。整數(shù)的求余操作(有時(shí)稱為取模),用 % 表示疮茄。

a = 15 , b = 2   a/b=7
a%b = 1;
a=15.0
a/b = 7.5

數(shù)學(xué)函數(shù)與常量

Math 類中包含了各種各樣的數(shù)學(xué)函數(shù)滥朱,比如這里有一個(gè)計(jì)算數(shù)值平方根的方法

double x = 4;
double y = Math.sqrt(x);// sqrt 接受一個(gè) double 值
System.out.println(y);

冪運(yùn)算的方法

// y 的值為 x 的 a 次冪,同樣接受 double 值力试。
double y = Math.pow(x,a);

數(shù)值類型之間的轉(zhuǎn)換

如果兩個(gè)操作數(shù)中有一個(gè)是 double 類型徙邻,另一個(gè)操作數(shù)就會(huì)轉(zhuǎn)換為 double 類型。
否則畸裳,如果其中一個(gè)操作數(shù)是 float 類型缰犁,另一個(gè)操作數(shù)將會(huì)轉(zhuǎn)換為 float 類型。
否則怖糊,如果其中一個(gè)操作數(shù)是 long 類型帅容,另一個(gè)操作數(shù)將會(huì)轉(zhuǎn)換為 long 類型。
否則伍伤,兩個(gè)操作數(shù)都將被轉(zhuǎn)換為 int 類型并徘。

結(jié)合賦值和運(yùn)算符

"+=",“-=”扰魂,“*=”麦乞,“%=”,這些都是在賦值中使用二元運(yùn)算符劝评,但是不會(huì)改變數(shù)據(jù)的類型姐直,例如:

int x  =2 ;
x+=3.5;
此時(shí)等價(jià)于: x = (int)(x+3.5)//先變成 double ,再被轉(zhuǎn)換為 int 

自增與自減運(yùn)算符

++n蒋畜,n++简肴,是兩種不同的含義,如果把加號(hào)放在前綴百侧,那么則會(huì)先自增砰识,再運(yùn)算表達(dá)式的值;如果放在后綴佣渴,那么則會(huì)先運(yùn)算表達(dá)式的值辫狼,再自增。另外辛润,++4膨处,是錯(cuò)誤的,自增與自減運(yùn)算符只能用于變量,不能是數(shù)值真椿。

int a = 2;
int c = 3;
System.out.println(a++);
System.out.println(++c);
--------------------------
2
4

關(guān)系和 boolean 運(yùn)算符

  • == :檢測相等性
  • !=:檢測不相等
  • <鹃答,>,<=突硝,>=:小于测摔,大于,小于等于解恰,大于等于
  • &&:采用短路的做法锋八,如果前者為 false ,則不計(jì)算后者
  • ||:采用短路的做法护盈,如果前者為 true 挟纱,則不計(jì)算后者
  • ?: :三元運(yùn)算符腐宋,condition? expression1:expression2紊服,如果 condition 為 true,就為第一個(gè)表達(dá)式的值胸竞,反之則為第二個(gè)表達(dá)式的值围苫。

位運(yùn)算符

  • &:&在運(yùn)算的時(shí)候,將2個(gè)數(shù)字的二進(jìn)制做比較撤师,當(dāng)2個(gè)數(shù)字的值都為1時(shí)剂府,才為1,否則就是0
  • |:充當(dāng) 數(shù)值運(yùn)算符的時(shí)候 同樣是比較2進(jìn)制剃盾,當(dāng)有一個(gè)數(shù)為1腺占,那么就取1
  • ^:充當(dāng) 數(shù)值運(yùn)算符的時(shí)候 同樣是比較2進(jìn)制, 只能有1個(gè)1痒谴,那就取1
  • ~:取反值
  • >>:補(bǔ)最左邊的數(shù)位時(shí)衰伯,會(huì)根據(jù)符號(hào)位, 符號(hào)是1 就填充1积蔚,符號(hào)是0意鲸,就填充0;
  • <<:左移
  • >>>:無符號(hào)右移,:對(duì)于正數(shù) 有符號(hào)與無符號(hào)的右移沒有區(qū)別尽爆。 對(duì)于負(fù)數(shù) 來說怎顾,不管你是0還是1,都會(huì)用0去補(bǔ)位

括號(hào)與運(yùn)算符級(jí)別

如果不使用括號(hào)漱贱,就按照給出的運(yùn)算符優(yōu)先級(jí)次序進(jìn)行計(jì)算槐雾,同一個(gè)級(jí)別的運(yùn)算符按照從左到右的次序進(jìn)行是計(jì)算(除了右結(jié)合運(yùn)算符),

運(yùn)算符 結(jié)合性
[] .()(方法調(diào)用) 從左向右
! ~ ++ -- +(一元運(yùn)算) -(一元運(yùn)算) ()(強(qiáng)制類型轉(zhuǎn)換) new 從右向左
* / % 從左向右
+(正) -(負(fù)) 從左向右
<< >> >>> 從左向右
< <= > >= instanceof 從左向右
== != 從左向右
& 從左向右
^ 從左向右
| 從左向右
&& 從左向右
|| 從左向右
?: 從右向左
= += -= *= /= %= &= ^= <<= >>= >>>= 從右向左

字符串概念

檢測字符串是否相等

使用 equals 方法檢測兩個(gè)字符串是否相等幅狮,s.equals(t)募强,如果字符串 s 與字符串 t 相等株灸,則返回 true,否則擎值,返回 false慌烧。s 和 t 可以是字符串變量,也可以是字符串字面量:

String abc = "Hello";
abc.equals("Hello");
"Hello".equals(abc);

如果你希望檢測兩個(gè)字符串是否相等鸠儿,而不區(qū)分大小寫屹蚊,可以使用 equalsIgnoreCase 方法。不能使用 == 來比較字符串是否相同捆交,因?yàn)?== 比較的是變量的內(nèi)存地址,而不是變量的值腐巢。

空串與 Null 串

空串 "" 是長度為0的字符串品追,可以調(diào)用以下代碼檢查一個(gè)字符串是否為空。

if(str.length()==0)
if(str.equals(""))

空串是一個(gè) Java 對(duì)象冯丙,有自己的串長度 (0) 和內(nèi)容 (空) 肉瓦,不過 String 變量還可以存放一個(gè)特殊值:null,表示目前沒有任何對(duì)象與該變量關(guān)聯(lián)胃惜,要檢查一個(gè)字符串是否為 null泞莉,可以使用以下條件:
if (str == null)
有時(shí)要檢查一個(gè)字符串既不是 null 也不為空串,這種情況下就需要使用以下條件:
if (str !=null && str.length() != 0)

String API

  • boolean equals(Object other)
  • boolean equalsIgnoreCase(String other)
  • boolean startWith(String prefix) 如果字符串以 prefix 開頭船殉,則返回 true
  • boolean endsWith(String suffix) 如果字符串以 suffix 結(jié)尾鲫趁,則返回 true
  • int indexOf(String str)
  • int indexOf(String str,int fromIndex)
  • int indexOf(int cp)
  • int indexOf(int cp利虫,int fromIndex)
  • int lastIndexOf(String str)
  • int lastIndexOf(String str挨厚,int fromIndex)
  • int lastIndexOf(int cp)
  • int lastIndexOf(int cp,int fromIndex)
  • length()
  • String replace(CharSequence oldString糠惫,CharSequence newString)疫剃,可以用 String 或 StringBuilder 對(duì)象作為 CharSequence 參數(shù)。
  • String substring(int beginIndex)
  • String substring(int beginIndex硼讽,int endIndex)
  • String toLowerCase() 轉(zhuǎn)換為小寫
  • String toUpperCase() 轉(zhuǎn)換為大寫
  • String trim() 這個(gè)字符串將刪除原始字符串頭部和尾部的空格
  • String join(CharSequence delimiter巢价,CharSequence... elements)

構(gòu)建字符串

如果單純用 String 來拼接字符串,每次連接字符串都會(huì)構(gòu)建一個(gè)新的 String 對(duì)象固阁,既耗時(shí)壤躲,又浪費(fèi)空間,使用 StringBuilder 就可以避免這個(gè)問題的發(fā)生备燃。

//構(gòu)建一個(gè)空的字符串構(gòu)建器
    StringBuilder builder = new StringBuilder();
        builder.append("Hello");
        builder.append("World");
        String completedString = builder.toString();

StringBuilder 的前身是 StringBuffer柒爵,StringBuffer 的效率略低,但是允許采用多線程的方式執(zhí)行添加或刪除字符的操作赚爵,如果所有字符串都再一個(gè)單線程中編輯棉胀,則應(yīng)該使用 StringBuilder法瑟,這兩個(gè)類的 API 是相同的。

  • StringBuilder()
  • int length()
  • StringBuilder append(String str)
  • StringBuilder append(char c)
  • StringBuilder insert(int offset,String str)
  • StringBuilder insert(int offset,Char c )
  • StringBuilder delete(int startIndex,int endIndex)
  • String toString()

格式化輸出

System.out.printf 沿用了 C 語言庫函數(shù)中的 printf 方法唁奢,可以設(shè)置多個(gè)參數(shù)霎挟,例如:

    String name = "Pudge";
        int age = 15;
        System.out.printf("Hello,%s. Next year,you'll be %d", name, age);

每一個(gè)以 % 字符開始的格式說明符都用相應(yīng)的參數(shù)替換。個(gè)數(shù)說明符尾部的轉(zhuǎn)換符將指示被格式化的數(shù)值類型:

  • d 十進(jìn)制整數(shù)
  • g 通用浮點(diǎn)數(shù)
  • s 字符串
  • c 字符
  • b 布爾
    用于 printf 的標(biāo)志
  • 給定被格式化的參數(shù)索引麻掸,例如:%1d

printf 用于輸出酥夭,可以使用 String.format 方法來創(chuàng)建一個(gè)格式化的字符串,而不打印輸出脊奋。

大數(shù)值

如果基本的整數(shù)和浮點(diǎn)數(shù)精度不能夠滿足需求熬北,那么可以使用 java.math 包中的兩個(gè)很有用的類:BigInteger 和 BigDecimal。這兩個(gè)類可以處理包含任意長度數(shù)字序列的數(shù)值诚隙。BigInteger 實(shí)現(xiàn)了任意精度的整數(shù)運(yùn)算讶隐,BigDecimal 實(shí)現(xiàn)了任意精度的浮點(diǎn)數(shù)運(yùn)算。

//使用靜態(tài)的 valueOf 方法可以將普通的數(shù)值轉(zhuǎn)換為大數(shù)值
//不能使用+久又、-巫延、*等運(yùn)算符,需要使用add地消、multiply方法
BigInteger a = BigInteger.valueOf(100);
BigInteger c = a.add(b) // c =  a + b 
BigInteger d = c.multiply(b.add(BigInteger.valueOf(2))); //d = c *(b + 2);

BigInteger API

  • BigInteger add(BigInteger other)//加法
  • BigInteger subtract(BigInteger other)//減法
  • BigInteger multiply(BigInteger other)//乘法
  • BigInteger divide(BigInteger other)//除法
  • BigInteger mod(BigInteger other)//取余
  • int compareTo(BigInteger other)//比較炉峰,相等則返回 0,大于則返回 1脉执,小于則返回 -1

BigDecimal API

  • BigDecimal add(BigDecimal other)
  • BigDecimal subtract(BigDecimal other)
  • BigDecimal multiply(BigDecimal other)
  • BigDecimal divide(BigDecimal other , RoundingMode mode)//需要給出舍入方式疼阔,如 RoundingMode.HALF_UP 是在學(xué)校中學(xué)習(xí)的四舍五入方式
  • int compareTo(BigDecimal other)
  • static BigDecimal valueOf(long x)
  • static BigDecimal valueOf(long x, int scale)// x / 10^scale

源碼中介紹的舍入模式

// Rounding Modes

    /**
     * Rounding mode to round away from zero.  Always increments the
     * digit prior to a nonzero discarded fraction.  Note that this rounding
     * mode never decreases the magnitude of the calculated value.
     */
    public final static int ROUND_UP =           0;

    /**
     * Rounding mode to round towards zero.  Never increments the digit
     * prior to a discarded fraction (i.e., truncates).  Note that this
     * rounding mode never increases the magnitude of the calculated value.
     */
    public final static int ROUND_DOWN =         1;

    /**
     * Rounding mode to round towards positive infinity.  If the
     * {@code BigDecimal} is positive, behaves as for
     * {@code ROUND_UP}; if negative, behaves as for
     * {@code ROUND_DOWN}.  Note that this rounding mode never
     * decreases the calculated value.
     */
    public final static int ROUND_CEILING =      2;

    /**
     * Rounding mode to round towards negative infinity.  If the
     * {@code BigDecimal} is positive, behave as for
     * {@code ROUND_DOWN}; if negative, behave as for
     * {@code ROUND_UP}.  Note that this rounding mode never
     * increases the calculated value.
     */
    public final static int ROUND_FLOOR =        3;

    /**
     * Rounding mode to round towards {@literal "nearest neighbor"}
     * unless both neighbors are equidistant, in which case round up.
     * Behaves as for {@code ROUND_UP} if the discarded fraction is
     * &ge; 0.5; otherwise, behaves as for {@code ROUND_DOWN}.  Note
     * that this is the rounding mode that most of us were taught in
     * grade school.
     */
    public final static int ROUND_HALF_UP =      4;

    /**
     * Rounding mode to round towards {@literal "nearest neighbor"}
     * unless both neighbors are equidistant, in which case round
     * down.  Behaves as for {@code ROUND_UP} if the discarded
     * fraction is {@literal >} 0.5; otherwise, behaves as for
     * {@code ROUND_DOWN}.
     */
    public final static int ROUND_HALF_DOWN =    5;

    /**
     * Rounding mode to round towards the {@literal "nearest neighbor"}
     * unless both neighbors are equidistant, in which case, round
     * towards the even neighbor.  Behaves as for
     * {@code ROUND_HALF_UP} if the digit to the left of the
     * discarded fraction is odd; behaves as for
     * {@code ROUND_HALF_DOWN} if it's even.  Note that this is the
     * rounding mode that minimizes cumulative error when applied
     * repeatedly over a sequence of calculations.
     */
    public final static int ROUND_HALF_EVEN =    6;

    /**
     * Rounding mode to assert that the requested operation has an exact
     * result, hence no rounding is necessary.  If this rounding mode is
     * specified on an operation that yields an inexact result, an
     * {@code ArithmeticException} is thrown.
     */
    public final static int ROUND_UNNECESSARY =  7;
ROUND_UP
向遠(yuǎn)離零的方向舍入。舍棄非零部分半夷,并將非零舍棄部分相鄰的一位數(shù)字加一竿开。
ROUND_DOWN
向接近零的方向舍入。舍棄非零部分玻熙,同時(shí)不會(huì)非零舍棄部分相鄰的一位數(shù)字加一否彩,采取截取行為。
ROUND_CEILING
向正無窮的方向舍入嗦随。如果為正數(shù)列荔,舍入結(jié)果同ROUND_UP一致;如果為負(fù)數(shù)枚尼,舍入結(jié)果同ROUND_DOWN一致贴浙。注意:此模式不會(huì)減少數(shù)值大小。
ROUND_FLOOR
向負(fù)無窮的方向舍入署恍。如果為正數(shù)崎溃,舍入結(jié)果同ROUND_DOWN一致;如果為負(fù)數(shù)盯质,舍入結(jié)果同ROUND_UP一致袁串。注意:此模式不會(huì)增加數(shù)值大小概而。
ROUND_HALF_UP
向“最接近”的數(shù)字舍入,如果與兩個(gè)相鄰數(shù)字的距離相等囱修,則為向上舍入的舍入模式赎瑰。如果舍棄部分>= 0.5,則舍入行為與ROUND_UP相同破镰;否則舍入行為與ROUND_DOWN相同餐曼。這種模式也就是我們常說的我們的“四舍五入”。
ROUND_HALF_DOWN
向“最接近”的數(shù)字舍入鲜漩,如果與兩個(gè)相鄰數(shù)字的距離相等源譬,則為向下舍入的舍入模式。如果舍棄部分> 0.5孕似,則舍入行為與ROUND_UP相同踩娘;否則舍入行為與ROUND_DOWN相同。這種模式也就是我們常說的我們的“五舍六入”鳞青。
ROUND_HALF_EVEN
向“最接近”的數(shù)字舍入霸饲,如果與兩個(gè)相鄰數(shù)字的距離相等为朋,則相鄰的偶數(shù)舍入臂拓。如果舍棄部分左邊的數(shù)字奇數(shù)彰导,則舍入行為與 ROUND_HALF_UP 相同羊苟;如果為偶數(shù),則舍入行為與 ROUND_HALF_DOWN 相同挑格。注意:在重復(fù)進(jìn)行一系列計(jì)算時(shí)霞溪,此舍入模式可以將累加錯(cuò)誤減到最小孵滞。此舍入模式也稱為“銀行家舍入法”,主要在美國使用鸯匹。四舍六入坊饶,五分兩種情況,如果前一位為奇數(shù)殴蓬,則入位匿级,否則舍去。
ROUND_UNNECESSARY
斷言請(qǐng)求的操作具有精確的結(jié)果染厅,因此不需要舍入痘绎。如果對(duì)獲得精確結(jié)果的操作指定此舍入模式,則拋出ArithmeticException肖粮。

第四章對(duì)象與類

面向?qū)ο蟪绦蛟O(shè)計(jì)概述

面向?qū)ο蟪绦蛟O(shè)計(jì)孤页,簡稱OOP。Java 是完全面向?qū)ο蟮纳荩仨毷煜?OOP 才能夠編寫 Java 程序行施。

class 是構(gòu)造對(duì)象的模板或藍(lán)圖允坚,由類構(gòu)造 (construct) 對(duì)象的過程稱為創(chuàng)建類的實(shí)例 (instance)。
封裝 (encapsulation悲龟,有時(shí)稱為數(shù)據(jù)隱藏)屋讶,對(duì)象中的數(shù)據(jù)稱為實(shí)例域 (instance field),操作數(shù)據(jù)的過程稱為方法 (method)须教。對(duì)于每個(gè)特定的類實(shí)例都有一組特定的實(shí)例域值皿渗。這些值的集合就是這個(gè)對(duì)象的當(dāng)前狀態(tài) (state)。實(shí)現(xiàn)封裝的關(guān)鍵在于絕對(duì)不能讓類中的方法直接地訪問其他類的實(shí)例域轻腺。程序僅通過對(duì)象的方法與對(duì)象數(shù)據(jù)進(jìn)行交互乐疆。
OOP的另一個(gè)原則就是可以通過擴(kuò)展一個(gè)類來建立另外一個(gè)新的類,在 Java 中贬养,所有的類都源自于 Object挤土。通過擴(kuò)展一個(gè)類來建立另外一個(gè)類的過程稱為繼承(inheritance)。

對(duì)象

對(duì)象的三個(gè)主要特性

  • 對(duì)象的行為 (behavior):可以對(duì)對(duì)象施加哪些操作误算,或可以對(duì)對(duì)象施加哪些方法仰美?
  • 對(duì)象的狀態(tài) (state):當(dāng)施加那些方法時(shí),對(duì)象如何響應(yīng)儿礼?
  • 對(duì)象標(biāo)識(shí) (identity):如何辨別具有相同行為與狀態(tài)的不同對(duì)象咖杂?

識(shí)別類

識(shí)別類的簡單規(guī)則是在分析問題的過程中尋找名詞,而方法對(duì)應(yīng)著動(dòng)詞蚊夫。

類之間的關(guān)系

  • 依賴:uses-a 一個(gè)類的方法操縱另一個(gè)類的對(duì)象诉字,我們就說一個(gè)類依賴于另一個(gè)類
  • 聚合:has-a 一個(gè)類的對(duì)象包含另一個(gè)類的對(duì)象
  • 繼承:is-a 一個(gè)類是另一個(gè)類的子類或者父類,類之間進(jìn)行了擴(kuò)展知纷。

使用預(yù)定義類

Java 中沒有類就不能做任何事情壤圃,然而,并不是所有的類都具有面向?qū)ο筇卣骼旁@?Math 類伍绳,在程序中,可以使用 Math 類的方法乍桂,只需要知道方法名和參數(shù)冲杀,而不必了解它的具體實(shí)現(xiàn)過程,這正是封裝的關(guān)鍵所在模蜡,但是 Math 類只封裝了功能漠趁,它不需要也不必隱藏?cái)?shù)據(jù),由于沒有數(shù)據(jù)忍疾,因此也不必?fù)?dān)心生成對(duì)象以及初始化實(shí)例域闯传。

對(duì)象與對(duì)象變量

要想使用對(duì)象,必須首先構(gòu)造對(duì)象卤妒,并指定其初始狀態(tài)甥绿。然后字币,對(duì)對(duì)象應(yīng)用方法。
在 Java 中共缕,使用構(gòu)造器 (constructor) 構(gòu)造新實(shí)例洗出,構(gòu)造器是一種特殊的方法,用來構(gòu)造并初始化對(duì)象图谷。構(gòu)造器的名字應(yīng)該與類名相同翩活,要想構(gòu)造一個(gè)類的對(duì)象,需要在構(gòu)造器前面加上 new 操作符便贵。
new Date() System.out.println(new Date()); String s = new Date().toString();
在上述例子種菠镇,構(gòu)造的對(duì)象僅使用了一次,如果希望多次使用承璃,需要將對(duì)象存放在一個(gè)變量中利耍。
Date birthday = new Date();
一定要認(rèn)識(shí)到:一個(gè)對(duì)象變量并沒有實(shí)際包含一個(gè)對(duì)象,而僅僅引用一個(gè)對(duì)象盔粹。

Java 類庫中的 LocalDate 類

Date 類用來表示時(shí)間點(diǎn)隘梨,LocalDate 用來表示大家熟悉的日歷表達(dá)法。
LocalDate.now()// 2017-07-08
還可以調(diào)用 LocalDate.of(1999,12,6) 方法來構(gòu)造對(duì)應(yīng)一個(gè)特定日期的對(duì)象舷嗡。
一旦有了一個(gè) LocalDate 對(duì)象轴猎,可以使用方法 geetYeargetMonthValue咬崔、getDayOfMonth得到年税稼、月烦秩、日垮斯。下列的方法可以得到未來的日期或者過去的日期。

LocalDate c = LocalDate.now();//c 是當(dāng)前時(shí)間
System.out.println(c.plusDays(1));//明天
System.out.println(c.plusDays(-1));//昨天

更改器方法與訪問器方法

Java 庫的一個(gè)較早版本曾經(jīng)有另一個(gè)類來處理日歷只祠,名為 GregorianCalendarc.plusDays(1) 不同兜蠕,GregorianCalendar.add 方法與 plusDays 方法功能差不多,但是是一個(gè)更改器方法 (mutator method)抛寝。調(diào)用這個(gè)方法后熊杨,GregorianCalendar 對(duì)象的狀態(tài)會(huì)改變。

GregorianCalendar c = new GregorianCalendar(1999,1,10);
c.add(Calendar.YEAR, 1);
int year = c.get(Calendar.YEAR);
System.out.println(year);

相反盗舰,只訪問對(duì)象而不修改對(duì)象的方法有時(shí)稱為訪問器方法 (accessor method)晶府。例如 LocalDate.getGregorianCalendar.get
下面的代碼可以構(gòu)建一個(gè)當(dāng)月的日歷钻趋。

//帶 * 號(hào)表示今天
Mon Tue Wed Thu Fri Sat Sun
                      1   2 
  3   4   5   6   7   8*   9 
 10  11  12  13  14  15  16 
 17  18  19  20  21  22  23 
 24  25  26  27  28  29  30 
 31 
 -----------------------------------------
LocalDate date = LocalDate.now();

    int month = date.getMonthValue();// 7
    int today = date.getDayOfMonth();// 8

    date = date.minusDays(today - 1);// 返回到月初
    DayOfWeek weekday = date.getDayOfWeek();// 得到星期幾
    int value = weekday.getValue();//得到月初的星期
    System.out.println("Mon Tue Wed Thu Fri Sat Sun");//先打印好星期行
    for (int i = 1; i < value; i++) {//控制 1 出現(xiàn)的位置
      System.out.print("    ");
    }
    while (date.getMonthValue() == month) {//

        System.out.printf("%3d", date.getDayOfMonth());//打印1

      if (date.getDayOfMonth() == today) {
        System.out.print("*");
      } else {
        System.out.print(" ");
      }
      date = date.plusDays(1);
      if (date.getDayOfWeek().getValue() == 1) {
        System.out.println();
      }
    }

LocalDate API

  • LocalTime now()
  • LocalTime of(int year, int month, int day)
  • int getYear()
  • int getMonthValue()
  • int getDayOfMonth()
  • DayOfWeek getDayOfWeek
  • LocalDate plusDays(int n)
  • LocalDate minusDays(int n)

用戶自定義類

要想創(chuàng)建一個(gè)完整的程序川陆,應(yīng)該將若干類組合在一起,其中只有一個(gè)類有 main 方法蛮位。

Employee 類

class ClassName
{
    field;
    field;
    ...
    constructor1
    constructor2
    ...
    method1
    method2
    ...
}
public class Demo01 {

  public static void main(String[] args) throws Exception {
    Employee[] staff = new Employee[3];
    staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);
    staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
    staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);

    for (Employee e : staff) {
      e.raiseSalary(5);
    }

    for(Employee e :staff){
      System.out.println("name=" +e.getName()+",salary="+e.getSalary()+",hireDay="+e.getHireDay());
    }
  }
}

class Employee {

  private String name;
  private double salary;
  private LocalDate hireDay;

  //constructor
  public Employee(String n, double s, int year, int month, int day) {
    name = n;
    salary = s;
    hireDay = LocalDate.of(year, month, day);
  }

  // a method
  public String getName() {
    return name;
  }

  public double getSalary() {
    return salary;
  }

  public LocalDate getHireDay() {
    return hireDay;
  }

  public void raiseSalary(double byPercent) {
    double raise = salary * byPercent / 100;
    salary += raise;
  }
}

當(dāng)我們的 .java 文件包含 2 個(gè)類的時(shí)候较沪,我們編譯時(shí)可以采用這兩種方法

javac Employee*.java  //可以使用通配符鳞绕,所有與通配符匹配的源文件都將被編譯成類文件
javac Demo01.java //Java編譯器會(huì)自動(dòng)搜索使用的Employee類,并編譯

從構(gòu)造器開始

public Employee(String n, double s, int year, int month, int day){
    name = n ;
    salary = s;
    LocalDate hireDay = LocalDate.of(year,month,day);
}
構(gòu)造器與類同名尸曼,構(gòu)造器與其他的方法有一個(gè)重要的不同们何。構(gòu)造器總是伴隨著 new 操作符的執(zhí)行被調(diào)用,而不能對(duì)一個(gè)已經(jīng)存在的對(duì)象調(diào)用 constructor 來達(dá)到重新設(shè)置實(shí)例域的目的控轿,例如
`james.Employee("James Bond",250000,1950,1,1)`//ERROR,會(huì)產(chǎn)生編譯錯(cuò)誤冤竹。

構(gòu)造器的基本特點(diǎn):

  • 構(gòu)造器與類同名
  • 每個(gè)類可以有一個(gè)以上的構(gòu)造器
  • 構(gòu)造器可以有 0 個(gè)、1 個(gè)或多個(gè)參數(shù)
  • 構(gòu)造器沒有返回值
  • 構(gòu)造器總是伴隨著 new 操作一起調(diào)用

隱式參數(shù)與顯式參數(shù)

public void raiseSalary(double byPercent){
    double raise = salary * byPercent / 100 
    salary +=raise;
    //salary的前面省略了參數(shù)茬射,
    //完整的寫法是
    double raise =  number007.salary * byPercent / 100
    number007.salary +=raise;
}

raiseSalary 有兩個(gè)參數(shù)贴见,第一個(gè)參數(shù)稱為隱式參數(shù) (implicit) 參數(shù),是出現(xiàn)在方法名前的 Employee 類對(duì)象躲株。第二個(gè)參數(shù)位于方法名后面括號(hào)中的數(shù)值片部,這是一個(gè)顯式 (explicit) 參數(shù)。隱式參數(shù)也被稱為方法調(diào)用的目標(biāo)或接收者霜定。
在每個(gè)方法中档悠,關(guān)鍵字 this 表示隱式參數(shù)。如果需要的話望浩,可以用下列方式編寫 raiseSalary 方法:

public void raiseSalary(double Percent){
    double raise = this.salary * byPercent / 100;
    salary +=raise;
}

encapsulation 的優(yōu)點(diǎn)

使用 public get 方法來代替 public name辖所,將全局變量設(shè)置為 private 可以防止受到外界的破壞。
注意:不要編寫返回引用可變對(duì)象的 get 方法磨德,因?yàn)樗旧硎强勺兊脑捲祷兀瑫?huì)破壞 encapsulation 性,我們應(yīng)該只能通過 set 方法來改變對(duì)象的狀態(tài)典挑,如果必須要這樣做酥宴,那么我們可以使用 clone

class Employee{
    public Date getHireDay()
    {
        return (Date) hireDay.clone();
    }
}

基于類的訪問權(quán)限

public boolean equals(Employee other) {
    return name.equals(other.name);
  }

這段代碼中 Employee 類的 name 變量是 private 修飾的您觉,other.name 意味著我們訪問了另一個(gè)對(duì)象的 private 屬性拙寡,這與我們之前說的是對(duì)不起來的,其原因是: other 是 Employee 類對(duì)象琳水,而 Employee 類的方法可以訪問 Employee 類的任何一個(gè)對(duì)象的私有域肆糕。

私有方法

有時(shí),可能希望將一個(gè)計(jì)算代碼劃分為若干個(gè)獨(dú)立的賦值方法在孝。通常诚啃,這些輔助方法不應(yīng)該成為公有接口的一部分,最好將這樣的方法設(shè)計(jì)為 private私沮。

final

構(gòu)造對(duì)象時(shí)必須初始化這樣的域始赎,也就是說,必須確保在每一個(gè)構(gòu)造器執(zhí)行之后,這個(gè)域的值被設(shè)置极阅,并且在后面的操作中胃碾,不能夠再對(duì)它進(jìn)行修改。如果將 final 用在一個(gè)可變對(duì)象上筋搏,那么 final 只表示該變量的對(duì)象引用不會(huì)更改仆百,對(duì)象本身是可以更改的。

靜態(tài)域與靜態(tài)方法 static

靜態(tài)域

如果將域定義為 static奔脐,每個(gè)類中只有一個(gè)這樣的域俄周。而每一個(gè)對(duì)象對(duì)于所有的實(shí)例域卻都有自己的一份拷貝。static 修飾的屬性屬于類髓迎,而不屬于任何獨(dú)立的對(duì)象峦朗。

靜態(tài)常量

靜態(tài)變量用得比較少,但靜態(tài)常量卻使用得筆記多排龄。例如 Math.PI

public class Math{
    public static final double PI = 3.14159265358979323846;
}

這樣做的好處就是波势,我們可以不需要構(gòu)建 Math 的對(duì)象,直接通過 Math.PI 來進(jìn)行訪問橄维,同時(shí)尺铣,設(shè)置為 fanal,避免了被修改的問題争舞。

靜態(tài)方法

靜態(tài)方法是一種不能向?qū)ο髮?shí)施操作的方法凛忿,例如,Math 類的 pow 方法就是一個(gè)靜態(tài)方法竞川。
Math.pow(x,a)店溢,在運(yùn)算時(shí),不使用任何 Math 對(duì)象委乌。換句話說床牧,沒有隱式的參數(shù)。
可以認(rèn)為靜態(tài)方法是沒有 this 參數(shù)的方法福澡,這也說明了為什么靜態(tài)方法無法調(diào)用非靜態(tài)變量叠赦。
在下面兩種情況下使用靜態(tài)方法:

  • 一個(gè)方法不需要訪問對(duì)象狀態(tài)驹马,其所需參數(shù)都是通過顯式參數(shù)提供革砸,例如:Math.pow
  • 一個(gè)方法只需要訪問類的靜態(tài)域,例如:Employee.getNextId

工廠方法

靜態(tài)方法還有另外一種常見的用途糯累。類似 LocalDate 和 NumberFormat 的類使用靜態(tài)工程方法 (factory method) 來構(gòu)造對(duì)象算利。

 NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
    NumberFormat percentFormatter = NumberFormat.getPercentInstance();
    double x = 0.1;
    System.out.println(currencyFormatter.format(x));  // ¥0.10
    System.out.println(percentFormatter.format(x));   //  10%

為什么 NumberFormat 不利用 constructor 來完成這些操作呢?

  • 無法命名構(gòu)造器泳姐,構(gòu)造器的名字必須要和類名一樣效拭,但是,這里希望將得到的貨幣實(shí)例和百分比實(shí)例采用不同的名字。
  • 當(dāng)使用構(gòu)造器時(shí)缎患,無法改變所構(gòu)造的對(duì)象類型慕的,而 Factory 方法將返回一個(gè) DecimalFormat 類對(duì)象,這是 NumberFormat 的子類挤渔。

main 方法

需要注意肮街,不需要使用對(duì)象調(diào)用靜態(tài)方法。例如判导,不需要構(gòu)造 Math 類對(duì)象就可以調(diào)用 Math.pow嫉父。每一個(gè)類可以有一個(gè) main 方法。這是一個(gè)常用于對(duì)類進(jìn)行單元測試的技巧眼刃。例如绕辖,可以在 Employee 類中添加一個(gè) main 方法,如果想要獨(dú)立地測試 Employee 類擂红,只需要執(zhí)行 java Employee仪际,如果 Employee 類是一個(gè)更大型應(yīng)用程序的一部分,就可以使用下面這條語句運(yùn)行程序 java Application昵骤,Employee 類的 main 方法永遠(yuǎn)不會(huì)執(zhí)行弟头。

方法參數(shù)

按值調(diào)用 (call by value) 表示方法接收的是調(diào)用者提供的值。而按引用調(diào)用 (call by reference) 表示方法接收的是調(diào)用者提供的變量地址涉茧。一個(gè)方法可以修改傳遞引用所對(duì)應(yīng)的變量值赴恨,而不能修改傳遞值調(diào)用所對(duì)應(yīng)的變量值。call by 是一個(gè)標(biāo)準(zhǔn)的計(jì)算機(jī)科學(xué)術(shù)語伴栓。Java 程序設(shè)計(jì)語言總是采用按值調(diào)用伦连。也就是說,方法得到的是所有參數(shù)值的一個(gè)拷貝钳垮,特別是惑淳,方法不能修改傳遞給它的任何參數(shù)變量的內(nèi)容。

//假定一個(gè)方法:試圖將參數(shù)值增加至3倍
public static void tripleValue(double x)
{
    x = 3 * x ;
}
double percent = 10;
tripleValue(percent)

不過饺窿,并沒有實(shí)現(xiàn)把參數(shù)增加到 3 倍歧焦。讓我們一步步來細(xì)化:

  • x 被初始化為 percent 值的一個(gè)拷貝(也就是10)
  • x 被乘以 3 后等于 30,但是 percent 還是 10
  • 這個(gè)方法結(jié)束之后肚医,參數(shù)變量 x 不再使用
    然后绢馍,方法參數(shù)共有兩種類型:
  • 基本數(shù)據(jù)類型 (數(shù)字、布爾值)
  • 對(duì)象引用
    下面這段代碼實(shí)現(xiàn)了當(dāng)對(duì)象引用作為參數(shù)的時(shí)候肠套,方法修改了參數(shù)舰涌。
public static void tripleDSalary(Employee x){
    x.raiseSalary(200);
}
harry  = new Employee(...);
tripleSalary(harry)

具體的執(zhí)行過程是:

  • x 被初始化為 harry 值的拷貝,這是一個(gè)對(duì)象的引用
  • raiseSalary 方法應(yīng)用于這個(gè)對(duì)象引用你稚。x 和 harry 同時(shí)引用的哪個(gè) Employee 對(duì)象的 salary 提高了 200%
  • 方法結(jié)束后瓷耙,參數(shù)變量 x 不再使用朱躺,當(dāng)然,對(duì)象變量 harry 繼續(xù)引用那個(gè)薪金增至 3 倍的雇員對(duì)象搁痛。
    下面總結(jié)一下 Java 中方法參數(shù)的使用情況:
  • 一個(gè)方法不能修改一個(gè)基本數(shù)據(jù)類型的參數(shù) (即數(shù)值型或布爾型)
  • 一個(gè)方法可以改變一個(gè)對(duì)象參數(shù)的狀態(tài)
  • 一個(gè)方法不能讓對(duì)象參數(shù)引用一個(gè)新的對(duì)象

對(duì)象構(gòu)造

由于對(duì)象構(gòu)造非常重要长搀,所以 Java 提供了多種編寫構(gòu)造器的機(jī)制。

重載

有些類有多個(gè) constructor鸡典,例如盈滴,可以如下構(gòu)造一個(gè)空的 StringBuilder 對(duì)象。
StringBuilder messages = new StringBuilder();
或者轿钠,可以指定一個(gè)初始字符串:
StringBuilder todoList = new StringBuilder("To do:\n");
這種特征叫做重載 (overloading)巢钓。如果多個(gè)方法有相同的名字、不同的參數(shù)疗垛,便產(chǎn)生了 overload症汹。編譯器必須挑選出具體執(zhí)行哪個(gè)方法,它通過用各個(gè)方法給出的參數(shù)類型與特定方法調(diào)用所使用的值類型進(jìn)行匹配來挑選出相應(yīng)的方法贷腕。要注意的是背镇,重載與返回值無關(guān),也就是說泽裳,方法名相同瞒斩,參數(shù)相同,返回值類型不相同是無法構(gòu)成 overload 的涮总。

默認(rèn)域初始化

如果在構(gòu)造器中沒有顯式地給域賦予初值胸囱,那么就會(huì)被自動(dòng)地賦為默認(rèn)值;數(shù)值為 0瀑梗、布爾值為 false烹笔、對(duì)象引用為 null。

這是全局變量與局部變量的主要不同點(diǎn)抛丽。必須明確地初始化方法中的局部變量谤职。但是,如果沒有初始化類中的全局變量亿鲜,將會(huì)被自動(dòng)初始化為默認(rèn)值 (0允蜈、false或null)。

無參數(shù)的構(gòu)造器

只有當(dāng)類沒有提供任何構(gòu)造器時(shí)蒿柳,系統(tǒng)才會(huì)提供一個(gè)默認(rèn)的無參構(gòu)造器饶套。

顯式域初始化

class Employee{
    private String name  = "";
}

在執(zhí)行構(gòu)造器之前,會(huì)先執(zhí)行顯式賦值操作其馏。當(dāng)一個(gè)類的所有構(gòu)造器都希望把相同的值賦予某個(gè)特定的實(shí)例域時(shí)凤跑,這種方法特別有用。

參數(shù)名

在編寫很小的構(gòu)造器時(shí)叛复,常常在參數(shù)命名上出現(xiàn)錯(cuò)誤。
通常,參數(shù)用單個(gè)字符命名褐奥,但是我們推薦這樣做:

public Employee(String aName, double aSalary){
    name = aName;
    salary = aSalary;
}

public Employee(String name, double salary){
    this.name = name;
    this.salary = salary;
}

調(diào)用另一個(gè)構(gòu)造器

關(guān)鍵字 this 引用方法的隱式參數(shù)咖耘。然而,這個(gè)關(guān)鍵字還有另外一個(gè)含義撬码。
如果構(gòu)造器的第一個(gè)語句形如 this(...)儿倒,這個(gè)構(gòu)造器將調(diào)用同一個(gè)類的另一個(gè)構(gòu)造器。下面是個(gè)典型的例子:

public Employee(double s){
    //call Employee(String, double)
    this("Employee #" + nextId,s);
    nextId++:
}

初始化塊

前面已經(jīng)講過兩種初始化數(shù)據(jù)域的方法:

  • 在構(gòu)造器中設(shè)置值
  • 在聲明中賦值
    實(shí)際上呜笑,Java 還有第三種機(jī)制夫否,稱為初始化塊 (initialization block)。在一個(gè)類的聲明種叫胁,可以包含多個(gè)代碼塊凰慈。只要構(gòu)造類的對(duì)象,這些塊就會(huì)被執(zhí)行驼鹅。例如微谓,
class Employee
{
    private static int nextId;
    
    private int id;
    private String name;
    private double salary;
    
    {
        d = nextId;
        nextId++:
    }
    
    public Employee(String n, double s)
    {
        name = n ;
        salary = s;
    }
    
    public Employee()
    {
        name = "";
        salary = 0;
    }
}

通常會(huì)直接將初始化代碼放在構(gòu)造器中。
由于初始化數(shù)據(jù)域有多種途徑输钩,所以列出構(gòu)造過程的所有路徑可能相當(dāng)混亂豺型,下面是具體處理步驟:

  • 所有數(shù)據(jù)域被初始化為默認(rèn)值 (0、false 或 null )买乃。
  • 按照在類聲明中出現(xiàn)的次序姻氨,依次執(zhí)行所有域初始化語句和初始化塊。
  • 如果構(gòu)造器第一行調(diào)用了第二個(gè)構(gòu)造器剪验,則執(zhí)行第二個(gè)構(gòu)造器主體哼绑。
  • 執(zhí)行這個(gè)構(gòu)造器的主體。
    還可以使用靜態(tài)代碼塊碉咆,在類第一次加載的時(shí)候抖韩,將會(huì)進(jìn)行靜態(tài)域的初始化。

Random API

  • Random() //構(gòu)造一個(gè)新的隨機(jī)數(shù)生成器
  • int nextInt(int n)//返回一個(gè) 0~ n-1 之間的隨機(jī)數(shù)

Java 允許使用 (package) 將類組織起來疫铜,借助于包可以方便地組織自己的代碼茂浮。使用包的主要原因是確保類名的唯一性。

類的導(dǎo)入

一個(gè)類可以使用所屬包中的所有類壳咕,以及其他包中的公有類 (public class)席揽,我們可以采用兩種方式訪問另一個(gè)包中的共有類。第一種方式是在每個(gè)類名之前添加完整的包名谓厘。

java.time.LocalDate today = java.time.LocalDate.now();

這樣比較麻煩幌羞,我們推薦使用 import 導(dǎo)包,import 語句應(yīng)該位于源文件的頂部 (但位于 package 語句的后面)竟稳。例如属桦,可以使用下面這條語句導(dǎo)入 java.util 包種所有的類熊痴。
import java.util.*;,也可以導(dǎo)入特定的類 import java.time.LocalDate;

靜態(tài)導(dǎo)入

import 不僅可以導(dǎo)入類聂宾,還增加導(dǎo)入靜態(tài)方法和靜態(tài)域的功能果善。
例如:import static java.lang.System.*;
我們就可以使用 System 類的靜態(tài)方法和靜態(tài)域,而不必加類名前綴:

out.println(&quot;Goodbye,World!&quot;);
//還可以導(dǎo)入特定的方法或域
import static java.lang.System.out;

將類放入包中

要想將一個(gè)類放入包中系谐,必須將包的名字放在源文件的開頭巾陕,包中定義類的代碼之前。

package com.horstmann.corejava;

public class Employee{
}

包作用域

標(biāo)記為 public 的部分可以被任意的類使用纪他;標(biāo)記為 private 的部分只能被定義它們的類使用鄙煤。如果沒有指定,則為 default茶袒,表示可以被同一個(gè)包中的所有方法訪問梯刚。

文檔注釋

JDK 包含一個(gè)很有用的工具,叫做 javadoc弹谁。它可以由源文件生成一個(gè) HTML 文檔乾巧。如果在源代碼種 添加以專用的 /** 開始注釋,那么可以很容易地生成一個(gè)看上去具有專業(yè)水準(zhǔn)的文檔预愤。

注釋的插入

javadoc 從下面幾個(gè)特性種抽取信息:

  • 公有類與接口
  • 公有的和受保護(hù)的構(gòu)造器及方法
  • 公有的和受保護(hù)的域
    每個(gè) /** */文檔注釋在標(biāo)記之后緊跟著自由格式文檔沟于。標(biāo)記由 @ 開始,如 @author 或 @param植康。在自由格式文本種旷太,可以使用 HTML 修飾符,例如:<em> </em>销睁,<strong> </strong>供璧。

類注釋

類注釋必須放在 import 語句之后,類定義之前冻记。

package com.example;

/**
 * Afdfsdfsdfsdf
 * sdfsdfsdfsdfs
 * fsdfdssfsdfsf
 */
public class PackageTest {

方法注釋

每一個(gè)方法注釋必須放在所描述的方法之前睡毒。除了通用標(biāo)記之外,還可以使用下面的標(biāo)記:

  • @param 變量描述
  • @return 描述
  • @throws 類描述
    下面是一個(gè)方法注釋的實(shí)例:
/**
   * 我用來說明方法的概要
   * @param s 我用來說明參數(shù)的作用
   * @param g 我用來說明參數(shù)的作用
   * @return 我用來說明返回值的作用
   */
  public int gogogo(String s , int g ){
    return 4;
  }

域注釋

只需要對(duì)公有域 (通常指的是靜態(tài)常量) 建立文檔冗栗。例如

  /**
   * 我用來說明常量作用
   */
  public static final int YEAR = 5;
  

通用注釋

下面的標(biāo)記可以用在類文檔的注釋中演顾。

  • @author 姓名,將產(chǎn)生一個(gè) "author" 條目隅居,可以使用多個(gè)钠至。
  • @version 版本,這個(gè)標(biāo)記將產(chǎn)生一個(gè) "version" 條目
  • @since 這個(gè)標(biāo)記將產(chǎn)生一個(gè) "since" 條目胎源,這里的 text 可以是對(duì)引入特性的版本描述棉钧。
  • @deprecated 這個(gè)標(biāo)記將對(duì)類、方法或變量添加一個(gè)不再使用的注釋涕蚤。
  • @see 引用宪卿,它可以用于類中的诵,也可以用于方法中。它有三種情況
    • package.class#feature label
    • <a href="...">label</a>
    • "text"
      @see 的第一種情況最常見愧捕。只要提供類奢驯、方法或變量的名字申钩,javadoc 就在文檔中插入一個(gè)超鏈接次绘。例如:
      @see com.horstmann.corejava.Employee#raiseSalary(double)
      @see com.example.GoGoGo#fuck(String)
      @see GoGoGo#fuck(String) 也可以省略包名。
      如果 @see 標(biāo)記后面有一個(gè) < 字符撒遣,就需要指定一個(gè)超鏈接邮偎,可以超鏈接到任何 URL。
@see <a href ="wwww.baidu.com">百度</a>
@see "百度"

如果愿意的話义黎,還可以在注釋中的任何位置放置指向其他類或方法的超級(jí)鏈接禾进,以及插入一個(gè)專用的標(biāo)記,例如:
{@link GoGoGo#fuck(String) label}

包與概述注解

類廉涕、方法泻云、變量的注釋都可以放置在 Java 源文件中,但是要想產(chǎn)生包注釋狐蜕,就需要在每一個(gè)包目錄中添加一個(gè)單獨(dú)的文件宠纯。可以有如下兩個(gè)選擇:

  • 提供一個(gè)以 package.html 命名的 HTML 文件层释。在標(biāo)記 <body>..</body> 之間的所有文本都會(huì)被抽取出來婆瓜。
  • 提供一個(gè)以 package-info.java 命名的 Java 文件。這個(gè)文件必須包含一個(gè)初始的以 /** */界定的 Javadoc 注釋贡羔,跟隨在一個(gè)包語句之后廉白。它不應(yīng)該包含更多的代碼或注釋。
    還可以為所有的源文件提供一個(gè)概述性的注釋乖寒,這個(gè)注釋將被放置在一個(gè)名為 overview.html 的文件中猴蹂,這個(gè)文件位于包含所有源文件的父目錄中。標(biāo)記 <body>..</body> 之間的所有文件將被抽取出來楣嘁。當(dāng)用戶從導(dǎo)航欄中選擇“Overview”時(shí)磅轻,就會(huì)顯示出這些注釋內(nèi)容。

類設(shè)計(jì)技巧

  • 一定要保證數(shù)據(jù)私有:將全局變量設(shè)置為 private马澈。
  • 一定要對(duì)數(shù)據(jù)初始化
  • 不要在類中使用過多的基本類型
  • 不是所有的變量都需要 get瓢省、set 方法
  • 將職責(zé)過多的類進(jìn)行分解
  • 類名和方法名要能夠體現(xiàn)它們的職責(zé)
  • 優(yōu)先使用不可變的類

第六章接口、lambda表達(dá)式與內(nèi)部類

接口

接口概念

在 Java 中痊班,接口不是類勤婚,而是對(duì)類的一組需求描述,這些類要遵從接口描述的統(tǒng)一格式進(jìn)行定義涤伐。
例如:Arrays 類中的 sort 方法承諾可以對(duì)對(duì)象數(shù)組進(jìn)行排序馒胆,但要求滿足下列前提:對(duì)象所屬的類必須實(shí)現(xiàn)了 Comparable 接口缨称。

//這是 Comparable 的代碼
public interface Comparable<T>
{
    int compareTo(T other);
}

接口中的所有方法自動(dòng)地屬于 public。因此祝迂,在接口中聲明方法時(shí)睦尽,不必提供關(guān)鍵字 public。接口可以定義常量型雳,接口絕不能含有實(shí)例域当凡。

  • Comparable<T>
    • int compareTo(T other):建議實(shí)現(xiàn)用這個(gè)對(duì)象與 other 進(jìn)行比較。如果這個(gè)對(duì)象小于 other 則返回負(fù)值纠俭;如果相等則返回0沿量;否則返回正值。
  • Arrays
    • static void sort(Object[] a):使用 mergesort 算法對(duì)數(shù)組 a 中的元素進(jìn)行排序冤荆。要求數(shù)組中的元素必須屬于實(shí)現(xiàn)了 Comparable 接口的類朴则,并且元素之間必須是可比較的牲剃。
  • Integer & Double
    • static int compare(int x, int y)
    • static int compare(double x, double y)
    • x < y 返回 -1丰榴,x=y 返回 0纹烹,x>y 返回 1

接口的特性

接口不是類谤逼,尤其不能使用 new 實(shí)例化一個(gè)接口滔迈。
然而袋马,盡管不能構(gòu)造接口的對(duì)象烂斋,卻能聲明接口的變量:Comparable x;
接口變量必須引用了實(shí)現(xiàn)接口的類對(duì)象:x = new Employee()
接下來辽俗,可以使用 instanceof 檢測一個(gè)對(duì)象是否實(shí)現(xiàn)了某個(gè)特定的接口
if(anObject instanceof Comparable)
接口可以實(shí)現(xiàn)繼承坐榆,而且可以多繼承拴魄。
雖然在接口中不能包含實(shí)例域或靜態(tài)方法,卻可以包含常量席镀,接口中的域?qū)⒈蛔詣?dòng)設(shè)為 public static final匹中。
盡管每個(gè)類只能擁有一個(gè)父類,但卻可以實(shí)現(xiàn)多個(gè)接口豪诲。

接口與抽象類

接口與抽象類的目的差不多顶捷,都想讓實(shí)現(xiàn)類實(shí)現(xiàn)自己的抽象方法,但 Java 是單繼承的屎篱,如果沒有接口服赎,只有抽象類,那一個(gè)類繼承完抽象類后交播,就不能再繼承其他類了重虑,所以接口顯得更加靈活。

靜態(tài)方法

在 Java8 中秦士,允許在接口中添加靜態(tài)方法缺厉。

默認(rèn)方法

可以為接口方法提供一個(gè)默認(rèn)實(shí)現(xiàn)。必須用 default 修飾符標(biāo)記這個(gè)方法。不過一般沒有什么作用提针,因?yàn)閷?shí)現(xiàn)了接口的類往往會(huì)重新實(shí)現(xiàn)這個(gè)方法命爬,如果設(shè)置了 default,那么在實(shí)現(xiàn)接口的時(shí)候就不會(huì)強(qiáng)制要求你實(shí)現(xiàn)這個(gè)抽象方法了辐脖。

 default int compareTo(Object other) {
    return 0;
  }

解決默認(rèn)方法沖突

如果先在一個(gè)接口中將一個(gè)方法定義為默認(rèn)方法饲宛,然后又在父類或另一個(gè)接口中定義了同樣的方法,會(huì)發(fā)生什么情況嗜价?

  • 父類優(yōu)先艇抠。如果父類提供了一個(gè)具體方法,同名而且有相同參數(shù)類型的默認(rèn)方法會(huì)被忽略炭剪。
  • 接口沖突练链。如果一個(gè)父接口提供了一個(gè)默認(rèn)方法翔脱,另一個(gè)接口提供了一個(gè)同名而且參數(shù)類型相同的方法奴拦,必須 override 這個(gè)方法來解決沖突。
//兩個(gè)接口届吁,同樣的方法错妖,一個(gè)設(shè)置為 default 
public interface Named {
  default String getName() {
    return “d“;
  }
}

interface Person {
  String getName();
}

//當(dāng)你同時(shí)實(shí)現(xiàn)這兩個(gè)接口的時(shí)候,編譯器會(huì)強(qiáng)制要求你實(shí)現(xiàn)一個(gè) getName() 方法
//而不是直接使用 Named 的 default 方法
class Student implements Named,Person{

}

那么父類和接口擁有同樣的方法會(huì)發(fā)生什么呢疚沐?

public interface Named {
  default String getName() {
    return ”d“;
  }
}


public class Student extends Person implements Named {

}

class Person {
  public String getName() {
    return ”superClass“;
  }
}

此時(shí)的話暂氯,是“類優(yōu)先”的,無論 Named 的方法加不加 default亮蛔,父類的方法都會(huì) override 接口的方法痴施。

接口實(shí)例

接口與回調(diào)

回調(diào) (callback) 是一種常見的程序設(shè)計(jì)模式。在這種模式中究流,可以指出某個(gè)特定事件發(fā)生時(shí)應(yīng)該采取的動(dòng)作辣吃。

Comparator 接口

之前我們已經(jīng)了解了如何對(duì)一個(gè)對(duì)象數(shù)組排序,前提是這些對(duì)象是實(shí)現(xiàn)了 Comparator 接口的類的實(shí)例芬探。例如神得,可以對(duì)一個(gè)字符串?dāng)?shù)組排序,因?yàn)?String 類實(shí)現(xiàn)了 Comparable&lt;String&gt;偷仿,而且 String.compareTo 方法可以對(duì)字典順序比較字符串哩簿。

public static void main(String[] args) {
    String[] staff = { "fgfgfgf", "v", "zdgdgdgdgdgdgdg", "adgdgdgfgdgdgdgdgdg", "d", "m", "q" };
    Arrays.sort(staff);
    System.out.println(Arrays.toString(staff));
  }
  //[adgdgdgfgdgdgdgdgdg, d, fgfgfgf, m, q, v, zdgdgdgdgdgdgdg]
  //是用字典順序規(guī)律排序的,無視長度

假設(shè)我們現(xiàn)在希望用長度遞增的順序?qū)ψ址M(jìn)行排序酝静,而不是按字典順序進(jìn)行排序节榜,如果要實(shí)現(xiàn)這種情況,可以選用 Arrays.sort 方法的第二個(gè)版本别智,有一個(gè)數(shù)組和一個(gè)比較器 (comparator) 作為參數(shù)宗苍,comparator 是實(shí)現(xiàn)了 Comparator 接口的類的實(shí)例。

public class LengthComparator implements Comparator&lt;String&gt; {
  @Override public int compare(String s, String t1) {
    return s.length() - t1.length();
  }
}
public class EmployeeSortTest {

  public static void main(String[] args) {
    String[] staff = { "fgfgfgf", "v", "zdgdgdgdgdgdgdg", "adgdgdgfgdgdgdgdgdg", "d", "m", "q" };
    Arrays.sort(staff);
    System.out.println(Arrays.toString(staff));
    Arrays.sort(staff, new LengthComparator());
    System.out.println(Arrays.toString(staff));
  }
}

//
[adgdgdgfgdgdgdgdgdg, d, fgfgfgf, m, q, v, zdgdgdgdgdgdgdg]
[d, m, q, v, fgfgfgf, zdgdgdgdgdgdgdg, adgdgdgfgdgdgdgdgdg]

對(duì)象克隆

本節(jié)會(huì)討論 Cloneable 接口亿遂,這個(gè)接口指示一個(gè)類提供了一個(gè)安全的 clone 方法浓若。(克隆并不太常見渺杉,可以稍作了解,等真正需要時(shí)再深入學(xué)習(xí))挪钓。先來回憶為一個(gè)包含對(duì)象引用的變量建立副本時(shí)會(huì)發(fā)生什么是越。原變量和副本都是同一個(gè)對(duì)象的引用。這說明碌上,任何一個(gè)變量改變都會(huì)影響另一個(gè)變量倚评。

Employee original = new Employee(&quot;John Public&quot;, 50000);
    Employee copy = original;
    copy.raiseSalary(10);

此時(shí),改變 copy 的狀態(tài)馏予,就會(huì)改變 original 的狀態(tài)天梧。如果我們希望 copy 是一個(gè)新對(duì)象,它的初始狀態(tài)與 original 相同霞丧,但是之后它們各自會(huì)有自己不同的狀態(tài)呢岗,這種情況下就可以使用 clone 方法。

 Employee original = new Employee(&quot;John Public&quot;, 50000);
    //Employee copy =original.clone();
    //copy.raiseSalary(10);  //此時(shí) original 不會(huì)發(fā)生改變

不過并沒有這么簡單蛹尝。clone 方法是 Object 的一個(gè) protected 方法后豫,如果我們使用從 Object 繼承得到的 clone 方法,從 A 克隆出一個(gè) B 的話突那,它們的域如果都是基本數(shù)據(jù)類型的話挫酿,那么是可以實(shí)現(xiàn)互不干涉的,但是假設(shè)它們的域中包含引用對(duì)象愕难,那么 A 和 B 的引用對(duì)象域仍然會(huì)存在共享的情況早龟。這種默認(rèn)的 clone 操作叫做淺拷貝,存在缺陷猫缭。
不過葱弟,通常子對(duì)象都是可變的,必須重新定義 clone 方法來建立一個(gè)深拷貝饵骨,同時(shí)克隆所有子對(duì)象翘悉。
對(duì)于一個(gè)類需要確定:

  • 默認(rèn)的 clone 方法是否滿足要求;
  • 是否可以在可變的子對(duì)象上調(diào)用 clone 來修補(bǔ)默認(rèn)的 clone 方法居触;
  • 是否不該使用 Clone妖混。
    如果選擇第 1 項(xiàng)或第 2 項(xiàng),類必須:
  • 實(shí)現(xiàn) Cloneable 接口
  • 重新定義 clone 方法轮洋,并指定 public 修飾符制市。
    Cloneable 接口是 Java 提供的一組標(biāo)記接口 (tagging interface) 之一。也可以稱為記號(hào)接口 (marker interface)弊予。
    即使 clone 的淺拷貝用法也能夠滿足要求祥楣,還是需要實(shí)現(xiàn) Cloneable 接口,將 clone 重新定義為 public,再調(diào)用 super.clone()误褪。下面是個(gè)例子责鳍。
class Employee implements Cloneable{
@Override
  public Employee clone() throws CloneNotSupportedException {
    return (Employee) super.clone();
  }
  }

下面來看一個(gè)深拷貝的 clone 方法的例子:

  @Override
  public CloneTest clone() throws CloneNotSupportedException {
    CloneTest copy = (CloneTest) super.clone();
    copy.mEmployee = mEmployee.clone();
    return copy;
  }

另外,所欲偶的數(shù)組類型都有一個(gè) public 的 clone 方法兽间,而不是 protected历葛。可以用這個(gè)方法建立一個(gè)新數(shù)組嘀略,包含原數(shù)組所有元素的副本恤溶,例如:

int[] luckyNumbers= {2,3,5,7,11,13}
int[] cloned = luckyNumbers.clone();
cloned[5] = 12; //不會(huì)改變 luckyNumbers[5] 的數(shù)值

lambda 表達(dá)式

了解如何使用 lambda 表達(dá)式采用一種簡潔的語法定義代碼塊,以及如何編寫處理 lambda 表達(dá)式的代碼帜羊。

Why lambda ?

lambda 表達(dá)式是一個(gè)可傳遞的代碼塊咒程,可以在以后執(zhí)行一次或多次。在 Java 中讼育,不能直接傳遞代碼塊帐姻。必須構(gòu)造一個(gè)對(duì)象,這個(gè)對(duì)象的類需要有一個(gè)方法能包含所需的代碼窥淆。lambda 的設(shè)計(jì)卖宠,是為了解決 Java 如何做到函數(shù)式編程。

lambda 表達(dá)式的語法

(String first, String second) -> first.length() - second.length()
參數(shù)忧饭,箭頭 → 以及一個(gè)表達(dá)式。如果代碼要完成的計(jì)算無法放在一個(gè)表達(dá)式中筷畦,就可以像寫方法一樣词裤,把這些代碼放在 {} 中,并包含顯式的 return 語句鳖宾。例如:

(String first, String second) ->
{
    if(first.length() < second.length()) return -1;
    else if(first.length() > second.length()) return 1;
    else return 0;
}

即使 lambda 表達(dá)式?jīng)]有參數(shù)吼砂,仍然要提供空括號(hào),就像無參數(shù)方法一樣

() -> {for (int i=100;i>=0;i--) System.out.println(i);}

如果可以推導(dǎo)出一個(gè) lambda 表達(dá)式的參數(shù)類型鼎文,則可以忽略其類型渔肩。例如:

Comparator<String> comp = (first, second) ->first.length() - second.length();

在這里,編譯器可以推導(dǎo)出 first 和 second 必然是字符串拇惋,因?yàn)檫@個(gè) lambda 表達(dá)式將賦給一個(gè)字符串比較器周偎。
如果方法只有一個(gè)參數(shù),而且這個(gè)參數(shù)的類型可以推導(dǎo)得出撑帖,那么甚至還可以省略小括號(hào)蓉坎;

ActionListener listener = event -> System.out.println("The time is"+new Date());

無需指定 lambda 表達(dá)式的返回類型。lambda 表達(dá)式的返回類型總是由上下文推導(dǎo)得出胡嘿。
(String first, String second) -> first.length() - second.length()蛉艾,可以在需要 int 類型結(jié)果的上下文中使用。

public static void main(String[] args) {
    String[] planets =
        new String[] { "Mercury", "Venus", "Earth", "Jupiter", "Saturn", "Uranus", "Neptune" };
    System.out.println(Arrays.toString(planets));
    System.out.println("Sorted in dictionary order:");
    Arrays.sort(planets);
    System.out.println(Arrays.toString(planets));
    System.out.println("Sorted by length");
    Arrays.sort(planets, (first, second) -> first.length() - second.length());
    System.out.println(Arrays.toString(planets));
  }

函數(shù)式接口

對(duì)于只有一個(gè)抽象方法的接口,需要這種接口的對(duì)象時(shí)勿侯,就可以提供一個(gè) lambda 表達(dá)式拓瞪。這種接口稱為函數(shù)式接口 (functionnal interface)。最好把 lambda 表達(dá)式看作是一個(gè)函數(shù)助琐,而不是一個(gè)對(duì)象吴藻,另外要接受 lambda 表達(dá)式可以傳遞到函數(shù)式接口。
lambda 表達(dá)式可以轉(zhuǎn)換為接口弓柱,這一點(diǎn)讓 lambda 表達(dá)式很有吸引力沟堡。

//這是將 lambda 轉(zhuǎn)換為函數(shù)式接口的例子
BiFunction<String, Integer, Boolean> comp = (name, age) -> name.length() > age;

ArrayList 類有一個(gè) removeIf 方法,它的參數(shù)就是一個(gè) Predicate矢空。
public interface Predicate<T> {boolean test(T t);}航罗,這也是一個(gè)函數(shù)式接口,所以我們可以使用 lambda屁药。

 ArrayList<String> a = new ArrayList<>();
    a.add(null);
    a.add(null);
    a.add(null);
    a.add(null);
    a.add("dsada");
    a.add("czxzd");
    a.add("gadga");
    a.add("zcbzc");
    a.removeIf(e -> e == null);
    for (int i = 0; i < a.size(); i++) {
      System.out.println(a.get(i)); 
    }
    //Predicate 的泛型是根據(jù) ArrayList 的泛型的粥血,這里的代碼就是將 ArrayList 中的 null 值都刪除了。

方法引用

有時(shí)酿箭,可能已經(jīng)有現(xiàn)成的方法可以完成你想要傳遞到其他代碼的某個(gè)動(dòng)作复亏。
Timer t = new Timer(10000,System.out::println),等價(jià)于 x -> System.out.println(x)
再來看一個(gè)例子,假設(shè)你想對(duì)字符串排序缭嫡,而不考慮字母的大小寫缔御。可以傳遞以下方法表達(dá)式:
Arrays.sort(strings,String::compareToIgonreCase)
從這些例子可以看出妇蛀,要用 :: 操作符分隔方法名與對(duì)象或類名耕突。主要有 3 種情況:

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod
    在前 2 種情況種,方法引用等價(jià)于提供方法參數(shù)的 lambda 表達(dá)式评架。前面已經(jīng)提到眷茁,System.out::println 等價(jià)于 x -> System.out.println(x)。類似地纵诞,Math::pow 等價(jià)于 (x,y) ->Math.pow(x,y)上祈。
    對(duì)于第 3 種情況,第 1 個(gè)參數(shù)會(huì)成為方法的目標(biāo)浙芙。
    例如登刺,String::compareToIgnoreCase 等同于(x,y) ->x.compareToIgnoreCase(y)
    可以在方法引用中使用 this 參數(shù)茁裙。例如塘砸,this::equals 等同于 x-> this.equals(x)。使用 super 也是合法的晤锥。下面的方法表達(dá)式
    super:instanceMethod

構(gòu)造器引用

構(gòu)造器引用與方法引用很類似掉蔬,只不過方法名為 new廊宪。例如,Person::new 是 Person 構(gòu)造器的一個(gè)引用女轿。哪一個(gè)構(gòu)造器呢箭启?這取決于上下文。

public static void main(String[] args) {
    ArrayList<String> names = new ArrayList<>();
    Stream<Person> stream = names.stream().map(Person::new);
  }
}

class Person {
  Person(String name) {

  

重點(diǎn)是 map 方法會(huì)為各個(gè)列表元素調(diào)用 Person(String) 構(gòu)造器蛉迹。
可以用數(shù)組類型建立構(gòu)造器引用傅寡。例如,int[]::new 是一個(gè)構(gòu)造器引用北救,它有一個(gè)參數(shù):即數(shù)組的長度荐操。這等價(jià)于 lambda 表達(dá)式 x -> new int[x]

@FunctionalInterface interface Fuck {
  int[] createIntegerArray(int length);
}
public static void main(String[] args) {
    Fuck fuck = int[]::new;
  }

Java 有一個(gè)限制珍策,無法構(gòu)造泛型類型 T 的數(shù)組托启。數(shù)組構(gòu)造器引用對(duì)于客服這個(gè)限制很有用。表達(dá)式 new T[n] 會(huì)產(chǎn)生錯(cuò)誤攘宙,因?yàn)檫@會(huì)改為 new Object[n]屯耸。不過 toArray 有一個(gè)重載方法,引用一個(gè)函數(shù)式接口蹭劈,解決了這個(gè)問題疗绣。

Person[] people = stream.toArray(Person[]::new);

變量作用域

通常,你可能希望能夠在 lambda 表達(dá)式中訪問外圍方法或類中的變量铺韧。

public static void repeatMessage(String test, int delay) {
    ActionListener listener = event -> System.out.println(test);//這里只是實(shí)現(xiàn)了接口的方法而已多矮,并沒有馬上調(diào)用
    test = "Change";//當(dāng)我們改變test值的時(shí)候,編譯器會(huì)報(bào)錯(cuò)
  }
  Variable used in lambda expression should be final or effectively final

lambda 表達(dá)式有 3個(gè)部分:

  • 一個(gè)代碼塊
  • 參數(shù)
  • 自由變量的值祟蚀,這是指非參數(shù)而且不在代碼中定義的變量工窍。
    在我們的代碼中,lambda 有 1 個(gè)自由變量 test前酿。表示 lambda 的數(shù)據(jù)結(jié)構(gòu)必須存儲(chǔ)自由變量的指。在這里就是字符串 test鹏溯。我們說它被 lambda 捕獲 (captured)罢维。例如,可以把一個(gè) lambda 轉(zhuǎn)換為包含一個(gè)方法的對(duì)象丙挽,這樣自由變量的值就會(huì)復(fù)制到這個(gè)對(duì)象的實(shí)例變量中肺孵。
    關(guān)于代碼塊以及自由變量值有一個(gè)術(shù)語:閉包 (closure),在 Java 中颜阐,lambda 表達(dá)式就是閉包平窘。
    在 lambda 中,只能引用值不會(huì)改變的變量凳怨,也不能在 lambda 中改變自由變量的值瑰艘。
    在一個(gè) lambda 中使用 this 關(guān)鍵字時(shí)是鬼,是指創(chuàng)建這個(gè) lambda 的方法的 this 參數(shù)。
public class Application()
{
    public void init()
    {
        ActionListener listener = event ->
            {
                System.out.println(this.toString());
            }
    }
}

這里會(huì)調(diào)用 Application 對(duì)象的 toString 方法紫新,而不是 ActionListener 實(shí)例的方法均蜜。

處理 lambda 表達(dá)式

下面來看如何編寫方法處理 lambda 表達(dá)式。
使用 lambda 的重點(diǎn)是延遲執(zhí)行 (deferred execution)芒率。之所以希望以后再執(zhí)行代碼囤耳,這有很多原因

  • 在一個(gè)單獨(dú)的線程中運(yùn)行代碼
  • 多次運(yùn)行代碼
  • 在算法適當(dāng)位置運(yùn)行代碼
  • 發(fā)生某種情況執(zhí)行代碼,點(diǎn)擊了一個(gè)按鈕偶芍,數(shù)據(jù)到達(dá)充择,等等
  • 只在必要時(shí)才運(yùn)行代碼
    可以為你自己設(shè)計(jì)的函數(shù)式接口打上 @FunctionalInterface 標(biāo)記。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末匪蟀,一起剝皮案震驚了整個(gè)濱河市椎麦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萄窜,老刑警劉巖铃剔,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異查刻,居然都是意外死亡键兜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門穗泵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來普气,“玉大人,你說我怎么就攤上這事佃延∠志鳎” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵履肃,是天一觀的道長仔沿。 經(jīng)常有香客問我,道長尺棋,這世上最難降的妖魔是什么封锉? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮膘螟,結(jié)果婚禮上成福,老公的妹妹穿的比我還像新娘。我一直安慰自己荆残,他們只是感情好奴艾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著内斯,像睡著了一般蕴潦。 火紅的嫁衣襯著肌膚如雪像啼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天品擎,我揣著相機(jī)與錄音埋合,去河邊找鬼。 笑死萄传,一個(gè)胖子當(dāng)著我的面吹牛甚颂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播秀菱,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼振诬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了衍菱?” 一聲冷哼從身側(cè)響起赶么,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脊串,沒想到半個(gè)月后辫呻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琼锋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年放闺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缕坎。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡怖侦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谜叹,到底是詐尸還是另有隱情匾寝,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布荷腊,位于F島的核電站艳悔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏女仰。R本人自食惡果不足惜很钓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望董栽。 院中可真熱鬧,春花似錦企孩、人聲如沸锭碳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擒抛。三九已至推汽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間歧沪,已是汗流浹背歹撒。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诊胞,地道東北人暖夭。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像撵孤,于是被迫代替她去往敵國和親迈着。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容