更好的閱讀體驗:點這里 ( www.doubibiji.com
)
更好的閱讀體驗:點這里 ( www.doubibiji.com
)
更好的閱讀體驗:點這里 ( www.doubibiji.com
)
7 面向對象
面向對象,首先涉及到的兩個概念就是:類 和 對象。
什么是類?
類就是對現實事物的抽象設計。
例如設計學生的類,可能包括屬性:學號,姓名、年齡、性別等。
設計狗的類,可能包括屬性:名字、年齡、品種。
類表示的是對一個事物的抽象,不是具體的某個事物。
什么是對象?
對象就是一個具體的實例,這個實例是從類派生出來的。
我們將類的屬性賦值,就產生了一個個實例,也就是對象。
例如通過學生的類,我們賦值:學號=001,姓名=張三,年齡=16,性別=男,班級=三年二班,產生了就是一個名字叫張三的具體的學生,這樣我們通過給類可以派生出一個個不同屬性值的對象,李四、王五、趙六…。
所以是先設計類,然后根據類來生成一個個對象。
7.1 類和對象
1 類的定義
類中可以定義屬性和行為,屬性也就是成員變量,表示這個類有哪些數據信息,行為也叫成員方法,表示這個類能干什么。
例如,對于學生類而言,學號、姓名、年級就是屬性,學習這個行為,可以定義為方法。
那么我們可以定義以下學生類:
/***定義類使用class關鍵字,后面跟類名*/
class Student {public String sid;public String name;public int age;public void study() {System.out.println("我是" + name + ", 我在學習");}
}
上面的類定義了三個成員變量(sid、name、age),public
表示訪問修飾符,用來限制屬性的訪問權限,后面再講,現在使用 public
。后面是變量類型和變量的名稱。
還定義了一個成員方法 study()
,定義成員方法和之前定義靜態方法是一樣的,只是沒有 static
關鍵字。
2 類的使用
上面我們定義好類了,現在可以使用類來創建對象了。
/*** 定義學生類*/
class Student {public String sid;public String name;public int age;public void study() {System.out.println("我是" + name + ", 我在學習");}
}/*** 測試類*/
public class Test {public static void main(String[] args) {Student stu = new Student(); // 創建對象stu.sid = "001"; // 為對象的sid屬性賦值stu.name = "張三"; // 為對象的name屬性賦值,現在學生是張三了stu.age = 18;System.out.println(stu.name); // 打印名稱stu.study(); // 使用對象調用方法}
}
因為 Java 中所有的代碼都要放到類中,這里將測試的 main 方法放到了另一個類中,當然 main 方法放到 Student 中也是可以的,這里為了結構清晰。
首先通過 new Student()
可以創建一個 Student 對象,賦值給 Student
類型的變量 stu
。
創建對象后,我們可以通過 對象.屬性
來訪問變量,也可以通過 對象.屬性=值
來給屬性賦值。
使用 對象.方法()
可以調用方法。
執行結果:
張三
我是張三, 我在學習
面向對象編程就是先設計類,然后通過類創建對象,由對象做具體的工作。
3 默認構造方法
在上面創建對象后,使用 對象.屬性=值
來給屬性賦值,有點麻煩,我們可以在創建對象的時候,直接傳遞參數,給屬性賦值。
這里就需要用到 構造方法。構造方法會在創建對象的時候自動執行,通過傳遞的屬性值,給屬性進行初始化。
舉個栗子:
構造方法的名稱和類的名稱是一致的,沒有返回值類型。
/*** 定義類*/
class Student {public String sid;public String name;public int age;/*** 構造方法*/public Student(String sid, String name, int age) {this.sid = sid; // this.sid 訪問的是屬性,sid 是方法的形參this.name = name;this.age = age;}public void study() {System.out.println("我是" + name + ", 我在學習");}
}/*** 測試類*/
public class Test{public static void main(String[] args) {// 創建張三Student stu1 = new Student("001", "張三", 18);stu1.study();// 創建李四Student stu2 = new Student("002", "李四", 19);stu2.study();}
}
在上面的代碼中,定義了構造方法,接收三個參數 (String sid, String name, int age)
,在構造方法中,分別將三個參數賦值給類中的成員變量。
因為構造方法的參數和類中的成員變量名稱相同了(也可以不同),所以如果在構造方法中直接使用變量名,訪問的將是構造方法的參數,訪問不到成員變量,如果要訪問成員,需要使用 this
關鍵字。
當然在 study()
方法中,沒有參數和成員變量 name
同名,所以 name
訪問到的就是成員變量,可以不用 this
(當然使用也沒毛病)。
在創建對象的時候,就可以傳遞參數為對象的屬性賦值: new Student("001", "張三", 18)
。每次 new
就會創建新的對象,每個對象是獨立的,所以上面張三和李四對象是獨立的兩個對象,修改其中一個對象的值,不會影響另一個對象。
執行結果:
我是張三, 我在學習
我是李四, 我在學習
4 this的作用
在上面的代碼中用到了 this
,因為上面我們寫構造方法的時候,形參和類的屬性名相同(當然也可以不同),導致在構造方法中,使用 sid/name/age
無法訪問到類的屬性,使用this,就表示訪問的是屬性 。
其實 this
表示的是調用當前方法的對象。如何理解?
舉個栗子:
下面的代碼,我們定義了學生類,創建了兩個學生的對象。
/*** 定義類*/
class Student {public String sid;public String name;public int age;/*** 構造方法*/public Student(String sid, String name, int age) {this.sid = sid; // this.sid 訪問的是屬性,sid 是方法的形參this.name = name;this.age = age;}public void study() {System.out.println("我是" + this.name + "我" + age +"歲了" + ", 我在學習");}
}/*** 測試類*/
public class Test{public static void main(String[] args) {// 創建張三Student stu1 = new Student("001", "張三", 18);stu1.study();// 創建李四Student stu2 = new Student("002", "李四", 19);stu2.study();}
}
執行結果:
我是張三, 我18歲了, 我在學習
我是李四, 我19歲了, 我在學習
當我們使用 stu1
調用 study()
方法的時候,this
就是指 張三
(stu1)這個對象,那么 this.name
的值就是張三;當我們使用 stu2
調用 study()
方法的時候,this
就是指 李四
(stu2) 這個對象,那么 this.name
的值就是李四。this
就是調用當前方法的對象。
5 對象內存解析
先查看下面的代碼:
Student stu1 = new Student(); // 創建張三對象
stu1.sid = "001";
stu1.name = "張三";
stu1.age = 18;Student stu2 = new Student(); // 創建李四對象
stu2.sid = "002";
stu2.name = "李四";
stu2.age = 19;Student stu3 = stu1;
stu3.name = "王五"; System.out.println(stu1.name); // 王五
System.out.println(stu2.name); // 李四
System.out.println(stu3.name); // 王五
在上面的代碼中,首先創建了兩個對象賦值給了 stu1 和 stu2,并對屬性進行賦值,然后定義了一個 stu3 的變量,將 stu1 賦值給了 stu3,我們前面說過 stu1 和 stu2 是獨立的兩個對象,那么 stu1 和 stu3 是獨立的兩個對象嗎?兩個是同一個對象。下面畫一下內存示意圖:
創建完 stu1 并賦值后,內存結構如下(其實字符串是不保存在堆或棧中的,這里簡化了):
創建完 stu2 并賦值后,內存結構如下:
Student stu3 = stu1
執行完,內存結構如下:
stu3.name = "王五";
執行完成,內存結構如下:
所以只在 new 的時候才會創建新的對象,stu3 = stu1
只是將 stu1 執行的對象的引用賦值給了 stu3 。
6 靜態變量
上面我們定義的屬性和方法,是實例變量和實例方法,也叫成員變量和成員方法。
實例變量對于每個實例而言,是獨立的數據,每個對象之間相互不會影響。創建一個對象,就會開辟獨立的內存空間保存對象的實例變量數據。但是無論創建多少對象,實例方法只有一份,所有對象共享,通過 this
,來確定是哪個對象調用了實例方法。
在類中還可以定義各個對象共享的數據,也就是靜態變量。
打個比方,我們定義了一個 Student 類,然后通過 Student 類來創建對象,我們想知道一共創建了多少個 Student 對象,應該如何操作呢?
我們可以通過定義 靜態變量 來實現,靜態變量和成員變量的區別就是:前面通過 static
關鍵字來修飾。
/*** 定義類*/
class Student {public static int stuCount = 0; // 定義一個靜態變量,用于記錄創建的對象個數public String sid;public String name;public int age;/*** 構造方法*/public Student(String sid, String name, int age) {stuCount++; // 創建對象會調用構造方法,調用一次就+1this.sid = sid; // this.sid 訪問的是屬性,sid 是方法的形參this.name = name;this.age = age;}
}/*** 測試類*/
public class Test{public static void main(String[] args) {Student stu1 = new Student("001", "張三", 18);Student stu2 = new Student("002", "李四", 19);Student stu3 = new Student("003", "王五", 20);// 通過類名訪問System.out.println(Student.stuCount); // 輸出: 3// 通過對象也可以訪問System.out.println(stu1.stuCount); // 輸出: 3System.out.println(stu2.stuCount); // 輸出: 3System.out.println(stu3.stuCount); // 輸出: 3}
}
在上面的代碼中,我們定義了一個類,然后在類中定義了一個 stuCount
靜態變量。當創建對象的時候,會調用構造方法,我們在構造方法中將 stuCount++
,這樣就可以記錄調用構造方法的次數。
靜態變量用來定義那些所有對象共享的數據。
**靜態變量是屬于類的,而不是屬于類的實例。在類的方法中,如果沒有局部變量和靜態變量重名,那么可以直接使用靜態變量,就像上面在構造方法中一樣,如果有局部變量和靜態變量重名,可以使用 類名.靜態變量
來訪問。 **
在類外,像上面在 Test 類中訪問 Student 類中的靜態變量,那么可以通過 類名.靜態變量
來賦值和訪問,也可以通過 對象.靜態變量
來訪問,但是推薦使用 類名.靜態變量
。
7 靜態方法
除了靜態變量,還有靜態方法。靜態變量也是屬于類的,而不是屬于類的實例。
靜態方法通過 static
關鍵字來修飾的方法,之前在學習方法的時候,我們定義的全是靜態方法。
/*** 定義類*/
class Student {public static int stuCount = 0; // 定義一個靜態變量,用于記錄創建的對象個數public String sid;public String name;public int age;/*** 構造方法*/public Student(String sid, String name, int age) {stuCount++; // 創建對象會調用構造方法,調用一次就+1this.sid = sid; // this.sid 訪問的是屬性,sid 是方法的形參this.name = name;this.age = age;}public void study() {System.out.println("我正在學習");}/*** 定義靜態方法*/public static void getStuCount() {System.out.println("一共創建了" + stuCount + "個學生");// System.out.println(name); // 靜態方法中無法訪問成員變量// study(); // 靜態方法中無法調用成員方法}
}/*** 測試類*/
public class Test{public static void main(String[] args) {// 創建張三new Student("001", "張三", 18);// 創建李四new Student("002", "李四", 19);// 創建王五new Student("003", "王五", 20);Student.getStuCount(); // 輸出: 一共創建了3個學生}
}
在靜態方法不能訪問成員變量和成員方法,因為靜態方法是通過 類.靜態方法()
調用的,不是通過對象實例來調用的,所以如果在靜態方法中調用的成員變量沒法確定是哪個實例的變量。但是在成員方法中是可以訪問靜態變量和靜態方法的。
靜態方法一般用來定義一些工具類,例如定義一個字符串的工具類:
public class StringUtils {/*** 判斷字符串是否為空*/public static boolean isEmpty(String arg) {// trim()方法是去掉字符串的前后空格return (null == arg || arg.trim().length() < 1);}/*** 判斷字符串是否不為空*/public static boolean isNotEmpty(String arg) {return !isEmpty(arg);}// ...定義其他的工具方法
}
定義完工具類,我就可以在其他的類中,通過 StringUtils.isEmpty("字符串")
來調用這個工具類中的方法了。
8 局部變量和全局變量
變量的作用域也就是在哪里可以訪問到這個變量。按照作用域的不同,變量可分為 局部變量 和 全局變量。
什么是局部變量?
局部變量就是在方法中(包括方法的參數)或代碼塊中(例如for循環中定義的變量)定義的變量,這些變量只能在該方法中進行訪問,其他方法中無法訪問。
什么是全局變量?
全局變量就是類的屬性,這些變量可以在多個方法中進行訪問。
局部變量和全局變量除了作用域不同,還有一些地方也不同:
- 權限修飾符:全局變量是類的屬性,可以添加權限修飾符,我們目前只使用了
public
,局部變量沒有權限修飾符,關于權限修飾符后面再講解; - 初始化:全局變量有初始化值,而局部變量在使用前,需要自己進行初始化,否則無法使用;
- 內存中的位置:全局變量是保存在堆中的,而局部變量(非static,static是保存在虛擬機的方法區中的)是保存在棧中的。
關于全局變量的初始化值,在這里舉個栗子:
/*** 定義類*/
class DataClass {public byte byteValue;public short shortValue;public int intValue;public long longValue;public float floatValue;public double doubleValue;public boolean booleanValue;public char charValue;public String stringValue;
}/*** 測試類*/
public class Test{public static void main(String[] args) {DataClass data = new DataClass();System.out.println(data.byteValue); // 0System.out.println(data.shortValue); // 0System.out.println(data.intValue); // 0System.out.println(data.longValue); // 0System.out.println(data.floatValue); // 0.0System.out.println(data.doubleValue); // 0.0System.out.println(data.booleanValue); // falsechar c = 0;System.out.println(data.charValue == c);// trueSystem.out.println(data.stringValue); // null}
}
在上面的代碼中,我們并沒有對類的屬性進行初始化,但是類中的屬性會默認有初始化值。
byte、short、int、long、char
默認初始化值為 0
,float、double
默認初始化值為 0.0
,boolean
默認初始化值為 false
,引用類型
的默認初始化值為 null
。
9 構造方法的重載
如果一個類中,沒有顯式的構造方法,那么會有一個隱式的無參構造方法。
class Student {public String sid;public String name;public int age;
}public class ObjectTest {public static void main(String[] args) {// 這里的Student()調用的就是默認隱式的構造方法Student stu = new Student();}
}
此時創建 Student 類對象,就是調用的隱式無參構造方法。當然,我們也可以手動寫一個無參構造方法。
class Student {public String sid;public String name;public int age;public Student() {System.out.println("無參構造方法");}
}public class ObjectTest {public static void main(String[] args) {Student stu = new Student(); // 打印:無參構造方法}
}
當我們手動寫了顯式的無參構造方法,那么就會調用這個顯式的無參構造方法了。
如果我們顯式的寫了有參的構造方法,那么隱式的無參構造方法就沒有了,所以在創建對象的時候,就必須傳遞參數。
舉個栗子:
class Student {public String sid;public String name;public int age;public Student(String sid, String name, int age) {this.sid = sid;this.name = name;this.age = age;}
}public class ObjectTest {public static void main(String[] args) {// Student stu = new Student(); // 報錯,沒有無參構造方法了Student stu = new Student("001", "ZhangSan", 18);}
}
當然構造方法也是可以重載的,我們可以定義多個構造方法。
class Student {public String sid;public String name;public int age;public Student() {System.out.println("無參構造方法");}public Student(String sid, String name) {System.out.println("有參構造方法: sid, name");this.sid = sid;this.name = name;}public Student(String sid, String name, int age) {System.out.println("有參構造方法: sid, name, age");this.sid = sid;this.name = name;this.age = age;}
}public class ObjectTest {public static void main(String[] args) {Student stu1 = new Student();Student stu2 = new Student("001", "ZhangSan");Student stu3 = new Student("001", "ZhangSan", 18);}
}
執行結果:
無參構造方法
有參構造方法: sid, name
有參構造方法: sid, name, age
那么如何在一個構造方法中調用另一個構造方法呢?
使用 this([參數])
來調用,舉個栗子:
class Student {public String sid;public String name;public int age;public Student() {System.out.println("無參構造方法");}public Student(String sid, String name) {this(); // 調用無參構造方法System.out.println("有參構造方法: sid, name");this.sid = sid;this.name = name;}public Student(String sid, String name, int age) {this(sid, name); // 調用其他的構造方法System.out.println("有參構造方法: sid, name, age");this.sid = sid;this.name = name;this.age = age;}
}public class ObjectTest {public static void main(String[] args) {Student stu1 = new Student();Student stu2 = new Student("001", "ZhangSan");Student stu3 = new Student("001", "ZhangSan", 18);}
}
需要注意,使用 this([參數]) 調用其他的構造方法,這句代碼必須放在構造方法的第一句。
執行結果:
無參構造方法
無參構造方法
有參構造方法: sid, name
無參構造方法
有參構造方法: sid, name
有參構造方法: sid, name, age
10 代碼塊
在類中還有代碼塊,代碼塊的主要作用也是進行一些初始化的工作。
代碼塊有靜態代碼塊和非靜態代碼塊。
靜態代碼塊主要用于 初始化類的靜態變量 或執行只需在類加載時運行一次的代碼。
非靜態代碼塊主要是對成員變量進行初始化工作。
舉個栗子:
class Student {// 靜態變量public static int staticValue;// 成員變量public int value;// 靜態代碼塊static {staticValue = 10;System.out.println("執行靜態代碼塊");}// 非靜態代碼塊{value = 20;System.out.println("執行非靜態代碼塊");}// 構造方法public Student() {System.out.println("執行構造方法");}
}/*** 測試類*/
public class ObjectTest {public static void main(String[] args) {new Student();new Student();}
}
執行結果:
執行靜態代碼塊
執行非靜態代碼塊
執行構造方法
執行非靜態代碼塊
執行構造方法
從執行結果可以看出,靜態代碼塊只會在類加載的時候執行一次,后面不會再執行了。靜態代碼塊在每次創建對象的時候都會執行,而且會在構造方法之前執行。在代碼塊中也是可以調用類中的方法的,當然靜態代碼塊只能調用靜態方法。
靜態代碼塊可以在需要進行一些邏輯處理后,再初始化靜態變量的時候會用到,非靜態代碼塊用的不多,一般使用構造方法就可以完成相同的功能。
需要注意,代碼塊和屬性的顯式賦值是同級的。誰寫在后面,誰后生效。
舉個栗子:
class Student {// 靜態變量public static int staticValue = 20;// 靜態代碼塊static {staticValue = 10;}
}class Teacher {// 靜態代碼塊static {staticValue = 10;}// 靜態變量public static int staticValue = 20;
}/*** 測試類*/
public class ObjectTest {public static void main(String[] args) {System.out.println(Student.staticValue); // 10System.out.println(Teacher.staticValue); // 20}
}
上面的 Student 和 Teacher 兩個類中的靜態變量,分別使用了顯式賦值和靜態代碼塊賦值,可以看到它們是同級的,誰在后面誰后執行,覆蓋前面的初始化值。
11 屬性的初始化順序
類中的屬性,有靜態變量和成員變量,我們可以不初始化,會有默認值,也可以顯式的進行初始化,也可以在靜態代碼塊中對靜態變量進行初始化,也可以構造方法中進行初始化。
這么多地方可以對屬性進行初始化,那么初始化順序是怎么樣的呢?
- 靜態變量默認初始化;
- 靜態變量顯式初始化;
- 靜態代碼塊初始化;
- 成員變量默認初始化;
- 成員變量顯式初始化;
- 構造方法中初始化;