java繼承
繼承的概念
繼承是java面向?qū)ο缶幊碳夹g(shù)的一塊基石惧互,因?yàn)樗试S創(chuàng)建分等級(jí)層次的類(lèi)建钥。
繼承就是子類(lèi)繼承父類(lèi)的特征和行為,使得子類(lèi)對(duì)象(實(shí)例)具有父類(lèi)的實(shí)例域和方法,或子類(lèi)從父類(lèi)繼承方法戚炫,使得子類(lèi)具有父類(lèi)相同的行為袭厂。
生活中的繼承:
兔子和羊?qū)儆谑巢輨?dòng)物類(lèi)墨吓,獅子和豹屬于食肉動(dòng)物類(lèi)。
食草動(dòng)物和食肉動(dòng)物又是屬于動(dòng)物類(lèi)纹磺。
所以繼承需要符合的關(guān)系是:is-a帖烘,父類(lèi)更通用,子類(lèi)更具體橄杨。
雖然食草動(dòng)物和食肉動(dòng)物都是屬于動(dòng)物秘症,但是兩者的屬性和行為上有差別,所以子類(lèi)會(huì)具有父類(lèi)的一般特性也會(huì)具有自身的特性式矫。
類(lèi)的繼承格式
在 Java 中通過(guò) extends 關(guān)鍵字可以申明一個(gè)類(lèi)是從另外一個(gè)類(lèi)繼承而來(lái)的乡摹,一般形式如下:
類(lèi)的繼承格式
class 父類(lèi) {
}
class 子類(lèi) extends 父類(lèi) {
}
為什么需要繼承
接下來(lái)我們通過(guò)實(shí)例來(lái)說(shuō)明這個(gè)需求。
開(kāi)發(fā)動(dòng)物類(lèi)采转,其中動(dòng)物分別為企鵝以及老鼠聪廉,要求如下:
- 企鵝:屬性(姓名,id)故慈,方法(吃板熊,睡,自我介紹)
- 老鼠:屬性(姓名惯悠,id)邻邮,方法(吃,睡克婶,自我介紹)
企鵝類(lèi):
public class Penguin {
private String name;
private int id;
public Penguin(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好筒严!我是" + id + "號(hào)" + name + ".");
}
}
老鼠類(lèi):
public class Mouse {
private String name;
private int id;
public Mouse(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好丹泉!我是" + id + "號(hào)" + name + ".");
}
}
從這兩段代碼可以看出來(lái),代碼存在重復(fù)了鸭蛙,導(dǎo)致后果就是代碼量大且臃腫摹恨,而且維護(hù)性不高(維護(hù)性主要是后期需要修改的時(shí)候,就需要修改很多的代碼娶视,容易出錯(cuò))晒哄,所以要從根本上解決這兩段代碼的問(wèn)題,就需要繼承肪获,將兩段代碼中相同的部分提取出來(lái)組成 一個(gè)父類(lèi):
公共父類(lèi):
public class Animal {
private String name;
private int id;
public Animal(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好寝凌!我是" + id + "號(hào)" + name + ".");
}
}
這個(gè)Animal類(lèi)就可以作為一個(gè)父類(lèi),然后企鵝類(lèi)和老鼠類(lèi)繼承這個(gè)類(lèi)之后孝赫,就具有父類(lèi)當(dāng)中的屬性和方法较木,子類(lèi)就不會(huì)存在重復(fù)的代碼,維護(hù)性也提高青柄,代碼也更加簡(jiǎn)潔伐债,提高代碼的復(fù)用性(復(fù)用性主要是可以多次使用,不用再多次寫(xiě)同樣的代碼) 繼承之后的代碼:
企鵝類(lèi):
public class Penguin extends Animal {
public Penguin(String myName, int myid) {
super(myName, myid);
}
}
老鼠類(lèi):
public class Mouse extends Animal {
public Mouse(String myName, int myid) {
super(myName, myid);
}
}
繼承的特性
子類(lèi)擁有父類(lèi)非private的屬性致开,方法峰锁。
子類(lèi)可以擁有自己的屬性和方法,即子類(lèi)可以對(duì)父類(lèi)進(jìn)行擴(kuò)展双戳。
子類(lèi)可以用自己的方式實(shí)現(xiàn)父類(lèi)的方法虹蒋。
Java的繼承是單繼承,但是可以多重繼承拣技,單繼承就是一個(gè)子類(lèi)只能繼承一個(gè)父類(lèi)千诬,多重繼承就是,例如A類(lèi)繼承B類(lèi)膏斤,B類(lèi)繼承C類(lèi)徐绑,所以按照關(guān)系就是C類(lèi)是B類(lèi)的父類(lèi),B類(lèi)是A類(lèi)的父類(lèi)莫辨,這是java繼承區(qū)別于C++繼承的一個(gè)特性傲茄。
提高了類(lèi)之間的耦合性(繼承的缺點(diǎn),耦合度高就會(huì)造成代碼之間的聯(lián)系)沮榜。
繼承關(guān)鍵字
繼承可以使用 extends 和 implements 這兩個(gè)關(guān)鍵字來(lái)實(shí)現(xiàn)繼承盘榨,而且所有的類(lèi)都是繼承于 java.lang.Object,當(dāng)一個(gè)類(lèi)沒(méi)有繼承的兩個(gè)關(guān)鍵字蟆融,則默認(rèn)繼承object(這個(gè)類(lèi)在 java.lang 包中草巡,所以不需要 import)祖先類(lèi)。
extends關(guān)鍵字
在 Java 中型酥,類(lèi)的繼承是單一繼承山憨,也就是說(shuō)查乒,一個(gè)子類(lèi)只能擁有一個(gè)父類(lèi),所以 extends 只能繼承一個(gè)類(lèi)郁竟。
extends 關(guān)鍵字
public class Animal {
private String name;
private int id;
public Animal(String myName, String myid) {
//初始化屬性值
}
public void eat() { //吃東西方法的具體實(shí)現(xiàn) }
public void sleep() { //睡覺(jué)方法的具體實(shí)現(xiàn) }
}
public class Penguin extends Animal{
}
implements關(guān)鍵字
使用 implements 關(guān)鍵字可以變相的使java具有多繼承的特性玛迄,使用范圍為類(lèi)繼承接口的情況,可以同時(shí)繼承多個(gè)接口(接口跟接口之間采用逗號(hào)分隔)棚亩。
implements 關(guān)鍵字
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
super 與 this 關(guān)鍵字
super關(guān)鍵字:我們可以通過(guò)super關(guān)鍵字來(lái)實(shí)現(xiàn)對(duì)父類(lèi)成員的訪(fǎng)問(wèn)蓖议,用來(lái)引用當(dāng)前對(duì)象的父類(lèi)。
this關(guān)鍵字:指向自己的引用讥蟆。
class Animal {
void eat() {
System.out.println("animal : eat");
}
}
class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 調(diào)用自己的方法
super.eat(); // super 調(diào)用父類(lèi)方法
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}
輸出結(jié)果為:
animal : eat
dog : eat
animal : eat
final關(guān)鍵字
final 關(guān)鍵字聲明類(lèi)可以把類(lèi)定義為不能繼承的勒虾,即最終類(lèi);或者用于修飾方法瘸彤,該方法不能被子類(lèi)重寫(xiě):
聲明類(lèi):
final class 類(lèi)名 {//類(lèi)體}
聲明方法:
修飾符(public/private/default/protected) final 返回值類(lèi)型 方法名(){//方法體}
注:實(shí)例變量也可以被定義為 final从撼,被定義為 final 的變量不能被修改。被聲明為 final 類(lèi)的方法自動(dòng)地聲明為 final钧栖,但是實(shí)例變量并不是 final
構(gòu)造器
子類(lèi)不能繼承父類(lèi)的構(gòu)造器(構(gòu)造方法或者構(gòu)造函數(shù)),但是父類(lèi)的構(gòu)造器帶有參數(shù)的婆翔,則必須在子類(lèi)的構(gòu)造器中顯式地通過(guò)super關(guān)鍵字調(diào)用父類(lèi)的構(gòu)造器并配以適當(dāng)?shù)膮?shù)列表拯杠。
如果父類(lèi)有無(wú)參構(gòu)造器,則在子類(lèi)的構(gòu)造器中用super調(diào)用父類(lèi)構(gòu)造器不是必須的啃奴,如果沒(méi)有使用super關(guān)鍵字潭陪,系統(tǒng)會(huì)自動(dòng)調(diào)用父類(lèi)的無(wú)參構(gòu)造器。
class SuperClass {
private int n;
SuperClass(){
System.out.println("SuperClass()");
}
SuperClass(int n) {
System.out.println("SuperClass(int n)");
this.n = n;
}
}
class SubClass extends SuperClass{
private int n;
SubClass(){
super(300);
System.out.println("SubClass");
}
public SubClass(int n){
System.out.println("SubClass(int n):"+n);
this.n = n;
}
}
public class TestSuperSub{
public static void main (String args[]){
SubClass sc = new SubClass();
SubClass sc2 = new SubClass(200);
}
}
輸出結(jié)果為:
SuperClass(int n)
SubClass
SuperClass()
SubClass(int n):200
Java 重寫(xiě)(Override)與重載(Overload)
重寫(xiě)(Override)
重寫(xiě)是子類(lèi)對(duì)父類(lèi)的允許訪(fǎng)問(wèn)的方法的實(shí)現(xiàn)過(guò)程進(jìn)行重新編寫(xiě), 返回值和形參都不能改變最蕾。即外殼不變依溯,核心重寫(xiě)!
重寫(xiě)的好處在于子類(lèi)可以根據(jù)需要瘟则,定義特定于自己的行為黎炉。 也就是說(shuō)子類(lèi)能夠根據(jù)需要實(shí)現(xiàn)父類(lèi)的方法。
重寫(xiě)方法不能拋出新的檢查異炒着。或者比被重寫(xiě)方法申明更加寬泛的異常。例如: 父類(lèi)的一個(gè)方法申明了一個(gè)檢查異常 IOException,但是在重寫(xiě)這個(gè)方法的時(shí)候不能拋出 Exception 異常三椿,因?yàn)?Exception 是 IOException 的父類(lèi)沃疮,只能拋出 IOException 的子類(lèi)異常。
在面向?qū)ο笤瓌t里菌赖,重寫(xiě)意味著可以重寫(xiě)任何現(xiàn)有方法缭乘。實(shí)例如下:
TestDog.java 文件代碼:
class Animal{
public void move(){
System.out.println("動(dòng)物可以移動(dòng)");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 對(duì)象
Animal b = new Dog(); // Dog 對(duì)象
a.move();// 執(zhí)行 Animal 類(lèi)的方法
b.move();//執(zhí)行 Dog 類(lèi)的方法
}
}
以上實(shí)例編譯運(yùn)行結(jié)果如下:
動(dòng)物可以移動(dòng)
狗可以跑和走
在上面的例子中可以看到,盡管b屬于Animal類(lèi)型琉用,但是它運(yùn)行的是Dog類(lèi)的move方法堕绩。
這是由于在編譯階段策幼,只是檢查參數(shù)的引用類(lèi)型。
然而在運(yùn)行時(shí)逛尚,Java虛擬機(jī)(JVM)指定對(duì)象的類(lèi)型并且運(yùn)行該對(duì)象的方法垄惧。
因此在上面的例子中,之所以能編譯成功绰寞,是因?yàn)锳nimal類(lèi)中存在move方法到逊,然而運(yùn)行時(shí),運(yùn)行的是特定對(duì)象的方法滤钱。
思考以下例子:
TestDog.java 文件代碼:
class Animal{
public void move(){
System.out.println("動(dòng)物可以移動(dòng)");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
public void bark(){
System.out.println("狗可以吠叫");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 對(duì)象
Animal b = new Dog(); // Dog 對(duì)象
a.move();// 執(zhí)行 Animal 類(lèi)的方法
b.move();//執(zhí)行 Dog 類(lèi)的方法
b.bark();
}
}
以上實(shí)例編譯運(yùn)行結(jié)果如下:
TestDog.java:30: cannot find symbol
symbol : method bark()
location: class Animal
b.bark();
^
該程序?qū)伋鲆粋€(gè)編譯錯(cuò)誤觉壶,因?yàn)閎的引用類(lèi)型Animal沒(méi)有bark方法。
方法的重寫(xiě)規(guī)則
- 參數(shù)列表必須完全與被重寫(xiě)方法的相同件缸;
- 返回類(lèi)型必須完全與被重寫(xiě)方法的返回類(lèi)型相同铜靶;
- 訪(fǎng)問(wèn)權(quán)限不能比父類(lèi)中被重寫(xiě)的方法的訪(fǎng)問(wèn)權(quán)限更低。例如:如果父類(lèi)的一個(gè)方法被聲明為public他炊,那么在子類(lèi)中重寫(xiě)該方法就不能聲明為protected争剿。
- 父類(lèi)的成員方法只能被它的子類(lèi)重寫(xiě)。
- 聲明為final的方法不能被重寫(xiě)痊末。
- 聲明為static的方法不能被重寫(xiě)蚕苇,但是能夠被再次聲明。
- 子類(lèi)和父類(lèi)在同一個(gè)包中凿叠,那么子類(lèi)可以重寫(xiě)父類(lèi)所有方法涩笤,除了聲明為private和final的方法。
- 子類(lèi)和父類(lèi)不在同一個(gè)包中盒件,那么子類(lèi)只能夠重寫(xiě)父類(lèi)的聲明為public和protected的非final方法蹬碧。
- 重寫(xiě)的方法能夠拋出任何非強(qiáng)制異常,無(wú)論被重寫(xiě)的方法是否拋出異常炒刁。但是恩沽,重寫(xiě)的方法不能拋出新的強(qiáng)制性異常,或者比被重寫(xiě)方法聲明的更廣泛的強(qiáng)制性異常切心,反之則可以飒筑。
- 構(gòu)造方法不能被重寫(xiě)。
- 如果不能繼承一個(gè)方法绽昏,則不能重寫(xiě)這個(gè)方法协屡。
Super關(guān)鍵字的使用
當(dāng)需要在子類(lèi)中調(diào)用父類(lèi)的被重寫(xiě)方法時(shí),要使用super關(guān)鍵字全谤。
TestDog.java 文件代碼:
class Animal{
public void move(){
System.out.println("動(dòng)物可以移動(dòng)");
}
}
class Dog extends Animal{
public void move(){
super.move(); // 應(yīng)用super類(lèi)的方法
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal b = new Dog(); // Dog 對(duì)象
b.move(); //執(zhí)行 Dog類(lèi)的方法
}
}
以上實(shí)例編譯運(yùn)行結(jié)果如下:
動(dòng)物可以移動(dòng)
狗可以跑和走
重載(Overload)
重載(overloading) 是在一個(gè)類(lèi)里面肤晓,方法名字相同,而參數(shù)不同。返回類(lèi)型可以相同也可以不同补憾。
每個(gè)重載的方法(或者構(gòu)造函數(shù))都必須有一個(gè)獨(dú)一無(wú)二的參數(shù)類(lèi)型列表漫萄。
最常用的地方就是構(gòu)造器的重載。
重載規(guī)則
- 被重載的方法必須改變參數(shù)列表(參數(shù)個(gè)數(shù)或類(lèi)型或順序不一樣)盈匾;
- 被重載的方法可以改變返回類(lèi)型腾务;
- 被重載的方法可以改變?cè)L問(wèn)修飾符;
- 被重載的方法可以聲明新的或更廣的檢查異常削饵;
- 方法能夠在同一個(gè)類(lèi)中或者在一個(gè)子類(lèi)中被重載岩瘦。
- 無(wú)法以返回值類(lèi)型作為重載函數(shù)的區(qū)分標(biāo)準(zhǔn)。
Overloading.java 文件代碼:
public class Overloading {
public int test(){
System.out.println("test1");
return 1;
}
public void test(int a){
System.out.println("test2");
}
//以下兩個(gè)參數(shù)類(lèi)型順序不同
public String test(int a,String s){
System.out.println("test3");
return "returntest3";
}
public String test(String s,int a){
System.out.println("test4");
return "returntest4";
}
public static void main(String[] args){
Overloading o = new Overloading();
System.out.println(o.test());
o.test(1);
System.out.println(o.test(1,"test3"));
System.out.println(o.test("test4",1));
}
}
重寫(xiě)與重載之間的區(qū)別
區(qū)別點(diǎn) | 重載方法 | 重寫(xiě)方法 |
---|---|---|
參數(shù)列表 | 必須修改 | 一定不能修改 |
返回類(lèi)型 | 可以修改 | 一定不能修改 |
異常 | 可以修改 | 可以減少或刪除窿撬,一定不能拋出新的或者更廣的異常 |
訪(fǎng)問(wèn) | 可以修改 | 一定不能做更嚴(yán)格的限制(可以降低限制) |
總結(jié)
方法的重寫(xiě)(Overriding)和重載(Overloading)是java多態(tài)性的不同表現(xiàn)启昧,重寫(xiě)是父類(lèi)與子類(lèi)之間多態(tài)性的一種表現(xiàn),重載可以理解成多態(tài)的具體表現(xiàn)形式劈伴。
- 方法重載是一個(gè)類(lèi)中定義了多個(gè)方法名相同,而他們的參數(shù)的數(shù)量不同或數(shù)量相同而類(lèi)型和次序不同,則稱(chēng)為方法的重載(Overloading)密末。
- 方法重寫(xiě)是在子類(lèi)存在方法與父類(lèi)的方法的名字相同,而且參數(shù)的個(gè)數(shù)與類(lèi)型一樣,返回值也一樣的方法,就稱(chēng)為重寫(xiě)(Overriding)。
- 方法重載是一個(gè)類(lèi)的多態(tài)性表現(xiàn),而方法重寫(xiě)是子類(lèi)與父類(lèi)的一種多態(tài)性表現(xiàn)跛璧。
java多態(tài)
多態(tài)是同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力严里。
多態(tài)就是同一個(gè)接口,使用不同的實(shí)例而執(zhí)行不同操作追城,如圖所示:
多態(tài)性是對(duì)象多種表現(xiàn)形式的體現(xiàn)田炭。
現(xiàn)實(shí)中,比如我們按下 F1 鍵這個(gè)動(dòng)作:
- 如果當(dāng)前在 Flash 界面下彈出的就是 AS 3 的幫助文檔漓柑;
- 如果當(dāng)前在 Word 下彈出的就是 Word 幫助;
- 在 Windows 下彈出的就是 Windows 幫助和支持叨吮。
同一個(gè)事件發(fā)生在不同的對(duì)象上會(huì)產(chǎn)生不同的結(jié)果辆布。
多態(tài)的優(yōu)點(diǎn)
- 消除類(lèi)型之間的耦合關(guān)系
- 可替換性
- 可擴(kuò)充性
- 接口性
- 靈活性
- 簡(jiǎn)化性
多態(tài)存在的三個(gè)必要條件
- 繼承
- 重寫(xiě)
- 父類(lèi)引用指向子類(lèi)對(duì)象
比如:
Parent p = new Child();
當(dāng)使用多態(tài)方式調(diào)用方法時(shí),首先檢查父類(lèi)中是否有該方法茶鉴,如果沒(méi)有锋玲,則編譯錯(cuò)誤;如果有涵叮,再去調(diào)用子類(lèi)的同名方法惭蹂。
多態(tài)的好處:可以使程序有良好的擴(kuò)展,并可以對(duì)所有類(lèi)的對(duì)象進(jìn)行通用處理割粮。
以下是一個(gè)多態(tài)實(shí)例的演示盾碗,詳細(xì)說(shuō)明請(qǐng)看注釋?zhuān)?/p>
Test.java 文件代碼:
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 對(duì)象調(diào)用 show 方法
show(new Dog()); // 以 Dog 對(duì)象調(diào)用 show 方法
Animal a = new Cat(); // 向上轉(zhuǎn)型
a.eat(); // 調(diào)用的是 Cat 的 eat
Cat c = (Cat)a; // 向下轉(zhuǎn)型
c.work(); // 調(diào)用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 類(lèi)型判斷
if (a instanceof Cat) { // 貓做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃魚(yú)");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨頭");
}
public void work() {
System.out.println("看家");
}
}
執(zhí)行以上程序,輸出結(jié)果為:
吃魚(yú)
抓老鼠
吃骨頭
看家
吃魚(yú)
抓老鼠
虛方法
我們將介紹在Java中舀瓢,當(dāng)設(shè)計(jì)類(lèi)時(shí)廷雅,被重寫(xiě)的方法的行為怎樣影響多態(tài)性。
我們已經(jīng)討論了方法的重寫(xiě),也就是子類(lèi)能夠重寫(xiě)父類(lèi)的方法航缀。
當(dāng)子類(lèi)對(duì)象調(diào)用重寫(xiě)的方法時(shí)商架,調(diào)用的是子類(lèi)的方法,而不是父類(lèi)中被重寫(xiě)的方法芥玉。
要想調(diào)用父類(lèi)中被重寫(xiě)的方法蛇摸,則必須使用關(guān)鍵字super。
Employee.java 文件代碼:
/* 文件名 : Employee.java */
public class Employee {
private String name;
private String address;
private int number;
public Employee(String name, String address, int number) {
System.out.println("Employee 構(gòu)造函數(shù)");
this.name = name;
this.address = address;
this.number = number;
}
public void mailCheck() {
System.out.println("郵寄支票給: " + this.name
+ " " + this.address);
}
public String toString() {
return name + " " + address + " " + number;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public void setAddress(String newAddress) {
address = newAddress;
}
public int getNumber() {
return number;
}
}
假設(shè)下面的類(lèi)繼承Employee類(lèi):
Salary.java 文件代碼:
/* 文件名 : Salary.java */
public class Salary extends Employee
{
private double salary; // 全年工資
public Salary(String name, String address, int number, double salary) {
super(name, address, number);
setSalary(salary);
}
public void mailCheck() {
System.out.println("Salary 類(lèi)的 mailCheck 方法 ");
System.out.println("郵寄支票給:" + getName()
+ " 灿巧,工資為:" + salary);
}
public double getSalary() {
return salary;
}
public void setSalary(double newSalary) {
if(newSalary >= 0.0) {
salary = newSalary;
}
}
public double computePay() {
System.out.println("計(jì)算工資赶袄,付給:" + getName());
return salary/52;
}
}
現(xiàn)在我們仔細(xì)閱讀下面的代碼,嘗試給出它的輸出結(jié)果:
VirtualDemo.java 文件代碼:
/* 文件名 : VirtualDemo.java */
public class VirtualDemo {
public static void main(String [] args) {
Salary s = new Salary("員工 A", "北京", 3, 3600.00);
Employee e = new Salary("員工 B", "上海", 2, 2400.00);
System.out.println("使用 Salary 的引用調(diào)用 mailCheck -- ");
s.mailCheck();
System.out.println("\n使用 Employee 的引用調(diào)用 mailCheck--");
e.mailCheck();
}
}
以上實(shí)例編譯運(yùn)行結(jié)果如下:
Employee 構(gòu)造函數(shù)
Employee 構(gòu)造函數(shù)
使用 Salary 的引用調(diào)用 mailCheck --
Salary 類(lèi)的 mailCheck 方法
郵寄支票給:?jiǎn)T工 A 砸烦,工資為:3600.0
使用 Employee 的引用調(diào)用 mailCheck--
Salary 類(lèi)的 mailCheck 方法
郵寄支票給:?jiǎn)T工 B 弃鸦,工資為:2400.0
例子解析
實(shí)例中,實(shí)例化了兩個(gè) Salary 對(duì)象:一個(gè)使用 Salary 引用 s幢痘,另一個(gè)使用 Employee 引用 e唬格。
當(dāng)調(diào)用 s.mailCheck() 時(shí),編譯器在編譯時(shí)會(huì)在 Salary 類(lèi)中找到 mailCheck()颜说,執(zhí)行過(guò)程 JVM 就調(diào)用 Salary 類(lèi)的 mailCheck()购岗。
因?yàn)?e 是 Employee 的引用,所以調(diào)用 e 的 mailCheck() 方法時(shí)门粪,編譯器會(huì)去 Employee 類(lèi)查找 mailCheck() 方法 喊积。
在編譯的時(shí)候,編譯器使用 Employee 類(lèi)中的 mailCheck() 方法驗(yàn)證該語(yǔ)句玄妈, 但是在運(yùn)行的時(shí)候乾吻,Java虛擬機(jī)(JVM)調(diào)用的是 Salary 類(lèi)中的 mailCheck() 方法。
以上整個(gè)過(guò)程被稱(chēng)為虛擬方法調(diào)用拟蜻,該方法被稱(chēng)為虛擬方法绎签。
Java中所有的方法都能以這種方式表現(xiàn),因此酝锅,重寫(xiě)的方法能在運(yùn)行時(shí)調(diào)用诡必,不管編譯的時(shí)候源代碼中引用變量是什么數(shù)據(jù)類(lèi)型。
多態(tài)的實(shí)現(xiàn)方式
方式一:重寫(xiě):
這個(gè)內(nèi)容已經(jīng)在上一章節(jié)詳細(xì)講過(guò)搔扁,就不再闡述爸舒,詳細(xì)可訪(fǎng)問(wèn):Java 重寫(xiě)(Override)與重載(Overload)。
方式二:接口
生活中的接口最具代表性的就是插座稿蹲,例如一個(gè)三接頭的插頭都能接在三孔插座中扭勉,因?yàn)檫@個(gè)是每個(gè)國(guó)家都有各自規(guī)定的接口規(guī)則,有可能到國(guó)外就不行苛聘,那是因?yàn)閲?guó)外自己定義的接口類(lèi)型剖效。
java中的接口類(lèi)似于生活中的接口嫉入,就是一些方法特征的集合,但沒(méi)有方法的實(shí)現(xiàn)璧尸。具體可以看 java接口 這一章節(jié)的內(nèi)容咒林。
方式三:抽象類(lèi)和抽象方法
Java抽象類(lèi)
在面向?qū)ο蟮母拍钪校械膶?duì)象都是通過(guò)類(lèi)來(lái)描繪的爷光,但是反過(guò)來(lái)垫竞,并不是所有的類(lèi)都是用來(lái)描繪對(duì)象的,如果一個(gè)類(lèi)中沒(méi)有包含足夠的信息來(lái)描繪一個(gè)具體的對(duì)象蛀序,這樣的類(lèi)就是抽象類(lèi)欢瞪。
抽象類(lèi)除了不能實(shí)例化對(duì)象之外,類(lèi)的其它功能依然存在徐裸,成員變量遣鼓、成員方法和構(gòu)造方法的訪(fǎng)問(wèn)方式和普通類(lèi)一樣。
由于抽象類(lèi)不能實(shí)例化對(duì)象重贺,所以抽象類(lèi)必須被繼承骑祟,才能被使用。也是因?yàn)檫@個(gè)原因气笙,通常在設(shè)計(jì)階段決定要不要設(shè)計(jì)抽象類(lèi)次企。
父類(lèi)包含了子類(lèi)集合的常見(jiàn)的方法,但是由于父類(lèi)本身是抽象的潜圃,所以不能使用這些方法缸棵。
在Java中抽象類(lèi)表示的是一種繼承關(guān)系,一個(gè)類(lèi)只能繼承一個(gè)抽象類(lèi)谭期,而一個(gè)類(lèi)卻可以實(shí)現(xiàn)多個(gè)接口堵第。
抽象類(lèi)
在Java語(yǔ)言中使用abstract class來(lái)定義抽象類(lèi)。如下實(shí)例:
Employee.java 文件代碼:
/* 文件名 : Employee.java */
public abstract class Employee
{
private String name;
private String address;
private int number;
public Employee(String name, String address, int number)
{
System.out.println("Constructing an Employee");
this.name = name;
this.address = address;
this.number = number;
}
public double computePay()
{
System.out.println("Inside Employee computePay");
return 0.0;
}
public void mailCheck()
{
System.out.println("Mailing a check to " + this.name
+ " " + this.address);
}
public String toString()
{
return name + " " + address + " " + number;
}
public String getName()
{
return name;
}
public String getAddress()
{
return address;
}
public void setAddress(String newAddress)
{
address = newAddress;
}
public int getNumber()
{
return number;
}
}
注意到該Employee類(lèi)沒(méi)有什么不同隧出,盡管該類(lèi)是抽象類(lèi)型诚,但是它仍然有3個(gè)成員變量,7個(gè)成員方法和1個(gè)構(gòu)造方法鸳劳。 現(xiàn)在如果你嘗試如下的例子:
AbstractDemo.java 文件代碼:
/* 文件名 : AbstractDemo.java */
public class AbstractDemo
{
public static void main(String [] args)
{
/* 以下是不允許的,會(huì)引發(fā)錯(cuò)誤 */
Employee e = new Employee("George W.", "Houston, TX", 43);
System.out.println("\n Call mailCheck using Employee reference--");
e.mailCheck();
}
}
當(dāng)你嘗試編譯AbstractDemo類(lèi)時(shí)也搓,會(huì)產(chǎn)生如下錯(cuò)誤:
Employee.java:46: Employee is abstract; cannot be instantiated
Employee e = new Employee("George W.", "Houston, TX", 43);
^
1 error
繼承抽象類(lèi)
我們能通過(guò)一般的方法繼承Employee類(lèi):
Salary.java 文件代碼:
/* 文件名 : Salary.java */
public class Salary extends Employee
{
private double salary; //Annual salary
public Salary(String name, String address, int number, double
salary)
{
super(name, address, number);
setSalary(salary);
}
public void mailCheck()
{
System.out.println("Within mailCheck of Salary class ");
System.out.println("Mailing check to " + getName()
+ " with salary " + salary);
}
public double getSalary()
{
return salary;
}
public void setSalary(double newSalary)
{
if(newSalary >= 0.0)
{
salary = newSalary;
}
}
public double computePay()
{
System.out.println("Computing salary pay for " + getName());
return salary/52;
}
}
盡管我們不能實(shí)例化一個(gè)Employee類(lèi)的對(duì)象赏廓,但是如果我們實(shí)例化一個(gè)Salary類(lèi)對(duì)象,該對(duì)象將從 Employee 類(lèi)繼承7個(gè)成員方法傍妒,且通過(guò)該方法可以設(shè)置或獲取三個(gè)成員變量幔摸。
AbstractDemo.java 文件代碼:
/* 文件名 : AbstractDemo.java */
public class AbstractDemo
{
public static void main(String [] args)
{
Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);
Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);
System.out.println("Call mailCheck using Salary reference --");
s.mailCheck();
System.out.println("\n Call mailCheck using Employee reference--");
e.mailCheck();
}
}
以上程序編譯運(yùn)行結(jié)果如下:
Constructing an Employee
Constructing an Employee
Call mailCheck using Salary reference --
Within mailCheck of Salary class
Mailing check to Mohd Mohtashim with salary 3600.0
Call mailCheck using Employee reference--
Within mailCheck of Salary class
Mailing check to John Adams with salary 2400.
抽象方法
如果你想設(shè)計(jì)這樣一個(gè)類(lèi),該類(lèi)包含一個(gè)特別的成員方法颤练,該方法的具體實(shí)現(xiàn)由它的子類(lèi)確定既忆,那么你可以在父類(lèi)中聲明該方法為抽象方法。
Abstract關(guān)鍵字同樣可以用來(lái)聲明抽象方法,抽象方法只包含一個(gè)方法名患雇,而沒(méi)有方法體跃脊。
抽象方法沒(méi)有定義,方法名后面直接跟一個(gè)分號(hào)苛吱,而不是花括號(hào)酪术。
public abstract class Employee
{
private String name;
private String address;
private int number;
public abstract double computePay();
//其余代碼
}
聲明抽象方法會(huì)造成以下兩個(gè)結(jié)果:
- 如果一個(gè)類(lèi)包含抽象方法,那么該類(lèi)必須是抽象類(lèi)翠储。
- 任何子類(lèi)必須重寫(xiě)父類(lèi)的抽象方法绘雁,或者聲明自身為抽象類(lèi)。
繼承抽象方法的子類(lèi)必須重寫(xiě)該方法援所。否則庐舟,該子類(lèi)也必須聲明為抽象類(lèi)。最終住拭,必須有子類(lèi)實(shí)現(xiàn)該抽象方法挪略,否則,從最初的父類(lèi)到最終的子類(lèi)都不能用來(lái)實(shí)例化對(duì)象废酷。
如果Salary類(lèi)繼承了Employee類(lèi)瘟檩,那么它必須實(shí)現(xiàn)computePay()方法:
Salary.java 文件代碼:
/* 文件名 : Salary.java */
public class Salary extends Employee
{
private double salary; // Annual salary
public double computePay()
{
System.out.println("Computing salary pay for " + getName());
return salary/52;
}
//其余代碼
}
抽象類(lèi)總結(jié)規(guī)定
抽象類(lèi)不能被實(shí)例化(初學(xué)者很容易犯的錯(cuò)),如果被實(shí)例化澈蟆,就會(huì)報(bào)錯(cuò)墨辛,編譯無(wú)法通過(guò)。只有抽象類(lèi)的非抽象子類(lèi)可以創(chuàng)建對(duì)象趴俘。
抽象類(lèi)中不一定包含抽象方法睹簇,但是有抽象方法的類(lèi)必定是抽象類(lèi)。
抽象類(lèi)中的抽象方法只是聲明寥闪,不包含方法體太惠,就是不給出方法的具體實(shí)現(xiàn)也就是方法的具體功能。
構(gòu)造方法疲憋,類(lèi)方法(用static修飾的方法)不能聲明為抽象方法凿渊。
抽象類(lèi)的子類(lèi)必須給出抽象類(lèi)中的抽象方法的具體實(shí)現(xiàn),除非該子類(lèi)也是抽象類(lèi)缚柳。
java封裝
在面向?qū)ο蟪淌皆O(shè)計(jì)方法中埃脏,封裝(英語(yǔ):Encapsulation)是指一種將抽象性函式接口的實(shí)現(xiàn)細(xì)節(jié)部份包裝、隱藏起來(lái)的方法秋忙。
封裝可以被認(rèn)為是一個(gè)保護(hù)屏障彩掐,防止該類(lèi)的代碼和數(shù)據(jù)被外部類(lèi)定義的代碼隨機(jī)訪(fǎng)問(wèn)。
要訪(fǎng)問(wèn)該類(lèi)的代碼和數(shù)據(jù)灰追,必須通過(guò)嚴(yán)格的接口控制堵幽。
封裝最主要的功能在于我們能修改自己的實(shí)現(xiàn)代碼狗超,而不用修改那些調(diào)用我們代碼的程序片段。
適當(dāng)?shù)姆庋b可以讓程式碼更容易理解與維護(hù)朴下,也加強(qiáng)了程式碼的安全性努咐。
封裝的優(yōu)點(diǎn)
良好的封裝能夠減少耦合。
類(lèi)內(nèi)部的結(jié)構(gòu)可以自由修改桐猬。
可以對(duì)成員變量進(jìn)行更精確的控制麦撵。
隱藏信息,實(shí)現(xiàn)細(xì)節(jié)溃肪。
實(shí)現(xiàn)Java封裝的步驟
1.修改屬性的可見(jiàn)性來(lái)限制對(duì)屬性的訪(fǎng)問(wèn)(一般限制為private)免胃,例如:
public class Person {
private String name;
private int age;
}
這段代碼中,將 name 和 age 屬性設(shè)置為私有的惫撰,只能本類(lèi)才能訪(fǎng)問(wèn)羔沙,其他類(lèi)都訪(fǎng)問(wèn)不了,如此就對(duì)信息進(jìn)行了隱藏厨钻。
2.對(duì)每個(gè)值屬性提供對(duì)外的公共方法訪(fǎng)問(wèn)扼雏,也就是創(chuàng)建一對(duì)賦取值方法,用于對(duì)私有屬性的訪(fǎng)問(wèn)夯膀,例如:
public class Person{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
}
采用 this 關(guān)鍵字是為了解決實(shí)例變量(private String name)和局部變量(setName(String name)中的name變量)之間發(fā)生的同名的沖突诗充。
實(shí)例
讓我們來(lái)看一個(gè)java封裝類(lèi)的例子:
EncapTest.java 文件代碼:
/* 文件名: EncapTest.java */
public class EncapTest{
private String name;
private String idNum;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public String getIdNum(){
return idNum;
}
public void setAge( int newAge){
age = newAge;
}
public void setName(String newName){
name = newName;
}
public void setIdNum( String newId){
idNum = newId;
}
}
以上實(shí)例中public方法是外部類(lèi)訪(fǎng)問(wèn)該類(lèi)成員變量的入口。
通常情況下诱建,這些方法被稱(chēng)為getter和setter方法蝴蜓。
因此,任何要訪(fǎng)問(wèn)類(lèi)中私有成員變量的類(lèi)都要通過(guò)這些getter和setter方法俺猿。
通過(guò)如下的例子說(shuō)明EncapTest類(lèi)的變量怎樣被訪(fǎng)問(wèn):
RunEncap.java 文件代碼:
/* F文件名 : RunEncap.java */
public class RunEncap{
public static void main(String args[]){
EncapTest encap = new EncapTest();
encap.setName("James");
encap.setAge(20);
encap.setIdNum("12343ms");
System.out.print("Name : " + encap.getName()+
" Age : "+ encap.getAge());
}
}
以上代碼編譯運(yùn)行結(jié)果如下:
Name : James Age : 20
java接口
接口(英文:Interface)茎匠,在JAVA編程語(yǔ)言中是一個(gè)抽象類(lèi)型,是抽象方法的集合押袍,接口通常以interface來(lái)聲明诵冒。一個(gè)類(lèi)通過(guò)繼承接口的方式,從而來(lái)繼承接口的抽象方法谊惭。
接口并不是類(lèi)汽馋,編寫(xiě)接口的方式和類(lèi)很相似,但是它們屬于不同的概念圈盔。類(lèi)描述對(duì)象的屬性和方法豹芯。接口則包含類(lèi)要實(shí)現(xiàn)的方法。
除非實(shí)現(xiàn)接口的類(lèi)是抽象類(lèi)药磺,否則該類(lèi)要定義接口中的所有方法。
接口無(wú)法被實(shí)例化煤伟,但是可以被實(shí)現(xiàn)癌佩。一個(gè)實(shí)現(xiàn)接口的類(lèi)木缝,必須實(shí)現(xiàn)接口內(nèi)所描述的所有方法,否則就必須聲明為抽象類(lèi)围辙。另外我碟,在 Java 中,接口類(lèi)型可用來(lái)聲明一個(gè)變量姚建,他們可以成為一個(gè)空指針矫俺,或是被綁定在一個(gè)以此接口實(shí)現(xiàn)的對(duì)象。
接口與類(lèi)相似點(diǎn):
- 一個(gè)接口可以有多個(gè)方法掸冤。
- 接口文件保存在 .java 結(jié)尾的文件中厘托,文件名使用接口名。
- 接口的字節(jié)碼文件保存在 .class 結(jié)尾的文件中稿湿。
- 接口相應(yīng)的字節(jié)碼文件必須在與包名稱(chēng)相匹配的目錄結(jié)構(gòu)中铅匹。
接口與類(lèi)的區(qū)別:
- 接口不能用于實(shí)例化對(duì)象。
- 接口沒(méi)有構(gòu)造方法饺藤。
- 接口中所有的方法必須是抽象方法包斑。
- 接口不能包含成員變量,除了 static 和 final 變量涕俗。
- 接口不是被類(lèi)繼承了罗丰,而是要被類(lèi)實(shí)現(xiàn)。
- 接口支持多繼承再姑。
接口特性
- 接口中每一個(gè)方法也是隱式抽象的,接口中的方法會(huì)被隱式的指定為 public abstract(只能是 public abstract萌抵,其他修飾符都會(huì)報(bào)錯(cuò))。
- 接口中可以含有變量询刹,但是接口中的變量會(huì)被隱式的指定為 public static final 變量(并且只能是 public谜嫉,用 private 修飾會(huì)報(bào)編譯錯(cuò)誤)。
- 接口中的方法是不能在接口中實(shí)現(xiàn)的凹联,只能由實(shí)現(xiàn)接口的類(lèi)來(lái)實(shí)現(xiàn)接口中的方法沐兰。
抽象類(lèi)和接口的區(qū)別
- 抽象類(lèi)中的方法可以有方法體,就是能實(shí)現(xiàn)方法的具體功能蔽挠,但是接口中的方法不行住闯。
- 抽象類(lèi)中的成員變量可以是各種類(lèi)型的,而接口中的成員變量只能是 public static final 類(lèi)型的澳淑。
- 接口中不能含有靜態(tài)代碼塊以及靜態(tài)方法(用 static 修飾的方法)比原,而抽象類(lèi)是可以有靜態(tài)代碼塊和靜態(tài)方法。
- 一個(gè)類(lèi)只能繼承一個(gè)抽象類(lèi)杠巡,而一個(gè)類(lèi)卻可以實(shí)現(xiàn)多個(gè)接口量窘。
接口的聲明
接口的聲明語(yǔ)法格式如下:
[可見(jiàn)度] interface 接口名稱(chēng) [extends 其他的類(lèi)名] {
// 聲明變量
// 抽象方法
}
Interface關(guān)鍵字用來(lái)聲明一個(gè)接口。下面是接口聲明的一個(gè)簡(jiǎn)單例子氢拥。
NameOfInterface.java 文件代碼:
/* 文件名 : NameOfInterface.java */
import java.lang.*;
//引入包
public interface NameOfInterface
{
//任何類(lèi)型 final, static 字段
//抽象方法
}
接口有以下特性:
- 接口是隱式抽象的蚌铜,當(dāng)聲明一個(gè)接口的時(shí)候锨侯,不必使用abstract關(guān)鍵字。
- 接口中每一個(gè)方法也是隱式抽象的冬殃,聲明時(shí)同樣不需要abstract關(guān)鍵字囚痴。
- 接口中的方法都是公有的。
Animal.java 文件代碼:
/* 文件名 : Animal.java */
interface Animal {
public void eat();
public void travel();
}
接口的實(shí)現(xiàn)
當(dāng)類(lèi)實(shí)現(xiàn)接口的時(shí)候审葬,類(lèi)要實(shí)現(xiàn)接口中所有的方法深滚。否則,類(lèi)必須聲明為抽象的類(lèi)涣觉。
類(lèi)使用implements關(guān)鍵字實(shí)現(xiàn)接口痴荐。在類(lèi)聲明中,Implements關(guān)鍵字放在class聲明后面旨枯。
實(shí)現(xiàn)一個(gè)接口的語(yǔ)法蹬昌,可以使用這個(gè)公式:
Animal.java 文件代碼:
...implements 接口名稱(chēng)[, 其他接口, 其他接口..., ...] ...
MammalInt.java 文件代碼:
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
以上實(shí)例編譯運(yùn)行結(jié)果如下:
Mammal eats
Mammal travels
重寫(xiě)接口中聲明的方法時(shí),需要注意以下規(guī)則:
- 類(lèi)在實(shí)現(xiàn)接口的方法時(shí)攀隔,不能拋出強(qiáng)制性異常皂贩,只能在接口中,或者繼承接口的抽象類(lèi)中拋出該強(qiáng)制性異常昆汹。
- 類(lèi)在重寫(xiě)方法時(shí)要保持一致的方法名明刷,并且應(yīng)該保持相同或者相兼容的返回值類(lèi)型。
- 如果實(shí)現(xiàn)接口的類(lèi)是抽象類(lèi)满粗,那么就沒(méi)必要實(shí)現(xiàn)該接口的方法辈末。
在實(shí)現(xiàn)接口的時(shí)候,也要注意一些規(guī)則:
- 一個(gè)類(lèi)可以同時(shí)實(shí)現(xiàn)多個(gè)接口映皆。
- 一個(gè)類(lèi)只能繼承一個(gè)類(lèi)挤聘,但是能實(shí)現(xiàn)多個(gè)接口。
- 一個(gè)接口能繼承另一個(gè)接口捅彻,這和類(lèi)之間的繼承比較相似组去。
接口的繼承
一個(gè)接口能繼承另一個(gè)接口,和類(lèi)之間的繼承方式比較相似步淹。接口的繼承使用extends關(guān)鍵字从隆,子接口繼承父接口的方法。
下面的Sports接口被Hockey和Football接口繼承:
// 文件名: Sports.java
public interface Sports
{
public void setHomeTeam(String name);
public void setVisitingTeam(String name);
}
// 文件名: Football.java
public interface Football extends Sports
{
public void homeTeamScored(int points);
public void visitingTeamScored(int points);
public void endOfQuarter(int quarter);
}
// 文件名: Hockey.java
public interface Hockey extends Sports
{
public void homeGoalScored();
public void visitingGoalScored();
public void endOfPeriod(int period);
public void overtimePeriod(int ot);
}
Hockey接口自己聲明了四個(gè)方法缭裆,從Sports接口繼承了兩個(gè)方法键闺,這樣,實(shí)現(xiàn)Hockey接口的類(lèi)需要實(shí)現(xiàn)六個(gè)方法澈驼。
相似的辛燥,實(shí)現(xiàn)Football接口的類(lèi)需要實(shí)現(xiàn)五個(gè)方法,其中兩個(gè)來(lái)自于Sports接口。
接口的多繼承
在Java中挎塌,類(lèi)的多繼承是不合法畅铭,但接口允許多繼承,勃蜘。
在接口的多繼承中extends關(guān)鍵字只需要使用一次,在其后跟著繼承接口假残。 如下所示:
public interface Hockey extends Sports, Event
以上的程序片段是合法定義的子接口缭贡,與類(lèi)不同的是,接口允許多繼承辉懒,而 Sports及 Event 可能定義或是繼承相同的方法
標(biāo)記接口
最常用的繼承接口是沒(méi)有包含任何方法的接口阳惹。
標(biāo)識(shí)接口是沒(méi)有任何方法和屬性的接口.它僅僅表明它的類(lèi)屬于一個(gè)特定的類(lèi)型,供其他代碼來(lái)測(cè)試允許做一些事情。
標(biāo)識(shí)接口作用:簡(jiǎn)單形象的說(shuō)就是給某個(gè)對(duì)象打個(gè)標(biāo)(蓋個(gè)戳)眶俩,使對(duì)象擁有某個(gè)或某些特權(quán)莹汤。
例如:java.awt.event 包中的 MouseListener 接口繼承的 java.util.EventListener 接口定義如下:
package java.util;
public interface EventListener
{}
沒(méi)有任何方法的接口被稱(chēng)為標(biāo)記接口。標(biāo)記接口主要用于以下兩種目的:
建立一個(gè)公共的父接口:
正如EventListener接口颠印,這是由幾十個(gè)其他接口擴(kuò)展的Java API纲岭,你可以使用一個(gè)標(biāo)記接口來(lái)建立一組接口的父接口。例如:當(dāng)一個(gè)接口繼承了EventListener接口线罕,Java虛擬機(jī)(JVM)就知道該接口將要被用于一個(gè)事件的代理方案止潮。
向一個(gè)類(lèi)添加數(shù)據(jù)類(lèi)型:
這種情況是標(biāo)記接口最初的目的,實(shí)現(xiàn)標(biāo)記接口的類(lèi)不需要定義任何接口方法(因?yàn)闃?biāo)記接口根本就沒(méi)有方法)钞楼,但是該類(lèi)通過(guò)多態(tài)性變成一個(gè)接口類(lèi)型喇闸。
Java 包(package)
為了更好地組織類(lèi),Java 提供了包機(jī)制询件,用于區(qū)別類(lèi)名的命名空間燃乍。
包的作用
把功能相似或相關(guān)的類(lèi)或接口組織在同一個(gè)包中,方便類(lèi)的查找和使用宛琅。
如同文件夾一樣刻蟹,包也采用了樹(shù)形目錄的存儲(chǔ)方式。同一個(gè)包中的類(lèi)名字是不同的夯秃,不同的包中的類(lèi)的名字是可以相同的座咆,當(dāng)同時(shí)調(diào)用兩個(gè)不同包中相同類(lèi)名的類(lèi)時(shí),應(yīng)該加上包名加以區(qū)別仓洼。因此介陶,包可以避免名字沖突。
包也限定了訪(fǎng)問(wèn)權(quán)限色建,擁有包訪(fǎng)問(wèn)權(quán)限的類(lèi)才能訪(fǎng)問(wèn)某個(gè)包中的類(lèi)哺呜。
Java 使用包(package)這種機(jī)制是為了防止命名沖突,訪(fǎng)問(wèn)控制箕戳,提供搜索和定位類(lèi)(class)某残、接口国撵、枚舉(enumerations)和注釋?zhuān)╝nnotation)等。
包語(yǔ)句的語(yǔ)法格式為:
package pkg1[.pkg2[.pkg3…]];
例如,一個(gè)Something.java 文件它的內(nèi)容
package net.java.util
public class Something{
...
}
那么它的路徑應(yīng)該是 net/java/util/Something.java 這樣保存的玻墅。 package(包) 的作用是把不同的 java 程序分類(lèi)保存介牙,更方便的被其他 java 程序調(diào)用。
一個(gè)包(package)可以定義為一組相互聯(lián)系的類(lèi)型(類(lèi)澳厢、接口环础、枚舉和注釋?zhuān)瑸檫@些類(lèi)型提供訪(fǎng)問(wèn)保護(hù)和命名空間管理的功能剩拢。
以下是一些 Java 中的包:
- java.lang-打包基礎(chǔ)的類(lèi)
- java.io-包含輸入輸出功能的函數(shù)
開(kāi)發(fā)者可以自己把一組類(lèi)和接口等打包线得,并定義自己的包。而且在實(shí)際開(kāi)發(fā)中這樣做是值得提倡的徐伐,當(dāng)你自己完成類(lèi)的實(shí)現(xiàn)之后贯钩,將相關(guān)的類(lèi)分組,可以讓其他的編程者更容易地確定哪些類(lèi)办素、接口角雷、枚舉和注釋等是相關(guān)的。
由于包創(chuàng)建了新的命名空間(namespace)性穿,所以不會(huì)跟其他包中的任何名字產(chǎn)生命名沖突谓罗。使用包這種機(jī)制,更容易實(shí)現(xiàn)訪(fǎng)問(wèn)控制季二,并且讓定位相關(guān)類(lèi)更加簡(jiǎn)單檩咱。
創(chuàng)建包
創(chuàng)建包的時(shí)候,你需要為這個(gè)包取一個(gè)合適的名字胯舷。之后刻蚯,如果其他的一個(gè)源文件包含了這個(gè)包提供的類(lèi)、接口桑嘶、枚舉或者注釋類(lèi)型的時(shí)候炊汹,都必須將這個(gè)包的聲明放在這個(gè)源文件的開(kāi)頭。
包聲明應(yīng)該在源文件的第一行逃顶,每個(gè)源文件只能有一個(gè)包聲明讨便,這個(gè)文件中的每個(gè)類(lèi)型都應(yīng)用于它。
如果一個(gè)源文件中沒(méi)有使用包聲明以政,那么其中的類(lèi)霸褒,函數(shù),枚舉盈蛮,注釋等將被放在一個(gè)無(wú)名的包(unnamed package)中废菱。
例子
讓我們來(lái)看一個(gè)例子,這個(gè)例子創(chuàng)建了一個(gè)叫做animals的包。通常使用小寫(xiě)的字母來(lái)命名避免與類(lèi)殊轴、接口名字的沖突衰倦。
在 animals 包中加入一個(gè)接口(interface):
Animal.java 文件代碼:
/* 文件名: Animal.java */
package animals;
interface Animal {
public void eat();
public void travel();
}
接下來(lái),在同一個(gè)包中加入該接口的實(shí)現(xiàn):
MammalInt.java 文件代碼:
package animals;
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
然后旁理,編譯這兩個(gè)文件樊零,并把他們放在一個(gè)叫做animals的子目錄中。 用下面的命令來(lái)運(yùn)行:
$ mkdir animals
$ cp Animal.class MammalInt.class animals
$ java animals/MammalInt
Mammal eats
Mammal travel
import 關(guān)鍵字
為了能夠使用某一個(gè)包的成員孽文,我們需要在 Java 程序中明確導(dǎo)入該包淹接。使用 "import" 語(yǔ)句可完成此功能。
在 java 源文件中 import 語(yǔ)句應(yīng)位于 package 語(yǔ)句之后叛溢,所有類(lèi)的定義之前,可以沒(méi)有劲适,也可以有多條楷掉,其語(yǔ)法格式為:
import package1[.package2…].(classname|*);
如果在一個(gè)包中,一個(gè)類(lèi)想要使用本包中的另一個(gè)類(lèi)霞势,那么該包名可以省略烹植。
例子
下面的 payroll 包已經(jīng)包含了 Employee 類(lèi),接下來(lái)向 payroll 包中添加一個(gè) Boss 類(lèi)愕贡。Boss 類(lèi)引用 Employee 類(lèi)的時(shí)候可以不用使用 payroll 前綴草雕,Boss類(lèi)的實(shí)例如下。
Boss.java 文件代碼:
package payroll;
public class Boss
{
public void payEmployee(Employee e)
{
e.mailCheck();
}
}
如果 Boss 類(lèi)不在 payroll 包中又會(huì)怎樣固以?Boss 類(lèi)必須使用下面幾種方法之一來(lái)引用其他包中的類(lèi)墩虹。
使用類(lèi)全名描述,例如:
payroll.Employee
用 import 關(guān)鍵字引入憨琳,使用通配符 "*"
import payroll.*;
使用 import 關(guān)鍵字引入 Employee 類(lèi):
import payroll.Employee;
注意:
類(lèi)文件中可以包含任意數(shù)量的 import 聲明诫钓。import 聲明必須在包聲明之后,類(lèi)聲明之前篙螟。
package 的目錄結(jié)構(gòu)
類(lèi)放在包中會(huì)有兩種主要的結(jié)果:
- 包名成為類(lèi)名的一部分菌湃,正如我們前面討論的一樣。
- 包名必須與相應(yīng)的字節(jié)碼所在的目錄結(jié)構(gòu)相吻合遍略。
下面是管理你自己 java 中文件的一種簡(jiǎn)單方式:
將類(lèi)惧所、接口等類(lèi)型的源碼放在一個(gè)文本中,這個(gè)文件的名字就是這個(gè)類(lèi)型的名字绪杏,并以.java作為擴(kuò)展名下愈。例如:
// 文件名 : Car.java
package vehicle;
public class Car {
// 類(lèi)實(shí)現(xiàn)
}
接下來(lái),把源文件放在一個(gè)目錄中蕾久,這個(gè)目錄要對(duì)應(yīng)類(lèi)所在包的名字驰唬。
....\vehicle\Car.java
現(xiàn)在,正確的類(lèi)名和路徑將會(huì)是如下樣子:
類(lèi)名 -> vehicle.Car
路徑名 -> vehicle\Car.java (在 windows 系統(tǒng)中)
通常,一個(gè)公司使用它互聯(lián)網(wǎng)域名的顛倒形式來(lái)作為它的包名.例如:互聯(lián)網(wǎng)域名是 runoob.com叫编,所有的包名都以 com.runoob 開(kāi)頭辖佣。包名中的每一個(gè)部分對(duì)應(yīng)一個(gè)子目錄。
例如:有一個(gè) com.runoob.test 的包搓逾,這個(gè)包包含一個(gè)叫做 Runoob.java 的源文件卷谈,那么相應(yīng)的,應(yīng)該有如下面的一連串子目錄:
....\com\runoob\test\Runoob.java
編譯的時(shí)候霞篡,編譯器為包中定義的每個(gè)類(lèi)世蔗、接口等類(lèi)型各創(chuàng)建一個(gè)不同的輸出文件,輸出文件的名字就是這個(gè)類(lèi)型的名字朗兵,并加上 .class 作為擴(kuò)展后綴污淋。 例如:
// 文件名: Runoob.java
package com.runoob.test;
public class Runoob {
}
class Google {
}
現(xiàn)在,我們用-d選項(xiàng)來(lái)編譯這個(gè)文件余掖,如下:
$javac -d . Runoob.java
這樣會(huì)像下面這樣放置編譯了的文件:
.\com\runoob\test\Runoob.class
.\com\runoob\test\Google.class
你可以像下面這樣來(lái)導(dǎo)入所有 \com\runoob\test\ 中定義的類(lèi)寸爆、接口等:
import com.runoob.test.*;
編譯之后的 .class 文件應(yīng)該和 .java 源文件一樣,它們放置的目錄應(yīng)該跟包的名字對(duì)應(yīng)起來(lái)盐欺。但是赁豆,并不要求 .class 文件的路徑跟相應(yīng)的 .java 的路徑一樣。你可以分開(kāi)來(lái)安排源碼和類(lèi)的目錄冗美。
<path-one>\sources\com\runoob\test\Runoob.java
<path-two>\classes\com\runoob\test\Google.class
這樣魔种,你可以將你的類(lèi)目錄分享給其他的編程人員,而不用透露自己的源碼粉洼。用這種方法管理源碼和類(lèi)文件可以讓編譯器和java 虛擬機(jī)(JVM)可以找到你程序中使用的所有類(lèi)型节预。
類(lèi)目錄的絕對(duì)路徑叫做 class path。設(shè)置在系統(tǒng)變量 CLASSPATH 中属韧。編譯器和 java 虛擬機(jī)通過(guò)將 package 名字加到 class path 后來(lái)構(gòu)造 .class 文件的路徑心铃。
<path- two>\classes 是 class path,package 名字是 com.runoob.test,而編譯器和 JVM 會(huì)在 <path-two>\classes\com\runoob\test 中找 .class 文件挫剑。
一個(gè) class path 可能會(huì)包含好幾個(gè)路徑去扣,多路徑應(yīng)該用分隔符分開(kāi)。默認(rèn)情況下樊破,編譯器和 JVM 查找當(dāng)前目錄愉棱。JAR 文件按包含 Java 平臺(tái)相關(guān)的類(lèi),所以他們的目錄默認(rèn)放在了 class path 中哲戚。
設(shè)置 CLASSPATH 系統(tǒng)變量
用下面的命令顯示當(dāng)前的CLASSPATH變量:
- Windows 平臺(tái)(DOS 命令行下):C:> set CLASSPATH
- UNIX 平臺(tái)(Bourne shell 下):# echo $CLASSPATH
刪除當(dāng)前CLASSPATH變量?jī)?nèi)容:
- Windows 平臺(tái)(DOS 命令行下):C:> set CLASSPATH=
- UNIX 平臺(tái)(Bourne shell 下):# unset CLASSPATH; export CLASSPATH
設(shè)置CLASSPATH變量:
- Windows 平臺(tái)(DOS 命令行下): C:> set CLASSPATH=C:\users\jack\java\classes
- UNIX 平臺(tái)(Bourne shell 下):# CLASSPATH=/home/jack/java/classes; export CLASSPATH
文章摘自菜鳥(niǎo)教程http://www.runoob.com/java/java-tutorial.html