【知識詳解】JAVA基礎(秋招總結)


JAVA基礎

問:面向過程(POP)和面向對象(OOP)?

  • 面向過程主要是指從前到后的執行,強調的是一種功能行為,是以函數為最小單位的,主要考慮從前到后該怎么做;
  • 面向對象指的是把一些屬性和方法給提取出來組合成一個類,主要強調的是具有功能的對象,是以類/或者說對象為最小單位的,考慮的是誰來做;

問:Python和Java的區別?

對象:首先兩者都是面向對象的語言,但是側重點我感覺不一樣,java語言的設計集中在對象和接口,提供了類和接口的實現,在對象中封裝變量和對應的方法,實現信息的封裝和隱藏,類提供了對象的原型,並且通過繼承的機制,子類可以使用父類提供的方法,實現代碼復用; python的話是既支持面向對象的函數編程,也支持面向對象的抽象編程;
語言:java是強類型語言,數據類型定了以后就不能改變,同時也是一種靜態語言,在變量聲明的時候就要聲明變量類型,這樣的話編譯器在程序運行的時候就知道變量的類型,提前按數據類型規划好了內存區域,找起來范圍就變小了,所以更快一些; 而python是弱類型語言,同時是一種動態語言,在程序運行時解釋器只知道變量是一個對象,具體是什么類型根本不知道;所以速度相對較慢,比如list,變量任意類型;
在python中一切皆對象,a=1背后是a=int(1),1在java里是基本數據類型,但在python里是對象,所以也是在堆里分配空間的;
應用:java主要用於商業上,比如各種web端等,但是python用途最多的是腳本,像數據分析或者深度學習算法上;
:實際使用的python入門很簡單,有很多很強大的庫,python的庫可以用python、c語言等設計,所以無論是深度學習,智能算法、數據分析,圖像處理都有各種各樣的庫,java的話庫沒有那么多,很多庫都是商業公司內部使用的,或者發布出來的一個jar包,看不到源代碼;

問:java的八大基本數據類型?

數據類型 內存大小
byte 1kb
short 2kb
int 4kb
long 8kb
float 4kb
double 8kb
char 英文1kb,中文utf-8 3kb
boolean 4kb

問:封裝繼承多態說一下?

封裝:封裝有很多體現,比如有一些事物擁有共同的屬性和方法,所以就可以把這些屬性和方法放到一起,這就是封裝的一種體現;再比如,把一些屬性和方法聲明為私有的,也就是private的,外部不能對其直接進行訪問和修改,只對外暴露一些接口,能夠解耦合,無需知道具體實現細節,做到一個信息的隱藏,這也是封裝的一種體現;
繼承:有一些類都有相同的屬性和方法,所以就可以把這些屬性和方法提出來放到一個公共的基類里,也就是父類,然后這些類就可以繼承這個父類,獲得父類里非私有化的屬性,能夠提高程序的復用性,同時這也是多態的前提;
多態:多態是指父類的引用能夠指向子類的對象。引用變量在調用方法時,並不能夠確定是哪個方法,只有在運行的時候才知道,其實這也是一種向上轉型。創建的是一個父類的引用,實際指向的是子類的對象。另外也體現在方法的重寫上,子類可以直接繼承父類的方法,如果想修改也可以對該方法進行重寫,這也是多態的體現。能夠提高程序的可擴展性;

問:方法和函數的區別?

方法可以說是函數的一種

  • 函數是一段代碼,可以通過名字調用,把一些數據傳進去處理,然后返回一些數據,或者沒有返回值也行;
  • 方法也是一段代碼,也是通過名字來調用的,但是它跟一個對象關聯,可以操作類內部的數據;

問:抽象類和接口的區別?

  • 一提抽象類,就是指不能被實例化,要被繼承,但是有構造器,構造器是供子類使用的,抽象類中有抽象方法,只有聲明,沒有方法體,if子類繼承了抽象類,那一定要重寫抽象方法;
  • 接口更多的是一種“能不能”的關系,本質上是一種標准和規范,其中的方法都是public abstract, 屬性都是public static final,也就是說只有抽象方法和常量,沒有構造器;主要用來被實現類實現,一個類可以實現多個接口;
  • 抽象類的成員變量可以是各種類型,但是接口是public static final類型,也就是常量;一個類可以繼承一個抽象類,但是可以實現多個接口;抽象類是對事物進行的一種抽象,包括屬性和行為,是一種“是不是”的關系,但是接口更多的是對一些行為進行抽象,主要是“能不能”的關系.

問:==和equals區別?

  • ==:它的作用是判斷兩個對象地址是否相同,判斷兩個對象是不是同一個(具體對於基本數據類型就是值相不相等,引用數據類型就是比較地址);
  • equals:也是判斷兩個對象是否相等;但是很多時候都會重寫equals方法,重寫以后就是比較的內容是不是相等;

問:final,finally和finalize的區別?

  • final是一個關鍵字,可以用來修飾屬性、方法和類,final修飾的屬性不能被修改,方法不能被重寫,類不能被繼承;
  • finally是異常處理的里面的語句,往往和try-catch一起使用,能夠保證在finally的程序一定會被執行;
  • finalize是Object類的一個方法,所以每個類都有這個方法,這個方法在在垃圾回收的時候被調用;

問:重寫和重載的區別?

  • 重寫:重寫指的是子類重寫了父類的方法;要求是同名同參數,即兩者的方法名相同,參數列表也相同;除此之外,子類方法的返回值類型應該小於等於父類方法的返回值類型,重寫的方法訪問權限要大於等於被重寫的;
  • 重載:重載指的是同樣的方法根據輸入的不同,做出的反應也不同,是同一個類中同名的參數,但是參數列表不同;

問:static關鍵字說一下?

static是一個關鍵字,能夠把屬性和方法聲明為靜態的,意思就是說這些是隨着類的加載而加載的,靜態變量只加載一次,存在方法區中,靜態方法只能調用靜態的屬性和方法,都可以直接用類來調用;之所以要用這個就是因為 有些數據在內存空間里只要有一份就行,不用每個對象都單獨再分配,是成員共享的;比如最常見的單例模式,就是只能創建一個實例對象;

問:Java的值傳遞?

java是采用的值傳遞,是將實參的副本傳入方法,參數本身不受影響;

  • 基本類型的變量,將變量進行了拷貝,所以方法無論怎么修改都不會影響原變量;
  • 引用類型的變量,將變量所指向的對象地址進行了拷貝,所以會修改到原對象;

問:深拷貝和淺拷貝?

這是經常會碰到的一種情況,因為經常會用到復制還有賦值操作,其實也是因為java的值傳遞機制;

  • 淺拷貝:拷貝后有和原始對象一樣的值,比如如果是基本數據類型,就拷貝這個類型的值,如果是引用數據類型,就拷貝內存中的地址,所以如果任何一個進行了修改的話,這個值都是要變化的;
  • 深拷貝:在拷貝引用數據類型的變量時,為這個成員開辟了一個獨立的內存空間,是內容上的拷貝,兩者指向的是不同的內存空間,兩者修改不再相互影響;

問:String、StringBuffer和StringBuilder區別?

  • String是字符串常量,是不可變的對象,每次對String拼接的時候,都是新創建了一個String對象,然后指向了新的String對象;
  • StringBuffer是字符串變量,初始化長度為16的數組,裝不下就擴容,擴容為原來的2倍+2,同時將原數組復制到新數組中; 是線程安全的,所以當多線程的時候用stringBuffer;
  • StringBuilder是字符串變量,是線程不安全的,但是其效率比StringBuffer要高,String的效率最低;

問:new一個字符串和直接定義個一個字符串?

  • 通過new來創建字符串:首先在編譯期間先去字符串常量池中檢查是否存在該字符串,不存在就開辟空間新建一個,存在的話就不鍵了,常量池中始終就存在一個這樣的字符串;然后在堆里開辟出一塊空間存剛在的字符串(姑且理解為存着方法區里的字符串地址),棧里開辟出空間存放創建的引用變量,引用變量指向堆里面那塊地址;
  • 通過字面量創建的話,也是在常量池中檢查是否有當前字符串,沒有的話就新建一個,然后棧中的引用變量指向方法區的字符串;

問:cookie和session的區別?

http是無狀態的協議,服務器不能夠從網絡連接上知道客戶端的身份;所以才有了cookie和session;

  • cookie是瀏覽器保存少量數據的一種技術,每次訪問服務器的時候都會帶着,比如用戶的登錄信息等;
  • session是服務器保存當前會話數據的一種技術,主要是在服務端記錄用戶的狀態;session放在服務端安全性要高一些;
  • 在實際應用中,是將兩者結合使用,如果所有信息都保存在cookie里太占用空間了,每次訪問都得帶着,所以就會用到session;瀏覽器第一次訪問服務器的時候,服務器會創建一個session和sessionID,然后設置cookie將sessionID發給瀏覽器,瀏覽器以后每次訪問服務器都要帶着,這樣服務器就能根據sessionID來判斷是哪個用戶了;

問:說一下java的異常(throw和throws)?

Java的異常都繼承自Java.lang.throwable類,主要分為兩大類:一個是error,一個是exception,error一般不需要編寫針對性代碼進行處理,exception又分為編譯時異常和運行時異常;

image

對於異常的處理,主要是用到抓拋模型,也就是說java在運行的時候if產生異常,會生成一個異常類對象,然后拋出,當捕獲到對應的異常后,進行相應的處理,然后異常類對象的拋出,可以有兩種實現方法,一是系統自動生成,一是手動生成並拋出(throw);
異常處理有兩種機制:

  • try-catch-finally:產生異常后通過catch進行捕獲並處理;

  • throws:聲明拋出異常,意思就是不處理,往上拋,由該方法的調用者去處理;

  • throw是在異常的生成階段,指的是手動拋出異常對象;

  • throws是在異常的處理階段,它是一種異常的處理方式;

問:什么是序列化?

Java 對象在 JVM退出時會全部銷毀,如果需要將對象持久化就要通過序列化實現,將內存中的對象保存在二進制流中,需要時再將二進制流反序列化為對象。對象序列化保存的是對象的狀態,屬於類屬性的靜態變量不會被序列化。

常見的序列化有三種:
① Java 原生序列化,實現 Serializabale標記接口,兼容性最好,但不支持跨語言,性能一般。序列化和反序列化必須保持序列化 ID 的一致,一般使用 private static final long serialVersionUID 定義序列化 ID,如果不設置編譯器會根據類的內部實現自動生成該值。
② Hessian 序列化,支持動態類型、跨語言。
③ JSON 序列化,將數據對象轉換為 JSON 字符串,拋棄了類型信息,反序列化時只有提供類型信息才能准確進行。相比前兩種方式可讀性更好。

序列化通常使用網絡傳輸對象,容易遭受攻擊,因此不需要進行序列化的敏感屬性應加上 transient 關鍵字,把變量生命周期僅限於內存,不會寫到磁盤

集合

問:List、Set、Map區別?

  • List是存儲有序、可重復的數據;類似於一種“動態”數組;建立時不用指定長度;
  • Set是存儲無序、不可重復的數據,類似於數學上“集合”的概念;
  • Map是存儲的鍵值對;Key-Value的形式;是一對的數據;

問:ArrayList、LinkedList、Vertor區別?

  • ArrayList是List的主要實現類,底層創建了長度為10的object數組,在jdk7的時候是初始化的時候就創建,然后如果不夠了就擴容為原來的1.5倍,然后把原來數組復制到新數組中;jdk8以后在初始化的時候不創建了,延遲創建,第一次調用add的時候創建;
  • LinkedList是維持了一個類似雙向鏈表,內部是Node類型,有first和last屬性,指向前一個元素和后一個元素;
  • Vector和ArrayList一樣,只不過是同步的,訪問慢點,無論是jdk7還是8,都是創建長度為10的數組,不夠了擴容為原來2倍;
  • ArrayList是基於動態數組,所以其獲取和修改比較快,而LinkedList是基於鏈表的,所以其插入和刪除有優勢;兩者都不是同步的, Vector就是同步的ArrayList,所以其訪問要比ArrayList慢,同時每次擴容為原來2倍,而ArrayList是1.5倍;

問:HashMap底層實現原理?

在jdk1.8以前,哈希表底層是采用數組+鏈表來實現的:

  • 1.當創建一個哈希表時,底層創建了一個長度為16的一維數組Entry[];
  • 2.然后向里面添加元素時,首先調用key所在類的hashcode計算出來它的哈希值,然后將哈希值通過某種散列函數得到在數組中的位置;
  • 3.if當前位置上沒有元素,那就直接添加成功;if當前位置上有元素,那就比較當前key的哈希值和已經存在元素的哈希值,if都不一樣,那就添加成功;如果有元素哈希值也一樣,那就調用equals方法,if都為false,那就添加,如果和誰比價是true,那就這個key的值做更新;

在jdk1.8里,哈希表底層采用的是數組+鏈表+紅黑樹的結構,和之前的區別主要有3個

  • 1.初始化的時候沒有創建數組,而是在第一個put的時候創建;
  • 2.底層數組不再是entry,變成了node;
  • 3.當數組某一索引處的元素個數>8但是< 64的時候,采用紅黑樹存儲;

問:為什么重寫equals必須重寫hashcode方法?

因為在進行哈希表的put或者其他操作時,if兩個對象相等,那兩個對象的hashcode也一定是相等的;if兩個對象相等,那去調用equals方法時返回true,但是反過來就不成立了,if兩個對象具有相等的hashcode,不一定相等,也就是說,當equals方法被覆蓋過后,hashcode也必須得覆蓋;

問:HashMap如何擴容?

哈希表有負載因子的概念,一般取0.75,當數組長度大於默認容量*負載因子的時候,就擴容,默認擴容為原來的2倍,然后重新計算元素在新數組中的位置(rehash),然后復制進去;
這個負載因子其實控制着哈希表中的數據密度。if過大,那就會增加碰撞的幾率,鏈表就會長;if過小,就很容易引發擴容,造成內存浪費;

問:HashMap的長度為什么是2的n次方?

HashMap是為了存取高效,要求是盡量減少碰撞,也就是盡可能把數據分配均勻;這個算法實際上就是在取模,hash%length;但是在計算機中求余運算不如位運算,所以在源碼中是使用位運算進行計算的:hash&(length-1);
但是hash%length == hash&(length-1)的前提是length是2的n次方;
為什么這樣能均勻分布減少碰撞呢?因為2的n次方實際上就是1后面n個0,2的n次方-1實際上就是n個1;這樣進行與的時候就會減少碰撞;

例如長度為9的時候:3&(9-1)=0, 2&(9-1)=0,都在0上,發生碰撞;      
例如長度為8的時候:3&(8-1)=0, 2&(8-1)=0,不同位置上,不發生碰撞;

問:hashmap和hashtable的區別?

  • 首先最重要的區別是hashmap是線程不安全的,而hashtable是線程安全的;
  • 其次hashmap是允許存放空置null的,而hashtable不允許存放空值,if為null會拋異常;
  • hashmap在底層實現上初始化容量為16,之后每次擴容為原來的2倍,而hashtable初始容量為11,之后每次擴容為原來的2倍+1;

問:Hashmap為什么線程不安全?

hashmap不安全其實主要體現在需要擴容的時候,比如如果在原數組里,有這幾個數據:[3,A],[7,B],[5,C],而且都在一個桶上,也就是entry數組,然后執行擴容,假如線程1執行完:Entry next = e.next,時間片切到了線程2,這時候e是[3,A],而next是[7.B],然后線程2 resize完了之后,假如[7.B]的next后成了[3,a],又回到線程1,線程1先處理自己當時的e,[3,a],然后next,[7,B],但是,這時候因為[7,B]的next變成了[3,a],所以就變成了一個循環鏈表,陷入死循環;
所以一般在多線程的時候不會用hashmap,hashtable是線程安全的,但是它是把所有需要多線程的操作都加上了synchronized關鍵字,所有線程都在競爭一把鎖,效率太低;
所以現在涉及到線程安全的時候一般采用concurrenthashmap;

問:HashMap實現線程安全的方法有哪些?

主要有兩個方法:

  • 使用concurrentHashMap[首選],因為下面兩個都會給整個集合進行加鎖,導致其他操作阻塞,性能低下;
  • 使用hashtable;
  • 使用線程安全下的hashmap,使用collections下的線程安全的容器;比如Collections.synchronizedMap(new HashMap());

問:說一下concurrenthashmap?

JDK1.7

底層是由segment數組、hashentry數組和鏈表組成;segment本身相當於一個hashmap對象,然后里面包含一個hashentry數組,數組中的每個hashentry既是一個鍵值對,也是一個鏈表的頭節點;
可以說concurrenthashmap是一個二級哈希表,在總哈希表下又若干個子哈希表;主要是通過分段鎖來實現的,每一個segment都是一個獨立的鎖,內部是hashmap 。整體的結構如下圖類似:

image

  • put操作:

1.進行第一次key的hash來定位Segment的位置(其實還會進行一次hash以減少沖突);
2.if Segment還沒有初始化,那就進行賦值;segment初始化主要是hashentry數組初始大小(cap,默認為1),負載因子;
3.進行第三次hash操作,找到相應的HashEntry的位置;
4.在將數據插入指定的HashEntry位置時,會通過繼承ReentrantLock的tryLock()方法嘗試去獲取鎖,如果獲取成功就直接插入相應的位置;
5.如果已經有線程獲取該Segment的鎖,那當前線程會以自旋的方式去繼續的調用tryLock()方法去獲取鎖,超過指定次數就掛起,等待喚醒;

  • get操作:

第一次需要經過一次hash定位到Segment的位置,再hash定位到指定的HashEntry,遍歷該HashEntry下的鏈表進行對比,成功就返回,不成功就返回null;

  • size操作:類似於樂觀鎖和悲觀鎖

1.第一種方案以不加鎖的模式去嘗試多次計算ConcurrentHashMap的size,最多三次,比較前后兩次計算的結果,結果一致就認為當前沒有元素加入,計算的結果是准確的;樂觀鎖
2.第二種方案是如果第一種方案不符合,他就會給每個Segment加上鎖,然后計算ConcurrentHashMap的size返回;悲觀鎖

JDK1.8
在JDK1.8中和hashmap一樣,為了防止查詢鏈表的復雜度變為O(N), 底層采用了Node數組+鏈表+紅黑樹組成;

node節點都采用volatile來修飾,保證並發的可見性;內部大量采用syschronized和cas來操作,每次只鎖定當前鏈表或者紅黑樹的根節點,保證效率;

concurrentmap和hashtable區別
hashtable是一種類似於全表鎖,使用synchronized來保證線程安全,效率很低,當一個線程訪問同步方法時,其他線程就不能訪問了,會進入阻塞狀態,效率太低;
而concurrentmap是使用了分段鎖的思想,效率就要高很多了。

泛型

問:說一下泛型?

泛型就是我們在定義類或方法的時候,可以不用確定好參數,而是使用一種類似標簽的想法,等編譯的時候才去確定好具體的類型,這個確定類型的工作推遲到了創建對象或者方法調用的時候。
采用泛型能夠解決一些問題,比如說集合里可以存儲object類型,所以各種類都能放到集合里,一方面這樣不安全,另一方面在使用集合里的元素時,需要進行強制類型轉換,麻煩而且容易出錯;使用了泛型,那就不需要了,而且這種可讀性和穩定性也提高了,在寫程序的時候就能夠限定類型;

反射

問:說一下反射?

我理解的反射,可以說是正常創建new對象的反過程,正常情況下,是引入需要的類,然后通過new來實例化,然后再得到實例化后的對象,反射的話是實例化對象,然后調用getClass方法能夠得到整個類的結構。這樣的話就能夠動態的獲取類的信息,或者動態的調用類對象的方法和屬性。這就是反射。

所寫的程序經過javac.exe命令以后,會生成一個或多個字節碼文件(以.class結尾),然后使用java.exe命令對字節碼文件進行解釋運行,相當於將某個字節碼文件加載到內存中,這就是類的加載,這時加載到內存中的類稱為運行時類,這個運行時類就作為整個Class類的一個實例;這個Class類是java反射的源頭;可以通過Class類來獲取Class類實例

Person p1 = new Person();
Class clazz = p1.getClass();  //獲取Class類實例,也就是運行時類;
//Class<Person> clazz = Person.class();  //得到運行時類的另一種方法;
Person obj = (Person)clazz.newInstance(); //得到運行時類的實例對象;需要有運行時類的空參構造器;

框架

問:說一下IOC的原理?

IoC 是控制反轉的意思,也就是把對象創建和對象之間的調用過程都交給Spring來進行管理,主要目的是為了降低程序之間的耦合度,IOC是基於IOC容器來完成的,底層就是對象工廠。主要有兩種創建方式,一種是基於xml配置文件,一種是基於注解方式。

問:說一下AOP?

AOP 是面向切面編程,將代碼中重復的部分抽取出來,使用動態代理技術,在不修改源碼的基礎上對方法進行增強。

如果目標對象實現了接口,默認采用 JDK 動態代理,也可以強制使用 CGLib;如果目標對象沒有實現接口,采用 CGLib 的方式。

常用場景包括權限認證、自動緩存、錯誤處理、日志、調試和事務等。

相關術語
Aspect:切面,一個關注點的模塊化,這個關注點可能會橫切多個對象。
Joinpoint:連接點,程序執行過程中的某一行為,即業務層中的所有方法。(類里面哪些方法可以被增強,這些方法稱為連接點);
Advice:通知,指切面對於某個連接點所產生的動作,包括前置通知、后置通知、返回后通知、異常通知和環繞通知。(實際增強的邏輯部分,稱為通知或者說增強)
Pointcut:切入點,指被攔截的連接點,切入點一定是連接點,但連接點不一定是切入點。(實際被真正增強的方法,稱為切入點);
Proxy:代理,Spring AOP 中有 JDK 動態代理和 CGLib 代理,目標對象實現了接口時采用 JDK 動態代理,反之采用 CGLib 代理。
Target:代理的目標對象,指一個或多個切面所通知的對象。
Weaving :織入,指把增強應用到目標對象來創建代理對象的過程。

相關注解

@Aspect:聲明被注解的類是一個切面 Bean。
@Before:前置通知,指在某個連接點之前執行的通知。
@After:后置通知,指某個連接點退出時執行的通知(不論正常返回還是異常退出)。
@AfterReturning:返回后通知,指某連接點正常完成之后執行的通知,返回值使用 returning 屬性接收。
@AfterThrowing:異常通知,指方法異常退出時執行的通知,和 @AfterReturning 只會有一個執行,異常使用 throwing 屬性接收

問:Spring中有哪些設計模式?

簡單工廠模式:Spring 中的 BeanFactory,根據傳入一個唯一的標識來獲得 Bean 實例。
工廠方法模式:Spring 的 FactoryBean 接口的 getObject 方法。
單例模式:Spring 的 ApplicationContext 創建的 Bean 實例都是單例對象。
代理模式:Spring 的 AOP。
適配器模式:Spring MVC 中的 HandlerAdapter,由於 handler 有很多種形式,包括 Controller、HttpRequestHandler、Servlet 等,但調用方式又是確定的,因此需要適配器來進行處理,根據適配規則調用 handle 方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM