一、 類和對象
1.1 面向對象與面向過程的區別
1.面向過程
採用面向過程必須瞭解整個過程,每個步驟都有因果關係,每個因果關係都構成了一個步驟,多個步驟就構成了一個系統,因爲存在因果關係每個步驟很難分離,非常緊密,耦合度高,當任何一步驟出現問題,將會影響到所有的系統。如:採用面向過程生產電腦,那麼他不會分CPU、主板和硬盤,它會按照電腦的工作流程一次成型。
2.面向對象
面向對象對會將現實世界分割成不同的單元(對象),實現各個對象,如果完成某個功能,只需要將各個對象協作起來就可以。
1.2 面向對象的三大特徵
1. 封裝
2. 繼承
3. 多態
1.3 類與對象的概念
類是對具有共性事物的抽象描述,是在概念上的一個定義,那麼如何發現類呢?通常根據名詞(概念)來發現類,如在成績管理系統中:學生、班級、課程、成績
學生—張三
班級—Java1059
課程—JavaSE
成績—張三成績
以上“張三”、“Java1059”、“JavaSE”和“張三的成績”他們是具體存在的,稱爲對象,也叫實例,就是說一個類的具體化(實例化),就是對象或實例。
爲什麼面向對象成爲主流技術,主要就是因爲更符合人的思維模式,更容易的分析現實世界,所以在程序設計中也採用了面嚮對象的技術,從軟件的開發的生命週期來看,基於面向對象可以分爲三個階段:
OOA(面向對象的分析) Object-OrientedAnalysis
OOD(面向對象的設計) Object-OrientedDesign
OOP(面向對象的編程) Object Oriented Programming
Java 就是一個純面向對象的語言
我們再進一步的展開,首先看看學生:
學生:學號、姓名、性別、地址,班級
班級:班級代碼、班級名稱
大家看到以上我們分析出來的都是類的屬性
接下來採用簡易的圖形來描述一下,來描述我們的概念
通過以上分析,大家應該瞭解:
類=屬性+方法
屬性來源於類的狀態,而方法來源於動作
以上模型完全可以使用面向對象的語言,如Java 來實現
1.1 如何定義一個類
在Java中如何定義一個類!格式如下。
[類的訪問修飾符] class 類名 extends 父類名稱 implements 接口名稱{ 類體:包括下面兩方面
屬性
方法 } |
定義學生類型Student,是對現實中的學生的一種抽象,是不存在的,是概念上的定義。注意:Student是一個類,是引用類型。
public class Student{
// 屬性 // 學生學號 // 成員變量,非靜態變量 // 成員變量是對象級別的,必須先存着對象纔有意義,才能訪問 // 成員變量不能使用類名.的方式進行訪問 int id; // 學生姓名 String name; // 學生性別 String sex; // 學生年齡 int age; // 家庭住址 String address;
// 方法 public void study(){ System.out.println("學習Java "); } } |
1.2 如何創建一個對象
在Java中如何創建一個對象呢!必須使用new關鍵字,當對象創建完成後,對於成員變量或成員方法纔可以訪問或調用,下面的例子。
public class ClassTest01 {
public static void main(String[] args){ // 創建對象 // 注意:stu1對象是局部變量,是Student01類型的局部變量 // 變量stu1不是基本數據類型,是引用類型 // 引用類型保存的是對象在堆內存中的地址 // 我們通過這個引用來訪問堆內存中的對象 Student01 stu1 = new Student01(); // 一個類可以創建多個對象 // 可以說,所有的學生都可以是Student01類的對象 Student01 stu2 = new Student01();
// 使用對象訪問成員屬性 // 訪問成員屬性,必須使用引用.的方式進行訪問 // 注意:成員變量不能採用類名.的方式進行訪問 System.out.println("id: " + stu1.id); System.out.println("name: " + stu1.name); System.out.println("age: " + stu1.age); System.out.println("sex: " + stu1.sex); System.out.println("address: " + stu1.address);
// 使用對象爲成員變量進行賦值 // 成員變量的讀取和賦值都是採用引用.的方式 stu1.id = 1001; stu1.name = "張三"; stu1.age = 21; stu1.sex = "男"; stu1.address = "太原小店區";
// 通過對象讀取成員變量的值 System.out.println("id: " + stu1.id); System.out.println("name: " + stu1.name); System.out.println("age: " + stu1.age); System.out.println("sex: " + stu1.sex); System.out.println("address: " + stu1.address); } }
class Student01{ int id;
String name;
int age;
String sex;
String address; } |
一個類可以創建多個個對象,成員變量只屬於當前的對象(每個對象擁有自己的成員屬性,所以成員屬性只屬於特定的對象,不屬於類),所以只有通過對象纔可以訪問成員變量,通過類不能直接訪問成員變量。
注意:stu1只是引用,指向堆內存中對象的地址,我們只能通過這個引用對內存中的對
象進行訪問。每一個對象會在內存中開創一塊空間,每個對象的屬性也只能屬於自己的
對象,所以成員變量是屬於對象的或實例的。
上面程序存在缺點,所有的成員屬性全部公開,如:年齡可以賦值爲負數!如何才能控制成員屬性不能隨意的賦值!
1.1 面向對象的封裝性
如何對成員屬性的值進行保護和限制,使成員變量的更安全!通過下面的例子說明。
public class ClassTest02 {
public static void main(String[] args){ // 創建對象 Student02 stu1 = new Student02(); // 讀取成員變量 System.out.println("age: " + stu1.age);
// 使用對象爲成員變量進行賦值 //stu1.age = 21; // 在程序中對age屬性沒有任何限制,外部程序可以隨意訪問屬性age, // 導致age不安全。這種可以隨意修改或賦值不合法的數據可能造成程序的 // 安全隱患,如何纔能有效的限制不合法的數據呢! stu1.age = -1;
// 通過對象讀取成員變量的值 System.out.println("age: " + stu1.age); } }
class Student02{ int id;
String name;
int age;
String sex;
String address; } |
在上面的例子中可以看到,程序中沒有對成員屬性進行任何的保護,可以隨意的訪問,並且將非法的值隨意的賦給成員變量,這可能造成整個程序的執行結果發生錯誤。如何才能對成員變量進行保護呢!可以爲每個成員變量的訪問權限進行限制,在每個成員屬性前加上訪問修飾符private,這樣就只有在本類中纔可以訪問成員屬性,類的外部是不能直接對成員屬性進行訪問的,這樣成員屬性就受到保護了,如下例子。
public class ClassTest03 {
public static void main(String[] args){ // 創建對象 Student03 stu1 = new Student03(); // 讀取成員變量 // 編譯不能通過,成員屬性訪問受到限制 // System.out.println("age: " + stu1.age);;
// 使用對象爲成員變量進行賦值 // 編譯不能通過,成員屬性訪問受到限制 // stu1.age = -1;
// 通過對象讀取成員變量的值 // 編譯不能通過,成員屬性訪問受到限制 // System.out.println("age: " + stu1.age); } }
class Student03{ private int id;
private String name;
private int age;
private String sex;
private String address; } |
通過對屬性訪問進行限制,在類的外部不能直接操作成員屬性了,成員屬性更安全了,但是,如果不能對成員屬性進行訪問,實際上這個類對其他類來說就無意義了,所以,既能訪問成員變量,又能對成員屬性進行保護,如何才能實現呢!那就是封裝性的第二個辦法了,爲每個成員屬性創建兩個公共的方法,一個可以爲成員屬性進行賦值,另一個可以獲取成員屬性的值,如下例子。
public class ClassTest04 {
public static void main(String[] args){ // 創建對象 Student04 stu1 = new Student04();
// 使用對象爲成員變量進行賦值 stu1.setId(1001); stu1.setName("張三"); stu1.setAge(-21); stu1.setSex("男"); stu1.setAddress("太原小店區");
// 通過對象讀取成員變量的值 System.out.println("id: " + stu1.getId()); System.out.println("name: " + stu1.getName()); System.out.println("age: " + stu1.getAge()); System.out.println("sex: " + stu1.getSex()); System.out.println("address: " + stu1.getAddress()); } }
class Student04{ private int id;
private String name;
private int age;
private String sex;
private String address;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { if(age >= 0 && age <= 100){ this.age = age; }else{ System.out.println("非法數據!"); this.age = 0; } }
public String getSex() { return sex; }
public void setSex(String sex) { this.sex = sex; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; } } |
從上面的示例可以看出,採用方法可以控制賦值的過程,加入了對年齡的檢查,避免了外部直接操縱成員屬性,這就是封裝,封裝其實就是封裝屬性,讓外界知道這個類的狀態越少越好。屬性只能通過方法訪問,在方法中對屬性的值進行限制就是封裝。通過方法我們就可以控制對內部狀態的讀取權利。封裝屬性,公開方法。所以,將屬性私有化,並且提供公共的方法對屬性進行訪問就是Java的封裝。
提供的方法有setter和getter兩種,setter方法是給屬性賦值的,而getter方法是取得屬性值的。關於這兩個方法是有規範的,setter方法是:set + 屬性名稱(第一個字母大寫)。getter方法是:get + 屬性名稱(第一個字母大寫)。
1.1 類的構造函數
構造函數也稱作構造方法或構造器,英文爲Constructor。構造方法也是一個類的組成部分,語法格式如下。
[方法訪問修飾符列表] 構造方法名(形式參數列表){ 方法體; } |
如何調用構造方法!
只能通過new關鍵字調用構造方法,調用會創建類的實例,過程是在堆內存開闢空間並保存類的實例。
回顧:靜態方法必須使用類名.的方式進行調用。
成員方法必須使用引用.的方式進行調用。
回顧:靜態方法必須使用類名.的方式進行調用。
成員方法必須使用引用.的方式進行調用。
第一個例子:創建一個類的實例或對象時實際上是調用了類的構造方法。
public class ClassTest04 {
public static void main(String[] args){ // 創建對象 Student04 stu1 = new Student04();
} }
class Student04{ private int id;
private String name;
//構造方法 public Student04() { System.out.println("創建一個類的" + "實例或對象時實際上是調用了類的構造方法"); }
}
|
構造方法修飾詞列表:public、protected和private。
第二個例子:構造方法的訪問修飾符的調用限制,public和private的區別。
public class ClassTest05 {
public static void main(String[] args){ // 創建對象 Student04 stu1 = new Student04();
//不能編譯通過,構造方法被private修飾訪問不到 //Student05 stu2 = new Student05(); } }
class Student04{ private int id;
private String name;
//構造方法 public Student04() { System.out.println("構造方法被public修飾"); }
}
class Student05{ private int id;
private String name;
//構造方法 private Student05() { System.out.println("構造方法被private修飾"); }
} |
構造方法和普通方法一樣可以重載。因爲形式參數列表可以有零到多個參數。
第三個例子:構造方法可以重載,可以爲不同的成員屬性進行賦初值。
public class ClassTest06 {
public static void main(String[] args){ // 創建對象 Student06 stu1 = new Student06(); Student06 stu2 = new Student06(1001,"張三"); } }
class Student06{ private int id;
private String name;
//構造方法 public Student06() { System.out.println("構造方法被public修飾"); }
//含參數的構造方法 public Student06(int id, String name) { this.id = id; this.name = name; } }
|
構造方法的特點:
1. 構造方法的名稱必須和類名相同。
2. 構造方法不能存在返回值。構造方法不具有任何返回值類型,即沒有返回值,關鍵字void也不能存在,如果存在void關鍵字就不是構造方法了,就是普通方法了。
3. 任何類都有構造函數,如果一個類沒有顯示的定義構造函數,系統會爲該類定義一個默認的構造器,這個構造器不含任何參數(無參的構造函數),如果在類中顯示的定義了構造器(有參數的或無參數的構造方法),系統就不會再創建默認的無參構造器,如果需要必須自己創建。通常情況下,如果在類中手動添加了帶參數的構造函數,那麼也會手動的添加一個無參數的構成方法。
第四個例子:如果類沒有顯示的構造方法,系統會默認創建一個無參數的構造方法,如果顯示的創建了一個構造方法,系統則不會在創建默認的無參數構造方法。
構造方法的作用是什麼!
1. 創建對象。
2. 創建對象時爲成員屬性賦初值。
成員屬性的賦值時機!
成員屬性聲明後不用手動賦初值既可以使用,因爲成員屬性必須實例進行調用,在創建實例或對象時,必須通過new 構造方法()的方式創建對象,系統會爲成員屬性賦初值。
如果調用無參數的構造方法,系統會爲成員屬性賦默認值,也可以通過帶參數的構造方法在創建對象的時候爲成員變量賦初值。
第五個例子:通過構造函數爲成員屬性進行賦初值。
1.1 類的實例和引用
1.1.1 Java中的內存概述
Java虛擬機啓動後的內存概述。
1.1.1 Java實例的創建
Student類:
public class Student {
private String id;
private String name;
private int age;
private String sex;
public static void main(String[] args){ //創建st對象 Student st = new Student(); //st對象成員變量賦值 st.id = "1001"; st.name = "張三"; st.age = 23; st.sex = "男"; } } |
第一步,JVM調用執行main 方法,將main 方法壓入棧,然後new Student 對象
//創建一個對象 Student st = new Student(); |
第二步,對st實例的屬性進行賦值
st.id = "1001"; st.name = "張三"; st.age = 23; st.sex = "男"; |
1.1.1 實例引用爲null時出現情況
一個類的實例必須通過new關鍵字創建後才能訪問成員屬性或成員方法,如果對象沒有創建即調用成員,會出現異常現象,下面通過例子說明。
第六個例子:如果不使用new關鍵字創建對象即使用引用.的方式訪問成員屬性或成員方法出現的問題。
注意:當一個對象沒有引用指向他,則這個在內存中的對象將成爲垃圾,等待垃圾回收器回收。
內存說明:
1.1 參數傳遞
在程序執行過程中可以進行參數的傳遞,參數類型分爲基本數據類型和引用類型,下面分別進行說明。
1.1.1 參數類型爲基本數據類型
參數的類型是基本數據類型,那麼傳遞的是基本數據類型的值。形參的變量是局部變量,那麼傳遞的值就賦給這個局部變量,在方法中對這個局部變量如何操作都不會影響都調用處變量的值。下面通過一個例子說明。
例1:ParameterTest01
public class ParameterTest01 {
public static void main(String[] args){
int i = 10; add(i);
System.out.println("調用處局部變量i的值:" + i); }
public static void add(int i){ i *= 2; System.out.println("局部變量i的值: " + i); } } |
值傳遞:
1.1.1 參數類型爲引用數據類型
參數是引用類型,傳遞的是對象的引用,也就是對象在堆內存中的地址。那麼通過參數操作實際上是操作的是內存中的對象。下面通過一個例子說明。
例2:ParameterTest02
public class ParameterTest02 {
public static void main(String[] args){
Student stu = new Student("張三",20); modify(stu);
System.out.println("學生姓名:" + stu.name + " 年齡:" + stu.age); }
public static void modify(Student stu){ stu.name = "李四"; stu.age = 23; } }
class Student{
public String name;
public int age;
public Student(String name,int age){ this.name = name; this.age = age; } } |
通過一張圖說明:
引用類型的參數傳遞的是對象在內存中的地址,實際上操作的是內存中的對象,所以對象的屬性值被modify()方法改變了。
1.1 Java中的this關鍵字
this關鍵字在Java的類中代表什麼含義!
1.this是一個引用,指向當前對象在內存中的引用(地址)。就是當前對象。
2.創建的每一個Java對象中都有一個this引用。
3.this引用保存的是當前對象在內存中的地址,每一個Java對象中都有一個this引用指向對象自身。
第一個例子:創建的對象在內存中,每個對象中都有一個this引用保存的是當前對象的內存地址。
public class Student {
private String id;
private String name;
private int age;
private String sex;
public static void main(String[] args){ //創建str1對象 Student str1 = new Student(); // str1對象成員變量賦值 str1.id = "1001"; str1.name = "張三"; str1.age = 23; str1.sex = "男";
//創建str2對象 Student str2 = new Student(); // str2對象成員變量賦值 str2.id = "1002"; str2.name = "李四"; str2.age = 22; str2.sex = "女"; } } |
用圖說明:
this關鍵字能用在哪些地方!
1.this可以用在成員方法中,代表當前對象。this保存當前對象的引用,指向當前對象在內存中的地址。
第二個例子:this關鍵字用在成員方法中。(this關鍵字不能用在靜態方法中)
public class ClassTest06 {
public static void main(String[] args){ // 創建對象 Student06 stu1 = new Student06(); Student06 stu2 = new Student06(1001,"張三"); stu2.method(); } }
class Student06{ private int id;
private String name;
//構造方法 public Student06() {
}
//含參數的構造方法 public Student06(int id, String name) { this.id = id; this.name = name; }
//成員方法 public void method(){ this.id = 8888; System.out.println(id); } }
|
第三個例子:this關鍵字可以區分成員屬性和局部變量。
public class ClassTest06 {
public static void main(String[] args){ // 創建對象 Student06 stu1 = new Student06(); stu1.method(); } }
class Student06{ private int id;
private String name;
//構造方法 public Student06() {
}
//成員方法 public void method(){ String name = "局部變量"; this.name = "成員屬性"; System.out.println("name:" + name); System.out.println("this.name" + this.name); } } |
第四個例子:this關鍵字不能使用在靜態方法中,因爲靜態方法的執行根本不需要Java對象的存在。直接使用類名.的方式訪問。而this代表的是當前對象。所以在靜態方法中根本就沒有當前對象。
2.this可以用在構造方法中,用來調用當前類中其他的構造函數。
語法:this(實參);
通過在構造方法調用當前類中另一個構造方法來實現代碼重用。
注意:通過this(實參);調用當前類中其他構造函數的語句必須出現在構造方法的第一
行。否則編譯不能通過。
第五個例子:this關鍵字用在構造方法中用來調用其他的構造方法,好處是代碼可以重用。但是要注意,如果在構造函數中通過this調用其他的構造函數,this語句必須是構造方法中的第一行。
public class ClassTest06 {
public static void main(String[] args){ // 創建對象 Student06 stu1 = new Student06(); System.out.println(stu1.id + stu1.name); } }
class Student06{ int id;
String name;
//構造方法 public Student06() { this(1001,"張三"); } //帶參構造 public Student06(int id, String name) { this.id = id; this.name = name; } }
|
1.1 Java中的static關鍵字
在Java中,static修飾符可以修飾:變量、方法和代碼塊。
1.static修飾的變量叫做靜態變量。
2.static修飾的方法叫做靜態方法。
3.static還可以定義靜態語句塊。
1.1.1 靜態語句塊
用static 修飾的變量和方法,可以採用類名.的方式進行訪問。
用static聲明的代碼塊爲靜態代碼塊,執行時機是類加載階段,在程序入口程序main()方法執行之前執行,只執行一次,是自上而下的順序執行(常用於初始化工作,創建對象)。
靜態語句塊的語法格式:
static{ 語句塊; } |
第一個例子:創建靜態語句塊,靜態語句塊在類加載時執行,只執行一次。
因爲靜態變量、靜態語句塊在類加載階段執行,所以,變量和語句塊聲明的順序是有關係的。
第二個例子:靜態變量和靜態語句塊的聲明順序
1.1.2 實例語句塊
實例語句塊,每一次調用構造方法之前會執行一次。實例語句塊執行順序也是自上而下。
實例語句塊語法格式:
{ 語句塊; } |
第三個例子:創建實例語句塊。
1.1.3 靜態方法
static修飾的方法叫做靜態方法,也成爲類方法。通常情況下工具類中的方法大部分都是靜態方法,不需要創建對象既可以直接以類名.的方式進行訪問,不需要創建對象。
第三個例子:靜態方法使用類名.的方式進行訪問,靜態方法中可以直接調用靜態屬性,靜態方法中不能訪問成員,如果要訪問成員需要創建實例,所以在靜態方法中沒有this的概念。
1.1.4 靜態變量
static修飾的變量叫做靜態變量,也成爲類變量。
在什麼情況下將變量聲明成靜態變量!如果這個屬性所有的對象都有,並且這個屬性的值是相同的,具有共性的屬性值的屬性,則該屬性聲明成靜態的屬性。
第四個例子:靜態變量在方法區,不在堆區。所有的對象共享。
成員變量在創建Java對象的時候初始化。而靜態變量在類加載階段賦值,並且只賦值一次。因爲只保存一份。
1.1.5 靜態變量和靜態塊的執行順序
在Java中,只有靜態變量和靜態塊在代碼中是有順序的,成員方法是沒有順序的。
第五個例子:靜態屬性和靜態塊的執行順序問題。
總結:我們已經知道在一個類中,可以存在靜態方法、靜態變量、成員方法、成員屬性、構造方法、靜態語句塊、實例語句塊和this關鍵字。
1.2 單例模式
單例模式是種設計模式中最簡單的一種設計模式。是較爲常見的一種設計模式,單例模式就是在任何時候都保證某個Java類的實例只有一個,主要是爲了節省內存空間。通常情況下,工具類一般採用單例模式,就一個對象,所有對這個工具類中的成員方法的調用都是通過這個唯一的實例調用的。
如何實現單例模式!實現單例模式的原則:
1.構造方法私有化。
2.對外提供一個公開的靜態的獲取當前類型對象的方法。
3.提供一個當前類型的私有的靜態變量。
單例模式分爲兩種模式:
餓漢式單例:在類加載階段就創建了對象。
懶漢式單例:用到對象的時候纔會創建對象。
第一個例子:創建一個餓漢式單例模式。
package weixin911; /* * 單例模式:保證類在內存中只有一個對象。 * * 如何保證類在內存中只有一個對象呢? * A:把構造方法私有 * B:在成員位置自己創建一個對象 * C:通過一個公共的方法提供訪問 */ public class StudentDemo { //餓漢式 public static void main(String[] args) { // Student s1 = new Student(); // Student s2 = new Student(); // System.out.println(s1 == s2); // false
// 通過單例如何得到對象呢?
// Student.s = null;
Student s1 = Student.getStudent(); Student s2 = Student.getStudent(); System.out.println(s1 == s2);
System.out.println(s1); // null,cn.itcast_03.Student@175078b System.out.println(s2);// null,cn.itcast_03.Student@175078b } }
class Student { private Student(){}//構造方法私有化
//成員位置自己造一個對象 private static Student s = new Student();
//提供公共的外界訪問方式 public static Student getStudent(){ return s; } }
|
第二個例子:創建一個懶漢式單例模式。
/* * 單例模式: * 餓漢式:類一加載就創建對象 * 懶漢式:用的時候,纔去創建對象 * * 面試題:單例模式的思想是什麼?請寫一個代碼體現。 * * 開發:餓漢式(是不會出問題的單例模式) * 面試:懶漢式(可能會出問題的單例模式) * A:懶加載(延遲加載) * B:線程安全問題 * a:是否多線程環境是 * b:是否有共享數據是 * c:是否有多條語句操作共享數據 是 */ public class TeacherDemo {
public static void main(String[] args) { Teacher t1 = Teacher.getTeacher(); Teacher t2 = Teacher.getTeacher(); System.out.println(t1 == t2); System.out.println(t1); // cn.itcast_03.Teacher@175078b
} } class Teacher{ private Teacher(){};
private static Teacher t = null;
public synchronized static Teacher getTeacher(){ if(t == null){ t = new Teacher(); } return t; } } |