1. 程式人生 > >過載和覆蓋的區別,通過反射獲取泛型實際型別,列舉要點,五個最常用的集合類之間的區別和聯絡,final總結

過載和覆蓋的區別,通過反射獲取泛型實際型別,列舉要點,五個最常用的集合類之間的區別和聯絡,final總結

java面試碰到過的題目之方法過載和覆蓋的區別。

1. 過載方法必須滿足以下條件: 
i. 方法名相同。
ii. 方法的引數型別、個數、順序至少有一項不同。
iii. 方法的返回型別可以不相同。
iv. 方法的修飾符可以不相同。

2. 重寫方法必須滿足以下條件: 
i. 子類方法的名稱、引數簽名和返回型別必須與父類方法的名稱、引數簽名和返回型別一致。
ii. 子類方法不能縮小父類方法的訪問許可權。
iii. 子類方法不能丟擲比父類方法更多的異常。
iv. 方法覆蓋只存在於子類和父類(包括直接父類和間接父類)之間。在同一個類中方法只能被過載,不能被覆蓋。
v. 父類的靜態方法不能被子類覆蓋為非靜態的方法。
vi. 子類可以定義與父類的靜態方法同名的靜態方法,以便在子類中隱藏父類的靜態方法。
vii. 父類的非靜態方法不能被子類覆蓋為靜態方法。
viii. 父類的私有方法不能被子類覆蓋。
ix. 父類的抽象方法可以被子類通過兩種途徑覆蓋:一是子類實現父類的抽象方法;二是子類重新宣告父類的抽象方法。
x. 父類的非抽象方法可以被覆蓋為抽象方法。

方法過載和方法覆蓋具有以下相同點: 
1)都要求方法名相同。 
2)都可以用於抽象方法和非抽象方法之間。

方法過載和方法覆蓋具有以下不同點:
1)方法覆蓋要求引數簽名必須一致,而方法過載要求引數簽名必須不一致。
2)方法覆蓋要求返回型別必須一致,而方法過載對此不做限制。 
3)方法覆蓋只能用於子類覆蓋父類的方法,而方法過載用於同一類的所有方法 
4)方法的覆蓋對方法的訪問許可權和丟擲的異常有特殊的要求,而方法過載在這方面沒有任何限制。 
5)父類的一個方法只能被子類覆蓋一次,而一個方法在所在的類中可以被過載多次。

=========================================================

通過反射獲取泛型實際型別
要求,利用反射技術,對於給定的一個集合例如:Vector v; 這個v接收從某個方法放回的物件,裡面儲存了某種同一型別物件,而我們現在不知道該v中具體的物件型別,現在使用反射技術獲取,方法是寫一個方法 如: public static void applyVector(Vector<Date> vector){ } 其中Date可以為任意物件 ,那麼從另外一個方法中來獲取這個方法中vector中泛型的引數。
程式碼如下  Method method = Test.class.getMethod("applyVector", Vector.class);     //得到給方法類的
                Type[] types= method.getGenericParameterTypes();                 //得到方法中泛型引數的型別
                ParameterizedType parameterizedType = (ParameterizedType)types[0]; //向下轉型
                System.out.println(parameterizedType.getRawType());               //得到源型別 即 Vector
                System.out.println(parameterizedType.getActualTypeArguments()[0]);     //得到泛型的型別 這裡獲取的是java.util.Date

第一個:既然要得到v中裝的物件的型別,卻通過方法得到,說明得到v 之後,我們必須知道獲得v的這個方法,這是可以知道,但是如果那個方法不是Vector<Date> vector  而是 Vector vector,也就是說他沒有指定泛型,那麼這程式是會報錯的! 而java是相容可以不指定泛型的。
第二個:既然我們已經有v這個物件了,如果裡面確實存有物件,即v.size() > 0  (等於0就沒有必要考慮了,那樣是沒有任何泛型型別的),我們無法直接根據v得到,通過v.getClass() 是得不到的,這樣得到的是Vector的類位元組碼,而不是裡面物件的位元組碼,確實,可是有v物件並且在裡面有元素的條件下,我們可以先獲得裡面的元素,再獲得元素的位元組碼。
程式碼如下:
             Vector v = new Vector(); //首先定義v,不指定其泛型
             v.add(new Date(System.currentTimeMillis()));//向v中新增物件, 這個v物件可以以任意方式從它出獲得,且不知道它裡面物件的型別
             System.out.println(v.get(0).getClass().getName()); //在v.size() > 0 的情況下,首先v.get(0)得到其內部元素,這時再getClass()這得到的Class是裡面元素的Class?
輸出結果:java.util.Date

這樣也得不到它的型別,只能得到值的型別,但未必是泛型的型別,因為泛型可以有泛型限定,而不是單一固定型別,所以值的型別有可能是泛型型別的子類。
因為既然不知道里面存的到底是什麼型別,只知道是相同的型別,那麼從其他的途徑獲得型別,再放到函式裡的驗證是不合適的,貌似返回的可以是指定泛型,也可以是指定泛型的子類:

public class ReflectTest {
        public static void main(String[] args) {
                Vector <Number>v = new Vector<Number>();
                v.add(new Integer(8));
                v.add(new Long(1));
        System.out.println(v.get(0).getClass().getName()); 
        System.out.println(v.get(1).getClass().getName()); 
        }
}
執行結果:
java.lang.Integer
java.lang.Long
Integer和long同屬於Number

而且就算是不同的包,只要是子類就行:
import java.sql.Time;
import java.util.Date;
import java.util.Vector;

public class ReflectTest1 {
        public static void main(String[] args) {
                Vector <Date>v = new Vector<Date>();
                v.add(new Time(8));
                v.add(new Date(1));
        System.out.println(v.get(0).getClass().getName()); 
        System.out.println(v.get(1).getClass().getName()); 
        }
}
執行結果:
java.sql.Time
java.util.Date
sql中Time是util中Date的子類。

所以通過泛方法獲取集合的泛型型別的優勢還是很明顯的,用於需要準確的返回泛型型別。

=========================================================

列舉型別是單例模式的。你需要例項化一次,然後再整個程式之中就可以呼叫他的方法和成員變量了。
列舉型別使用單例模式是因為他的值是固定的,不需要發生改變。
列舉類也是一個類當然可以實現一個介面也可以在其中定義構造方法,方法,和內部類

Enum要點
1、在程式中可以使用一個列舉類來指定物件的取值範圍。
2、在Java中使用enum關鍵字定義一個列舉類,每一個列舉類都是繼承Enum類。
3、在列舉中可以通過values()方法取得列舉中的全部內容。
4、在列舉類中可以定義構造方法,但在設定列舉範圍時必須顯式地呼叫構造方法。
5、所有的列舉類都可以直接使用Comparable進行排序,因為Enum類實現了Comparable介面。
6、Java類集中提供列舉的支援類是EnumMap、EnumSet。
7、一個列舉類可以實現一個介面或者直接定義一個抽象方法,但是每個列舉物件都必須分別實現全部的抽象方法。

=========================================================

Java五個最常用的集合類之間的區別和聯絡

1.ArrayList: 元素單個,效率高,多用於查詢
2.Vector:    元素單個,執行緒安全,多用於查詢
3.LinkedList:元素單個,多用於插入和刪除
4.HashMap:   元素成對,元素可為空
5.HashTable: 元素成對,執行緒安全,元素不可為空

ArrayList
底層是Object陣列,所以ArrayList具有陣列的查詢速度快的優點以及增刪速度慢的缺點。
而在LinkedList的底層是一種雙向迴圈連結串列。在此連結串列上每一個數據節點都由三部分組成:前指標(指向前面的節點的位置),資料,後指標(指向後面的節點的位置)。最後一個節點的後指標指向第一個節點的前指標,形成一個迴圈。雙向迴圈連結串列的查詢效率低但是增刪效率高。
ArrayList和LinkedList在用法上沒有區別,但是在功能上還是有區別的。

LinkedList
經常用在增刪操作較多而查詢操作很少的情況下:佇列和堆疊。
佇列:先進先出的資料結構。
棧:後進先出的資料結構。
注意:使用棧的時候一定不能提供方法讓不是最後一個元素的元素獲得出棧的機會。

Vector
與ArrayList相似,區別是Vector是重量級的元件,使用使消耗的資源比較多。
結論:在考慮併發的情況下用Vector(保證執行緒的安全)。在不考慮併發的情況下用ArrayList(不能保證執行緒的安全)。

面試經驗(知識點):
java.util.stack(stack即為堆疊)的父類為Vector。可是stack的父類是最不應該為Vector的。因為Vector的底層是陣列,且Vector有get方法(意味著它可能訪問到並不屬於最後一個位置元素的其他元素,很不安全)。對於堆疊和佇列只能用push類和get類。Stack類以後不要輕易使用。實現棧一定要用LinkedList。(在JAVA1.5中,collection有queue來實現佇列。)

Set-HashSet實現類:
遍歷一個Set的方法只有一個:迭代器(interator)。
HashSet中元素是無序的(這個無序指的是資料的新增順序和後來的排列順序不同),而且元素不可重複。
在Object中除了有finalize(),toString(),equals(),還有hashCode()。
HashSet底層用的也是陣列。當向陣列中利用add(Object o)新增物件的時候,系統先找物件的hashCode:
int hc = o.hashCode(); 返回的hashCode為整數值。
Int I = hc%n;(n為陣列的長度),取得餘數後,利用餘數向陣列中相應的位置新增資料,以n為6為例,如果I=0則放在陣列a[0]位置,如果I=1,則放在陣列a[1]位置。如果equals()返回的值為true,則說明資料重複。如果equals()返回的值為false,則再找其他的位置進行比較。這樣的機制就導致兩個相同的物件有可能重複地新增到陣列中,因為他們的hashCode不同。
如果我們能夠使兩個相同的物件具有相同hashcode,才能在equals()返回為真。
在例項中,定義student物件時覆蓋它的hashcode。
因為String類是自動覆蓋的,所以當比較String類的物件的時候,就不會出現有兩個相同的string物件的情況。
現在,在大部分的JDK中,都已經要求覆蓋了hashCode。
結論:如將自定義類用hashSet來新增物件,一定要覆蓋hashcode()和equals(),覆蓋的原則是保證當兩個物件hashcode返回相同的整數,而且equals()返回值為True。
如果偷懶,沒有設定equals(),就會造成返回hashCode雖然結果相同,但在程式執行的過程中會多次地呼叫equals(),從而影響程式執行的效率。

我們要保證相同物件的返回的hashCode一定相同,也要保證不相同的物件的hashCode儘可能不同(因為陣列的邊界性,hashCode還是可能相同的)。
例子:
public int hashCode(){
return name.hashcode()+age*33;
}
這個例子保證了相同姓名和年齡的記錄返回的hashCode是相同的。

使用hashSet的優點:
hashSet的底層是陣列,其查詢效率非常高。而且在增加和刪除的時候由於運用的hashCode的比較確定新增元素的位置,所以不存在元素的偏移,所以效率也非常高。雖然hashSet查詢和刪除和增加元素的效率都非常高,但是hashSet增刪的高效率是通過花費大量的空間換來的:因為空間越大,取餘數相同的情況就越小。HashSet這種演算法會建立許多無用的空間。使用hashSet類時要注意,如果發生衝突,就會出現遍歷整個陣列的情況,這樣就使得效率非常的低。

=========================================================

一、final 
根據程式上下文環境,Java關鍵字final有“這是無法改變的”或者“終態的”含義,它可以修飾非抽象類、非抽象類成員方法和變數。你可能出於兩種理解而需要阻止改變:設計或效率。 
final類不能被繼承,沒有子類,final類中的方法預設是final的。  
final方法不能被子類的方法覆蓋,但可以被繼承。  
final成員變量表示常量,只能被賦值一次,賦值後值不再改變。必須在初始化物件的時候賦初值,否則編譯器報錯。  
final不能用於修飾構造方法。  
注意:父類的private成員方法是不能被子類方法覆蓋的,因此private型別的方法預設是final型別的??不確定  

1、final類  
final類不能被繼承,因此final類的成員方法沒有機會被覆蓋,預設都是final的。在設計類時候,如果這個類不需要有子類,類的實現細節不允許改變,並且確信這個類不會載被擴充套件,那麼就設計為final類。  
2、final方法  
如果一個類不允許其子類覆蓋某個方法,則可以把這個方法宣告為final方法。  
使用final方法的原因有二:  
第一、把方法鎖定,防止任何繼承類修改它的意義和實現。  
第二、高效。編譯器在遇到呼叫final方法時候會轉入內嵌機制,大大提高執行效率。
3、final變數(常量)  
用final修飾的成員變量表示常量,值一旦給定就無法改變!  
final修飾的變數有三種:靜態變數、例項變數和區域性變數,分別表示三種類型的常量。  
另外,final變數定義的時候,可以先宣告,而不給初值,這種變數也稱為final空白,無論什麼情況,編譯器都確保空白final在使用之前必須被初始化。但是,final空白在final關鍵字final的使用上提供了更大的靈活性,為此,一個類中的final資料成員就可以實現依物件而有所不同,卻有保持其恆定不變的特徵。

class FinalTest
{
	static{ B = 1002;}

	public final int E; //final空白,必須在初始化物件的時候賦初值 
	public final int b = 0;
	public final static int A;
	public final static int B;

	static{ A = 1001;}

	public FinalTest(int E)
	{ 
		this.E = E; 
	} 

	public static void test2(final int x)
	{
		System.out.println(new FinalTest(120).E);
		System.out.println("x = "+x);
//		x++;
	}
	
	public static void test1(int x)
	{
		final int a;
		a = x;
		System.out.println("a = " + a);
	}

	public static void main(String[] args)
	{
		FinalTest test = new FinalTest(2);
		test.test1(10);
		test.test1(100000);
		test2(190);
	//	test.b = 10;
	}
}
4、final引數  
當函式引數為final型別時,你可以讀取使用該引數,但是無法改變該引數的值。
5、static和final一塊用表示什麼  
static final用來修飾成員變數和成員方法,可簡單理解為“全域性常量”!  
對於變數,表示一旦給值就不可修改,並且通過類名可以訪問。  
對於方法,表示不可覆蓋,並且可以通過類名直接訪問。
=========================================================
在軟體工程中,效能分析(performance analysis也稱為profiling),是以收集程式執行時資訊為手段研究程式行為的分析方法。 這種方法與靜態程式碼分析相對。效能分析的目的在於決定程式的哪個部分應該被優化,從而提高程式的速度或者記憶體使用效率。
效能分析工具(Profiler)是一個軟體工具用於測量程式的執行,特別是函式呼叫頻率和所耗費時間。輸出以事件的記錄流(蹤跡trace,或者事件的資料彙總(輪廓profile)。效能分析工具使用很廣泛的技術手段收集資料,包括硬體中斷,程式碼指令,作業系統(鉤子),CPU內建的效能計數暫存器等等。效能分析工具的使用稱為效能工程學過程。
效能測量資料量與程式碼大小成線性關係,這是由於資料彙總(profile)的操作通常與產生事件的原始碼位置相關。與之對比,事件的蹤跡(trace)則與執行時長成線性關係,常使之不符合實際應用。對於順序執行的程式,通常輪廓就足夠了。但並行執行的程式的效能問題(等待訊息或者同步問題)常常依賴於事件的關係,因此需要全部的蹤跡才能找到問題。