目錄
-
- 一、初識java
- 1.1程序
- 1.2 Java平台的三個版本
- 1.3 Java特點
- 1.4 JRE基本介紹
- 1.5 JDK基本介紹
- 1.6 JVM基本介紹
- 1.7什麼是字節碼?
- 1.8 Java內存結構(簡單版)
- 1.9遞歸
- 二、類
- 2.1類含義的解釋
- 2.2對象
- 2.3 Java 面向對象編程三大特性:封裝、繼承、多態
- 2.4 Java基本類型
- 2.5訪問修飾符
- 2.6成員變量與局部變量
- 2.6.1局部變量與成員變量區別
- 2.7字符串與字符的區別
- 2.8類的構造方法
- 2.9靜態變量和靜態方法
- 2.9.1 main方法
- 2.10成員方法
- 2.11可變參數
- 2.12重寫(Override)
- 2.13重載(Overload)
- 2.14重寫和重載區別
- 2.15包
- 2.16 this指針
- 2.17 super關鍵字
- 2.18 super和this的區別
- 2.19 Object類
- 2.20對象類型的轉型
- 2.20.1向上轉型
- 2.20.2向下轉型
- 2.21 final關鍵字
- 2.21.1 final變量
- 2.21.2 final方法
- 2.21.3 final類
- 2.21.4 final總結
- 2.22面向對象編程特性(詳細)
- 2.22.1繼承
- 2.22.2多態
- 2.22.3 Java的動態綁定機制
- 2.23代碼塊
- 2.24類什麼時候被加載?
- 2.25對象創建時代碼塊構造方法調用順序
- 2.26抽象類與接口
- 2.26.1抽象類
- 2.26.2接口
- 2.26.3實現接口VS繼承
- 2.27內部類
- 2.27.1局部內部類
- 2.27.2匿名內部類
- 2.27.3成員內部類
- 2.27.4靜態內部類
一、初識java
1.1程序
計算機執行某些操作或解決某個問題而編寫的一系列有序指令的集合;
1.2 Java平台的三個版本
①JavaSE:即java標準版,主要用於開發和部署桌面。
②JavaEE:即Java企業版,主要針對企業應用的開發。
③JavaME:即Java微型版,主要針對移動設備和嵌入式設備。
1.3 Java特點
①Java語言是面向對象的語言(Object Oriented Programming)
②Java語言是跨平台性
③Java語言是健壯性的。
④Java語言是解釋型的(c/c++是編譯型的)
1.4 JRE基本介紹
① JRE(Java Runtime Environment)是Java的運行環境;
JRE = JVM + Java的核心類庫
② 包括了Java虛擬機和Java程序所需要的核心類庫等
因此運行Java程序只需要安裝JRE即可
1.5 JDK基本介紹
①JDK(Java Development Kit):Java開發工具包 JDK = JRE + java開發工具
②JDK是提供給Java開發人員使用的,包含了Java開發工具也包含了JRE
1.6 JVM基本介紹
①JVM是一個虛擬的計算機,具有指令集並使用不同的存儲區域。負責執行指令,管理數據、內存、寄存器;
②JVM負責將字節碼轉換為特定機器代碼(不同平台的運行代碼)。
③Java虛擬機機制屏蔽了底層運行平台的差別從而保證Java程序跨平台性;
1.7什麼是字節碼?
①瞭解字節碼前需要知道java文件類型:
擴展名為java,編譯之前是純文本文件,用來存放Java源代碼。
擴展名為class,編譯之後生成用來存儲字節碼的二進制文件。
②為什麼用字節碼?
可以實現一次編譯到處運行,也就是與運行的平台無關,它依靠不同平台的JVM虛擬機將字節碼解釋為特定平台的機器指令並且執行。
1.8 Java內存結構(簡單版)
①棧區:一般存放基本數據類型和局部變量等。
②堆區:存放對象、數組等
③方法區:常量池(常量,比如:字符串等),類加載信息等。
1.9遞歸
①執行一個方法時,就創建新的受保護的獨立空間
②方法的局部變量是獨立的,不會相互影響
③如果方法中使用的是引用類型變量(數組,對象等)就會共享該引用類型 的數據
④遞歸必須向退出遞歸的條件逼近,否則無限遞歸(棧溢出)
⑤當一個方法執行完畢遇到return,就會返回同時遵守誰調用返回給誰
二、類
2.1類含義的解釋
①類就是一類事物的統稱,如果將現實世界某一事物抽象成對象,類就是這類對象的統稱。例如:鳥類、魚類、人類等等。基本上所有的鳥具有“翅膀”可以飛行,這樣具有相同特性和行為的一類事物稱其為類。
②類是封裝對象的屬性和行為的載體。
2.2對象
上面講了類,而對象則是類抽象出來的一個實例。
2.3 Java 面向對象編程三大特性:封裝、繼承、多態
①封裝
- 封裝是面向對象編程的核心思想。將對象的屬性和行為封裝起來,其實現的載體是類,類通常會對外部隱藏其實現的細節,這就是封裝的思想。
- 只需向外提供接口供外部使用內部實現使用者無需瞭解。
②繼承
繼承是使用已有類作為新類的基礎,新類可以添加新的成員(包含數據成員、方法或者重寫父類方法)同時也可以使用父類的成員。通過使用繼承我們能夠方便地複用以前的代碼從而提高開發效率。子類全部繼承了父類的屬性和方法但受到訪問限制(訪問修飾符)。
注:父類與子類只是相對的。
③多態
多態就是指程序中定義的引用變量所指向的具體類型和通過該引用變量調用的方法在編程時並不確定的,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量調用的方法調用到底是哪個類中的方法,必須在由程序運行期間才能決定。
2.4 Java基本類型
①Java語言提供了八種基本類型。六種數字類型(四個整數型,兩個浮點型),一種字符類型,還有一種布爾型。
注:Java中char類型佔兩個字節
②引用類型
類、接口類型、數組類型、枚舉類型、註解類型。
③主要區別
基本數據類型在被創建時,在棧上給其分一塊內存,將數值直接存儲在棧上。而引用類型在棧上創建空間存放堆上返回的地址。
2.5訪問修飾符
注:默認等於不寫訪問修飾符
2.6成員變量與局部變量
class Test01 {
private String name;
public int i;
protected char j;
boolean k;
}
成員變量
①定義: [訪問修飾符] 類型 標識符(名字);
②屬性定義類型可以為任意類型,包含基本類型和引用類型
class Test01 {
private int a;//成員變量
public void cry() {
int b;//方法內的局部變量
}
{//代碼塊內的局部變量
int c;
}
}
局部變量
顧名思義:變量定義在程序的局部位置。例如:代碼塊內,方法內(c++稱為函數)
2.6.1局部變量與成員變量區別
①語法層面:成員變量屬於類的,而局部變量是在代碼塊和方法內。成員變量可以被訪問修飾符所修飾及static修飾,而局部變量不能被訪問修飾符所修飾同時也不能被static所修飾。但局部變量和成員變量可以被final修飾。
②生命週期:成員變量是對象的屬性所以它和創建它的對象伴生,而局部變量隨着方法調用的結束而被銷燬,代碼塊執行結束也被銷燬。
③存儲層面:因為成員變量是對象的屬性,而對象存在於堆中。局部變量存在於棧內存中
④初始化問題:成員變量沒有賦初值則是默認值,如果被final修飾必須顯式賦值。而局部變量不會隱式賦值。
2.7字符串與字符的區別
char ch = 'A';
String str01 = "";//可以是空串
String str02 = "哈嘍";
①從定義上看:字符常量是單引號裏面只有一個字符,而字符串常量是雙引號裏面可以有若干個字符。
②從內容看:字符常量其實相當於一個整形數字(ASCII碼值)所以它可以參加表達式的運算,而字符串常量則代表的是一個地址,該字符串在內存中的存放位置。
2.8類的構造方法
①構造方法的定義語法格式如下:
class F1 {
public F1() {
//構造方法體
}
}
- public:構造器訪問修飾符。也可以是private、protected
- F1:構造方法名。
②構造方法特點:
- 構造方法沒有返回值。
- 構造方法的名稱要與本類的名稱相同。
注:如果在類中定義的構造方法都不是無參的構造方法,那麼編譯器也不會為類提供一個無參的構造方法。所以只有類中無構造方法時編譯器才會給類提供一個無參的構造方法。
2.9靜態變量和靜態方法
①因為由static關鍵字修飾的變量和方法被稱為靜態變量或靜態方法。
②靜態變量
- 語法定義:
[訪問修飾符] [static] [數據類型] [變量名] ;(推薦定義方式)
public static int a;
[static] [訪問修飾符] [數據類型] [變量名] ;
static public int a;
- 訪問方式:
類名.變量名;(推薦)
對象名.變量名;
- 靜態變量特性:
1.可以做到共享數據(訪問權限和普通變量一樣)。
2.在類加載的時候就已經生成(隨着類的銷燬而消亡)。
③靜態方法
- 語法:
[訪問修飾符] [static] [數據返回類型] [方法名]() {
}
public static void cry() {
//statement;方法體
}
- 調用方式:
類名.方法名
對象名.方法名
- 使用場景:
當方法不涉及到任何和對象相關的成員,則可以將方法設計成靜態方法。
- 注意:
<1>靜態方法和普通方法都伴隨類的加載而加載,將信息存儲於方法。
<2>靜態方法中不允許使用和對象有關的關鍵字,例如:this和super;靜態方法中無這些參數(普通方法中隱含this等一些參數)。
<3>靜態方法中只能訪問靜態變量和靜態方法。非靜態方法既可以訪問靜態的也可以訪問非靜態的。
2.9.1 main方法
public static void main(String[] args) {
}
①main方法是虛擬機調用,因為調用着和main方法不在一個包中所以使用public修飾。
②Java虛擬機在執行main()方法時不必創建對象,所以該方法必須被修飾為static
③該方法接收String類型的數組參數,該數組中保存執行Java命令時傳遞給運行的類參數。
④靜態方法main要訪問本類的非靜態成員,先創建實例後才能通過對象訪問。
2.10成員方法
①定義成員方法的語法格式如下:
訪問修飾符 返回值類型 方法名(參數類型 參數名){
statement;//方法體
return 返回值;(有時沒有返回值)
}
成員方法中參數可以是基本類型也可以是引用類型。要使成員方法無返回值可以使用void關鍵字。
②成員方法調用細節
舉例:Person是一個類。
- 先加載Person類信息(屬性和方法信息,只會加載一次)
- 在堆中分配空間,進行默認初始化(new Person()語句)
- 把堆中地址賦值給P,P指向對象,P是局部變量所以被創建於棧上
- 進行指定初始化,例如構造器給數據成員賦值
注:默認初始化是對數據成員進行賦值,具體默認值是什麼請參照Java基本類型中的默認值然後才會對數據成員特定賦值(用户給定的值)。
2.11可變參數
①概念:Java允許將同一個類中的多個同名同功能但參數個數不同的方法,封裝成一個方法,可以通過可變參數實現。
②基本語法:
/*
語法:[訪問修飾符] [返回類型] 方法名(數據類型...形參名){
//方法體
}
*/
public int Variable(int... nums) {//求和
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
- int…表示接收的是可變參數,類型為int,即可以接收多個int(0到N)
- 使用可變參數時可以當做數組使用,即nums數組
- 它的本質是數組
- 可變參數可以和普通類型的參數一起放在形參列表但必須保證可變參數是最後一個參數同時列表中只能出現一個參編參數
注:編譯器是對應賦值所以普通參數必須放在可變參數前邊
2.12重寫(Override)
①重寫是子類對父類的允許訪問的方法的實現過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!
②重寫的好處在於子類可以根據需要,定義特定於自己的行為。 也就是説子類能夠根據需要實現父類的方法。
③重寫規則(重寫是建立在繼承的基礎之上)
- 參數列表與被重寫方法的參數列表必須完全相同。
- 返回類型與被重寫方法的返回類型可以不相同,但是必須是父類返回值的派生類。
- 訪問權限不能比父類中被重寫的方法的訪問權限更低。(只能升不能降)
- 父類的成員方法只能被它的子類重寫。
- 聲明為 final 的方法不能被重寫。
- 聲明為 static 的方法不能被重寫,但是能夠被再次聲明。
- 構造方法不能被重寫。
class F1 {
public void show(){
System.out.println("F1的show方法");
}
}
class F2 extends F1{
@Override
public void show() {//重寫了F1類的show方法
System.out.println("F2的show方法");
}
}
2.13重載(Overload)
①重載是在一個類裏面,方法名字相同,而參數不同。返回類型可以相同也可以不同。每個重載的方法(或者構造函數)都必須有一個獨一無二的參數類型列表。
②重載規則
- 被重載的方法必須改變參數列表(參數個數或類型不一樣);
- 被重載的方法可以改變返回類型;
- 被重載的方法可以改變訪問修飾符;
- 方法能夠在同一個類中或者在一個子類中被重載;
- 無法以返回值類型作為重載函數的區分標準;
2.14重寫和重載區別
①方法的重寫和重載是java多態性的不同表現,重寫是父類與子類之間多態性的一種表現,重載可以理解成多態的具體表現形式。
- 方法重載是一個類中定義了多個方法名相同,而他們的參數的數量不同或數量相同而類型和次序不同,則稱為方法的重載。
- 方法重寫是在子類存在方法與父類的方法的名字相同,而且參數的個數與類型一樣,返回值也一樣的方法,就稱為重寫。
- 方法重載是一個類的多態性表現,而方法重寫是子類與父類的一種多態性表現。
2.15包
①包的三大作用
- 區分相同類名字的類
- 當很多類時,可以很好的管理類
- 控制訪問範圍
②包的基本語法:package com.Test;
- package關鍵字表示打包
- com.Test表示包名
③包的本質:實際上就是創建不同的文件夾來保存類文件
//實例
{
//本類的Dog類
Dog dog01 = new Dog();
//別的包的Dog類
com.Test.Dog dog02 = new com.Test.Dog();
}
④包的命名
- 只能包含數字、字母、下劃線、小圓點,但不能用數字開頭,不能是關鍵字或保留字
- 命名規範:一般是小寫字母和小圓點(com.公司名.項目名.業務模塊名)
⑤常用包
- Java.lang是基本包,默認引入不需要再引入
- Java.util系統提供的工具包(工具類)
- Java.net網絡包(網絡開發)
- Java.awt是做java的界面開發GUI
注意:
- package的作用是聲明當前類所在包,需要放在類(或文件)最上面,一個類中最多隻有一句package
- import關鍵字,放在package
2.16 this指針
①Java語言中規定使用this關鍵字來代表本類對象的引用,this關鍵字被隱式地用於引用對象的成員變量與方法。Java虛擬機會給每個對象分配this代表當前對象。
類可以實例化多個對象,編譯器是如何正確調用合適對象的方法和屬性?
在通過對象名調用方法時會隱式傳入對象地址所以this可以指向正確對象
public void setName(String name){
this.name = name;
}
②this關鍵字可以用來訪問本類的屬性、方法、構造器
訪問屬性語法:this.屬性名;
訪問方法語法:this.方法名(參數列表);
訪問構造器語法:this(參數列表); 注意只能在構造器中使用(一個構造器訪問另外一個構造器且語句必須放在第一句)
③this作為返回值
public Person getPerson(){
return this;//this指向對象即this存着對象的地址
}
④this區分當前類的屬性和局部變量
在方法中如果傳入的參數和數據成員重名即可顯式使用this指針指向數據成員。如果不使用this指針則name = name把參數name賦給參數name。
⑤this關鍵字只能在方法內使用
2.17 super關鍵字
①基本介紹:super代表父類的引用,用於訪問父類的屬性、方法、構造器。
3. 訪問父類屬性和方法:不能訪問父類的private屬性和方法。
4. 訪問父類構造器:super(參數);只能放在構造器第一句話且只能出現一次。
②細節
- 調用父類的構造器的好處:分工明確、父類屬性由父類構造器初始化、子類屬性由子類構造器初始化。
- 當子類中有和父類中成員(屬性和方法)重名時,為了能訪問父類的成員必須通過super。如果沒有重名使用super和this直接訪問是一樣的效果。
- super的訪問不限於直接父類,如果上級類和本類中有同名的成員,也可以使用super去訪問父類的成員,super訪問遵循就近原則。
2.18 super和this的區別
|
區別
|
this
|
super
|
|
訪問屬性
|
訪問本類中的屬性,如果本類沒有此屬性則從父類查找
|
從父類開始查找
|
|
調用方法
|
先本類後父類
|
從父類開始
|
|
調用構造器
|
調用本類構造器必須放在構造器內首行
|
調用父類構造器必須放在構造器內首行
|
|
特殊性
|
表示當前對象
|
子類訪問父類對象
|
2.19 Object類
詳細講述Object類中幾個重要的方法。
①getClass()方法
概念:getClass()方法時Object類定義的方法,它會返回對象執行時的Class實例,然後使用此實例調用getName()方法可以取得類的名稱。語法如下:
getClass().getname();
②toString()方法
基本介紹:toString()方法功能是將一個對象返回為字符串形式。
默認返回:全類名+@+哈希值的十六進制。子類往往重寫toString方法
注:全限定名 = 包名+類型。
③equals()方法
在Java語言中,有兩種比較對象的方式,分別為“==”運算符和equals()方法。
- ==和equals的對比
“ ==”是一個比較運算符。
“ ==”即可以判斷基本類型,又可以判斷引用類型,如果判斷基本類型,判斷的是值是否相等。如果判斷引用類型,判斷的是地址是否相等。 - equals是Object類中的方法,只能判斷引用類型。
- equals默認判斷的是地址相等,子類中往往重寫該方法,用於判斷內容是否相等。
④finalize()方法
- 概念:當對象被收回時,系統自動調用該對象的finalize方法,子類可以重寫該方法做一些釋放資源的操作。
- 什麼時候被回收:當某個對象沒有任何引用時,JVM就認為該對象是一個垃圾對象,就會使用垃圾回收機制來銷燬該對象,在銷燬該對象前會先調用finalize方法
- 垃圾回收機制的調用是由系統決定的。(GC算法)
2.20對象類型的轉型
2.20.1向上轉型
①概念:向上轉型可以被理解為將子類類型對象轉換為父類類型的對象。即把子類類型的對象直接賦值給父類類型的對象。
②本質:父類的引用指向子類對象
③語法:父類類型 引用名 = new 子類類型();
④特點:
- 可以調用父類中的所有成員(需要受訪問權限限制)。
- 不能調用子類特有成員(編譯器階段調用成員,是由編譯類型決定的)。
- 最終運行效果看子類實現(子類重寫了父類方法,沒有重寫向上找父類的方法)。
2.20.2向下轉型
①概念:向下轉型可以被理解為將父類類型對象轉換為子類類型的對象。即把父類類型的對象直接賦值給子類類型的對象。
②語法:子類類型 引用名 = (子類類型)父類引用;
③注意:
- 只能強轉父類的引用,不能強轉父類對象。
- 要求父類的引用必須指向的是當前目標類型的對象(父類引用必須指向需要轉的類型)。
- 可以調用子類類型中所有成員(需要受訪問權限限制)。
2.21 final關鍵字
2.21.1 final變量
final關鍵字可用於變量聲明,一旦該變量被設定,就不可以改變該變量的值。通常,由final定義的變量為常量。
final double PI = 3.14;
當程序中使用到PI這個常量,它的值就是3.14。如果在程序中再次對定義為final的常量賦值,編譯器將不會接受。一旦一個對象的引用被修飾為final後,它就只能恆定指向一個對象,無法將其改變指向其他對象。一個既是static又是final的字段只能佔據不能改變的存儲空間。
2.21.2 final方法
將方法定義為final類型,可以防止子類修改父類的定義與實現方式,同時定義為final的方法的執行效率高於非final方法。一個定義為private訪問權限的方法隱式被指定為final類型。
private final void cry() {
//程序代碼;
}
2.21.3 final類
定義為final的類不能被繼承。語法如下:
final 類名{}
如果將某個類設置為final類,則該類中的所有方法都將被隱式設置為final方法,但是final中成員變量可以被定義為final或非final形式。
2.21.4 final總結
①在這些情況下可能有需求:
- 當不希望類被繼承時,可以用final修飾。
- 當不希望父類的某個方法被子類覆蓋/重寫,可用final修飾。
- 當不希望類的某個屬性的值被修改,可以用final修飾。
- 當不希望某個局部變量被修改,可以用final修飾。
②注意事項和細節
- final修飾的屬性又叫常量,一般用xx_xx來命名。(單詞大寫,單詞之間用下劃線)
- final修飾的屬性在定義時,必須賦初值並且以後不能修改,賦值可以在如下位置之一:
<1>定義時:public final double TAX_PATE=0.08;
<2>在構造器中和代碼塊中賦值,定義可以在類內只在構造和代碼塊內賦值。
class F1 {
final double A = 0.07;//定義時賦初值
final double D;
final double G;
public F1() {
D = 0.08; //在構造器內賦值
}
{
G = 0.09;//在代碼塊內賦值
}
}
③如果final修飾的屬性是靜態的,則初始化的位置只能是
- 定義時。
- 靜態代碼塊內。
④final類不能繼承,但可以實例化對象。
⑤final不修飾構造方法
⑥final和static往往搭配使用,效率更高,不會導致類加載(底層編譯器已經優化處理了)
⑦有些包裝類也是被final修飾的。
2.22面向對象編程特性(詳細)
2.22.1繼承
①繼承細節
- 子類繼承了父類的所有屬性和方法,但是私有屬性和方法不能在子類直接訪問,可以通過公共的方法訪問。不能直接訪問是因為訪問機制的限制(訪問修飾符)。
- 子類必須調用父類的構造器,完成對父類的初始化。
- 當創作子類對象時,不管使用子類的那個構造器,默認情況下總會去調用父類的無參構造器。如果父類沒有提供的無參構造器,則必須在子類的構造器中顯式用super去調用父類中指定的構造器完成對父類的初始化,否則編譯器不會通過。super(參數)通過參數類型和參數個數具體調用那個構造器。
- super在使用時,必須放在構造器第一行且只能在構造器中使用。
- super()和this()都要放在構造器的第一行,因此這兩種方法不可同時使用於同一構造器中。this()是調用本類的其他構造器而super()調用父類的構造器。
- Java所有類都是Object類的子類,所以Object類是所有的基類。
- 父類構造器的調用不限於直接父類,一直往上找父類直到Object類(最上層的類)
- 由於Java是單繼承的,所以子類只能直接繼承一個父類。
- 不能濫用繼承,子類和父類間必須符合一定的邏輯關係。
②子類訪問某屬性
- 首先看本類是否有該屬性。
- 如果本類有這個屬性,並且可以訪問則使用(調用)屬性(方法)。
- 如果子類沒有這個屬性,則往上(指父類)依次找父類有沒有該屬性;如果有該屬性且可以訪問則返回信息。
注:在查找過程中,如果遇到第一個該屬性但是不能訪問(私有屬性)則停止訪問,編譯器不會跳過這個屬性去找第二個相同的屬性(同名和同屬性)只會找到第一個該屬性
//子類訪問某屬性
class F1 {
private int f1_a = 10;
public int b = 100;
public int f_c = 1000;
}
class F2 extends F1 {
private int f2_a = 20;
public int b = 200;
private int f_c = 2000;
}
class F3 extends F2 {
public void show() {
//f1_a是F1的私有屬性所以無法直接訪問
//System.out.println(f1_a);
//只會訪問F2類的b而不會訪問F1類裏的b。
System.out.println(b);
//編譯器只會找到第一個f_c屬性
//如果無法訪問則報錯而不會越過F2的f_c去訪問F1的f_c屬性
//System.out.println(f_c);
}
}
public class Test {
public static void main(String[] args) {
F3 f3 = new F3();
f3.show();//入口
}
}
創建F3之前會往上查找父類直到Object類(或其他類)停止,然後從Object類開始初始化直到F3類。
2.22.2多態
①基本介紹:方法或對象具有多種形態,多態是建立在封裝的基礎之上。
②具體體現:
- 方法的多態:重寫和重載就體現多態。
- 對象的多態:例 Animal animal = new dog();
<1>一個對象的編譯類型和運行類型可以不一致。
<2>編譯類型在定義對象時,就確定了不能改變。
<3>運行類型是可以變化的。
<4>編譯類型看定義時“=”的左邊,運行類型看“=”的右邊。 - 多態的前提:兩個對象(類)存在繼承關係。
③注意:
- 屬性沒有重寫之説,屬性的值看編譯類型。
- instanceof比較操作符,用於判斷對象的運行類型是否為xx類型或xx類型的子類型。
public class Test {
public static void main(String[] args) {
People people = new Teacher();
System.out.println(people.a);//屬性的值看編譯類型(people)
}
}
class People {
public int a = 10;
}
class Teacher extends People {
public int a = 20;
}
④多態應用
- 多態數組:數組的定義類型為父類類型,裏面保存的實際元素為父類類型和子類類型。
- 多態參數:方法定義的形參類型為父類類型,實參允許為子類類型。
2.22.3 Java的動態綁定機制
①內容:
- 當調用對象方法時候,該方法會和該對象的內存地址(運行類型)綁定。
- 當調用對象屬性的時,沒有動態綁定機制,哪裏聲明那裏使用。
2.23代碼塊
①代碼塊分為普通代碼塊和靜態代碼塊。
- 普通代碼塊的定義:封裝在一對花括號之間
{
statement;//內容
}
在創建對象實例時,會被隱式的調用(被創建一次,就會調用一次)。如果只是通過類名訪問靜態成員時,普通代碼塊不會執行。類先加載所以靜態代碼塊先執行。
- 靜態代碼塊的定義:封裝在一對花括號之間且括號前與static修飾
static {
statement;//內容
}
靜態代碼塊只能調用靜態成員(靜態屬性,靜態方法),普通代碼塊可以調用任意成員。
2.24類什麼時候被加載?
①創建對象實例時。
②創建子類對象實例,父類也會被加載。
③使用類的靜態成員時(靜態屬性,靜態方法)。
2.25對象創建時代碼塊構造方法調用順序
①創建一個對象(無繼承關係)時,在一個類調用的順序是:
- 調用靜態代碼塊和靜態屬性初始化(注意:靜態代碼和靜態屬性初始化調用的優先級一樣,如有多個則按定義順序調用)。
- 調用普通代碼塊和普通屬性初始化(注意:普通代碼和普通屬性初始化調用的優先級一樣,如有多個則按定義順序調用)。
- 調用構造方法。
class F1 {
public F1() {
//1.執行普通代碼塊
//2.執行super()方法
//3.執行構造方法體
}
}
注:構造器的最前面其實隱含了super()和調用普通代碼塊,靜態代碼塊再類加載時就已經執行完畢了。
②創建一個子類對象時(具有繼承關係),它們的靜態代碼塊,靜態屬性初始化,普通代碼塊,普通屬性初始化,構造方法的調用順序如下:
- 父類的靜態代碼塊和靜態屬性初始化(優先級一樣,按照定義順序)。
- 子類的靜態代碼塊和靜態屬性初始化(優先級一樣,按照定義順序)。
- 父類的普通代碼塊和普通屬性初始化(優先級一樣,按照定義順序)。
- 父類的構造方法。
- 子類的普通代碼塊和普通屬性初始化(優先級一樣,按照定義順序)。
- 子類的構造方法。
1-2是類加載時就已經完成(只加載一次),3-6是創建對象時才執行(創建一次執行一次)。
//例:
class F1 {
public F1() {
//1.執行普通代碼塊
//2.執行super()方法
//3.執行構造方法體
System.out.println("F1的構造方法");
}
{
System.out.println("F1普通代碼塊!!!");
}
static {
System.out.println("F1靜態代碼塊");
}
}
class F2 extends F1 {
public F2() {
System.out.println("F2的構造方法");
}
{
System.out.println("F2普通代碼塊!!!");
}
static {
System.out.println("F2靜態代碼塊");
}
}
public class Test {
public static void main(String[] args) {
F2 f2 = new F2();
/*運行結果->
F1靜態代碼塊
F2靜態代碼塊
F1普通代碼塊!!!
F1的構造方法
F2普通代碼塊!!!
F2的構造方法
*/
}
}
2.26抽象類與接口
2.26.1抽象類
①為什麼需要抽象類?
在解決實際問題時,一般將父類定義為抽象類,需要使用這個父類進行繼承與多態處理。想回繼承和多態原理,繼承樹中越是向上方的類越抽象,如果鴿子類繼承鳥類、鳥類繼承動物等。在多態機制中並不需要將父類的初始化為對象,我們只需的只是子類對象,所以在Java語言中設置抽象類不可實例化為對象。
②使用abstract關鍵字定義的類稱為抽象類,而使用這個關鍵字定義的方法稱為抽象方法。抽象方法沒有方法體,這個方法本身沒有任何意義,除非被重寫,而繼承一個抽象方法的類必須被繼承,實際上抽象類除了被繼承沒有任何意義。語法如下:
public abstract class Parent {
abstract void testAbstract();
}
注:只要類中有一個抽象方法,此類必須被定義為抽象類。
③注意事項和細節
- 抽象類不能被實例化。
- 抽象類不一定要包含abstract方法,也就是説,抽象類可以沒有abstract方法。
- 一旦包含了abstract方法,類必須聲明為abstract類。
- abstract只能修飾類和方法,不能修飾其他的。
- 抽象類可以有任意成員(本質還是類)。
- 如果一個類繼承了抽象類,則它必須實現抽象類的所有抽象方法,除非它自己也聲明為abstract類
- 抽象方法不能使用private、final和static來修飾,因為這些關鍵字都是和重寫相違背的。
/*
* 子類繼承F1類,子類實現job()方法,子類創建對象調用calTime();
*/
abstract class F1 {
public abstract void job();
public void calTime() {
//記錄開始時間
long state = System.currentTimeMillis();
//子類實現
job();//子類實現
//記錄結束時間
long end = System.currentTimeMillis();
//計算出子類job大概運行時間
System.out.println("服務時間:" + (end - state));
}
}
2.26.2接口
①基本介紹:接口就是給出一些沒有實現方法,封裝到一起。在某個類要使用的時候,在根據具體情況把這些方法寫出來
②語法:
interface 接口名 {
//屬性
//方法
}
注:
<1>在JDK7.0前,接口裏的所有方法都沒有方法體,即都是抽象方法。
<2>在JDK8.0後,接口可以有靜態方法,默認方法也就是説接口中可以有方法體的具體實現。(默認方法需要使用default關鍵字修飾)
③注意事項和細節
- 接口不能被實例化(接口本質抽象類)。
- 接口中所有的方法是public方法,接口中抽象方法可以不用abstract修飾。
- 一個普通類實現接口,就必須將該接口的所有方法都實現。
- 抽象類實現接口,可以不用實現接口的方法。
- 一個類同時可以實現多個接口。class 類名 implements A,B {}
- 接口中的屬性只能是final的,而且還是public、static、final修飾。
- 接口不能繼承其他的類,但是可以繼承多個別的接口。
interface ID extends IC,IB {} - 接口的修飾符只能是public或默認。
2.26.3實現接口VS繼承
①當子類繼承了父類,就自動的擁有父類的功能。
②如果子類需要擴展功能,可以通過實現接口的方式擴展。可以理解為實現接口是對Java單繼承機制的一種補充。
③繼承的價值主要在於:解決代碼的複用性和可維護性。
接口的價值主要在於:設計好各種規範(方法),讓其它類去實現這些方法。
接口比繼承更加靈活。
2.27內部類
①基本介紹:一個類的內部又完整的嵌套了另一個類結構。被嵌套的類稱為內部類,嵌套其他類的類稱為外部類。
②語法:
class Outer {//外部類
class Inner {}//內部類
}
class other {}//外部其他類
③內部類分類:
- 局部內部類(有類名)
- 匿名內部類(無類名)
- 成員內部類
- 靜態內部類
2.27.1局部內部類
class Outer {
private int n = 100;
public void m1(){
class Inner02 {//m1方法內的內部類
public void f1(){
System.out.println("n = " + n);//訪問外部類的私有屬性
}
}
class Inner03 extends Inner02 {}
Inner02 inner02 = new Inner02();//實例化內部類Inner02
inner02.f1();
}
{
class Inner04{}//代碼塊中的內部類
}
}
①局部內部類是定義在外部類的局部位置,比如:方法和代碼塊中並且有類名。
②可以直接訪問外部類的所有成員,包含私有的。
③不能添加訪問修飾符,因為它的地位是一個局部變量,局部變量不能使用訪問修飾符,但可以用final。
④作用域:僅僅在定義它的方法或代碼塊中。
⑤局部內部類直接訪問外部類的成員,外部類必須在作用域內創建對象再訪問成員。
⑥外部其他類不能訪問局部內部類(因為它的地位是一個局部變量)。
⑦如果外部類和局部類的成員重名時,默認遵循就近原則,如果想訪問外部類的成員則可以使用(外部類名.this.成員名)去訪問。
class Outer {
private int a = 100;
public void m1(){
class Inner02{
private int a = 200;
public void F1(){
//訪問局部內部類的a(200);
System.out.println(a);
//訪問外部類的a(100);
System.out.println(Outer.this.a);
}
}
Inner02 inner02 = new Inner02();
inner02.F1();
}
}
public static void main(String[] args) {
Outer outer = new Outer();//創建外部類
outer.m1();//調用m1方法
}
注:Outer.this本質就是外部類的一個對象,即誰調用了m1()方法,Outer就是哪個對象。
2.27.2匿名內部類
①本質還是一個類,它是一個內部類,該類沒有名字(表面看沒有,但系統分配了名字),同時還是一個對象。匿名類是隻在創建對象時才會編譯類體的一種寫法。
②語法:
new 類/接口(參數列表) {
//類體
};
例:使用匿名內部類創建一個抽象狗類的對象。
/**
1. 創建一個抽象的狗類,類中有一個顏色屬性和兩個抽象方法,
2. 在測試類的主方法中創建抽象類對像,並且用匿名內部類實現
*/
abstract class Dog {
String Color;
public abstract void move();
public abstract void call();
}
public class Test {
public static void main(String[] args) {
Dog mao = new Dog() {
@Override
public void move() {
System.out.println("狂奔");
}
@Override
public void call() {
System.out.println("嗷嗚!");
}
};
mao.Color="灰色";
mao.call();
mao.call();
}
}
/**
*運行結果:
*狂奔
*嗷嗚!
*/
③注意:
- 匿名類不能寫構造方法。
- 匿名類不能定義靜態成員。
- 如果匿名類創建的對象沒有賦值給任何引用變量,會導致該對象用完一次會被Java虛擬機銷燬。
④注意事項
- 匿名內部類的調用(即使類又是對象)
//第一種調用方式
class Outer {
public void f1(){
Person person = new Person() {
@Override
public void hi() {}
};
person.hi();
}
}
//第二種調用方式
class Outer01 {
public void f2(){
new Person() {
@Override
public void hi() {}
}.hi();//new對象直接調用方法
}
}
- 可以直接訪問外部類的所有成員,包括私有的。
- 不能添加訪問修飾符,因為地位是一個局部變量。
- 作用域:僅僅在定義時方法內和代碼塊內。
- 外部其他類不能訪問匿名內部類。(因為地位是一個局部變量)
- 如果外部類和匿名局部類的成員重名時,默認遵循就近原則,如果想訪問外部類的成員則可以使用(外部類名.this.成員名)去訪問。
2.27.3成員內部類
成員內部類是定義在外部類的成員位置並且沒有static修飾。
class Outer {
public int i;
class Inner{}//成員內部類
}
①可以直接訪問外部類的所有成員,包含私有的。
②可以添加任意訪問修飾符(public、protected、默認、private)因為它的地位就是一個成員。
③作用域:整個類體。
④其他類訪問成員內部類
- 成員內部類直接訪問訪問外部類成員包含私有的。
- 外部類需要創建對象再訪問成員內部類。
- 外部其他類訪問成員內部類
<1>Outer.Inner inner = Outer.new Inner();
外部類名[點]成員內部類名 名字 = 外部類名[點] new 成員內部類名();
<2>在外部類中編寫一個方法,返回一個成員內部類對象。
class Outer {
class Inner{}
public Inner getInner(){
return new Inner();
}
}
//先創建一個外部類對象
Outer outer = new Outer();
//通過外部類對象調用getInner方法返回一個Inner對象
Outer.Inner inner = outer.getInner();
<3>將方法二整合在一起
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();//直接new
⑤如果外部類和成員局部類的成員重名時,默認遵循就近原則,如果想訪問外部類的成員則可以使用(外部類名.this.成員名)去訪問。
2.27.4靜態內部類
靜態內部類是定義在外部類的成員位置,並且用static修飾。
①可以直接訪問外部類的所有成員,包含私有的,但不能直接訪問非靜態成員。
②可以添加任意訪問修飾符(public、protected、默認、private)因為它的地位就是一個成員。
③作用域:整個類體。
④其他類訪問靜態內部類
- 靜態內部類直接訪問外部內部類的成員。
- 外部類需要創建對象再訪問靜態內部類。
- 外部其他類訪問靜態內部類
<1>Outer.Inner inner = new Outer.Inner();
Inner地位是類的成員,由於是靜態的,所以直接訪問可以不創建外部類對象。
<2>編寫一個方法,返回一個靜態內部類對象。
⑤如果外部類和靜態局部類的成員重名時,默認遵循就近原則,如果想訪問外部類的成員則可以使用(外部類名.this.成員名)去訪問。