CH05 面向物件(下)
阿新 • • 發佈:2019-01-07
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 returntrue
if and only ify.equals(x)
returnstrue
. - It is transitive: For any references x, y and z, if
x.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
returnstrue
. - 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.
- It is reflexive: For any non-null reference x ,x.equals(x) should return
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.