java面試題2018及答案
面向物件特徵
封裝,繼承,多型和抽象
封裝
封裝給物件提供了隱藏內部特性和行為的能力。物件提供一些能被其他物件訪問的方法來改
變它內部的資料。在 Java 當中,有 3 種修飾符: public, private 和 protected。每一種修飾符
給其他的位於同一個包或者不同包下面物件賦予了不同的訪問許可權。
下面列出了使用封裝的一些好處:
通過隱藏物件的屬性來保護物件內部的狀態。
提高了程式碼的可用性和可維護性,因為物件的行為可以被單獨的改變或者是擴充套件。
禁止物件之間的不良互動提高模組化
繼承
繼承給物件提供了從基類獲取欄位和方法的能力。繼承提供了程式碼的重用行,也可以在不修改類的情況下給現存的類新增新特性。
多型
多型是程式語言給不同的底層資料型別做相同的介面展示的一種能力。一個多型型別上的操作可以應用到其他型別的值上面。
抽象
抽象是把想法從具體的例項中分離出來的步驟,因此,要根據他們的功能而不是實現細節來建立類。 Java 支援建立只暴漏介面而不包含方法實現的抽象的類。這種抽象技術的主要目的是把類的行為和實現細節分離開。
final, finally, finalize 的區別
final
修飾符(關鍵字)如果一個類被宣告為final,意味著它不能再派生出新的子類,不能作為父類被繼承。因此一個類不能既被宣告為 abstract的,又被宣告為final的。將變數或方法宣告為final,可以保證它們在使用中不被改變。被宣告為final的變數必須在宣告時給定初值,而在以後的引用中只能讀取,不可修改。被宣告為final的方法也同樣只能使用,不能過載。
finally
在異常處理時提供 finally 塊來執行任何清除操作。如果丟擲一個異常,那麼相匹配的 catch 子句就會執行,然後控制就會進入 finally 塊(如果有的話)。
finalize
方法名。Java 技術允許使用 finalize() 方法在垃圾收集器將物件從記憶體中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在確定這個物件沒有被引用時對這個物件呼叫的。它是在 Object 類中定義的,因此所有的類都繼承了它。子類覆蓋 finalize() 方法以整理系統資源或者執行其他清理工作。finalize() 方法是在垃圾收集器刪除物件之前對這個物件呼叫的。
int 和 Integer 有什麼區別
int 是基本資料型別
Integer是其包裝類,注意是一個類。
為什麼要提供包裝類呢???
一是為了在各種型別間轉化,通過各種方法的呼叫。否則 你無法直接通過變數轉化。
比如,現在int要轉為String
int a=0;
String result=Integer.toString(a);
在java中包裝類,比較多的用途是用在於各種資料型別的轉化中。
我寫幾個demo
//通過包裝類來實現轉化的
int num=Integer.valueOf("12");
int num2=Integer.parseInt("12");
double num3=Double.valueOf("12.2");
double num4=Double.parseDouble("12.2");
//其他的類似。通過基本資料型別的包裝來的valueOf和parseXX來實現String轉為XX
String a=String.valueOf("1234");//這裡括號中幾乎可以是任何型別
String b=String.valueOf(true);
String c=new Integer(12).toString();//通過包裝類的toString()也可以
String d=new Double(2.3).toString();
再舉例下。比如我現在要用泛型
List<Integer> nums;
這裡<>需要類。如果你用int。它會報錯的。
過載和重寫的區別
override(重寫)
1. 方法名、引數、返回值相同。
2. 子類方法不能縮小父類方法的訪問許可權。
3. 子類方法不能丟擲比父類方法更多的異常(但子類方法可以不丟擲異常)。
4. 存在於父類和子類之間。
5. 方法被定義為final不能被重寫。
overload(過載)
1. 引數型別、個數、順序至少有一個不相同。
2. 不能過載只有返回值不同的方法名。
3. 存在於父類和子類、同類中。
抽象類和介面有什麼區別
介面是公開的,裡面不能有私有的方法或變數,是用於讓別人使用的,而抽象類是可以有私有方法或私有變數的,
另外,實現介面的一定要實現接口裡定義的所有方法,而實現抽象類可以有選擇地重寫需要用到的方法,一般的應用裡,最頂級的是介面,然後是抽象類實現介面,最後才到具體類實現。
還有,介面可以實現多重繼承,而一個類只能繼承一個超類,但可以通過繼承多個介面實現多重繼承,介面還有標識(裡面沒有任何方法,如Remote介面)和資料共享(裡面的變數全是常量)的作用。
說說反射的用途及實現
Java反射機制主要提供了以下功能:在執行時構造一個類的物件;判斷一個類所具有的成員變數和方法;呼叫一個物件的方法;生成動態代理。反射最大的應用就是框架
Java反射的主要功能:
確定一個物件的類
取出類的modifiers,資料成員,方法,構造器,和超類.
找出某個接口裡定義的常量和方法說明.
建立一個類例項,這個例項在執行時刻才有名字(執行時間才生成的物件).
取得和設定物件資料成員的值,如果資料成員名是執行時刻確定的也能做到.
在執行時刻呼叫動態物件的方法.
建立陣列,陣列大小和型別在執行時刻才確定,也能更改陣列成員的值.
反射的應用很多,很多框架都有用到
spring 的 ioc/di 也是反射….
javaBean和jsp之間呼叫也是反射….
struts的 FormBean 和頁面之間…也是通過反射呼叫….
JDBC 的 classForName()也是反射…..
hibernate的 find(Class clazz) 也是反射….
反射還有一個不得不說的問題,就是效能問題,大量使用反射系統性能大打折扣。怎麼使用使你的系統達到最優就看你係統架構和綜合使用問題啦,這裡就不多說了。
來源:http://www.wityx.com/javaee/
說說自定義註解的場景及實現
(此題自由發揮,就看你對註解的理解了!==)登陸、許可權攔截、日誌處理,以及各種Java框架,如Spring,Hibernate,JUnit 提到註解就不能不說反射,Java自定義註解是通過執行時靠反射獲取註解。實際開發中,例如我們要獲取某個方法的呼叫日誌,可以通過AOP(動態代理機制)給方法新增切面,通過反射來獲取方法包含的註解,如果包含日誌註解,就進行日誌記錄。
HTTP 請求的 GET 與 POST 方式的區別
GET方法會把名值對追加在請求的URL後面。因為URL對字元數目有限制,進而限制了用在客戶端請求的引數值的數目。並且請求中的引數值是可見的,因此,敏感資訊不能用這種方式傳遞。
POST方法通過把請求引數值放在請求體中來克服GET方法的限制,因此,可以傳送的引數的數目是沒有限制的。最後,通過POST請求傳遞的敏感資訊對外部客戶端是不可見的。
參考:http://www.wityx.com/post/561_1_1.html
session 與 cookie 區別
cookie 是 Web 伺服器傳送給瀏覽器的一塊資訊。瀏覽器會在本地檔案中給每一個 Web 服務
器儲存 cookie。以後瀏覽器在給特定的 Web 伺服器發請求的時候,同時會發送所有為該服
務器儲存的 cookie。下面列出了 session 和 cookie 的區別:
無論客戶端瀏覽器做怎麼樣的設定,session都應該能正常工作。客戶端可以選擇禁用 cookie,
但是, session 仍然是能夠工作的,因為客戶端無法禁用服務端的 session。
JDBC 流程
1、 載入JDBC驅動程式:
在連線資料庫之前,首先要載入想要連線的資料庫的驅動到JVM(Java虛擬機器),
這通過java.lang.Class類的靜態方法forName(String className)實現。
例如:
try{
//載入MySql的驅動類
Class.forName("com.mysql.jdbc.Driver") ;
}catch(ClassNotFoundException e){
System.out.println("找不到驅動程式類 ,載入驅動失敗!");
e.printStackTrace() ;
}
成功載入後,會將Driver類的例項註冊到DriverManager類中。
2、 提供JDBC連線的URL
連線URL定義了連線資料庫時的協議、子協議、資料來源標識。
書寫形式:協議:子協議:資料來源標識
協議:在JDBC中總是以jdbc開始 子協議:是橋連線的驅動程式或是資料庫管理系統名稱。
資料來源標識:標記找到資料庫來源的地址與連線埠。
例如:
jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=gbk;useUnicode=true;(MySql的連線URL)
表示使用Unicode字符集。如果characterEncoding設定為 gb2312或GBK,本引數必須設定為true 。characterEncoding=gbk:字元編碼方式。
3、建立資料庫的連線
要連線資料庫,需要向java.sql.DriverManager請求並獲得Connection物件, 該物件就代表一個數據庫的連線。
使用DriverManager的getConnectin(String url , String username , String password )方法傳入指定的欲連線的資料庫的路徑、資料庫的使用者名稱和 密碼來獲得。
例如: //連線MySql資料庫,使用者名稱和密碼都是root
String url = "jdbc:mysql://localhost:3306/test" ;
String username = "root" ;
String password = "root" ;
try{
Connection con = DriverManager.getConnection(url , username , password ) ;
}catch(SQLException se){
System.out.println("資料庫連線失敗!");
se.printStackTrace() ;
}
4、 建立一個Statement
•要執行SQL語句,必須獲得java.sql.Statement例項,Statement例項分為以下3 種類型:
1、執行靜態SQL語句。通常通過Statement例項實現。
2、執行動態SQL語句。通常通過PreparedStatement例項實現。
3、執行資料庫儲存過程。通常通過CallableStatement例項實現。
具體的實現方式:
Statement stmt = con.createStatement() ; PreparedStatement pstmt = con.prepareStatement(sql) ; CallableStatement cstmt = con.prepareCall(“{CALL demoSp(? , ?)}”) ;
5、執行SQL語句
Statement介面提供了三種執行SQL語句的方法:executeQuery 、executeUpdate 和execute
1、ResultSet executeQuery(String sqlString):執行查詢資料庫的SQL語句 ,返回一個結果集(ResultSet)物件。
2、int executeUpdate(String sqlString):用於執行INSERT、UPDATE或 DELETE語句以及SQL DDL語句,如:CREATE TABLE和DROP TABLE等
3、execute(sqlString):用於執行返回多個結果集、多個更新計數或二者組合的 語句。 具體實現的程式碼:
ResultSet rs = stmt.executeQuery(“SELECT * FROM …”) ; int rows = stmt.executeUpdate(“INSERT INTO …”) ; boolean flag = stmt.execute(String sql) ;
6、處理結果
兩種情況:
1、執行更新返回的是本次操作影響到的記錄數。
2、執行查詢返回的結果是一個ResultSet物件。
• ResultSet包含符合SQL語句中條件的所有行,並且它通過一套get方法提供了對這些 行中資料的訪問。
• 使用結果集(ResultSet)物件的訪問方法獲取資料:
while(rs.next()){
String name = rs.getString(“name”) ;
String pass = rs.getString(1) ; // 此方法比較高效
}
(列是從左到右編號的,並且從列1開始)
7、關閉JDBC物件
操作完成以後要把所有使用的JDBC物件全都關閉,以釋放JDBC資源,關閉順序和聲 明順序相反:
1、關閉記錄集
2、關閉宣告
3、關閉連線物件
if(rs != null){ // 關閉記錄集
try{
rs.close() ;
}catch(SQLException e){
e.printStackTrace() ;
}
}
if(stmt != null){ // 關閉宣告
try{
stmt.close() ;
}catch(SQLException e){
e.printStackTrace() ;
}
}
if(conn != null){ // 關閉連線物件
try{
conn.close() ;
}catch(SQLException e){
e.printStackTrace() ;
}
}
MVC 設計思想
MVC就是
M:Model 模型
V:View 檢視
C:Controller 控制器
模型就是封裝業務邏輯和資料的一個一個的模組,控制器就是呼叫這些模組的(java中通常是用Servlet來實現,框架的話很多是用Struts2來實現這一層),檢視就主要是你看到的,比如JSP等.
當用戶發出請求的時候,控制器根據請求來選擇要處理的業務邏輯和要選擇的資料,再返回去把結果輸出到檢視層,這裡可能是進行重定向或轉發等.
equals 與 == 的區別
值型別(int,char,long,boolean等)都是用==判斷相等性。物件引用的話,==判斷引用所指的物件是否是同一個。equals是Object的成員函式,有些類會覆蓋(override)這個方法,用於判斷物件的等價性。例如String類,兩個引用所指向的String都是”abc”,但可能出現他們實際對應的物件並不是同一個(和jvm實現方式有關),因此用==判斷他們可能不相等,但用equals判斷一定是相等的。
集合
List 和 Set 區別
List,Set都是繼承自Collection介面
List特點:元素有放入順序,元素可重複
Set特點:元素無放入順序,元素不可重複,重複元素會覆蓋掉
(注意:元素雖然無放入順序,但是元素在set中的位置是有該元素的HashCode決定的,其位置其實是固定的,加入Set 的Object必須定義equals()方法 ,另外list支援for迴圈,也就是通過下標來遍歷,也可以用迭代器,但是set只能用迭代,因為他無序,無法用下標來取得想要的值。)
Set和List對比:
Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變。
List:和陣列類似,List可以動態增長,查詢元素效率高,插入刪除元素效率低,因為會引起其他元素位置改變。
List 和 Map 區別
List是物件集合,允許物件重複。
Map是鍵值對的集合,不允許key重複。
Arraylist 與 LinkedList 區別
Arraylist:
優點:ArrayList是實現了基於動態陣列的資料結構,因為地址連續,一旦資料儲存好了,查詢操作效率會比較高(在記憶體裡是連著放的)。
缺點:因為地址連續, ArrayList要移動資料,所以插入和刪除操作效率比較低。
LinkedList:
優點:LinkedList基於連結串列的資料結構,地址是任意的,所以在開闢記憶體空間的時候不需要等一個連續的地址,對於新增和刪除操作add和remove,LinedList比較佔優勢。LinkedList 適用於要頭尾操作或插入指定位置的場景
缺點:因為LinkedList要移動指標,所以查詢操作效能比較低。
適用場景分析:
當需要對資料進行對此訪問的情況下選用ArrayList,當需要對資料進行多次增加刪除修改時採用LinkedList。
ArrayList 與 Vector 區別
public ArrayList(int initialCapacity)//構造一個具有指定初始容量的空列表。
public ArrayList()//構造一個初始容量為10的空列表。
public ArrayList(Collection<? extends E> c)//構造一個包含指定 collection 的元素的列表
Vector有四個構造方法:
public Vector()//使用指定的初始容量和等於零的容量增量構造一個空向量。
public Vector(int initialCapacity)//構造一個空向量,使其內部資料陣列的大小,其標準容量增量為零。
public Vector(Collection<? extends E> c)//構造一個包含指定 collection 中的元素的向量
public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量構造一個空的向量
ArrayList和Vector都是用陣列實現的,主要有這麼三個區別:
Vector是多執行緒安全的,執行緒安全就是說多執行緒訪問同一程式碼,不會產生不確定的結果。而ArrayList不是,這個可以從原始碼中看出,Vector類中的方法很多有synchronized進行修飾,這樣就導致了Vector在效率上無法與ArrayList相比;
兩個都是採用的線性連續空間儲存元素,但是當空間不足的時候,兩個類的增加方式是不同。
Vector可以設定增長因子,而ArrayList不可以。
Vector是一種老的動態陣列,是執行緒同步的,效率很低,一般不贊成使用。
適用場景分析:
Vector是執行緒同步的,所以它也是執行緒安全的,而ArrayList是執行緒非同步的,是不安全的。如果不考慮到執行緒的安全因素,一般用ArrayList效率比較高。
如果集合中的元素的數目大於目前集合陣列的長度時,在集合中使用資料量比較大的資料,用Vector有一定的優勢。
HashMap 和 Hashtable 的區別
1.hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
2.hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。
3.hashMap允許空鍵值,而hashTable不允許。
注意:
TreeMap:非執行緒安全基於紅黑樹實現。TreeMap沒有調優選項,因為該樹總處於平衡狀態。
Treemap:適用於按自然順序或自定義順序遍歷鍵(key)。
參考:http://www.wityx.com/post/466_1_1.html
HashSet 和 HashMap 區別
set是線性結構,set中的值不能重複,hashset是set的hash實現,hashset中值不能重複是用hashmap的key來實現的。
map是鍵值對對映,可以空鍵空值。HashMap是Map介面的hash實現,key的唯一性是通過key值hash值的唯一來確定,value值是則是連結串列結構。
他們的共同點都是hash演算法實現的唯一性,他們都不能持有基本型別,只能持有物件
HashMap 和 ConcurrentHashMap 的區別
ConcurrentHashMap是執行緒安全的HashMap的實現。
(1)ConcurrentHashMap對整個桶陣列進行了分割分段(Segment),然後在每一個分段上都用lock鎖進行保護,相對於HashTable的syn關鍵字鎖的粒度更精細了一些,併發效能更好,而HashMap沒有鎖機制,不是執行緒安全的。
(2)HashMap的鍵值對允許有null,但是ConCurrentHashMap都不允許。
HashMap 的工作原理及程式碼實現
參考:https://tracylihui.github.io/2015/07/01/Java集合學習1:HashMap的實現原理/
ConcurrentHashMap 的工作原理及程式碼實現
HashTable裡使用的是synchronized關鍵字,這其實是對物件加鎖,鎖住的都是物件整體,當Hashtable的大小增加到一定的時候,效能會急劇下降,因為迭代時需要被鎖定很長的時間。
ConcurrentHashMap算是對上述問題的優化,其建構函式如下,預設傳入的是16,0.75,16。
public ConcurrentHashMap(int paramInt1, float paramFloat, int paramInt2) {
//…
int i = 0;
int j = 1;
while (j < paramInt2) {
++i;
j <<= 1;
}
this.segmentShift = (32 - i);
this.segmentMask = (j - 1);
this.segments = Segment.newArray(j);
//…
int k = paramInt1 / j;
if (k * j < paramInt1)
++k;
int l = 1;
while (l < k)
l <<= 1;
for (int i1 = 0; i1 < this.segments.length; ++i1)
this.segments[i1] = new Segment(l, paramFloat);
}
public V put(K paramK, V paramV) {
if (paramV == null)
throw new NullPointerException();
int i = hash(paramK.hashCode()); //這裡的hash函式和HashMap中的不一樣
return this.segments[(i >>> this.segmentShift & this.segmentMask)].put(paramK, i, paramV, false);
}
ConcurrentHashMap引入了分割(Segment),上面程式碼中的最後一行其實就可以理解為把一個大的Map拆分成N個小的HashTable,在put方法中,會根據hash(paramK.hashCode())來決定具體存放進哪個Segment,如果檢視Segment的put操作,我們會發現內部使用的同步機制是基於lock操作的,這樣就可以對Map的一部分(Segment)進行上鎖,這樣影響的只是將要放入同一個Segment的元素的put操作,保證同步的時候,鎖住的不是整個Map(HashTable就是這麼做的),相對於HashTable提高了多執行緒環境下的效能,因此HashTable已經被淘汰了。