摘要:點擊進入我的博客初始化和清理是編程安全的兩個問題。延續了中的構造器確保初始化,并引入了垃圾回收器管理和釋放內存。用構造方法確保初始化構造方法和類名相同。用于強制進行終結動作。載入該文件,靜態數據進行初始化,執行靜態代碼塊。
點擊進入我的博客
初始化和清理是編程安全的兩個問題。Java延續了C++中的構造器確保初始化,并引入了“垃圾回收器”管理和釋放內存。
5.1 用構造方法確保初始化構造方法和類名相同。原因是盡量減少和成員名稱沖突;并且調用構造方法是編譯器的責任,名稱相同編譯器才會知道應該調用哪個方法。
由于構造方法和類名相同,所以構造方法不適應于首字母小寫的約定規范。
構造方法有默認的無參構造方法,也可以帶參數。
構造方法沒有返回值,請注意這跟void不同。
5.2 方法重載方法重載是指方法名相同,但是參數的個數、類型和順序不同。
由于構造方法必須和類名相同,即方法名已經確定,但想要用多種方式(參數)創建一個對象,就必須引入方法重載。
方法重載不僅適用于構造方法,還適用于其他方法。
warning:func(int i, String str)和func(String str, int i)參數順序不同構成重載,但請盡量避免這種寫法。
構成重載深層次的原因:只要這兩個方法編譯器能區分開來,在調用的時候知道要調用的是哪一個,不會產生混淆,這兩個方法就構成重載。
對于byte、short、int、float、double如果找不到對應基本類型方法,則會按照向上轉化的路線找匹配的方法
如果是char,如果找不到對應的基本類型方法,直接從int向上找匹配的方法。
public static void print(char c) { System.out.println("char: " + c); } public static void print(byte b) { System.out.println("byte: " + b); } public static void print(short s) { System.out.println("short: " + s); } public static void print(int i) { System.out.println("int: " + i); } public static void print(long l) { System.out.println("long: " + l); } public static void print(float f) { System.out.println("float: " + f); } public static void print(double d) { System.out.println("double: " + d); }
void f() {}; boolean f() { return true; }; // 只調用f()無法區分是哪個方法5.3 默認構造器
如果你的類中沒有構造器,則編譯器會幫你自動創建一個默認構造器。可以通過反編譯.class文件來驗證這一點。
如果你自己定義了一個構造方法,則編譯器不會幫你創建默認構造器。
5.4 this關鍵字下述代碼中,有兩個對象a1、a2,按照面向過程的函數形式,在執行func()函數的時候,怎么知道是被a1、a2調用呢?為了能用面向對象的語法來編寫代碼,編譯器做了一些幕后工作。它暗自把“所操作的對象”作為第一個參數傳遞給func()函數,即func(a1)。這是內部的表示形式,我們并不能這樣寫代碼。
public class Test { public static void main(String[] args) { A a1 = new A(); A a2 = new A(); a1.func(); a2.func(); } } class A { void func() {} }
this關鍵字只能在方法內部使用,表示對“調用方法的那個對象的引用。”
有人喜歡將this放到每個方法調用和字段引用前,千萬不要這么做!
當需要返回當前對象的引用時,可以通過return this;
5.4.1 在構造器中調用構造器可以通過this(params);來調用其他的構造方法
可以通過this調用一個其他的構造方法,但不能調用兩個及以上
通過this調用其他的構造方法必須放到該構造方法的第一行
構造方法不能通過this調用自己
public Test(int i) { System.out.println("Test " + i); } public Test(String str) { System.out.println("Test " + str); } // (1) public Test() { this(1); // this("imbug"); System.out.println("Test"); } public static void main(String[] args) { Test test = new Test(); }5.4.2 static方法
static方法中不能使用this關鍵字
static方法中不能調用非靜態方法,反之則可以
5.5 清理:終結處理和垃圾回收Java的垃圾回收器(GC)負責回收無用對象占據的內存資源
假定你的對象(不是通過new)獲得了一塊“特殊”的內存區域,由于GC只知道new分配的內存,所以它不知道如何釋放該對象的“特殊”內存區域。為了應付這種情況,Java允許在類中定義一個名為finalize()的方法。
5.5.1 finalize()方法一旦GC準備釋放對象的存儲空間,首先調用該方法;并且在下一次垃圾回收動作發生時,才會真正回收對象占用的內存。即調用該方法但時候,對象還沒有被回收。
finalize()方法不是C++中的析夠方法,
在C++中對象一定會被銷毀(代碼無Bug),但是在Java里的對象并非總是被垃圾回收。
垃圾回收只與內存相關,也就是說使用GC的唯一原因是為了回收程序不再使用的內存。
上述討論了,對象可能會獲得一塊“特殊”的內存區域,這主要發生在JNI本地方法的情況下,本地方法是在Java中使用非Java代碼的方式。非Java代碼可能會調用C的malloc()來分配存儲空間,而且除了free()方法否則其存儲空間將得不到釋放,從而造成內存泄漏。此時就可以在finalize()中調用free()方法,清理本地對象。
不建議用finalize方法完成“非內存資源”的清理工作,但也可以作為確保某些非內存資源(如Socket、文件等)釋放的一個補充。
System.gc()與System.runFinalization()方法增加了finalize方法執行的機會,但不保證一定會執行。
用戶可以手動調用對象的finalize方法,但并不影響GC對finalize的行為,即沒有卵用~
finalize()執行流程
5.5.2 你必須實施清理Java不允許創建局部對象(即堆棧上的對象),必須使用new創建對象。
無論是“垃圾回收”還是“終結”,都不保證一定會發生。
5.5.3 終結條件如果某個對象的內存可以被安全釋放了,例如對象代表了一個打開的文件,那么回收內存前必須保證文件關閉。這個在finalize()中可以檢驗文件的狀態。
System.gc()用于強制進行終結動作。
@Override protected void finalize() throws Throwable { super.finalize(); // if(文件未安全關閉) System.out.println("error"); } public static void main(String[] args) { func(); System.gc(); } public static void func() { Test t1 = new Test(); Test t2 = new Test(); }5.5.4 GC如何工作
更詳細內容請看JVM工作原理!!!
GC會整理堆內存空間,因此導致new新建對象時的內存分配速度
每個對象都含有一個計數器,當引用連接至對象時+1,引用離開作用域或被置為null時-1。GC遍歷全部對象,發現計數器為0的時候就會釋放其內存。
優點:簡單
缺點:慢、循環引用問題、對象應該被回收但引用計數不為零
引用計數只是為了說明GC的工作方式,但實際上似乎沒有任何Java虛擬機實現過。
原理:每個“活”的對象,一定能追溯到其存活在堆棧或靜態存儲區之中的引用。
方法:從堆棧和靜態存儲區開始,遍歷所有引用;然后追蹤它所引用的對象,然后是這些對象包含的所有對象,反復進行直至“根源于堆棧和靜態存儲區的引用”所形成的網絡被全部訪問完為止
先暫停程序的運行,然后將全部活的對象從當前堆復制到另一個堆,沒有復制的都是垃圾;新堆里的對象在內存中時連續的
不屬于后臺回收模式,因為要暫停程序的運行
把對象從一個堆復制到另一個堆時,所有指向它們的引用都必須要修正。
效率低的原因(1):需要兩個分離的堆,因此需要兩倍的內存空間
效率低的原因(2):程序穩定后垃圾很少,即需要存活的對象遠大于垃圾數量,此時復制到另一個堆非常浪費。
用根搜索算法找到所有存活的對象并標記(此過程不回收),當全部標記工作完成的時候,清理所有沒有標記的對象
缺點(1):導致內存空間不連續
缺點(2):也會暫停程序
JVM中,內存以較大的“塊”為單位;如果對象比較大,它會占據多帶帶的塊;有了塊之后,GC就可以在回收的時候往廢棄的塊中拷貝對象了
每個塊用相應的代數(generation count)來記錄是否存活;如果塊在某處被引用,其代數會增加;GC會對上次回收動作之后新分配的塊進行整理
GC會定期進行完整的清理動作,大型對象不會被復制但是其代數會增加;小型對象的那些塊則被復制并整理
JVM會進行監視,如果所有對象都很穩定,垃圾回收器的效率降低的話,就切換到標記-清掃模式;同樣,JVM會跟蹤標記-清掃的效果,要是堆空間出現很多碎片,就會切換回停止-復制模式。
即使編譯器(Just-In-Time JIT):可以把程序全部或部分翻譯成機器碼來提高運行速度。當需要裝載某個類時,編譯器會先找到其.class文件,然后將該類的字節碼裝入內存。此時,有兩種方案可供選擇:
讓即時編譯器編譯所有代碼:這種操作散落在整個程序的聲明周期內,累加起來耗時更長;會增加可執行代碼的長度,造成頁面調度
惰性評估:意思是即時編譯器只在必要的時候才編譯代碼,這樣,從不會被執行的代碼也許就壓根不會被JIT所編譯。
5.6 成員初始化Java盡量保證:所有變量使用前一定會初始化
局部變量:不會自動初始化,而是編譯錯誤
類成員變量:類的每個基本類型數據成員都保證會有初始值;引用類型為null
定義類成員變量的時候給它賦值——(1)
通過調用某個方法來提供初值——(2)
注意:(2)、(3)不能顛倒順序,因為存在向前引用。
缺點:這種方式所有成員有相同的屬性
public class Test { // (1) int a = 10; // (2) int i = f(); // (3) int j = g(i); int g(int n) { return n; } int f() { return 1; } public static void main(String[] args) { Test t = new Test(); } }5.7 構造器初始化
無法阻止自動初始化的進行,它發生在構造器被調用之前!
5.7.1 初始化順序遍歷定義的先后順序決定了初始化的順序。
5.7.2 靜態數據的初始化靜態數據跟非靜態數據的默認初值是一致的。
先初始化靜態對象,然后初始化非靜態對象。
靜態初始化只有在必要的時候執行,如創建第一個該類對象或調用靜態方法的時候執行。
在調用該類的靜態方法或者首次new對象(構造器其實也是靜態方法)的時候,Java解釋器查找類路徑定位到該類的.class文件。
載入該.class文件,靜態數據進行初始化,執行靜態代碼塊。
當new對象創建對象的時候,首先在堆內存中為此對象分配足夠的內存空間。
把此存儲空間清零,即所有非靜態基本數據類型置為0,對象類型置為null
執行非靜態數據初始化動作。
執行構造器。
5.7.3 顯式的靜態初始化即靜態代碼塊。
在調用該類的靜態方法或者首次new對象的時候執行,即和靜態數據初始化相同的條件,但是發生在靜態數據初始化之后。
5.7.4 非靜態實例初始化Java中也有被稱為實例初始化的語法,用來初始化每一個對象的非靜態變量。
實例初始代碼塊和成員變量的初始化順序是按照遍歷的先后順序執行的,但兩者執行都在構造方法之前。即如果(1)、(2)位置改變,輸出會變成213。
這種語法對于支持“匿名內部類”的初始化是必須的
// (1) { System.out.println(1); } // (2) int i = func(); int func() { System.out.println(2); return 2; } // (3) Test() { System.out.println(3); } public static void main(String[] args) { new Test(); // output 123 }5.8 數組初始化
int[] arr、int arr[]這兩種寫法都可以,但更推薦前者。
為了給數組創建相應的內存空間,必須初始化數組的大小;或者初始化的時候直接初始化數組的值(int[] arr = {1, 2, 3}),此時存儲空間的分配由編譯器負責。
所有數組都有一個固定成員length獲知成員數量,但不可以修改這個值。
數組坐標從0開始。
數組中的元素會自動初始化為空值。
5.8.1 可變參數列表void func(String... args) {}
可變參數列表可以接受不傳任何參數,即func()是可行的。
可變列表與自動包裝機制可以和諧相處
基本數據類型:class、空格、多個(值為數組維數)[、對應數據類型的標識
對象類型:class、空格、多個(值為數組維數)[、大寫L、對應數據類型的全路徑、;
System.out.println(new int[0].getClass()); // class [I System.out.println(new Integer[0].getClass()); // class [Ljava.lang.Integer; System.out.println(new long[0].getClass()); // class [J System.out.println(new double[0].getClass()); // class [D System.out.println(new int[0][0].getClass()); // class [[I System.out.println(new int[0][0][0].getClass()); // class [[[I System.out.println(new String[0].getClass()); // class [Ljava.lang.String; System.out.println(new String[0][0].getClass()); // class [[Ljava.lang.String;
此段代碼編譯失敗,因為編譯器發現有多個方法可以調用。
public static void main(String[] args) { func(1, "a"); func("a", "b"); } static void func(int i, Character... args) { System.out.println("first"); } static void func(Character... args) { System.out.println("second"); }5.9 枚舉類型
枚舉常量命名規范:全部大寫字母用下劃線分割
枚舉會自動創建toString()方法
會自動創建ordinal()方法,用來表示枚舉常量的聲明順序
枚舉可以在switch中使用
public class Test { public static void main(String[] args) { Color green = Color.GREEN; Color red = Color.RED; System.out.println(green + " " + green.ordinal()); System.out.println(red + " " + red.ordinal()); } } enum Color { RED, GREEN; }
在代碼中Enum禁止繼承
// final class 禁止繼承 final class Color extends Enum { public static Color[] values() { return (Color[])$VALUES.clone(); } public static Color valueOf(String name) { return (Color)Enum.valueOf(s2/Color, name); } // 私有構造方法,所以無法用new創建對象 private Color(String s, int i) { super(s, i); } public static final Color RED; public static final Color GREEN; private static final Color $VALUES[]; static { RED = new Color("RED", 0); GREEN = new Color("GREEN", 1); $VALUES = (new Color[] { RED, GREEN }); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72184.html
摘要:多態的作用是消除類型之間的耦合關系。編寫構造器準則用盡可能簡單的方法使對象進入正常狀態,如果可以的話,避免調用其他方法。 點擊進入我的博客 在面向對象的程序設計語言中,多態是繼數據抽象(封裝)和繼承之后的第三種基本特征。多態通過分離做什么和怎么做,從另一角度將接口和實現分離開來。多態的作用是消除類型之間的耦合關系。 8.1 再論向上轉型 對象既可以作為它自己的本類使用,也可以作為它的...
摘要:在類的構造方法中。對基類構造器的調用必須放到子類構造器的第一行。約定用大寫字母下劃線命名規范空白空白指被聲明為但又未給定初值的域,但可以在構造方法必須在域的定義處代碼塊或構造器中對進行賦值。 點擊進入我的博客 復用代碼是Java眾多引人注目的功能之一,但要成為極具革命性的語言,僅僅能夠復制代碼并對之加以改變是不夠的,它還必須能夠做更多的事情。 7.1 組合 組合語法 就是在當前類中產...
摘要:一引用操縱對象在的世界里,一切都被視為對象。特點創建程序時,需要知道存儲在棧內所有數據的確切生命周期,以便上下移動堆棧指針。因為,指向同一塊內存空間除了通過對象引用靜態變量,我們還可以通過類直接引用靜態變量 一、引用操縱對象 在Java的世界里,一切都被視為對象。操縱的標識符實際上是對象的引用, 例如:遙控器與電視的關系。 可以在沒有對象關聯的情況下,擁有一個引用。沒有電視機,也可以擁...
摘要:一旦異常被拋出,就表明錯誤已無法挽回,也不能回來繼續執行。這種在編譯時被強制檢查的異常稱為被檢查的異常。通過獲取原始異常。構造器對于在構造階段可能會拋出異常,并要求清理的類,最安全的做法是使用嵌套的子句。 點擊進入我的博客 Java異常處理的目的在于通過使用少于目前數量的代碼來簡化大型、可靠的程序的生成,并且通過這種方式可以使你更自信:你的應用中沒有未處理的錯誤。 12.1 概念 異...
摘要:注本文首發于公眾號,可長按或掃描下面的小心心來訂閱關于構造器與初始化無參構造器默認構造器自己未寫編譯器幫忙自動創建的若自行定義了構造器無論參數有否,編譯器便停止默認創建動作類里的對象引用默認初始化為,基本類型初始化為構造器也是類的靜態方法四 showImg(https://segmentfault.com/img/remote/1460000015723687); 注: 本文首發于 ...
閱讀 2337·2021-11-24 11:16
閱讀 2022·2021-09-30 09:47
閱讀 1997·2021-09-10 10:51
閱讀 1316·2019-08-30 14:08
閱讀 3133·2019-08-30 13:47
閱讀 1521·2019-08-30 13:02
閱讀 3227·2019-08-29 12:29
閱讀 3179·2019-08-26 17:05