26、簡述Spring中Bean的生命周期?
????????在原生的java環境中,一個新的對象的產生是我們用new()的方式產生出來的。在Spring的IOC容器中,將這一部分的工作幫我們完成了(Bean對象的管理)。既然是對象,就存在生命周期,也就是作用域。了解Spring生命周期的意義在于,可以利用Bean在其存活期間的指定時刻可以完成一些特定的任務。
????????一般來講,有兩種作用域的bean對象:一、singleton(單例)作用域,即意味著整個Spring容器中只會存在一個bean實例;二、prototype(原型)作用域,即每次去IOC容器中獲取bean的時候都會返回一個新的實例對象;但是在基于Spring框架下的web應用里面,增加了會話維度來控制bean的生命周期:①request:針對每一次的http請求都會創建一個bean;②session:同一個session共享一個bean實例,不同的session會產生不同的bean實例對象;③globalSession:針對全局session維度共享同一個bean。
27、Spring中@Lazy注解的作用和原理?
????????我們在使用Spring的IOC容器進行bean對象的管理的時候,容器一啟動,就會將我們所需要的bean對象(像@Component、@Configuration等注解表明的類)創建并初始化,@Lazy注解即懶加載,即在容器初始化的時候并不會去加載相應的對象(單例模式下,是可以在容器啟動的時候創建對象的,而使用了@Lazy懶加載注解后,可以改變這一特性,讓對象在方法調用的時候再創建)。也可以通過添加此注解的方式來解決循環依賴的問題。
????????對于@Lazy的依賴,其實是返回了一個代理類,而不是拿到真正的bean注入,在真正使用這個bean的時候才會去加載初始化實體類出來,所以可以解決循環依賴的問題。
28、Scope注解的作用?
????????@Scope注解是用來控制實例作用域的,單實例還是多實例,該注解可以作用在類和方法上面,通過屬性來控制作用域,如下:
????????prototype:多實例,IOC容器啟動的時候并不會創建對象放在容器中,每次獲取的時候才會調用方法創建對象
????????singleton:單實例,IOC容器啟動的時候就會調用方法創建對象放到容器中,以后每次獲取都是從容器map中拿同一個對象
????????request:同一次請求創建一個實例
????????session:同一個session創建一個實例
29、增強for循環和for循環有什么區別?
我們先看一個簡單的例子:
@Testpublic void test1(){String[] arr = new String[]{"a","b","c","d"};for (String s :arr){System.out.print(s+"\t");}System.out.println();List<String> list = Lists.newArrayList("a","b","c","d");for (String s : list) {System.out.print(s+"\t");}}
上面的例子是兩個增強for循環的例子,意思很簡單就是遍歷然后輸出每個元素。稍微有點不同的是第一個是遍歷數組,第二個是遍歷集合。輸出結果其實也很明確,就是如下:
?然后我們可以在生成的class文件中看看,
@Testpublic void test1() {String[] arr = new String[]{"a", "b", "c", "d"};String[] var2 = arr;int var3 = arr.length;for(int var4 = 0; var4 < var3; ++var4) {String s = var2[var4];System.out.print(s + "\t");}System.out.println();List<String> list = Lists.newArrayList(new String[]{"a", "b", "c", "d"});Iterator var7 = list.iterator();while(var7.hasNext()) {String s = (String)var7.next();System.out.print(s + "\t");}}
遍歷數組時,其實生成的class文件中還是原始的for(int i = 0; i < length-1 ;i++){}的方式,而遍歷集合則使用的是迭代器。
30、final關鍵字的用法?
修飾類:表示類不可以被繼承;
修飾方法:表示方法不可被子類覆蓋,但是可以重載;
修飾變量:表示變量一旦被賦值就不可以更改它的值;
如果final修飾的是類變量(靜態變量),只能在靜態初始化塊中進行初始化該變量,或者是聲明該變量時進行賦值;
如果final修飾的是成員變量,可以在非靜態初始化塊,聲明該變量時或者構造器中進行初始化值;
如果final修飾局部變量,需要在使用該變量之前進行顯示的賦值初始化,切只能賦值一次;
如果final修飾基本類型的變量,則其數值一旦初始化便不可再進行修改;如果修飾的是引用類型的變量,則其初始化之后便不可以再指向另一個對象,但是其引用的值是可以更改的。
31、為什么局部內部類和匿名內部類只能訪問局部final變量?
// 匿名內部類
public class Test {public void test(final int b){final int a = 10;new Thread(){public void run(){System.out.println(a);System.out.println(b);}}.start();}
}
// 局部內部類
public class Test2 {public static void main(String[] args) {new Test2().outPrint(3);}private int age = 12;public void outPrint(final int x){class InClass{public void InPrint(){System.out.println(x);System.out.println(age);}}new InClass().InPrint();}
}
? ? ? ? 結合上述兩個例子匿名內部類也好,局部內部類也罷,它和外層的Test類在編譯之后其實是同級存在的,同時,匿名內部類和局部內部類并不會因為外層方法的結束而回收內部類,內部類也是一直存在的。這樣就存在一個問題,根據垃圾回收的機制,外部方法結束,局部變量就會被GC回收,但是局部變量又被內部類所引用,如果局部變量被回收,那么內部類則引用了一個不存在的變量,為了解決這個問題,將局部變量copy了一份放在了內部類中,就算局部變量被回收,內部類也會一直引用局部變量的copy的副本,就像延長了局部變量的生命周期。但是還得保證局部變量和內部類中copy的副本的值一致,所以有了final關鍵字的引用才可以保證兩處的值是一致的。
32、ArrayList和LinkedList的區別?
①、數據結構不同:ArrayList是基于數組實現的,而LinkedList是基于鏈表實現的;②、效率不同:Arraylist因為是基于數組實現,所以在查找元素的時候是根據下表索引去查找的,所以查詢的速度很快,而Linkedlist是基于鏈表實現的,每個節點保存了前一個節點的地址和下一個節點的地址,所以linkedlist刪除和添加元素很快,反而查詢起來比較慢了;③、自由性不同:ArrayList自由性較低,因為它需要手動的設置固定大小的容量,但是它的使用比較方便,只需要創建,然后添加數據,通過調用下標進行使用;而LinkedList自由性較高,能夠動態的隨數據量的變化而變化,但是它不便于使用。④:主要開銷不同:ArrayList主要控件開銷在于需要在lList列表預留一定空間;而LinkList主要控件開銷在于需要存儲節點信息以及節點指針;
ArrayList:內部使用數組的形式實現了存儲,實現了RandomAccess接口,利用數組的下面進行元素的訪問,因此對元素的隨機訪問速度非常快。
因為是數組,所以ArrayList在初始化的時候,有初始大小10,插入新元素的時候,會判斷是否需要擴容,擴容的步長是0.5倍原容量,擴容方式是利用數組的復制,因此有一定的開銷;
另外,ArrayList在進行元素插入的時候,需要移動插入位置之后的所有元素,位置越靠前,需要位移的元素越多,開銷越大,相反,插入位置越靠后的話,開銷就越小了,如果在最后面進行插入,那就不需要進行位移。
LinkedList:內部使用雙向鏈表的結構實現存儲,LinkedList有一個內部類作為存放元素的單元,里面有三個屬性,用來存放元素本身以及前后2個單元的引用,另外LinkedList內部還有一個header屬性,用來標識起始位置,LinkedList的第一個單元和最后一個單元都會指向header,因此形成了一個雙向的鏈表結構。
33、ArrayList是否會越界?
會出現下標越界。
首先,ArrayList是基于數組實現的,是一個動態數組,其容量能自動增長,類似于C語言中的動態申請內存,動態增長內存。
對于ArrayList而言,它實現List接口、底層使用數組保存所有元素。其操作基本上是對數組的操作。添加操作,首先會調用ensureCapacityInternal(size + 1),其作用為保證數組的容量始終夠用,其中size是elementData數組中元組的個數,初始為0。
在ensureCapacityInternal()函數中,用if判斷,如果數組沒有元素,給數組一個默認大小,會選擇實例化時的值與默認大小中較大值,然后調用ensureExplicitCapacity()。如果數組長度小于默認的容量10,則調用擴大數組大小的方法grow()。函數grow()解釋了基于數組的ArrayList是如何擴容的。數組進行擴容時,會將老數組中的元素重新拷貝一份到新的數組中,每次數組容量的增長大約是其原容量的1.5倍。
? ?接下來回到Add()函數,繼續執行,elementData[size++] = e; 這行代碼就是問題所在,當添加一個元素的時候,它可能會有兩步來完成:1. 在 elementData[Size] 的位置存放此元素;2. 增大 Size 的值。
? ?在單線程運行的情況下,如果 Size = 0,添加一個元素后,此元素在位置 0,而且 Size=1;
? ?而如果是在多線程情況下,比如有兩個線程,線程 A 先將元素存放在位置 0。但是此時 CPU 調度線程A暫停,線程 B 得到運行的機會。線程B也向此 ArrayList 添加元素,因為此時 Size 仍然等于 0 (注意哦,我們假設的是添加一個元素是要兩個步驟哦,而線程A僅僅完成了步驟1),所以線程B也將元素存放在位置0。然后線程A和線程B都繼續運行,都增加 Size 的值。那好,我們來看看 ArrayList 的情況,元素實際上只有一個,存放在位置 0,而 Size 卻等于 2。這就是“線程不安全”了。這就解釋了為何集合中會出現null。
? ?但是數組下標越界還不能僅僅依靠這個來解釋。我們觀察發生越界時的數組下標,分別為10、15、22、33、49和73。結合前面講的數組自動機制,數組初始長度為10,第一次擴容為15=10+10/2,第二次擴容22=15+15/2,第三次擴容33=22+22/2...以此類推,我們不難發現,越界異常都發生在數組擴容之時。
? ?由此給了我想法,我猜想是,由于沒有該方法沒有同步,導致出現這樣一種現象,用第一次異常,即下標為15時的異常舉例。當集合中已經添加了14個元素時,一個線程率先進入add()方法,在執行ensureCapacityInternal(size + 1)時,發現還可以添加一個元素,故數組沒有擴容,但隨后該線程被阻塞在此處。接著另一線程進入add()方法,執行ensureCapacityInternal(size + 1),由于前一個線程并沒有添加元素,故size依然為14,依然不需要擴容,所以該線程就開始添加元素,使得size++,變為15,數組已經滿了。而剛剛阻塞在elementData[size++] = e;語句之前的線程開始執行,它要在集合中添加第16個元素,而數組容量只有15個,所以就發生了數組下標越界異常!
34、&和&&的區別
&運算符有兩種用法:(1)按位與;(2)邏輯與。&&運算符是短路與運算。邏輯與 跟短路與的差別是非常巨大的,雖然二者都要求運算符左右兩端的布爾值都是 true 整個表達式的值才是 true。&&之所以稱為短路運算是因為,如果&&左邊的 表達式的值是 false,右邊的表達式會被直接短路掉,不會進行運算。
35、當一個對象被當作參數傳遞到一個方法后,此方法可改變 這個對象的屬性,并可返回變化后的結果,那么這里到底是值傳 遞還是引用傳遞?
是值傳遞。Java 語言的方法調用只支持參數的值傳遞。當一個對象實例作為一個 參數被傳遞到方法中時,參數的值就是對該對象的引用。對象的屬性可以在被調 用過程中被改變,但對對象引用的改變是不會影響到調用者的。
36、String、StringBuffer、StringBuilder區別
String 和 StringBuffer/StringBuilder,它 們可以儲存和操作字符串。其中 String 是只讀字符串,也就意味著 String 引用的 字符串內容是不能被改變的。而 StringBuffer/StringBuilder 類表示的字符串對象 可以直接進行修改。StringBuilder 和 StringBuffer 的方 法完全相同,區別在于StringBuffer的相關方法都被synchronized修飾,所以是線程安全的,而StringBuilder是非線程安全的,因此StringBuilder比StringBuffer的速度要快。
37、重載(Overload)和重寫(Override)的區別。重載的 方法能否根據返回類型進行區分?
方法的重載和重寫都是實現多態的方式,區別在于前者實現的是編譯時的多態性, 而后者實現的是運行時的多態性。重載發生在一個類中,同名的方法如果有不同 的參數列表(參數類型不同、參數個數不同或者二者都不同)則視為重載;重寫發生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的返 回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常(里 氏代換原則)。重載對返回類型沒有特殊的要求。所以無法根據返回類型區分重載和重寫。
38、char 型變量中能不能存貯一個中文漢字,為什么?
char 類型可以存儲一個中文漢字,因為 Java 中使用的編碼是 Unicode(不選擇 任何特定的編碼,直接使用字符在字符集中的編號,這是統一的唯一方法),一 個 char 類型占 2 個字節(16 比特),所以放一個中文是沒問題的。
39、抽象類(abstract class)和接口(interface)有什么異 同?
抽象類和接口都不能夠實例化,但可以定義抽象類和接口類型的引用。一個類如 果繼承了某個抽象類或者實現了某個接口都需要對其中的抽象方法全部進行實 現,否則該類仍然需要被聲明為抽象類。接口比抽象類更加抽象,因為抽象類中 可以定義構造器,可以有抽象方法和具體方法,而接口中不能定義構造器而且其 中的方法全部都是抽象方法。抽象類中的成員可以是 private、默認、protected、 public 的,而接口中的成員全都是 public 的。抽象類中可以定義成員變量,而接 口中定義的成員變量實際上都是常量。有抽象方法的類必須被聲明為抽象類,而 抽象類未必要有抽象方法。
40、Java 中會存在內存泄漏嗎
理論上 Java 因為有垃圾回收機制(GC)不會存在內存泄露問題(這也是 Java 被 廣泛使用于服務器端編程的一個重要原因);然而在實際開發中,可能會存在無 用但可達的對象,這些對象不能被 GC 回收,因此也會導致內存泄露的發生。例如 Hibernate 的 Session(一級緩存)中的對象屬于持久態,垃圾回收器是不會回收 這些對象的,然而這些對象中可能存在無用的垃圾對象,如果不及時關閉(close) 或清空(flush)一級緩存就可能導致內存泄露。
41、抽象的(abstract)方法是否可同時是靜態的(static), 是否可同時是本地方法(native),是否可同時被 synchronized 修飾?
都不能。抽象方法需要子類重寫,而靜態的方法是無法被重寫的,因此二者是矛 盾的。本地方法是由本地代碼(如 C 代碼)實現的方法,而抽象方法是沒有實現 的,也是矛盾的。synchronized 和方法的實現細節有關,抽象方法不涉及實現細 節,因此也是相互矛盾的。
42、Java 中如何實現序列化,有什么意義
序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流 化。可以對流化后的對象進行讀寫操作,也可將流化后的對象傳輸于網絡之間。 序列化是為了解決對象流讀寫操作時可能引發的問題(如果不進行序列化可能會 存在數據亂序的問題)。 要實現序列化,需要讓一個類實現 Serializable 接口,該接口是一個標識性接口, 標注該類對象是可被序列化的,然后使用一個輸出流來構造一個對象輸出流并通 過 writeObject(Object)方法就可以將實現對象寫出(即保存其狀態);如果需要 反序列化則可以用一個輸入流建立對象輸入流,然后通過 readObject 方法從流中 讀取對象。序列化除了能夠實現對象的持久化之外,還能夠用于對象的深度克隆
43、Statement 和 PreparedStatement 有什么區別?哪個性 能更好?
與 Statement 相比,①PreparedStatement 接口代表預編譯的語句,它主要的優 勢在于可以減少 SQL 的編譯錯誤并增加 SQL 的安全性(減少 SQL 注射攻擊的可 能性);②PreparedStatement 中的 SQL 語句是可以帶參數的,避免了用字符串 連接拼接 SQL 語句的麻煩和不安全;③當批量處理 SQL 或頻繁執行相同的查詢時, PreparedStatement 有明顯的性能上的優勢,由于數據庫可以將編譯優化后的 SQL 語句緩存起來,下次執行相同結構的語句時就會很快(不用再次編譯和生成 執行計劃)。
44、事務的 ACID 是指什么
原子性(Atomic):事務中各項操作,要么全做要么全不做,任何一項操作 的失敗都會導致整個事務的失敗;
?一致性(Consistent):事務結束后系統狀態是一致的;
?隔離性(Isolated):并發執行的事務彼此無法看到對方的中間狀態;?
持久性(Durable):事務完成后所做的改動都會被持久化,即使發生災難 性的失敗。通過日志和同步備份可以在故障發生后重建數據。
45、a = a + b 與 a += b 的區別
+= 操作符會進行隱式自動類型轉換,此處 a+=b 隱式的將加操作的結果類型強制轉換為持有結果的類型,而 a=a+b 則不會自動進行類型轉換
46、3*0.1 == 0.3 將會返回什么?true 還是 false?
false,因為有些浮點數不能完全精確的表示出來。浮點數不能用==判斷是否相等。
47、64 位 JVM 中,int 的長度是多數?
Java 中,int 類型變量的長度是一個固定值,與平臺無關,都是 32 位。意思就 是說,在 32 位 和 64 位 的 Java 虛擬機中,int 類型的長度是相同的。
48、“a==b”和”a.equals(b)”有什么區別?
如果 a 和 b 都是對象,則 a==b 是比較兩個對象的引用,只有當 a 和 b 指 向的是堆中的同一個對象才會返回 true,而 a.equals(b) 是進行邏輯比較,所以 通常需要重寫該方法來提供邏輯一致性的比較。例如,String 類重寫 equals() 方 法,所以可以用于兩個不同對象,但是包含的字母相同的比較。
49、a.hashCode() 有什么用?與 a.equals(b) 有什么關系?
hashCode() 方法是相應對象整型的 hash 值。它與 equals() 方法關系特 別緊密。根據 Java 規范,兩個使用 equal() 方法來判斷相等的對象,必須具有 相同的 hash code。
50、有沒有可能兩個不相等的對象有有相同的 hashcode?
有可能,兩個不相等的對象可能會有相同的 hashcode 值,這就是為什么在 hashmap 中會有沖突。相等 hashcode 值的規定只是說如果兩個對象相等,必 須有相同的 hashcode 值,但是沒有關于不相等對象的任何規定。