Java開發面試題匯總 -- 精選版(附答案)


最近事情太多,沒太時間寫公眾號。今天抽空再整理整理面試中的那點事吧,幫助那些正在找工作或想跳槽找工作的兄弟姐妹們。

前面我己寫過多篇推文,相信關注此公眾號的伙伴們已經了解掌握了不少。從目前流行的開發技術、常見的面試問題以及問題的答案都已經寫的特別清楚了,今天我在之前的基礎上,再基於面個人的經驗繼續精選一些面試題給大家閱讀參考。

 

1,Java的反射

Java 反射機制是在運行狀態中,對於任意一個類,都能夠獲得這個類的所有屬性和方法,對於任意一個對象都能夠調用它的任意一個屬性和方法。這種在運行時動態的獲取信息以及動態調用對象的方法的功能稱為Java 的反射機制。反射也就是動態加載對象,並對對象進行剖析。Class 類與java.lang.reflect 類庫一起對反射的概念進行了支持,該類庫包含了Field,Method,Constructor類(每個類都實現了Member 接口)。

反射的作用:

1),在運行時判斷任意一個對象所屬的類

2),在運行時構造任意一個類的對象

3),在運行時判斷任意一個類所具有的成員變量和方法

4),在運行時調用任意一個對象的方法

 

優點:可以動態的創建對象和編譯,最大限度發揮了java的靈活性。

缺點:對性能有影響。使用反射基本上一種解釋操作,告訴JVM我們要做什么並且滿足我們的要求,這類操作總是慢於直接執行java代碼。

如何使用java的反射? 

a. 通過一個全限類名創建一個對象

1) Class.forName("全限類名"); 

2) 類名.class; 獲取Class<?> clazz 對象  

3) 對象.getClass();   

b. 獲取構造器對象,通過構造器new出一個對象 

1) Clazz.getConstructor([String.class]); 

2) Con.newInstance([參數]); 

c. 通過class對象創建一個實例對象(就相當與new類名()無參構造器) 

1) Clazz.newInstance();

d. 通過class對象獲得一個屬性對象

1) Field c=clazz.getFields(); 獲得某個類的所有的公共(public)的字段,包括父類中的字段。

2) Field c=clazz.getDeclaredFields(); 獲得某個類的所有聲明的字段,即包括public、private和proteced,但是不包括父類的申明字段。

e、通過class對象獲得一個方法對象 

1)Clazz.getMethod("方法名",class…..parameaType);(只能獲取公共的) 

2)Clazz.getDeclareMethod("方法名"); (獲取任意修飾方法,不能執行私有) 

3) M.setAccessible(true);(讓私有的方法可以執行) 

f. 讓方法執行 

Method.invoke(obj實例對象,obj可變參數);

2,Spring的源碼分析及實現

Spring是一個開源的、輕量級的Java 開發框架。Spring使用的是基本的JavaBean來完成以前只可能由EJB完成的事情。然而,Spring的用途不僅僅限於服務器端的開發。從簡單性、可測試性和松耦合性角度而言,絕大部分Java應用都可以從Spring中受益。Spring的核心是控制反轉(IOC)和面向切面(AOP)。

什么是IOC?

控制反轉(IOC)就是把原先我們代碼里面需要實現的對象創建、依賴的代碼,反轉給容器來幫忙實現。那么必然的我們需要創建一個容器,同時需要一種描述來讓容器知道需要創建的對象與對象的關系。這個描述最具體表現就是我們可配置的文件。IOC容器最主要是完成了對象的創建和依賴的管理注入等。

Spring IOC體系結構:

Spring Bean的創建是典型的工廠模式,這一系列的Bean工廠,也即IOC容器為開發者管理對象間的依賴關系提供了很多便利和基礎服務,在Spring中有許多的IOC容器的實現供用戶選擇和使用,其相互關系如下:

BeanFactory作為最頂層的一個接口類,它定義了IOC容器的基本功能規范,BeanFactory 有三個子類:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory。IOC容器接口BeanFactory:

public interface BeanFactory { //對FactoryBean的轉義定義,因為如果使用bean的名字檢索FactoryBean得到的對象是工廠生成的對象,    //如果需要得到工廠本身,需要轉義     String FACTORY_BEAN_PREFIX = "&"; //根據bean的名字,獲取在IOC容器中得到bean實例     Object getBean(String name) throws BeansException; //根據bean的名字和Class類型來得到bean實例,增加了類型安全驗證機制。    Object getBean(String name, Class requiredType) throws BeansException;    //提供對bean的檢索,看看是否在IOC容器有這個名字的bean    boolean containsBean(String name); //根據bean名字得到bean實例,並同時判斷這個bean是不是單例     boolean isSingleton(String name) throws NoSuchBeanDefinitionException;    //得到bean實例的Class類型     Class getType(String name) throws NoSuchBeanDefinitionException; //得到bean的別名,如果根據別名檢索,那么其原名也會被檢索出來     String[] getAliases(String name); }

在BeanFactory里只對IOC容器的基本行為作了定義,根本不關心你的bean是如何定義怎樣加載的。正如我們只關心工廠里得到什么的產品對象,至於工廠是怎么生產這些對象的,這個基本的接口不關心。

而要知道工廠如何產生對象,就需要看具體的IOC容器實現,spring提供了許多IOC容器的實現。如XmlBeanFactory,ClasspathXmlApplicationContext等。其中XmlBeanFactory就是針對最基本的ioc容器的實現,這個IOC容器可以讀取XML文件定義的BeanDefinition(XML文件中對bean的描述),如果說XmlBeanFactory是容器中的屌絲,ApplicationContext應該算容器中的高富帥了。

ApplicationContext是Spring提供的一個高級的IoC容器,它除了能夠提供IoC容器的基本功能外,還為用戶提供了以下的附加服務。從ApplicationContext接口的實現,我們看出其特點:

1, 支持信息源,可以實現國際化。(實現MessageSource接口);

2, 訪問資源。(實現ResourcePatternResolver接口,這個后面要講);

3, 支持應用事件。(實現ApplicationEventPublisher接口);

Spring AOP

AOP即面向切面編程。可以說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。OOP引入封裝、繼承、多態等概念來建立一種對象層次結構,用於模擬公共行為的一個集合。AOP技術利用一種稱為"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行為封裝到一個可重用模塊,並將其命名為"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重復代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。

AOP把軟件系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關系不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處基本相似,比如權限認證、日志、事物。AOP的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。

Spring對AOP的支持

Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關系也由IOC容器負責管理。因此,AOP代理可以直接使用容器中的其它bean實例作為目標,這種關系可由IOC容器的依賴注入提供。Spring創建代理的規則為:

   1)、默認使用Java動態代理來創建AOP代理,這樣就可以為任何接口實例創建代理了;

   2)、當需要代理的類不是代理接口的時候,Spring會切換為使用CGLIB代理,也可強制使用CGLIB;

AOP編程其實是很簡單的事情,縱觀AOP編程,程序員只需要參與三個部分:

    1)、定義普通業務組件

    2)、定義切入點,一個切入點可能橫切多個業務組件

    3)、定義增強處理,增強處理就是AOP框架為普通業務組件織入的處理動作

所以進行AOP編程的關鍵就是定義切入點和定義增強處理,一旦定義了合適的切入點和增強處理,AOP框架將自動生成AOP代理,即:代理對象的方法=增強處理+被代理對象的方法。

下面給出一個Spring AOP的.xml文件模板,名字叫做aop.xml,之后的內容都在aop.xml上進行擴展:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"></beans>

 

3,動態代理(cglib 與 JDK)

JDK動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。而cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。

   1、如果目標對象實現了接口,默認情況下會采用JDK的動態代理實現AOP;

   2、如果目標對象實現了接口,可以強制使用CGLIB實現AOP;

   3、如果目標對象沒有實現了接口,必須采用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換;

如何強制使用CGLIB實現AOP?

    1) 添加CGLIB庫,SPRING_HOME/cglib/*.jar

  2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

JDK動態代理和CGLIB字節碼生成的區別?

    1)  JDK動態代理只能對實現了接口的類生成代理,而不能針對類;

    2)  CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,因為是繼承,所以該類或方法最好不要聲明成final;

4,分布式鎖的實現方案

分布式鎖一般有三種實現方式:

1. 數據庫鎖;

2. 基於Redis的分布式鎖;

3. 基於ZooKeeper的分布式鎖。

基於數據庫鎖實現

方案1:

CREATE TABLE `methodLock` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '鎖定的方法名', `desc` varchar(1024) NOT NULL DEFAULT '備注信息', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存數據時間,自動生成', PRIMARY KEY (`id`), UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='鎖定中的方法';

當我們想要鎖住某個方法時,執行以下SQL:

insert into methodLock(method_name,desc) values (`method_name`,`desc`);

:想要釋放鎖的話,需要執行以下Sql:

delete from methodLock where method_name ='method_name';

 

方案2:

CREATE TABLE `method_lock` (  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',  `method_name` varchar(64) NOT NULL COMMENT '鎖定的方法名',  `state` tinyint NOT NULL COMMENT '1:未分配;2:已分配',  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `version` int NOT NULL COMMENT '版本號', `PRIMARY KEY (`id`), UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='鎖定中的方法';

獲取鎖:

 select id, method_name, state,version from method_lock where state=1 and method_name='methodName';

占有鎖:

update t_resoure set state=2, version=2, update_time=now() where method_name='methodName' and state=1 and version=2;

缺點:

       1、這把鎖強依賴數據庫的可用性,數據庫是一個單點,一旦數據庫掛掉,會導致業務系統不可用。

       2、這把鎖沒有失效時間,一旦解鎖操作失敗,就會導致鎖記錄一直在數據庫中,其他線程無法再獲得到鎖。

       3、這把鎖只能是非阻塞的,因為數據的insert操作,一旦插入失敗就會直接報錯。沒有獲得鎖的線程並不會進入排隊隊列,要想再次獲得鎖就要再次觸發獲得鎖操作。

       4、這把鎖是非重入的,同一個線程在沒有釋放鎖之前無法再次獲得該鎖。因為數據中數據已經存在了。

解決方案:

        1、數據庫是單點?搞兩個數據庫,數據之前雙向同步。一旦掛掉快速切換到備庫上。

        2、沒有失效時間?只要做一個定時任務,每隔一定時間把數據庫中的超時數據清理一遍。

        3、非阻塞的?搞一個while循環,直到insert成功再返回成功。

        4、非重入的?在數據庫表中加個字段,記錄當前獲得鎖的機器的主機信息和線程信息,那么下次再獲取鎖的時候先查詢數據庫,如果當前機器的主機信息和線程信息在數據庫可以查到的話,直接把鎖分配給他就可以了。

基於redis的分布式鎖

實現方案:

 

try{ lock = redisTemplate.opsForValue().setIfAbsent(lockKey, LOCK); logger.info("cancelCouponCode是否獲取到鎖:"+lock); if (lock) { // TODO redisTemplate.expire(lockKey,1, TimeUnit.MINUTES); //成功設置過期時間 return res; }else { logger.info("cancelCouponCode沒有獲取到鎖,不執行任務!"); }}finally{ if(lock){  redisTemplate.delete(lockKey); logger.info("cancelCouponCode任務結束,釋放鎖!");  }else{ logger.info("cancelCouponCode沒有獲取到鎖,無需釋放鎖!"); }}

缺點:

在這種場景(主從結構)中存在明顯的競態:
    客戶端A從master獲取到鎖,
    在master將鎖同步到slave之前,master宕掉了。
    slave節點被晉級為master節點,
    客戶端B取得了同一個資源被客戶端A已經獲取到的另外一個鎖。安全失效!

基於zookeeper實現

方案:可以直接使用zookeeper第三方庫Curator客戶端,這個客戶端中封裝了一個可重入的鎖服務。

Curator提供的InterProcessMutex是分布式鎖的實現。acquire方法用戶獲取鎖,release方法用於釋放鎖。

缺點:

   性能上可能並沒有緩存服務那么高。因為每次在創建鎖和釋放鎖的過程中,都要動態創建、銷毀瞬時節點來實現鎖功能。ZK中創建和刪除節點只能通過Leader服務器來執行,然后將數據同不到所有的Follower機器上。

三種方案的比較

上面幾種方式,哪種方式都無法做到完美。就像CAP一樣,在復雜性、可靠性、性能等方面無法同時滿足,所以,根據不同的應用場景選擇最適合自己的才是王道。

從理解的難易程度角度(從低到高):數據庫 > 緩存 > Zookeeper

從實現的復雜性角度(從低到高):Zookeeper >= 緩存 > 數據庫

從性能角度(從高到低):緩存 > Zookeeper >= 數據庫

從可靠性角度(從高到低):Zookeeper > 緩存 > 數據庫

5,Java設計模式部分搞笑解讀

1、工廠模式,

搞笑解讀:—追MM少不了請吃飯了,麥當勞的雞翅和肯德基的雞翅都是MM愛吃的東西,雖然口味有所不同,但不管你帶MM去麥當勞或肯德基,只管向服務員說“來四個雞翅”就行了。麥當勞和肯德基就是生產雞翅的Factory。
工廠模式:客戶類和工廠類分開。消費者任何時候需要某種產品,只需向工廠請求即可。消費者無須修改就可以接納新產品。缺點是當產品修改時,工廠類也要做相應的修改。如:如何創建及如何向客戶端提供。

2,單例模式

搞笑解讀:俺有6個漂亮的老婆,她們的老公都是我,我就是我們家里的老公Sigleton,她們只要說道“老公”,都是指的同一個人,那就是我(白日美夢)。

單例模式確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例單例模式。單例模式只應在有真正的“單一實例”的需求時才可使用。

3,適配器模式

搞笑解讀:在一次聚會碰到了一個很漂亮的烏克蘭MM,可不會說烏克蘭語,她也不會說普通話,只好求助於會烏克蘭語的朋友,他作為我們之間的Adapter,讓我們可以相互交談了(也不知道他會不會耍我)。
適配器模式:把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口原因不匹配而無法一起工作的兩個類能夠一起工作。適配類可以根據參數返還一個合適的實例給客戶端。

4,代理模式

搞笑解讀:跟MM在網上聊天,一開頭總是“hi,你好”,“你從哪兒來呀?”“你多大了?”“身高多少呀?”這些話,真煩人,寫個程序做為我的Proxy吧,凡是接收到這些話都設置好了自動的回答,接收到其他的話時再通知我回答,酷吧。

代理模式:代理模式給某一個對象提供一個代理對象,並由代理對象控制對源對象的引用。代理就是一個人或一個機構代表另一個人或者一個機構采取行動。某些 情況下,客戶不想或者不能夠直接引用一個對象,代理對象可以在客戶和目標對象直接起到中介的作用。客戶端分辨不出代理主題對象與真實主題對象。代理模式可 以並不知道真正的被代理對象,而僅僅持有一個被代理對象的接口,這時候代理對象不能夠創建被代理對象,被代理對象必須有系統的其他角色代為創建並傳入。

領取福利啦,關於Java的學習資料!感興趣的開發者請關注此公眾號,關注成功后發送“Java學習”關鍵詞,你就可以獲得資源下載鏈接!之后也會有更多資料持續免費送出。


免責聲明!

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



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