【Java 面向對象】基礎、Java程序的內存劃分、嵌套類(內部類、靜態嵌套類)、局部類、接口的升級問題(默認方法、靜態方法)、instanceof

Java筆記目錄可以點這裏:Java 強化筆記

畢竟是進階學習,挑了一些個人認爲的易錯點難點,還有些太基礎的我覺得是在沒有必要記錄了。。。
在這裏插入圖片描述

對象的內存

Java 中所有對象都是 new 出來的,所有對象的內存都是在堆空間,所有保存對象的變量都是引用類型

public static void main(String[] args) {
	Dog dog = new Dog();
	dog.age = 20;
	dog.weight = 5.6;
	dog.run();
	dog.eat("appel");
}

在這裏插入圖片描述

Java 運行時環境有個垃圾回收器(garbage collector,簡稱GC),會自動回收不再使用的內存

  • 當一個對象沒有任何引用指向時,會被 GC 回收掉內存

複雜對象的內存

public class Dog {
	public int price;
}
public class Person {
	public int age;
	public Dog dog;
}
public static void main(String[] args) {
	Dog dog = new Dog();
	dog.price = 100;
	
	Person person = new Person();
	person.age = 20;
	person.dog = dog;
}

在這裏插入圖片描述

對象數組的內存

public static void main(String[] args) {
	Dog[] dogs = new Dog[7];
	for (int i = 0; i < dogs.length; i++) {
		dogs[i] = new Dog();
	}
	dogs[6] = null;
}

在這裏插入圖片描述

思考:方法存儲在哪裏?

public class Dog {
	public int price;
	
	public void run() {
		System.out.println(price + "_run");
	}
	public void eat() {
		System.out.println(price + "_eat");
	}
	
	public static void main(String [] args) {
		Dog dog1 = new Dog();
		dog1.price = 100;
		dog1.run();
		dog1.eat();
		
		Dog dog2 = new Dog();
		dog2.price = 200;
		dog2.run();
		dog2.eat();
	}
}

在這裏插入圖片描述

Java程序的內存劃分

Java 虛擬機在執行 Java 程序時會將內存劃分爲若干個不同的數據區域,主要有:

  • PC 寄存器(Program Counter Register):存儲 Java 虛擬機正在執行的字節碼指令的地址
  • Java 虛擬機棧(Java Virtual Machine Stack):存儲棧幀
  • 堆(Heap):存儲 GC 所管理的各種對象
  • 方法區(Method Area):存儲每一個類的結構信息
    (比如字段和方法信息、構造方法和普通方法的字節碼等)
  • 本地方法棧(Native Method Stack):用來支持 native 方法的調用
    (比如用 C 語言編寫的方法)

this、super

this 是一個指向當前對象的引用,常見用途是:

  • 訪問當前類中定義的成員變量
  • 調用當前類中定義的方法(包括構造方法)

只能在構造方法中使用 this 調用其他構造方法;

如果在構造方法中調用了其他構造方法:

  • 構造方法調用語句必須是構造方法中的第一條語句

this本質是一個隱藏的、位置最靠前的方法參數

public class Dog {
	public String name;
	public int age;
	public int price;
	public Dog(String name, int age, int price) {
		this.name = name;
		this.age = age;
		this.price = price;
	}
	// 只能在構造方法中使用 this 調用其他構造方法
	public Dog(String name) {
		// 如果在構造方法中調用了其他構造方法
		// 構造方法調用語句必須是構造方法中的第一條語句
		// int a = 1; //error
		this(name, 0, 0);
	}
}

super 的常見用途是:

  • 訪問父類中定義的成員變量
  • 調用父類中定義的方法(包括構造方法)

子類的構造方法必須先調用父類的構造方法,再執行後面的代碼;

如果子類的構造方法沒有顯式調用父類的構造方法:

  • 編譯器會自動調用父類無參的構造方法(若此時父類沒有無參的構造方法,編譯器將報錯)
public class Person {
	public int age;
	public Person(int age) {
		this.age = age;
	}
}
public class Student extends Person {
	public int no;
	public Student(int no) {
		super(0); // 不寫會報錯
		this.no = no;
	}
}

註解(Annotation)

3 個常見的註解:

  • @Override::告訴編譯器這是一個重寫後的方法
  • @SuppressWarnings({"rawtypes", "ununsed"}):讓編譯器不生成某個類別警告信息
  • @Deprecated::表示這個內容已經過期,不推薦使用

訪問控制(Access Control)

Java 中有 4 個級別的訪問權限,從高到低如下所示:

  • public:在任何地方都是可見的
  • protected:僅在自己的包中、自己的子類中可見
  • 無修飾符(package-private):僅在自己的包中可見
  • private:僅在自己的類中可見

在這裏插入圖片描述
使用注意:

  • 上述 4 個訪問權限都可以修飾類的成員,比如成員變量、方法、嵌套類(Nested Class)等
  • 只有 public、無修飾符(package-private)可以修飾頂級類(Top-level Class)
  • 上述 4 個訪問權限不可以修飾局部類(Local Class)、局部變量
  • 一個 Java 源文件中可以定義多個頂級類,public 頂級類的名字必須和文件名一樣

toString方法(類名 + @ + 哈希值的16進制)

當打印一個對象時,會自動調用對象的 toString 方法,並將返回的字符串打印出來

toString 方法來源於基類 java.lang.Object,默認實現如下所示:

默認打印:類名 + @ + 哈希值的16進制

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

static、靜態導入

static 常用來修飾類的成員:成員變量、方法、嵌套類

成員變量:(類變量,實例變量)

  • static 修飾:類變量,靜態變量,靜態字段
    在程序運行過程中只佔用一份固定的內存(存儲在方法區
    可以通過實例、類訪問

  • 沒有被 static 修飾:實例變量
    在每個實例內部都有一份內存
    只能通過實例訪問,不可以通過類訪問

  • 不推薦使用實例訪問類變量、類方法

方法:(類方法、實例方法)

  • static 修飾:類方法、靜態方法
    可以通過實例、類訪問
    內部不可以使用 this
    可以直接訪問類變量、類方法
    不可以直接訪問實例變量、實例方法
  • 沒有被 static 修飾:實例方法
    只能通過實例訪問,不可以通過類訪問
    內部可以使用 this
    可以直接訪問實例變量、實例方法
    可以直接訪問類變量、類方法

在同一個類中:不能有同名的實例變量和類變量不能有相同簽名的實例方法和類方法

static 有個用法:靜態導入

  • 使用了靜態導入後,就可以省略類名來訪問靜態成員(成員變量、方法、嵌套類)

靜態導入的經典使用場景:

import static java.lang.Math.PI;

public class Main {
	public static void main(String[] args) {
		System.out.println(2 * PI * 10);
		System.out.println(2 * PI * 20);
	}
}

正確使用靜態導入,可以消除一些重複的類名,提高代碼可讀性;
過度使用靜態導入,會讓讀者分不清靜態成員是在哪個類中定義的;

建議:謹慎使用靜態導入

成員變量的初始化

編譯器會自動爲未初始化的成員變量設置初始值;

如何手動給實例變量提供初始值?

  • 在聲明中
  • 在構造方法中
  • 在初始化塊中
    編譯器會將初始化塊複製到每個構造方法的頭部(每創建一個實例對象,就會執行一次初始化塊)

如何手動給類變量提供初始值?

  • 在聲明中
  • 在靜態初始化塊中
    當一個類被初始化的時候執行靜態初始化塊;
    當一個類第一次被主動使用時,JVM 會對類進行初始化;

初始化塊、靜態初始化塊

public class Person {

	static { // 靜態初始化塊
		System.out.println("static block");
	}
	{ // 初始化塊
		System.out.println("block");
	}
	
	public Person() {}
	public Person(int age) {}
	
	public static void main(String[] args) {
		new Person();
		// static block
		// block
		new Person(20);
		// block
	}
}

一個更復雜的示例:

public class Person {
	static {
		System.out.println("Person static block");
	}
	{
		System.out.println("Person block");
	}
	public Person() {
		System.out.println("Person constructor");
	}
public class Student extends Person {
	static {
		System.out.println("Student static block");
	}
	{
		System.out.println("Student block");
	}
	public Student() {
		System.out.println("Student constructor");
	}
}

執行順序:父類靜態塊 -> 子類靜態塊 -> 父類代碼塊 -> 父類構造器 -> 子類代碼塊 -> 子類構造器

public static void main(String[] args) {
	new Student();
	// Person static block
	// Student static block
	// Person block
	// Person constructor
	// Student block
	// Student constructor
}

單例模式(Singleton Pattern)

如果一個類設計成單例模式,那麼在程序運行過程中,這個類只能創建一個實例。

餓漢式單例模式:像餓漢一樣,上來就直接創建了唯一的那個實例。(線程安全

/*
 * 餓漢式單例模式
 */
public class Rocket {
	private static Rocket instance = new Rocket();
	private Rocket(){}
	public static Rocket getInstance() {
		return instance;
	}
}

懶漢式單例模式:像懶漢一樣,只有用到的時候採取創建實例。(線程不安全

/*
 * 懶漢式單例模式
 */
public class Rocket {
	private static Rocket instance = null;
	private Rocket(){}
	public static Rocket getInstance() {
		if (instance == null) {
			instance =  new Rocket();
		} 
		return instance;
	}
}

final、常量(Constant)

final 修飾的類:不能被子類化,不能被繼承

final 修飾的方法:不能被重寫

final 修飾的變量:只能進行1次賦值

常量的寫法:

  • public static final double PI = 3.14159265358979323846;
  • private static final int NOT_FOUND = - 1;

如果將基本類型字符串定義爲常量,並且在編譯時就能確定值

  • 編譯器會使用常量值替代各處的常量名(類似於 C 語言的宏替換)
  • 稱爲編譯時常量( compile-time constant)

例如下面的情況,編譯時會被替換

public class Main {
	static final int A = 123456;
	static final String B = "HELLO";
	public static void main(String[] args) {
		System.out.println(A); // 編譯時直接被替換爲下面
		// System.out.println(123456);
		
		System.out.println(B); // 編譯時直接被替換爲下面
		// System.out.println("HELLO");
	}
}

下面這種情況,編譯時無法確定值,不會被替換

public class Main {
	static int NUMBER = getNum();
	static int getNum() {
		int a = 10;
		int b = 20;
		return a + b * 2 + 6;
	}
	
	public static void main(String[] args) {
		System.out.println(NUMBER); // 不會被替換
	}
}

嵌套類(Nested Class)

  • 嵌套類:定義在另一個類中的類;
  • 在嵌套類外層的類,稱爲:外部類(Outer Class)
  • 最外層的外部類,稱爲:頂級類(Top-level Class)
public class OuterClass { // 頂級類
	// 靜態嵌套類
	static class StaticNestedClass {
		
	}
	// 非靜態嵌套類(內部類)
	class InnerClass {
		
	}
}

內部類(Inner Class)

內部類:沒有被 static 修飾的嵌套類,非靜態嵌套類

跟實例變量、實例方法一樣,內部類與外部類的實例相關聯:

  • 必須先創建外部類實例,然後再用外部類實例創建內部類實例
  • 內部類不能定義除編譯時常量以外的任何 static 成員

內部類與外部類

  • 內部類可以直接訪問外部類中的所有成員(即使是 private
  • 外部類可以直接訪問內部類實例的成員變量、方法(即使是 private

內部類示例:先有公司 company 纔能有員工 employee,將 employee 設置爲 company 的內部類,而 company 可以訪問 employee 的實例的成員變量、方法(包括private),employee 可以訪問 company 的所有成員(包括 private)。

public class Company {
	private String name;
	public Company(String name) {
		this.name = name;
	}
	public void fire(Employee e) {
		// 外部類可以直接訪問內部類實例的成員變量(包括 private)
		System.out.println(name + " fire " + e.no);
	}
	
	
	public class Employee {
		private int no;
		public Employee(int no) {
			this.no = no;
		}
		public void show() {
			// 內部類可以直接訪問外部類中的所有成員(包括 private)
			System.out.println(name + " : " + no);
		}
	}
	
	public static void main(String[] args) {
		Company c = new Company("Google");
		Employee e = c.new Employee(17210224);
		e.show();
		c.fire(e);
	}
}

內部類的細節:如果有同名變量,默認訪問內部的,訪問外部的需要特別指出。

public class OuterClass {
	private int x = 1; // 外部類變量x
	public class InnerClass {
		private int x = 2; // 內部類變量x
		public void show() {
			// 默認訪問內部
			System.out.println(x); // 2
			System.out.println(this.x); // 2
			// 訪問外部類的同名變量需要這麼寫
			System.out.println(OuterClass.this.x); // 1
		}
	}

	public static void main(String[] args) {
		new OuterClass().new InnerClass().show();
	}
}

內部類內存分佈

public class Person {
	private int age;
	
	public class Hand {
		private int weight;
	}
	
	public static void main(String[] args) {
		// 必須先有Person對象才能創建Hand對象
		Person p1 = new Person();
		Hand h1 = p1.new Hand();
		
		Person p2 = new Person();
		Hand h2 = p2.new Hand();
	}
}

Hand 類被釋放前,Person 類不會被釋放(被 Hand 指向着)
在這裏插入圖片描述

靜態嵌套類(Static Nested Class)

靜態嵌套類:被 static 修飾的嵌套類

靜態嵌套類在行爲上就是一個頂級類,只是定義的代碼寫在了另一個類中;

對比一般的頂級類,靜態嵌套類多了一些特殊權限

  • 可以直接訪問外部類中的成員(即使被聲明爲 private
public class Person {
	private int age;
	private static int count = 1;
	private static void run() {
		System.out.println("Person - run");
	}
	
	public static class Car { // 靜態嵌套類
		public void test() {
			Person person = new Person();
			// 靜態嵌套類可以直接訪問外部類中的成員(包括 private)
			System.out.println(person.age); // 0
			Person.count = 1;
			Person.run(); // Person - run
			
			System.out.println(count); // 1
			run(); // Person - run
		}
	}
}
public static void main(String[] args) {
	Person p = new Person();
	// 靜態嵌套類的使用
	Person.Car c = new Person.Car(); 
	// 如果之前 import Person.Car; 可以直接使用;
	// Car c = new Car();
	c.test();
}

什麼情況使用嵌套類?

如果類 A 只用在類 C 內部,可以考慮將類 A 嵌套到類 C 中;

  • 封裝性更好
  • 程序包更加簡化
  • 增強可讀性、維護性

如果類 A 需要經常訪問類 C 的非公共成員,可以考慮將類 A嵌套到類 C 中;

  • 另外也可以根據需要將類 A 隱藏起來,不對外暴露

如果需要經常訪問非公共的實例成員,設計成內部類(非靜態嵌套類),否則設計成靜態嵌套類;

  • 如果必須先有 C 實例,才能創建 A 實例,那麼可以將 A 設計爲 C 的內部類

局部類(Local Class)

局部類:定義在代碼塊中的類(可以定義在方法中、for 循環中、if 語句中等)

局部類不能定義除編譯時常量以外的任何 static 成員;

局部類只能訪問 final 或者 有效final 的局部變量;

  • 從 Java 8 開始,如果局部變量沒有被第二次賦值,就認定爲是有效final

局部類可以直接訪問外部類中的所有成員(即使被聲明爲 private

  • 局部類只有定義在實例相關的代碼塊中,才能直接訪問外部類中的實例成員(實例變量、實例方法)

局部類示例:

public class TestLocalClass {
	private int a = 1;
	private static int b = 2;
	private static void test1() {}
	private void test2() {}
	
	public void test3() {
		int c = 2;
		
		class LocalClass {
			static final int d = 4;
			void test4() {
				System.out.println(a + b + c  + d);
				test1();
				test2();
			}
		}
		new LocalClass().test4();
	}
	
}

抽象類(Abstract Class)與接口(Interface)

抽象類

抽象方法:被 abstract 修飾的實例方法

  • 只有方法聲明,沒有方法實現(參數列表後面沒有大括號,而是分號)
  • 不能是 private 權限(因爲定義抽象方法的目的讓子類去實現)
  • 只能定義在抽象類、接口中

抽象類:被 abstract 修飾的類

  • 可以定義抽象方法
  • 不能實例化,但可以自定義構造方法
  • 子類必須實現抽象父類中的所有抽象方法(除非子類也是一個抽象類)
  • 可以像非抽象類一樣定義成員變量、常量、嵌套類型、初始化塊、非抽象方法等
    也就說,抽象類也可以完全不定義抽象方法

常見使用場景:

  • 抽取子類的公共實現到抽象父類中,要求子類必須要單獨實現的定義成抽象方法

實例:

public abstract class Shape {
	protected double area;
	protected double girth;
	public double getArea() {
		return area;
	}
	public double getGirth() {
		return girth;
	}
	public void show() {
		calculate();
		System.out.println(area + "_" + girth);
	}
	protected abstract void calculate();
}
public class Rectangle extends Shape {
	private double width;
	private double height;
	public Rectangle(double width, double height) {
		super();
		this.width = width;
		this.height = height;
	}
	@Override
	protected void calculate() {
		area = width * height;
		girth = (width + height) * 2;
	}
}
public class Circle extends Shape {
	private double radius;
	public Circle(double radius) {
		this.radius = radius;
	}
	@Override
	protected void calculate() {
		double half = Math.PI * radius;
		area = half * radius;
		girth = half * 2;
	}
}
public static void main(String[] args) {
	Rectangle rectangle = new Rectangle(10, 20);
	rectangle.show();
	
	Circle circle = new Circle(30);
	circle.show();
}

接口(Interface)

API(Application Programming Interface)

  • 應用編程接口,提供給開發者調用的一組功能(無須提供源碼)

Java 中的接口

  • 一系列方法聲明的集合
  • 用來定義規範、標準

接口中可以定義的內容

  • 抽象方法(可以省略 abstract)
  • 常量(可以省略 static、final)
  • 嵌套類型
  • 從 Java 8 開始可以定義:默認方法(default)靜態方法
    上述可以定義的內容都是隱式 public 的,因此可以省略 public 關鍵字
  • 從 Java 9 開始可以定義:private 方法
  • 不能自定義構造方法、不能定義(靜態)初始化塊、不能實例化

接口的細節
一個類可以通過 implements 關鍵字實現一個或多個接口

  • 實現接口的類必須實現接口中定義的所有抽象方法,除非它是個抽象類
  • 如果一個類實現的多個接口中有相同的抽象方法,只需要實現此方法一次
  • extends 和 implements 可以一起使用,implements 必須寫在 extends 的後面
  • 當父類、接口中的方法簽名一樣時,那麼返回值類型也必須一樣

一個接口可以通過 extends 關鍵字繼承一個或者多個接口

  • 當多個父接口中的方法簽名一樣時,那麼返回值類型也必須一樣

抽象與接口的對比(如何選擇)

抽象類和接口的用途還是有點類似,該如何選擇?

何時選擇抽象類

  • 緊密相關的類之間共享代碼
  • 需要除 public 之外的訪問權限
  • 需要定義實例變量、非 final 的靜態變量

何時選擇接口

  • 不相關的類實現相同的方法
  • 只是定義行爲,不關心具體是誰實現了行爲
  • 想實現類型的多重繼承

接口的升級問題(默認方法、靜態方法)

如果接口需要升級,比如增加新的抽象方法:會導致大幅的代碼改動,以前實現接口的類都得改動

若想在不改動以前實現類的前提下進行接口升級,從 Java 8 開始,有 2 種方案:

  • 默認方法(Default Method)
  • 靜態方法(Static Method)

默認方法(Default Method)

  • default 修飾默認方法
  • 默認方法只能是實例方法

默認方法的使用
當一個類實現的接口中有默認方法時,這個類可以:

  • 啥也不幹,沿用接口的默認實現
  • 重新定義默認方法,覆蓋默認方法的實現
  • 重新聲明默認方法,將默認方法聲明爲抽象方法(此類必須是抽象類)

當一個接口繼承的父接口中有默認方法時,這個接口可以:

  • 啥也不幹,沿用接口的默認實現
  • 重新定義默認方法,覆蓋默認方法的實現
  • 重新聲明默認方法,將默認方法聲明爲抽象方法

簡單示例:Eatable 中有默認方法,Dog啥也不幹,Cat 覆蓋默認方法。

public interface Eatable {
	// 默認方法
	default void eat(String name) {
		System.out.println("Eatable - eat - " + name);
	}
}
// Eatable接口中新增了方法, 但是沒有影響到Dog類
public class Dog implements Eatable {}
// Eatable接口中新增了方法, Cat類中可以覆蓋
public class Cat implements Eatable {
	@Override
	public void eat(String name) {
		Eatable.super.eat(name);
		System.out.println("Cat - eat - " + name);
	}
}
	public static void main(String[] args) {
		Dog dog = new Dog();
		dog.eat("bone"); 
		// Eatable - eat - bone
		
		Cat cat = new Cat();
		cat.eat("fish");
		// Eatable - eat - fish
		// Cat - eat - fish
	}

如果父類定義的非抽象方法與接口的默認方法相同時,最終將調用父類的方法(就近原則):

public class Animal {
	public void run() {
		System.out.println("Animal - run");
	}
}
public interface Runnable {
	default void run() {
		System.out.println("Runnable - run");
	}
}
public class Dog extends Animal implements Runnable {}
public static void main(String[] args) {
	Dog dog = new Dog();
	dog.run(); // 繼承的父類、實現的接口中都有 run() 方法, 默認調用父類的
	// Animal - run
}

如果父類定義的抽象方法與接口的默認方法相同時,要求子類實現此抽象方法

  • 可以通過 super 關鍵字調用接口的默認方法
public interface Runnable {
	default void run() {
		System.out.println("Runnable - run");
	}
}
public abstract class Animal {
	public void run() {}
}
public class Dog extends Animal implements Runnable {
	@Override
	public void run() { // 父類的抽象方法run方法與接口中的run方法相同, 要求實現父類的抽象方法
		Runnable.super.run(); // 可以通過super調用接口的默認方法
		System.out.println("Dog - run");
		// Runnable - run
		// Dog - run
	}
}

如果(父)接口定義的默認方法與其他(父)接口定義的方法相同時,要求子類型實現此默認方法:
例:RunnableWalkable 兩個父接口中定義的默認方法都是 run()Testable 繼承了兩個父類,則要求實現默認方法 run()Dog 類同理。

public interface Runnable {
	default void run() {
		System.out.println("Runnable - run");
	}
}
public interface Walkable {
	default void run() {
		System.out.println("Walkable - run");
	}
}
// Testable 父接口繼承了 Runnable 父接口和 Walkable 父接口
// 他們都有默認方法 run, 要求 Testable 接口實現該默認方法
public interface Testable extends Runnable, Walkable {
	@Override
	default void run() {
		Runnable.super.run();
		Walkable.super.run();
		System.out.println("Testable - run");
	}
}
// Dog 類實現了 Runnable、Walkable 兩個接口
// 他們都有默認方法 run, 要求 Dog 類實現該默認方法
public class Dog implements Runnable, Walkable {
	@Override
	public void run() {
		Runnable.super.run();
		Walkable.super.run();
		System.out.println("Dog - run");
	}
}
public static void main(String[] args) {
	Dog dog = new Dog();
	dog.run();
	// Runnable - run
	// Walkable - run
	// Dog - run
}

再看一個例子:

public interface Animal {
	default String myself() {
		return "I am an animal.";
	}
}
public interface Fire extends Animal {}
public interface Fly extends Animal {
	@Override
	default String myself() {
		return "I am able to fly.";
	}
}
public class Dragon implements Fly, Fire {}
public static void main(String[] args) {
	Dragon dragon = new Dragon();
	System.out.println(dragon.myself());
	// I am able to fly.
}

靜態方法(Static Method)

  • 接口中定義的靜態方法只能通過接口名調用,不能被繼承;
public interface Eatable {
	static void eat(String name) {
		System.out.println("Eatable - eat - " + name);
	}
}
public interface Sleepable {
	static void eat(String name) {
		System.out.println("Sleepable - eat - " + name);
	}
}
public interface Dog extends Sleepable, Eatable {
	static void eat(String name) {
		System.out.println("Dog - eat - " + name);
	}
}
public static void main(String[] args) {
	Dog.eat("1");
	Eatable.eat("1");
	Sleepable.eat("3");
	// Dog - eat - 1
	// Eatable - eat - 1
	// Sleepable - eat - 3
}

多態(Polymorphism)

什麼是多態?

  • 具有多種形態
  • 同一操作作用於不同的對象,產生不同的執行結果

多態的體現:

  • 父類(接口)類型指向子類對象
  • 調用子類重寫的方法

JVM 會根據引用變量指向的具體對象來調用對應的方法:

  • 這個行爲叫做:虛方法調用(virtual method invocation)
  • 類似於 C++ 中的虛函數調用

多態示例:

public class Animal {
	public void speak() {
		System.out.println("Animal - speak");
	}
}
public class Dog extends Animal {
	@Override
	public void speak() {
		System.out.println("Dog - wangwang");
	}
}
public class Cat extends Animal {
	@Override
	public void speak() {
		System.out.println("Cat - miaomiao");
	}
}
public static void main(String[] args) {
	speak(new Dog()); // Dog - wangwang
	speak(new Cat()); // Cat - miaomiao
}
// 多態的體現: 父類(接口)類型指向子類對象, 調用子類重寫的方法
static void speak(Animal animal) {
	animal.speak();
}

類方法調用的細節

調用類方法只看左邊聲明的類,與實例化的類無關

public class Animal {
	public static void run() {
		System.out.println("Animal - run");
	}
}
public class Dog extends Animal{
	public static void run() {
		System.out.println("Dog - run");
	}
}
public static void main(String[] args) {
	Dog.run(); // Dog - run
	Animal.run(); // Animal - run
	
	Dog dog1 = new Dog();
	// 調用類方法只看左邊聲明的類,與實例化的類無關
	dog1.run(); // Dog - run
	
	Animal dog2 = new Dog();
	// 調用類方法只看左邊聲明的類,與實例化的類無關
	dog2.run(); // Animal - run
}

成員變量訪問的細節

public class Person {
	public int age = 1;
	public int getPAge(){
		return age;
	}
}
public class Student extends Person {
	public int age = 2;
	public int getSAge() {
		return age;
	}
}
public static void main(String[] args) {
	Student stu1 = new Student();
	System.out.println(stu1.age); // 2
	System.out.println(stu1.getPAge()); // 1
	System.out.println(stu1.getSAge()); // 2
	
	Person stu2 = new Student();
	System.out.println(stu2.age); // 1
	System.out.println(stu2.getPAge()); // 1
}

instanceof

  • 可以通過 instanceof 判斷某個類型是否屬於某種類型(及其子類)
public class Animal {}
public interface Runnable {}
public class Dog extends Animal implements Runnable {}
public static void main(String[] args) {
	Object dog = new Dog();
	System.out.println(dog instanceof Dog); // true
	System.out.println(dog instanceof Animal); // true
	System.out.println(dog instanceof Runnable); // true
	System.out.println(dog instanceof String); // false
}

instanceof 使用場景:

public class Animal {}
public class Cat extends Animal {
	public void miao() {
		System.out.println("Cat - miao");
	}
}

public class Dog extends Animal{
	public void wang() {
		System.out.println("Dog - wang");
	}
}
public static void main(String[] args) {
	speak(new Dog()); // Dog - wang
	speak(new Cat()); // Cat - miao
}

static void speak(Animal animal) {
	if (animal instanceof Dog) {
		((Dog) animal).wang();
	} else if (animal instanceof Cat) {
		((Cat) animal).miao();
	}
}

對象數組的注意點

public static void main(String[] args) {
	Object obj1 = 11;
	Integer obj2 = (Integer) obj1; // 可以轉換成功
	System.out.println(obj2);
	// 下面一行等價於這麼寫: Object[] objs1 = new Object[] {11, 22, 33};
	Object[] objs1 = { 11, 22, 33 };
	//  java.lang.ClassCastException: 
	// java.lang.Integer cannot be cast to [Ljava.lang.Integer
	Integer[] objs2 = (Integer[]) obj1; // 轉換失敗, 拋異常
	System.out.println(objs2);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章