前言
題目彙總來源 史上最全各類面試題彙總,沒有之一,不接受反駁
春招的進度有些太快了,不知道現在開始學還來不來得及。跟着上面那篇公衆號文章裏的問題學的,順便做個記錄。
- 面試題彙總一 Java 語言基礎篇
- 面試題彙總二 Java 多線程篇
- 面試題彙總三 Java 集合篇
- 面試題彙總四 JVM 篇
- 面試題彙總五 Spring 篇
- 面試題彙總六 數據庫篇
- 面試題彙總七 計算機網絡篇
目錄
Object類的equal和hashCode方法重寫,爲什麼?
String、StringBuffer、StringBuilder區別
靜態屬性和靜態方法是否可以被繼承?是否可以被重寫?以及原因?
成員內部類、靜態內部類、局部內部類和匿名內部類的理解,以及項目中的應用
反射中ClassLoader.loadClass和class.ForName區別
Java基礎知識
面向對象三大特徵
- 繼承:子類繼承父類的特徵和行爲
- 封裝:隱藏內部實現,暴露公共行爲
- 多態:不同對象對一個動作有不同表現
面向對象六大原則
- 單一職責原則 SRP:一個類只擔負一個職責
- 開閉原則 OCP:類對擴展開放,對修改關閉
- 裏式替換原則 LSP:引用基類的地方必須能透明地使用其子類對象
- 依賴倒置原則 DIP:依賴抽象接口,不依賴具體實現
- 接口隔離原則 ISP:客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上
- 迪米特原則 LOD:低耦合,高內聚
Java可見性修飾符
public > protected > default > private
Java設計模式
Java中==和equals和hashCode的區別
1. 基本數據類型用==進行比較。
2. 引用使用==判斷是否指向同一個對象,也可覆蓋equals方法實現自定義判斷。
3. hashCode生成hash碼。同一對象一定生成相同的hash碼
4. hashCode和equals的關係
-
同一對象上多次調用hashCode()方法,總是返回相同的整型值。
-
如果a.equals(b),則一定有a.hashCode() 一定等於 b.hashCode()。
-
如果!a.equals(b),則a.hashCode() 不一定不等於 b.hashCode()。此時如果a.hashCode() 總是不等於 b.hashCode(),會提高hashtables的性能。
-
a.hashCode() == b.hashCode() 則 a.equals(b)可真可假
-
a.hashCode() != b.hashCode() 則 a.equals(b)爲假。
Object類的equal和hashCode方法重寫,爲什麼?
- 如果兩個對象equals,Java運行時環境會認爲他們的hashcode一定相等。
- 如果兩個對象不equals,他們的hashcode有可能相等。
- 如果兩個對象hashcode相等,他們不一定equals。
- 如果兩個對象hashcode不相等,他們一定不equals。
- 若重寫 equals(Object obj) 方法,有必要重寫 hashcode() 方法,確保通過 equals(Object obj) 方法判斷結果爲true的兩個對象具備相等的 hashcode() 返回值。說得簡單點就是:“如果兩個對象相同,那麼他們的 hashcode 應該相等”。不過請注意:這個只是規範,如果你非要寫一個類讓 equals(Object obj) 返回 true 而 hashcode() 返回兩個不相等的值,編譯和運行都是不會報錯的。不過這樣違反了Java規範,程序也就埋下了BUG。
- 一個很常見的錯誤根源在於沒有覆蓋 hashCode 方法。在每個覆蓋了 equals 方法的類中,也必須覆蓋 hashCode 方法。如果不這樣做的話,就會違反 Object.hashCode 的通用約定,從而導致該類無法結合所有基於散列的集合一起正常運作,這樣的集合包括 HashMap、HashSet 和 Hashtable。
Java基本類型及其佔用空間
byte | 1字節 |
short | 2字節 |
char | 2字節 |
int | 4字節 |
float | 4字節 |
boolean | 4字節 |
long | 8字節 |
double | 8字節 |
int與integer的區別
1. Integer是int的包裝類,int則是java的一種基本數據類型
2. Integer變量必須實例化後才能使用,而int變量不需要
3. Integer實際是對象的引用,當new一個Integer時,實際上是生成一個指針指向此對象;而int則是直接存儲數據值
4. Integer的默認值是null,int的默認值是0
類變量初始化順序
- 父類顯式靜態初始化代碼塊
- 子類顯式靜態初始化代碼塊
- 父類非靜態實例初始化代碼塊
- 父類構造函數
- 子類非靜態實例初始化代碼塊
- 子類構造函數
抽象類的意義
一個類中如果包含抽象方法,這個類應該用abstract關鍵字聲明爲抽象類。
意義:
1. 爲子類提供一個公共的類型;
2. 封裝子類中重複內容(成員變量和方法);
3. 定義有抽象方法,子類雖然有不同的實現,但該方法的定義是一致的。
接口和抽象類的區別
抽象類:
包含抽象方法的類要用abstract修飾(雖然沒有抽象方法也可以)。
和普通類一樣可以有普通成員變量和方法。
接口:
接口中所有的方法不能有具體的實現。
變量會被隱式地指定爲public static final變量
能否創建一個包含可變對象的不可變對象?
原公衆號放答案了,自己看吧?
談談對java多態的理解
概念:
同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果,這就是多態性。
父類引用指向子類對象,可以提高代碼的靈活性和可擴展性。
實現條件:
繼承、重寫、向上轉型。
實現形式:
繼承和接口。
重載規則:必須具有不同的參數列表; 可以有不同的返回類型;可以有不同的訪問修飾符;可以拋出不同的異常。
重寫規則:參數列表必須完全相同;返回類型必須相同;訪問修飾符的限制一定要大於等於被重寫方法的訪問修飾符;重寫方法一定不能拋出新的檢查異常或者比被重寫方法申明更加寬泛的檢查型異常。
Java中String的瞭解
emmm有點泛,隨便貼個鏈接
String、StringBuffer、StringBuilder區別
圖析:String,StringBuffer與StringBuilder的區別
探祕Java中的String、StringBuilder以及StringBuffer
String:不可變,每次修改都會生成新的類
StringBuffer:可變,線程安全
StringBuilder:可變,線程不安全,速度快
你對String對象的intern()熟悉麼?
intern() 對 String s = new String(...) 這樣的形式有效,要保證此時字符串在堆中而不在常量池中。
intern() 會先判斷字符串是否在常量池中,如果不是,則將字符串放入常量池中;如果在常量池中,則直接返回自身。
String爲什麼要設計成不可變的?
總結就是兩點:安全和性能
安全:不可變對象線程安全;作爲最常用的數據類型設置爲不可變可保證數據安全。
性能:不變性可引入常量池概念,複用字符串,節省空間,提高性能。
泛型中extends和super的區別
Java 泛型 <? super T> 中 super 怎麼 理解?與 extends 有何不同?
<? extends T>:
上界通配符(Upper Bounds Wildcards)
只能輸出T及其父類,也就是返回值爲T及其父類(只取不存)
<? super T>:
下界界通配符(Lower Bounds Wildcards)
只能輸入T及其子類,也就是傳入參數爲T及其子類(只存不取)
<?>:
既不能用於入參也不能用於返參
PECS原則:
Producer Extends Consumer Super
頻繁往外讀取內容的,適合用上界Extends。
經常往裏插入的,適合用下界Super。
進程和線程的區別
進程:是併發執行的程序在執行過程中分配和管理資源的基本單位,是一個動態概念,競爭計算機系統資源的基本單位。
線程:是進程的一個執行單元,是進程內的調度實體。比進程更小的獨立運行的基本單位。線程也被稱爲輕量級進程。
一個程序至少一個進程,一個進程至少一個線程。
final,finally,finalize的區別
final:修飾符,可修飾類、方法、成員變量
finally:用於try/catch語句後,若try語句被執行則finally一定會執行
finalize:在java.lang.Object裏定義的,gc時調用,且只調用一次
序列化的方式
如何將一個Java對象序列化到文件裏?
Java中實現序列化的兩種方式 Serializable 接口和 Externalizable接口
Serializable:
一個對象的所有屬性包括私有屬性都可以序列化,可以使用 transient 關鍵詞排除序列化的屬性。
serialVersionUID 保證對象的一致性。
使用 ObjectInputStream 和 ObjectOutputStream 實現對象的序列化和反序列化。
Externalizable:
Serializable 接口的子類。
writeExternal() 和 readExternal() 方法可以指定序列化哪些屬性。
更多序列化方式:幾種Java序列化方式的實現
String 轉換成 integer的方式及原理
Integer.parseInt(String str)方法,具體思路其實就是一個字符一個字符的轉換。
要注意的就是邊界值的處理。
靜態屬性和靜態方法是否可以被繼承?是否可以被重寫?以及原因?
能被繼承,不能被重寫。
誰調用靜態函數,執行的靜態函數就屬於誰。
成員內部類、靜態內部類、局部內部類和匿名內部類的理解,以及項目中的應用
Java內部類詳解--成員內部類,局部內部類,匿名內部類,靜態內部類
成員內部類:
定義於一個類內部,依附於外部類存在。
可訪問外部類所有成員變量和成員方法,包括 private 和 static。
外部類訪問內部類需通過內部類對象的引用。
修飾符 private, protected, public。
局部內部類:
定義於方法或作用域內。
不能有修飾符。
只能訪問 final 局部變量。
匿名內部類:
在實現父類或接口時生成對象,沒有構造器。
不能有修飾符。
只能訪問 final 局部變量。
靜態內部類:
定義於外部類,有static關鍵字,不依附於外部類。
未持有外部類引用,不能使用外部類的非static成員變量或者方法。
講一下常見編碼方式?
常見的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等
如何格式化日期?
通過 java.text.DateFormat 格式化日期。
// 2019年2月23日 星期六
String s1 = DateFormat.getDateInstance(DateFormat.FULL).format(new Date());
// 2019-02-23 10:59:29
String s2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
Java的異常體系
異常類型:
Throwable 可分爲 Error 和 Exception。
Exception 可分爲 RuntimeException(不受檢異常) 和其他異常(受檢異常)。
異常捕獲:
try-catch-finally體系。
異常拋出:
throw 和 throws 關鍵詞。
異常轉譯與異常鏈
什麼是異常鏈
捕獲一個異常後拋出另外一個異常,並且把異常原始信息保存下來,這被稱爲異常鏈。
Throwable及其子類的構造器可以接收一個cause參數追蹤到異常最初發生的位置。
throw和throws的區別
throw關鍵字是用於方法體內部,用來拋出一個Throwable類型的異常。如果拋出了檢查異常, 則還應該在方法頭部聲明方法可能拋出的異常類型。該方法的調用者也必須檢查處理拋出的異常。 如果所有方法都層層上拋獲取的異常,最終JVM會進行處理,處理也很簡單,就是打印異常消息和堆棧信息。 如果拋出的是Error或RuntimeException,則該方法的調用者可選擇處理該異常。有關異常的轉譯會在下面說明。
throws關鍵字用於方法體外部的方法聲明部分,用來聲明方法可能會拋出某些異常。僅當拋出了檢查異常, 該方法的調用者才必須處理或者重新拋出該異常。當方法的調用者無力處理該異常的時候,應該繼續拋出, 而不是囫圇吞棗一般在catch塊中打印一下堆棧信息做個勉強處理。
說說你對Java反射的理解
反射的原理,反射創建類實例的三種方式是什麼
反射:
Reflection(反射)是Java被視爲動態語言的關鍵,反射機制允許程序在執行期藉助於Reflection API取得任何類的內部信息,並能直接操作任意對象的內部屬性及方法。
Class類:
描述類的類,封裝了描述方法的 Method,描述字段的 Filed,,描述構造器的 Constructor 等屬性。
Class類加載的三種方式:
try {
Class c1 = new Food().getClass();
Class c2 = Food.class;
Class c3 = Class.forName("GenericTest.Food");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
反射中ClassLoader.loadClass和class.ForName區別
forName()會初始化類,將.class文件加載到jvm中,還會執行類中的static塊,還會執行給靜態變量賦值的靜態方法。
loadClass()只將class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊。
java當中的四種引用
強引用 - FinalReference:
最常見的引用。
String str1 = new String("str");
軟引用 - SoftReference:
在快報 OutOfMemoryError 異常時GC回收。
SoftReference<String> str2 = new SoftReference<>(new String("str"));
弱引用 - WeakReference:
只能活到下一次GC前,一旦發生GC就會被回收。
WeakReference<String> str3 = new WeakReference<>(new String("str"));
虛引用 - PhantomReference:
完全不影響對象的生命週期,爲一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。
必須配合引用隊列使用。
ReferenceQueue<String> rq = new ReferenceQueue<>();
PhantomReference<String> str4 = new PhantomReference<>(new String("str"), rq);
引用隊列 - ReferenceQueue:
軟引用(SoftReference)和引用隊列(ReferenceQueue)
軟引用、弱引用和虛引用通過 get() 方法獲得對象的強引用,若對象已被回收,則返回 null。
若爲這三種引用指定引用隊列,則在對象回收後,會將空的引用放入引用隊列中以供後續處理。
深拷貝和淺拷貝的區別是什麼?
對象要克隆需要實現 Cloneable 接口。
淺拷貝只複製了對象本身,使得對象內部的引用可能指向同一個對象;
深拷貝將拷貝對象內部引用的對象也都複製一遍。
什麼是編譯期常量?使用它有什麼風險?
在編譯時就能確定這個常量的具體值,與運行時常量對應。
由於編譯期常量在編譯時就確定了值,編譯時會直接替換成對應的值。
對應到實際業務中,可能是我們的程序中使用了一個第三方庫中公有的編譯期常量時,如果對方更新了該常量的值,而我們隨後也只更新依賴的jar包,那麼我們的程序中該常量就是老值,就會產生隱患。爲了避免這種情況,在更新依賴的jar文件時,應該重新編譯我們的程序。
a=a+b與a+=b有什麼區別嗎?
若 a 的範圍比 b 小,則 a += b 有自動的類型轉換,而 a = a + b 沒有類型轉換,會報錯,需要強制轉換。除此之外基本相同。
靜態代理和動態代理的區別,什麼場景使用?
概念:
爲某個對象提供一個代理,以控制對這個對象的訪問。 代理類和委託類有共同的父類或父接口,這樣在任何使用委託類對象的地方都可以用代理對象替代。代理類負責請求的預處理、過濾、將請求分派給委託類處理、以及委託類執行完請求後的後續處理。
角色:
接口
委託類:真正執行任務的類
代理類:代理類持有一個委託類的對象引用
靜態代理:
編譯期就確定代理關係
動態代理:
在代碼運行期間加載被代理的類
Proxy.newProxyInstance(接口類的ClassLoader, 接口類的class, 實現了InvocationHandler的類)
// 接口 java.lang.Runnable
// 委託類
class Peer implements Runnable {
@Override
public void run() {
System.out.println("This is peer!");
}
}
// 代理類
class Ih implements InvocationHandler {
Object o;
public Object bind(Object o) {
this.o = o;
return Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法執行前操作
System.out.println("before");
Object result = method.invoke(o, args);
// 方法執行後
System.out.println("after");
return result;
}
}
// 運行
Runnable p = (Runnable) new Ih().bind(new Peer());
p.run();
// 輸出
before
This is peer!
after
Java中實現多態的機制是什麼?
方法的重寫 Overriding 和重載 Overloading 是Java多態性的不同表現。
重寫 Overriding 是父類與子類之間多態性的一種表現。
重載 Overloading 是一個類中多態性的一種表現。
說說你對Java註解的理解
什麼是註解(Annotation):
Annotation(註解)就是Java提供了一種元程序中的元素關聯任何信息和着任何元數據(metadata)的途徑和方法。Annotion(註解)是一個接口,程序可以通過反射來獲取指定程序元素的Annotion對象,然後通過Annotion對象來獲取註解裏面的元數據。
Java SE5內置標準註解:
@Override,表示當前的方法定義將覆蓋超類中的方法。
@Deprecated,使用了註解爲它的元素編譯器將發出警告,因爲註解@Deprecated是不贊成使用的代碼,被棄用的代碼。
@SuppressWarnings,關閉不當編譯器警告信息。
用於創建註解的註解:
@Target |
表示該註解可以用於什麼地方,可能的ElementType參數有: CONSTRUCTOR:構造器的聲明 FIELD:域聲明(包括enum實例) LOCAL_VARIABLE:局部變量聲明 METHOD:方法聲明 PACKAGE:包聲明 PARAMETER:參數聲明 TYPE:類、接口(包括註解類型)或enum聲明 |
@Retention |
表示需要在什麼級別保存該註解信息。可選的RetentionPolicy參數包括: SOURCE:註解將被編譯器丟棄 CLASS:註解在class文件中可用,但會被VM丟棄 RUNTIME:VM將在運行期間保留註解,因此可以通過反射機制讀取註解的信息。 |
@Document |
將註解包含在Javadoc中 |
@Inherited |
允許子類繼承父類中的註解 |
註解的創建:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation {
int id();
String name() default "TestAnnotation";
}
註解的讀取:
@TestAnnotation(id = 1)
class AnnotationTest {
@TestAnnotation(id = 2,name = "MethodAnnotation")
void testFun() {}
}
// ----------------------------
Class cls = AnnotationTest.class;
TestAnnotation clsAnnotation = (TestAnnotation) cls.getAnnotation(TestAnnotation.class);
System.out.println("clsAnnotation " + clsAnnotation.id() + " " + clsAnnotation.name());
Method[] methods = cls.getDeclaredMethods();
for (Method m : methods) {
TestAnnotation mAnnotation = m.getAnnotation(TestAnnotation.class);
System.out.println("mAnnotation " + mAnnotation.id() + " "+ mAnnotation.name());
}
// 輸出
clsAnnotation 1 TestAnnotation
mAnnotation 2 MethodAnnotation
說說你對依賴注入的理解
這部分我對很多概念的印象還是糊的,以下請謹慎參考。
還是採用上文中的人開車的例子。
第一步:
一個人開豐田車,最基本的寫法就是人持有豐田車的引用。
class Person {
Toyota toyota;
Person(Toyota toyota) {
this.toyota = toyota;
}
void drive() {
toyota.run();
}
}
第二步:
如果這個人不開豐田車了,改開Audi,則需改動代碼。改進這一點,就涉及到依賴倒置原則。
- 高層模塊不應該依賴低層模塊,兩者都應該依賴抽象
- 抽象不應該依賴細節
- 細節應該依賴抽象
class GoOut {
void go() {
// Toyota類實現了Car接口
Toyota t = new Toyota();
Person person = new Person(t);
person.drive();
}
}
class Person {
Car car;
Person(Car car) {
this.car = car;
}
void drive() {
car.run();
}
}
第三步:
經過上一步改進,實現了Person和具體車型的解耦,只需改動GoOut類的代碼就可以更換Person的車型。如果要更進一步,可將依賴關係交由專門容器管理,即依賴注入。Spring實現IoC首先需要配置xml文件:
<?xml version="1.0" encoding="GBK"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="oneCar" class="Toyota"> <!-- Toyota類是ICar的一個實現-->
</bean>
<bean id="onePerson" class="Person"> <!--本例以屬性方式注入爲例 -->
<property name="car">
<ref bean="oneCar"></ref>
</property>
</bean>
</beans>
然後使用 BeanFactroy 類調用:
class GoOut {
void go() {
BeanFactory factory=new XmlBeanFactory("bean.xml");
Person boy=(Person)factory.getBean("onePerson");
boy.drive();
}
}
說一下泛型原理,並舉例說明
java泛型(二)、泛型的內部原理:類型擦除以及類型擦除帶來的問題
類型擦除