文章目錄
1. 海興電網Java面試總結
1.1 你理解多態是什麼,來說一說
- Java是面向對象的語言,具有封裝繼承多態的特點:
- 封裝性着重考慮其安全性和重用性。
- 繼承性着重考慮其高效性和重用性。
- 多態性着重考慮其統一性(有機性)和高效性。
- 多態的前提是一個父類和多個子類,即父類引用指向子類對象。在調用一個方法時,從源代碼上看(即編寫代碼的時候),無法確定調用了哪個對象的方法(因爲父子類有相同的方法),只有在程序
運行期間
根據對象變量引用的實際對象才能確定此方法是哪個對象的,這種現象稱之爲動態綁定。
- 多態的具體體現有哪些?
- 繼承:多個子類對同一方法的重寫
- 接口:實現接口並覆蓋接口中的同一方法
- 項目裏面哪個地方用到和具體實現?(擴展瞭解)
- 多態有編譯時多態和運行時多態。
- 第一種就是我們調用方法是不用區分參數類型,程序會自動執行相應方法,如: 加法運算,可以使int相加,可以是double相加,都是同一個方法名。
- 第二種就是動態綁定,使用父類引用指向子類對象,再調用某一父類中的方法時,不同子類會表現出不同結果。 這樣的作用就是擴展性極好,玩過網遊的話應該知道 遊戲中有不同的角色,它們都有一個父類,它們做相同動作時表現出來的效果就會不一樣,比如跑,魔法師的跑跟狂戰士的跑就不會一樣,這就是倆者都覆蓋了父類中的跑方法,各自有自己的現實,表現出來多態。 如果有一天你想再加個角色,只用再寫一個類繼承該父類,覆蓋其中的跑方法就行了,其他代碼不用怎麼改,所以可維護性也很好。
- 其實說到多態就是 面向接口編程,它不和具體類盡心掛鉤了。比如 你沒用多態的話,你每實例化一個對象 就要new一下,那假如你那天改變了需求了呢?那是不是又要改裏面的?這樣不好,所以 你可以通過多態,把需要相似的給提出來,然後繼承它 這樣以後需要擴展你僅僅只是繼承而已,這樣就很簡單。
- 所以
多態性着重考慮其統一性(有機性)和高效性。
1.2 來說一說Java類的加載過程
Java類加載的目的?
- 所有的類都由類加載器將class文件加載到內存中。
類加載過程?
- 加載–連接–初始化
- 連接包括:驗證–準備–解析
類加載器
ClassLoader
都有哪些?
- 啓動類加載器
BootstrapClassLoader
最頂層的加載類,由C++實現
,負責加載
%JAVA_HOME%/lib目錄下的jar包和類
或者被-Xbootclasspath參數指定的路徑中的所有類
。- 擴展類加載器
ExtensionClassLoader
主要負責加載目錄 %JRE_HOME%/lib/ext 目錄下的jar包和類
,或被 java.ext.dirs 系統變量
所指定的路徑下的jar包。- 應用程序類加載器
ApplicationClassLoader
面向我們用戶的加載器,負責加載當前應用classpath
下的所有jar包和類。- 用戶自定義類加載
全局委託機制(雙親委派機制)
- 系統中的
ClassLoder
在協同工作的時候會默認使用 雙親委派模型 。即在類加載的時候,系統會首先判斷當前類是否被加載過。已經被加載的類會直接返回,否則纔會嘗試加載。加載的時候,首先會把該請求委派該父類加載器的 loadClass() 處理
,因此所有的請求最終都應該傳送到頂層的啓動類加載器BootstrapClassLoader
中。當父類加載器無法處理時,才由自己來處理。當父類加載器爲null時,會使用啓動類加載器BootstrapClassLoader
作爲父類加載器。ClassLoaderDemo.class.getClassLoader().getParent().getParent()
AppClassLoader
的父類加載器爲ExtClassLoader
ExtClassLoader
的父類加載器爲null,null並不代表ExtClassLoader
沒有父類加載器,而是BootstrapClassLoader
。自底向上檢查類是否被加載,自頂向下嘗試加載類。
相同的類文件被不同的類加載器加載的結果是不是一樣的?
- 是兩個不同的類,JVM區分不同的類的方式不僅僅根據類型,這樣結果也保證了Java的核心API(應用程序接口)不被篡改。如果沒有使用雙親委派模型,而是每個類加載器加載自己的話就會出現一些問題,比如我們編寫一個稱爲
java.lang.Object
類的話,那麼程序運行的時候,系統就會出現多個不同的 Object 類。單列的通過不同的類加載器是不是相同的?
- 單列的對象被不同的類加載也是兩個不同的對象?是因爲…
如果我們不想用雙親委派模型怎麼辦?
- 爲了避免雙親委託機制,我們可以自己定義一個類加載器,然後重載
loadClass()
即可。
loadClass()
源碼分析(瞭解)private final ClassLoader parent; protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先,檢查請求的類是否已經被加載過 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) {//父加載器不爲空,調用父加載器 loadClass()方法處理 c = parent.loadClass(name, false); } else {//父加載器爲空,使用啓動類加載器 BootstrapClassLoader 加載 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { //拋出異常說明父類加載器無法完成加載請求 } if (c == null) { long t1 = System.nanoTime(); //自己嘗試加載 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
1.3 Java多線程的基礎類?
創建一個線程對象的三種方式?(使用場景很少,一般使用線程池)
通過繼承
Thread類
創建線程對象//繼承自Thread類 public class CreateThreadClass extends Thread{ @Override//重寫其run()---run()方法稱爲執行體 public void run() { super.run(); System.out.println("通過繼承Thread類創建線程"); } } //測試 class TestThreadClass{ public static void main(String[] args) { CreateThreadClass myThread = new CreateThreadClass(); myThread.start(); } }
實現
Runnable接口
的方式,推薦此種方法開發多線程,因爲Java單繼承但是可以實現多個接口public class CreateThreadInterface implements Runnable{ @Override public void run() { System.out.println("實現Runnable接口的方式創建線程"); } } class TestThreadInterface{ public static void main(String[] args) { Runnable runnable = new CreateThreadInterface(); // 注意創建線程的方式 Thread thread = new Thread(runnable); thread.start(); } }
實現Callable接口
採用實現Runnable、Callable接口的方式創建多線程的優劣勢?
- 優勢是:線程類只是實現了Runnable接口或Callable接口,還可以繼承其他類。在這種方式下,多個線程可以共享同一個target對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。
- 劣勢是:
編程稍微複雜,如果要訪問當前線程,則必須使用Thread.currentThread()
方法。使用繼承Thread類的方式創建多線程時的優劣勢?
- 優勢是:編寫簡單,如果需要訪問當前線程,則無需使用
Thread.currentThread()
方法,直接使用this即可獲得當前線程。- 劣勢是:線程類已經繼承了Thread類,所以不能再繼承其他父類。
線程池的底層線程實現?線程池的使用有兩種方式?
**
ExecutorService
**是真正的線程池接口
Executor
是線程池的頂級接口,只是一個執行線程的工具,只提供一個execute(Runnable command)的方法,真正的線程池接口是ExecutorService
**
AbstractExecutorService
實現了ExecutorService
**接口,實現了其中大部分的方法(有沒有實現的方法,所以被聲明爲Abstract)
ThreadPoolExecutor
,繼承了**AbstractExecutorService
,是ExecutorService
**的默認實現不提倡我們直接使用**
ThreadPoolExecutor
**,而是使用Executors類中提供的幾個靜態方法來創建線程池ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5)); threadPoolExecutor.execute(new Runnable() { @Override public void run() { } });
**
ExecutorService
**是真正的線程池接口,所以我們在通過Executors創建各種線程時,都是採用下述代碼所示的方式ExecutorService executorService1 = Executors.newFixedThreadPool(3); ExecutorService executorService2 = Executors.newSingleThreadExecutor(); ExecutorService executorService3 = Executors.newCachedThreadPool(); ExecutorService executorService4 = Executors.newScheduledThreadPool(3); executorService1.execute(new Runnable() { @Override public void run() { } });
1.4 鎖是怎麼實現的?
悲觀鎖(
synchronized
和 **ReentrantLock
**使用悲觀鎖的思想實現的)和樂觀鎖(版本號機制和CAS算法實現)思想。
1.4.1 樂觀鎖
- 版本號機制:數據表中添加數據的版本號version字段表示數據被修改的次數,數據被修改時version值會+1;
- CAS(比較於交換–無鎖算法)
1. **ABA
**問題;
2. 循環時間長開銷大;
3. CAS只對單個共享變量有效,當操作涉及跨多個共享變量時CAS無效;
1.4.2 悲觀鎖
synchronized
- JDK1.6之前:synchronized屬於重量級鎖,效率低下,因爲監視器鎖是依賴於底層的系統Mutex Lock來實現的,java的線程是映射到操作系統的原生線程之上的。(掛起和喚醒一個線程)需要操作系統實現線程時需要從用戶態切換到內核態,耗時,效率低;
- JDK1.6開始:Java官方從JVM層面對synchronized做了較大的優化(自旋鎖,適應性自旋鎖,鎖消除…);
synchronized加鎖的方式
- 修飾實例方法–給對象實例加鎖?
- 修飾靜態方法–給類加鎖 通過ACC_synchronized標示
- 修飾代碼塊–實現使用monitorenter和monitorexit指令(每個java對象的對象頭中 monitor 1 0);
synchronized 和 ReentrantLock的區別?
- 兩者都是可重入鎖
- JVM層面和API層面(ReentrantLock lock() unlock() 配合着try/finally)
- ReentrantLock 比synchronized 增加一些高級功能:
- 等待可中斷
- 可實現公平鎖
- 可實現選擇性通知
1.5 線程之間的切換是多線程層面(上下文切換),生產者消費者模式?
生產者消費者問題是線程模型中的經典問題:生產者和消費者在同一時間段內共用同一存儲空間,生產者向空間裏生產數據,而消費者取走數據。
阻塞隊列就相當於一個緩衝區,平衡了生產者和消費者的處理能力。這個阻塞隊列就是用來給生產者和消費者解耦的。
wait和notifiy()
和信號量來 實現生產者方式。
1.6 說一下你知道的設計模式?
1.6.1 單列模式servlet,連接池
懶漢式:就是不在系統加載時就創建類的單例,而是在第一次使用實例的時候再創建。
public class Singleton{ private static Singleton singleton = null; private Singleton() { } public synchronized static Singleton newInstance() { if (singleton == null) { singleton= new Singleton(); } return singleton; } }
餓漢式:在加載類的時候就會創建類的單例,並保存在類中。
public class EHanDanli { private static EHanDanli dl = new EHanDanli(); private EHanDanli(){ } public static EHanDanli getInstance(){ return dl; } }
雙重加鎖機制:懶漢形式的加強版,將synchronized關鍵字移到了**
newInstance
**方法裏面,同時將singleton對象加上volatile關鍵字,這種方式既可以避免多線程問題,又不會降低程序的性能。但volatile關鍵字也有一些性能問題,不建議大量使用。public class Singleton { private volatile static Singleton singleton; private Singleton() { } public static Singleton newInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
內部靜態類:內部類的機制使得單例對象可以延遲加載,同時內部類相當於是外部類的靜態部分,所以可以通過
jvm
來保證其線程安全。public class Singleton { private static class SingletonHolder { private static Singleton singleton = new Singleton(); } private Singleton() { } public static Singleton newInstance() { return SingletonHolder.singleton; } }
1.6.2 工廠模式
- 簡單工廠模式(靜態工廠模式)
- 簡單工廠模式的實質是由一個工廠類根據傳入的參數,動態決定應該創建哪一個產品類。
- 問題: 對於新產品的加入,都要在工廠類中增加相應的創建業務邏輯,不符合開閉原則(對擴展開放,對修改封閉)
- 工廠方法模式
- 去掉了靜態工廠的靜態屬性,使得它可以被子類繼承,這樣簡單工廠集中在工廠方法上的壓力可以由工廠模式中不同的子類來承擔
- 問題:工廠方法模式,彷彿已經很完美的對創建的對象進行了封裝,但當產品種類非常多時,會出現大量的工廠對象
- 抽象工廠模式:
- 抽象工廠模式是工廠模式的升級版,用於創建一組相關或者相互依賴的對象,每一個具體的工廠是生產一個對應的產品族的。
1.6.3 裝飾着模式
- 動態的將責任附加到對象身上,若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
- 動態附加
- 擴展功能
- 比繼承更優
- 裝飾者模式的應用:IO流
- Java的IO流是踐行裝飾者模式的典型實踐。以InputStream字節流爲例。首先,瞭解一下基本的繼承關係(但實際上字節流的類複雜得多,以下只列出了一部分)InputStream是最上層的父類,表示字節流FileInputStream是一個具體類,用來對文件進行讀取BufferedInputStream是字節緩衝流,主要是爲了減少I/O操作的次數,提高效率。學習Java基礎的過程中,經常會使用BufferedInputStream進行文件讀取
1.6.4 代理模式
- 代理模式它的特徵是代理類與委託類有同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務。
- 動態代理模式:
- 動態代理機制:首先通過**
newProxyInstance
**方法獲取代理類實例,而後我們便可以通過這個代理類實例調用代理類的方法,對代理類的方法的調用實際上都會調用中介類(調用處理器)的invoke方法,在invoke方法中我們調用委託類的相應方法,並且可以添加自己的處理邏輯。- 靜態代理和動態代理的區別?
- 代理類對象的本身並不提供服務,而是由委託對象的方法提供服務
- 代理模式通過代理將具體的實現與調用的方法解耦,面向接口將具體的實現隱藏在內部
- 靜態代理類在程序運行之前它的字節碼文件已經存在了,動態代理在程序運行中利用反射動態的創建
- 靜態代理事先知道自己要代理什麼,而動態代理不知道
- 靜態代理只能爲特定的接口服務,要想代理多個接口,必須要創建多個代理類
1.7 Spring框架的核心是什麼?
- spring的核心IOC和AOP
- spring裏面IOC的核心生產的核心是bean
- spring的作用範圍:在Spring中,可以在元素的scope屬性裏設置bean的作用域,以決定這個bean是單實例的還是多實例的。
- singleton :單例的,默認作用域; 特點:單例的bean在
ioc
容器被創建的時候就會創建。- prototype:多例的。
- 特點:多例的bean不會在
ioc
容器創建的時候創建對象。 bean的對象會被創建多次。每次通過getBean方法獲取對象的時候都會返回一個新的bean對象- request:一次請求期間只創建一次bean對象
- session:一次會話期間只會創建一個bean對象。