面向對象
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
}
}
如果(父)接口定義的默認方法與其他(父)接口定義的方法相同時,要求子類型實現此默認方法:
例:Runnable
和 Walkable
兩個父接口中定義的默認方法都是 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);
}