思想比知識更重要 成長比成功更重要
回歸blogjava |
?第一章 一般技術
1.java只有唯一一種參數傳遞方式:by value(值傳遞)。對于primitive types(基本型別)很容易理解,對于object references(對象引用),傳遞的是object reference的拷貝。
2.polymorphism(多態)優于instanceof:instanceof很容易被誤用,很多場合都應該以多態代替,無論何時看到instanceof,請判斷是否可以改進以消除它。
3.避免創建重復對象。比如一個類A的某個方法新建了一個類B,且此類B不會改變,則每次建立該類A的一個對象就會新建B的對象,此時應把
B設為private static final。
4.清除過期的對象引用。
5.避免使用終結函數。因為終結函數可能得不到執行或很久后得到執行,所以要避免使用。顯示的中止方法通常與try-finally結構結合使用,防止出現異常時終結函數得不到執行。
eg: Foo foo = new Foo(...);
??? try{
??????? //do what must be done with foo???
??? }finally{
??????? foo.terminate();
??? }
6.通過私有構造函數來強化不可實例化的能力。比如一些工具類不希望被實例化,然而在缺少顯示構造函數時編譯器會自動提供一個默認構造函數,為防止以上情況要構造一個顯示的私有的構造函數。
eg:public class UtilityClass{
???? private UtilityClass(){
???? }
?? }
7.通過私有構造函數強化singleton屬性。singleton是指這樣的類,它只能實例化一次。singleton通常被用來代表那些本質上具有唯一性的系統組件,比如視頻顯示或者文件系統。
? eg:public class Elvis{
?????? public static final Elvis INSTANCE = new Elvis();
?????? private Elvis(){
?????? }
???? }
8.考慮用靜態工廠方法代替構造函數,但如果沒有其他強烈的因素,最好還是簡單的使用構造函數,畢竟它是語言規范。靜態工廠方法實際上是一個簡單的靜態方法,他返回的是類的一個實例。
? 有點:a.與構造函數不同,靜態工廠方法具有名字。
??????? b.與構造函數不同,它們每次被調用的時候不要求非得創建一個對象。
??????? c.與構造函數不同,與構造函數不同,它們可以返回一個原類型的子類型對象。
第二章 所有對象都通用的方法(equals(),hashCode(),toString(),clone(),Comparable接口)
一.按規則使用equals():
1.使用equals的規則:
? a.如果一個class的兩個對象占據不同的內存空間也可被視為邏輯相等的話,那么得為這個class提供一個equals()
? b.檢查是否等于this
? c.比較關鍵域以判斷兩個對象是否相等
? d.如果有java.lang.Object以外的任何base class實現了equals(),那么就應該調用super.equals()
? e.如果只允許同一個class所產生的對象被視為相等,則通常使用getClass()
??? eg1:一般情況
??? public boolean equals(Object obj){
??????? if(this == obj){
????????? return true;
??????? }
??????? if(obj != nul && getClass() == obj.getClass()){
????????? Test test = (Test)obj;
????????? if(***){//相等條件
????????????? return true;
????????? }
??????? }
??????? return false;
????? }
??? eg2:調用super.equals()情況
??? public boolean equals(Object obj){
????? if(super.equals(obj)){//已經包含了this == obj; obj !=null && getClass() == obj.getClass()的判斷
??????? Test test = (Test)obj;
????????? if(***){//相等條件
????????????? return true;
????????? }
??????? }
??????? return false;
????? }
? f.只有在不得不對derived class對象與base classes對象進行比較的場合中,才使用instanceof,并且你應該明白這樣做帶來的可能問題和復雜性,并且derived class和base classes都用instanceof實現equals()時,這種比較不會展現“對稱相等性”。
??? Base b;Derived d;//分別表示父類、子類
??? 1)父類實現equals,子類繼承父類的equals,b.equals(d) == d.equals(d);
??? 2)父類子類分別實現了equals,b.equals(d) != d.equals(b);
??? 3)父類未實現equals,子類實現了equals,b.equals(d) != d.equals(b);
2.對于既不是float也不是double類型的primitive types,使用==操作符;對于對象引用域,可以遞歸的調用equals方法;對于float域,先使用Float.floatToIntBits轉換成int類型值,然后使用==操作符比較int類型的值;對于double域,先使用Double.doubleToLongBits轉換成int類型的值,然后使用==操作符比較long類型的值.(這是由于存在Float.NaN、-0.0f以及類似的double類型的常量)
二.hashCode():
1。改寫equals時總是要改寫hashCode方法,如果不這樣作,會導致該類無法與所有基于散列值(hash)的集合類在一起正常工作,這樣的集合類包括HashMap、HashSet、HashTable
2。hashCode方法的簡單方法:
? 1。把某個非零數值(比如17),保存在int result變量里。
? 2。對于對象中每一個關鍵域f(指equals方法中考慮的每一個域),完成以下步驟:
? a)為該域計算int類型的散列碼c:
??? i.該域為boolean型,c = f ? 0 : 1
??? ii.byte, char, short, int型, c = (int)f
??? iii.long型, c = (int)(f ^ (f >>> 32))
??? iv.float型, c = Float.floatToIntBits(f)
??? v.double型, Double.doubleToLongBits(f)得到long型,然后按iii計算散列值
??? vi.如果是對象引用,c = (this.*** == null) ? 0 : this.***.hashCode();
??? vii.如果該域是個數組,則把其中每一個元素當作單獨的域來處理
?? b)result = 37 * result + c;//把每個c都組合到result中
?? 3。返回result
?? eg1:
?public int hashCode() {
???? int result = 17;
???? //對于關鍵域是id的情況
???? int idValue = (this.getId() == null) ? 0 : this.getId().hashCode();
???? result = (result * 37) + idValue;
???? //如果還有第二個關鍵域name
???? //int nameValue = (this.getName() == null) ? 0 : this.getName().hashCode();
???? //result = (result * 37) + nameValue;
???? this.hashValue = result;
?return this.hashValue;
?}
??? eg2:
?如果一個類是非可變的,并且計算散列碼代價較大,則應把散列碼存到對象內部:
?private int hashValue = 17;//先定義hashValue,不需要get/set方法
?........................
?public int hashCode() {//對于關鍵域是id的情況
???? if (this.hashValue == 17) {
??int result = 17;
??int idValue = (this.getId() == null) ? 0 : this.getId().hashCode();
??result = (result * 37) + idValue;
??//如果還有第二個關鍵域name
??//int nameValue = (this.getName() == null) ? 0 : this.getName().hashCode();
??//result = (result * 37) + nameValue;
??this.hashValue = result;
???? }
???? return this.hashValue;
?}
三。toString():會使這個類用起來更加方便。
四。謹慎的改寫clone()。實現拷貝的方法有兩個:一是實現cloneable接口(effective java 39頁,沒仔細看),二是提供拷貝構造函數
? public Yum(Yum yum);
? 或是上面的微小變形:提供一個靜態工廠來代替構造函數:
? public static Yum newInstance(Yum yum);
五、用到搜索、排序、計算極值的情況時,考慮實現Comparable接口。
public int compareTo(Object o)//方法不需要手工檢查參數的類型,如參數類型不符合會拋出ClassCastException;如參數為null,該方法拋出NullPointerException。
第三章 類和接口
1。使類和成員(變量、方法、內部類、內部接口)的可訪問能力最小化。
2。private和friendly成員都是一個類實現中的一部分,并不會影響到導出API。然而,如果這些域所在的類實現了Serializable接口,那么這些成員可能會被泄漏到導出API中。
3。如果一個方法改寫了超類中的一個方法,那么子類中該方法的訪問級別不能低于父類中該方法的訪問級別。特別是:類實現了接口,那么接口中的方法在這個類中必須聲明為公有的,因為接口中方法默認為public abstract。
六、異常處理
1.決不可忽略異常,即catch后什么也不做。
2.決不可掩蓋異常
try{
? e1;//異常1
? e2;//異常2
}catch(Exception e){
? e.printStackTrace()
}//只能捕獲異常2
辦法:要仔細分析,用棧來保存異常
3.覆寫異常處理時:
父類不拋出異常時,自類不能拋出異常。
父類拋出異常時,自類三種情況:a)不拋出異常b)拋出父類異常c)拋出父類異常的派生異常。
4.只要有finally塊就一定會進入,即使try-catch塊有return/break/continue語句。
5.養成將try/catch塊放在循環外的習慣,在不啟動JIT時節省時間。
1 protected void collectionsExample() {
2??ArrayList list = new ArrayList();
3??list.add(new String("test string"));
4??list.add(new Integer(9)); // purposely placed here to create a runtime ClassCastException
5??inspectCollection(list);
6 }
7
8
9 protected void inspectCollection(Collection aCollection) {
10??Iterator i = aCollection.iterator();
11??while (i.hasNext()) {
12?? String element = (String) i.next();
13??}
14 }
1 protected void collectionsExample() {
2??ArrayList<String> list = new ArrayList<String>();
3??list.add(new String("test string"));
4??// list.add(new Integer(9)); this no longer compiles
5??inspectCollection(list);
6 }
7
8
9 protected void inspectCollection(Collection<String> aCollection) {
10??Iterator<String> i = aCollection.iterator();
11??while(i.hasNext()) {
12?? String element = i.next();
13??}
14 }
1 public class ArrayList<E> extends AbstractList<E> {
2??// details omitted...
3??public void add(E element) {
4?? // details omitted
5??}
6??public Iterator<E> iterator() {
7?? // details omitted
8??}
9 }
1 public <T extends Comparable> T max(T t1, T t2) {
2??if (t1.compareTo(t2) > 0)
3?? return t1;
4??else return t2;
5 }
1 Integer iresult = max(new Integer(100), new Integer(200));
2 String sresult = max("AA", "BB");
3 Number nresult = max(new Integer(100), "AAA"); // does not compile
List<String> stringList = new ArrayList<String>(); //1乍一看,Example 6 是正確的。但stringList本意是存放String類型的ArrayList,而objectList中可以存入任何對象,當在第3行進行處理時, stringList也就無法保證是String類型的ArrayList,此時編譯器不允許這樣的事出現,所以第3行將無法編譯。
List<Object> objectList = stringList ;//2
objectList .add(new Object()); // 3
String s = stringList .get(0);//4
void printCollection(Collection<Object> c)
{ for (Object e : c) {
System.out.println(e);
}}
void printCollection(Collection<?> c)
{ for (Object e : c) {
System.out.println(e);
}}
class NonGen {????
??Object ob; // ob is now of type Object
??// Pass the constructor a reference to??
??// an object of type Object
??NonGen(Object o) {??
????ob = o;??
??}??
??// Return type Object.
??Object getob() {??
????return ob;??
??}??
??// Show type of ob.??
??void showType() {??
????System.out.println("Type of ob is " +??
?????????????????????? ob.getClass().getName());??
??}??
}??
// Demonstrate the non-generic class.??
public class NonGenDemo {??
??public static void main(String args[]) {??
????NonGen iOb;??
????// Create NonGen Object and store
????// an Integer in it. Autoboxing still occurs.
????iOb = new NonGen(88);??
????// Show the type of data used by iOb.
????iOb.showType();
????// Get the value of iOb.
????// This time, a cast is necessary.
????int v = (Integer) iOb.getob();??
????System.out.println("value: " + v);??
????System.out.println();??
????// Create another NonGen object and??
????// store a String in it.
????NonGen strOb = new NonGen("Non-Generics Test");??
????// Show the type of data used by strOb.
????strOb.showType();
????// Get the value of strOb.
????// Again, notice that a cast is necessary.??
????String str = (String) strOb.getob();??
????System.out.println("value: " + str);??
????// This compiles, but is conceptually wrong!
????iOb = strOb;
????v = (Integer) iOb.getob(); // runtime error!
??}??
}
class Example1<T>{
private T t;
Example1(T o){
??this.t=o;
??}
T getOb(){
??return t;
}
void ShowObject(){
??System.out.println("對象的類型是:"+t.getClass().getName());
}
}
public class GenericsExample1 {
/**
??* @param args
??*/
public static void main(String[] args) {
??// TODO Auto-generated method stub
??Example1<Integer> examplei=new Example1<Integer>(100);
??examplei.ShowObject();
??System.out.println("對象是:"+examplei.getOb());
??Example1<String> examples=new Example1<String>("Bill");
??examples.ShowObject();
??System.out.println("對象是:"+examples.getOb());
}
}
class TwoGen<T, V> {
?? T ob1;
?? V ob2;
?? // Pass the constructor a reference to??
?? // an object of type T.
?? TwoGen(T o1, V o2) {
???? ob1 = o1;
???? ob2 = o2;
?? }
?? // Show types of T and V.
?? void showTypes() {
???? System.out.println("Type of T is " +
????????????????????????ob1.getClass().getName());
???? System.out.println("Type of V is " +
????????????????????????ob2.getClass().getName());
?? }
?? T getob1() {
???? return ob1;
?? }
?? V getob2() {
???? return ob2;
?? }
}
public class GenericsExampleByTwoParam {
/**
??* @param args
??*/
public static void main(String[] args) {
??// TODO Auto-generated method stub
??TwoGen<Integer, String> tgObj =
?????? new TwoGen<Integer, String>(88, "Generics");
???? // Show the types.
???? tgObj.showTypes();
???? // Obtain and show values.
???? int v = tgObj.getob1();
???? System.out.println("value: " + v);
???? String str = tgObj.getob2();
???? System.out.println("value: " + str);
?? }
}
class Stats<T extends Number> {????
?? T[] nums; // array of Number or subclass
?? // Pass the constructor a reference to??
?? // an array of type Number or subclass.
?? Stats(T[] o) {??
???? nums = o;??
?? }??
?? // Return type double in all cases.
?? double average() {??
???? double sum = 0.0;
???? for(int i=0; i < nums.length; i++)??
?????? sum += nums[i].doubleValue();
???? return sum / nums.length;
?? }??
}??
public class GenericsExampleByHierarchy {
/**
??* @param args
??*/
public static void main(String[] args) {
??// TODO Auto-generated method stub
?? Integer inums[] = { 1, 2, 3, 4, 5 };
???? Stats<Integer> iob = new Stats<Integer>(inums);??
???? double v = iob.average();
???? System.out.println("iob average is " + v);
???? Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
???? Stats<Double> dob = new Stats<Double>(dnums);??
???? double w = dob.average();
???? System.out.println("dob average is " + w);
???? // This won't compile because String is not a
???? // subclass of Number.
//???? String strs[] = { "1", "2", "3", "4", "5" };
//???? Stats<String> strob = new Stats<String>(strs);??
//???? double x = strob.average();
//???? System.out.println("strob average is " + v);
?? }??
}
class StatsWildCard<T extends Number> {
T[] nums; // array of Number or subclass
// Pass the constructor a reference to
// an array of type Number or subclass.
StatsWildCard(T[] o) {
??nums = o;
}
// Return type double in all cases.
double average() {
??double sum = 0.0;
??for (int i = 0; i < nums.length; i++)
?? sum += nums[i].doubleValue();
??return sum / nums.length;
}
// Determine if two averages are the same.
// Notice the use of the wildcard.
boolean sameAvg(StatsWildCard<?> ob) {
??if (average() == ob.average())
?? return true;
??return false;
}
}
public class GenericsExampleByWildcard {
/**
??* @param args
??*/
public static void main(String[] args) {
??// TODO Auto-generated method stub
??Integer inums[] = { 1, 2, 3, 4, 5 };
??StatsWildCard<Integer> iob = new StatsWildCard<Integer>(inums);
??double v = iob.average();
??System.out.println("iob average is " + v);
??Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
??StatsWildCard<Double> dob = new StatsWildCard<Double>(dnums);
??double w = dob.average();
??System.out.println("dob average is " + w);
??Float fnums[] = { 1.0F, 2.0F, 3.0F, 4.0F, 5.0F };
??StatsWildCard<Float> fob = new StatsWildCard<Float>(fnums);
??double x = fob.average();
??System.out.println("fob average is " + x);
??// See which arrays have same average.
??System.out.print("Averages of iob and dob ");
??if (iob.sameAvg(dob))
?? System.out.println("are the same.");
??else
?? System.out.println("differ.");
??System.out.print("Averages of iob and fob ");
??if (iob.sameAvg(fob))
?? System.out.println("are the same.");
??else
?? System.out.println("differ.");
}
}
class TwoD {
??int x, y;
??TwoD(int a, int b) {
????x = a;
????y = b;
??}
}
// Three-dimensional coordinates.
class ThreeD extends TwoD {
??int z;
??ThreeD(int a, int b, int c) {
????super(a, b);
????z = c;
??}
}
// Four-dimensional coordinates.
class FourD extends ThreeD {
??int t;
??FourD(int a, int b, int c, int d) {
????super(a, b, c);
????t = d;??
??}
}
// This class holds an array of coordinate objects.
class Coords<T extends TwoD> {
??T[] coords;
??Coords(T[] o) { coords = o; }
}
// Demonstrate a bounded wildcard.
public class BoundedWildcard {
??static void showXY(Coords<?> c) {
????System.out.println("X Y Coordinates:");
????for(int i=0; i < c.coords.length; i++)
??????System.out.println(c.coords[i].x + " " +
???????????????????????? c.coords[i].y);
????System.out.println();
??}
??static void showXYZ(Coords<? extends ThreeD> c) {
????System.out.println("X Y Z Coordinates:");
????for(int i=0; i < c.coords.length; i++)
??????System.out.println(c.coords[i].x + " " +
???????????????????????? c.coords[i].y + " " +
???????????????????????? c.coords[i].z);
????System.out.println();
??}
??static void showAll(Coords<? extends FourD> c) {
????System.out.println("X Y Z T Coordinates:");
????for(int i=0; i < c.coords.length; i++)
??????System.out.println(c.coords[i].x + " " +
???????????????????????? c.coords[i].y + " " +
???????????????????????? c.coords[i].z + " " +
???????????????????????? c.coords[i].t);
????System.out.println();
??}
??public static void main(String args[]) {
????TwoD td[] = {
??????new TwoD(0, 0),
??????new TwoD(7, 9),
??????new TwoD(18, 4),
??????new TwoD(-1, -23)
????};
????Coords<TwoD> tdlocs = new Coords<TwoD>(td);????
????System.out.println("Contents of tdlocs.");
????showXY(tdlocs); // OK, is a TwoD
//??showXYZ(tdlocs); // Error, not a ThreeD
//??showAll(tdlocs); // Erorr, not a FourD
????// Now, create some FourD objects.
????FourD fd[] = {
??????new FourD(1, 2, 3, 4),
??????new FourD(6, 8, 14, 8),
??????new FourD(22, 9, 4, 9),
??????new FourD(3, -2, -23, 17)
????};
????Coords<FourD> fdlocs = new Coords<FourD>(fd);????
????System.out.println("Contents of fdlocs.");
????// These are all OK.
????showXY(fdlocs);??
????showXYZ(fdlocs);
????showAll(fdlocs);
??}
}
public class ArrayListGenericDemo {
??public static void main(String[] args) {
????ArrayList<String> data = new ArrayList<String>();
????data.add("hello");
????data.add("goodbye");
????// data.add(new Date()); This won't compile!
????Iterator<String> it = data.iterator();
????while (it.hasNext()) {
??????String s = it.next();
??????System.out.println(s);
????}
??}
}
public class HashDemoGeneric {
??public static void main(String[] args) {
????HashMap<Integer,String> map = new HashMap<Integer,String>();
????map.put(1, "Ian");
????map.put(42, "Scott");
????map.put(123, "Somebody else");
????String name = map.get(42);
????System.out.println(name);
??}
}
interface MinMax<T extends Comparable<T>> {
??T min();
??T max();
}
// Now, implement MinMax
class MyClass<T extends Comparable<T>> implements MinMax<T> {
??T[] vals;
??MyClass(T[] o) { vals = o; }
??// Return the minimum value in vals.
??public T min() {
????T v = vals[0];
????for(int i=1; i < vals.length; i++)
??????if(vals[i].compareTo(v) < 0) v = vals[i];
????return v;
??}
??// Return the maximum value in vals.
??public T max() {
????T v = vals[0];
????for(int i=1; i < vals.length; i++)
??????if(vals[i].compareTo(v) > 0) v = vals[i];
????return v;
??}
}
public class GenIFDemo {
??public static void main(String args[]) {
????Integer inums[] = {3, 6, 2, 8, 6 };
????Character chs[] = {'b', 'r', 'p', 'w' };
????MyClass<Integer> iob = new MyClass<Integer>(inums);
????MyClass<Character> cob = new MyClass<Character>(chs);
????System.out.println("Max value in inums: " + iob.max());
????System.out.println("Min value in inums: " + iob.min());
????System.out.println("Max value in chs: " + cob.max());
????System.out.println("Min value in chs: " + cob.min());
??}
}
interface Executor<E extends Exception> {
????void execute() throws E;
}
public class GenericExceptionTest {
????public static void main(String args[]) {
????????try {
????????????Executor<IOException> e =
????????????????new Executor<IOException>() {
????????????????public void execute() throws IOException
????????????????{
????????????????????// code here that may throw an
????????????????????// IOException or a subtype of
????????????????????// IOException
????????????????}
????????????};
????????????e.execute();
????????} catch(IOException ioe) {
????????????System.out.println("IOException: " + ioe);
????????????ioe.printStackTrace();
????????}
????}
}??
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=661415
Java虛擬機的深入研究
作者:劉學超
1??Java技術與Java虛擬機
說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成: Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關系如下圖所示:
圖1??Java四個方面的關系
運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然后將之編譯成字節碼(.class文件)。最后字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。從上圖也可以看出Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫并編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:
在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴于平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。
那么到底什么是Java虛擬機(JVM)呢?通常我們談論JVM時,我們的意思可能是:
對JVM規范的的抽象說明是一些概念的集合,它們已經在書《The Java Virtual Machine Specification》(《Java虛擬機規范》)中被詳細地描述了;對JVM的具體實現要么是軟件,要么是軟件和硬件的組合,它已經被許多生產廠商所實現,并存在于多種平臺之上;運行Java程序的任務由JVM的運行期實例單個承擔。在本文中我們所討論的Java虛擬機(JVM)主要針對第三種情況而言。它可以被看成一個想象中的機器,在實際的計算機上通過軟件模擬來實現,有自己想象中的硬件,如處理器、堆棧、寄存器等,還有自己相應的指令系統。
JVM在它的生存周期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。
2??Java虛擬機的體系結構
剛才已經提到,JVM可以由不同的廠商來實現。由于廠商的不同必然導致JVM在實現上的一些不同,然而JVM還是可以實現跨平臺的特性,這就要歸功于設計JVM時的體系結構了。
我們知道,一個JVM實例的行為不光是它自己的事,還涉及到它的子系統、存儲區域、數據類型和指令這些部分,它們描述了JVM的一個抽象的內部體系結構,其目的不光規定實現JVM時它內部的體系結構,更重要的是提供了一種方式,用于嚴格定義實現時的外部行為。每個JVM都有兩種機制,一個是裝載具有合適名稱的類(類或是接口),叫做類裝載子系統;另外的一個負責執行包含在已裝載的類或接口中的指令,叫做運行引擎。每個JVM又包括方法區、堆、Java棧、程序計數器和本地方法棧這五個部分,這幾個部分和類裝載機制與運行引擎機制一起組成的體系結構圖為:
圖3??JVM的體系結構
JVM的每個實例都有一個它自己的方法域和一個堆,運行于JVM內的所有的線程都共享這些區域;當虛擬機裝載類文件的時候,它解析其中的二進制數據所包含的類信息,并把它們放到方法域中;當程序運行的時候,JVM把程序初始化的所有對象置于堆上;而每個線程創建的時候,都會擁有自己的程序計數器和Java棧,其中程序計數器中的值指向下一條即將被執行的指令,線程的Java棧則存儲為該線程調用Java方法的狀態;本地方法調用的狀態被存儲在本地方法棧,該方法棧依賴于具體的實現。
下面分別對這幾個部分進行說明。
執行引擎處于JVM的核心位置,在Java虛擬機規范中,它的行為是由指令集所決定的。盡管對于每條指令,規范很詳細地說明了當JVM執行字節碼遇到指令時,它的實現應該做什么,但對于怎么做卻言之甚少。Java虛擬機支持大約248個字節碼。每個字節碼執行一種基本的CPU運算,例如,把一個整數加到寄存器,子程序轉移等。Java指令集相當于Java程序的匯編語言。
Java指令集中的指令包含一個單字節的操作符,用于指定要執行的操作,還有0個或多個操作數,提供操作所需的參數或數據。許多指令沒有操作數,僅由一個單字節的操作符構成。
虛擬機的內層循環的執行過程如下: do{ 取一個操作符字節; 根據操作符的值執行一個動作; }while(程序未結束)
由于指令系統的簡單性,使得虛擬機執行的過程十分簡單,從而有利于提高執行的效率。指令中操作數的數量和大小是由操作符決定的。如果操作數比一個字節大,那么它存儲的順序是高位字節優先。例如,一個16位的參數存放時占用兩個字節,其值為:
第一個字節*256+第二個字節字節碼。
指令流一般只是字節對齊的。指令tableswitch和lookup是例外,在這兩條指令內部要求強制的4字節邊界對齊。
對于本地方法接口,實現JVM并不要求一定要有它的支持,甚至可以完全沒有。Sun公司實現Java本地接口(JNI)是出于可移植性的考慮,當然我們也可以設計出其它的本地接口來代替Sun公司的JNI。但是這些設計與實現是比較復雜的事情,需要確保垃圾回收器不會將那些正在被本地方法調用的對象釋放掉。
Java的堆是一個運行時數據區,類的實例(對象)從中分配空間,它的管理是由垃圾回收來負責的:不給程序員顯式釋放對象的能力。Java不規定具體使用的垃圾回收算法,可以根據系統的需求使用各種各樣的算法。
Java方法區與傳統語言中的編譯后代碼或是Unix進程中的正文段類似。它保存方法代碼(編譯后的java代碼)和符號表。在當前的Java實現中,方法代碼不包括在垃圾回收堆中,但計劃在將來的版本中實現。每個類文件包含了一個Java類或一個Java界面的編譯后的代碼。可以說類文件是Java語言的執行代碼文件。為了保證類文件的平臺無關性,Java虛擬機規范中對類文件的格式也作了詳細的說明。其具體細節請參考Sun公司的Java虛擬機規范。
Java虛擬機的寄存器用于保存機器的運行狀態,與微處理器中的某些專用寄存器類似。Java虛擬機的寄存器有四種:
在上述體系結構圖中,我們所說的是第一種,即程序計數器,每個線程一旦被創建就擁有了自己的程序計數器。當線程執行Java方法的時候,它包含該線程正在被執行的指令的地址。但是若線程執行的是一個本地的方法,那么程序計數器的值就不會被定義。
Java虛擬機的棧有三個區域:局部變量區、運行環境區、操作數區。
局部變量區
每個Java方法使用一個固定大小的局部變量集。它們按照與vars寄存器的字偏移量來尋址。局部變量都是32位的。長整數和雙精度浮點數占據了兩個局部變量的空間,卻按照第一個局部變量的索引來尋址。(例如,一個具有索引n的局部變量,如果是一個雙精度浮點數,那么它實際占據了索引n和n+1所代表的存儲空間)虛擬機規范并不要求在局部變量中的64位的值是64位對齊的。虛擬機提供了把局部變量中的值裝載到操作數棧的指令,也提供了把操作數棧中的值寫入局部變量的指令。
運行環境區
在運行環境中包含的信息用于動態鏈接,正常的方法返回以及異常捕捉。
動態鏈接
運行環境包括對指向當前類和當前方法的解釋器符號表的指針,用于支持方法代碼的動態鏈接。方法的class文件代碼在引用要調用的方法和要訪問的變量時使用符號。動態鏈接把符號形式的方法調用翻譯成實際方法調用,裝載必要的類以解釋還沒有定義的符號,并把變量訪問翻譯成與這些變量運行時的存儲結構相應的偏移地址。動態鏈接方法和變量使得方法中使用的其它類的變化不會影響到本程序的代碼。
正常的方法返回
如果當前方法正常地結束了,在執行了一條具有正確類型的返回指令時,調用的方法會得到一個返回值。執行環境在正常返回的情況下用于恢復調用者的寄存器,并把調用者的程序計數器增加一個恰當的數值,以跳過已執行過的方法調用指令,然后在調用者的執行環境中繼續執行下去。
異常捕捉
異常情況在Java中被稱作Error(錯誤)或Exception(異常),是Throwable類的子類,在程序中的原因是:①動態鏈接錯,如無法找到所需的class文件。②運行時錯,如對一個空指針的引用。程序使用了throw語句。
當異常發生時,Java虛擬機采取如下措施:
操作數棧區
機器指令只從操作數棧中取操作數,對它們進行操作,并把結果返回到棧中。選擇棧結構的原因是:在只有少量寄存器或非通用寄存器的機器(如Intel486)上,也能夠高效地模擬虛擬機的行為。操作數棧是32位的。它用于給方法傳遞參數,并從方法接收結果,也用于支持操作的參數,并保存操作的結果。例如,iadd指令將兩個整數相加。相加的兩個整數應該是操作數棧頂的兩個字。這兩個字是由先前的指令壓進堆棧的。這兩個整數將從堆棧彈出、相加,并把結果壓回到操作數棧中。
每個原始數據類型都有專門的指令對它們進行必須的操作。每個操作數在棧中需要一個存儲位置,除了long和double型,它們需要兩個位置。操作數只能被適用于其類型的操作符所操作。例如,壓入兩個int類型的數,如果把它們當作是一個long類型的數則是非法的。在Sun的虛擬機實現中,這個限制由字節碼驗證器強制實行。但是,有少數操作(操作符dupe和swap),用于對運行時數據區進行操作時是不考慮類型的。
本地方法棧,當一個線程調用本地方法時,它就不再受到虛擬機關于結構和安全限制方面的約束,它既可以訪問虛擬機的運行期數據區,也可以使用本地處理器以及任何類型的棧。例如,本地棧是一個C語言的棧,那么當C程序調用C函數時,函數的參數以某種順序被壓入棧,結果則返回給調用函數。在實現Java虛擬機時,本地方法接口使用的是C語言的模型棧,那么它的本地方法棧的調度與使用則完全與C語言的棧相同。
3??Java虛擬機的運行過程
上面對虛擬機的各個部分進行了比較詳細的說明,下面通過一個具體的例子來分析它的運行過程。
虛擬機通過調用某個指定類的方法main啟動,傳遞給main一個字符串數組參數,使指定的類被裝載,同時鏈接該類所使用的其它的類型,并且初始化它們。例如對于程序:
class HelloApp { public static void main(String[] args) { System.out.println("Hello World!"); for (int i = 0; i < args.length; i++ ) { System.out.println(args[i]); } } }
編譯后在命令行模式下鍵入: java HelloApp run virtual machine
將通過調用HelloApp的方法main來啟動java虛擬機,傳遞給main一個包含三個字符串"run"、"virtual"、"machine"的數組。現在我們略述虛擬機在執行HelloApp時可能采取的步驟。
開始試圖執行類HelloApp的main方法,發現該類并沒有被裝載,也就是說虛擬機當前不包含該類的二進制代表,于是虛擬機使用ClassLoader試圖尋找這樣的二進制代表。如果這個進程失敗,則拋出一個異常。類被裝載后同時在main方法被調用之前,必須對類HelloApp與其它類型進行鏈接然后初始化。鏈接包含三個階段:檢驗,準備和解析。檢驗檢查被裝載的主類的符號和語義,準備則創建類或接口的靜態域以及把這些域初始化為標準的默認值,解析負責檢查主類對其它類或接口的符號引用,在這一步它是可選的。類的初始化是對類中聲明的靜態初始化函數和靜態域的初始化構造方法的執行。一個類在初始化之前它的父類必須被初始化。整個過程如下:
圖4:虛擬機的運行過程
4??結束語
本文通過對JVM的體系結構的深入研究以及一個Java程序執行時虛擬機的運行過程的詳細分析,意在剖析清楚Java虛擬機的機理。
abstract class和interface是Java語言中對于抽象類定義進行支持的兩種機制,正是由于這兩種機制的存在,才賦予了Java強大的面向對象能力。abstract class和interface之間在對于抽象類定義的支持方面具有很大的相似性,甚至可以相互替換,因此很多開發者在進行抽象類定義時對于abstract class和interface的選擇顯得比較隨意。其實,兩者之間還是有很大的區別的,對于它們的選擇甚至反映出對于問題領域本質的理解、對于設計意圖的理解是否正確、合理。本文將對它們之間的區別進行一番剖析,試圖給開發者提供一個在二者之間進行選擇的依據。
理解抽象類
abstract class和interface在Java語言中都是用來進行抽象類(本文中的抽象類并非從abstract class翻譯而來,它表示的是一個抽象體,而abstract class為Java語言中用于定義抽象類的一種方法,請讀者注意區分)定義的,那么什么是抽象類,使用抽象類能為我們帶來什么好處呢?
在面向對象的概念中,我們知道所有的對象都是通過類來描繪的,但是反過來卻不是這樣。并不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。抽象類往往用來表征我們在對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不同,但是本質上相同的具體概念的抽象。比如:如果我們進行一個圖形編輯軟件的開發,就會發現問題領域存在著圓、三角形這樣一些具體概念,它們是不同的,但是它們又都屬于形狀這樣一個概念,形狀這個概念在問題領域是不存在的,它就是一個抽象概念。正是因為抽象的概念在問題領域沒有對應的具體概念,所以用以表征抽象概念的抽象類是不能夠實例化的。
在面向對象領域,抽象類主要用來進行類型隱藏。我們可以構造出一個固定的一組行為的抽象描述,但是這組行為卻能夠有任意個可能的具體實現方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現則表現為所有可能的派生類。模塊可以操作一個抽象體。由于模塊依賴于一個固定的抽象體,因此它可以是不允許修改的;同時,通過從這個抽象體派生,也可擴展此模塊的行為功能。熟悉OCP的讀者一定知道,為了能夠實現面向對象設計的一個最核心的原則OCP(Open-Closed Principle),抽象類是其中的關鍵所在。
從語法定義層面看abstract class和interface
在語法層面,Java語言對于abstract class和interface給出了不同的定義方式,下面以定義一個名為Demo的抽象類為例來說明這種不同。
使用abstract class的方式定義Demo抽象類的方式如下:
abstract class Demo {
abstract void method1();
abstract void method2();
…
}
使用interface的方式定義Demo抽象類的方式如下:
interface Demo {
void method1();
void method2();
…
}
在abstract class方式中,Demo可以有自己的數據成員,也可以有非abstarct的成員方法,而在interface方式的實現中,Demo只能夠有靜態的不能被修改的數據成員(也就是必須是static final的,不過在interface中一般不定義數據成員),所有的成員方法都是abstract的。從某種意義上說,interface是一種特殊形式的abstract class。
從編程的角度來看,abstract class和interface都可以用來實現"design by contract"的思想。但是在具體的使用上面還是有一些區別的。
首先,abstract class在Java語言中表示的是一種繼承關系,一個類只能使用一次繼承關系。但是,一個類卻可以實現多個interface。也許,這是Java語言的設計者在考慮Java對于多重繼承的支持方面的一種折中考慮吧。
其次,在abstract class的定義中,我們可以賦予方法的默認行為。但是在interface的定義中,方法卻不能擁有默認行為,為了繞過這個限制,必須使用委托,但是這會 增加一些復雜性,有時會造成很大的麻煩。
在抽象類中不能定義默認行為還存在另一個比較嚴重的問題,那就是可能會造成維護上的麻煩。因為如果后來想修改類的界面(一般通過abstract class或者interface來表示)以適應新的情況(比如,添加新的方法或者給已用的方法中添加新的參數)時,就會非常的麻煩,可能要花費很多的時間(對于派生類很多的情況,尤為如此)。但是如果界面是通過abstract class來實現的,那么可能就只需要修改定義在abstract class中的默認行為就可以了。
同樣,如果不能在抽象類中定義默認行為,就會導致同樣的方法實現出現在該抽象類的每一個派生類中,違反了"one rule,one place"原則,造成代碼重復,同樣不利于以后的維護。因此,在abstract class和interface間進行選擇時要非常的小心。
從設計理念層面看abstract class和interface
上面主要從語法定義和編程的角度論述了abstract class和interface的區別,這些層面的區別是比較低層次的、非本質的。本小節將從另一個層面:abstract class和interface所反映出的設計理念,來分析一下二者的區別。作者認為,從這個層面進行分析才能理解二者概念的本質所在。
前面已經提到過,abstarct class在Java語言中體現了一種繼承關系,要想使得繼承關系合理,父類和派生類之間必須存在"is a"關系,即父類和派生類在概念本質上應該是相同的(參考文獻〔3〕中有關于"is a"關系的大篇幅深入的論述,有興趣的讀者可以參考)。對于interface 來說則不然,并不要求interface的實現者和interface定義在概念本質上是一致的,僅僅是實現了interface定義的契約而已。為了使論述便于理解,下面將通過一個簡單的實例進行說明。
考慮這樣一個例子,假設在我們的問題領域中有一個關于Door的抽象概念,該Door具有執行兩個動作open和close,此時我們可以通過abstract class或者interface來定義一個表示該抽象概念的類型,定義方式分別如下所示:
使用abstract class方式定義Door:
abstract class Door {
abstract void open();
abstract void close();
}
使用interface方式定義Door:
interface Door {
void open();
void close();
}
其他具體的Door類型可以extends使用abstract class方式定義的Door或者implements使用interface方式定義的Door。看起來好像使用abstract class和interface沒有大的區別。
如果現在要求Door還要具有報警的功能。我們該如何設計針對該例子的類結構呢(在本例中,主要是為了展示abstract class和interface反映在設計理念上的區別,其他方面無關的問題都做了簡化或者忽略)?下面將羅列出可能的解決方案,并從設計理念層面對這些不同的方案進行分析。
解決方案一:
簡單的在Door的定義中增加一個alarm方法,如下:
abstract class Door {
abstract void open();
abstract void close();
abstract void alarm();
}
或者
interface Door {
void open();
void close();
void alarm();
}
那么具有報警功能的AlarmDoor的定義方式如下:
class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}
或者
class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }
}
這種方法違反了面向對象設計中的一個核心原則ISP(Interface Segregation Priciple),在Door的定義中把Door概念本身固有的行為方法和另外一個概念"報警器"的行為方法混在了一起。這樣引起的一個問題是那些僅僅依賴于Door這個概念的模塊會因為"報警器"這個概念的改變(比如:修改alarm方法的參數)而改變,反之依然。
解決方案二:
既然open、close和alarm屬于兩個不同的概念,根據ISP原則應該把它們分別定義在代表這兩個概念的抽象類中。定義方式有:這兩個概念都使用abstract class方式定義;兩個概念都使用interface方式定義;一個概念使用abstract class方式定義,另一個概念使用interface方式定義。
顯然,由于Java語言不支持多重繼承,所以兩個概念都使用abstract class方式定義是不可行的。后面兩種方式都是可行的,但是對于它們的選擇卻反映出對于問題領域中的概念本質的理解、對于設計意圖的反映是否正確、合理。我們一一來分析、說明。
如果兩個概念都使用interface方式來定義,那么就反映出兩個問題:1、我們可能沒有理解清楚問題領域,AlarmDoor在概念本質上到底是Door還是報警器?2、如果我們對于問題領域的理解沒有問題,比如:我們通過對于問題領域的分析發現AlarmDoor在概念本質上和Door是一致的,那么我們在實現時就沒有能夠正確的揭示我們的設計意圖,因為在這兩個概念的定義上(均使用interface方式定義)反映不出上述含義。
如果我們對于問題領域的理解是:AlarmDoor在概念本質上是Door,同時它有具有報警的功能。我們該如何來設計、實現來明確的反映出我們的意思呢?前面已經說過,abstract class在Java語言中表示一種繼承關系,而繼承關系在本質上是"is a"關系。所以對于Door這個概念,我們應該使用abstarct class方式來定義。另外,AlarmDoor又具有報警功能,說明它又能夠完成報警概念中定義的行為,所以報警概念可以通過interface方式定義。如下所示:
abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}
這種實現方式基本上能夠明確的反映出我們對于問題領域的理解,正確的揭示我們的設計意圖。其實abstract class表示的是"is a"關系,interface表示的是"like a"關系,大家在選擇時可以作為一個依據,當然這是建立在對問題領域的理解上的,比如:如果我們認為AlarmDoor在概念本質上是報警器,同時又具有Door的功能,那么上述的定義方式就要反過來了。
?
結論
abstract class和interface是Java語言中的兩種定義抽象類的方式,它們之間有很大的相似性。但是對于它們的選擇卻又往往反映出對于問題領域中的概念本質的理解、對于設計意圖的反映是否正確、合理,因為它們表現了概念間的不同的關系(雖然都能夠實現需求的功能)。這其實也是語言的一種的慣用法。
| |||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
---|---|---|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 1 | 2 | 3 | |||
4 | 5 | 6 | 7 | 8 | 9 | 10 | |||
11 | 12 | 13 | 14 | 15 | 16 | 17 | |||
18 | 19 | 20 | 21 | 22 | 23 | 24 | |||
25 | 26 | 27 | 28 | 29 | 30 | 31 | |||
1 | 2 | 3 | 4 | 5 | 6 | 7 |