1. 程式人生 > >CH05 面向物件(下)

CH05 面向物件(下)

5.1 包裝類

  • Java的8種資料型別對應包裝類(Wrapper classes):Integer,Long,Float,Double,Short,Byte,Character, Boolean。
    • The wrapper classes are immutable.
    • The wrapper classes are final, so you cannot subclass them.
  • 基本型別變數和包裝類物件之間的轉換-自動裝箱(Autoboxing)和自動拆箱(AutoUnboxing)功能。
  • 例:AutoBoxingUnboxing.java
public class AutoBoxingUnboxing {
	public static void main(String[] args) {
		// 直接把一個基本型別變數賦給Integer物件
		Integer inObj = 5;
		// 直接把一個boolean型別變數賦給一個Object型別的變數
		Object boolObj = true;
		// 直接把一個Integer物件賦給int型別的物件
		int it = inObj;
		if (boolObj instanceof Boolean)
		{
			// 先把Object物件強制型別轉換為Boolean型別,再賦給boolean變數
boolean b = (Boolean)boolObj; System.out.println(b); } } }
  • 包裝類還可以實現基本型別變數和String物件的轉換
  • 例:Primitive2String.java
public class Primitive2String {
	public static void main(String[] args) {
		String intStr = "123";
		// 把一個特定字串轉換成int變數
		int it1 = Integer.parseInt(intStr);
		int it2 =
new Integer(intStr); System.out.println(it2); String floatStr = "4.56"; // 把一個特定字串轉換成float變數 float ft1 = Float.parseFloat(floatStr); float ft2 = new Float(floatStr); System.out.println(ft2); // 把一個float變數轉換成String變數 String ftStr = String.valueOf(2.345f); System.out.println(ftStr); // 把一個double變數轉換成String變數 String dbStr = String.valueOf(3.344); System.out.println(dbStr); // 把一個boolean變數轉換成String變數 String boolStr = String.valueOf(true); System.out.println(boolStr.toUpperCase()); } }
  • 基本型別轉換成字串還有更簡單的方法:將基本型別變數和""進行連線運算,系統會自動把基本型別變數轉換成字串。
// intStr的值為"5"
String intStr = 5 + "";

5.2 Object類

  • Object類是所有類的父類,即所有類都繼承自Object類。

5.2.1 equals方法

  • Java程式中測試兩個變數是否相等有兩種方式:一個是利用==運算子,另一種是利用.equals()方法
  • 對於兩個基本型別變數,且都是數值型別,只要兩個變數的值相等,返回True
  • 例:EqualTest.java
public class EqualTest {
	public static void main(String[] args) {
		int it = 65;
		float f1 = 65.0f;
		System.out.println("65和65.0是否相等?" + (it == f1));
		char ch = 'A';
		System.out.println("65和'A'是否相等?" + (it == ch));
		String str1 = new String("hello");
		String str2 = new String("hello");
		System.out.println("str1和str2是否相等?" + (str1 == str2));
		System.out.println("str1是否equals str2? " + (str1.equals(str2)));
		// 由於java.lang.String與EqualTest類沒有繼承關係
		// 所以下面語句導致編譯錯誤
//		System.out.println("hello" == new EqualTest());
	}
}

執行結果:

65和65.0是否相等?true
65和'A'是否相等?true
str1和str2是否相等?false
str1是否equals str2? true
  • "hello"字串直接量和new String("hello")的區別
    • 字串直接量由常量池來管理
    • 當使用new String("hello")時,JVM先使用常量池來管理"hello"直接量,再呼叫String類的構造器來建立一個新的String物件,新建立的String物件被儲存在堆記憶體中。
public class StringCompareTest {
	public static void main(String[] args) {
		// s1直接引用常量池中的"Crazy Java"
		String s1 = "CrazyJava";
		String s2 = "Crazy";
		String s3 = "Java";
		// s4後面的字串值可以在編譯時就確定下來,直接引用常量池中的"Crazy Java"
		String s4 = "Crazy" + "Java";
		String s5 = "Cra" + "zy" + "Java";
		// s6後面的字串值不能在編譯時就確定下來,不能引用常量池中的字串
		String s6 = s2 + s3;
		// s7引用堆記憶體中新建立的String物件
		String s7 = new String("CrazyJava");
		System.out.println(s1 == s4);
		System.out.println(s1 == s5);
		System.out.println(s1 == s6);
		System.out.println(s1 == s7);
	}
}

執行結果:

true
true
false
false

重寫equals方法

  • Object預設提供的equals()只是比較物件的地址,即Object類的equals()方法比較的結果與==運算子比較的結果完全相同。因此,在實際應用中常常需要重寫equals()方法。
    - 正確地重寫equals()方法應滿足下列條件。
    • It is reflexive: For any non-null reference x ,x.equals(x) should return true.
    • It is symmetric: For any references x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
    • It is transitive: For any references x, y and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) returns true.
    • It is consistent: If the objects to which x and y refer haven’t changed, then repeated calls to x.equals(y) return the same value.
    • For any non-null reference x, x.equals(null) should return false.

5.2.2 toString方法

  • toString()方法是Object類裡的一個例項方法,所有的Java類都是Object類的子類,因此所有的Java物件都具有toString()方法。
  • 例:PrintObject.java
class Person
{
	private String name;
	public Person(String name)
	{
		this.name = name;
	}
}
public class PrintObject {
	public static void main(String[] args) {
		Person p = new Person("孫悟空");
		// 列印p所引用的Person物件
		System.out.println(p);
	}
}

執行結果

[email protected]
  • 程式中的System.out.println(p);System.out.println(p.toString());效果完全一樣。
  • toString()方法是一個“自我描述”的方法,該方法通常用於實現這樣一個功能:當程式設計師直接列印該物件時,系統將會輸出物件的“自我描述”資訊,用以告訴外界該物件具有的狀態資訊。
  • Object類提供的toSpring()方法總是返回該物件實現類的”類名[email protected]+hashcode"值。如果使用者需要自定義類能實現“自我描述”的功能,就必須重寫Object類的toString()方法。
  • 例:toStringTest.java
class Apple
{
	private String color;
	private double weight;
	public Apple() {}
	public Apple(String color, double weight)
	{
		this.color = color;
		this.weight = weight;
	}
	// 省略color、weight的setter和getter方法
//	...
	// 重寫toString()方法,用於實現Applew物件的“自我描述”
	public String toString()
	{
		return "一個蘋果,顏色是:" + color + ", 重量是:" + weight;
	}
}
public class ToStringTest {
	public static void main(String[] args) {
		Apple a = new Apple("紅色", 5.68);
		System.out.println(a);
	}
}

執行結果:

一個蘋果,顏色是:紅色, 重量是:5.68

5.3 final修飾符

  • final關鍵字可用於修飾類、變數和方法,用於表示它修飾的類、變數和方法不可改變。
  • final修飾變數時,表示該變數一旦獲得了初始值就不可被改變。final既可以修飾成員變數,也可以修飾區域性變數、形參。

5.3.1 final成員變數

  • final修飾的成員變數必須由程式設計師顯式地指定初始值。
  • final修飾的類變數、例項變數能指定初始值的位置如下:
    • 類變數:靜態初始化塊、或宣告該類變數時
    • 例項變數:非靜態初始化塊、宣告該例項變數時、構造器中
  • 例:final修飾成員變數,FinalVariableTest.java
public class FinalVariableTest {
	// 定義成員變數時指定預設值,合法
	final int a = 6;
	// 下面變數將在構造器或初始化塊中分配初始值
	final String str;
	final int c;
	final static double d;
	// 既沒有指定預設值,又沒有在初始化塊、構造器中指定初始值
	// 下面定義的ch例項變數是不合法的
	// final char ch;
	// 初始化塊,可對沒有指定預設值的例項變數指定初始值
	{
		// 在初始化塊中為例項變數指定初始值,合法
		str = "Hello";
		// 定義a例項變數時已經指定了預設值
		// 不能為a重新賦值,因此下面賦值語句不合法
//		a = 9;
	}
	// 靜態初始化塊,可對沒有指定預設值的類變數指定初始值
	static
	{
		// 在靜態初始化塊中為類變數指定初始值,合法
		d = 5.6;
	}
	// 構造器,可對既沒有指定預設值,又沒有在初始化塊中指定初始值的例項變數指定初始值
	public FinalVariableTest()
	{
		// 如果在初始化塊中已經對str指定了初始值
		// 那麼在構造器中不能對final變數重新賦值,下面賦值語句非法
		// str = "java"
		c = 5;
	}
	public void changeFinal()
	{
		// 普通方法不能為final修飾的成員變數賦值
//		d = 1.2;
		// 不能在普通方法中為final成員變數指定初始值
//		ch = 'a';
	}
	public static void main(String[] args) {
		FinalVariableTest ft = new FinalVariableTest();
		System.out.println(ft.a);
		System.out.println(ft.c);
		System.out.println(ft.d);
	}
}

5.3.2 final區域性變數

  • 系統不會對區域性變數進行初始化,必須由程式設計師顯式初始化。因此使用final修飾區域性變數是,既可以在定義時指定預設值,也可以不指定預設值,但final修飾的變數只能賦值一次。
public class FinalLocalVariable {
	public void test(final int a)
	{
		// 不能對final修飾的形參賦值,下面語句不合法
//		a = 5;
		
	}
	public static void main(String[] args) {
		// 定義final區域性變數時指定預設值,則str變數無法重新賦值
		final String str = "hello";
		// 下面賦值語句不合法
//		str = "Java";
		// 定義final區域性變數時沒有指定預設值,則d變數可被賦值一次
		final double d;
		// 第一次賦初值
		d = 5.6;
		// 對final變數重複賦值,下面語句不合法
//		d = 3.4;
	}
}

5.3.3 final修飾基本型別變數和引用型別變數的區別

  • final修飾基本型別變數時,不能對基本型別變數重新賦值,因此基本型別變數不能被改變。
  • 對於引用變數,final只能保證這個引用變數所引用的地址不會改變,即一直引用同一個物件,但這個物件可以發生改變。
  • 例:FinalReferenceTest.java
import java.util.Arrays;
class Person
{
	private int age;
	public Person() {}
	public Person(int age)
	{
		this.age = age;
	}
	// 省略age的getter和setter方法
}
public class FinalReferenceTest {
	public static void main(String[] args) {
		// final修飾陣列變數,iArr是一個引用變數
		final int[] iArr = {5,6,12,9};
		System.out.println(Arrays.toString(iArr));
		// 對陣列元素進行排序,合法
		Arrays.sort(iArr);
		System.out.println(Arrays.toString(iArr));
		// 對陣列元素進行賦值,合法
		iArr[2] = -8;
		System.out.println(Arrays.toString(iArr));
		// 下面語句對iArr重新賦值,不合法
//		iArr = null;
		// final修飾Person變數,p是一個引用變數
		final Person p = new Person(45);
		// 改變Person物件的age例項變數,合法
		p.setAge(23);
		System.out.println(p.getAge());
		// 下面語句對p重新賦值,非法
		// p = null;
	}
}

5.3.4 final方法

  • final修飾的方法不可被重寫,如果由於某些原因,不希望子類重寫父類的某個方法,則可以使用final修飾該方法。
  • 例:FinalMethodTest.java
public class FinalMethodTest
{
    public final void test(){}
}
class Sub extends FinalMethodTest
{
    // 下面定義將出現編譯錯誤,不能重寫final方法
    public void test(){}
}
  • 對於一個private方法,僅在當前類中可見,其子類無法訪問該方法,所以子類無法重寫該方法,如果子類中定義一個與父類private方法有相同方法名、相同形參列表、相同返回值型別的方法,也不是方法重寫,只是定義了一個新方法。
public class PrivateFinalMethodTest
{
    private final void test();
}
class Sub extends PrivateFinalMethodTest
{
    // 下面的方法定義不會出現問題
    public void test(){}
}
  • final修飾的方法僅僅是不能被重寫,並不是不能被過載,因此下面程式完全沒有問題。
public class FinalOverload
{
    public final void test(){}
    public final void test(String arg){}
}

5.3.5 final類

  • final修飾的類不可以有子類。為了保證某個類不可被繼承,則可以使用final修飾這個類

5.3.6 不可變類

  • 不可變類的意思是建立該類的例項後,該例項的例項變數是不可變的。Java提供的8個包裝類和java.lang.String類都是不可變類。
  • 如果需要建立自定義的不可變類,可遵守如下規則:
    • 使用private和final修飾符來修飾該類的成員變數。
    • 提供帶引數構造器,用於根據傳入引數來初始化類的成員變數。
    • 僅為該類的成員變數提供getter方法,不提供setter方法(普通方法無法修改final修飾的成員變數)
    • 如果有必要,重寫Object類的hashCode()和equals()方法
  • 例:Address.java
public class Address {
	private final String detail;
	private final String postCode;
	// 在構造器裡初始化兩個例項變數
	public Address()
	{
		this.detail = "";
		this.postCode = "";
	}
	public Address(String detail, String postCode)
	{
		this.detail = detail;
		this.postCode = postCode;
	}
	// 僅為兩個例項變數提供getter方法
	public String getDetail()
	{
		return this.detail;
	}
	public String getPostCode()
	{
		return this.postCode;
	}
	// 重寫equals()方法,判斷兩個物件是否相等
	public boolean equals(Object obj)
	{
		if (this == obj)
		{
			return true;
		}
		if (obj != null && obj.getClass() == Address.class)
		{
			Address ad = (Address)obj;
			// 當detail和postCode相等時,可認為兩個Address物件相等
			if (this.getDetail().equals(ad.getDetail()) &&
					this.getPostCode().equals(ad.getPostCode()))
			{
				return true;
			}
		}
		return false;
	}
	public int hashCode()
	{
		return detail.hashCode() + postCode.hashCode() * 31;
	}
}

5.4 抽象類

5.4.1 抽象方法和抽象類

  • 抽象方法是隻有方法簽名,沒有方法實現的方法。
  • 抽象類是一種模板模式,抽象類為所有子類提供了一個通用模板,子類可以在此模板基礎上進行擴充套件。通過抽象類可以避免子類設計的隨意性。
  • 抽象方法和抽象類必須使用abstract修飾符來定義。
  • 使用要點如下:
    • 有抽象方法的類只能定義抽象類。
    • 抽象類不能例項化,不能new一個抽象類。
    • 抽象類可以包含屬性、方法、構造方法(構造器、初始化塊)、內部類(介面、列舉)。但是構造方法不能用來new例項,只能用來被子類呼叫。
    • 抽象類只能用來繼承。
    • 抽象方法必須被子類實現。
    • final和abstract不能同時使用。
    • static和abstract不能同時修飾某個方法(但他們可以同時修飾內部類)
    • private和abstract不能同時修飾某個方法
  • 例:定義一個抽象類Person.
public abstract class Person {
	public abstract String getDescription();
	private String name;
	public Person(String name)
	{
		this.name = name;
	}
	public String getName()
	{
		return name;
	}
}
  • 定義Employee例項,Student例項繼承Person類
import java.time.*;
public class Employee extends Person 
{
	private double salary;
	private LocalDate hireDay;
	
	public Employee(String name, double salary, int year, int month, int day) {
		super(name);
		this.salary = salary;
		hireDay = LocalDate.of(year, month, day);
	}
	public double getSalary()
	{
		return salary;
	}
	public LocalDate getHireDay()
	{
		return hireDay;
	}
	public String getDescription()
	{
		return String.format("an employee with a salary of $%.2f", salary);
	}
	public void raiseSaraly(double byPercent)
	{
		double raise = salary * byPercent / 100;
		salary += raise;
	}
}
public class Student extends Person 
{
	private String major;
	
	public Student(String name, String major) {
		super(name);
		this.major = major;
	}
	public String getDescription() {
		return "a student majoring in " + major;
	}
}
  • 測試Person類:PersonTest.java
public class PersonTest {
	public static void main(String[] args) {
		Person[] people = new Person[2];
		// fill the people array with Student and Employee objects
		people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
		people[1] = new Student("Maria Morris", "computer science");
		// print out names and descriptions of all Person objects
		for (Person p:people)
			System.out.println(p.getName() + ", " + p.getDescription());
	}
}

5.5 介面

5.5.1 介面的概念

  • In the Java language, an interface is not a class but a set of requirements for the classes that want to conform to the interface.
  • 介面讓規範和實現分離,讓軟體系統的各元件之間面向介面耦合,是一種鬆耦合的設計。

5.5.2 介面的定義

  • 定義介面使用interface關鍵字。介面定義的基本語法如下:
[修飾符] interface 介面名 extends 父介面1,父介面2.