帶你入門Java的類與對象
類和對象
在面向對象中,類和對象是最基本、最重要的組成單元。類實際上是表示一個客觀世界某類群體的一些基本特征抽象。對象就是表示一個個具體的東西。所以說類是對象的抽象,對象是類的具體。
“人類”隻是一個抽象的概念,它僅僅是一個概念,是不存在的實體!但是所有具備“人類”這個群體的屬性與方法的對象都叫人!這個對象“人” 是實際存在的實體!每個人都是“人”這個群體的一個對象。
類的屬性
在 Java 中類的成員變量定義瞭類的屬性。聲明成員變量的語法如下:
[public|protected|private][static][final]<type><variable_name>
各參數的含義如下。
public、protected、private
:用於表示成員變量的訪問權限。static
:表示該成員變量為類變量,也稱為靜態變量。final
:表示將該成員變量聲明為常量,其值無法更改。type
:表示變量的類型。variable_name
:表示變量名稱。
可以在聲明成員變量的同時對其進行初始化,如果聲明成員變量時沒有對其初始化,則系統會使用默認值初始化成員變量。
- 初始化的默認值如下:
- 整數型(byte、short、int 和 long)的基本類型變量的默認值為 0。
- 單精度浮點型(float)的基本類型變量的默認值為 0.0f。
- 雙精度浮點型(double)的基本類型變量的默認值為 0.0d。
- 字符型(char)的基本類型變量的默認值為 “\u0000”。
- 佈爾型的基本類型變量的默認值為 false。
- 數組引用類型的變量的默認值為 null。如果創建瞭數組變量的實例,但沒有顯式地為每個元素賦值,則數組中的元素初始化值采用數組數據類型對應的默認值。
成員方法
聲明成員方法可以定義類的行為,行為表示一個對象能夠做的事情或者能夠從一個對象取得的信息。類的各種功能操作都是用方法來實現的,屬性隻不過提供瞭相應的數據。一個完整的方法通常包括方法名稱、方法主體、方法參數和方法返回值類型。若方法有返回值,則在方法體中用 return 語句指明要返回的值。
形參和實參
關於方法的參數,經常會提到形參與實參,形參是定義方法時參數列表中出現的參數,實參是調用方法時為方法傳遞的參數。
方法的形參和實參具有以下特點:
- 形參變量隻有在被調用時才分配內存單元,在調用結束時,即刻釋放所分配的內存單元。因此,形參隻有在方法內部有效,方法調用結束返回主調方法後則不能再使用該形參變量。
- 實參可以是常量、變量、表達式、方法等,無論實參是何種類型的量,在進行方法調用時,它們都必須具有確定的值,以便把這些值傳送給形參。因此應預先用賦值、輸入等辦法使實參獲得確定值。
- 實參和形參在數量、類型和順序上應嚴格一致,否則會發生“類型不匹配” 的錯誤。
- 方法調用中發生的數據傳送是單向的,即隻能把實參的值傳送紿形參,而不能把形參的值反向地傳送給實參。因此在方法調用過程中,形參的值發生改變,而實參中的值不會變化。
- 實參變量對形參變量的數據傳遞是“值傳遞”,即隻能由實參傳遞給形參,而不能由形參傳遞給實參。程序中執行到調用成員方法時,Java 把實參值復制到一個臨時的存儲區(棧)中,形參的任何修改都在棧中進行,當退出該成員方法時,Java 自動清除棧中的內容。
局部變量
在方法體內可以定義本方法所使用的變量,這種變量是局部變量。它的生存期與作用域是在本方法內,也就是說,局部變量隻能在本方法內有效或可見,離開本方法則這些變量將被自動釋放。
在方法體內定義變量時,變量前不能加修飾符。局部變量在使用前必須明確賦值,否則編譯時會出錯。另外,在一個方法內部,可以在復合語句(把多個語句用括號{}括起來組成的一個語句稱復合語句)中定義變量,這些變量隻在復合語句中有效。
可變參數
在具體實際開發過程中,有時方法中參數的個數是不確定的。為瞭解決這個問題,在 J2SE 5.0 版本中引入瞭可變參數的概念。
聲明可變參數的語法格式如下:
methodName({paramList},paramType…paramName)
其中,methodName 表示方法名稱;paramList 表示方法的固定參數列表;paramType 表示可變參數的類型;… 是聲明可變參數的標識;paramName 表示可變參數名稱。
註意:可變參數必須定義在參數列表的最後。
public class StudentTestMethod { // 定義輸出考試學生的人數及姓名的方法 public void print(String...names) { int count = names.length; // 獲取總個數 System.out.println("本次參加考試的有"+count+"人,名單如下:"); for(int i = 0;i < names.length;i++) { System.out.println(names[i]); } } public static void main(String[] args) { // TODO Auto-generated method stub StudentTestMethod student = new StudentTestMethod(); student.print("張強","李成","王勇"); // 傳入3個值 student.print("馬麗","陳玲"); } }
構造方法
構造方法是類的一種特殊方法,用來初始化類的一個新的對象,在創建對象(new 運算符)之後自動調用。Java 中的每個類都有一個默認的構造方法,並且可以有一個以上的構造方法。
Java 構造方法有以下特點:
- 方法名必須與類名相同
- 可以有 0 個、1 個或多個參數
- 沒有任何返回值,包括 void
- 默認返回類型就是對象類型本身
- 隻能與 new 運算符結合使用
值得註意的是,如果為構造方法定義瞭返回值類型或使用 void 聲明構造方法沒有返回值,編譯時不會出錯,但 Java 會把這個所謂的構造方法當成普通方法來處理。
這時候大傢可能會產生疑問,構造方法不是沒有返回值嗎?為什麼不能用 void 聲明呢?
簡單的說,這是 Java 的語法規定。實際上,類的構造方法是有返回值的,當使用 new 關鍵字來調用構造方法時,構造方法返回該類的實例,可以把這個類的實例當成構造器的返回值,因此構造器的返回值類型總是當前類,無須定義返回值類型。但必須註意不要在構造方法裡使用 return 來返回當前類的對象,因為構造方法的返回值是隱式的。
註意:構造方法不能被 static、final、synchronized、abstract 和 native(類似於 abstract)修飾。構造方法用於初始化一個新對象,所以用 static 修飾沒有意義。構造方法不能被子類繼承,所以用 final 和 abstract 修飾沒有意義。多個線程不會同時創建內存地址相同的同一個對象,所以用 synchronized 修飾沒有必要。
在一個類中定義多個具有不同參數的同名方法,這就是方法的重載。
如果在類中沒有定義任何一個構造方法,則 Java 會自動為該類生成一個默認的構造方法。默認的構造方法不包含任何參數,並且方法體為空。
無參構造方法和有參構造方法如下:
public class MyClass { private int m; // 定義私有變量 MyClass() { // 定義無參的構造方法 m = 0; } MyClass(int m) { // 定義有參的構造方法 this.m = m; } }
this關鍵字
this.屬性名
大部分時候,普通方法訪問其他方法、成員變量時無須使用 this 前綴,但如果方法裡有個局部變量和成員變量同名,但程序又需要在該方法裡訪問這個被覆蓋的成員變量,則必須使用 this 前綴。
// 創建構造方法,為上面的3個屬性賦初始值 public Teacher(String name,double salary,int age) { this.name = name; // 設置教師名稱 this.salary = salary; // 設置教師工資 this.age = age; // 設置教師年齡 }
this.方法名
this 關鍵字最大的作用就是讓類中一個方法,訪問該類裡的另一個方法或實例變量。
public class Dog { // 定義一個jump()方法 public void jump() { System.out.println("正在執行jump方法"); } // 定義一個run()方法,run()方法需要借助jump()方法 public void run() { // 使用this引用調用run()方法的對象 this.jump(); System.out.println("正在執行run方法"); } }
this( )訪問構造方法
public class Student { String name; // 無參構造方法(沒有參數的構造方法) public Student() { this("張三"); } // 有參構造方法 public Student(String name) { this.name = name; } // 輸出name和age public void print() { System.out.println("姓名:" + name); } public static void main(String[] args) { Student stu = new Student(); stu.print(); } }
註意:
- this( ) 不能在普通方法中使用,隻能寫在構造方法中。
- 在構造方法中使用時,必須是第一條語句。
static關鍵字
在類中,使用 static 修飾符修飾的屬性(成員變量)稱為靜態變量,也可以稱為類變量,常量稱為靜態常量,方法稱為靜態方法或類方法,它們統稱為靜態成員,歸整個類所有。
靜態成員不依賴於類的特定實例,被類的所有實例共享,就是說 static 修飾的方法或者變量不需要依賴於對象來進行訪問,隻要這個類被加載,Java 虛擬機就可以根據類名找到它們。
調用靜態成員的語法形式如下:
類名.靜態成員
註意:
- static 修飾的成員變量和方法,從屬於類。
- 普通變量和方法從屬於對象。
- 靜態方法不能調用非靜態成員,編譯會報錯。
靜態變量
類的成員變量可以分為以下兩種:
- 靜態變量(或稱為類變量),指被 static 修飾的成員變量。
- 實例變量,指沒有被 static 修飾的成員變量。
靜態變量與實例變量的區別如下:
1)靜態變量
- 運行時,Java 虛擬機隻為靜態變量分配一次內存,在加載類的過程中完成靜態變量的內存分配。
- 在類的內部,可以在任何方法內直接訪問靜態變量。
- 在其他類中,可以通過類名訪問該類中的靜態變量。
2)實例變量
- 每創建一個實例,Java 虛擬機就會為實例變量分配一次內存。
- 在類的內部,可以在非靜態方法中直接訪問實例變量。
- 在本類的靜態方法或其他類中則需要通過類的實例對象進行訪問。
靜態變量在類中的作用如下:
- 靜態變量可以被類的所有實例共享,因此靜態變量可以作為實例之間的共享數據,增加實例之間的交互性。
- 如果類的所有實例都包含一個相同的常量屬性,則可以把這個屬性定義為靜態常量類型,從而節省內存空間。例如,在類中定義一個靜態常量 PI。
public class StaticVar { public static String str1 = "Hello"; public static void main(String[] args) { String str2 = "World!"; // 直接訪問str1 String accessVar1 = str1+str2; System.out.println("第 1 次訪問靜態變量,結果為:"+accessVar1); // 通過類名訪問str1 String accessVar2 = StaticVar.str1+str2; System.out.println("第 2 次訪問靜態變量,結果為:"+accessVar2); // 通過對象svt1訪問str1 StaticVar svt1 = new StaticVar(); svt1.str1 = svt1.str1+str2; String accessVar3 = svt1.str1; System.out.println("第3次訪向靜態變量,結果為:"+accessVar3); // 通過對象svt2訪問str1 StaticVar svt2 = new StaticVar(); String accessVar4 = svt2.str1+str2; System.out.println("第 4 次訪問靜態變量,結果為:"+accessVar4); } }
運行該程序後的結果如下所示。
第 1 次訪問靜態變量,結果為:HelloWorld!
第 2 次訪問靜態變量,結果為:HelloWorld!
第 3 次訪向靜態變量,結果為:HelloWorld!
第 4 次訪問靜態變量,結果為:HelloWorld!World!
靜態方法
與成員變量類似,成員方法也可以分為以下兩種:
- 靜態方法(或稱為類方法),指被 static 修飾的成員方法。
- 實例方法,指沒有被 static 修飾的成員方法。
靜態方法與實例方法的區別如下:
- 靜態方法不需要通過它所屬的類的任何實例就可以被調用,因此在靜態方法中不能使用 this 關鍵字,也不能直接訪問所屬類的實例變量和實例方法,但是可以直接訪問所屬類的靜態變量和靜態方法。另外,和 this 關鍵字一樣,super 關鍵字也與類的特定實例相關,所以在靜態方法中也不能使用 super 關鍵字。
- 在實例方法中可以直接訪問所屬類的靜態變量、靜態方法、實例變量和實例方法。
public class StaticMethod { public static int count = 1; // 定義靜態變量count public int method1() { // 實例方法method1 count++; // 訪問靜態變量count並賦值 System.out.println("在靜態方法 method1()中的 count="+count); // 打印count return count; } public static int method2() { // 靜態方法method2 count += count; // 訪問靜態變量count並賦值 System.out.println("在靜態方法 method2()中的 count="+count); // 打印count return count; } public static void PrintCount() { // 靜態方法PrintCount count += 2; System.out.println("在靜態方法 PrintCount()中的 count="+count); // 打印count } public static void main(String[] args) { StaticMethod sft = new StaticMethod(); // 通過實例對象調用實例方法 System.out.println("method1() 方法返回值 intro1="+sft.method1()); // 直接調用靜態方法 System.out.println("method2() 方法返回值 intro1="+method2()); // 通過類名調用靜態方法,打印 count StaticMethod.PrintCount(); } }
運行該程序後的結果如下所示。
在靜態方法 method1()中的 count=2
method1() 方法返回值 intro1=2
在靜態方法 method2()中的 count=4
method2() 方法返回值 intro1=4
在靜態方法 PrintCount()中的 count=6
靜態代碼塊
指 Java 類中的 static{ } 代碼塊,主要用於初始化類,為類的靜態變量賦初始值,提升程序性能。
靜態代碼塊的特點如下:
- 靜態代碼塊類似於一個方法,但它不可以存在於任何方法體中。
- 靜態代碼塊可以置於類中的任何地方,類中可以有多個靜態初始化塊。
- Java 虛擬機在加載類時執行靜態代碼塊,所以很多時候會將一些隻需要進行一次的初始化操作都放在 static 代碼塊中進行。
- 如果類中包含多個靜態代碼塊,則 Java 虛擬機將按它們在類中出現的順序依次執行它們,每個靜態代碼塊隻會被執行一次。
- 靜態代碼塊與靜態方法一樣,不能直接訪問類的實例變量和實例方法,而需要通過類的實例對象來訪問。
public class StaticCode { public static int count = 0; { count++; System.out.println("非靜態代碼塊 count=" + count); } static { count++; System.out.println("靜態代碼塊1 count=" + count); } static { count++; System.out.println("靜態代碼塊2 count=" + count); } public static void main(String[] args) { System.out.println("*************** StaticCode1 執行 ***************"); StaticCode sct1 = new StaticCode(); System.out.println("*************** StaticCode2 執行 ***************"); StaticCode sct2 = new StaticCode(); } }
如上述示例,為瞭說明靜態代碼塊隻被執行一次,特地添加瞭非靜態代碼塊作為對比,並在主方法中創建瞭兩個類的實例對象。上述示例的執行結果為:
靜態代碼塊1 count=1
靜態代碼塊2 count=2
*************** StaticCode1 執行 ***************
非靜態代碼塊 count=3
*************** StaticCode2 執行 ***************
非靜態代碼塊 count=4
對象的創建
對象是對類的實例化。對象具有狀態和行為,變量用來表明對象的狀態,方法表明對象所具有的行為。Java 對象的生命周期包括創建、使用和清除。在 Java 語言中創建對象分顯式創建與隱含創建兩種情況。
顯式創建對象
對象的顯式創建方式有 4 種。
使用 new 關鍵字創建對象
這是常用的創建對象的方法,語法格式如下:
類名();
調用 java.lang.Class 或者 java.lang.reflect.Constuctor 類的 newlnstance() 實例方法
在 Java 中,可以使用 java.lang.Class 或者 java.lang.reflect.Constuctor 類的 newlnstance() 實例方法來創建對象,代碼格式如下:
java.lang.Class Class 類對象名稱 = java.lang.Class.forName(要實例化的類全稱);
類名 對象名 = (類名)Class類對象名稱.newInstance();
調用 java.lang.Class 類中的 forName() 方法時,需要將要實例化的類的全稱(比如 com.mxl.package.Student)作為參數傳遞過去,然後再調用 java.lang.Class 類對象的 newInstance() 方法創建對象。
調用對象的 clone() 方法
該方法不常用,使用該方法創建對象時,要實例化的類必須繼承 java.lang.Cloneable 接口。 調用對象的 clone() 方法創建對象的語法格式如下:
類名對象名 = (類名)已創建好的類對象名.clone();
下面創建一個示例演示常用的前三種對象創建方法。示例代碼如下:
public class Student implements Cloneable { // 實現 Cloneable 接口 private String Name; // 學生名字 private int age; // 學生年齡 public Student(String name,int age) { // 構造方法 this.Name = name; this.age = age; } public Student() { this.Name = "name"; this.age = 0; } public String toString() { return"學生名字:"+Name+",年齡:"+age; } public static void main(String[] args)throws Exception { System.out.println("---------使用 new 關鍵字創建對象---------"); // 使用new關鍵字創建對象 Student student1 = new Student("小劉",22); System.out.println(student1); System.out.println("-----------調用 java.lang.Class 的 newInstance() 方法創建對象-----------"); // 調用 java.lang.Class 的 newInstance() 方法創建對象 Class c1 = Class.forName("Student"); Student student2 = (Student)c1.newInstance(); System.out.println(student2); System.out.println("-------------------調用對象的 clone() 方法創建對象----------"); // 調用對象的 clone() 方法創建對象 Student student3 = (Student)student2.clone(); System.out.println(student3); } }
對上述示例的說明如下:
- 使用 new 關鍵字或 Class 對象的 newInstance() 方法創建對象時,都會調用類的構造方法。
- 使用 Class 類的 newInstance() 方法創建對象時,會調用類的默認構造方法,即無參構造方法。
- 使用 Object 類的 clone() 方法創建對象時,不會調用類的構造方法,它會創建一個復制的對象,這個對象和原來的對象具有不同的內存地址,但它們的屬性值相同。
- 如果類沒有實現 Cloneable 接口,則 clone。方法會拋出 java.lang.CloneNotSupportedException 異常,所以應該讓類實現 Cloneable 接口。
程序執行結果如下:
———使用 new 關鍵字創建對象———
學生名字:小劉,年齡:22
———–調用 java.lang.Class 的 newInstance() 方法創建對象———–
學生名字:name,年齡:0
——————-調用對象的done()方法創建對象———-
學生名字:name,年齡:0
調用 java.io.ObjectlnputStream 對象的 readObject()
方法隱含創建對象
除瞭顯式創建對象以外,在 Java 程序中還可以隱含地創建對象,例如下面幾種情況。
1)String strName = “strValue”,其中的“strValue”就是一個 String 對象,由 Java 虛擬機隱含地創建。
2)字符串的“+”運算符運算的結果為一個新的 String 對象,示例如下:
String str1 = “Hello”;
String str2 = “Java”;
String str3 = str1+str2; // str3引用一個新的String對象
3)當 Java 虛擬機加載一個類時,會隱含地創建描述這個類的 Class 實例。
提示:類的加載是指把類的 .class 文件中的二進制數據讀入內存中,把它存放在運行時數據區的方法區內,然後在堆區創建一個 java.lang.Class 對象,用來封裝類在方法區內的數據結構。
總結
無論釆用哪種方式創建對象,Java 虛擬機在創建一個對象時都包含以下步驟:
- 給對象分配內存。
- 將對象的實例變量自動初始化為其變量類型的默認值。
- 初始化對象,給實例變量賦予正確的初始值。
註意:每個對象都是相互獨立的,在內存中占有獨立的內存地址,並且每個對象都具有自己的生命周期,當一個對象的生命周期結束時,對象就變成瞭垃圾,由 Java 虛擬機自帶的垃圾回收機制處理。
匿名對象
每次 new 都相當於開辟瞭一個新的對象,並開辟瞭一個新的物理內存空間。如果一個對象隻需要使用唯一的一次,就可以使用匿名對象,匿名對象還可以作為實際參數傳遞。
匿名對象就是沒有明確的給出名字的對象,是對象的一種簡寫形式。一般匿名對象隻使用一次,而且匿名對象隻在堆內存中開辟空間,而不存在棧內存的引用。
public class Person { public String name; // 姓名 public int age; // 年齡 // 定義構造方法,為屬性初始化 public Person(String name, int age) { this.name = name; this.age = age; } // 獲取信息的方法 public void tell() { System.out.println("姓名:" + name + ",年齡:" + age); } public static void main(String[] args) { new Person("張三", 30).tell(); // 匿名對象 } }
程序運行結果為:
姓名:張三,年齡:30
在以上程序的主方法中可以發現,直接使用瞭“new Person(“張三”,30)”語句,這實際上就是一個匿名對象,與之前聲明的對象不同,此處沒有任何棧內存引用它,所以此對象使用一次之後就等待被 GC(垃圾收集機制)回收。
匿名對象在實際開發中基本都是作為其他類實例化對象的參數傳遞的,在Java 應用部分的很多地方都可以發現其用法,匿名對象實際上就是個堆內存空間,對象不管是匿名的還是非匿名的,都必須在開辟堆空間之後才可以使用。
總結
本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您都能夠多多關註WalkonNet的更多內容!
推薦閱讀:
- Java創建對象(顯式創建和隱含創建)
- Java中反射的"暴破"機制(SetAccessible方法)詳解
- 手把手帶你粗略瞭解Java–類和對象
- Java抽象類和接口使用梳理
- Java中數組的定義與使用