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); } }
測試結果:
static修飾方法:我是靜態方法 我的名字是: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與final:/*static程式碼塊也叫靜態程式碼塊,是在類中獨立於類成員的static語句塊,可以有多個,位置可以隨便放,它不在任何的方法體內, * JVM載入類時會執行這些靜態的程式碼塊,如果static程式碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,每個程式碼塊只會被執行一次*/ static { int ui = 88; //不是成員變數 System.out.println("我是static區1 "); System.out.println(ui); }
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範圍還是會新建的物件:程式碼如下
可以看到對於範圍在-128到127的整數,valueOf方法做了特殊處理。/* * 返回一個表示指定的 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);
採用IntegerCache.cache[i + offset]這個方法。
從名字,我們可以猜出這是某種快取機制。
進一步跟蹤IntegerCache這個類,此類程式碼如下:這就是valueOf方法真正的優化方法,當-128=<i<=127的時候,返回的是IntegerCache中的陣列的值;當 i>127 或 i<-128 時,返回的是Integer類物件/* * 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); } }
例子:
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 位元組碼)