1. 程式人生 > >Java基礎之面向物件的方法引數和物件上轉型

Java基礎之面向物件的方法引數和物件上轉型

 本篇部落格著重說明方法的引數傳遞機制和 物件上轉型,作為前幾篇部落格的續貂之作,當然面向物件我還沒有回顧完呢。言歸正傳。

一、方法的引數傳遞機制

1.1說明:java裡的方法不能單獨存在,呼叫方法必須使用類或者物件作為主調者。

如果宣告的方法有形式引數宣告,則在呼叫時必須指定這些形式引數的實際值

那麼java的引數實際值是怎麼傳遞到方法體內部的呢?

 首先說一下:java的方法的引數傳遞方式只有“值傳遞”一種,

值傳遞:將實際引數值的副本(複製品)傳入方法內部。而引數本身不會受到影響。

參看下面的程式碼:

例項程式碼1;

public class PrimitiveTransferTest {
		public static void main(String[] args) {
			int a = 9;
			int b = 10;
			System.out.println("原值: a = "+a+" b = "+b);
			swap( a , b);
			System.out.println("交換後的原值: a = "+a+" b = "+b);
		}
		public static void swap(int a ,int b){
			int temp = a;
			a = b;
			b = temp;
			System.out.println("引數的副本交換 :  a = "+a+" ,  b = "+b);
		}
}

執行結果是a,b在main函式裡的值並沒有交換,但是swap裡的引數值卻交換了,但是swap卻沒有達到它應該有的功能,是因為程式執行swap方法的時候,為該方法傳遞的是a,b變數的副本,而不是a,b,變數的本身,進入swap方法後系統產生了四個變數,a,b本身在main方法所在的棧區裡,而a,b的副本在swap方法的棧區中,swap的功能僅僅是交換自己棧區的a,b的值。

這個例子很經典,也很容易令初學者犯錯,其實是很簡單的,但是如何能讓swap達到可以交換main方法裡的a,b的值呢,大家應該能想到用指標可以實現這個交換操作,。

怎麼使用指標呢,a,b是基本資料型別,只有直接呼叫的方式,而main方法中的a,b值是區域性變數,因此可以在main方法中直接進行a,b的交換(有時候在演算法設計時該交換是單獨作為一個函數出現的),或者將a,b放入一個引用型別的資料型別裡。

解決方法一:

 a = a^b;b = a^b;a= b^a;

這個是交換兩個數最快也最省事的了。

   解決方法二:該方法需要兩個空間的陣列,因為陣列是引用型別的,因此可以使用該方法,傳遞的是一個int型別的陣列。

例項程式碼2;

int arr[] = new int[2];
arr[0] = a;
 arr[1] = b;
 swap(arr);

public static void swap(int array[]){
			array[0] = array[0]^array[1];
			array[1] = array[0]^array[1];
			array[0] = array[1]^array[0];
			//System.out.println("交換後的引數2:arr[0] = "+arr[0]+" arr[1] = "+arr[1]);

		}	
解決方法三:
	   Info info = new Info();
			System.out.println("交換前: "+info.a+"  ----  "+info.b);
			 swap(info);
			 System.out.println("交換後: "+info.a+"  ----  "+info.b);

/*
		 * 第三種方式使用class 將 a,b的值存入,然後方法內使用Info 的例項,進行交換
		 */
		public static void swap(Info info){
			int temp = info.a;
			info.a = info.b;
		    info.b = temp;
		}

對於第二種和第三種方式的解釋:由於採用了引用型別的資料進行傳值,所傳的便是真正的值本身,而不是值的一個副本,

系統複製的只是引用資料指標,而不是引用資料指向的資料,因此在程式執行的時候main方法建立了一個Info的物件,並定義了一個info引用變數來指向Info物件,這是一個與基本型別不同的地方,建立一個物件時,系統記憶體中有兩個東西:堆記憶體中儲存了物件本身,棧記憶體儲存了引用該物件的引用變數。在swap方法交換時,只是利用引用變數的副本實現指向兩個變數的功能,並在swap函式裡進行交換。

 可以將第三種方式裡的info引數賦值為null;

 即 info = null;

再在主函式裡進行info呼叫a,b的值,可以發現不會報錯,

下面是這個例子的完整例項:提供了三個過載swap的方法。

例項程式碼3;


public class PrimitiveTransferTest {
		public static void main(String[] args) {
			Info info = new Info();
			System.out.println("交換前: "+info.a+"  ----  "+info.b);
			 swap(info);
			 System.out.println("交換後: "+info.a+"  ----  "+info.b);
		}
		public static void swap(int array[]){
			array[0] = array[0]^array[1];
			array[1] = array[0]^array[1];
			array[0] = array[1]^array[0];
			//System.out.println("交換後的引數2:arr[0] = "+arr[0]+" arr[1] = "+arr[1]);

		}
		public static void swap(int a ,int b){
			int temp = a;
			a = b;
			b = temp;
			System.out.println("引數的副本交換 :  a = "+a+" ,  b = "+b);
		}
		/*
		 * 第三種方式使用class 將 a,b的值存入,然後方法內使用Info 的例項,進行交換
		 */
		public static void swap(Info info){
			int temp = info.a;
			info.a = info.b;
		    info.b = temp;
		}


}
class Info{
	int a = 10;
	int b = 11312;
}

1.2形參個數可變的方法。

說明:從JDK 1.5之後,java允許定義形參個數可變的引數,從而允許為方法指定數量不確定的形參。

例項程式碼4;

package cn.com.basicTwo;
public class Varargs {
	//形引數目可變的方法
	public static void test(int a,int b,String ... books){
		//這種形式的是增強for迴圈形式
		for(String book: books){
			System.out.println(book);
		}
	}
	public static void test(int param[]){
		for(int i =0;i<param.length;i++){
			System.out.println(param[i]);
		}
	}
	public static void main(String[] args) {
		Varargs.test(1, 2, "abcd","efds");
		System.out.println("\n\n");
		
		Varargs.test(3, 4, "asdfawe","sdfwewr","sdf","asdwe");
		System.out.println("\n\n");
		
		int array []= new int [4];
		array[0]= 130;
		array[1] = 102;
		array[3]= 2425;
		Varargs.test(array);
	}
}

上面使用兩種方式指定形引數目不同,可以滿足一定特殊的需求。

注意:長度可變的形參只能出現在形參列表裡的最後,一個方法裡最多隻能包含一個長度可變的形參,呼叫包含一個長度可變的形參方法時,這個長度可變的形參既可以傳入多個引數,也可以傳入一個數組。

不過陣列形式的可變引數跟普通引數一樣,沒有什麼特殊要求。

1.3遞迴方法

 說明:一個方法體內呼叫它自身,被稱為方法遞迴。方法遞迴包含了一種隱士的迴圈,會重複執行某段程式碼,但這種重複執行無需迴圈控制。

例項程式碼5:

public class TestRec {
	//求和(有限遞迴),根據返回值遞迴退出迴圈
	public static int getSum(int x){
		if(x == 1){
			return 0;
		}else{
			return getSum(x-1)+x;
		}
	}
	/**
	 * 
	 * 作者:FCs
	 * 描述:這種方式沒有返回值重複呼叫自己,
	 * 迴圈不能終止,這種方式類似於死迴圈,將不斷的申請棧空間最終導致記憶體溢位。
	 * 儘量不要這樣用。而且這種方法非常危險。
	 * 時間:2014年8月18日
	 * 返回:void
	 */
	public static void getInfo(){
		System.out.println("我是不好的程式碼,迴圈呼叫自己。。。。");
		getInfo();
	}
	
	public static void main(String[] args) {
	 int  a = getSum(5);
	 System.out.println(a);
	   // getInfo();
	}
}	

說明:一般使用遞迴都是有某種返回值的,當滿足返回條件的時候,遞迴會逐層返回,就像棧一樣,每一次呼叫自己都會在自己的棧區裡申請一塊記憶體,用指標指向它,這種方法非常消耗記憶體,一般可以使用遞迴的情況都可以使用非遞迴形式進行遞迴的消除,但是使用非遞迴的形式進行消除是一種非常困難的時候,程式碼難懂,尤其是在演算法方面,因為遞迴的形式比非遞迴的形式更加簡潔明瞭。

 二、物件上轉型,下轉型(為下一章做個引子先)

物件上轉型屬於java多型的內容,但是先在這裡總結一下。

定義: 父類宣告,子類例項化的物件稱為上轉型物件。該物件是子類的簡化,不關係子類的新增功能,只關心子類繼承的和重寫的功能。

這種型別通常見於父類與子類之間

形式:

上轉型物件: 父類   name = new 子類();

下轉型物件: 子類   name = (父類)子類name;

說明:下轉型物件需要強制轉型,

上轉型物件的特點

1.上轉物件不能操作子類新增的成員變數,失掉了這部分屬性,不能使用子類新增的方法,失掉了一些功能。

2.上轉型物件可以操作子類繼承的成員變數,也可以使用子類繼承的或重寫的方法。

3.如果子類重寫了父類的某個方法後,當物件的上轉型物件呼叫這個方法時一定是呼叫了子類重寫的方法。因為程式在執行時知道,這個上轉物件的例項是子類建立的,只不過損失了一些功能而已。

注意:
1.可以將物件的上轉型物件再強制轉換為一個子類物件,這時,

該物件又具備了子類所有屬性和功能。

2.不可以將父類建立的物件賦值給子類宣告的物件。

例項程式碼6:

package cn.com.basicThree;
/**
 * 
 * @author fcs
 * 2014年8月18日
 * Computer
 * 說明:物件上轉型,與下轉型
 */
public class Computer{
	public String  name = "重量";
	public String  band = "品牌";
	public String getInfo(){
		return name+" -- "+band;
	}
	//父類特有方法
	public void  getBand(){
		System.out.println(band);
		
	}
	public static void main(String[] args) {
		Computer computer = new Lenovo();   //上轉型物件
		System.out.println(computer.band);  //上轉型物件呼叫從父類繼承的變數,該物件不能呼叫自己的變數
		System.out.println(computer.name);
		computer.getInfo();                 //上轉型物件呼叫從父類重寫的方法的方法,該物件不能呼叫自己的增加的方法
	                                        //computer.getCom();報錯
		computer.getBand();                 //呼叫父類未被重寫的方法
		
		Lenovo  lenovo = (Lenovo) computer;  //將上轉型物件再轉回子類物件(父類下轉型),跟普通子類物件具有相同功能特點。
		System.out.println(lenovo.band);     //呼叫父類變數
		System.out.println(lenovo.price);    //呼叫自己的變數
		lenovo.getBand();
		//lenovo.getInfo();      這種方式不建議使用,即當上轉型物件再轉回子類物件的時候,不要再呼叫被子類重寫的方法。
		lenovo.getCom();   //呼叫子類新增方法
		//		Computer comp = new Computer();
//		Lenovo  lenv = (Lenovo)comp;   編譯報錯? 請讀者思考
		//System.out.println(lenv.band);
		//System.out.println(lenv.price);
		//lenv.getBand();
		//System.out.println(lenv.getCom());
		//lenv.getInfo();
	}
}
class Lenovo extends Computer{
	public String  xinghao = "G480";
	public double   price = 3453.89;
	//重寫父類方法
	public String getInfo(){
		return xinghao+" -- "+price;
	}
	//子類新增方法
	public String getCom(){
		return "Lenovo";
	}

}

上轉型物件擴充套件了父類的使用範圍和靈活度,因為子類對父類的方法在某種程度上進行了優化,因此在這種形式下上轉型物件可以最大化的使用從父類繼承的優點。可以使用父類對各個繼承自己的子類進行這種方式的變化,進而體現了多型的特性。

三、instanceof 運算子說明

  Instanceof的運算子的前一個運算元通常是一個引用型別的變數,後一個運算元通常是一個類或者介面,

作用:首先用於判斷前面的物件是否是後面的類的例項,然後是否可以成功進行型別轉換,從而使程式更加健壯。

說明:整個表示式的值要麼是true,要麼是false

使用:instanceof運算子前面運算元的編譯時型別要麼與後面的類相同,要麼與後面的類具有父子繼承關係。否則編譯時出錯。

例項程式碼7:
package cn.com.basicThree;
/**
 * 
 * @author fcs
 * 2014年8月19日
 * Instanceof
 * 說明:instanceof功能說明
 * 
 * 
 */
public class Instanceof {
	public static void main(String[] args) {
		//宣告Hello的時候使用Object類。則Hello的編譯型別是Object
		//Object是所有類的父類,但Hello變數的實際型別是String 
		
		Object hello = "hello";
		//String是Object的子類返回true 
		System.out.println("字串是否是Object類的例項:"+(hello instanceof Object));
		System.out.println("字串是否是 String類的例項: "+(hello instanceof String));
		System.out.println("字串是否是Math類的例項: "+(hello instanceof Math));
		//String 實現了Comparable介面,返回true
		System.out.println("字串是否是Comparable介面的例項: "+(hello instanceof Comparable));
		String a = "hello";
		System.out.println("字串是否是Object類的例項:"+(a instanceof Object));
		
		TestInstanceof ti1 = new TestInstanceof();
		System.out.println("ti1 物件是否是Object類的例項 ;"+(ti1 instanceof Object));
		TestInstanceof2 ti2 = new TestInstanceof2();
		//System.out.println("ti2 物件是否是Object類的例項 ;"+(ti2 instanceof ti1));  //第二個運算元是物件,編譯出錯
		System.out.println("ti2 物件是否是Object類的例項 ;"+(ti2 instanceof TestInstanceof2));
		
		//前後型別不同編譯出錯。
		//System.out.println("a 是否是TestInstanceof2類的例項 ;"+(a instanceof TestInstanceof2));
	}
}
class TestInstanceof {     }

class TestInstanceof2{     }