Java 面向?qū)ο笾R(shí)分享

本文原文

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 圖為:

16317985375578.jpg

初識(shí)面向?qū)ο?/h2>

定義類(lèi)

在 Java 中薛躬,定義類(lèi)的統(tǒng)一格式如下:

[修飾符] class 類(lèi)名 {
    // ...
}

其中,修飾符可省略)中的訪(fǎng)問(wèn)權(quán)限只能為public公開(kāi)類(lèi))呆细,其他修飾符只能為finalabstract(抽象類(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|abstractstatic字逗。

返回值類(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í)確定
16323961305864.jpg

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文件反編譯的代碼迫靖。

16322017907275.jpg

實(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壹甥、equalshashCode方法。

在 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眨攘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末主慰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鲫售,更是在濱河造成了極大的恐慌共螺,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件情竹,死亡現(xiàn)場(chǎng)離奇詭異藐不,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)秦效,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)雏蛮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人阱州,你說(shuō)我怎么就攤上這事挑秉。” “怎么了苔货?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵犀概,是天一觀的道長(zhǎng)立哑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)姻灶,這世上最難降的妖魔是什么铛绰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮产喉,結(jié)果婚禮上捂掰,老公的妹妹穿的比我還像新娘。我一直安慰自己曾沈,他們只是感情好这嚣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著晦譬,像睡著了一般疤苹。 火紅的嫁衣襯著肌膚如雪互广。 梳的紋絲不亂的頭發(fā)上敛腌,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音惫皱,去河邊找鬼像樊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛旅敷,可吹牛的內(nèi)容都是我干的生棍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼媳谁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼涂滴!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起晴音,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤柔纵,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后锤躁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體搁料,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年系羞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了郭计。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡椒振,死狀恐怖昭伸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情澎迎,我是刑警寧澤庐杨,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布宋下,位于F島的核電站,受9級(jí)特大地震影響辑莫,放射性物質(zhì)發(fā)生泄漏学歧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一各吨、第九天 我趴在偏房一處隱蔽的房頂上張望枝笨。 院中可真熱鬧,春花似錦揭蜒、人聲如沸横浑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)徙融。三九已至,卻和暖如春瑰谜,著一層夾襖步出監(jiān)牢的瞬間欺冀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工萨脑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留隐轩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓渤早,卻偏偏與公主長(zhǎng)得像职车,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹊杖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355