基礎 類有屬性和方法,它們對本類有效(作用範圍)。類的屬性就是成員變數,它預設會賦值初始化。類的方法是類具有的一些行為。 類是抽象的,將它們實例化後就是對象(通過new進行實例化),各實例化後的對象都具有這些成員變數的屬性,且賦有具體的值,如果某對象沒有為成員變數賦值,則採用預設初始化時的值。每個n ...
基礎
類有屬性和方法,它們對本類有效(作用範圍)。類的屬性就是成員變數,它預設會賦值初始化。類的方法是類具有的一些行為。
類是抽象的,將它們實例化後就是對象(通過new進行實例化),各實例化後的對象都具有這些成員變數的屬性,且賦有具體的值,如果某對象沒有為成員變數賦值,則採用預設初始化時的值。每個new出來的對象都有自己獨立的成員變數,但某個類的所有對象都共用類的方法,因為類方法只是一段放在代碼區的代碼,只有執行調用類方法時才會產生相關內容。
有了實例化後的對象,就可以引用對象的屬性並調用對象的方法(實際上是類的方法,方法是共用的,並不屬於某個單獨的對象),這樣就可以實現這個對象的相關操作。引用對象的屬性方式為"對象名.成員變數",調用對象的方法的方式為"對象名.方法"。雖說方法是各對象共用的,但顯然,"對象名1.方法1"的方法1執行時,方法內部的變數採用的都是對象名1的成員變數。
示例分析:
以一個三維空間上的點類來說,點具有三維坐標xyz,x、y、z就是它們的屬性,需要定義為點類的成員變數。點可以求出它到原點的距離、到另一個點的距離,求距離就是通過類的方法(函數)實現。通過new這個點類,就可以實實在在地創建一個點,new一次就一個點,每個點都有自己的xyz屬性,每個點的成員變數都在new出來的時候和對象一起存放在heap記憶體區。每個點都擁有大家共用的求距離方法getDistance()。於是,就可以將這個點類定義為下麵的形式:
class Point {
//定義成員變數,即三個坐標,坐標可能是小數,因此定義為double類型
double x;
double y;
double z;
//定義構造方法,使得以後在new一個點對象的時候為點的成員變數xyz賦值
Point(double _x,double _y,double _z) {
x = _x;
y = _y;
z = _z;
}
//有了構造方法,就可以new對象的時候賦值,例如賦值點p對象的xyz分別為1/2/3:
//Point p = new Point(1.0,2.0,3.0);
//定義求兩點間距離的方法getDistance()。
//涉及到兩個點:一個是調用該方法的點對象自身,一個是目標點對象
//因此,需要將目標點對象作為方法的形參,並使用目標點的坐標屬性
double getDistance(Point px) {
return (x-px.x)*(x-px.x)+(y-px.y)*(y-px.y)+(z-px.z)*(z-px.z);
}
//有了方法,以後就可以調用該方法求距離,例如,求點(1,2,3)到原點(0,0,0)的距離
//Point p = new Point(1.0,2.0,3.0);
//Point p1 = new Point(0.0,0.0,0.0);
//System.out.println(p.getDistance(p1));
//這表示調用點p的方法,求點p到原點p1的距離,
//p1的屬性賦值給方法的形參px(px指向點p1對象),因此其坐標值為(0,0,0)
//因為調用的是點p的方法,因此方法中的x/y/z是點p的成員變數值,即(1,2,3)
}
將上述代碼整理,並寫一個main方法,就可以實現一個計算兩點距離的小程式。例如,TestPoint.java文件內是如下內容:
class Point {
double x,y,z;
Point(double _x,double _y,double _z) {
x = _x;
y = _y;
z = _z;
}
double getDistance(Point p){
return (x-p.x)*(x-p.x)+(y-p.y)*(y-p.y)+(z-p.z)*(z-p.z);
}
}
public class TestPoint {
public static void main(String[] args) {
Point p = new Point(1.0,2.0,3.0);
Point p1 = new Point(1.0,0.0,0.0);
System.out.println(p.getDistance(p1));
}
}
從上面的例子中可以感受到,面向對象更抽象地說是面向類。在實現某個功能的時候,例如求兩個三維點之間的距離時,將點的屬性和求距離的方法定義到點類中,以後就不用管點的xyz屬性、求三維點之間距離的表達式方法,只要在需要時面向這個類new出點對象,它就有了點的xyz屬性,再調用點對象的求距離的方法就可以了。有了面向對象,求三維點距離時,只需知道兩件事:為成員變數xyz賦值;記得點類中的方法的名稱。這就像為了查看文本內容執行cat命令一樣,只要記得cat命令的名稱、功能和選項參數即可,無需關心cat的內部機制是如何讀取文本內容並將其顯示出來的。
在定義一個方法時,需要考慮三個問題:方法的名稱如何取、方法的參數、方法是否有返回值。方法的名稱暫且不說,方法的參數必須要考慮清楚,例如求兩點的距離時,參數可以是某個點的坐標,也可以直接是一個點對象。如果已經定義了點類,那麼使用點對象作為參數更符合面向對象的原則;方法的返回值同樣重要,返回值決定了這個方法的性質,例如判斷兩點間的距離是否大於20,就應該返回布爾類型,而不是double類型。
構造方法
構造方法和類同名,它的作用是在對象被new出來時做初始化行為。因為構造方法的目的是初始化,因此構造方法必須不能有返回值,即不能寫上數據類型或void關鍵字。
例如:
class Point {
double x,y,z;
//構造方法,註意其名稱必須為Point()
Point(double _x,double _y,double _z) {
x = _x;
y = _y;
z = _z;
}
}
以後就可以在new對象的時候進行初始化,例如:
Point p = new Point(1,2,3);
如果沒有顯式定義構造方法,則隱含了一個空構造方法,例如下麵的代碼中,兩個class是完全等價的。
class Point {
double x,y,z;
}
class Point {
double x,y,z;
Point() {}
}
因為初始化有預設的值,所以它們還等價於(0.0是初始化double時的預設值):
class Point {
double x,y,z;
Point() {
x = 0.0;
y = 0.0;
z = 0.0;
}
}
正因為有隱含的空構造方法,才能在new對象的時候不使用任何參數就能進行成員變數的初始化。例如:
Point p = new Point();
在new對象的時候,對象的參數必須和構造方法完全對應,例如定義了構造方法Point(double _x,double _y,double _z)
後,就只能new Point(value1,value2,value3)
,而不能不接任何參數new Point()
或接少於3個的參數new Point(value1,value2)
。
方法的重載(overload)
當兩個或多個方法的名稱相同,只有參數不同時(可以是參數的個數、參數的數據類型不同),它們就構成了方法的重載。
方法的重載大大減少了方法數量的定義。例如,要求兩個值中較大者,考慮到值可以是整形也可以是小數,於是使用如下方式:
public class Num {
void intMax(int a,int b) {
System.out.println(a>b ? a : b);
}
void doubleMax(double a,double b) {
System.out.println(a>b ? a : b);
}
public static void main(String[] args) {
Num n = new Num();
n.intMax(2,3);
n.doubleMax(2.0,3.0);
}
}
這裡第一個方法intMax()和第二個方法doubleMax()實際上是重覆的,僅僅只是參數類型上不同。這樣的設計很不方便,不僅在比較數值時不知道應該調用哪一個方法,還要知道各個方法的區別。
而使用重載就不再有這樣的問題。
public class Num {
void Max(int a,int b) {
System.out.println(a>b ? a : b);
}
void Max(double a,double b) {
System.out.println(a>b ? a : b);
}
public static void main(String[] args) {
Num n = new Num();
n.Max(2,3);
n.Max(2.0,3.0);
}
}
這兩個Max方法名稱相同,僅僅只是參數不同。在調用Max()的時候,根據傳遞的實參(2,3)和(2.0,3.0),它能能夠區分出前者應該使用第一個Max(),後者使用第二個Max()。
重載的本質是在調用方法時能夠通過傳遞的參數個數、參數的值篩選出具體應該使用哪個方法。
例如下麵的方法中,前4個都能構成方法重載,而最後一個不能,因為它的定義方式不同。從本質上來說,是因為調用Max()傳遞兩個int整型數值時,無法確定是選擇第一個方法還是最後一個方法,而它們又正好是衝突的,因此它們不構成重載。
void Max(int a,int b) {}
void Max(int a,int b,int c) {}
void Max() {}
void Max(char a,char b)
Max(int a,int b) {}
this關鍵字
在類的方法定義中使用this關鍵字可以代表該方法的對象的引用,它是new出來的對象中指向對象自身的關鍵字。當必須指出當前所使用方法的對象是誰時需要使用this,使用this還可以避免成員變數和形參重名的問題。
public class TestThis {
int i = 100;
TestThis(int i) {
this.i = i; //i為形參i(就近原則),this.i代表的是對象中的i,即成員變數i
//this在此避免了成員變數和形參同名的問題
}
TestThis increment() {
++i; //由tt.increment()調用,因此i是成員變數
return this; //返回的this代表TestThis類的對象自身
//this在此表示對象自身
}
void print() {
System.out.println("i=" + i);
}
public static void main(String[] args) {
TestThis tt = new TestThis(10);
System.out.println("i= " + tt.i);
tt.increment().increment().print();
}
}
上述示例中,new TestThis創建了一個TestThis對象,tt指向該對象。tt.increment()表示調用一次tt對象的方法,此時i自增一次,並返回this,this代表的對象正是tt指向的對象,是TestThis類的對象,所以他也有increment()方法,所以還可以繼續執行increment()方法,最後再次返回this,最後執行print()方法,輸出自增兩次後的i值。
註意,雖然可以return this來返回自身對象的引用,但卻不能使用return super來返回父類對象的引用。也就是說,父類對象只能操作其內某個成員,不能直接返回父對象整體。
static關鍵字
static聲明的成員變數為靜態成員變數,它是該類的共用變數。在第一次使用時被初始化,對於該類的所有對象來說,static成員變數只有一份。
可以通過對象引用或直接通過類名來引用靜態成員。即使在沒有new出任何對象時,也能直接引用靜態成員,因為它屬於類。假如類名為T,靜態成員變數有i,靜態方法有m(),則可以直接使用T.i和T.m()分別引用。當new出來T的一個對象t時,可以使用t.i或t.m(),這和使用T.i和T.m()是完全等價的。(實際上,在不產生衝突的情況下,即使不寫類名也可以直接引用靜態變數、靜態方法)
用static聲明的方法為靜態方法,靜態方法不是針對某個對象來調用的。在調用靜態方法時,不會將對象的引用傳遞給它,所以在static方法中不可訪問非static的成員,即靜態方法中不可以訪問非靜態成員變數和其他非靜態方法。換句話說,因為靜態方法屬於類,靜態方法看不到heap中各對象中的成員,它只能看到data segment中的靜態成員。
public class Student {
static int cnt = 0; //static成員變數
int id;
String name;
Student(String name) {
id = ++cnt;
this.name = name;
}
public void info() {
System.out.println("id=" + id + ", name=" + name);
}
public static void main(String[] args) {
Student.cnt = 100; //在new之前就可以使用類名直接引用static成員變數並賦值
//還可以直接寫為"cnt = 100;"
Student s1 = new Student("Malongshuai");
Student s2 = new Student("Gaoxiaofang");
s1.info();
s2.info();
}
}
在上面的例子中,靜態變數cnt使用類名直接訪問,並使用靜態變數cnt作為成員變數id的賦值基礎("id=++cnt;")。由於靜態變數只在最初進行了賦值,後續一直都通過自增的方式進行改變,這是靜態變數的廣為使用的功能:"充當計數器"。
如果把上面的static關鍵字去掉,並註釋Student.cnt行,再編譯運行,那麼id的輸出結果將總是1,因為cnt作為成員變數被初始化,所有對象的cnt都一樣,從而導致id的值也一樣。
無論是靜態變數還是靜態方法,它們都可以在new出對象之前直接引用,這時還不存在對象,因此在靜態方法中無法使用非靜態的成員變數(它們還不存在)。正如上面的public static void main(),它是靜態的,可以直接引用cnt,它不需要在運行時先去new一個對象才能執行,否則main就太"不智能",每次執行都要先new出對象。
如果將static關鍵字去掉,在編譯時將報如下錯誤:
λ javac Student.java
Student.java:16: 錯誤: 無法從靜態上下文中引用非靜態 變數 cnt
Student.cnt=100;
^
1 個錯誤
註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!