目錄
Java語言基本元素
類和對象
概念
-
類(Class)和對象(Object)是面向對象的核心概念。
類是對一類事物的描述,是抽象的、概念上的定義
對象是實際存在的該類事物的每個個體,因而也稱爲實例(instance)。
-
“萬事萬物皆對象”
Field = 屬性 = 成員變量
Method = (成員)方法 = 函數
創建類的對象 = 類的實例化 = 實例化類
類
現實世界的生物體,大到鯨魚,小到螞蟻,都是由最基本的細胞構成的。同理, Java代碼世界是由諸多個不同功能的類構成的。
現實生物世界中的細胞又是由什麼構成的呢?細胞核、細胞質、 … 那麼,Java中用類class來描述事物也是如此。常見的類的成員有:
- 屬 性:對應類中的成員變量
- 行 爲:對應類中的成員方法
圖示屬性和行爲
圖示類成員的構成(完成版)
類的語法格式
修飾符 class 類名 {
屬性聲明;
方法聲明;
}
說明: 修飾符public:類可以被任意訪問
類的正文要用{ }括起來
舉例:
public class Person{
private int age ; //聲明私有變量 age
public void showAge(int i) { //聲明方法showAge( )
age = i;
}
}
如何創建Java自定義類
-
定義類(考慮修飾符、類名)
-
編寫類的屬性(考慮修飾符、屬性類型、屬性名、 初始化值)
-
編寫類的方法(考慮修飾符、返回值類型、方法名、形參等)
對象
對象的創建和使用
創建
類名 對象名 = new 類名();
使用
訪問對象成員(包括屬性和方法)
對象名.對象成員
注意
如果創建了一個類的多個對象,則每個對象都獨立的擁有一套類的屬性。(非static的)
- 意味着:如果我們修改一個對象的屬性a,則不影響另外一個對象屬性a的值。
對象的內存解析
對象的產生
Person p1=new Person();
對象的生命週期
類對象的賦值相當於賦值的是引用(保存對象的地址)
Person p1=new Person(); Person p2=p1;
內存解析
內存解析相關概念
堆(Heap) , 此內存區域的唯一目的就是存放對象實例, 幾乎所有的對象實例都在這裏分配內存。 這一點在Java虛擬機規範中的描述是:所有的對象實例以及數組都要在堆上分配。
通常所說的棧(Stack) , 是指虛擬機棧。 虛擬機棧用於存儲局部變量等。局部變量表存放了編譯期可知長度的各種基本數據類型(boolean、 byte、char 、 short 、 int 、 float 、 long 、double) 、 對象引用(reference類型,它不等同於對象本身, 是對象在堆內存的首地址) 。 方法執行完, 自動釋放。
方法區(Method Area) , 用於存儲已被虛擬機加載的類信息、 常量、 靜態變量、 即時編譯器編譯後的代碼等數據。
內存解析實例
匿名對象
不定義對象的句柄,而直接調用這個對象的方法。這樣的對象叫做匿名對象。
例如:
new Person().shout();
什麼情況下使用匿名對象?
- 如果對一個對象只需要進行一次方法調用,那麼就可以使用匿名對象。
- 我們經常將匿名對象作爲實參傳遞給一個方法調用。
類的成員
屬性(變量)
語法格式
修飾符 數據類型 屬性名 = 初始化值 ;
說明1:修飾符
-
常用的權限修飾符有: private、缺省、 protected、 public
-
其他修飾符: static、 final (暫不考慮)
說明2:數據類型
任何基本數據類型(如int、 Boolean) 或 任何引用數據類型。
說明3:屬性名
屬於標識符,符合命名規則和規範即可
變量的分類
成員變量:在方法體外,類體內聲明的變量稱爲成員變量。
局部變量:在方法體內部聲明的變量稱爲局部變量。
圖示變量的分類
成員變量(屬性)和局部變量的區別?
成員變量 | 局部變量 | |
---|---|---|
聲明的位置 | 直接聲明在類中 | 方法形參或內部、代碼塊內、構造器內等 |
修飾符 | private、 public、 static、 final等 | 不能用權限修飾符修飾,可以用final修飾 |
初始化值 | 有默認初始化值 | 沒有默認初始化值,必須顯式賦值,方可使用 |
內存加載位置 | 堆空間 或 靜態域內 棧 | 棧空間 |
解析
1.相同點:
1.1 定義變量的格式:數據類型 變量名 = 變量值
1.2 先聲明,後使用
1.3 變量都有其對應的作用域
2.不同點:
2.1 在類中聲明的位置的不同
-
成員變量:直接定義在類的一對{}內
-
局部變量:聲明在方法內、方法形參、代碼塊內、構造器形參、構造器內部的變量
2.2 關於權限修飾符的不同
-
成員變量:可以在聲明屬性時,指明其權限,使用權限修飾符。
-
常用的權限修飾符:private、public、缺省、protected —>封裝性
-
目前,大家聲明屬性時,都使用缺省就可以了。
-
局部變量:不可以使用權限修飾符。
2.3 默認初始化值的情況:
-
成員變量:類的屬性,根據其類型,都有默認初始化值。- 默認初始化賦值表
-
局部變量:沒有默認初始化值。意味着,我們在調用局部變量之前,一定要顯式賦值。
-
特別地:形參在調用時,我們賦值即可。
2.4 在內存中加載的位置:
-
成員變量:加載到堆空間中 (非static)
-
局部變量:加載到棧空間
變量的默認初始化賦值
當一個對象被創建時,會對其中各種類型的成員變量自動進行初始化賦值。除了
基本數據類型之外的變量類型都是引用類型,如上面的Person及前面講過的數組。
成員變量類型 | 初始值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0F |
double | 0.0 |
char | 0 或寫爲:’\u0000’(表現爲空) |
boolean | false |
引用類型 | null |
屬性賦值的先後順序
① 默認初始化
② 顯式初始化
③ 構造器中初始化
④ 通過"對象.方法" 或 "對象.屬性"的方式,賦值
⑤在代碼塊中賦值
以上操作的先後順序:① - ②/⑤(根據先後順序判斷) - ③ - ④
方法
什麼是方法(method、函數)
- 方法是類或對象行爲特徵的抽象,用來完成某個功能操作(描述類應該具有的功能)。在某些語言中也稱爲函數或過程。
- 將功能封裝爲方法的目的是,可以實現代碼重用,簡化代碼
- Java裏的方法不能獨立存在,所有的方法必須定義在類裏。
方法的聲明格式
修飾符 返回值類型 方法名(參數類型 形參1, 參數類型 形參2, ….){
方法體程序代碼
return 返回值;
}
注意:
- 修飾符: public,缺省,private, protected等
- 返回值類型:
- 沒有返回值: void。
- 有返回值,聲明出返回值的類型。與方法體中“return 返回值” 搭配使用
-
方法名:屬於標識符,命名時遵循標識符命名規則和規範,“見名知意”
形參列表:可以包含零個,一個或多個參數。多個參數時,中間用“,”隔開
返回值:方法在執行完畢後返還給調用它的程序的數據。 -
方法中,不可以定義方法。
-
特殊的:方法A中又調用了方法A:遞歸方法。
return關鍵字的使用:
使用範圍:使用在方法體中
作用:① 結束方法
② 針對於有返回值類型的方法,使用"return 數據"方法返回所要的數據。
注意點:return關鍵字後面不可以聲明執行語句。
如何理解方法返回值類型爲void的情況 ?
返回值類型: 有返回值 vs 沒有返回值
-
如果方法有返回值,則必須在方法聲明時,指定返回值的類型。同時,方法中,需要使用return關鍵字來返回指定類型的變量或常量:“return 數據”。
-
如果方法沒有返回值,則方法聲明時,使用void來表示。通常,沒有返回值的方法中,就不需要使用return.但是,如果使用的話,只能“return;”表示結束此方法的意思。
方法的分類
分類 | 有返回值 | 無返回值 |
---|---|---|
無形參 | void 方法名(){} | 返回值的類型 方法名(){} |
有形參 | 有形參 void 方法名(形參列表){} | 返回值的類型 方法名(形參列表){} |
方法的調用
方法通過方法名被調用,且只有被調用纔會執行。
方法調用的過程分析
注意事項
- 方法被調用一次,就會執行一次
- 沒有具體返回值的情況,返回值類型用關鍵字void表示,那麼方法體中可以不必使用return語句。如果使用,僅用來結束方法。
- 定義方法時,方法的結果應該返回給調用者,交由調用者處理。
- 方法中只能調用方法或屬性, 不可以在方法內部定義方法。
方法的重載
概念
重載:在同一個類中,允許存在一個以上的同名方法,只要它們的參數個數或者參數類型不同即可。
特點
與返回值類型無關,只看參數列表,且參數列表必須不同。 (參數個數或參數類型)。調用時,根據方法參數列表的不同來區別。
“兩同一不同”:
- 同一個類、相同方法名
-
參數列表不同:參數個數不同,參數類型不同
示例
//返回兩個整數的和
int add(int x,int y){return x+y;}
//返回三個整數的和
int add(int x,int y,int z){return x+y+z;}
//返回兩個小數的和
double add(double x,double y){return x+y;}
判斷是否爲重載
跟方法的權限修飾符、返回值類型、形參變量名、方法體都沒有關係!
方法的重寫
主要用於繼承。繼承
在子類中可以根據需要對從父類中繼承來的方法進行改造, 也稱爲方法的重置、覆蓋。在程序執行時,子類的方法將覆蓋父類的方法。
要求:
-
子類重寫的方法必須和父類被重寫的方法具有相同的方法名稱、 參數列表
-
返回值類型:
-
父類被重寫的方法的返回值類型是void,則子類重寫的方法的返回值類型只能是void
-
父類被重寫的方法的返回值類型是A類型,則子類重寫的方法的返回值類型可以是A類或A類的子類
-
父類被重寫的方法的返回值類型是基本數據類型(比如:double),則子類重寫的方法的返回值類型必須是相同的基本數據類型(必須也是double)
-
-
子類重寫的方法使用的訪問權限不能小於父類被重寫的方法的訪問權限
- 子類不能重寫父類中聲明爲private權限的方法
- 子類方法拋出的異常不能大於父類被重寫方法的異常
注意:
子類與父類中同名同參數的方法必須同時聲明爲非static
的(即爲重寫),或者同時聲明爲static
的(不是重寫) 。因爲static方法是屬於類的,子類無法覆蓋父類的方法。
示例
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
public class Student extends Person {
public String school;
public String getInfo() { //重寫方法
return "Name: "+ name + "\nage: "+ age + "\nschool: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
}
}
分析
Person p1=new Person();
//調用Person類的getInfo()方法
p1.getInfo();
Student s1=new Student();
//調用Student類的getInfo()方法
s1.getInfo();
這是一種“多態性”:同名的方法,用不同的對象來區分調用的是哪一個方法。
再談方法的重載與重寫
詳情見多態性的方法的重載與重寫 方法的重載與重寫與多態性(方法的重載是多態性的一種體現?NO)
方法中可變個數的形參
JavaSE 5.0 中提供了Varargs(variable number of arguments)機制,允許直接定義能和多個實參相匹配的形參。從而,可以用一種更簡單的方式,來傳遞個數可變的實參。
//JDK 5.0以前: 採用數組形參來定義方法,傳入多個同一類型變量
public static void test(int a ,String[] books);
//JDK 5.0開始: 採用可變個數形參來定義方法,傳入多個同一類型變量
public static void test(int a ,String…books);
注意事項
-
聲明格式: 方法名(參數的類型名 …參數名)
-
可變參數:方法參數部分指定類型的參數個數是可變多個: 0個, 1個或多個
-
可變個數形參的方法與同名的方法之間,彼此構成重載
-
可變參數方法的使用與方法參數部分使用數組是一致的
-
方法的參數部分有可變形參,需要放在形參聲明的最後
-
在一個方法的形參位置,最多隻能聲明一個可變個數形參
方法參數的值傳遞機制
形參:方法定義時,聲明的小括號內的參數
實參: 方法調用時,實際傳遞給形參的數據
Java的實參值如何傳入方法呢?
Java裏方法的參數傳遞方式只有一種: 值傳遞。 即將實際參數值的副本(複製品)傳入方法內,而參數本身不受影響。
- 形參是基本數據類型:將實參基本數據類型變量的“數據值”傳遞給形參
- 形參是引用數據類型:將實參引用數據類型變量的“地址值”傳遞給形參
參數傳遞解析
基本數據類型的參數傳遞
形參不會修改堆空間實參的具體值,因爲形參此時存儲的是實參的數據值
public static void main(String[] args) {
int x = 5;
System.out.println("修改之前x = " + x);// 5
// x是實參
change(x);
System.out.println("修改之後x = " + x);// 5
}
public static void change(int x) {
System.out.println("change:修改之前x = " + x);//5
x = 3;
System.out.println("change:修改之後x = " + x);//3
}
引用數據類型的參數傳遞
形參會修改堆空間實參的具體值,因爲形參此時存儲的是實參的地址引用.
public static void main(String[] args) {
Person obj = new Person();
obj.age = 5;
System.out.println("修改之前age = " + obj.age);// 5
// x是實參
change(obj);
System.out.println("修改之後age = " + obj.age);// 3
}
public static void change(Person obj) {
System.out.println("change:修改之前age = " + obj.age);//5
obj.age = 3;
System.out.println("change:修改之後age = " + obj.age);//3
}
//其中Person類定義爲:
class Person{
int age;
}
遞歸方法
遞歸方法:一個方法體內調用它自身。
- 方法遞歸包含了一種隱式的循環,它會重複執行某段代碼,但這種重複執行無須循環控制。
- 遞歸一定要向已知方向遞歸,否則這種遞歸就變成了無窮遞歸,類似於死循環。
//計算1-100之間所有自然數的和
public int sum(int num){
if(num == 1){
return 1;
}else{
return num + sum(num - 1);
}
}
main方法
main方法的特點
-
main()方法作爲程序的入口
-
main()方法也是一個普通的靜態方法
-
main()方法可以作爲我們與控制檯交互的方式。(之前:使用Scanner)
命令行參數用法舉例
public class CommandPara { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println("args[" + i + "] = " + args[i]); } } }
//運行程序CommandPara.java
java CommandPara “Tom" “Jerry" “Shkstart"
輸出結果:
輸出結果:
args[0] = Tom args[1] = Jerry args[2] = Shkstart
理解main方法的語法
- 由於Java虛擬機需要調用類的main()方法,所以該方法的訪問權限必須是public。又因爲Java虛擬機在執行main()方法時不必創建對象,所以該方法必須是static的,該方法接收一個String類型的數組參數,該數組中保存執行Java命令時傳遞給所運行的類的參數。
- 因爲main() 方法是靜態的,我們不能直接訪問該類中的非靜態成員,必須創建該類的一個實例對象後,才能通過這個對象去訪問類中的非靜態成員。
構造器(構造方法)
構造器的特徵
- 它具有與類相同的名稱
- 它不聲明返回值類型。(與聲明爲void不同)
- 不能被static、 final、 synchronized、 abstract、 native修飾,不能有return語句返回值
構造器的作用
1. 創建對象;
2. 初始化對象的信息
如: Order o = new Order(); Person p = new Person(“Peter”,15);
如同我們規定每個“人”一出生就必須先洗澡,我們就可以在“人” 的構造器中加入完成“洗澡”的程序代碼,於是每個“人”一出生就會自動完成“洗澡”,程序就不必再在每個人剛出生時一個一個地告訴他們要“洗澡”了。
語法格式
修飾符 類名 (參數列表) {
初始化語句;
}
構造器的分類
根據參數不同,構造器可以分爲如下兩類:
- 隱式無參構造器(系統默認提供)
- 顯式定義一個或多個構造器(無參、有參)
構造器的注意事項
- Java語言中,每個類都至少有一個構造器
- 默認構造器的修飾符與所屬類的修飾符一致
- 一旦顯式定義了構造器, 則系統不再提供默認構造器
- 一個類可以創建多個重載的構造器
- 父類的構造器不可被子類繼承
- 一個類中,至少會有一個構造器。
構造器重載
構造器重載,參數列表必須不同
構造器重載使得對象的創建更加靈活,方便創建各種不同的對象
構造器重載舉例:
public class Person{
public Person(String name, int age, Date d) {this(name,age);…}
public Person(String name, int age) {…}
public Person(String name, Date d) {…}
public Person(){…}
}
代碼塊
代碼塊的作用
用來初始化類、對象
代碼塊的分類
代碼塊如果有修飾的話,只能使用static.
一個類中代碼塊若有修飾符, 則只能被static修飾, 稱爲靜態代碼塊(static block), 沒有使用static修飾的, 爲非靜態代碼塊。
靜態代碼塊 vs 非靜態代碼塊
靜態代碼塊:用static 修飾的代碼塊
- 內部可以有輸出語句。
- 可以對類的屬性、類的聲明進行初始化操作。
- 不可以對非靜態的屬性初始化。即:不可以調用非靜態的屬性和方法。
- 若有多個靜態的代碼塊,那麼按照從上到下的順序依次執行。
- 靜態代碼塊的執行要先於非靜態代碼塊。
- 靜態代碼塊隨着類的加載而加載,且只執行一次。
- 作用:初始化類的信息
非靜態代碼塊:沒有static修飾的代碼塊
- 內部可以有輸出語句。
- 可以對類的屬性、 類的聲明進行初始化操作。
- 非靜態代碼塊內可以調用靜態的屬性、靜態的方法,或非靜態的屬性、非靜態的方法
- 若有多個非靜態的代碼塊, 那麼按照從上到下的順序依次執行。
- 每次創建對象的時候, 都會執行一次(隨着對象的創建而執行)。 且先於構造器執行。
- 作用:可以在創建對象時,對對象的屬性等進行初始化
使用示例
public class BlockTest {
public static void main(String[] args) {
String desc = Person.desc;
System.out.println(desc);
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1.age);
Person.info();
}
}
class Person{
//屬性
String name;
int age;
static String desc = "我是一個人";
//構造器
public Person(){}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//非static的代碼塊
{
System.out.println("hello, block - 2");
}
{
System.out.println("hello, block - 1");
//調用非靜態結構
age = 1;
eat();
//調用靜態結構
desc = "我是一個愛學習的人1";
info();
}
//static的代碼塊
static{
System.out.println("hello,static block-2");
}
static{
System.out.println("hello,static block-1");
//調用靜態結構
desc = "我是一個愛學習的人";
info();
//不可以調用非靜態結構
// eat();
// name = "Tom";
}
//方法
public void eat(){
System.out.println("喫飯");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public static void info(){
System.out.println("我是一個快樂的人!");
}
}
內部類
在Java中,允許一個類的定義位於另一個類的內部,前者稱爲內部類,後者稱爲外部類。
什麼情況下使用內部類?
當一個事物的內部,還有一個部分需要一個完整的結構進行描述,而這個內部的完整的結構又只爲外部事物提供服務,那麼整個內部的完整結構最好使用內部類。
內部類分類
成員內部類(靜態、非靜態)
一方面,作爲外部類的成員:
-
調用外部類的結構
-
可以被static修飾, 但此時就不能再使用外層類的非static的成員變量
-
可以被4種不同的權限修飾
另一方面,作爲一個類:
-
類內可以定義屬性、方法、構造器等
-
可以被final修飾,表示此類不能被繼承。言外之意,不使用final,就可以被繼承
-
可以被abstract修飾,因此可以被其它的內部類繼承
- 編譯以後生成OuterClass$InnerClass.class字節碼文件(也適用於局部內部類)
局部內部類(方法內、代碼塊內、構造器內)
如何聲明內部類?
class 外部類{
//方法中使用局部內部類
方法(){
class 局部內部類{
}
}
//代碼塊中使用局部內部類
{
class 局部內部類{
}
}
}
如何使用局部內部類
-
只能在聲明它的方法或代碼塊中使用,而且是先聲明後使用。除此之外的任何地方都不能使用該類
-
但是它的對象可以通過外部方法的返回值返回使用,返回值類型只能是局部內部類的父類或父接口類型、
public class InnerClassTest { //開發中很少見 public void method(){ //局部內部類 class AA{ } } //返回一個實現了Comparable接口的類的對象 public Comparable getComparable(){ //創建一個實現了Comparable接口的類:局部內部類 //方式一: // class MyComparable implements Comparable{ // // @Override // public int compareTo(Object o) { // return 0; // } // // } // // return new MyComparable(); //方式二: return new Comparable(){ @Override public int compareTo(Object o) { return 0; } }; } }
局部內部類的特點
- 內部類仍然是一個獨立的類,在編譯之後內部類會被編譯成獨立的.class文件,但是前面冠以外部類的類名和$符號,以及數字編號。
- 只能在聲明它的方法或代碼塊中使用,而且是先聲明後使用。除此之外的任何地方都不能使用該類。
- 局部內部類可以使用外部類的成員,包括私有的。
- 局部內部類可以使用外部方法的局部變量,但是必須是final的。 由局部內部類和局部變量的聲明週期不同所致。
- 局部內部類和局部變量地位類似,不能使用public,protected,缺省,private
- 局部內部類不能使用static修飾,因此也不能包含靜態成員
匿名內部類
匿名內部類不能定義任何靜態成員、方法和類,只能創建匿名內部類的一個實例。一個匿名內部類一定是在new的後面,用其隱含實現一個接口或實現一個類。
格式:
new 父類構造器(實參列表) |實現接口(){
//匿名內部類的類體部分
}
匿名內部類的特點
- 匿名內部類必須繼承父類或實現接口
- 匿名內部類只能有一個對象
- 匿名內部類對象只能使用多態形式引用
注意事項
- 非static的成員內部類中的成員不能聲明爲static的, 只有在外部類或static的成員內部類中才可聲明static成員。
- 外部類訪問成員內部類的成員, 需要“內部類.成員”或“內部類對象.成員”的方式
- 成員內部類可以直接使用外部類的所有成員, 包括私有的數據
- 當想要在外部類的靜態成員部分使用內部類時, 可以考慮內部類聲明爲靜態的
使用示例
public class InnerClassTest {
public static void main(String[] args) {
//創建Dog實例(靜態的成員內部類):
Person.Dog dog = new Person.Dog();
dog.show();
//創建Bird實例(非靜態的成員內部類):
// Person.Bird bird = new Person.Bird();//錯誤的
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();
System.out.println();
bird.display("黃鸝");
}
}
class Person{
String name = "小明";
int age;
public void eat(){
System.out.println("人:喫飯");
}
//靜態成員內部類
static class Dog{
String name;
int age;
public void show(){
System.out.println("卡拉是條狗");
}
}
//非靜態成員內部類
class Bird{
String name = "杜鵑";
public Bird(){
}
public void sing(){
System.out.println("我是一隻小小鳥");
Person.this.eat();//調用外部類的非靜態屬性
eat();
System.out.println(age);
}
public void display(String name){
System.out.println(name);//方法的形參
System.out.println(this.name);//內部類的屬性
System.out.println(Person.this.name);//外部類的屬性
}
}
public void method(){
//局部內部類
class AA{}
}
{
//局部內部類
class BB{}
}
public Person(){
//局部內部類
class CC{}
}
}