黑馬程序員——Java基礎知識——面向對象(三)

------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! 


一、多態

       就是一個抽象的種類可以對應多種屬於它這類的具體事物,多態就是事物存在的多種體現形態。例如:動物中有貓、狗,貓這個對象對應的類型是貓類型(貓 x=new 貓());同時貓也是動物中的一種,也可以把貓稱爲動物,可以這樣表示:動物 y=new 貓();那麼動物就是貓和狗這些具體事物中向上抽取出來的父類型,父類的引用可以指向子類型對象。

       多態的體現就是父類的引用指向或接收自己的子類對象。例如:動物 y=new 貓(),表示父類類型的變量y引用指向了子類型的對象。類與類之間必須有繼承或實現 關係,並且 子類中複寫了父類中的方法,這時纔可以使用多態。

       多態的出現提高了程序的可擴展性和後期可維護性。例如需要一個父類的不確定的子類對象作爲參數傳遞給一個函數時,定義時可以將這個父類的引用作爲形式參數傳遞給這個函數,而在調用時傳入一個子類對象。但在多態中父類引只能訪問父類中的成員,也就是父類引用不能調用子類中特有的方法,如代表動物的y不能調用貓對象的抓老鼠的功能。

       父類的引用指向子類對象(例:動物 y=new 貓())的過程中,是貓類型向上轉型,提升爲動物類型。而當我們需要用父類的引用調用子類的特有功能時,就需要將父類的引用向下轉型,轉型成子類類型,例如把上面的動物類型向下轉換爲貓類型(貓 c=(貓)y),這樣就可以調用抓老鼠的功能了。但注意不要將父類對象轉成子類類型。我們能轉換的是父類引用指向了自己的子類對象時,該引用可以向上提升,也可以向下轉換,多態自始自終都是子類對象在做着變化。

          下面就以貓狗爲例,介紹一下多態的使用。

//定義父類Animal,表示動物
abstract class Animal
{
	abstract void eat();
}

//定義Cat類,表示貓類,繼承Animal類
class Cat extends Animal
{
    //複寫父類中的方法
	public void eat()
	{
		System.out.println("吃魚");
	}
	//定義特有抓老鼠方法。
	public void catchMouse()
	{
		System.out.println("抓老鼠");
	}
}

//定義表示狗的Dog類,繼承Animal類
class Dog extends Animal
{
	//複寫父類中的方法
	public void eat()
	{
		System.out.println("吃骨頭");
	}
	//定義特有看家方法
	public void guardHome()
	{
		System.out.println("看家");
	}
}

class DuoTaiDemo 
{
	public static void main(String[] args) 
	{
		//父類Animal引用指向子類Cat類對象
		Animal a=new Cat();
		//調用共性方法
                a.eat();
		Animal b=new Dog();
		//將父類引用向下轉換爲Dog類型
		Dog d=(Dog)b;
		//調用特有方法。
		 d.guardHome();
		//在調用function方法時,將子類對象作爲實際參數傳給函數。
		function(new Cat());
	}
	//定義父類引用爲形式參數,調用時傳入其子類對象
	public static void function(Animal a)
	{	
		a.eat();
	}
	
}
       多態中成員的特點:

     (1)非靜態成員函數的特點:

         在編譯時期:參閱引用型變量所屬的類中是否有調用的方法。如果有,編譯通過,如果沒有,編譯失敗。

         在運行時期:參閱子類對象所屬的類中是否有調用的方法。這就是說,子類複寫了父類中的函數,在多態中,父類引用調用這個函數時,調用的是子  類中的方法。

        概括:成員函數在調用時,編譯看左邊,運行看右邊。

     (2)成員變量的特點 

           無論編譯和運行,都參考左邊(引用變量所屬的類)。如:多態中的父類引用訪問成員變量時,如果父類和子類有同名的成員變量,那麼被調用的是父   類中的成員變量。

     (3)靜態成員函數的特點

          無論編譯和運行,都參考左邊。因爲靜態函數可以通過類名實現調用,在內存中靜態函數隨着類的加載而存在於靜態方法區中,不需要創建對象。

  下面通過一個小程序瞭解一下多態的應用,如下:

/*
需求:
電腦運行實例,
電腦運行基於主板。
*/

//定義一個接口,表示插口規則
interface PCI
{
	public abstract void open();
	public abstract void close();
}
//定義表示主板的類
class MainBoard
{
	//定義核心功能,運行
	public void run()
	{
		System.out.println("mainboard run ");
	}
	//定義擴展功能,利用PCI
	public void usePCI(PCI p)//PCI p = new NetCard()//接口型引用指向自己的子類對象。
	{
		if(p!=null)
		{
			p.open();
			p.close();
			
		}
	}
}

//定義網卡,實現PCI,符合規則
class NetCard implements PCI
{
	//複寫PCI的功能
	public void open()
	{
		System.out.println("netcard open");
	}
	public void close()
	{
		System.out.println("netcard close");
	}
	
}
//定義聲卡,實現PCI,符合規則
class SoundCard implements PCI
{
	public void open()
	{
		System.out.println("SoundCard open");
	}
	public void close()
	{
		System.out.println("SoundCard close");
	}
}

class DuoTaiDemo 
{
	public static void main(String[] args) 
	{
		MainBoard mb = new MainBoard();
		//主板運行
		mb.run();
		//將網卡對象作爲實際參數傳入,實現上網功能。
		mb.usePCI(new NetCard());
		//將聲卡對象作爲實際參數傳入,實現發音功能。
		mb.usePCI(new SoundCard());
		
	}
}


二、Object類

         Object類是類層次結構的根類,每個類都使用Object作爲超類(父類),所有對象(包括數組)都實現這個類的方法,所以該類中的定義的是所有對象都具備的功能。

         Object中定義了用於判斷對象是否相等的equals(Object obj)方法,注意在建立對象,自定義比較方法時,只要沿襲Object類中的功能,建立自己特有的比較內容,這就是覆蓋,注意傳入的參數類型一定要是Object類型,否則不是覆蓋,而是重載。

     例如:

//定義一個類,系統會默認爲Object的子類
class Demo 
{
	private int num;
	Demo(int num)
	{
		this.num = num;
	}
	//複寫equals方法,用於定義比較內容
	public boolean equals(Object obj)//Object obj = new Demo();
	{
        //判斷傳入的實際參數是否是本類類型
		if(!(obj instanceof Demo))
			return false;
		//向下轉換
		Demo d = (Demo)obj;

		return this.num == d.num;
	}
}

三、內部類

        當一個類定義在另一個類的裏面,裏面哪個類就稱爲內部類,也叫內置類、嵌套類。當描述事物時,有時事物的內部還有事物,這時就可以用內部類描述內部的事物。如人可以定義成一個類,那麼人的器官屬於人這個類,又有自己特有的功能,這時就可以用內部類描述器官,定義在人這個類中。

        在編譯時,如果類中有內部類,生成的.class文件會含有這樣的文件:Test$1.class。編譯器將會把內部類翻譯成用$分割外部類名和內部類名的常規類文件。

        內部類可以直接訪問外部類中的成員,包括私有。是因爲內部類中持有了一個外部類的引用(外部類名.this)。而外部類要訪問內部類,則必須建立內部類的對象。

        外部其他類如何訪問內部類,根據內部類的不同定義與在外部類中的不同位置,有下面幾種不同的訪問格式。

        (1)當內部類定義在外部類的成員位置上,並且非私有,則在外部其他類中,可以直接建立內部類對象。

            格式:外部類名.內部類名 變量名=外部類對象.內部類對象。例如:

<pre name="code" class="java">class Outer
{
   //在外部類的成員位置上定義一個內部類
   class  Inner
   {
       void method()
	  {
		   System.out.println("method run");
          }
  }

}

class InnerDemo
{
	public static void main(String[]args)
	{
		//建立內部類的對象
		Outer.Inner oi=new Outer().new Inner();
		//調用內部類的方法
		oi.method();
	}
}



   當內部類在外部類的成員位置上時,可以被成員修飾符修飾。例如:

        private:將內部類在外部類中進行封裝。

        static:被static修飾的內部類就具有靜態的特性,但只能訪問外部類中的靜態成員,出現了訪問權限。

       在外部其他類中,訪問static內部類的非靜態成員的格式爲:new 外部類名.內部類名().方法名();而訪問static內部類的靜態成員格式爲:外部類名.內部類名.方法名()。例如:

class Outer
{
   //在外部類的成員位置上定義一個靜態內部類
   static class Inner
   {
       //定義靜態方法
	   static void open()
	   {
              System.out.println("open");   
	   }
	   void method()
	  {
             System.out.println("method run");
          }
   }
}

class InnerDemo
{
	public static void main(String[]args)
	{
		//調用靜態內部內部類的非靜態方法
		new Outer.Inner().method();
		//調用靜態內部內部類的靜態方法
	    Outer.Inner.method();
	}
}
       當內部類中定義了靜態成員時,該內部類也必須定義成靜態內部類。當外部類中的靜態方法訪問內部類時,內部類必須是靜態內部類。在實際的開發中,內部類通常被定義成私有的。
        (2)內部類也可以定義在局部:內部類可以定義在外部類的方法中,創建這個類型的對象時,僅使用一次,那麼可在這個方法中定義內部類。定義在局部的內部類不可以被成員修飾符(如public、private、static等)修飾,因爲它的作用於被限定在了聲明這個局部類的代碼塊中。定義在局部的內部類可以直接訪問外部類中的成員,因爲還持有外部類中的引用。要特別注意的是,內部類不能訪問它所在的局部中非最終變量,只能訪問被final修飾的局部變量。例如:

class Outer
{
	int x = 3;

	void method(final int a)
	{
		//內部類只能訪問final修飾的局部變量
		final int y = 4;
        //在局部定義一個內部類		
		class Inner
		{
			void function()
			{
				System.out.println(a);
				System.out.println(y);
			}
		}
	    //在方法內部調用內部類的方法。
		new Inner().function();
		
	}
}


class  InnerClassDemo3
{
	public static void main(String[] args) 
	{
		Outer out = new Outer();
		out.method(7);
		out.method(8);
	}

}
執行結果爲:
          

      被傳入的參數是一個常量,但當方法被調用完後,傳入的常量和方法都從棧內存中釋放了,所以當再次調用這個方法時,就可以重新傳入被final修飾的變量。

     匿名內部類:

     就是內部類的簡寫格式。定義一個匿名內部類,這個內部類必須繼承一個類或者實現一個接口,格式:new 父類或接口(){定義子類的內容}。可以把匿名內部類理解爲一個帶內容的匿名子類對象。注意匿名內部類中定義的方法少於3個。

      匿名類的出現簡化了代碼書寫,但匿名內部類只能調用父類或接口中有的方法,不能調用自己的特有方法,不能做強轉動作,如果繼承的父類或接口中方法較多時,使用內部類閱讀性會很差,調用效率低,所以匿名內部類中定義的方法一般少於3個。 

     以一個匿名內部類的小程序,來表明其使用。例如:

abstract class InnerDemo
{
   abstract void show();
}
class Outer
{
   public void function()
	{
	   //定義一個匿名內部類
	   InnerDemo d = new InnerDemo()
		{   //複寫父類方法
		    void show()
		    {
		      System.out.println("show");
		    }
	           //定義特有方法 
		   void run()
		   {
		      System.out.println("run");
		   }
		};
                 d.show();
		         d.run();//編譯失敗;不能調用特有方法。
        }
}


四、異常

        就是程序在運行時出現不正常情況。就像人都會生病、路上有時候會堵車,問題是現實生活中一個具體的事物,可以通過Java類的形式進行描述,並封裝成對象。其實就是Java對不正常情況進行描述後的對象體現。在程序運行時各方面都可能會出現各種問題,例如用戶輸入一些非法參數、設備出現問題、存儲空間不足、代碼錯誤無法運行等。例如:

class ExceptionDemo
{
	public static void main(String[]args)
	{
		int x=5;
		int y=0;
	        int z=div(x,y);//由於y=0,無法運算,程序出現異常
	}
	public static int div(int x,int y)
	{
		return x/y;
	}
} 

       Java中問題劃分爲嚴重的問題和非嚴重的問題。對於嚴重的問題,Java通過Error類進行描述。對Error一般不編寫針對性的代碼對其進行處理;對於非嚴重的問題,Java通過Exception類進行描述。對於Exception可以使用針對性的處理方式進行處理。無論Error或者Exception都有一些共性內容。比如:出現的不正常情況的信息,引發的原因等。把共性內容向上抽取,最後構成了Java的異常體系:

        Throwable

            |----Error 

            |----Exception  

     Error和Exception有很多子類,它們的子類名都是以父類名作爲後綴。

     異常體系中的所有類以及建立的對象都具備可拋性,也就是說可以被throw和throws操作,只有異常體系具備這個特點。

     Exception的大部分異常在編譯時,如果沒有處理(沒有拋沒有try),編譯會失敗。該異常被標示,代表着可以被處理;而在Exception有一個特殊的子類RuntimeException以及它的子類在編譯時不需要處理,編譯器不檢查。該異常發生時,不處理,讓程序停止,這時需要對代碼進行修正。

      異常的處理:

      Java中提供了特有的語句對異常進行處理,格式如下:

      try
     {
需要被檢測的代碼;
    }
    catch(異常類 變量)

   {
處理異常的代碼;(處理方式)
   }
  finally
 {
一定要執行的語句;
 }

     finally中的定義的通常是關閉系統資源的語句。如果要定義一些必須要執行的語句,可以用try{}finally{}格式,將語句放在finally代碼中。但如果程序前面前面執行到System.exit(0),程序會結束,fianlly不會被執行。

    throw和throws:throw定義在函數內,用與拋出異常對象;throws定義在函數上,用於拋出異常類,可以拋出多個異常類,用逗號隔開。當函數內容有throw拋出異常對象,並未處理,必須要在函數上聲明,否則編譯失敗。但是函數內如果拋出的是RuntimeException異常,函數上可以不用聲明。

    當在函數中出現了throw拋出異常對象,要麼在內部用try catch語句進行處理,要麼在函數上聲明讓方法調用者去處理。一般情況下函數中出現異常,並未處理,函數上需要聲明,通過throws的關鍵字聲明該功能可有會出現的異常類型。而調用者需要進行處理。可以繼續拋出或者trycatch處理;如果在函數中拋出RuntimeException異常對象。函數上不用聲明,編譯通過,而調用者也不需要進行處理。這是因爲不需要讓調用者處理,當該異常發生時,希望程序停止。因爲程序運行時出現了無法繼續運算的狀況,程序停止後,對代碼進行檢查修正。

        catch中對捕獲到的異常對象的常見操作:

             String getMessage();獲取異常的信息

             String  toString(); 獲取異常類名和異常信息             

             void   printStackTrace();獲取異常類名和異常信息,以及異常出現在程序中的位置。JVM默認的異常處理機制,就是在調用printStackTrace方法,打印異常的堆棧的跟蹤信息。
              void   printStackTrace(PrintStream s)//將異常內容保存在日誌文件中,以便查閱。

       多異常的處理:聲明異常時,建議聲明更爲具體的異常,這樣處理的也可以更具體。對方聲明幾個異常,就對應有幾個catch代碼塊,如果多個catch代 碼塊中的異常出現繼承關係,把處理父類異常的catch代碼塊放在最下面。否則會報錯,因爲其餘的catch語句執行不到。

       下面通過一段代碼,看一下Java中對異常的處理。如下:


class Demo
{
	int div(int a,int b)throws ArithmeticException,ArrayIndexOutOfBoundsException//聲明瞭該功能中存在的異常。
	{

		int[] arr = new int[a];

		System.out.println(arr[4]);

		return a/b;
	}
}


class  ExceptionDemo2
{
	public static void main(String[] args) //throws Exception
	{
		Demo d = new Demo();
		//檢測異常
                try
		{
			int x = d.div(5,0);
			System.out.println("x="+x);
		}
		
		//處理異常
                catch (ArithmeticException e)
		{
			System.out.println(e.toString());
			System.out.println("被零除了!!");

		}
		catch (ArrayIndexOutOfBoundsException e)
		{
			System.out.println(e.toString());
			System.out.println("角標越界啦!!");
		}
		
		
	}
}
 自定義異常:
           因爲項目中會出現特有的問題,而這些問題並未被java所描述並封裝對象。所以對這些特有的問題可以按照java中的面向對象思想。將特有的問題,進行自定義的異常封裝。定義類繼承Exception或者RuntimeException,這樣做是爲了讓該類也具備可拋性和操作異常的共性方法。這就叫做自定義異常。

           在自定義的異常類中,要定義自定義的信息,可以使用父類已經定義好的功能。把異常信息傳遞給父類的構造函數。因爲父類中已經把異常信息的操作都完成了。所以子類只要在構造時,將異常信息通過super語句傳遞給父類即可,那麼就可以通過getMessage()等方法獲取自定義的異常信息。在自定義異常時,如果該異常發生時,無法再繼續運算,那麼可以讓自定義的異常類繼承RuntimeException。

下面的代碼就是表示一個自定義的異常類:

class MyException extends Exception
{
	MyException(String msg)
	{   //把信息傳遞給父類。
	    super(msg);
	}
}
          Java中的異常處理方式,可以將問題進行封裝,將正常流程語句和問題處理代碼分離,便於閱讀。在處理異常時,如果catch代碼塊中處理不了異常,但該異常並不屬於       該功能出現的異常,可以將異常轉換後,再拋出和該功能相關的異常。或者異常可以處理,但需要將異常產生後和本功能相關的問題提供出去,讓調用者知道,並處理。也可     以將捕獲異常處理後,轉換新的異常。例如:用ATM機給別人轉賬,當ATM機出現故障,可以去其他的地方轉,也可以告訴對方轉賬不成功。還要注意的是當子類覆蓋父類方     法涉及到異常時,子類拋出的異常必須是父類的異常的子類或子集,而父類或者接口沒有異常拋出時,子類的方法內容中有異常,只能trycatch處理,不能拋。
   下面是一個處理異常的練習,如下:

/*
畢老師用電腦上課。
但電腦可能出現的問題:電腦藍屏,電腦冒煙。

可是當電腦冒煙發生後,出現講課進度無法繼續。

出現了講師的問題:課時計劃無法完成。
*/

//定義電腦藍屏異常
class LanPingException extends Exception
{
	LanPingException(String message)
	{
		super(message);
	}
}
//定義電腦藍屏異常
class MaoYanException extends Exception
{
	MaoYanException(String message)
	{
		super(message);
	}
}

//定義老師講課異常
class NoPlanException extends Exception
{
	NoPlanException(String msg)
	{
		super(msg);
	}
}

class Computer
{          
	private int state = 3;
	//電腦運行
        public void run()throws LanPingException,MaoYanException
	{
		if(state==2)
			throw new LanPingException("藍屏了");
		if(state==3)
			throw new MaoYanException("冒煙了");

		System.out.println("電腦運行");
	}
	//電腦重啓
        public void reset()
	{
		state = 1;
		System.out.println("電腦重啓");
		
	}
}

class Teacher
{
	private String name;
	private Computer comp;

	Teacher(String name)
	{
		this.name = name;
		comp = new Computer();

	}

	public void teach()throws NoPlanException
	{
		try
		{
			comp.run();			
		}
		catch (LanPingException e)
		{
			comp.reset();
		}
		catch (MaoYanException e)
		{
			
			test();
			throw new NoPlanException("課時無法繼續"+e.getMessage());
			
		}
		System.out.println("講課");
	}
	public void test()
	{
		System.out.println("練習");
	}

}

class ExceptionTest 
{
	public static void main(String[] args) 
	{
		Teacher t = new Teacher("畢老師");
		try
		{
			t.teach();
		}
		catch (NoPlanException e)
		{
			System.out.println(e.toString());
			System.out.println("換老師或者放假");
		}
		
	}
}


-------------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! -------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章