面向物件(下)
目錄
1. 繼承
1.1 概述
多個類中存在相同屬性和行為時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為,只要繼承那個類即可。
單獨的這個類稱為父類,基類或者超類;這多個類可以稱為子類或者派生類。 有了繼承以後,我們定義一個類的時候,可以在一個已經存在的類的基礎上,還可以定義自己的新成員。
通過extends關鍵字可以實現類與類的繼承
格式:class 子類名 extends 父類名 {}
案例1:用繼承改進學生老師案例
//多個類同屬性同行為的一般寫法 //學生類 class Student{ public void eat(){ System.out.println("吃飯"); } public void sleep(){ System.out.println("睡覺"); } } //老師類 class Teacher{ public void eat(){ System.out.println("吃飯"); } public void sleep(){ System.out.println("睡覺"); } } //測試類 class ExtendsDemo{ public static void main(String[] args){ //建立學生類物件 Student s = new Student(); //通過物件呼叫學生類中的方法 s.eat(); s.sleep(); System.out.println("-----"); //建立老師類物件 Teacher t = new Teacher(); t.eat(); t.sleep(); } } //用繼承改進 //父類 class Person{ public void eat(){ System.out.println("吃飯"); } public void sleep(){ System.out.println("睡覺"); } } //子類 class Student extends Person{} class Teacher extends Person{} //測試類 class ExtendsDemo{ public static void main(String[] args){ //建立學生類物件 Student s = new Student(); //通過物件呼叫學生類中的方法 s.eat(); s.sleep(); System.out.println("-----"); //建立老師類物件 Teacher t = new Teacher(); t.eat(); t.sleep(); } }
1.2 繼承的優缺點
1.2.1 優點
- 提高了程式碼的複用性——多個類相同的成員可以放到同一個類中
- 提高了程式碼的維護性——如果功能的程式碼需要修改,修改一處即可
- 讓類與類之間產生了關係,是多型的前提。
1.2.2 缺點
類與類之間產生了關係,這也是繼承的一個弊端:類的耦合性很強
開發原則:低耦合,高內聚
解釋:耦合——類與類之間的關係;內聚——自己完成某件事情的能力
1.3 Java中繼承的特點
1. Java只支援單繼承,不支援多繼承
即,一個類只能繼承一個父類,不能有多個父類。有的語言是可以多繼承的,格式為:extends 父類1,父類2,……
2. Java支援多層繼承(繼承體系)
//格式:
class A{}
class B extends A{}
class C extends B{}
//在C類中建立物件,可以通過物件直接呼叫B類和A類中的方法
1.4 Java中繼承的注意事項
- 子類只能繼承父類所有非私有的成員(成員方法和成員變數)——也體現了另一個弊端:打破了封裝性
- 子類不能繼承父類的構造方法,但是可以通過super(後面講)關鍵字去訪問父類構造方法。
- 不要為了部分功能而去繼承 ,繼承中類之間體現的是:”is a”的關係,即子類是父類的一種
1.5 繼承中類的各組成部分關係
1.5.1 繼承中成員變數之間的關係
- 子類中的成員變數的名稱和父類中成員變數不一樣
- 子類中的成員變數的名稱和父類中成員變數一樣時,其值採用就近原則
名稱一樣時,在子類方法中訪問一個變數的查詢順序:
- 首先在子類方法的區域性範圍找,有就使用;
- 然後在子類的成員範圍找 ,有就使用;
- 最後在父類成員範圍找(不能訪問到父類區域性範圍,因為方法不呼叫不執行) ,有就使用;
- 如果還是沒有就報錯
super關鍵字和this關鍵字的區別
案例2:this和super的應用和輸出區別
//this和super的區別
//父類
class Father{
int num = 10;
}
//子類
class Son extends Father{
int num = 20;
//show方法
public void show(){
int num = 30;
System.out.println(num);//輸出30
System.out.println(this.num);//輸出20
System.out.println(super.num);//輸出10
}
}
class ExtendsDemo1{
public static void main(String[] args){
int num = 30;
//建立子類物件
Son s = new Son();
//呼叫show方法
s.show();
}
}
this:代表本類對應的引用
super:代表父類儲存空間的標識(可以理解為父類引用)
this | 解釋 | super | 解釋 | |
---|---|---|---|---|
訪問成員變數 | this.成員變數 | 呼叫本類的成員方法 | super.成員變數 | 呼叫父類的成員變數 |
訪問構造方法 | this(...) | 呼叫本類的構造方法,()裡為引數 | super(...) | 呼叫父類的構造方法,()裡為引數 |
訪問成員方法 | this.成員方法() | 呼叫本類的成員方法 | super.成員方法() | 呼叫父類的成員方法 |
1.5.2 繼承中構造方法之間的關係
- 子類中所有的構造方法預設都會訪問父類中空引數的構造方法
案例3:子類構造方法和父類構造方法的關係
//子類構造方法和父類構造方法的關係
//父類
class Father{
//父類無參構造方法,構造方法的方法名與該類的類名相同
public Father(){
System.out.println("這是父類的無參構造方法");
}
//父類帶參構造方法
public Father(String name){
System.out.println("這是父類的帶參構造方法");
}
}
//子類
class Son extends Father{
//子類無參構造方法,構造方法的方法名與該類的類名相同
public Son(){
//super();//預設呼叫父類的無參構造方法
System.out.println("這是子類的無參構造方法");
}
//子類帶參構造方法
public Son(String name){
//super();//預設呼叫父類的無參構造方法
System.out.println("這是子類的帶參構造方法");
}
}
class ExtendsDemo2{
public static void main(String[] args){
//建立子類物件
Son s = new Son();
System.out.println("-----------");
//建立帶參的子類物件
Son s1 = new Son("卡卡西");
}
}輸出:
這是父類的無參構造方法
這是子類的無參構造方法
-----------
這是父類的無參構造方法
這是子類的帶參構造方法
原因:
因為子類會繼承父類中的資料,可能還會使用父類的資料。所以,子類初始化之前,一定要先完成父類資料的初始化。
注意:
子類每一個構造方法的第一條語句預設都是:super();
注意事項:
1. 子類的構造方法預設會去訪問父類的無參構造方法 是為了子類訪問父類資料的初始化
2. 父類中如果沒有無參構造方法(只給了帶參構造方法),怎麼辦? a. 子類通過super去明確呼叫帶參構造 b. 子類通過this呼叫本身的其他構造,但是一定會有一個去訪問了父類的構造,讓父類提供無參構造
練習題1:
//以下程式碼的輸出結果是?
class Fu{
public int num = 10;
public Fu(){
System.out.println("fu");
}
}
class Zi extends Fu{
public int num = 20;
public Zi(){
System.out.println("zi");
}
public void show(){
int num = 30;
System.out.println(num);
System.out.println(this.num);
System.out.println(super.num);
}
}
class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
//輸出結果:
fu
zi
30
20
10
練習題2:
要點:
- 一個類的靜態程式碼塊、構造程式碼塊、構造方法執行流程——靜態程式碼塊 > 構造程式碼塊 > 構造方法;
- 靜態的內容是隨著類的載入而載入;
- 子類初始化之前會進行父類的初始化。
class Fu {
static {
System.out.println("靜態程式碼塊Fu");
}
{
System.out.println("構造程式碼塊Fu");
}
public Fu() {
System.out.println("構造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("靜態程式碼塊Zi");
}
{
System.out.println("構造程式碼塊Zi");
}
public Zi() {
System.out.println("構造方法Zi");
}
}
class Test1 {
public static void main(String[] args) {
Zi z = new Zi();
}
}
//輸出結果
靜態程式碼塊Fu
靜態程式碼塊Zi
構造程式碼塊Fu
構造方法Fu
構造程式碼塊Zi
構造方法Zi
練習題3:
要點:
- 成員變數型別(基本型和引用型)
- 一個類的初始化過程
- 成員變數的初始化(預設初始化→顯示初始化→構造方法初始化)
- 子父類的初始化(又稱分層初始化)
- 先進行父類初始化,再進行子類初始化
class X {
Y b = new Y();
X() {
System.out.print("X");
}
}
class Y {
Y() {
System.out.print("Y");
}
}
public class Z extends X {
Y y = new Y();
Z() {
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
//輸出結果:
YXYZ
1.5.3 繼承中成員方法之間的關係
- 子類中的方法和父類中的方法宣告不一樣
- 子類中的方法和父類中的方法宣告一樣時,先看子類中有無,有就使用;沒有就在父類中找
案例4:
class Father{
public void show(){
System.out.println("Show Father");
}
}
class Son extends Father{
public void method(){
System.out.println("method Son");
}
public void show(){
System.out.println("Show Son");
}
}
class ExtendsDemo3{
public static void main(String[] args){
Son s = new Son();
s.show();
s.method();
}
}
輸出結果:
Show Son
method Son
1.6 方法重寫
1.6.1 概述
子類中出現了和父類中一模一樣的方法宣告,也被稱為方法覆蓋,方法複寫
1.6.2 使用特點
- 如果方法名不同,就呼叫對應的方法
- 如果方法名相同,最終使用的是子類自己的
1.6.3 注意事項
- 父類中私有方法不能被重寫
- 子類重寫父類方法時,訪問許可權不能更低
- 父類靜態方法,子類也必須通過靜態方法進行重寫(這個算不上方法重寫)
兩個思考題: 1. Override和Overload的區別?Overload是否可以改變返回值型別?
答:
- 方法重寫——在子類中出現和父類一模一樣的方法宣告的現象
- 方法過載——同一個類中出現的方法名相同,引數列表不同的現象,能改變返回值,因為與返回值型別無關
2. this和super的區別和各自的作用?
- this——代表當前類的物件的引用
- super——代表父類儲存空間的標識
作用見1.5.3節的表格
1.7 final關鍵字
1.7.1 概述
final關鍵字是最終的意思,可以修飾類,成員變數,成員方法
1.7.2 特點
- 修飾類——被修飾的類為最終類,最終類不能被繼承(不能有子類)
- 修飾方法——被final修飾的方法不能被重寫(覆蓋)
- 修飾變數——被修飾的變數不能被重新賦值,其實就是常量(為自定義常量,之前學的為字面值常量)
1.7.3 兩個問題
1. final修飾區域性變數的問題
- 在方法內部,該變數不可以被改變
- 在方法宣告上,引數型別
- 為基本型別,值不能被改變,
- 為引用型別,地址值不能被改變
2. final修飾變數的初始化時機
- final修飾的變數只能賦值一次
- 在物件構造完畢前即可
2. 多型
2.1 多型的概述
某一個物件(事物),在不同時刻表現出來的不同狀態
舉例:水的不同狀態
2.2 多型的前提和體現
前提:
- 要有繼承關係
- 要有方法重寫
- 要有父類引用指向子類物件( 父 f = new 子(); )
2.3 多型中的成員訪問特點
2.3.1 成員變數
編譯看左邊,執行看左邊
2.3.2 構造方法
建立子類物件的時候,訪問父類的構造方法,對父類的資料進行初始化
2.3.3 成員方法
編譯看左邊,執行看右邊(方法重寫覆蓋了)
2.3.4 靜態方法
編譯看左邊,執行看左邊(靜態隨著類的載入而載入)
2.4 多型的利弊
利:
- 提高了程式碼的維護性(由繼承保證)
- 提高了程式碼的擴充套件性(由多型保證)
弊:
- 不能使用子類中的特有功能
2.5 多型中的轉型問題
要想使用子類中的特有功能,有兩種方法:
- 建立子類物件呼叫方法即可(佔記憶體)
- 將父類的引用強制轉換為子類的引用(向下轉型)——Zi z = (Zi) f;
2.5.1 向上轉型
Fu f = new Zi();//左邊為大類,右邊為小類
2.5.2 向下轉型
Zi z = (Zi) f;——要求該f必須是能轉換為Zi的
2.6 多型的練習
案例:貓狗案例
//貓狗案例
class Animal{
public void eat(){
System.out.println("吃飯");
}
}
//狗類繼承動物類
class Dog extends Animal{
public void eat(){
System.out.println("狗吃肉");
}
public void lookDoor(){
System.out.println("狗看門");
}
}
//貓繼承動物類
class Cat extends Animal{
public void eat(){
System.out.println("貓吃魚");
}
public void playGame(){
System.out.println("貓玩捉迷藏");
}
}
//測試類
class DuoTaiTest{
public static void main(String[] args){
//Animal類中的引用a指向Dog類,定義為狗
Animal a = new Dog();
a.eat();
System.out.println("-------------");
//建立Dog中的物件d,將a強制轉換使用Dog類中的特有功能,還原成狗
Dog d = (Dog) a;
d.eat();
d.lookDoor();
System.out.println("-------------");
//定義為貓
a = new Cat();
a.eat();
System.out.println("-------------");
//還原為貓
Cat c = (Cat)a;
c.eat();
c.playGame();
}
}
輸出:
狗吃肉
-------------
狗吃肉
狗看門
-------------
貓吃魚
-------------
貓吃魚
貓玩捉迷藏
3. 抽象類
3.1 抽象類的概述
把多個共性的東西提取到一個類中,這是繼承的做法。但是,這多個共性的東西,在有些時候,方法宣告一樣,但是方法體。也就是說,方法宣告一樣,但是每個具體的物件在具體實現的時候內容不一樣。所以,我們在定義這些共性的方法的時候,就不能給出具體的方法體。 而一個沒有具體的方法體的方法是抽象的方法(注意沒有方法體和空方法體的區別)。 在一個類中如果有抽象方法,該類必須定義為抽象類。
3.2 抽象類的特點
- 抽象類和抽象方法必須用abstract關鍵字修飾
格式:
abstract class 類名 {}
public abstract void eat();
- 抽象類不一定有抽象方法,有抽象方法的類必須是抽象類
- 抽象類不能例項化,因為它不是具體的。按照多型的方式,由具體的子類例項化。其實這也是多型的一種,抽象類多型
- 抽象類的子類,要麼是抽象類(不重寫抽象方法),要麼重寫抽象類中的所有抽象方法(此時子類為具體類)
3.3 抽象類的成員特點
成員變數
既可以是變數,又可以是常量
構造方法
有構造方法,用於子類訪問父類資料的初始化
成員方法
既可以是抽象方法,也可以是非抽象方法
抽象方法——強制要求子類做的事
非抽象方法——子類繼承的事情,提高程式碼複用性
4. 介面
4.1 概述
為了體現事物功能的擴充套件性,Java中就提供了介面來定義這些額外功能,並不給出具體實現
4.2 介面的特點
- 介面用關鍵字interface修飾
格式:interface 介面名 {}
- 類實現介面用implements修飾
格式:class 類名 implements 介面名 {}
- 介面不能例項化(用多型的方式來實現例項化)
多型的三種方式:
- 具體類多型(幾乎沒有)
- 抽象類多型
- 介面多型(最常見)
- 介面的子類
- 是一個抽象類,意義不大
- 是一個具體類,這個類必須重寫介面中的所有抽象方法
案例1:
/*
介面特點:
介面用關鍵字interface表示
格式:interface 介面名 {}
類實現介面用implements表示
格式:class 類名 implements 介面名 {}
*/
//貓狗案例
//定義動物培訓介面
interface AnimalTrain {
//定義抽象類,無方法體
public abstract void jump();
}
//抽象類實現介面,可以,但是意義不大
abstract class Dog implements AnimalTrain {
}
//具體類實現介面
class Cat implements AnimalTrain {
//重寫方法
public void jump(){
System.out.println("小貓釣魚");
}
}
class InterfaceDemo {
public static void main(String[] args){
/*
在類AnimalTrain中建立物件at報錯,因為AnimalTrain是抽象類,無法例項化
AnimalTrain at = new AnimalTrain();
at.jump();
*/
AnimalTrain at = new Cat();
at.jump();
}
}
4.3 介面的成員特點
成員變數
- 介面中的變數預設為常量,且是靜態的
- 有預設修飾符:public static final
- 預設修飾符建議自己手動給出
構造方法
- 介面沒有構造方法,因為介面主要功能為擴充套件,沒具體存在
- 其實現介面的具體類有構造方法,預設繼承於Object類
- 所有的類都預設繼承自一個類:Object
成員方法
- 只能是抽象方法
- 預設修飾符:public abstract
- 預設修飾符建議自己手動給出
案例2:
//介面的成員特點
//定義Inter介面
interface Inter {
//定義成員變數
public int num = 10;
public final int num1 = 20;
//給介面寫構造方法,編譯報錯
//因為介面沒有構造方法
//public Inter(){}//錯誤: 需要<識別符號>
//寫成員方法
//public void show(){}//錯誤: 介面抽象方法不能帶有主體
abstract void show();//預設許可權為public
}
/*
//具體類InterImpl實現介面
//開發中,介面名+Impl這種格式是介面的實現類格式
class InterImpl implements Inter {
//給實現類寫構造方法,編譯不報錯。因為所有的類都預設繼承自一個類:Object
public InterImpl(){
//訪問父類構造方法
super();
}
}
*/
//具體類InterImpl實現介面(預設繼承於Object類)
class InterImpl extends Object implements Inter {
//給實現類寫構造方法,編譯不報錯
//因為所有的類都預設繼承自一個類:Object
public InterImpl(){
//訪問父類構造方法
super();
}
//具體類中的成員方法
public void show(){}//不寫public報錯:正在嘗試分配更低的訪問許可權; 以前為public
}
//測試類
class InterfaceDemo1 {
public static void main(String[] args) {
//建立物件i
Inter i = new InterImpl();
//多型中訪問成員變數是訪問的左邊(只有成員方法是訪問的右邊,因為方法重寫)
System.out.println(i.num);//輸出10
System.out.println(i.num1);//輸出20
//i.num = 100;// 錯誤: 無法為最終變數num分配值
//i.num1 = 200;// 錯誤: 無法為最終變數num1分配值
System.out.println(Inter.num);//通過介面名字訪問變數,輸出10
System.out.println(Inter.num1);//通過介面名字訪問變數,輸出20
}
}
4.2 類與類、類與介面、介面與介面的關係
4.2.1 類與類的關係
繼承關係(extends):只能單繼承,可以多層繼承
4.2.2 類與介面的關係
實現關係(interface定義介面,implements實現介面):
可以單實現,也可以多實現(介面之間用逗號隔開即可,呼叫的時候注意方法與介面對應)
還可以在繼承一個類的同時實現多個介面(聯想預設繼承類Object)
4.2.3 介面與介面的關係
繼承關係,可以單繼承,也可以多繼承
抽象類和介面的區別:
A:成員區別
抽象類:
成員變數:可以是常量,也可以是變數
構造方法:有
成員方法:可以使抽象,也可以是非抽象
介面:
成員變數:只能是常量
成員方法:只能是抽象的
B:關係區別
類與類:
繼承,只能單繼承,不能多繼承
類與介面:
實現,可以單實現,也可以多實現
介面與介面:
繼承,可以單繼承,也可以多繼承
C:設計理念區別
抽象類:
被繼承體現的是is a的關係
抽象類中定義的是該繼承體系中的共性功能
介面:
被繼承體現的是like a的關係
介面中定義的是該繼承體系中的擴充套件功能
4.3 形式引數和返回值的問題
形式引數:
- 基本型別
- 引用型別(類、抽象類、介面)
案例1:類名作為形參,需要的是該類的物件
class Student{
public void study(){
System.out.println("好好學習,天天向上");
}
}
class StudentDemo{
public void method(Student s){
s.study();
}
}
//測試類
class StudentTest{
public static void main(String[] args){
//測試Student中的study方法,在Student類中建立物件s
Student s = new Student();
//通過該類中的物件s呼叫study方法
s.study();
//測試StudentDemo中的method方法,在StudentDemo類中建立物件ss
StudentDemo sd = new StudentDemo();
//在Student類中建立物件ss作為呼叫method方法時的引數
Student ss = new Student();
//呼叫該類中的method方法,不要忘記傳參
sd.method(ss);
//匿名物件用法
new StudentDemo().method(new Student());
}
}
案例2:抽象類作為形參,需要的是該抽象類的子類物件
//抽象類作為形參
//抽象類(抽象類的格式)
abstract class Person{
public abstract void study();//抽象方法沒有具體的方法體
}
class PersonDemo{
public void method(Person p){
p.study();
}
}
//定義一個具體的學生類繼承自抽象類Person
class Student extends Person{
//重寫抽象類的study方法
public void study(){
System.out.println("好好學習,天天向上");
}
}
//測試類
class PersonTest{
public static void main(String[] args){
//使用PersonDemo類中的method方法,在該類中建立物件pd
PersonDemo pd = new PersonDemo();
//抽象類Person例項化
//Person p = new Person();//錯誤: Person是抽象的; 無法例項化
//間接例項化
Person p = new Student();
pd.method(p);
}
}
案例3:介面作為形參,需要的是該介面的實現類物件
//介面作為形參
//定義一個興趣的介面
interface Hobby{
//介面的成員方法只能是抽象方法
public abstract void hobby();
}
class HobbyDemo{
public void method(Hobby h){
h.hobby();
}
}
//定義一個具體類來實現介面
class Teacher implements Hobby{
//方法重寫
public void hobby(){
System.out.println("卡卡西愛親熱天堂,貓咪老師愛燒酒");
}
}
//測試類
class HobbyTest{
public static void main(String[] args){
//測試HobbyDemo類中的method方法,先建立物件
HobbyDemo ht = new HobbyDemo();
Hobby h = new Teacher();
ht.method(h);
}
}
返回值型別:
- 基本型別
- 引用型別(類、抽象類、介面)
案例1:返回值型別為類的時候,返回的其實是該類的物件
class Student {
public void study() {
System.out.println("Good Good Study,Day Day Up");
}
}
class StudentDemo {
//返回一個Student型別的值,其實是返回Student類的物件
public Student getStudent() {
//建立Student類的物件s並返回s
//Student s = new Student();
//return s;
//用匿名物件簡化
return new Student();
}
}
class StudentTest2 {
public static void main(String[] args) {
//呼叫Student類中的study()方法,不要直接建立Student的物件
//使用StudentDemo建立物件sd
StudentDemo sd = new StudentDemo();
Student s = sd.getStudent();
s.study();
}
}
案例2:返回值為抽象類的時候,返回的其實是該抽象類的具體類的物件
//定義一個抽象類
abstract class Person {
public abstract void study();
}
class PersonDemo {
//返回的是抽象類Person的子類Student的物件
public Person getPerson() {
//Person p = new Student();
//return p;
//匿名物件簡化
return new Student();
}
}
//定義抽象類的子類
class Student extends Person {
//方法重寫
public void study() {
System.out.println("Good Good Study,Day Day Up");
}
}
class PersonTest2 {
public static void main(String[] args) {
//測試Person類中的study()方法
PersonDemo pd = new PersonDemo();
Person p = pd.getPerson();
p.study();
}
}
案例3:返回值位介面的時候,返回的其實是該介面的實現類的物件
略
鏈式程式設計 物件.方法1().方法2().......方法n(); 這種用法:其實在方法1()呼叫完畢後,應該返回一個物件; 方法2()呼叫完畢後,應該返回一個物件 方法n()呼叫完畢後,可能是物件,也可以不是物件
匿名物件中,物件是作為引數傳遞
5. 包和導包
5.1 包的概述
5.1.1 概念
其實就是資料夾
5.1.2 作用
- 區分同名的類
- 對類進行分類管理
- 按照功能分(增、刪、查、改)
- 按照模組分
5.2 包的定義格式及注意事項
5.2.1 包的定義格式
package 包名;
多級包用.分開即可
舉例:
package cn.itcast;
5.2.2 注意事項
- package語句必須是程式的第一條可執行的程式碼
- package語句在一個java檔案中只能有一個
- 如果沒有package,預設表示無包名
5.3 帶包的類的編譯和執行
手動式
- javac編譯當前類檔案。
- 手動建立包對應的資料夾。
- 把1步驟的class檔案放到2步驟的最終資料夾下。
- 通過java命令執行 (eg:需要帶包名稱的執行 java cn.itcast.HelloWorld )
自動式
- javac編譯的時候帶上-d即可 javac -d . HelloWorld.java
- 通過java命令執行,和手動式步驟4一樣
5.4 導包
5.4.1 概述
每次使用不同包下的類的時候,都需要加包的全路徑。java就提供了導包的功能
5.4.2 導包格式
import 包名;
這種方式匯入是到類的名稱。 雖然可以最後寫*,但是不建議,建議用誰導誰
思考題:
package,import,class有沒有順序關係?
答:有,package>import>class
- package只能有一個
- import能有很多個
- class能有多個,以後建議只有一個
6. 許可權修飾符
6.1 四種許可權修飾符
public | protected | 預設 | private | |
同一類中訪問 | √ | √ | √ | √ |
同一包中訪問子類和其他類 | √ | √ | √ | |
不同包中訪問子類 | √ | √ | ||
不同包中訪問其他類 | √ |
6.2 類及其組成常用的修飾符
修飾符的分類:
- 許可權修飾符:public、protected、預設許可權修飾符、private
- 狀態修飾符:static、final
- 抽象修飾符:abstract
類 的常用修飾符:
- 許可權修飾符:public(最常用)、預設許可權修飾符,protected和private不能修飾類
- 狀態修飾符:final
- 抽象修飾符:abstract
成員變數的常用修飾符:
- 許可權修飾符:private(最常用),預設的,protected,public
- 狀態修飾符:static,final
構造方法的常用修飾符:
- 許可權修飾符:private,預設的,protected,public(最常用)
成員方法的常用修飾符:
- 許可權修飾符:private,預設的,protected,public(最常用)
- 狀態修飾符:static,final
- 抽象修飾符:abstract
除此以外的組合規則:
- 成員變數:public static final
- 成員方法:
- public static
- public abstract
- public final
7. 內部類
7.1 概述
把類定義在其他類的內部,這個類就被稱為內部類
7.2 內部類的訪問特點
- 內部類可以直接訪問外部類的成員,包括私有
- 外部類要訪問內部類的成員,必須建立物件
7.3 內部類的分類
根據內部類在類中定義的位置不同,可以分為兩大類:
- 成員內部類(內部類在成員位置)
- 區域性內部類(內部類在成員方法內部,即區域性位置)
7.4 成員內部類
案例1:成員內部類訪問
要點:
建立物件格式:外部類名.內部類名 物件名 = 外部類物件.內部類物件; eg: Outer.Inner oi = new Outer().new Inner();
class Outer {
private int num = 10;
class Inner {
public void show(){
System.out.println(num);
}
}
}
class InnerClassDemo {
public static void main(String[] args){
//訪問成員內部類Inner的show()方法
//建立物件格式:外部類名.內部類名 物件名 = 外部類物件.內部類物件;
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
成員內部類的常用修飾符
private 為了保證資料的安全性
static 為了讓資料訪問更方便 (內部類用靜態修飾,此時內部類可以看成外部類的成員)
- 靜態的內部類訪問的外部類資料必須是靜態修飾的
- 內部類被靜態修飾後的方法 可以是:
- 靜態方法
- 非靜態方法
7.5 區域性內部類
案例:區域性內部類訪問
- 可以直接訪問外部類的成員
- 可以建立內部類物件,通過物件呼叫內部類方法,來使用區域性內部類功能
class Outer {
//定義成員變數
private int num = 10;
//定義成員方法
public void method(){
//定義區域性內部類
class Inner {
public void show(){
System.out.println(num);
}
}
//在區域性位置建立內部類物件i呼叫內部類show方法
Inner i = new Inner();
i.show();
}
}
class InnerClassDemo {
public static void main(String[] args){
//建立外部類物件o,通過o訪問外部類成員方法method
Outer o = new Outer();
o.method();
}
}
區域性內部類訪問區域性變數 必須用final修飾
原因:
因為區域性變數是隨著方法的呼叫二呼叫,隨著方法呼叫完畢而消失
這時,區域性物件並沒有立馬從堆記憶體中消失,還要使用那個變數
為了讓資料還能繼續被使用,就用fianl修飾,這樣,在堆記憶體裡面儲存的其實是一個常量值
7.6 匿名內部類
匿名內部類其實就是內部類的簡化寫法
前提:存在一個類或者介面
- 這裡的類可以是具體類也可以是抽象類
格式:
new 類名或者介面名() {
重寫方法;
}
本質: 是一個繼承了該類或者實現了該介面的子類匿名物件