第6章 面向物件下
文章目錄
基本資料型別的包裝類
Java 是面向物件的程式語言,但它也包含了8種基本資料型別,這8個基本型別不支援面向物件的程式設計機制,基本資料型別的資料也不具備“物件” 的特性:沒有屬性方法可以被呼叫。為了解決8個基本資料型別不能當成 Object 型別變數使用的問題,Java 提供了包裝類的概念,為8個基本型別分別定義了響應的引用型別,並稱之為基本資料型別的包裝類。
基本資料型別 | 包裝類 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
public class Primitive2Wrapper { public static void main(String[] args) { boolean b1 = true; //通過構造器將b1基本型別變數包裝成包裝類物件 Boolean b1Obj = new Boolean(b1); int it = 5; Integer itObj = new Integer(it); //把一個字串轉換成 Float 物件 Float f1 = new Float("4.5"); //把一個字串轉化為 Boolean 物件 Boolean bObj = new Boolean("false"); //異常 //Long lObj = new Long("ddd"); } }
如果希望取出包裝類物件中包裝的基本型別變數,可以使用包裝類提供的xxxValue() 方法
//取出 Boolean 物件中的 boolean 變數
boolean bb = bObj.booleanValue();
JDK1.5 提供了自動裝箱和自動拆箱機制,所謂自動裝箱就是可以把一個基本型別變數直接賦給對應的包裝類變數,或者賦給 Object 變數;自動拆箱與之相反,允許直接把包裝類物件直接賦給一個對應的基本型別變數。
public class AutoBoxingUnboxing {
public static void main(String[] args) {
//直接把一個基本型別變數賦給 Integer
Integer intObj = 5;
Object boolObj = true;
//直接把一個 Integer 物件賦給 int 型別變數
int it = intObj;
if(boolObj instanceof Boolean){
boolean b = (Boolean)boolObj;
System.out.println(b);
}
}
}
除此之外,包裝類還可以實現基本型別變數和字串之間的轉換,出了 Character 之外的所有包裝類都提供了一個 parseXxx(String s) 靜態方法,用於將一個特定字串裝換成基本型別變數;除此之外,在 String 類裡也提供了多個過載
valueOf() 方法,用於將基本型別變數轉換為字串
public class Primitive2String {
public static void main(String[] args) {
String intStr = "123";
//把一個特定的字串轉換為 int 變數
int it = Integer.parseInt(intStr);
String floatStr = "4.56";
float ft = Float.parseFloat(floatStr);
//把一個 float 變數轉換成 String 變數
String ftStr = String.valueOf(2.345f);
//把一個 double 變數轉換成 String 變數
String dbStr = String.valueOf(3.456);
//把一個 boolean 變數轉換成 String 變數
String boolStr = String.valueOf(true);
}
}
基本型別變數和字串之間的轉換關係
處理物件
Java 物件都是 Object 類的例項, 都可以呼叫該類中定義的方法,這些方法是處理 Java 物件的通用方法。
列印物件和 toString 方法
class Person11{
private String name;
public Person11(String name){
this.name = name;
}
public void info(){
System.out.println("名字為:" + name);
}
}
public class PrintObject {
public static void main(String[] args) {
Person11 p = new Person11("大聖");
//列印 p 所引用的 Person11 物件
//這裡實際上輸出的是Person11物件的toString()方法返回值
System.out.println(p);
}
}
toString() 方法是 Object 類裡的一個例項方法,所有 Java 類都是 Object 的子類,因此所有 Java 物件都具有 toString 方法。不僅如此,所有 Java 物件都可以和字串進行連線運算,當 Java 物件和字串進行連線運算時,系統自動呼叫 Java 物件的toString() 方法的返回值和字串進行連線運算。
toString() 方法是一個非常特殊的方法,他是一個“自我描述”方法,該方法通常用於實現這樣一個功能:當程式設計師直接列印該物件時,系統將會輸出該物件的自我描述資訊。
toString() 方法總是返回該物件實現類的類名[email protected]+hashCode值,因此,如果想要實現自我描述功能,必須重寫 Object 類的 toString() 方法。
class Apple11{
private String color;
private double weight;
public Apple11(String color, double weight){
this.color = color;
this.weight = weight;
}
public String toSring(){
return "一個蘋果的顏色是:" + color + "重量是:" + weight;
}
}
public class TestToString {
public static void main(String[] args) {
Apple11 a = new Apple11();
System.out.println(a);
}
}
== 和 equals 比較運算子
Java 程式中測試兩個變數是否相等有量中方式,一種是利用 == ,另一種是利用 equals 方法。
當使用 == 來判斷兩個變數是否相等時,如果兩個變數時基本型別變數,且時數值型,則只要兩個變數相等,則使用 == 判斷就會返回 true。
對於兩個引用型別的變數,必須它們指向同一個物件時,== 判斷才會返回 true。
equals() 方法也是 Object 類提供的一種例項方法,因此,所有引用變數都可以呼叫該方法來判單是否與其他引用變數相等。但這個方法判斷兩個物件相等的標準與==沒有區別,同樣要求兩個引用變數指向同一個物件才會返回 true。但是可以通過重寫 equals 方法來實現其它的功能。
equals() 方法的重寫
重寫 equals() 方法使得Person5物件和任何物件都相等:
class Person5{
public boolean equals(Object obj){
//不加判斷,總是返回true,即Person物件與任何物件都相等
return true;
}
}
class Dog1{}
public class OverrideEqualsError {
public static void main(String[] args) {
Person5 person5 = new Person5();
System.out.println("Person5物件是否equals Dog1物件?" + (person5.equals(new Dog1())));
System.out.println("Person5物件是否equals String物件?" + (person5.equals(new String("hello"))));
}
}
類成員
static 修飾的成員就是類成員,static 修飾的類成員屬於整個類,不屬於單個例項。
理解類成員
在 Java 類裡,只能包含屬性、方法、構造器、初始化塊、內部類和列舉類等六中成員。其中 static 可以修飾屬性、方法、初始化塊、內部類和列舉類。
類屬性既可以通過類來訪問,也可以通過物件來訪問。但通過類的物件來訪問類屬性時,實際上並不是訪問該物件所具有的屬性,因為當系統建立該類的物件時,系統不會再為類屬性分配記憶體,也不會再次為類屬性進行初始化,也就是說物件根本不包括對應類的類屬性。可以這樣理解,通過物件訪問類屬性時,系統會在底層轉換為通過該類來訪問類屬性。
對於 static 關鍵字而言,有一條非常重要的規則:**類成員不能訪問例項成員。**因為類成員是屬於類的,類成員的作用域比例項成員的作用域更大。完全可能出現類成員已經初始化完成,但例項成員還不曾初始化,如果允許類成員來訪問例項成員將會引起大量的錯誤。
單例類
如果一個類始終只能建立一個例項,則這個類被稱為單例類。在一些特殊的場景下,要求不允許自由地建立該類的例項,而是隻允許為該類建立一個物件。為了避免其它類自由地建立該類的例項,我們把該類的構造器使用 private 修飾,從而把該類的構造器隱藏起來。
根據良好的封裝的原則:一旦把該類的構造器隱藏起來,則需要提供一個 public 方法作為該類的訪問點,用於建立該類的物件,且該類必須使用 static 修飾(因為呼叫該方法之前還不存在物件,因此呼叫該方法的不可能是物件,只能是類)。
class Singleton {
//使用一個變數來快取曾經建立過的例項
private static Singleton instance;
//將構造器使用 private 修飾,隱藏構造器
private Singleton(){}
/*提供一個靜態方法用於返回Singleton例項
*該方法可以加入自定義控制,保證只產生一個Singleton物件
* */
public static Singleton getInstance(){
//如果instance為null,說明還不曾建立Singleton物件
//如果instance不為null,說明已經建立了Singleton物件,將不會執行該方法
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
public class TestSingleton{
public static void main(String[] args) {
//建立Singleton物件不能通過構造器,只能通過getInstance()方法
//Singleton s1 = new Singleton();
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
final 修飾符
final 關鍵字可用於修飾類、方法和變數,它用於表示修飾的類、方法和變數不可改變。
final 變數
final 修飾變數時,表示該變數一旦獲得了初始值後就不可改變。final 既可以修飾成員變數,也可以修飾區域性變數、形參。因為 final 變數獲得初始值後不能被重新賦值,因此 final 修飾區域性變數和成員變數時有一定的不同。
- final 修飾成員變數
public class TestFinalVariable {
//定義成員變數時指定預設值
final int a = 6;
//下面變數將在構造器或初始化塊中分配初始值
final String str;
final int c;
final static double d;
//既沒有預設值,又沒有在初始化塊、構造器中指定初始值,下面定義的char屬性是不合法的
//final char ch;
//初始化塊,可對沒有指定預設值的例項屬性指定初始值
{
str = "Hello";
//a已經有預設值,不能再為a賦值
//a = 9;
}
//靜態初始化塊,可對沒有指定預設值的類屬性賦初值
static
{
d = 5.6;
}
//構造器,可對沒有指定預設值、並且沒有在初始化塊中指定初始值的例項屬性賦初值
public TestFinalVariable(){
c = 5;
}
public void changeFinal(){
//d = 1.2;
}
}
與普通成員變數不同的是,final 成員變數必須由程式設計師顯示地初始化,系統不會對 final 成員進行隱式初始化。
- final 修飾區域性變數
系統不會對區域性變數進行初始化,區域性變數必須由程式設計師進行顯式初始化。因此,使用 final 修飾區域性變數時,既可以在定義時指定預設值,也可以不指定。如果 final 修飾的區域性變數在定義時沒有指定預設值,則可以在後邊的程式碼中對該 final 變數賦初值,但是隻能一次。
public class TestFinalLocalVariable {
public void test(final int a){
//a = 5;
}
public static void main(String[] args) {
final String str = "Hello";
//str = "Java";
final double d;
d = 5.6;
//d = 3.4;
}
}
- final 修飾基本型別和引用型別的區別
當 final 修飾基本型別變數時,不能對基本型別變數重新賦值,因此,基本型別變數不能被改變。但對於引用型別變數而言,它儲存的僅僅是一個引用,final 只保證這個引用的引用地址不會改變,即一直引用同一個物件,但這個物件完全可以改變。
class Person5{
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person5(int age){
this.age = age;
}
}
public class TestFinalReference {
public static void main(String[] args) {
//final修飾陣列變數
final int[] iArr = {5, 6, 12, 9};
System.out.println(Arrays.toString(iArr));
//對陣列進行排序
Arrays.sort(iArr);
System.out.println(Arrays.toString(iArr));
//對陣列元素進行賦值,合法
iArr[2] = 4;
//對iArr進行賦值,非法
//iArr = null;
final Person5 p = new Person5(45);
p.setAge(23);
//改變p的地址,非法
//p = null;
}
}
從上面的程式中可以看出,使用 final 修飾的引用型別變數不能被重新賦值,但可以改變引用型別變數所引用物件的內容。
final 方法
final 修飾的方法不可以被重寫。如果出於某些原因,不希望子類重寫父類的某個方法,可以用 final 修飾該方法。
final 類
final 修飾的類不可以有子類。當子類繼承父類時,就可以訪問到父類內部的資料,並通過重寫父類的方法來改變父類方法的實現細節,這可能導致一些不安全的因素。為了保證某個類不可以被繼承,則可以使用 final 修飾這個類。
不可變類
不可變類的意思是建立該類的例項後,該例項的屬性是不可改變的。
如果需要建立自定義的不可變類,可遵循如下規則:
- 使用 private 和 final 修飾符來修飾類的屬性
- 提供帶參構造器,用於根據傳入引數來初始化類裡的引數
- 僅為該類的屬性提供 getter 方法,不提供 setter 方法,因為普通方法無法修改 final 修飾的屬性
- 如果有必要,重寫 Object 中的hashCode 和 equals 方法
public class Address {
private final String detail;
public String getDetail() {
return detail;
}
public String getPostCode() {
return postCode;
}
private final String postCode;
public Address(){
this.detail = "";
this.postCode = "";
}
public Address(String detail, String postCode){
this.detail = detail;
this.postCode = postCode;
}
//重寫 equals 方法判斷兩個物件是否相等
public boolean equals(Object obj){
if(obj instanceof Address){
Address ad = (Address) obj;
if(this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode()))
return true;
}
return false;
}
public int hashCode(){
return detail.hashCode() + postCode.hashCode();
}
}
對於上面的Address類,我們建立了它的物件後同樣無法修改Address物件的 detail 和 postCode 屬性。
與不可變類對應的是可變類,我們大部分時候建立的類都是可變類,特別是 JavaBean,我們總是為其屬性提供 getter 和 setter 方法。
快取例項的不可變類
抽象類
抽象方法和抽象類
抽象方法和抽象類必須使用 abstract 修飾符來定義,有抽象方法的類只能被定義為抽象類,抽象類裡可以沒有抽象方法。
抽象方法和抽象類的規則如下:
- 抽象類和抽象方法必須使用 abstract 修飾符來修飾,抽象方法不能有方法體
- 抽象類不能被例項化,無法使用 new 關鍵字來呼叫抽象類的構造器建立抽象類的例項
- 抽象類的構造器不能用於建立例項,主要用於被其子類呼叫
- 含抽象方法的類(包括直接定義了一個抽象方法;繼承一個抽象父類,但沒有完全實現父類包含的抽象方法;以及實現了一個介面,但沒有完全實現介面的抽象方法三種情況)只能被定義成抽象類。
當 abstract 修飾某個類時,表明這個類只能被繼承,當 abstract 修飾方法時,表明這個方法只必須由子類提供實現(重寫)。而 final 修飾的類不能被繼承,final 修飾的方法不能被重寫。因此,final 和 abstract 永遠不能同時使用。
當使用 static 來修飾一個方法時,表明這個方法屬於當前類,即該方法可以通過類來呼叫,如果該方法被定義為抽象方法,則將導致通過該類來呼叫該方法時出現錯誤,因此static 和 abstract 不能同時修飾某個方法。
abstract 關鍵字修飾的方法必須被其子類重寫才有意義,否則這個方法永遠不會有方法體,因此 abstract 方法不能定義為 private 訪問許可權,即 private 和 abstract 不能同時使用。
抽象類的作用
抽象類不能建立例項,只能當成父類來被繼承。抽象類是從多個具體類中抽象出來的父類,它具有更高層次的抽象。從多個具有相同特徵的類中抽象出一個抽象類,以這個抽象類作為其子類的模板,從而避免了子類設計的隨意性。
更徹底的抽象:介面
抽象類是從多個類中抽象出來的模板,如果將這種類進行得更徹底,則可以提取出一種更加特殊的“抽象類” —— 介面(interface),接口裡不能包含普通方法,接口裡所有的方法都是抽象方法。
介面的概念
類是一種具體實現體,而介面定義了一種規範,介面定義某一批類所要遵守的規範,介面不關心這些類的內部狀態資料,也不關心這些類裡方法的實現細節。它之規定這些類裡必須提供某些方法。可見,介面是從多個相似類中抽象出來的規範,介面不提供任何實現。介面體現的是規範和實現分離的設計哲學。
介面的定義
定義介面使用 interface 關鍵字。一個介面可以有多個直接父介面,但介面只能繼承介面,不能繼承類。
由於介面定義的是一種規範,因此接口裡不能包含構造器和初始化塊定義。接口裡可以包含屬性(只能是常量)、方法(只能是抽象例項方法)、內部類(包括內部介面)和列舉類定義。
對於接口裡定義的常量屬性而言,它們是介面相關的,而且它們只能是常量,因此係統會自動為這些屬性增加 static 和 final 兩個修飾符,**接口裡的屬性總是使用public、static 和 final 修飾符來修飾。**接口裡沒有構造器和初始化塊,因此介面只能在定義時指定預設值。
// 系統自動為介面定義屬性增加 public static final 修飾符
int MAX_SIZE = 50;
public static final int MAX_SIZE = 50;
對於接口裡定義的方法而言,它們只能是抽象方法,因此係統自動為其增加 abstract 修飾符:由於接口裡的方法全部時抽象方法,因此接口裡不允許定義靜態方法(原因在上面的抽象方法裡有說明),即不可以使用 static 來修飾接口裡的方法。不管定義接口裡的方法是否使用 public abstract 修飾,接口裡的方法總是使用 public abstract 來修飾。
介面的繼承
介面的繼承和類的繼承不一樣,介面完全支援多繼承,即一個介面可以有多個完全父介面。和類繼承相似,子介面擴充套件某個父介面,將會獲得夫接口裡定義的所有抽象方法、常量屬性、內部類和列舉類定義。
使用介面
介面的主要用途就是被實現類實現。一個類可以實現一個或多個介面,繼承使用 extends 關鍵字,而實現則使用 implements 關鍵字。一個類實現一個或多個介面之後,這個類必須完全實現這些接口裡所定義的全部抽象方法,否則,該類將保留從父介面那裡繼承到的抽象方法,該類也必須定義成抽象類。
實現介面方法時,必須使用 public 訪問許可權修飾符,因為接口裡定義的方法都是 public 的,而子類重寫父類方法時訪問許可權只能更大或者相等。