TL;DR
面向?qū)ο?3 大類(lèi)型:類(lèi)攒菠、接口歇由、枚舉邪意。
面向?qū)ο?4 大修飾符:private
| protected
| public
(互斥)牵囤、static
斗塘、final
古瓤、abstract
止剖。
面向?qū)ο?5 大成員:成員變量腺阳、方法、構(gòu)造器穿香、初始化塊亭引、內(nèi)部類(lèi)。
總述
面向?qū)ο缶幊?/strong>(Object-Oriented Programming, 簡(jiǎn)稱(chēng) OOP)是一種編程范式扔水。
在 面向?qū)ο缶幊?/strong> 中痛侍,有兩個(gè)重要的概念:
- 類(lèi):一類(lèi)事物的統(tǒng)稱(chēng)(例如 動(dòng)物 類(lèi))
- 對(duì)象:某個(gè)類(lèi)中的實(shí)例(例如 狗、貓 )
在一個(gè)類(lèi)中魔市,有 成員變量 ( field )主届、 方法 ( method )、構(gòu)造器(constructor)待德、初始化塊君丁、內(nèi)部類(lèi)(nested class)五大成員。
牢記:
始終只定義你所關(guān)心的項(xiàng)目将宪!
始終只定義你所關(guān)心的項(xiàng)目绘闷!
始終只定義你所關(guān)心的項(xiàng)目!
舉個(gè)例子:現(xiàn)定義一個(gè)人類(lèi)(Human)较坛,成員變量可以有姓名(name)印蔗、性別(gender)、年齡(age)丑勤、身高(height)华嘹、體重(weight)等,方法可以有走(walk)法竞、跑(run)耙厚、跳(jump)、吃(eat)等岔霸。
對(duì)應(yīng)的 UML 圖為:
初識(shí)面向?qū)ο?/h2>
定義類(lèi)
在 Java 中薛躬,定義類(lèi)的統(tǒng)一格式如下:
[修飾符] class 類(lèi)名 {
// ...
}
其中,修飾符(可省略)中的訪(fǎng)問(wèn)權(quán)限只能為public
(公開(kāi)類(lèi))呆细,其他修飾符只能為final
或abstract
(抽象類(lèi))型宝。
類(lèi)名則為合法的標(biāo)識(shí)符,一般采用大駝峰命名法來(lái)表示絮爷。(詳見(jiàn)駝峰命名法)
成員變量
在 Java 中诡曙,定義成員變量的統(tǒng)一格式如下:
[修飾符] 類(lèi)型 成員變量名 [= 初始值];
其中,修飾符(可省略)中的訪(fǎng)問(wèn)權(quán)限可以為public
|protected
|private
略水。其他修飾符可以為final
价卤、static
。
類(lèi)型可以為任意的基本類(lèi)型或引用類(lèi)型渊涝。
成員變量名同樣為合法的標(biāo)識(shí)符慎璧,一般采用小駝峰命名法來(lái)表示床嫌。成員變量名通常為名詞(如身高、體重)胸私。
初始值可以省略厌处,如不顯式指定初始值則為該類(lèi)型的默認(rèn)值(數(shù)值型為0
,布爾型為False
岁疼,引用類(lèi)型為null
)阔涉。
方法
在 Java 中,方法必須定義在類(lèi)中捷绒,不能單獨(dú)存在瑰排。定義方法的統(tǒng)一格式如下:
[修飾符] 返回值類(lèi)型 方法名( [形參列表] ) {
// ...
// 如果聲明了返回值類(lèi)型,必須有 return 語(yǔ)句
}
其中暖侨,修飾符(可省略)中的訪(fǎng)問(wèn)權(quán)限可以為public
|protected
|private
椭住。其他修飾符可以為final
|abstract
、static
字逗。
返回值類(lèi)型可以為任意的基本類(lèi)型或引用類(lèi)型京郑,也可以為void
(無(wú)返回值)。
方法名為合法的標(biāo)識(shí)符葫掉,一般采用小駝峰命名法來(lái)表示些举。方法名通常為動(dòng)詞(如走、跑)俭厚。
構(gòu)造器
構(gòu)造器的作用是在new
一個(gè)對(duì)象時(shí)自動(dòng)執(zhí)行的方法户魏。如果沒(méi)有為類(lèi)創(chuàng)建構(gòu)造器,Java 會(huì)自動(dòng)創(chuàng)建一個(gè)無(wú)參構(gòu)造器套腹。
在 Java 中绪抛,定義構(gòu)造器的統(tǒng)一格式如下:
[修飾符] 類(lèi)名( [形參列表] ) {
// ...
}
注意:構(gòu)造器名只能與類(lèi)名相同资铡,且不能寫(xiě)返回值類(lèi)型
其中电禀,修飾符(可省略)只能為public
|protected
|private
。
關(guān)鍵字
訪(fǎng)問(wèn)控制
Java 面向?qū)ο笾性L(fǎng)問(wèn)權(quán)限有 3 個(gè)(從小到大排序):
-
private
:類(lèi)訪(fǎng)問(wèn)權(quán)限笤休,只能在該類(lèi)中被訪(fǎng)問(wèn)(徹底隱藏)尖飞。 - 默認(rèn)(不寫(xiě)):包訪(fǎng)問(wèn)權(quán)限,只能在該類(lèi)和該類(lèi)所在的包中被訪(fǎng)問(wèn)(部分隱藏)店雅。
-
protected
:子類(lèi)訪(fǎng)問(wèn)權(quán)限政基,只能在該類(lèi)、該類(lèi)所在的包及該類(lèi)的子類(lèi)中被訪(fǎng)問(wèn)(部分暴露)闹啦。 -
public
:公共訪(fǎng)問(wèn)權(quán)限沮明,該類(lèi)可以在任意地方來(lái)訪(fǎng)問(wèn)(徹底暴露)。
private |
默認(rèn) | protected |
public |
|
---|---|---|---|---|
當(dāng)前類(lèi) | ? | ? | ? | ? |
當(dāng)前包 | ? | ? | ? | ? |
子類(lèi) | ? | ? | ? | ? |
任意 | ? | ? | ? | ? |
static
有static
的成員屬于類(lèi)成員窍奋,無(wú)static
的成員屬于實(shí)例成員荐健。static
只能修飾成員變量酱畅、方法、初始化塊江场、內(nèi)部類(lèi)纺酸。
使用static
修飾的成員,通常使用類(lèi)名.成員名
訪(fǎng)問(wèn)址否。
注意:
- Java 允許通過(guò)實(shí)例對(duì)象來(lái)訪(fǎng)問(wèn)
static
成員餐蔬,但并不推薦這么做。 - 非
static
成員可以訪(fǎng)問(wèn)static
成員佑附,相反樊诺,static
成員不能調(diào)用非static
成員。
this
this 引用
this
可以出現(xiàn)在非static
的方法帮匾、構(gòu)造器中啄骇。用于代表當(dāng)前正在使用的對(duì)象(誰(shuí)調(diào)用他就代表誰(shuí))。
例如:
public class Flower {
String color;
public Flower() {
this.color = "yellow"; // 創(chuàng)建對(duì)象時(shí)將顏色設(shè)置為 黃色
}
public void output() {
System.out.println("已修改");
}
public void change() {
this.color = "red"; // 將當(dāng)前對(duì)象的顏色修改為 紅色
this.output(); // 調(diào)用當(dāng)前對(duì)象中的 output 方法
}
}
this 調(diào)用
可以通過(guò)this(參數(shù))
來(lái)調(diào)用該類(lèi)的對(duì)應(yīng)構(gòu)造器瘟斜,具體可見(jiàn)下方的構(gòu)造器重載缸夹。
super
super 限定
與this 引用類(lèi)似,super
用于限定訪(fǎng)問(wèn)父類(lèi)的實(shí)例變量螺句、實(shí)例方法虽惭。
class Base {
int foo = 20;
public void bar() {
System.out.println("Base中的foo方法");
}
}
class Sub extends Base {
int foo = 200;
@Override
public void bar() {
System.out.println("Sub中的foo重寫(xiě)方法");
}
public void test() {
System.out.println(foo); // 200 (Sub類(lèi))
System.out.println(this.foo); // 200 (Sub類(lèi))
System.out.println(super.foo); // 20 (Base類(lèi))
bar(); // Sub中的foo重寫(xiě)方法 (Sub類(lèi))
this.bar(); // Sub中的foo重寫(xiě)方法 (Sub類(lèi))
super.bar(); // Base中的foo方法 (Base類(lèi))
}
}
調(diào)用父類(lèi)構(gòu)造器
與this 調(diào)用類(lèi)似,用于調(diào)用父類(lèi)構(gòu)造器蛇尚。
子類(lèi)構(gòu)造器一定要調(diào)用父類(lèi)構(gòu)造器一次芽唇。如果子類(lèi)構(gòu)造器沒(méi)有顯式調(diào)用父類(lèi)構(gòu)造器,系統(tǒng)將在子類(lèi)構(gòu)造器開(kāi)頭自動(dòng)調(diào)用父類(lèi)無(wú)參構(gòu)造取劫。
class Fruit {
private double weight;
public Fruit(double weight) {
this.weight = weight;
}
}
class Apple extends Fruit {
private String name;
public Apple(double weight, String name) {
super(weight);
this.name = name;
}
}
final
final
可以修飾變量(成員變量匆笤、局部變量)、方法谱邪、類(lèi)炮捧。與abstract
互斥。
final
修飾變量
當(dāng)final
修飾變量時(shí)惦银,必須為該變量賦初始值咆课,且無(wú)法重新賦值(對(duì)象可以進(jìn)行修改)。
- 當(dāng)
final
修飾實(shí)例變量時(shí)扯俱,必須顯式指定初始值书蚪,且只能在定義時(shí)、實(shí)例初始化塊迅栅、各構(gòu)造器其中的一個(gè)位置指定殊校。 - 當(dāng)
final
修飾類(lèi)變量時(shí),必須顯式指定初始值读存,且只能在定義時(shí)为流、類(lèi)初始化塊其中的一個(gè)位置指定窜醉。
final
的宏替換
當(dāng)變量滿(mǎn)足以下條件時(shí),該變量出現(xiàn)的所有地方將會(huì)被替換成變量的值:
- 變量有
final
修飾 - 聲明時(shí)指定了初始值
- 變量初始值可在編譯時(shí)確定
final
修飾方法
final
修飾方法指不允許被子類(lèi)重寫(xiě)艺谆,避免該方法被子類(lèi)破壞榨惰。
final
修飾類(lèi)
final
修飾類(lèi)以后該類(lèi)不允許派生子類(lèi)。
abstract
abstract
只能修飾類(lèi)和方法静汤,且與final
互斥琅催。
抽象類(lèi)
抽象類(lèi)指使用abstract
修飾的類(lèi),主要作用是派生子類(lèi)虫给。
抽象類(lèi)有以下特性:
- 抽象類(lèi)可以有抽象方法藤抡。
- 抽象類(lèi)無(wú)法創(chuàng)建對(duì)象。
public abstract class Test {}
抽象方法
抽象方法指只有方法簽名抹估,無(wú)方法體的方法付枫。抽象方法必須被子類(lèi)重寫(xiě)急凰,否則不能被調(diào)用白翻。
public abstract void test() // 不能有方法體
面向?qū)ο笕筇卣?/h2>
封裝
封裝包含兩方面含義:
- 隱藏:將內(nèi)部實(shí)現(xiàn)細(xì)節(jié)隱藏
- 暴露:通過(guò)暴露的接口來(lái)操作對(duì)象欣福。
封裝的要求:合理隱藏,合理暴露语泽。
封裝主要是通過(guò)訪(fǎng)問(wèn)控制修飾符來(lái)實(shí)現(xiàn)
在 Java 中贸典,實(shí)例變量通常使用private
來(lái)修飾,將其隱藏踱卵。并提供相應(yīng)的getter
廊驼、setter
方法,來(lái)控制該成員變量的訪(fǎng)問(wèn)惋砂。
如下代碼所示妒挎,User
類(lèi)中有一個(gè)成員變量(name
),要求name
的長(zhǎng)度在 10 位以?xún)?nèi)西饵。
class User {
private String name;
public void setName(String name){
if (name.length > 10) {
System.out.println("名稱(chēng)長(zhǎng)度必須在10位以?xún)?nèi)酝掩!");
return;
}
this.name = name;
}
public String getName() {
return this.name;
}
}
繼承
繼承最大好處是代碼復(fù)用。
Java 中繼承是類(lèi)與類(lèi)之間的關(guān)系(而非對(duì)象與對(duì)象之間的關(guān)系)罗标,是一種由一般到特殊的關(guān)系(如蘋(píng)果類(lèi)(子類(lèi)庸队、派生類(lèi))繼承了水果類(lèi)(父類(lèi)积蜻、超類(lèi)闯割、基類(lèi))),子類(lèi)的實(shí)例可以當(dāng)作父類(lèi)的實(shí)例來(lái)使用竿拆。
在 Java 中宙拉,繼承通過(guò)如下語(yǔ)法定義:
[修飾符] class 子類(lèi)名 extends 父類(lèi) {
// ...
}
注意:
- Java 是單繼承父類(lèi),只能有 1 個(gè)直接繼承的父類(lèi)丙笋。
- 如果不顯式繼承父類(lèi)谢澈,Java 默認(rèn)繼承
Object
類(lèi)煌贴。
子類(lèi)可以調(diào)用父類(lèi)非private
修飾的成員變量、方法(見(jiàn)上方訪(fǎng)問(wèn)控制表格)锥忿。
多態(tài)
多態(tài)是指同一個(gè)類(lèi)型的多個(gè)實(shí)例牛郑,在執(zhí)行同一個(gè)方法時(shí),呈現(xiàn)出多種行為特征敬鬓。
變量的類(lèi)型
編譯時(shí)類(lèi)型:聲明該變量時(shí)指定的類(lèi)型淹朋。在 Java 程序編譯階段,Java 編譯器只認(rèn)編譯時(shí)類(lèi)型钉答。當(dāng)調(diào)用子類(lèi)有而父類(lèi)沒(méi)有的方法础芍,且使用向上轉(zhuǎn)型時(shí),編譯器將報(bào)錯(cuò)数尿。
運(yùn)行時(shí)類(lèi)型:該變量實(shí)際所引用的類(lèi)型仑性。
向上轉(zhuǎn)型
子類(lèi)的對(duì)象可以直接賦值給父類(lèi)變量,其可以自動(dòng)完成右蹦。
例如诊杆,Ostrich 類(lèi)繼承了Bird 類(lèi),那么定義Ostrich實(shí)例時(shí)可以進(jìn)行如下定義:
class Bird {
public void fly() {
System.out.println("飛咯~");
}
}
class Ostrich extends Bird {
@Override
public void fly() {
System.out.println("不會(huì)飛呀~");
}
}
public class Test {
public static void main(String[] args){
Bird b1 = new Bird();
Bird b2 = new Ostrich();
b1.fly(); // 飛咯~
b2.fly(); // 不會(huì)飛呀~
}
}
向下轉(zhuǎn)型 (強(qiáng)制轉(zhuǎn)換)
當(dāng)使用向上轉(zhuǎn)型特性時(shí)何陆,想要調(diào)用子類(lèi)方法時(shí)刽辙,需要強(qiáng)制轉(zhuǎn)換成對(duì)應(yīng)類(lèi)型。
class Bird {
public void fly() {
System.out.println("飛咯~");
}
}
class Ostrich extends Bird {
public void run() {
System.out.println("跑得快呢");
}
}
public class Test {
public static void main(String[] args){
Bird b1 = new Ostrich();
// b1.run(); // 無(wú)法編譯
Ostrich b2 = (Ostrich) b1;
b2.run(); // 正常編譯
}
}
注意:
- 強(qiáng)轉(zhuǎn)運(yùn)算符只能在編譯類(lèi)型具有繼承關(guān)系的變量之間進(jìn)行強(qiáng)轉(zhuǎn)甲献,否則編譯將會(huì)報(bào)錯(cuò)(如
String
類(lèi)型強(qiáng)轉(zhuǎn)成Integer
)宰缤。 - 如果在編譯類(lèi)型具有繼承關(guān)系的變量之間轉(zhuǎn)換時(shí),如果被轉(zhuǎn)變量的實(shí)際類(lèi)型不是要轉(zhuǎn)換的目標(biāo)類(lèi)型晃洒,程序就會(huì)引發(fā)
ClassCastException
異常慨灭。
instanceof
為了避免ClassCastException
異常,Java 提供了instanceof
運(yùn)算符球及。格式是變量名 instanceof 類(lèi)型
氧骤,當(dāng)變量所引用的對(duì)象是后面類(lèi)或子類(lèi)的實(shí)例時(shí),返回true
吃引。
instanceof 只能在具有繼承關(guān)系的變量之間進(jìn)行強(qiáng)轉(zhuǎn)筹陵,否則編譯將會(huì)報(bào)錯(cuò),故當(dāng)變量與類(lèi)之間沒(méi)有關(guān)系時(shí)镊尺,也不會(huì)返回false
朦佩。
例如:
class Fruit {}
class Apple {}
public class Test {
public static void main(String[] args){
Fruit fruit = new Apple();
if(fruit instanceof Apple) {
Apple apple = (Apple)fruit;
}
}
}
從 Java 16 開(kāi)始,instanceof 運(yùn)算符得到了升級(jí)庐氮。見(jiàn)本文最后Java 16 增強(qiáng)的 instanceof语稠。
面向?qū)ο笾攸c(diǎn)
方法重載
方法重載(Overload
)指方法名相同,形參列表不同的方法。Java 通過(guò)參數(shù)類(lèi)型來(lái)判斷該方法是否為重載方法仙畦。修飾符不同或返回值類(lèi)型不同的方法不能稱(chēng)為方法重載输涕!
例如:
public void show() {
System.out.println("哦吼?");
}
public void show(String name) {
System.out.println("商品名:"+ name);
}
public void show(String name, double price) {
System.out.println("商品名:" + name + ", 價(jià)格:" + price);
}
public static void main(String[] args) {
show(); // 哦吼慨畸?
show("手機(jī)"); // 商品名:手機(jī)
show("平板", 3000); // 商品名:平板, 價(jià)格:3000
}
構(gòu)造器重載
構(gòu)造器重載要求形參列表不同莱坎。
例如:
class Dog{
private String name;
private int age;
public Dog(){
// 無(wú)參構(gòu)造
}
public Dog(String name){
this.name = name;
}
public Dog(String name, int age){
this.name = name;
this.age = age;
}
}
這里有個(gè)問(wèn)題,就是當(dāng)需要為某個(gè)成員統(tǒng)一修改時(shí)寸士,可能需要一一修改構(gòu)造器型奥。例如,需要在name
成員前添加Dog:
的前綴碉京,需要一一修改構(gòu)造器厢汹。
這里可以通過(guò)this(參數(shù))
來(lái)調(diào)用對(duì)應(yīng)的構(gòu)造方法。
class Dog{
private String name;
private int age;
public Dog(){
// 無(wú)參構(gòu)造
}
public Dog(String name){
this.name = "Dog: " + name;
}
public Dog(String name, int age){
this(name); // 調(diào)用 `public Dog(String)` 構(gòu)造器
this.age = age;
}
}
方法重寫(xiě)
方法重寫(xiě)(Override
)是指子類(lèi)將父類(lèi)的方法重寫(xiě)谐宙。方法重寫(xiě)要求方法名烫葬、形參列表相同,返回值類(lèi)型凡蜻、聲明拋出的異常相同或更小(即子類(lèi))搭综,訪(fǎng)問(wèn)權(quán)限相同或更大。
重寫(xiě)的方法通常使用@Override
注解來(lái)修飾(避免重寫(xiě)錯(cuò)代碼)划栓。
例如:
class Bird {
public void fly() {
System.out.println("鳥(niǎo)飛咯");
}
}
class Ostrich extends Bird {
@Override
public void fly() {
System.out.println("鴕鳥(niǎo)不會(huì)飛……");
}
}
toString 與 equals 方法
toString
方法是將當(dāng)前對(duì)象以文本的方式來(lái)表示出來(lái)兑巾,Java 默認(rèn)的toString
方法是類(lèi)名@哈希碼
的格式,通常我們重寫(xiě)該方法將其內(nèi)部的成員變量表示出來(lái)忠荞。
equals
方法則是用于比較兩個(gè)對(duì)象是否相同蒋歌,Java 默認(rèn)通過(guò)比較兩個(gè)引用變量是否指向同一個(gè)對(duì)象,通常我們重寫(xiě)該方法使用該類(lèi)的關(guān)鍵屬性來(lái)比較委煤。
class Person {
private String name;
private int age;
// 有參構(gòu)造
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter / Setter 方法
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
// toString 方法
@Override
public String toString() {
return "Person[name=" + name
+ ", age=" + age
+ "]";
}
// equals 方法
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 同一個(gè)對(duì)象
if (obj != null && obj.getClass() == Person.class) {
// obj不為null且obj和當(dāng)前對(duì)象的類(lèi)相同
Person target = (Person) obj;
return this.name.equals(target.getName())
// String類(lèi)型需要使用equals方法比較
&& this.age == target.getAge();
}
return false;
}
}
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person("王強(qiáng)", 18);
Person p2 = new Person("王強(qiáng)", 18);
Person p3 = new Person("張三", 30);
System.out.println(p1); // Person[name=王強(qiáng), age=18]
System.out.println(p2); // Person[name=王強(qiáng), age=18]
System.out.println(p3.toString()); // Person[name=張三, age=30]
if (p1.equals(p2)) // true
System.out.println("p1和p2是同一個(gè)人");
}
}
初始化塊
初始化塊的語(yǔ)法如下:
[修飾符] {
// ...
}
其中修飾符只能出現(xiàn)static
堂油,無(wú)static
修飾的稱(chēng)為實(shí)例初始化塊,有static
修飾的稱(chēng)為類(lèi)初始化塊碧绞。
實(shí)例初始化塊
實(shí)例初始化塊實(shí)際上是「假象」府框,塊中所有代碼在編譯時(shí)將會(huì)被還原到每個(gè)構(gòu)造器的最前面。左圖為編譯前的原始代碼讥邻,右圖為class
文件反編譯的代碼迫靖。
實(shí)例初始化塊的作用是將各個(gè)構(gòu)造器前相同的代碼抽離到實(shí)例初始化塊,從而實(shí)現(xiàn)代碼復(fù)用兴使。
類(lèi)初始化塊
類(lèi)初始化塊負(fù)責(zé)對(duì)類(lèi)進(jìn)行初始化系宜。當(dāng)程序第一次主動(dòng)使用(除了僅使用該類(lèi)聲明變量)該類(lèi)時(shí),系統(tǒng)會(huì)為該類(lèi)分配內(nèi)存空間鲫惶,并調(diào)用類(lèi)初始化塊蜈首。程序運(yùn)行時(shí)实抡,該類(lèi)初始化塊只執(zhí)行一次欠母。
執(zhí)行次數(shù) | 執(zhí)行先后 | 執(zhí)行時(shí)間 | |
---|---|---|---|
類(lèi)初始化塊 | 1 次 | 先 | 第一次主動(dòng)使用該類(lèi) |
實(shí)例初始化塊 | N 次 | 后 | 每次調(diào)用構(gòu)造器 |
抽象類(lèi)
見(jiàn)上面abstract
關(guān)鍵字
接口
接口相當(dāng)于一個(gè)徹底抽象的類(lèi)欢策,體現(xiàn)一種規(guī)范。接口中所有東西都使用public
修飾(通常省略)赏淌。接口支持多繼承踩寇。
接口定義
接口的定義如下:
[修飾符] interface 接口名 [extends 父接口1, 父接口2, ...] {
// 成員變量(常量,自動(dòng)使用`public static final`修飾)
// 抽象方法:Java 8 后支持類(lèi)方法六水、默認(rèn)方法(帶有方法體的抽象方法俺孙,與實(shí)例方法類(lèi)似)。
}
其中掷贾,修飾符只能為public
睛榄。接口名命名規(guī)范基本與類(lèi)名系統(tǒng),通常使用形容詞來(lái)定義想帅。
public interface MyInterface {
// 反編譯后:
// public static final int MAX_WEIGHT = 100;
int MAX_WEIGHT = 100;
void test(); // 抽象方法
// Java 8 后的類(lèi)方法
static void staticMethod() {
// ...
}
// Java 8 后的默認(rèn)方法
default void defaultMethod() {
// ...
}
}
使用接口
使用接口中成員變量场靴、類(lèi)方法時(shí),與調(diào)用類(lèi)成員相似港准,即接口名.成員名
旨剥。
例如:
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(MyInterface.MAX_WEIGHT); // 100
MyInterface.staticMethod();
}
}
實(shí)現(xiàn)接口
子類(lèi)要么重寫(xiě)接口中所有抽象方法,要么定義為抽象類(lèi)浅缸。
實(shí)現(xiàn)接口的格式如下:
[修飾符] class 類(lèi)名 implements 父接口1 [, 父接口2, ...] {
// 5大成員
}
以實(shí)現(xiàn)上方的接口為例:
public class Test implements MyInterface {
@Override
public void test() {
// ...
}
}
private
方法本質(zhì)是實(shí)例方法轨帜。
內(nèi)部類(lèi)
內(nèi)部類(lèi)是在類(lèi)體中定義的類(lèi)。
class 外部類(lèi) {
[修飾符] class 內(nèi)部類(lèi) [extends 父類(lèi)] [implements 接口] {
// ...
}
}
內(nèi)部類(lèi)與外部類(lèi)的區(qū)別如下:
-
內(nèi)部類(lèi)與外部類(lèi)相比可使用
static
衩椒、private
蚌父、protected
修飾符。 - 非靜態(tài)內(nèi)部類(lèi)不能擁有靜態(tài)成員(常量除外)毛萌。
- 內(nèi)部類(lèi)可以直接訪(fǎng)問(wèn)外部類(lèi)私有成員梢什,但靜態(tài)內(nèi)部類(lèi)不能訪(fǎng)問(wèn)外部類(lèi)的非靜態(tài)成員。
內(nèi)部類(lèi)的意義:當(dāng)某個(gè)類(lèi)的實(shí)例必須依附于另一個(gè)類(lèi)存在時(shí)朝聋,可使用內(nèi)部類(lèi)。且內(nèi)部類(lèi)可以提供更好的封裝(可使用private
修飾)言蛇。
內(nèi)部類(lèi)生成的文件名格式為:外部類(lèi)$內(nèi)部類(lèi).class
內(nèi)部類(lèi)區(qū)分同名變量
class Foo {
int length = 20;
class Bar {
int length = 200;
void foo() {
int length = 2000;
System.out.println(length); // 2000
System.out.println(this.length); // 200
System.out.println(Foo.this.length); // 20
}
}
}
使用內(nèi)部類(lèi)
(1)在外部類(lèi)中使用內(nèi)部類(lèi)
基本與使用其他類(lèi)相同劝篷,需要注意的是靜態(tài)成員不能使用非靜態(tài)內(nèi)部類(lèi)創(chuàng)建實(shí)例哈恰。
(2)在外部類(lèi)的外面使用靜態(tài)內(nèi)部類(lèi)
該內(nèi)部類(lèi)不能使用private
修飾
class Foo {
public static class Bar {
public static void test() {
System.out.println("我來(lái)自Foo.Bar.test()");
}
}
}
public class Test {
public static void main(String[] args) {
Foo.Bar.test(); // 我來(lái)自Foo.Bar.test()
}
}
(3)在外部類(lèi)的外面使用非靜態(tài)內(nèi)部類(lèi) (不常見(jiàn))
class Foo {
public class Bar {
public void test() {
System.out.println("非靜態(tài)內(nèi)部類(lèi)");
}
}
}
public class Test{
public static void main(String[] args) {
Foo foo = new Foo();
Foo.Bar fb = foo.new Bar();
fb.test(); // 非靜態(tài)內(nèi)部類(lèi)
}
}
局部?jī)?nèi)部類(lèi)
定義在方法中的內(nèi)部類(lèi),不常用,略缕棵。
匿名內(nèi)部類(lèi)
匿名內(nèi)部類(lèi)指沒(méi)有名字的類(lèi)虱饿,無(wú)法復(fù)用爽冕。
匿名內(nèi)部類(lèi)的語(yǔ)法如下:
new 父類(lèi)構(gòu)造器(參數(shù))|接口() {
// 除了構(gòu)造器,其他都可以定義
// 但一般只實(shí)現(xiàn)抽象方法
}
注意:
- 匿名內(nèi)部類(lèi)必須顯式繼承父類(lèi)娜搂,或?qū)崿F(xiàn)一個(gè)接口。
- 匿名內(nèi)部類(lèi)不能是抽象類(lèi)秘豹,因此必須實(shí)現(xiàn)抽象父類(lèi)或接口中的所有抽象方法携御。
例如:
abstract class Foo {
public abstract void test();
}
public class Bar {
public static void main(String[] args) {
Foo foo = new Foo() {
@Override
public void test() {
System.out.println("匿名內(nèi)部類(lèi)");
}
};
foo.test(); // 匿名內(nèi)部類(lèi)
}
}
枚舉
枚舉的定義格式如下:
[修飾符] enum 枚舉名 {
// 第一行列出所有實(shí)例
// 可以定義 5 大成員
}
修飾符只能為public
。
枚舉類(lèi)與普通類(lèi)的區(qū)別:
-
枚舉默認(rèn)已經(jīng)繼承
Enum
類(lèi)既绕,無(wú)法繼承其他類(lèi)啄刹。 -
枚舉要么是
final
類(lèi),要么是abstract
類(lèi)凄贩。且 Java 會(huì)自動(dòng)判斷該類(lèi)為final
類(lèi)還是abstract
類(lèi)誓军。 - 枚舉要求在開(kāi)頭列出所有實(shí)例
枚舉類(lèi)默認(rèn)擁有以下方法:
-
static Weekday[] values()
: 返回所有枚舉實(shí)例 -
static Weekday valueOf(String)
:根據(jù)枚舉名返回枚舉實(shí)例 -
String name()
:返回枚舉實(shí)例的名稱(chēng) -
int ordinal()
:返回枚舉實(shí)例的序號(hào)
例如:
enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT; // 所有實(shí)例
Weekday() {
System.out.println("我來(lái)自構(gòu)造器");
}
public void foo() {
System.out.println("我來(lái)自枚舉中的foo方法");
}
}
public class EnumTest {
public static void main(String[] args) {
System.out.println(Weekday.WED); // WED
System.out.println(Arrays.toString(Weekday.values())); // [SUN, MON, TUE, WED, THU, FRI, SAT]
Weekday d1 = Weekday.SUN;
System.out.println(d1.ordinal()); // 0
System.out.println(d1.name()); // SUN
d1.foo(); // 我來(lái)自枚舉中的foo方法
Weekday d2 = Weekday.valueOf("TUE");
}
}
枚舉與switch
switch
語(yǔ)句可以與枚舉共同使用:
enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
public void info() {
switch (this) {
case MON:
case TUE:
case WED:
case THU:
case FRI:
System.out.println("上班哦");
break;
case SAT:
case SUN:
System.out.println("放假哦");
break;
}
}
}
public class EnumTest {
public static void main(String[] args) {
Weekday day = Weekday.SUN;
day.info(); // 放假哦
}
}
枚舉類(lèi)與構(gòu)造器
枚舉定義后本質(zhì)為public static final
的常量:
// 編譯前:
public enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}
// 編譯后
public final class Weekday extends Enum {
public static final Weekday SUN = new Weekday();
public static final Weekday MON = new Weekday();
public static final Weekday TUE = new Weekday();
public static final Weekday WED = new Weekday();
public static final Weekday THU = new Weekday();
public static final Weekday FRI = new Weekday();
public static final Weekday SAT = new Weekday();
private Weekday() {}
}
當(dāng)然,我們可以自己手動(dòng)編寫(xiě)構(gòu)造器疲扎,其使用方式與正常類(lèi)相似:
enum Weekday {
SUN(false),
MON(true),
TUE(true),
WED(true),
THU(true),
FRI(true),
SAT(false);
private final boolean isWorkday;
Weekday(boolean isWorkday) { // private 構(gòu)造器
this.isWorkday = isWorkday;
}
}
N More Things
記錄類(lèi)
記錄類(lèi)(Record)是從 Java 16 正式引入的類(lèi)型(JEP 395)昵时。記錄類(lèi)會(huì)自動(dòng)地為其添加有參構(gòu)造、Getter椒丧、toString
壹甥、equals
和hashCode
方法。
在 Java 16 之前壶熏,定義一個(gè)純數(shù)據(jù)類(lèi)可能需要如下代碼:
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y == y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}
而引入Record
類(lèi)以后句柠,該類(lèi)可以簡(jiǎn)化為如下代碼:
record Point(int x, int y) {}
當(dāng)然,Record
類(lèi)能做的事不止這些棒假。具體可以閱讀JEP 395俄占。
形參個(gè)數(shù)可變方法
在 Java 中,方法可以有可變長(zhǎng)參數(shù)淆衷。該參數(shù)位于某一方法的最后一位缸榄。
例如,如下定義了一個(gè)形參個(gè)數(shù)可變方法:
public void test(String... sites){
// ...
}
該方法中祝拯,定義了一個(gè)String
類(lèi)型的可變長(zhǎng)參數(shù)甚带∷希可變長(zhǎng)參數(shù)本質(zhì)上是一個(gè)數(shù)組。可變長(zhǎng)參數(shù)只能位于形參列表的最后一位鹰贵!
該方法可以通過(guò)如下方式調(diào)用:
test(new String[]{"GitHub", "Google", "Bing"}) // 方法 1
test("GitHub", "Google", "Bing") // 方法2
使用Type...
和Type[]
的區(qū)別:前者可以通過(guò)兩種方法調(diào)用晴氨,后者只能由方法 1 調(diào)用
Java 8 函數(shù)式編程
見(jiàn)Java 函數(shù)式編程知識(shí)整理。
Java 9 接口的 private 方法
Java 8 中default
方法本質(zhì)是實(shí)例方法碉输。在 Java 9 之前定義默認(rèn)方法時(shí)籽前,如果某些方法有公共部分,需要多次編寫(xiě)相同的代碼敷钾。Java 9 以后枝哄,可以將重復(fù)的代碼抽離出來(lái),獨(dú)立成private
方法阻荒,同時(shí)實(shí)現(xiàn)隱藏挠锥。
例如:
public interface PrivateMethod {
default void foo() {
System.out.println("foo");
common();
}
default void bar() {
System.out.println("bar");
common();
}
// 工具方法只被本類(lèi)默認(rèn)方法使用,并不希望暴露出去
private void common() {
for(var i = 0; i < 10; i++) {
System.out.println("公共部分");
}
}
}
Java 16 增強(qiáng)的 instanceof
在 JEP 394中侨赡,instanceof
運(yùn)算符得到了升級(jí)蓖租。
從 Java 16 開(kāi)始,可以通過(guò)變量名 instanceof 類(lèi)名 新變量名
判斷該變量是否屬于某個(gè)類(lèi)的實(shí)例羊壹。如果屬于蓖宦,Java 將自動(dòng)將其強(qiáng)制轉(zhuǎn)換,并賦值到新的變量中油猫。
Object obj = "test";
// Java 16 之前
if (obj instanceof String) {
String s = (String) obj;
// ...
}
// Java 16 及以后
if (obj instanceof String s) {
// ...
}
當(dāng)然稠茂,Java 16 增強(qiáng)的 instanceof 功能遠(yuǎn)遠(yuǎn)不止這些。具體閱讀JEP 394眨攘。