面試複習筆記一(java基礎)

zhumeilu發表於2017-12-14

###==和equals之間的區別 ==在基礎資料型別中比較的是其數值,比如

int i=1;
int j=2;
i==j;
複製程式碼

而==在物件之間比較的就是其物件的記憶體地址,也就是比較是否是同一個物件. equals是Object類裡面的方法,在Object類裡面的預設實現其實就是使用==,然而有一些比較特殊的類,如String在繼承Object類的時候將equals方法重寫, 如:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                        return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
複製程式碼

先用==比較,如果不是同一個物件,然後在比較值.

###switch可以作用在哪些型別上 switch可以作用在int上,由於byte,short,char可以隱式轉換成int,所以也可以被switch使用,java7中,String也可以被switch使用

###&和&&的區別 &&在比較的時候,如果第一個為false,則不會比較後面的,而&則會比較全部的表示式.&可以做位運算.

###final關鍵字 final關鍵字修飾的物件,物件引用不可改變,物件內容可以改變,也就是一旦該變數指向了該物件,那麼就不能再給他賦值其他的物件了,但是物件裡面的屬性值可以修改.

###String和StringBuffer的區別 StringBuffer比String更能節省系統的資源,String內部是一個final修飾的char[],StringBuffer內部是一個char[],每次新增字串的時候都是把字串放入char[]中,中途會產生char[]陣列的擴容和陣列的複製.在進行字串拼接的時候不會產生新的String, 而使用String+String拼接,會產生一個新的String物件. 系統有一個常量池,每次通過String a = "123";這種方式產生的字串時,系統都會從常量池中取,如果沒有在建立一個放入常量池中,如果有,則直接拿來用. 而如果String a = new String("123");這種方式建立出來的String物件,則不會去常量池中取,而是作為一個普通的物件放入堆中.

###什麼情況下用+運算子進行字串連線比呼叫StringBuffer/StringBuilder物件的append方法連線字串效能更好 String a = "hello"+"world"; StringBulider b = new StringBulider("hello").append("world"); 因為第一行語句,在編譯的時候就自動整合成"helloworld"了.這是編譯器的優化. ###StringBuffer和StringBulider的區別 StringBuffer和StringBulider功能類似,都是優化String的拼接.但是StringBulider是執行緒不安全的,而StringBuffer是執行緒安全的.StringBuffer的方法都加了synchronized修飾. 所以這就導致了StringBuffer的效率沒有StringBulider高. 如果一個字串是在方法內被定義,則推薦使用StringBulider.而如果是類的成員變數,並且會被多個執行緒同時訪問,則使用StringBuffer. ###HashMap和HashTable的區別 HashMap是Hashtable的輕量級實現(非執行緒安全的實現),他們都完成了Map介面,主要區別在於HashMap允許空(null)鍵值(key),由於非執行緒安全,在只有一個執行緒訪問的情況下,效率要高於Hashtable。      HashMap允許將null作為一個entry的key或者value,而Hashtable不允許。      HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因為contains方法容易讓人引起誤解。      Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現。      最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多個執行緒訪問Hashtable時,不需要自己為它的方法實現同步,而HashMap 就必須為之提供外同步。      Hashtable和HashMap採用的hash/rehash演算法都大概一樣,所以效能不會有很大的差異。      就HashMap與HashTable主要從三方面來說。 一.歷史原因:Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map介面的一個實現 二.同步性:Hashtable是執行緒安全的,也就是說是同步的,而HashMap是執行緒序不安全的,不是同步的 三.值:只有HashMap可以讓你將空值作為一個表的條目的key或value

###sleep和wait的區別 sleep是讓出當前cpu,讓cpu去執行其他的執行緒.不會釋放鎖. wait是指在一個已經進入了同步鎖的執行緒內,讓自己暫時讓出同步鎖,以便其他正在等待此鎖的執行緒可以得到同步鎖並執行,只有其他執行緒呼叫了notify方法,呼叫wait方法的執行緒就會解除wait狀態和程式可以再次得到鎖後繼續向下執行

###多執行緒有幾種實現方法?同步有幾種實現方法? 多執行緒有兩種實現方法,分別是繼承Thread類與實現Runnable介面      同步的實現方面有兩種,分別是synchronized,wait與notify      wait():使一個執行緒處於等待狀態,並且釋放所持有的物件的lock。      sleep():使一個正在執行的執行緒處於睡眠狀態,是一個靜態方法,呼叫此方法要捕捉InterruptedException異常。      notify():喚醒一個處於等待狀態的執行緒,注意的是在呼叫此方法的時候,並不能確切的喚醒某一個等待狀態的執行緒,而是由JVM確定喚醒哪個執行緒,而且不是按優先順序。      Allnotity():喚醒所有處入等待狀態的執行緒,注意並不是給所有喚醒執行緒一個物件的鎖,而是讓它們競爭。

##集合類的問題 ###List和Set的區別 Set下面的實現由TreeSet和HashSet,每次插入操作的時候,Set都會根據自己特殊的比較器比較進行排序,與插入順序無關,而且所有的元素不能重複, Set的底層其實使用的是Map,只是使用了key,而忽略了value. List的實現有ArrayList和LinkedList,List可以重複. List的順序和插入順序有關 ###foreach原理 foreach的原理就是呼叫物件的iterator()獲取迭代器,然後遍歷該物件,所以只要改集合實現了Iterator介面,就可以使用foreach,除此以外,還陣列也可以使用foreach,其原理就是通過下標遍歷物件. 在使用foreach遍歷物件的時候,是不能修改集合結構的,但是集合元素的物件裡面的屬性是可以修改的. ###float和double的區別 定義float變數後面必須加f,double可以不加 ###精度轉換 高精度轉低精度時,因為會丟失精確度,所以需要強制轉換, 低精度轉高精度時,不需要強制轉換. ###Integer的比較 在-128到127之間,Integer會快取,所以這個範圍內的物件,如果值相等,那麼其物件就相等.

Integer f1 = 100,f2 = 100,f3 = 200,f4 = 200;
System.out.print(f1 ==f2);
System.out.print(f3 ==f4);
複製程式碼

###棧,堆,靜態區 通常我們定義一個基本資料型別的變數,一個物件的引用,還有就是函式呼叫的現場儲存都使用記憶體中的棧空間;而通過new關鍵字和構造器建立的物件放在堆空間;程式中的字面量(literal)如直接書寫的100、”hello”和常量都是放在靜態區中。棧空間操作起來最快但是棧很小,通常大量的物件都是放在堆空間,理論上整個記憶體沒有被其他程式使用的空間甚至硬碟上的虛擬記憶體都可以被當成堆空間來使用。 String a = new String("hello"); 上面的語句中變數a放在棧上,用new建立出來的字串物件放在堆上,而”hello”這個字面量放在靜態區。

###Math.round() 四捨五入的原理是在引數上加0.5然後進行下取整。 ###最有效率的計算方法 使用位運算和加減來代替乘除 ###陣列中只有length數學,而String中有length()方法,集合中只有size()方法 ###兩個物件值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對? 答:不對,如果兩個物件x和y滿足x.equals(y) == true,它們的雜湊碼(hash code)應當相同。Java對於eqauls方法和hashCode方法是這樣規定的:(1)如果兩個物件相同(equals方法返回true),那麼它們的hashCode值一定要相同;(2)如果兩個物件的hashCode相同,它們並不一定相同。當然,你未必要按照要求去做,但是如果你違背了上述原則就會發現在使用容器時,相同的物件可以出現在Set集合中,同時增加新元素的效率會大大下降(對於使用雜湊儲存的系統,如果雜湊碼頻繁的衝突將會造成存取效能急劇下降)。

補充:關於equals和hashCode方法,很多Java程式都知道,但很多人也就是僅僅知道而已,在Joshua Bloch的大作《Effective Java》(很多軟體公司,《Effective Java》、《Java程式設計思想》以及《重構:改善既有程式碼質量》是Java程式設計師必看書籍,如果你還沒看過,那就趕緊去亞馬遜買一本吧)中是這樣介紹equals方法的:首先equals方法必須滿足自反性(x.equals(x)必須返回true)、對稱性(x.equals(y)返回true時,y.equals(x)也必須返回true)、傳遞性(x.equals(y)和y.equals(z)都返回true時,x.equals(z)也必須返回true)和一致性(當x和y引用的物件資訊沒有被修改時,多次呼叫x.equals(y)應該得到同樣的返回值),而且對於任何非null值的引用x,x.equals(null)必須返回false。實現高質量的equals方法的訣竅包括:1. 使用==操作符檢查”引數是否為這個物件的引用”;2. 使用instanceof操作符檢查”引數是否為正確的型別”;3. 對於類中的關鍵屬性,檢查引數傳入物件的屬性是否與之相匹配;4. 編寫完equals方法後,問自己它是否滿足對稱性、傳遞性、一致性;5. 重寫equals時總是要重寫hashCode;6. 不要將equals方法引數中的Object物件替換為其他的型別,在重寫時不要忘掉@Override註解。
複製程式碼

###值傳遞和引用傳遞 java裡面只有值傳遞,即使傳遞的是一個物件的引用,那也是傳遞物件的地址.

public class User {

	public int age = 18;
	public static void main(String[] args) {
		User user = new User();
		change(user);
		System.out.println(user.age);
	}
	public static void change(User user){
		user.age = 100;
		user = new User();
	}
}
複製程式碼

在這裡,並沒有改變傳入的user物件,只是改變了user物件的屬性.

###描述一下JVM載入class檔案的原理機制? JVM中類的裝載是由類載入器(ClassLoader)和它的子類來實現的,Java中的類載入器是一個重要的Java執行時系統元件,它負責在執行時查詢和裝入類檔案中的類。 由於Java的跨平臺性,經過編譯的Java源程式並不是一個可執行程式,而是一個或多個類檔案。當Java程式需要使用某個類時,JVM會確保這個類已經被載入、連線(驗證、準備和解析)和初始化。類的載入是指把類的.class檔案中的資料讀入到記憶體中,通常是建立一個位元組陣列讀入.class檔案,然後產生與所載入類對應的Class物件。載入完成後,Class物件還不完整,所以此時的類還不可用。當類被載入後就進入連線階段,這一階段包括驗證、準備(為靜態變數分配記憶體並設定預設的初始值)和解析(將符號引用替換為直接引用)三個步驟。最後JVM對類進行初始化,包括:1)如果類存在直接的父類並且這個類還沒有被初始化,那麼就先初始化父類;2)如果類中存在初始化語句,就依次執行這些初始化語句。 類的載入是由類載入器完成的,類載入器包括:根載入器(BootStrap)、擴充套件載入器(Extension)、系統載入器(System)和使用者自定義類載入器(java.lang.ClassLoader的子類)。從Java 2(JDK 1.2)開始,類載入過程採取了父親委託機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根載入器,其他的載入器都有且僅有一個父類載入器。類的載入首先請求父類載入器載入,父類載入器無能為力時才由其子類載入器自行載入。JVM不會向Java程式提供對Bootstrap的引用。下面是關於幾個類載入器的說明:

    Bootstrap:一般用原生程式碼實現,負責載入JVM基礎核心類庫(rt.jar);
    Extension:從java.ext.dirs系統屬性所指定的目錄中載入類庫,它的父載入器是Bootstrap;
    System:又叫應用類載入器,其父類是Extension。它是應用最廣泛的類載入器。它從環境變數classpath或者系統屬性java.class.path所指定的目錄中記載類,是使用者自定義載入器的預設父載入器。
複製程式碼

###Java中非靜態內部類物件的建立要依賴其外部類物件

###java的記憶體洩漏 定義:物件已經沒有被應用程式使用,但是垃圾回收器沒辦法移除它們,因為還在被引用著。

###使用多執行緒的三種方式 繼承Thread類,實現Runnable介面,實現Callable介面

public class MyTask implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		System.out.println("lalala");
		return 1;
	}

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		Callable<Integer> myTask = new MyTask();
		FutureTask<Integer> futureTask = new FutureTask<>(myTask);
		Thread thread = new Thread(futureTask);
		thread.start();
		System.out.println(futureTask.get());
	}
}
複製程式碼

###實現執行緒同步 使用synchronized,在方法上,或者程式碼塊上.使用Look

private Lock accountLock = new ReentrantLock();
public void deposit(double money) {
    accountLock.lock();
    try {
        double newBalance = balance + money;
        try {
            Thread.sleep(10); // 模擬此業務需要一段處理時間
        }
        catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        balance = newBalance;
    }
    finally {
        accountLock.unlock();
    }
}
複製程式碼

###簡述一下物件導向的”六原則一法則”

  • 單一職責原則:一個類只做它該做的事情。(單一職責原則想表達的就是”高內聚”,寫程式碼最終極的原則只有六個字”高內聚、低耦合”,就如同葵花寶典或辟邪劍譜的中心思想就八個字”欲練此功必先自宮”,所謂的高內聚就是一個程式碼模組只完成一項功能,在物件導向中,如果只讓一個類完成它該做的事,而不涉及與它無關的領域就是踐行了高內聚的原則,這個類就只有單一職責。我們都知道一句話叫”因為專注,所以專業”,一個物件如果承擔太多的職責,那麼註定它什麼都做不好。這個世界上任何好的東西都有兩個特徵,一個是功能單一,好的相機絕對不是電視購物裡面賣的那種一個機器有一百多種功能的,它基本上只能照相;另一個是模組化,好的自行車是組裝車,從減震叉、剎車到變速器,所有的部件都是可以拆卸和重新組裝的,好的乒乓球拍也不是成品拍,一定是底板和膠皮可以拆分和自行組裝的,一個好的軟體系統,它裡面的每個功能模組也應該是可以輕易的拿到其他系統中使用的,這樣才能實現軟體複用的目標。)
  • 開閉原則:軟體實體應當對擴充套件開放,對修改關閉。(在理想的狀態下,當我們需要為一個軟體系統增加新功能時,只需要從原來的系統派生出一些新類就可以,不需要修改原來的任何一行程式碼。要做到開閉有兩個要點:①抽象是關鍵,一個系統中如果沒有抽象類或介面系統就沒有擴充套件點;②封裝可變性,將系統中的各種可變因素封裝到一個繼承結構中,如果多個可變因素混雜在一起,系統將變得複雜而換亂,如果不清楚如何封裝可變性,可以參考《設計模式精解》一書中對橋樑模式的講解的章節。)
  • 依賴倒轉原則:面向介面程式設計。(該原則說得直白和具體一些就是宣告方法的引數型別、方法的返回型別、變數的引用型別時,儘可能使用抽象型別而不用具體型別,因為抽象型別可以被它的任何一個子型別所替代,請參考下面的里氏替換原則。) 里氏替換原則:任何時候都可以用子型別替換掉父型別。(關於里氏替換原則的描述,Barbara Liskov女士的描述比這個要複雜得多,但簡單的說就是能用父型別的地方就一定能使用子型別。里氏替換原則可以檢查繼承關係是否合理,如果一個繼承關係違背了里氏替換原則,那麼這個繼承關係一定是錯誤的,需要對程式碼進行重構。例如讓貓繼承狗,或者狗繼承貓,又或者讓正方形繼承長方形都是錯誤的繼承關係,因為你很容易找到違反里氏替換原則的場景。需要注意的是:子類一定是增加父類的能力而不是減少父類的能力,因為子類比父類的能力更多,把能力多的物件當成能力少的物件來用當然沒有任何問題。)
  • 介面隔離原則:介面要小而專,絕不能大而全。(臃腫的介面是對介面的汙染,既然介面表示能力,那麼一個介面只應該描述一種能力,介面也應該是高度內聚的。例如,琴棋書畫就應該分別設計為四個介面,而不應設計成一個介面中的四個方法,因為如果設計成一個介面中的四個方法,那麼這個介面很難用,畢竟琴棋書畫四樣都精通的人還是少數,而如果設計成四個介面,會幾項就實現幾個介面,這樣的話每個介面被複用的可能性是很高的。Java中的介面代表能力、代表約定、代表角色,能否正確的使用介面一定是程式設計水平高低的重要標識。)
  • 合成聚合複用原則:優先使用聚合或合成關係複用程式碼。(通過繼承來複用程式碼是物件導向程式設計中被濫用得最多的東西,因為所有的教科書都無一例外的對繼承進行了鼓吹從而誤導了初學者,類與類之間簡單的說有三種關係,Is-A關係、Has-A關係、Use-A關係,分別代表繼承、關聯和依賴。其中,關聯關係根據其關聯的強度又可以進一步劃分為關聯、聚合和合成,但說白了都是Has-A關係,合成聚合複用原則想表達的是優先考慮Has-A關係而不是Is-A關係複用程式碼,原因嘛可以自己從百度上找到一萬個理由,需要說明的是,即使在Java的API中也有不少濫用繼承的例子,例如Properties類繼承了Hashtable類,Stack類繼承了Vector類,這些繼承明顯就是錯誤的,更好的做法是在Properties類中放置一個Hashtable型別的成員並且將其鍵和值都設定為字串來儲存資料,而Stack類的設計也應該是在Stack類中放一個Vector物件來儲存資料。記住:任何時候都不要繼承工具類,工具是可以擁有並可以使用的,而不是拿來繼承的。)
  • 迪米特法則:迪米特法則又叫最少知識原則,一個物件應當對其他物件有儘可能少的瞭解。(迪米特法則簡單的說就是如何做到”低耦合”,門面模式和調停者模式就是對迪米特法則的踐行。對於門面模式可以舉一個簡單的例子,你去一家公司洽談業務,你不需要了解這個公司內部是如何運作的,你甚至可以對這個公司一無所知,去的時候只需要找到公司入口處的前臺美女,告訴她們你要做什麼,她們會找到合適的人跟你接洽,前臺的美女就是公司這個系統的門面。再複雜的系統都可以為使用者提供一個簡單的門面,Java Web開發中作為前端控制器的Servlet或Filter不就是一個門面嗎,瀏覽器對伺服器的運作方式一無所知,但是通過前端控制器就能夠根據你的請求得到相應的服務。調停者模式也可以舉一個簡單的例子來說明,例如一臺計算機,CPU、記憶體、硬碟、顯示卡、音效卡各種裝置需要相互配合才能很好的工作,但是如果這些東西都直接連線到一起,計算機的佈線將異常複雜,在這種情況下,主機板作為一個調停者的身份出現,它將各個裝置連線在一起而不需要每個裝置之間直接交換資料,這樣就減小了系統的耦合度和複雜度,如下圖所示。迪米特法則用通俗的話來將就是不要和陌生人打交道,如果真的需要,找一個自己的朋友,讓他替你和陌生人打交道。)

相關文章