多態(Polymorphism)
多態的其他叫法:動態綁定,後期綁定,運行時綁定。
多態的前提:
1、類與類之間必須有關係,要麼繼承,要麼實現。
2、存在覆蓋。父類中有方法被子類重寫。
例子:
/***
* 多態的實例
* @author LQX
*涉及:向上轉型,多態
*/
public class Test1 {
//此處使用Animal類將使得調用更方便,如果寫他的子類,那麼顯得冗餘複雜,直接寫他們的父類將會顯得簡單。
//此處就是動態綁定(多態),運行時根據對象類型進行綁定
public static void choose(Animal a)
{
a.eat();
}
public static void main(String[] args) {
// choose(new Cat());等價於Animal a = new Cat();choose(a); 涉及了:向上轉型,Cat本質就是Animal類。
choose(new Cat());
choose(new Dog());
}
}
//動物類,父類
class Animal
{
//動物都具有的行爲,吃
public void eat()
{
System.out.println("Animal eat!");
}
}
//貓
class Cat extends Animal
{
//貓的行爲
public void eat()
{
System.out.println("貓愛吃魚!");
}
}
//狗
class Dog extends Animal
{
//狗的行爲
public void eat()
{
System.out.println("狗愛吃骨頭!");
}
}
總結:
1.綁定:
將一個方法調用同一個方法主體關聯起來被稱作綁定。程序在執行前進行綁定(由編譯器和連接程序實現)稱爲前期綁定。上述程序之所以迷惑,主要是因爲前期綁定。因爲只有一個Animal引用時,他無法知道調用Animal類還是Cat類還是Dog類的eat方法。
解決辦法就是後期綁定(多態),意思就是運行時根據傳進去的對象類型進行綁定。
2.多態的體現
a. 父類的引用指向了自己子類的對象。
b. 父類的引用也可以接收自己的子類對象。
如: Animal a = new Cat();
其中就將父類型的 a 引用指向了子類的對象。
3.多態的利與弊
利:提高了程序的可擴展性和後期可以維護性。
弊:只能使用父類中的引用訪問父類中的成員。也就是說使用了多態,父類型的引用在使用功能時,不能直接調用子類中的特有方法。如:Animal a = new Cat(); 這代碼就是多態的體現,假設子類Cat中有特有的抓老鼠功能,父類型的 a就不能直接調用。這上面的代碼中,可以理解爲Cat類型提升了,向上轉型。
如果此時父類的引用想要調用Cat中特有的方法,就需要強制將父類的引用,轉成子類類型,向下轉型。如:Cat c = (Cat)a;
注:如果父類可以創建對象,如:Animal a = new Animal(); 此時,就不能向下轉型了,Cat c = (Cat)a; 這樣的代碼就變得不容許,編譯時會報錯。所以千萬不能出現這樣的操作,就是將父類對象轉成子類類型。
我們能轉換的是父類引用指向了自己的子類對象時,該引用可以被提升,也可以被強制轉換。多態至始至終都是子類對象在做着變化。
4.多態的特點
Animal a = new Cat();爲例
引用類型是《Animal》
實際類型是《Cat》
a.成員函數(非靜態)
在編譯的時候,編譯器會先檢查“引用類型”是否存在符合定義的“eat”的方法,如果沒有,不能編譯。
在執行a.eat();的時候。檢查本類,也就是實際類型的類,有沒有符合的方法。如果沒有,就執行父類繼承過來的。如果有,就執行本類(子類)的。
總結:編譯看左邊,運行看右邊。
public class Test3 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Father f = new Son();
f.run();
}
}
class Father
{
//此方法註釋掉,編譯通不過
public void run()
{
System.out.println("Father run");
}
}
class Son extends Father
{
//此方法註釋掉,將顯示父類該方法
public void run()
{
System.out.println("Son run");
}
}
b.成員函數(靜態)
調用當前引用類型變量類型中的方法。
因爲靜態是屬於類的,由實例共享,所以只看當前引用變量所屬的類中的靜態方法。
總結:編譯和運行看左邊
public class Test3 {
public static void main(String[] args) {
Father f = new Son();
f.method();
}
}
class Father
{
public static void method()
{
System.out.println("Father static method");
}
}
class Son extends Father
{
public static void method()
{
System.out.println("Son static method");
}
}
c.成員變量
無論編譯期還是運行期,都只參考引用類型變量所屬的類中是否有對象的成員變量。有,編譯或運行通過,沒有,編譯或運行失敗
簡單說:編譯和運行都參考等號的左邊
public class Test2 {
public static void main(String[] args) {
//多態的成員變量看引用類型(左邊),右邊爲實際類型
Father f = new Son();
System.out.println(f.i);
Son s = new Son();
System.out.println(s.i);
}
}
class Father
{
int i = 1;
}
class Son extends Father
{
int i = 4;
}
小測驗:
/***
* 需求:
* 電腦的運行實例。電腦的運行由主板控制,假設主板只是提供電腦運行,但是沒有上網,
* 聽歌等功能。而上網、聽歌需要硬件的支持。而現在主板上沒有網卡和聲卡,
* 這時可以定義一個規則,叫PCI,只要符合這個規則的網卡和聲卡都可以在主板上使用,
* 這樣就降低了主板和網卡、聲卡之間的耦合性。用程序體現。
*/
public class Test4 {
public static void main(String[] args) {
Mainboard m1 = new Mainboard();
m1.run();
m1.usePCI(new AudioCard());
m1.usePCI(new NetworkCard());
}
}
//主板
class Mainboard
{
public void run()
{
System.out.println("主板啓動");
}
//調用PCI接口
public void usePCI(PCI p)
{
if(p!=null)
{
p.open();
p.close();
}
}
}
//PCI接口
interface PCI
{
void open();
void close();
}
//聲卡實現PCI接口
class AudioCard implements PCI
{
@Override
public void open() {
System.out.println("聲卡啓動!");
}
@Override
public void close() {
System.out.println("聲卡關閉!");
}
}
//網卡實現PCI接口
class NetworkCard implements PCI
{
@Override
public void open() {
System.out.println("網卡啓動!");
}
@Override
public void close() {
System.out.println("網卡關閉!");
}
}
內部類(Inner Class)
一、成員內部類
1、定義
一個類的定義放在另一個類的內部,這個類就叫做內部類。
就好像,外部類的一個成員一樣,故稱成員內部類。
public class First {
public class Contents{
public void f(){
System.out.println("In Class First's inner Class Contents method f()");
}
}
}
像這樣的,Contents就叫做內部類
內部類瞭解外圍類,並能與之通信。
2、鏈接到外圍類
創建了內部類對象時,它會與創造它的外圍對象有了某種聯繫,於是能訪問外圍類的所有成員,不需任何特殊條件。
public class Test2 {
//外部類定義的字符串
String s = "abcdefg";
class Innerclass
{
public void show()
{
//內部類可以訪問
System.out.println(s);
}
}
public static void main(String[] args) {
Test2 t = new Test2();
Innerclass i = t.new Innerclass();
i.show();
}
}
在內部類InnerClass中,可以使用外圍類成員變量s
實現方式:
非靜態內部類對象的創建需要先創建外部類對象,此時的內部類會祕密的獲取到外部類對象的引用,通過這個引用可以來訪問外圍對象成員。
通常,這些是由編譯器所做的,我們所關注的應該是:
創建內部類對象時,與外部類對象的關聯。
3、使用關鍵字.this與.new
.this關鍵字用於在內部類中獲取當前外部類引用,注意與new的區別。
public class Test1 {
int i = 2;
public Test1(){
}
public Test1(int i)
{
this.i = i;
}
class inner
{
public Test1 getTest1()
{
//使用.this後,得到時創建該內部類時使用的外圍類對象的引用,
return Test1.this;
}
public Test1 newTest1()
{
//new則是創建了一個新的引用。
return new Test1();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test1 t1 = new Test1(5);
// 必須是外圍類對象.new(實例.new),而不能是外圍類.new
Test1.inner inner1 = t1.new inner();
Test1 t2 = inner1.getTest1();
Test1 t3 = inner1.newTest1();
System.out.println(t2.i);
System.out.println(t3.i);
}
}
使用.this關鍵字,得到創建改內部類的外部類對象的引用,而new則是建立了一個新的引用。
使用.new關鍵字,會根據一個外部類對象來創建一個內部類對象。
形式是這樣的:
OutClass.InnerClass obj = outClassInstance.new InnerClass();
注意使用的是外部類對象來new,而不是外部類。
4、內部類與向上轉型
將內部類向上轉型爲基類型,尤其是接口時,內部類就有了用武之地。
public class Mianboard {
//內部類,網卡實現PCI接口
private class NetworkCard implements PCI{
@Override
public void open() {
System.out.println("Open NetworkCard!");
}
}
//獲取PCI接口實例
public PCI getPCI()
{
return new NetworkCard();
}
public static void main(String[] args) {
Mianboard m1 = new Mianboard();
PCI p1 = m1.getPCI();
p1.open();
}
}
//PCI接口
interface PCI
{
void open();
}
通過內部類,可以對外隱藏一些類的細節,除了他的外部類可以訪問,其他人無法訪問。並且,由於寫在了內部,也降低了耦合性。
二、方法內部類
內部類定義在外部類中的某個方法中,創建了這個類型的對象時,且僅使用了一次,那麼可在這個方法中定義局部類。
1)不可以被成員修飾符修飾。如public、private、static等修飾符修飾。它的作用域被限定在了聲明這個局部類的代碼塊中
2)可以直接訪問外部類中的成員,因爲還持有外部類中的引用。
3)方法內部的類也不是在調用方法時纔會創建的,它們一樣也被編譯了。
注意:內部類不可以訪問它所在的局部中非最終變量。只能訪問被final修飾的局部變量。
原因:http://blog.csdn.net/craigyang/article/details/4680506
三、匿名內部類
1.定義:
就是內部類的簡化寫法。
2.定義匿名內部類的前提
內部類必須實現了某個接口或者繼承了某個類
3.格式
new外部類名或者接口名(){覆蓋類或者接口中的代碼,(也可以自定義內容。)};
4.實質
其實匿名內部類就是一個匿名子類對象。可以理解爲帶內容的對象。
5.什麼時候使用匿名內部類呢?
通常使用方法是接口類型參數,並且該接口中的方法不超過三個,可以將匿名內部類作爲參數傳遞
6.匿名內部類的利與弊
優:簡化書寫
弊:1.匿名內部類沒有名字,也就不能調用自己的方法,且只使用一次。
2.沒有引用,也就不可以做強轉動作。
3.與正規的繼承相比有些受限,因爲匿名內部類既可以擴展類,也可以實現接口,但不能兩者兼備。切如果實現接口,也只能實現一個。
4.內部類方法不宜過多,否則影響閱讀性,一般不超過3個。
//方法一:
public class Mianboard2 {
//匿名內部類,網卡實現PCI接口
//獲取PCI接口實例
public PCI1 getPCI1()
{
return new PCI1(){
public void open() {
System.out.println("Open NetworkCard!");
}
};
}
public static void main(String[] args) {
Mianboard2 m1 = new Mianboard2();
PCI1 p = m1.getPCI1();
p.open();
}
}
//PCI接口
interface PCI1
{
void open();
}
/////////////////////////////////////
//方法二:
public class Mianboard2 {
//內部類,網卡實現PCI接口
//獲取PCI接口實例
public void getPCI1(PCI1 p)
{
p.open();
}
public static void main(String[] args) {
Mianboard2 m1 = new Mianboard2();
m1.getPCI1(new PCI1(){
public void open() {
System.out.println("Open NetworkCard!");
}
});
}
}
//PCI接口
interface PCI1
{
void open();
}
7.匿名內部類中如果需要傳遞參數的話,那麼這個參數就必須是final的。
/***
* 匿名內部類傳參必須是final的 在這個例子中,
* 可以爲A的構造方法傳入一個參數 在匿名內部類中,並沒有使用到這個參數。
* 如果使用到了這個參數,那麼這個參數就必須是final的。
*/
public class Test4 {
public A getA(final int num) {
return new A(num) {
public int getNum() {
return num;
}
};
}
}
class A {
private int num;
public A(int num) {
this.num = num;
}
}
8. 由於類是匿名的,自然沒有構造器,如果想模仿構造器,可以採用實例初始化({})
//匿名內部類想模仿構造器
public class Test5 {
public B getB() {
return new B() {
void c() {
System.out.println("dsds");
}
String s;
int n;
// 模仿構造器,採用實例初始化
{
s = "abcdefg";
System.out.println("實例初始化");
}
public void show() {
System.out.println(s);
}
};
}
public static void main(String[] args) {
Test5 t = new Test5();
B b1 = t.getB();
b1.show();
}
}
class B {
public void show() {
}
}
四、static內部類(嵌套類)
使用嵌套類時有兩點需要注意:
a、創建嵌套類對象時,不需要外圍類
b、在嵌套類中,不能像普通內部類一樣訪問外圍類的非static成員
c、被static修飾的內部類只能訪問外部類中的靜態成員
d、如果內部類中定義了靜態成員,該內部類也必須是靜態的
嵌套類還有特殊之處,就是嵌套類中可以有static方法,static字段與嵌套類,而普通內部類中不能有這些。
//嵌套類(static內部類)總結
public class Test6 {
static int i = 8;
String s = "haha";
static class C {
//靜態類中定義非靜態方法是沒有意義的,因爲靜態類是不可以被實例化的
public void show() {
// 嵌套類不可以獲取外部類的非靜態成員
System.out.println("show" + new Test6().s);
//非靜態方法可以調用靜態方法,反之不行,因爲非靜態方法需要實例
take();
}
static void take() {
// 嵌套類可以直接獲取外部類的靜態成員
System.out.println("take" + i);
}
}
public static void main(String[] args) {
//嵌套類的使用
Test6.C.take();
}
}