1. 程式人生 > >java: 封裝快取池(int與Integer)、常量池(拘留池)、static變數 static程式碼塊 static方法、 final變數、final 方法、final類 整理

java: 封裝快取池(int與Integer)、常量池(拘留池)、static變數 static程式碼塊 static方法、 final變數、final 方法、final類 整理

java 記憶體模型:

JVM主要管理兩種型別記憶體:堆和非堆,堆記憶體(Heap Memory)是在 Java 虛擬機器啟動時建立,

非堆記憶體(Non-heap Memory)是在

JVM堆之外的記憶體

物件的具體(屬性 方法);

非堆區

棧:方法執行時 在此操作,區域性變數,堆中物件的地址;

靜態資料區

static修飾的資料區(假設 技術不能假設!!):如果經static修飾的資料 在static區,

經static修飾過的變數 比如成員變數

建立一個物件時,這個成員變數不會在堆裡再有這個變量了,這就是static 變數的

作用;它是多物件共享的;

程式碼:

/*
 * 記住:靜態資料區不屬於堆,當然更不屬於物件
Static : static修飾的,在編譯期就有了,其早於物件的建立;屬於類;
在靜態方法區內,不能呼叫其他方法(因為其他方法屬於物件),也不能用this,super,但可以使用靜態變數
反之:在其他方法區內可以呼叫靜態方法
 */
public class Sta { private static String name = "duck"; public int age = 21; static int hh = 99; public void say() { System.out.println("我 是一隻可愛的鴨子"); } public String getName() { return name; } public static void action() { name = "my name is not duck"; System.out.println("我是靜態方法"); } }
package com.stati;

/*
 * 
 * Out.In in = new Out().new In()可以用來生成內部類的物件,這種方法存在兩個小知識點需要注意

  1.開頭的Out是為了標明需要生成的內部類物件在哪個外部類當中

  2.必須先有外部類的物件才能生成內部類的物件,因為內部類的作用就是為了訪問外部類中的成員變數
 */

public class Test {
	
	//private int h = 7;
	public static int a;
	int b;
	public static void main(String[] args) {
		 a = 3;
//		 b = 4;  //這是錯誤的,其屬於物件成員變數,static修飾的在方法塊,早於物件的建立;
		// TODO Auto-generated method stub
		
		/*
		 * Java關鍵字this只能用於方法方法體內。當一個物件建立後,Java虛擬機器(JVM)就會給這個物件分配一個引用自身的指標,這個指標的名字就是this。
		 * 因此,this只能在類中的非靜態方法中使用,靜態方法和靜態的程式碼塊中絕對不能出現this,
		 * 這在“Java關鍵字static、final使用總結”一文中給出了明確解釋。並且this只和特定的物件關聯,而不和類關聯,同一個類的不同物件有不同的this
		 * Static 修飾方法:不屬於物件,屬於類,用類名直接呼叫
		 */
		Sta.action();
		
		Sta sta = new Sta();
		System.out.println("我的名字是:" + sta.getName());
		
		Sta s1 = new Sta();
		
		s1.hh = 88;
		
		Sta s2 = new Sta();
		System.out.println(s2.hh);
		
	}

}
測試結果:
我是靜態方法
我的名字是:my name is not duck
88
static修飾方法:
/*靜態方法可以直接通過類名呼叫,任何的例項也都可以呼叫,
		因此靜態方法中不能用this和super關鍵字,不能直接訪問所屬類的例項變數和例項方法(就是不帶static的成員變數和成員成員方法),
		只能訪問所屬類的靜態成員變數和成員方法。*/
		
		/* 原因 :jvm 會在 沒有執行之前 首先對 static的 變數  程式碼塊 函式 進行處理 所以 可以如此 
		 * //靜態的方法 變數 程式碼塊 在靜態去 在所有的物件建立之前 就存在  因此   用類名引用*/public static void StaticWay(){
			
			//System.out.println(age); //錯誤
			//action();  //錯誤
			
			
			System.out.println(a); // a是靜態成員變數 所以在此可以
			
			System.out.println("我是靜態方法");
			
			StaticWay2();   // static 是靜態方法所以 在此可以
			
			Animal.StaticWay2();  //通過類名 也可以
			
		}
static程式碼塊:
/*static程式碼塊也叫靜態程式碼塊,是在類中獨立於類成員的static語句塊,可以有多個,位置可以隨便放,它不在任何的方法體內,
		 * JVM載入類時會執行這些靜態的程式碼塊,如果static程式碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,每個程式碼塊只會被執行一次*/
		
		static {
			
			int ui = 88;  //不是成員變數  
			System.out.println("我是static區1 ");
			
			System.out.println(ui);
			
		}
static與final:

static final作用 : 在一塊公共的記憶體 且只讀;static final用來修飾成員變數和成員方法,

可簡單理解為“全域性常量”!對於變數,表示一旦給值就不可修改,並且通過類名可以訪問  對於方法,

表示不可覆蓋,並且可以通過類名直接訪問。

常量區(只讀): final修飾的;

final修飾成員變數:final int W_FINAL = 9;//常亮 大寫下劃線

修飾作用:只讀,在常量區,在編譯期解決;

final修飾方法: 對於方法,表示不可覆蓋,並且可以通過類名直接訪問;

final修飾類:表示該類不可以被繼承;

 java快取池(相對於封裝int與Integer):

裝箱:把基本型別用它們相應的引用型別包裝起來,使其具有物件的性質。int包裝成Integer、

float包裝成Float;

拆箱:和裝箱相反,將引用型別的物件簡化成值型別的資料;

Integer a = 100; 這是自動裝箱  (編譯器呼叫的是static Integer valueOf(int i))即:

等同Integer a = Integer.valueOf(100)
int     b = new Integer(100); 這是自動拆箱


jvm會編譯期就在堆裡建立快取池數值【-128,127】;

Integer i = 12;  //會在堆裡找12這個數值,找到就把快取池中該值得地址返回給i;

Integer j= 12; //i的地址與j的地址相同;

//超出範圍System.out.println(u==j); false

Integer u = 128; 

Integer j = 128;

Integer與Integer間的比較,從jdk1.5開始,有自動裝箱這麼一個機制,在byte-128到127範圍內

(ps整型的八位二進位制的表示的範圍為-128到127),如果存在了一個值,再建立相同值的時候就不會重

新建立,而是引用原來那個,但是超過byte範圍還是會新建的物件:程式碼如下

 /*   
  * 返回一個表示指定的 int 值的 Integer 例項。如果不需要新的 Integer 例項,則   
  * 通常應優先使用該方法,而不是構造方法 Integer(int),因為該方法有可能通過   
  * 快取經常請求的值而顯著提高空間和時間效能。    
  * @param  i an <code>int</code> value.   
  * @return a <tt>Integer</tt> instance representing <tt>i</tt>.   
  * @since  1.5   
  */    
public static Integer valueOf(int i) {     
      final int offset = 128;     
      if (i >= -128 && i <= 127) { // must cache      
        return IntegerCache.cache[i + offset];     
     }     
     return new Integer(i);     
可以看到對於範圍在-128到127的整數,valueOf方法做了特殊處理。 
採用IntegerCache.cache[i + offset]這個方法。 
從名字,我們可以猜出這是某種快取機制。 
進一步跟蹤IntegerCache這個類,此類程式碼如下:
/*   
  * IntegerCache內部類   
  * 其中cache[]陣列用於存放從-128到127一共256個整數   
  */    
private static class IntegerCache {     
    private IntegerCache(){}     
    
    static final Integer cache[] = new Integer[-(-128) + 127 + 1];     
    
    static {     
        for(int i = 0; i < cache.length; i++)     
        cache[i] = new Integer(i - 128);     
    }     
}    
這就是valueOf方法真正的優化方法,當-128=<i<=127的時候,返回的是IntegerCache中的陣列的值;當 i>127 或 i<-128 時,返回的是Integer類物件

例子:

package com.third_day;


public class Wrapper {
	
	
	public static void main(String[] args) {
		
		String s1 = "123";
		
		int i1 = Integer.parseInt(s1);
		
		System.out.println(i1);
		
		
		Integer i = 12; 
		
		int ii = 12;
		
		System.out.println(i==ii);
		
		
		 Integer a = new Integer(100); //在堆中建立了一個物件
		 
	     Integer b = 100; //等同Integer。valueOf(100); 即從return IntegerCache.cache[i + offset]; 如果zai
	    /* 堆裡找到100 就把 堆裡100值得地址 給棧裡的b*/
	     
	     int bb = 100;
	     
	     System.out.println("m11 result " + (a == b)); //false
	     
	     System.out.println("m11 result " + (bb == b));
	     
	     
	     Integer c = new Integer(101);
	     Integer d = new Integer(101);
	     System.out.println("m21 result " + (c == d));
	     
	     
	     Integer k = new Integer(101);
	     
	     Integer e = Integer.valueOf(101); //相當於Integer f = 101;
	     
	     Integer f = 101;
	     
	     
	     System.out.println("m41 result " + (e == f));
	     
	     System.out.println("m41 result " + (k == f));
	     
	     System.out.println("m41 result " + (k == e));
	     
	     
	     
	     
	     /*注意::基本資料型別和物件比較的時候,物件會自動拆箱為基本資料型別再比較,比較的就是裡面的值而不是地址*/
	     
	     Integer i11 = new Integer(120);
	     
	     int i2 = 120;
	     
	     Integer i22 = 120;
	     // newInteger與int11: Integer i11 = new Integer(120);會自動拆箱(即:堆裡的內容 拿到棧裡面)變成 i11=120; 就比較值了 而不是地址了
	     System.out.println("---newInteger與int11---"+(i11==i2));  
	     
	     //比較的是地址 一個指向快取池(池範圍:-128--127) 也是在堆裡的 跟 常量池不同 ,一個指向 新建的堆裡的內容
	     System.out.println("---newInteger與int---"+(i11==i22));
	     
	     //比較內容 i22 會轉換 int i22 = 120; 
	     System.out.println("---newInteger與int---"+(i2==i22));
	}

}

java常量池(拘留池):

String物件的初始化

由於String物件特別常用,所以在對String物件進行初始化時,Java提供了一種簡化的特殊語法,格式如下:

                            String s = “abc”;

                            s = “Java語言”;

其實按照面向物件的標準語法,其格式應該為:

                            String s = new String(“abc”);

                            s = new String(“Java語言”);

只是按照面向物件的標準語法,在記憶體使用上存在比較大的浪費。例如String s = new String(“abc”);實際上

建立了兩個String物件,一個是”abc”物件,儲存在常量空間中,一個是使用new關鍵字為對

s申請的空間其它的構造方法的引數,

可以參看String類的API文件。


 String str1 = "abc"; 分析---》

公共語言執行庫通過維護一個表來存放字串,該表稱為拘留池,它包含程式中以程式設計方式宣告或建立

的每個唯一的字串的一個引用。

因此,具有特定值的字串的例項在系統中只有一個。

例如,如果將同一字串分配給幾個變數,

執行庫就會從拘留池中檢索對該字串的相同引用,並將它分配給各個變數。

個初始時為空的字串池,它由類 String 私有地維護。 當呼叫 intern 方法時,

如果池已經包含一個等於此 String 物件的字串(該物件由 equals(Object) 方法確定),

則返回池中的字串。否則,將此 String 物件新增到池中,並且返回此 String 物件的引用

(1) String str1 = "abc"; 
    System.out.println(str1 == "abc");

步驟: 
1) 棧中開闢一塊空間存放引用str1;
2) String池中開闢一塊空間,存放String常量"abc"; 
3) 引用str1指向池中String常量"abc";
4) str1所指代的地址即常量"abc"所在地址,輸出為true;

(2) String str2 = new String("abc"); 
    System.out.println(str2 == "abc");

步驟: 
1) 棧中開闢一塊空間存放引用str2; 
2) 堆中開闢一塊空間存放一個新建的String物件"abc"; 
3) 引用str2指向堆中的新建的String物件"abc";
4) str2所指代的物件地址為堆中地址,而常量"abc"地址在池中,輸出為false;

私自感覺不錯的部落格:@http://blog.csdn.net/u010644448/article/details/51980370

小結:注意java編譯期(生成.class) 與執行期(jvm解釋class 位元組碼)