面向對象
-
面向過程和面向對象
-
面向對象的三大基本特征:封裝、繼承、多態
- 封裝:隱藏內部細節
-
繼承:復用現有代碼
-
多態:改寫對象行為
-
JAVA為什么是面向對象的,為什么還用int等基礎類型
- 面向對象的特征:封裝,繼承,多態。JAVA語言符合這些特征。
- 因為在每個封裝體里,基本還是面向過程的代碼思想,因此還是需要這些基礎數據類型。
-
面向對象軟件開發的優點有哪些?
- 更符合人們思考習慣的思想,更多體現的是指揮者,指揮對象做事情,與面向過程的執行者區別;
- 可以將復雜問題簡單化,保證了較高的開發效率,保證了程序的魯棒性和可維護性。
- 易維護、易復用、易擴展,由於面向對象有封裝、繼承、多態性的特性,可以設計出低耦合的系統。
- 代碼開發模塊化,更易維護和修改。
- 增強代碼的可靠性和靈活性。
- 增加代碼的可理解性。
-
1、封裝
- 封裝:隱藏對象的屬性和實現細節,僅對外公開接口,控制在程序中屬性的讀和修改的訪問級別,將抽象得到的數據和行為(或功能)相結合,形成一個有機的整體,也就是將數據與操作數據的源代碼進行有機的結合,形成“類”,其中數據和函數都是類的成員。
- 封裝的目的:是增強安全性和簡化編程,使用者不必了解具體的實現細節,而只是要通過外部接口,以特定的訪問權限來使用類的成員。
- 面向對象就是使用程序處理事情時以對象為中心去分析;與面向過程不同,面向過程關心處理的邏輯、流程等問題,而不關心事件主體。而面向對象即面向主體,所以我們在解決問題時應該先進行對象的封裝(對象是封裝類的實例,比如張三是人,人是一個封裝類,張三只是對象中的一個實例、一個對象)。比如我們日常生活中的小兔子、小綿羊都可以封裝為一個類。
-
封裝的定義和好處有哪些?
- 一是提高了數據的安全性。用private把類的細節與外界隔離起來,從而實現數據項和方法的隱藏,而要訪問這些數據項和方法唯一的途徑就是通過類本身,類才有資格調用它所擁有的資源(方法,數據項屬性等等)。
- 二是提高了代碼的可用性和可維護性。通過隱藏隔離,只允許外部對類做有限的訪問,開發者可以自由的改變類的內部實現,而無需修改使用該類的那些程序。只要那些在類外部就能被調用的方法保持其外部特征不變,內部代碼就可以自由改變,各取所需,利於分工。
- 三就是提高了代碼的重用性,封裝成工具類以后能夠減少很多繁瑣的步驟。
-
抽象的定義?抽象和封裝的不同點?
- 抽象是把想法從具體的實例中分離出來的步驟,因此,要根據他們的功能而不是實現細節來創建類。Java支持創建只暴漏接口而不包含方法實現的抽象的類。這種抽象技術的主要目的是把類的行為和實現細節分離開。
- 抽象和封裝是互補的概念。一方面,抽象關注對象的行為。另一方面,封裝關注對象行為的細節。一般是通過隱藏對象內部狀態信息做到封裝,因此,封裝可以看成是用來提供抽象的一種策略。
-
2、繼承
- 繼承是指:保持已有類的特性而構造新類的過程。繼承后,子類能夠利用父類中定義的變量和方法,就像它們屬於子類本身一樣。
- 繼承是面向對象的基本特征之一,支持單繼承,不支持多繼承;繼承機制允許創建分等級層次的類。
- 繼承就是子類繼承父類的特征和行為,使得子類對象(實例)具有父類的實例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。
- 繼承之間是子父類的關系。繼承機制可以很好的描述一個類的生態,也提高了代碼復用率,在Java中的Object類是所有類的超類,常稱作上帝類。
- 作用:
- 繼承提高了代碼復用性,提供了多態的前提。
- 繼承是指:保持已有類的特性而構造新類的過程。繼承后,子類能夠利用父類中定義的變量和方法,就像它們屬於子類本身一樣。
-
單繼承和多繼承
- 只支持單繼承(一個類只能有一個父類),不支持多繼承(多繼承用接口實現)。
- 單繼承:java類是單繼承的,一個類只允許有一個父類。
public class A extends B{ } //繼承單個父類
- 多繼承:java接口多繼承的,一個類允許繼承多個接口。
public class A extends B implements C{ } //同時繼承父類和接口
public class A implements B,C{ } //繼承多個接口
- 支持多重繼承
class A {}
class B extends A{}
class C extends B{}
-
3、多態
- 某一類事物的多種表現形態。
- 具體來說,在父類中定義的屬性和方法被子類繼承之后,這使得同一個屬性或方法在父類及其各個子類中具有不同的含義。
- 子類對象的多態性使用前提:
- 有類的繼承或實現關系;
- 由子類對父類方法的重寫(覆蓋)
- 多態:同一個行為具有多個不同表現形式或形態的能力。
- 是指一個類實例(對象)的相同方法在不同情形有不同表現形式。
- 多態機制使具有不同內部結構的對象可以共享相同的外部接口。這意味着,雖然針對不同對象的具體操作不同,但通過一個公共的類,它們(那些操作)可以通過相同的方式予以調用。
- 多態的優點:
- 消除類型之間的耦合關系
- 可替換性
- 可擴充性
- 接口性
- 靈活性
- 簡化性
- 多態存在的三個必要條件:
- 繼承
- 重寫(子類繼承父類后對父類方法進行重新定義)
- 父類引用指向子類對象
- 多態的成員變量和成員函數:
- 成員變量和靜態變量:編譯和運行都看左邊(父類)
- 成員函數:編譯看左邊(父類),運行看右邊(子類)
- 寫一個多態:
class Fu{ int a = 3; void show() { System.out.println("fu show."); } } class Zi extends Fu{ int a = 5; void show() { System.out.println("zi show."); } } public class DuotaiDemo { public static void main(String[] args) { Fu fu = new Fu(); fu.show(); Fu f = new Zi(); System.out.println("f.a = "+f.a); f.show(); Zi z = new Zi(); System.out.println("z.a = "+z.a); z.show(); } } //結果 fu show. f.a = 3 zi show. z.a = 5 zi show.
java基礎
-
Integer和int的區別
- int則是java的一種基本數據類型,Integer是int的包裝類
- int的默認值是0,Integer的默認值是null
- Integer變量必須實例化后才能使用,而int變量不需要
- Integer實際是對象的引用,當new一個Integer時,實際上是生成一個指針指向此對象;而int則是直接存儲數據值
-
java常見的幾種運行時異常RuntimeException
- NullPointerException - 空指針引用異常
- ClassCastException - 類型強制轉換異常。
- IllegalArgumentException - 傳遞非法參數異常。
- ArithmeticException - 算術運算異常
- ArrayStoreException - 向數組中存放與聲明類型不兼容對象異常
- IndexOutOfBoundsException - 下標越界異常
- NegativeArraySizeException - 創建一個大小為負數的數組錯誤異常
- NumberFormatException - 數字格式異常
- SecurityException - 安全異常
- UnsupportedOperationException - 不支持的操作異常
-
Java 異常中的getMessage()和toString()方法的異同
- e.toString(): 獲得異常種類和錯誤信息
- e.getMessage():獲得錯誤信息
- e.printStackTrace():在控制台打印出異常種類,錯誤信息和出錯位置等
-
java中的length和length(),size
-
重寫與重載
- 重載:
- 發生在同一個類里面,兩個或者是多個方法的方法名相同但是參數不同的情況。
- 注:參數順序不同也參數列表不同的情況。
- 重載與返回值類型和修飾符無關。
- 重寫或覆蓋:
- 是指子類重新定義了父類的方法;
- 重寫必須有相同的方法名,參數列表和返回類型。
- 子類函數的訪問修飾權限不能少於父類的。
- 重載:
-
棧和堆的區別
- 1.棧內存存儲的是局部變量,基本類型的變量表示的是數據本身;而堆內存存儲的是實體,每個實體對象都有地址值和默認初始化值;
- 2.棧內存的讀取和更新速度要快於堆內存,因為局部變量的生命周期很短;
- 3.棧內存使用一級緩存,存放的變量生命周期一旦結束就會被釋放;而堆內存使用二級緩存,存放的實體會被垃圾回收機制不定時的回收。
-
== 和equals
- ==:
- 基本數據類型比較的是值
- 引用類型比較的是地址值
- equals(Object o):
- 不能比較基本數據類型,基本數據類型不是類類型
- 比較引用類型時(該方法繼承自Object,在object中比較的是地址值)等同於”==”;
- 如果自己所寫的類中已經重寫了equals()方法,那么就按照用戶自定義的方式來比較兩個對象是否相等,如果沒有重寫過equals()方法,那么會調用父類(Object)中的equals()方法進行比較,也就是比較地址值。
- 注意:equals(Object o)方法只能是一個對象來調用,然后參數也應傳一個對象。
- ==:
public boolean equals (Object x){ return this == x; }
-
什么時候用==,什么時候用equals()呢?
- 如果是基本數據類型:用==比較
- 如果是引用類型:
- 想按照自己的方式去比較,就要重寫這個類中的equals()方法;如果沒有重寫,那么equals()和==比較的效果是一樣的,都是比較引用的地址值
- 如果是比較字符串:
- 直接用equals就可以了,因為String類里面已經重寫了equals()方法,比較的是字符串的內容,而不是引用的地址值了。
=============================
-
java容器
- List、Set、Map、Queue
- List、Set、Map、Queue
-
List接口
- List是有序的 collection(也稱為序列)。用戶插入的順序或者指定的位置就是元素插入的位置。用戶可以根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。
- 與Set不同,List允許插入重復的值。
- List的子類:Vector、ArrayList、LinkedList
- 1.1) ArrayList (類)
- ArrayLis是基於數組實現的List類,它封裝了一個動態的、增長的、允許再分配的Object[ ]數組.它允許對元素進行快速隨機訪問
當從ArrayList的中間位置插入或者刪除元素時,需要對數組進行復制、移動、代價比較高。因此,它適合隨機查找和遍歷,不適合插入和刪除。
- ArrayLis是基於數組實現的List類,它封裝了一個動態的、增長的、允許再分配的Object[ ]數組.它允許對元素進行快速隨機訪問
- 1.2)Vector (類)
- Vector與ArrayList一樣,也是通過數組實現的,不同的是它支持線程的同步,但實現同步需要很高的花費,因此,訪問它比訪問ArrayList慢。所以現在已經不太常用了。
- 1.2.1)Stack (類):Stack是Vector提供的一個子類,用於模擬"棧"這種數據結構(LIFO后進先出)
- 1.3)LinkedList (類)
- LinkedList是用鏈表結構存儲數據的,很適合數據的動態插入和刪除,隨機訪問和遍歷速度比較慢。另外,它還實現了Deque接口,專門用於操作表頭和表尾元素,可以當作堆棧、隊列和雙向隊列使用。
- 1.1) ArrayList (類)
-
Set接口
- Set是無序、不可重復的。同時,如果有多個null,則不滿足單一性了,所以Set只能有一個null。
- Set判斷兩個對象相同不是使用"=="運算符,而是根據equals方法。
- Set的子類:Set最流行的實現類有HashSet、TreeSet、LinkedHashSet(從HashSet繼承而來)。
- 1.1) HashSet (類)
- HashSet是Set接口的典型實現,HashSet使用HASH算法來存儲集合中的元素,因此具有良好的存取和查找性能。
- HashSet集合判斷兩個元素相等的標准是兩個對象通過equals()方法比較相等,並且兩個對象的hashCode()方法的返回值相等。
- 1.1.1)LinkedHashSet(類)
- LinkedHashSet集合也是根據元素的hashCode值來決定元素的存儲位置,但和HashSet不同的是,它同時使用鏈表維護元素的次序,這樣使得元素看起來是以插入的順序保存的。
- LinkedHashSet將會按元素的添加順序來訪問集合里的元素。
- LinkedHashSet需要維護元素的插入順序,因此性能略低於HashSet的性能,但在迭代訪問Set里的全部元素時(遍歷)將有很好的性能(鏈表很適合進行遍歷)
- 1.2) SortedSet (接口):
- 主要用於排序操作,實現了此接口的子類都屬於排序的子類
- 1.2.1)TreeSet(類):SortedSet接口的實現類,TreeSet可以確保集合元素處於排序狀態
- 1.3) EnumSet (類)
- EnumSet是一個專門為枚舉類設計的集合類,EnumSet中所有元素都必須是指定枚舉類型的枚舉值,該枚舉類型在創建EnumSet時顯式、或隱式地指定。EnumSet的集合元素也是有序的,
- 1.1) HashSet (類)
-
Queue 接口
- 此接口用於模擬“隊列”數據結構(FIFO)。新插入的元素放在隊尾,隊頭存放着保存時間最長的元素。
- Queue的子類、子接口
- 1.1) PriorityQueue—— 優先隊列(類)
- 其實它並沒有按照插入的順序來存放元素,而是按照隊列中某個屬性的大小來排列的。故而叫優先隊列。
- 1.2) Deque——雙端隊列(接口)
- ArrayDeque(類):基於數組的雙端隊列,類似於ArrayList有一個Object[] 數組。
- LinkedList (類)(上文已有,略)
- 1.1) PriorityQueue—— 優先隊列(類)
-
Map 接口
- Map不是collection的子接口或者實現類。Map是一個接口。
- Map用於保存具有“映射關系”的數據。每個Entry都持有鍵-值兩個對象。其中,Value可能重復,但是Key不允許重復(和Set類似)。
- Map可以有多個Value為null,但是只能有一個Key為null。
- Map的子類、子接口
- 1) HashMap (類)
- 和HashSet集合不能保證元素的順序一樣,HashMap也不能保證key-value對的順序。
- 並且類似於HashSet判斷兩個key是否相等的標准一樣: 兩個key通過equals()方法比較返回true、 同時兩個key的hashCode值也必須相等
- 1.1) LinkedHashMap (類)
- LinkedHashMap也使用雙向鏈表來維護key-value對的次序,該鏈表負責維護Map的迭代順序,與key-value對的插入順序一致(注意和TreeMap對所有的key-value進行排序區分)。
- 2) HashTable (類):是一個古老的Map實現類。
- 2.1) Properties(類)
- Properties對象在處理屬性文件時特別方便(windows平台的.ini文件)。Properties類可以把Map對象和屬性文件關聯,從而把Map對象的key - value對寫入到屬性文件中,也可把屬性文件中的“屬性名-屬性值”加載進Map對象中。
- 2.1) Properties(類)
- 3) SortedMap(接口)
- 如同Set->SortedSet->TreeSet一樣,Map也有Map->SortedMap->TreeMap的繼承關系。
- 3.1) TreeMap(類)
- TreeMap是一個紅黑樹結構,每個鍵值對都作為紅黑樹的一個節點。TreeMap存儲鍵值對時,需要根據key對節點進行排序,TreeMap可以保證所有的key-value對處於有序狀態。 同時,TreeMap也有兩種排序方式:自然排序、定制排序(類似於上面List的重寫CompareTo()方法)。
- 1) HashMap (類)
-
數組與鏈表
- 數據中數組的內存是順序存儲的,而鏈表是隨機存取的。
- 數組隨機訪問效率很高,但插入刪除操作的效率比較低。
- 鏈表在插入刪除操作上相對數組有很高的效率,而如果訪問鏈表中的某個元素,那就要從表頭逐個遍歷,直到找到所需要的元素為止,所以鏈表的隨機訪問效率比數組低。
- 鏈表不存在越界問題,數組有越界問題。
- 數組節省空間但是長度固定。鏈表雖然變長,但是占了更多的存儲空間。
- 靜態)數組從棧中分配內存空間,對於程序員方便快速,但是自由度小。鏈表從堆中分配空間,自由度大,但申請管理比較麻煩。
-
arraylist和linkedlist的區別
- ArrayList和LinkedList都實現了List接口,他們有以下的不同點:
- ArrayList是基於索引的數據接口,它的底層是數組。它可以以O(1)時間復雜度對元素進行隨機訪問。與此對應,LinkedList是以元素列表的形式存儲它的數據,每一個元素都和它的前一個和后一個元素鏈接在一起,在這種情況下,查找某個元素的時間復雜度是O(n)。
- 相對於ArrayList,LinkedList的插入,添加,刪除操作速度更快,因為當元素被添加到集合任意位置的時候,不需要像數組那樣重新計算大小或者是更新索引。
- LinkedList比ArrayList更占內存,因為LinkedList為每一個節點存儲了兩個引用,一個指向前一個元素,一個指向下一個元素。
-
HashMap和Hashtable的區別
- 底層都是數組+鏈表實現
- 1、繼承的父類不同: Hashtable繼承自Dictionary類,而HashMap繼承自AbstractMap類。但二者都實現了Map接口。
- 2、線程安全性不同: HashMap是線程不安全的,可用於單線程;Hashtable是線程安全的,可用於多線程。
- 3、是否提供contains方法
- HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因為contains方法容易讓人引起誤解。
- Hashtable則保留了contains,containsValue和containsKey三個方法,其中contains和containsValue功能相同。
- 4、key和value是否允許null值:其中key和value都是對象,並且不能包含重復key,但可以包含重復的value。
- Hashtable中,key和value都不允許出現null值。但是如果在Hashtable中有類似put(null,null)的操作,編譯同樣可以通過,因為key和value都是Object類型,但運行時會拋出NullPointerException異常,這是JDK的規范規定的。
- HashMap中,null可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值為null。當get()方法返回null值時,可能是 HashMap中沒有該鍵,也可能使該鍵所對應的值為null。因此,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵, 而應該用containsKey()方法來判斷。
- 5、兩個遍歷方式的內部實現上不同:
- Hashtable、HashMap都使用了 Iterator。而由於歷史原因,Hashtable還使用了Enumeration的方式 。
- 6、hash值不同:
- 哈希值的使用不同,HashTable直接使用對象的hashCode。而HashMap重新計算hash值。
- 7、內部實現使用的數組初始化和擴容方式不同:
- HashTable在不指定容量的情況下的默認容量為11,而HashMap為16,Hashtable不要求底層數組的容量一定要為2的整數次冪,而HashMap則要求一定為2的整數次冪。
- Hashtable擴容時,將容量變為原來的2倍加1,而HashMap擴容時,將容量變為原來的2倍。
-
HashMap為什么是線程不安全的?
- HashMap是線程不安全的,我們應該使用ConcurrentHashMap
- Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情況下是非Synchronize的
- HashMap的線程不安全主要體現在下面兩個方面:
- 1.在JDK1.7中,當並發執行擴容操作時會造成環形鏈和數據丟失的情況。
- 2.在JDK1.8中,在並發執行put操作時會發生數據覆蓋的情況。
- 注:ConcurrentHashMap使用了分段鎖技術來提高了並發度,不在同一段的數據互相不影響,多個線程對多個不同的段的操作是不會相互影響的。每個段使用一把鎖。所以在需要線程安全的業務場景下,推薦使用ConcurrentHashMap,而HashTable不建議在新的代碼中使用,如果需要線程安全,則使用ConcurrentHashMap,否則使用HashMap就足夠了。
-
哈希表:哈希函數構造;哈希表解決地址沖突的方法
- 散列函數構造方法:
- 1.直接定址法:H(key) = a*key + b
- 2.除留余數法:H(key) = key % p(p為不大於散列表表長,但最接近或等於表長的質數p)
- 3.數字分析法:選取r進制數數碼分布較為均勻的若干位作為散列地址
- 4.平方取中法:取關鍵字的平方值的中間幾位作為散列地址
- 5.折疊法:將關鍵字分割成位數相同的幾部分,然后取這幾部份的疊加和作為散列地址
- 處理沖突的方法:
- 1.開放定址法(閉哈希表):在沖突的哈希地址的基礎上進行處理,得到新的地址值。Hi = (H(key)+di) % m(m表示散列表表長,di為增量序列)
- 1)線性探測法:dii=1,2,3,…,m-1
- 2)二次探測法:di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
- 沖突發生時,以原哈希地址為中心,在表的左右進行跳躍式探測,比較靈活。
- 3)偽隨機數法:di=偽隨機數序列。
- 具體實現時,應建立一個偽隨機數發生器,(如i=(i+p) % m),並給定一個隨機數做起點。
- 線性探測再散列的優點是:只要哈希表不滿,就一定能找到一個不沖突的哈希地址,而二次探測再散列和偽隨機探測再散列則不一定。
- 注:在開放定址的情形下,不能隨便物理刪除表中已有元素,若刪除元素將會截斷其他具有相同散列地址的元素的查找地址。若想刪除一個元素,給它做一個刪除標記,進行邏輯刪除。
- 2.鏈地址法、拉鏈法(開哈希表)
- 將所有哈希地址為i的元素構成一個稱為同義詞鏈的單鏈表,並將單鏈表的頭指針存在哈希表的第i個單元中,因而查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於經常進行插入和刪除的情況。
- 3.再哈希法:同時構造多個不同的哈希函數,發生沖突時,使用其他哈希函數求值。這種方法不易產生聚集,但增加了計算時間。
- 4.建立公共溢出區:將哈希表分為基本表和溢出表兩部分,凡是和基本表發生沖突的元素,一律填入溢出表
- 1.開放定址法(閉哈希表):在沖突的哈希地址的基礎上進行處理,得到新的地址值。Hi = (H(key)+di) % m(m表示散列表表長,di為增量序列)
- 散列函數構造方法:
=============================
-
String中兩種初始化方式比較:直接賦值和構造函數初始化
- 通過直接賦值創建對象是在方法區的常量池:
String s1 = "abc";
- 首先在常量池中查找"abc",如果沒有則在常量池創建該對象
- 在棧中創建s1的引用,將s1直接指向對象"abc"
- 因此在這里"abc"是常量池中的對象,如果聲明另一個String類型的對象引用,並將它指向對象"abc",則這兩個引用指向的是同一個常量池中的對象。
- 直接賦值:存儲在常量池。
- 多次直接賦值:將存儲在常量池中的"hello"的地址傳遞給s變量,且常量池已有字符串公用。
- 通過構造方法創建字符串對象是在堆內存:
String s = new String(“abc”);
- 凡是經過 new 創建出來的對象,都會在堆內存中分配新的空間,創建新的對象,所以s是String類新創建的對象;
- 每new一次,都會創建新的對象,即使對象值相同。
- 通過直接賦值創建對象是在方法區的常量池:
-
String、StringBuilder與StringBuffer
- 字符修改上的區別
- String:字符串常量,不可變字符串,每次對String的操作都可能生成新的String對象,效率低下,而且大量浪費有限的內存空間。
- 注:對string重新賦值,如果字符串常量池不存在這個新的賦值對象,就會創造新的對象,如果存在,就不會創建。
- StringBuffer:字符串變量,可變字符串、效率低、線程安全;
- StringBuilder:字符串變量,可變字符序列、效率高、線程不安全;
- 注:StringBuffer 和 StringBuilder 類的對象能夠被多次的修改,並且不產生新的未使用對象。
- String:字符串常量,不可變字符串,每次對String的操作都可能生成新的String對象,效率低下,而且大量浪費有限的內存空間。
- 三者在執行速度方面的比較:
- StringBuilder > StringBuffer > String
- 繼承結構不同
- String繼承自CharSequence接口,StringBuilder和StringBuffer繼承自Appendable接口。
- 小結:
- (1)如果要操作少量的數據用 String;
- (2)多線程操作字符串緩沖區下操作大量數據 StringBuffer;
- (3)單線程操作字符串緩沖區下操作大量數據 StringBuilder(推薦使用)。
- 字符修改上的區別
-
辨析:replace,replaceAll,replaceFirst
- replace和replaceAll:
- 相同點:替換所有匹配的字符串(都是替換所有)
- 不同點:replace支持字符替換,字符串替換; replaceAll是正則表達式替換
- replaceFirst: 同replaceAll一樣,也是基於規則表達式的替換
- 不同之處是:只替換第一次出現的字符串
- replace和replaceAll:
-
public、protected、缺省、private
- public修飾的成員變量和函數可以被類、子類、同一個包中的類以及任意其他類訪問。
- protected修飾的成員變量和函數能被類本身、子類及同一個包中的類訪問。
- 缺省情況(不寫)下,屬於一種包訪問,即能被類本身以及同一個包中的類訪問。
- private修飾的成員變量和函數只能在類本身和內部類中被訪問。
-
判斷一個類是否“無用”,則需同時滿足三個條件:
- 該類所有的實例都已經被回收,也就是Java堆中不存在該類的任何實例;
- 加載該類的ClassLoader已經被回收
- 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法
-
垃圾回收算法:復制算法、標記-清除算法、標記-整理算法、分代收集算法
- 如何確定某個對象是垃圾:引用計數法。
- 在java中是通過引用來和對象進行關聯的,也就是說如果要操作對象,必須通過引用來進行。那么很顯然一個簡單的辦法就是通過引用計數來判斷一個對象是否可以被回收。不失一般性,如果一個對象沒有任何引用與之關聯,則說明該對象基本不太可能在其他地方被使用到,那么這個對象就成為可被回收的對象了。這種方式成為引用計數法。
- 這種方式的特點是實現簡單,而且效率較高,但是它無法解決循環引用的問題,因此在Java中並沒有采用這種方式(Python采用的是引用計數法)。
- 垃圾回收算法:復制算法、標記-清除算法、標記-整理算法、分代收集算法
- 標記-清除算法:
- 分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所占用的空間。
- 實現起來比較容易,但是有一個比較嚴重的問題就是容易產生內存碎片,碎片太多可能會導致后續過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發新的一次垃圾收集動作。
- 復制算法:
- 將可用內存按容量划分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象復制到另外一塊上面,然后再把已使用的內存空間一次清理掉,這樣一來就不容易出現內存碎片的問題。
- 實現簡單,運行高效且不容易產生內存碎片,但是卻對內存空間的使用做出了高昂的代價,因為能夠使用的內存縮減到原來的一半。
- Copying算法的效率跟存活對象的數目多少有很大的關系,如果存活對象很多,那么Copying算法的效率將會大大降低。
- 標記-整理算法:
- 該算法標記階段和Mark-Sweep一樣,但是在完成標記之后,它不是直接清理可回收對象,而是將存活對象都向一端移動,然后清理掉端邊界以外的內存。
- 分代收集算法:
- 是目前大部分JVM的垃圾收集器采用的算法。
- 它的核心思想是根據對象存活的生命周期將內存划分為若干個不同的區域。一般情況下將堆區划分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點是每次垃圾收集時只有少量對象需要被回收,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那么就可以根據不同代的特點采取最適合的收集算法。
- 新生代:Copying算法,因為新生代中每次垃圾回收都要回收大部分對象,也就是說需要復制的操作次數較少,但是實際中並不是按照1:1的比例來划分新生代的空間的,一般來說是將新生代划分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象復制到另一塊Survivor空間中,然后清理掉Eden和剛才使用過的Survivor空間。
- 老年代:Mark-Compact算法。根據老年代的特點:每次回收都只回收少量對象。
- 在堆區之外還有一個代就是永久代(Permanet Generation):存儲class類、常量、方法描述等。對永久代的回收主要回收兩部分內容:廢棄常量和無用的類。
- 標記-清除算法:
- 典型的垃圾收集器:
- 垃圾收集算法是 內存回收的理論基礎,而垃圾收集器就是內存回收的具體實現。
- 下面介紹一下HotSpot(JDK 7)虛擬機提供的幾種垃圾收集器,用戶可以根據自己的需求組合出各個年代使用的收集器。
- 小結:Serial/Serial Old,ParNew,Parallel Scavenge,Parallel Old,CMS,G1
- 如何確定某個對象是垃圾:引用計數法。
-
內存溢出和內存泄露
- 內存泄露:
- 是指分配出去的內存沒有被回收回來,由於失去了對該內存區域的控制,因而造成了資源的浪費。
- Java中一般不會產生內存泄露,因為有垃圾回收器自動回收垃圾,但這也不絕對,當我們new了一個對象,並保存了其引用,但是后面一直沒用它,而垃圾回收器又不會去回收它,這便會造成內存泄露。
- 內存溢出:
- 是指程序所需要的內存超出了系統所能分配的內存(包括動態擴展)的上限。
- 內存泄露:
-
java會不會內存泄漏
- 內存泄漏:一個不再被程序使用的對象或變量還在內存中占用存儲空間。
- Java的垃圾回收機制可以回收這類不再使用的對象。
- 但是Java還存在內存泄漏的問題。
- 原因:
- 靜態集合類,如哈希表:因為是靜態的,生命周期與程序一致,在程序結束前不能釋放,造成內存泄漏;
- 變量不合理的作用域:如果一個變量定義的作用范圍大於使用范圍,可能造成內存泄漏。
- 其他:建立各種鏈接后,監聽器,單例模式中靜態存儲單例對象等等。
-
抽象類
- 抽象類和抽象方法必須用abstract修飾;
- 抽象方法:只有方法聲明,沒有方法體,定義在抽象類中;
- 格式:
修飾符 abstract 返回值類型 函數名(參數列表){}
- 抽象類不可以被實例化,即不可以用new創建對象。
- 抽象類通過其子類實例化,而子類需要覆蓋掉抽象類中所有抽象方法才可以創建對象,否則該子類也是抽象類。
-
接口
- 抽象類和接口:
- 抽象類中可以定義抽象方法和非抽象方法;
- 當抽象類中的方法都是抽象的時,可以定義為接口。
- 接口最重要的體現:解決單繼承的弊端。
- 多繼承父類中有方法主體,導致調用時的不確定性;
- 接口中沒有方法體,由子類來定義。
- 接口的特點:
- 接口不可以創建對象;
- 子類必須覆蓋掉接口中的所有的抽象方法后,子類才可以實例化,否則子類是一個抽象類。
- 固定修飾符:
- 成員變量(其實是常量):public static final
- 成員方法:public abstract
- 代碼體現:
- 抽象類和接口:
interface A{ void show();} interface B{ void show();} class C implements A,B{public void show(){...}} C c = new C(); c.show();
-
抽象類和接口的區別
- 共性:都是不斷抽取出來的抽象的概念。
- 不同1:
- 抽象類體現的是繼承關系,一個類只能單繼承;
- 接口體現的是實現關系,一個類可以多實現。
- 不同2:
- 抽象類是繼承,是is a的關系;
- 接口是實現,是like a的關系。
- 不同3:
- 抽象類中可以定義非抽象方法,供子類直接使用;
- 接口的方法都是抽象,接口中的成員都有固定修飾符。
-
多線程的實現方式有哪些 extend Thread、implement runnable、implement callable
- 繼承Thread類。
- Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。讓自己的類直接extends Thread,並在此類中復寫run()方法。
- 啟動線程的方法就是通過Thread類的start()實例方法,start()方法將啟動一個新線程,並執行其中的run()方法。
- 代碼實現:
- 繼承Thread類。
public class MyThread extends Thread { //繼承Thread類 public void run() { //復寫run()方法 System.out.println("MyThread.run()"); } } MyThread myThread1 = new MyThread(); //創建一個myThread實例 MyThread myThread2 = new MyThread(); myThread1.start(); //啟動線程 myThread2.start();
- 實現Runnable接口。
- 如果自己的類已經extends另一個類了,就無法再直接extends Thread,此時,可以通過讓它來實現Runnable接口來創建多線程。
- 代碼實現:
public class MyThread extends OtherClass implements Runnable { //實現Runnable接口 public void run() { //復寫run()方法 System.out.println("MyThread.run()"); } } MyThread myThread = new MyThread(); //創建一個myThread實例 Thread thread = new Thread(myThread); //將自己的myThread傳入Thread實例中 thread.start(); //啟動線程
-
實現Callable接口,重寫call函數。
- 繼承Thread類實現多線程,但重寫run()方法時沒有返回值也不能拋出異常,使用Callable接口就可以解決這個問題。
-
Callable接口和Runnable接口的不同之處:
- Callable規定的方法是call(),而Runnable是run();
- call()方法可以拋出異常,但是run()方法不行;
- Callable對象執行后可以有返回值,運行Callable任務可以得到一個Future對象,通過Future對象可以了解任務執行情況,可以取消任務的執行,而Runnable不可有返回值。
-
兩個線程交替打印hello
//只寫關鍵的同步部分 public class Test { public static void main(String[] args) { final PrintAB print = new PrintAB(); new Thread(new Runnable() { public void run(){ for(int i=0;i<5;i++) { print.printA(); } } }).start(); new Thread(new Runnable() { public void run() { for(int i=0;i<5;i++) { print.printB(); } } }).start(); } } class PrintAB{ private boolean flag = true; public synchronized void printA () { while(!flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print("你好+A"); flag = false; this.notify(); } public synchronized void printB () { while(flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print("你好+B"); flag = true; this.notify(); } }
-
序列化和反序列化
- 定義:
- Java序列化就是指把Java對象轉換為字節序列的過程。
- Java反序列化就是指把字節序列恢復為Java對象的過程。
- 作用:
- 序列化:在傳遞和保存對象時,保證對象的完整性和可傳遞性。對象轉換為有序字節流,以便在網絡上傳輸或者保存在本地文件中。
- 反序列化:根據字節流中保存的對象狀態及描述信息,通過反序列化重建對象。
- 總結:核心作用就是對象狀態的保存和重建。
- 定義:
設計模式
-
單例模式
- 什么情況下會用到:
- 假如有很多地方都需要使用配置文件的內容,也就是說,很多地方都需要創建 AppConfig對象的實例,這就導致系統中存在多個AppConfig的實例對象,在配置文件內容很多的情況下會嚴重浪費內存資源。類似AppConfig這樣的類,我們希望在程序運行期間只存在一個實例對象。
- 優點:速度快、在使用時不需要創建、直接使用即可。
- 缺點:可能存在內存浪費
- 什么情況下會用到:
-
單例模式代碼:
//單例模式 //餓漢式:直接new對象,開發多見 public class Test1 { //私有構造函數 private Test1(){} //自行創建私有、靜態的對象 private static Test1 uniqueInstance1 = new Test1(); //對外提供公共、靜態的訪問接口 public static Test1 getInstance(){ return uniqueInstance1; } //懶漢式 public class Test2 { //私有構造函數 private Test2(){} //自行創建私有、靜態的空對象 private static Test2 uniqueInstance2 = null; //對外提供公共、靜態的訪問接口,並創建對象 public static Test2 getInstance() { if(uniqueInstance2==null) uniqueInstance2 = new Test2(); return uniqueInstance2; } } //懶漢式之並發訪問 class Single { private Single(){} private static Single s = null; /* 並發訪問有安全隱患,所以加入同步機制解決安全問題 但是,同步的出現卻降低了效率。 提高效率:減少判斷鎖的次數,可以通過雙重判斷的方式。 */ public static void getInstance() { if(s==null) { synchronized(Single.class){ if(s==null) s = new Single(); return s; } } } }