第6章 面向對象程序設計
6.1面向對象技術基礎
面向對象三大特徵:封裝性, 繼承性,多態性
封裝性:1. 把屬性和方法都放在一起 2.實現信息隱藏
類和對象的關係:類是對象的抽象描述。對象是類的實例。
一個類中包含屬性和方法。
在Java語言中,編譯器爲所有的Java程序自動導入包“java.lang”,因此Java程序可以直接用“java.lang”中的類和接口。位於類繼承關係層次結構樹的根部的類Object就是在包“java.lang”中的一個類。
類前的訪問限定符
public:1.入口類:類名必須和文件名相同;【注意】一個java源文件中只能有一個入口類。
2.公有類:此類可以被跨包訪問。
default:默認(即class關鍵字前什麼都不寫),此類只能在本包中使用。
abstract: 抽象類
final: 最終類,此類不能被其它類繼承。
成員變量(屬性)前的限定符
public: 公共的,在所有類中都可以使用
default: 只有本包中的類可以使用
protected: 本包中的類和其他包中此類的子類都可以使用
private: 只在當前類中使用
【注意】一般情況下,屬性的訪問限定符用private(私有),實現信息隱藏。
final: 定義屬性爲常量,只能初始化一次,以後其值不能被修改。
static:靜態變量,靜態屬性(類屬性): 此屬性被所有對象共享,在內存中只分配一次空間。
【使用方式】:對象名.屬性名 或 類名.屬性名
【注意】對於 非靜態變量(即實例屬性):每個對象都各自分配空間、賦值。
transient:暫時性變量,對象序列化
volatile:用於併發線程共享(瞭解)
成員方法的修飾符:
public: 所有類可以使用
default: 本包中使用
protected: 本包中和其他包中此類的子類可以使用
private: 在當前類中使用
【注意】:一般情況下,成員方法的訪問限定符用public(公共的),供外部使用
final: 最終方法,此方法不能被子類重寫(覆蓋),但可重載
static: 靜態方法(類方法) 【調用方式】:對象.方法名 或 類名.方法名
synchronized: 線程安全的
abstract: 抽象方法
native:此方法中可以使用其他如c/c++語言來實現一些底層操作
類的實例化
new 運算符:在堆內存上分配空間
構造方法:
構造方法在創建新的實例對象時起作用
構造方法通常用來初始化實例對象,例如: 初始化成員域或設置工作環境
可以含有多種構造方法(重載),但必須具有不同的參數列表
構造方法的特點:
與類同名
沒有返回值,不能加void
通過new 關鍵字調用
默認的構造方法
n 如果沒有顯式地定義類的構造方法,則系統會爲該類定義一個默認的構造方法。該構造方法不含任何參數。
格式爲:類名( )
{ }
初始化的默認值: 基本數值類型: 0; boolean: false; 引用數據類型: null
最好自己寫構造方法
一旦在類中定義了帶參的構造方法,系統就不會再創建這個默認無參的構造方法了。所以如果程序中用到此構造方法的話,必須自己定義上。
【調用順序】:先調用父類的構造方法,再調用自身的構造方法
繼承性:extends : 子類可以直接使用父類的所有的非私有(private)的屬性和方法
【繼承的優點】:提高代碼的複用率,精簡代碼
Java中只有單繼承:即每個類只能有一個父類
this: 指當前對象
super: 指當前對象的父類。 用於調用父類的構造方法
【示例程序】
class A {
int a = 1;
double d = 2.0;
void show() {
System.out.println("Class A: a=" + a + "\td=" + d);
}
}
class B extends A {
float a = 3.0f;
String d = "Java program.";
void show() {
super.show();
System.out.println("Class B: a=" + a + "\td=" + d);
}
}
public class Test {
public static void main(String[]args) {
A a = new A();
a.show(); //輸出:Class A: a=1 d=2.0
A b = new B(); //輸出:Class A: a=1 d=2.0
// Class B: a=3.0 d=Java program.
b.show();
}
}
多態性:
靜態多態(編譯時多態):方法重載overload
動態多態(運行時多態):方法重寫(覆蓋)override (重寫只存在於繼承關係中)
方法重寫:父類中非私有的方法,在子類中重新實現,並且方法名和父類中方法名必須一樣,參數必須一樣,返回值一樣。
【構成重寫的條件】:1.子類中方法名和父類中一樣 2.參數必須一樣 3.返回值一樣 4.子類中方法的訪問限定符的範圍要大於等於父類中方法的訪問限定符的範圍。
【運行時多態發生的條件】:父類的引用指向子類的對象,通過父類的引用去調用父類被子類重寫的方法的時候,發生動態多態。
【重載與重寫練習】看這樣一個題:
1) class Super{
2) public float getNum(){return 3.0f;}
3) }
4)
5) public class Sub extends Super{
6)
7) }
whichmethod, placed at line 6, will cause a compiler error?
A.public float getNum(){return 4.0f;} B. public void getNum(){}
C.public void getNum(double d){} D.public double getNum(float d){return 4.0d;}
Answer:B
A屬於方法的重寫,因爲修飾符和參數列表都一樣。
B出現編譯錯誤,如下:
Sub.java:6:Sub 中的getNum() 無法覆蓋Super 中的getNum();正在嘗試使用不兼容的返回類型
找到: void
需要: float
public void getNum(){}
^
1 錯誤
B既不是重寫也不是重載,重寫需要相同的返回值類型和參數列表,訪問修飾符的限制一定要大於或等於被重寫方法的訪問修飾符(public>protected>default>private);
【重載】:必須具有不同的參數列表;可以有不同的返回類型,只要參數列表不同就可以了;可以有不同的訪問修飾符;
把其看做是重載,那麼在java中是不能以返回值來區分重載方法的,所以b不對.
值傳遞與引用傳遞
值傳遞: 實參傳值給形參,形參的改變不會影響實參、基本數據類型的參數傳遞,都是值傳遞
引用傳遞:因爲引用中存的是地址,實參傳給形參地址,形參的改變會影響實參
【注意特殊】String:雖然是引用類型,但作爲參數傳遞時,採用的是值傳遞。
關鍵字:final
1) final 屬性:這樣的變量就是常量了。final的變量假如沒有賦予初值的話,其他方法就必須給它賦值,但只能賦值一次,以後其值不能被修改。而且執行效率也比普通的變量要高。
2) final 方法:最終方法,此方法不能被子類重寫(覆蓋),但可重載。而且定義爲final的方法執行效率高。
3) final 類:最終類,此類不能被繼承。java.lang.String就是一個final類。
【總結】
final的意思是“最終的”,它所修飾的東西都是最終的、不可改變的,效率也比較高。通常在Java的優化編程中會提及這一點。
【習題】
下面程序有錯嗎?
public class Something {
void doSomething() {
private String s = "";
int l = s.length();
}
}
答案: 錯。局部變量前不能放置任何訪問修飾符(private,public和protected)。final可以用來修飾局部變量(final是非訪問修飾符)。
l 下面程序有錯嗎?
public class Something {
public int addOne(final int x) {
return ++x;
}
}
答案: 錯。int x被修飾成final,意味着x是常量,不能在addOnemethod中被修改。
下面程序有錯嗎?
class Something {
int i;
public void doSomething() {
System.out.println("i = " + i);
}
}
答案: 正確。輸出的是"i= 0"。int i屬於instant variable (實例變量,或叫成員變量)。instant variable有default value。int的default value是0。
下面程序有錯嗎? 和上題只有一個地方不同,就是多了一個final。這難道就錯了嗎?
class Something {
final int i;
public void doSomething() {
System.out.println("i = " + i);
}
}
答案: 錯。final int i是個final的instant variable。final的instant variable沒有default value,必須在constructor (構造器)結束之前被賦予一個明確的值。可以修改爲"final int i = 0;"。
【經典問題】final關鍵字到底修飾了什麼?
final使得被修飾的變量“不變”,但是由於對象型變量的本質是“引用”,使得“不變”也有了兩種含義:引用本身的不變和引用指向的對象不變。
引用本身的不變:
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//編譯器錯誤
引用指向的對象不變:
final StringBuffer a=new StringBuffer("immutable");
a.append(" broken!"); //編譯通過
System.out.println(a); //運行結果:immutable broken!
可見,final只對引用的“值”(即引用所指向的那個對象的內存地址)有效,它迫使引用只能指向初始指向的那個對象,改變它的指向會導致編譯器錯誤。至於它所指向的對象的變化,final是不負責的。這很類似==操作符:==操作符只負責引用的“值”相等,至於這個地址所指向的對象內容是否相等,==操作符是不管的。
理解final問題有很重要的含義。許多程序漏洞都基於此——final只能保證引用永遠指向固定對象,不能保證那個對象的狀態不變。在多線程的操作中,一個對象會被多個線程共享或修改,一個線程對對象無意識的修改可能會導致另一個使用此對象的線程崩潰。一個錯誤的解決方法就是在此對象新建的時候把它聲明爲final,意圖使得它“永遠不變”。其實那是徒勞的。
【思考】final和abstract是否可同時修飾一個方法
內部類
class EnclosingOne{
public class InsideOne{}
}
public class InnerTest{
public static void main(String args[]){
EnclosingOne eo=new EnclosingOne();
//insert code here
}
}
A.InsideOne ei=eo.new InsideOne();
B.eo.InsideOne ei=eo.new InsideOne();
C.InsideOne ei=EnclosingOne.new InsideOne();
D.InsideOne ei=eo.new InsideOne();
E.EnclosingOne.InsideOne ei=eo.new InsideOne();
Answer:E
包裝類:擴充功能
基本數據類型的變量一般分配在棧上,其包裝類的對象分配在堆上。
八種:Integer、Character、Boolean、Byte、Short、Long、Double、Float
Integer:
常用方法:
Integer.parseInt(..)
Integer.valueOf(..)
Integer.toString(..)
【程序示例】
l 判斷從控制檯輸入的是否是一個整數
import java.util.Scanner;
public class E {
public static void main(String[]args) {
Scanner sc = new Scanner(System.in);
System.out.println("請輸入整數:");
try {
int i = Integer.parseInt(sc.next());
System.out.println(i);
} catch (Exception e) {
System.out.println("您輸入的不是整數!");
}
}
}