Java面試大全(2020年版)201-300

目錄

201. 刪除_你們怎麼處理redis緩存的數據,怎麼刪除的

redis緩存的數據有一些是常駐緩存的,當數據庫中數據有變化時做數據同步。
有一些緩存是設置有效期的,當緩存到期後會自動刪除。刪除redis緩存使用del或者hdel命令。[備註:hdel,hashKey delete]

202. redis的事務

  • redis事務:可以一次執行多個命令,本質是一組命令的集合。一個事務中的所有命令都會序列化,按順序地串行化執行而不會被其它命令插入,不許加塞.
  • redis事務能幹嘛:一個隊列中,一次性、順序性、排他性的執行一系列命令.
    [備註: ACID,原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)]
  • redis監控:鎖的介紹
    在MySQL中我們都知道有行鎖和表鎖的概念,所謂的行鎖也就是把我需要改的那一行給鎖住,不讓其他的事務去修改;而表鎖就是在修改一張表的時候把整張表都鎖住,不讓其他的事務修改,所以行鎖的效率比表鎖的概念更高,那麼在redis也存在鎖的概念
    • 樂觀鎖(Optimistic Lock): 顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,樂觀鎖策略:提交版本必須大於記錄當前版本才能執行更新
    • 悲觀鎖(Pessimistic Lock): 顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關係型數據庫裏邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖.[備註: 在Java中,synchronized的思想也是悲觀鎖]
    • CAS(Check And Set):
  • redis事務三階段:
    • 開啓:以MULTI開始一個事務
    • 入隊:將多個命令入隊到事務中,接到這些命令並不會立即執行,而是放到等待執行的事務隊列裏面
    • 執行:由EXEC命令觸發事務(execute,執行)
  • redis事務三大特性:
    • 單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。
    • 沒有隔離級別的概念:隊列中的命令沒有提交之前都不會實際的被執行,因爲事務提交前任何指令都不會被實際執行,也就不存在”事務內的查詢要看到事務裏的更新,在事務外查詢不能看到”這個讓人萬分頭痛的問題
    • 不保證原子性:redis同一個事務中如果有一條命令執行失敗,其後的命令仍然會被執行,沒有回滾

203. 什麼是ES?

es是一個高擴展、開源的全文檢索和分析引擎,它可以準實時地快速存儲、搜索、分析海量的數據。

204. 爲什麼要使用到ES?

因爲在我們商城中的數據,將來會非常多,所以採用以往的模糊查詢,模糊查詢前置配置,會放棄索引,導致商品查詢是全表掃面,在百萬級別的數據庫中,效率非常低下,而我們使用ES做一個全文索引,我們將經常查詢的商品的某些字段,比如說商品名,描述、價格還有id這些字段我們放入我們索引庫裏,可以提高查詢速度。

205. Elasticsearch是如何實現Master選舉的?

  • Elasticsearch的選主是ZenDiscovery模塊負責的,主要包含Ping(節點之間通過這個RPC來發現彼此)和Unicast(單播模塊包含一個主機列表以控制哪些節點需要ping通)這兩部分;
  • 對所有可以成爲master的節點(node.master: true)根據nodeId字典排序,每次選舉每個節點都把自己所知道節點排一次序,然後選出第一個(第0位)節點,暫且認爲它是master節點。
  • 如果對某個節點的投票數達到一定的值(可以成爲master節點數n/2+1)並且該節點自己也選舉自己,那這個節點就是master。否則重新選舉一直到滿足上述條件。
    補充:master節點的職責主要包括集羣、節點和索引的管理,不負責文檔級別的管理;data節點可以關閉http功能。

206. Elasticsearch中的節點(比如共20個),其中的10個選了一個master,另外10個選了另一個master,怎麼辦?

  • 當集羣master候選數量不小於3個時,可以通過設置最少投票通過數量(discovery.zen.minimum_master_nodes)超過所有候選節點一半以上來解決腦裂問題;
  • 當候選數量爲兩個時,只能修改爲唯一的一個master候選,其他作爲data節點,避免腦裂問題。

207. 客戶端在和集羣連接時,如何選擇特定的節點執行請求的?

TransportClient利用transport模塊遠程連接一個elasticsearch集羣。它並不加入到集羣中,只是簡單的獲得一個或者多個初始化的transport地址,並以 輪詢 的方式與這些地址進行通信。

208. 詳細描述一下Elasticsearch索引文檔的過程。

協調節點默認使用文檔ID參與計算(也支持通過routing),以便爲路由提供合適的分片。
shard = hash(document_id) % (num_of_primary_shards)

  • 當分片所在的節點接收到來自協調節點的請求後,會將請求寫入到Memory Buffer,然後定時(默認是每隔1秒)寫入到Filesystem Cache,這個從Momery Buffer到Filesystem Cache的過程就叫做refresh;
  • 當然在某些情況下,存在Momery Buffer和Filesystem Cache的數據可能會丟失,ES是通過translog的機制來保證數據的可靠性的。其實現機制是接收到請求後,同時也會寫入到translog中,當Filesystem cache中的數據寫入到磁盤中時,纔會清除掉,這個過程叫做flush;
  • 在flush過程中,內存中的緩衝將被清除,內容被寫入一個新段,段的fsync將創建一個新的提交點,並將內容刷新到磁盤,舊的translog將被刪除並開始一個新的translog。
  • flush觸發的時機是定時觸發(默認30分鐘)或者translog變得太大(默認爲512M)時;
    補充:關於Lucene的Segement:
    • Lucene索引是由多個段組成,段本身是一個功能齊全的倒排索引。
    • 段是不可變的,允許Lucene將新的文檔增量地添加到索引中,而不用從頭重建索引。
    • 對於每一個搜索請求而言,索引中的所有段都會被搜索,並且每個段會消耗CPU的時鐘周、文件句柄和內存。這意味着段的數量越多,搜索性能會越低。
    • 爲了解決這個問題,Elasticsearch會合並小段到一個較大的段,提交新的合併段到磁盤,並刪除那些舊的小段。

209. 詳細描述一下Elasticsearch更新和刪除文檔的過程。

  • 刪除和更新也都是寫操作,但是Elasticsearch中的文檔是不可變的,因此不能被刪除或者改動以展示其變更;
  • 磁盤上的每個段都有一個相應的.del文件。當刪除請求發送後,文檔並沒有真的被刪除,而是在.del文件中被標記爲刪除。該文檔依然能匹配查詢,但是會在結果中被過濾掉。當段合併時,在.del文件中被標記爲刪除的文檔將不會被寫入新段。
  • 在新的文檔被創建時,Elasticsearch會爲該文檔指定一個版本號,當執行更新時,舊版本的文檔在.del文件中被標記爲刪除,新版本的文檔被索引到一個新段。舊版本的文檔依然能匹配查詢,但是會在結果中被過濾掉。

210. 詳細描述一下Elasticsearch搜索的過程。

  • 搜索被執行成一個兩階段過程,我們稱之爲 Query Then Fetch;
  • 在初始查詢階段時,查詢會廣播到索引中每一個分片拷貝(主分片或者副本分片)。 每個分片在本地執行搜索並構建一個匹配文檔的大小爲 from + size 的優先隊列。PS:在搜索的時候是會查詢Filesystem Cache的,但是有部分數據還在Memory Buffer,所以搜索是近實時的。
  • 每個分片返回各自優先隊列中 所有文檔的 ID 和排序值 給協調節點,它合併這些值到自己的優先隊列中來產生一個全局排序後的結果列表。
  • 接下來就是 取回階段,協調節點辨別出哪些文檔需要被取回並向相關的分片提交多個 GET 請求。每個分片加載並 豐富 文檔,如果有需要的話,接着返回文檔給協調節點。一旦所有的文檔都被取回了,協調節點返回結果給客戶端。
    補充:
    Query Then Fetch的搜索類型在文檔相關性打分的時候參考的是本分片的數據,這樣在文檔數量較少的時候可能不夠準確,DFS Query Then Fetch增加了一個預查詢的處理,詢問Term和Document frequency,這個評分更準確,但是性能會變差。

211. 爲什麼使用索引工具查詢快

(使用了倒排索引的技術,大致介紹一下倒排索引,還有索引庫中的詞都是按照順序排列,後期根據一個關鍵詞查詢的時候,可以利用類似折半查找的算法,查詢效率非常高)
使用了倒排索引的技術,一般我們都是這樣定義id 關鍵詞,倒排索引是關鍵詞 id正好相反,使用索引工具進行查詢時,首先得到關鍵詞,建立倒排索引表,關鍵詞----索引列表包含該關鍵詞所在的文檔的id、在該文檔中出現的次數、在該文檔中出現的位置信息,這種由屬性值確定記錄的位置的方式成爲倒排索引。還有索引庫中的詞都是按照順序排列 ,後期根據一個關鍵詞查詢的時候,可以利用類似折半查找的算法,查詢效率非常高;

212. es集羣的腦裂問題

es集羣有可能會出現腦裂問題,原因主要有兩個:

  • 如果集羣中節點不在同一個網段有可能是網絡延遲造成的
  • 如果集羣中的節點在同一個網段,有可能是主節點負載太大造成的
    解決方案主要有兩種:
    • 把主從節點的職責分離,設置三個儲備主節點,node.master=true,node.data=false,從節點只存儲數據,node.master=false,node.data=true
    • 增加延遲時間:將儲備主節點數最小設爲n/2+1個

213. 詳細描述一下Elasticsearch更新和刪除文檔的過程。

  • 刪除和更新也都是寫操作,但是Elasticsearch中的文檔是不可變的,因此不能被刪除或者改動以展示其變更;
  • 磁盤上的每個段都有一個相應的.del文件。當刪除請求發送後,文檔並沒有真的被刪除,而是在.del文件中被標記爲刪除。該文檔依然能匹配查詢,但是會在結果中被過濾掉。當段合併時,在.del文件中被標記爲刪除的文檔將不會被寫入新段。
  • 在新的文檔被創建時,Elasticsearch會爲該文檔指定一個版本號,當執行更新時,舊版本的文檔在.del文件中被標記爲刪除,新版本的文檔被索引到一個新段。舊版本的文檔依然能匹配查詢,但是會在結果中被過濾掉。

214. 在併發情況下,Elasticsearch如果保證讀寫一致?

可以通過版本號使用樂觀併發控制,以確保新版本不會被舊版本覆蓋,由應用層來處理具體的衝突;
另外對於寫操作,一致性級別支持quorum/one/all,默認爲quorum,即只有當大多數分片可用時才允許寫操作。但即使大多數可用,也可能存在因爲網絡等原因導致寫入副本失敗,這樣該副本被認爲故障,分片將會在一個不同的節點上重建。
對於讀操作,可以設置replication爲sync(默認),這使得操作在主分片和副本分片都完成後纔會返回;如果設置replication爲async時,也可以通過設置搜索請求參數_preference爲primary來查詢主分片,確保文檔是最新版本。

215. 如何監控Elasticsearch集羣狀態?

Marvel 讓你可以很簡單的通過 Kibana 監控 Elasticsearch。你可以實時查看你的集羣健康狀態和性能,也可以分析過去的集羣、索引和節點指標。

216. Elasticsearch中的倒排索引是什麼?

倒排索引是搜索引擎的核心。搜索引擎的主要目標是在查找發生搜索條件的文檔時提供快速搜索。倒排索引是一種像數據結構一樣的散列圖,可將用戶從單詞導向文檔或網頁。它是搜索引擎的核心。其主要目標是快速搜索從數百萬文件中查找數據。

217. ElasticSearch中的分片是什麼?

在大多數環境中,每個節點都在單獨的盒子或虛擬機上運行。

  • 索引:在Elasticsearch中,索引是文檔的集合。
  • 分片:因爲Elasticsearch是一個分佈式搜索引擎,所以索引通常被分割成分佈在多個節點上的被稱爲分片的元素。

218. ElasticSearch中的副本是什麼?

一個索引被分解成碎片以便於分發和擴展。副本是分片的副本。一個節點是一個屬於一個集羣的ElasticSearch的運行實例。一個集羣由一個或多個共享相同集羣名稱的節點組成。

219. ElasticSearch中的分析器是什麼?

在ElasticSearch中索引數據時,數據由爲索引定義的Analyzer在內部進行轉換。 分析器由一個Tokenizer和零個或多個TokenFilter組成。編譯器可以在一個或多個CharFilter之前。分析模塊允許您在邏輯名稱下注冊分析器,然後可以在映射定義或某些API中引用它們。
Elasticsearch附帶了許多可以隨時使用的預建分析器。或者,您可以組合內置的字符過濾器,編譯器和過濾器器來創建自定義分析器。

220. 什麼是ElasticSearch中的編譯器?

編譯器用於將字符串分解爲術語或標記流。一個簡單的編譯器可能會將字符串拆分爲任何遇到空格或標點的地方。Elasticsearch有許多內置標記器,可用於構建自定義分析器。

221. Elasticsearch是如何實現高亮?

使用高亮碎片.先創建高亮字段碎片HighlightBuilder.Field對象.然後設置高亮字段Field進行標籤設置(preTags和postTags),最後設置文本前後字節數,fragmentSize。

222. 如何實現搜索排名

當你創建了SearchQuery對象後,你可以對任意字段進行排序,裏面的addSort方法,在給他一個Sort對象即可,如果在嵌套域查詢中.可設置得分情況,在QueryBuilder中的nesterQuery中可設置得分情況。

223. 一個".java"源文件中是否可以包括多個類(不是內部類)?有什麼限制?

可以有多個類,但只能有一個public的類,並且public的類名必須與文件名相一致。

224. Java有沒有goto?

java中的保留字,現在沒有在java中使用。

225. 說說&和&&的區別。

&和&&都可以用作邏輯與的運算符,表示邏輯與(and),當運算符兩邊的表達式的結果都爲true時,整個運算結果才爲true,否則,只要有一方爲false,則結果爲false。
&&還具有短路的功能,即如果第一個表達式爲false,則不再計算第二個表達式,例如,對於if(str != null && !str.equals(“”))表達式,當str爲null時,後面的表達式不會執行,所以不會出現NullPointerException如果將&&改爲&,則會拋出NullPointerException異常。If(x33 & ++y>0) y會增長,If(x33 && ++y>0)不會增長
&還可以用作位運算符,當&操作符兩邊的表達式不是boolean類型時,&表示按位與操作,我們通常使用0x0f來與一個整數進行&運算,來獲取該整數的最低4個bit位,例如,0x31 & 0x0f的結果爲0x01。

226. 在JAVA中如何跳出當前的多重嵌套循環?

在Java中,要想跳出多重循環,可以在外面的循環語句前定義一個標號,然後在裏層循環體的代碼中使用帶有標號的break 語句,即可跳出外層循環。例如,

ok:
	for(int i=0;i<10;i++)	{
		for(int j=0;j<10;j++)		{
			System.out.println(“i=” + i + “,j=” + j);
			if(j == 5) break ok;
		}
	} 

另外,我個人通常並不使用標號這種方式,而是讓外層的循環條件表達式的結果可以受到裏層循環體代碼的控制,例如,要在二維數組中查找到某個數字。

int arr[][] = {{1,2,3},{4,5,6,7},{9}};
boolean found = false;
for(int i=0;i<arr.length && !found;i++)	{
		for(int j=0;j<arr[i].length;j++){
			System.out.println(“i=” + i + “,j=” + j);
			if(arr[i][j]  == 5) {
				found = true;
				break;
			}
		}
	} 

227. switch語句能否作用在byte上,能否作用在long上,能否作用在String上?

在switch(expr1)中,expr1只能是一個整數表達式或者枚舉常量(更大字體),整數表達式可以是int基本類型或Integer包裝類型,由於,byte,short,char都可以隱含轉換爲int,所以,這些類型以及這些類型的包裝類型也是可以的。顯然,long和String類型都不符合switch的語法規定,並且不能被隱式轉換成int類型,所以,它們不能作用於swtich語句中。

228. short s1 = 1; s1 = s1 + 1;有什麼錯? short s1 = 1; s1 += 1;有什麼錯?

對於short s1 = 1; s1 = s1 + 1; 由於s1+1運算時會自動提升表達式的類型,所以結果是int型,再賦值給short類型s1時,編譯器將報告需要強制轉換類型的錯誤。
對於short s1 = 1; s1 += 1;由於 += 是java語言規定的運算符,java編譯器會對它進行特殊處理,因此可以正確編譯。

229. char型變量中能不能存貯一箇中文漢字?爲什麼?

char型變量是用來存儲Unicode編碼的字符的,unicode編碼字符集中包含了漢字,所以,char型變量中當然可以存儲漢字啦。不過,如果某個特殊的漢字沒有被包含在unicode編碼字符集中,那麼,這個char型變量中就不能存儲這個特殊漢字。補充說明:unicode編碼佔用兩個字節,所以,char類型的變量也是佔用兩個字節。

230. 用最有效率的方法算出2乘以8等於幾?

2 << 3,因爲將一個數左移n位,就相當於乘以了2的n次方,那麼,一個數乘以8只要將其左移3位即可,而位運算cpu直接支持的,效率最高,所以,2乘以8等於幾的最效率的方法是2 << 3。

231. 請設計一個一百億的計算器

首先要明白這道題目的考查點是什麼,一是大家首先要對計算機原理的底層細節要清楚、要知道加減法的位運算原理和知道計算機中的算術運算會發生越界的情況,二是要具備一定的面向對象的設計思想。
首先,計算機中用固定數量的幾個字節來存儲的數值,所以計算機中能夠表示的數值是有一定的範圍的,爲了便於講解和理解,我們先以byte 類型的整數爲例,它用1個字節進行存儲,表示的最大數值範圍爲-128到+127。-1在內存中對應的二進制數據爲11111111,如果兩個-1相加,不考慮Java運算時的類型提升,運算後會產生進位,二進制結果爲1,11111110,由於進位後超過了byte類型的存儲空間,所以進位部分被捨棄,即最終的結果爲11111110,也就是-2,這正好利用溢位的方式實現了負數的運算。-128在內存中對應的二進制數據爲10000000,如果兩個-128相加,不考慮Java運算時的類型提升,運算後會產生進位,二進制結果爲1,00000000,由於進位後超過了byte類型的存儲空間,所以進位部分被捨棄,即最終的結果爲00000000,也就是0,這樣的結果顯然不是我們期望的,這說明計算機中的算術運算是會發生越界情況的,兩個數值的運算結果不能超過計算機中的該類型的數值範圍。由於Java中涉及表達式運算時的類型自動提升,我們無法用byte類型來做演示這種問題和現象的實驗,大家可以用下面一個使用整數做實驗的例子程序體驗一下:

int a = Integer.MAX_VALUE;
int b = Integer.MAX_VALUE;
int sum = a + b;
System.out.println(“a=”+a+”,b=”+b+”,sum=”+sum);

先不考慮long類型,由於int的正數範圍爲2的31次方,表示的最大數值約等於210001000*1000,也就是20億的大小,所以,要實現一個一百億的計算器,我們得自己設計一個類可以用於表示很大的整數,並且提供了與另外一個整數進行加減乘除的功能,大概功能如下:
()這個類內部有兩個成員變量,一個表示符號,另一個用字節數組表示數值的二進制數
()有一個構造方法,把一個包含有多位數值的字符串轉換到內部的符號和字節數組中
()提供加減乘除的功能

public class BigInteger {
    int sign;
    byte[] val;

    public Biginteger(String val) {
        sign =;
        val =;
    }

    public BigInteger add(BigInteger other) {

    }

    public BigInteger subtract(BigInteger other) {

    }

    public BigInteger multiply(BigInteger other) {

    }

    public BigInteger divide(BigInteger other) {

    }

}

232. 使用final關鍵字修飾一個變量時,是引用不能變,還是引用的對象不能變?

使用final關鍵字修飾一個變量時,是指引用變量不能變,引用變量所指向的對象中的內容還是可以改變的。例如,對於如下語句:

final StringBuffer a=new StringBuffer("immutable");

執行如下語句將報告編譯期錯誤:

a=new StringBuffer("");

但是,執行如下語句則可以通過編譯:

a.append(" broken!"); 

有人在定義方法的參數時,可能想採用如下形式來阻止方法內部修改傳進來的參數對象:

public void method(final  StringBuffer  param){} 

實際上,這是辦不到的,在該方法內部仍然可以增加如下代碼來修改參數對象:

param.append("a");

233. "=="和equals方法究竟有什麼區別?

操作符專門用來比較兩個變量的值是否相等,也就是用於比較變量所對應的內存中所存儲的數值是否相同,要比較兩個基本類型的數據或兩個引用變量是否相等,只能用操作符。
如果一個變量指向的數據是對象類型的,那麼,這時候涉及了兩塊內存,對象本身佔用一塊內存(堆內存),變量也佔用一塊內存,例如Objet obj = new Object();變量obj是一個內存,new Object()是另一個內存,此時,變量obj所對應的內存中存儲的數值就是對象佔用的那塊內存的首地址。對於指向對象類型的變量,如果要比較兩個變量是否指向同一個對象,即要看這兩個變量所對應的內存中的數值是否相等,這時候就需要用==操作符進行比較。
equals方法是用於比較兩個獨立對象的內容是否相同,就好比去比較兩個人的長相是否相同,它比較的兩個對象是獨立的。例如,對於下面的代碼:

String a=new String("foo");
String b=new String("foo");

兩條new語句創建了兩個對象,然後用a,b這兩個變量分別指向了其中一個對象,這是兩個不同的對象,它們的首地址是不同的,即a和b中存儲的數值是不相同的,所以,表達式ab將返回false,而這兩個對象中的內容是相同的,所以,表達式a.equals(b)將返回true。
在實際開發中,我們經常要比較傳遞進行來的字符串內容是否等,例如,String input = …;input.equals(“quit”),許多人稍不注意就使用
進行比較了,這是錯誤的,隨便從網上找幾個項目實戰的教學視頻看看,裏面就有大量這樣的錯誤。記住,字符串的比較基本上都是使用equals方法。
如果一個類沒有自己定義equals方法,那麼它將繼承Object類的equals方法,Object類的equals方法的實現代碼如下:

boolean equals(Object o){
return this==o;

}
這說明,如果一個類沒有自己定義equals方法,它默認的equals方法(從Object 類繼承的)就是使用操作符,也是在比較兩個變量指向的對象是否是同一對象,這時候使用equals和使用會得到同樣的結果,如果比較的是兩個獨立的對象則總返回false。如果你編寫的類希望能夠比較該類創建的兩個實例對象的內容是否相同,那麼你必須覆蓋equals方法,由你自己寫代碼來決定在什麼情況即可認爲兩個對象的內容是相同的。

234. 靜態變量和實例變量的區別?

在語法定義上的區別:靜態變量前要加static關鍵字,而實例變量前則不加。
在程序運行時的區別:實例變量屬於某個對象的屬性,必須創建了實例對象,其中的實例變量纔會被分配空間,才能使用這個實例變量。靜態變量不屬於某個實例對象,而是屬於類,所以也稱爲類變量,只要程序加載了類的字節碼,不用創建任何實例對象,靜態變量就會被分配空間,靜態變量就可以被使用了。總之,實例變量必須創建對象後纔可以通過這個對象來使用,靜態變量則可以直接使用類名來引用。
例如,對於下面的程序,無論創建多少個實例對象,永遠都只分配了一個staticVar變量,並且每創建一個實例對象,這個staticVar就會加1;但是,每創建一個實例對象,就會分配一個instanceVar,即可能分配多個instanceVar,並且每個instanceVar的值都只自加了1次。

public class VariantTest{
		public static int staticVar = 0; 
		public int instanceVar = 0; 
		public VariantTest(){
			staticVar++;`
			instanceVar++;
			System.out.println(“staticVar=” + staticVar + ”,instanceVar=” + instanceVar);
		}
}

235. 是否可以從一個static方法內部發出對非static方法的調用?

不可以。因爲非static方法是要與對象關聯在一起的,必須創建一個對象後,纔可以在該對象上進行方法調用,而static方法調用時不需要創建對象,可以直接調用。也就是說,當一個static方法被調用時,可能還沒有創建任何實例對象,如果從一個static方法中發出對非static方法的調用,那個非static方法是關聯到哪個對象上的呢?這個邏輯無法成立,所以,一個static方法內部發出對非static方法的調用。

236. Integer與int的區別

int是java提供的8種原始數據類型之一。Java爲每個原始類型提供了封裝類,Integer是java爲int提供的封裝類。int的默認值爲0,而Integer的默認值爲null,即Integer可以區分出未賦值和值爲0的區別,int則無法表達出未賦值的情況,例如,要想表達出沒有參加考試和考試成績爲0的區別,則只能使用Integer。在JSP開發中,Integer的默認爲null,所以用el表達式在文本框中顯示時,值爲空白字符串,而int默認的默認值爲0,所以用el表達式在文本框中顯示時,結果爲0,所以,int不適合作爲web層的表單數據的類型。
在Hibernate中,如果將OID定義爲Integer類型,那麼Hibernate就可以根據其值是否爲null而判斷一個對象是否是臨時的,如果將OID定義爲了int類型,還需要在hbm映射文件中設置其unsaved-value屬性爲0。
另外,Integer提供了多個與整數相關的操作方法,例如,將一個字符串轉換成整數,Integer中還定義了表示整數的最大值和最小值的常量。

237. Math.round(11.5)等於多少? Math.round(-11.5)等於多少?

Math類中提供了三個與取整有關的方法:ceil、floor、round,這些方法的作用與它們的英文名稱的含義相對應,例如,ceil的英文意義是天花板,該方法就表示向上取整,Math.ceil(11.3)的結果爲12,Math.ceil(-11.3)的結果是-11;floor的英文意義是地板,該方法就表示向下取整,Math.ceil(11.6)的結果爲11,Math.ceil(-11.6)的結果是-12;最難掌握的是round方法,它表示“四捨五入”,算法爲Math.floor(x+0.5),即將原來的數字加上0.5後再向下取整,所以,Math.round(11.5)的結果爲12,Math.round(-11.5)的結果爲-11。

238. 請說出作用域public,private,protected,以及不寫時的區別

這四個作用域的可見範圍如下表所示。
說明:如果在修飾的元素上面沒有寫任何訪問修飾符,則表示friendly。

作用域    當前類 同一package 子孫類 其他package
public    √     √          √       √ 
protected  √     √          √      × 
friendly   √     √          ×      × 
private    √     ×          ×      × 

239. Overload和Override的區別。Overloaded的方法是否可以改變返回值的類型?

Overload是重載的意思,Override是覆蓋的意思,也就是重寫。
重載Overload表示同一個類中可以有多個名稱相同的方法,但這些方法的參數列表各不相同(即參數個數或類型不同)。
重寫Override表示子類中的方法可以與父類中的某個方法的名稱和參數完全相同,通過子類創建的實例對象調用這個方法時,將調用子類中的定義方法,這相當於把父類中定義的那個完全相同的方法給覆蓋了,這也是面向對象編程的多態性的一種表現。子類覆蓋父類的方法時,只能比父類拋出更少的異常,或者是拋出父類拋出的異常的子異常,因爲子類可以解決父類的一些問題,不能比父類有更多的問題。子類方法的訪問權限只能比父類的更大,不能更小。如果父類的方法是private類型,那麼,子類則不存在覆蓋的限制,相當於子類中增加了一個全新的方法。
至於Overloaded的方法是否可以改變返回值的類型這個問題,要看你倒底想問什麼呢?這個題目很模糊。如果幾個Overloaded的方法的參數列表不一樣,它們的返回者類型當然也可以不一樣。但我估計你想問的問題是:如果兩個方法的參數列表完全一樣,是否可以讓它們的返回值不同來實現重載Overload。這是不行的,我們可以用反證法來說明這個問題,因爲我們有時候調用一個方法時也可以不定義返回結果變量,即不要關心其返回結果,例如,我們調用map.remove(key)方法時,雖然remove方法有返回值,但是我們通常都不會定義接收返回結果的變量,這時候假設該類中有兩個名稱和參數列表完全相同的方法,僅僅是返回類型不同,java就無法確定編程者倒底是想調用哪個方法了,因爲它無法通過返回結果類型來判斷。
override可以翻譯爲覆蓋,從字面就可以知道,它是覆蓋了一個方法並且對其重寫,以求達到不同的作用。對我們來說最熟悉的覆蓋就是對接口方法的實現,在接口中一般只是對方法進行了聲明,而我們在實現時,就需要實現接口聲明的所有方法。除了這個典型的用法以外,我們在繼承中也可能會在子類覆蓋父類中的方法。在覆蓋要注意以下的幾點:

  • 覆蓋的方法的標誌必須要和被覆蓋的方法的標誌完全匹配,才能達到覆蓋的效果;
  • 覆蓋的方法的返回值必須和被覆蓋的方法的返回一致;
  • 覆蓋的方法所拋出的異常必須和被覆蓋方法的所拋出的異常一致,或者是其子類;
  • 被覆蓋的方法不能爲private,否則在其子類中只是新定義了一個方法,並沒有對其進行覆蓋。
    overload對我們來說可能比較熟悉,可以翻譯爲重載,它是指我們可以定義一些名稱相同的方法,通過定義不同的輸入參數來區分這些方法,然後再調用時,JVM就會根據不同的參數樣式,來選擇合適的方法執行。在使用重載要注意以下的幾點:
  • 在使用重載時只能通過不同的參數樣式。例如,不同的參數類型,不同的參數個數,不同的參數順序(當然,同一方法內的幾個參數類型必須不一樣,例如可以是fun(int,float),但是不能爲fun(int,int));
  • 不能通過訪問權限、返回類型、拋出的異常進行重載;
  • 方法的異常類型和數目不會對重載造成影響;
  • 對於繼承來說,如果某一方法在父類中是訪問權限是priavte,那麼就不能在子類對其進行重載,如果定義的話,也只是定義了一個新方法,而不會達到重載的效果。

240. 構造器Constructor是否可被override?

構造器Constructor不能被繼承,因此不能重寫Override,但可以被重載Overload。

241. 接口是否可繼承接口? 抽象類是否可實現(implements)接口? 抽象類是否可繼承具體類(concrete class)? 抽象類中是否可以有靜態的main方法?

接口可以繼承接口。抽象類可以實現(implements)接口,抽象類是否可繼承具體類。抽象類中可以有靜態的main方法。
備註:只要明白了接口和抽象類的本質和作用,這些問題都很好回答,你想想,如果你是java語言的設計者,你是否會提供這樣的支持,如果不提供的話,有什麼理由嗎?如果你沒有道理不提供,那答案就是肯定的了。
只有記住抽象類與普通類的唯一區別就是不能創建實例對象和允許有abstract方法。

242. 寫clone()方法時,通常都有一行代碼,是什麼?

clone 有缺省行爲,super.clone();因爲首先要把父類中的成員複製到位,然後纔是複製自己的成員。

243. 面向對象的特徵有哪些方面

計算機軟件系統是現實生活中的業務在計算機中的映射,而現實生活中的業務其實就是一個個對象協作的過程。面向對象編程就是按現實業務一樣的方式將程序代碼按一個個對象進行組織和編寫,讓計算機系統能夠識別和理解用對象方式組織和編寫的程序代碼,這樣就可以把現實生活中的業務對象映射到計算機系統中。
面向對象的編程語言有封裝、繼承 、抽象、多態等4個主要的特徵。

  • 封裝:封裝是保證軟件部件具有優良的模塊性的基礎,封裝的目標就是要實現軟件部件的“高內聚、低耦合”,防止程序相互依賴性而帶來的變動影響。在面向對象的編程語言中,對象是封裝的最基本單位,面向對象的封裝比傳統語言的封裝更爲清晰、更爲有力。面向對象的封裝就是把描述一個對象的屬性和行爲的代碼封裝在一個“模塊”中,也就是一個類中,屬性用變量定義,行爲用方法進行定義,方法可以直接訪問同一個對象中的屬性。通常情況下,只要記住讓變量和訪問這個變量的方法放在一起,將一個類中的成員變量全部定義成私有的,只有這個類自己的方法纔可以訪問到這些成員變量,這就基本上實現對象的封裝,就很容易找出要分配到這個類上的方法了,就基本上算是會面向對象的編程了。把握一個原則:把對同一事物進行操作的方法和相關的方法放在同一個類中,把方法和它操作的數據放在同一個類中。
    例如,人要在黑板上畫圓,這一共涉及三個對象:人、黑板、圓,畫圓的方法要分配給哪個對象呢?由於畫圓需要使用到圓心和半徑,圓心和半徑顯然是圓的屬性,如果將它們在類中定義成了私有的成員變量,那麼,畫圓的方法必須分配給圓,它才能訪問到圓心和半徑這兩個屬性,人以後只是調用圓的畫圓方法、表示給圓發給消息而已,畫圓這個方法不應該分配在人這個對象上,這就是面向對象的封裝性,即將對象封裝成一個高度自治和相對封閉的個體,對象狀態(屬性)由這個對象自己的行爲(方法)來讀取和改變。一個更便於理解的例子就是,司機將火車剎住了,剎車的動作是分配給司機,還是分配給火車,顯然,應該分配給火車,因爲司機自身是不可能有那麼大的力氣將一個火車給停下來的,只有火車自己才能完成這一動作,火車需要調用內部的離合器和剎車片等多個器件協作才能完成剎車這個動作,司機剎車的過程只是給火車發了一個消息,通知火車要執行剎車動作而已。

  • 抽象:抽象就是找出一些事物的相似和共性之處,然後將這些事物歸爲一個類,這個類只考慮這些事物的相似和共性之處,並且會忽略與當前主題和目標無關的那些方面,將注意力集中在與當前目標有關的方面。例如,看到一隻螞蟻和大象,你能夠想象出它們的相同之處,那就是抽象。抽象包括行爲抽象和狀態抽象兩個方面。例如,定義一個Person類,如下:

    class Person{
    String name;
    int age;
    }
    人本來是很複雜的事物,有很多方面,但因爲當前系統只需要瞭解人的姓名和年齡,所以上面定義的類中只包含姓名和年齡這兩個屬性,這就是一種抽像,使用抽象可以避免考慮一些與目標無關的細節。我對抽象的理解就是不要用顯微鏡去看一個事物的所有方面,這樣涉及的內容就太多了,而是要善於劃分問題的邊界,當前系統需要什麼,就只考慮什麼。

  • 繼承:在定義和實現一個類的時候,可以在一個已經存在的類的基礎之上來進行,把這個已經存在的類所定義的內容作爲自己的內容,並可以加入若干新的內容,或修改原來的方法使之更適合特殊的需要,這就是繼承。繼承是子類自動共享父類數據和方法的機制,這是類之間的一種關係,提高了軟件的可重用性和可擴展性。

  • 多態:多態是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。因爲在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現上,從而導致該引用調用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態,這就是多態性。多態性增強了軟件的靈活性和擴展性。例如,下面代碼中的UserDao是一個接口,它定義引用變量userDao指向的實例對象由daofactory.getDao()在執行的時候返回,有時候指向的是UserJdbcDao這個實現,有時候指向的是UserHibernateDao這個實現,這樣,不用修改源代碼,就可以改變userDao指向的具體類實現,從而導致userDao.insertUser()方法調用的具體代碼也隨之改變,即有時候調用的是UserJdbcDao的insertUser方法,有時候調用的是UserHibernateDao的insertUser方法:

    UserDao userDao = daofactory.getDao();
    userDao.insertUser(user);

244. java中實現多態的機制是什麼?

靠的是父類或接口定義的引用變量可以指向子類或具體實現類的實例對象,而程序調用的方法在運行期才動態綁定,就是引用變量所指向的具體實例對象的方法,也就是內存里正在運行的那個對象的方法,而不是引用變量的類型中定義的方法。

245. abstract class和interface有什麼區別?

含有abstract修飾符的class即爲抽象類,abstract 類不能創建的實例對象。含有abstract方法的類必須定義爲abstract class,abstract class類中的方法不必是抽象的。abstract class類中定義抽象方法必須在具體(Concrete)子類中實現,所以,不能有抽象構造方法或抽象靜態方法。如果的子類沒有實現抽象父類中的所有抽象方法,那麼子類也必須定義爲abstract類型。
接口(interface)可以說成是抽象類的一種特例,接口中的所有方法都必須是抽象的。接口中的方法定義默認爲public abstract類型,接口中的成員變量類型默認爲public static final。
下面比較一下兩者的語法區別:

  • 抽象類可以有構造方法,接口中不能有構造方法。

  • 抽象類中可以有普通成員變量,接口中沒有普通成員變量

  • 抽象類中可以包含非抽象的普通方法,接口中的所有方法必須都是抽象的,不能有非抽象的普通方法。

  • 抽象類中的抽象方法的訪問類型可以是public,protected和(默認類型,雖然eclipse下不報錯,但應該也不行),但接口中的抽象方法只能是public類型的,並且默認即爲public abstract類型。

  • 抽象類中可以包含靜態方法,接口中不能包含靜態方法

  • 抽象類和接口中都可以包含靜態成員變量,抽象類中的靜態成員變量的訪問類型可以任意,但接口中定義的變量只能是public static final類型,並且默認即爲public static final類型。

  • 一個類可以實現多個接口,但只能繼承一個抽象類。
    下面接着再說說兩者在應用上的區別:
    接口更多的是在系統架構設計方法發揮作用,主要用於定義模塊之間的通信契約。而抽象類在代碼實現方面發揮作用,可以實現代碼的重用,例如,模板方法設計模式是抽象類的一個典型應用,假設某個項目的所有Servlet類都要用相同的方式進行權限判斷、記錄訪問日誌和處理異常,那麼就可以定義一個抽象的基類,讓所有的Servlet都繼承這個抽象基類,在抽象基類的service方法中完成權限判斷、記錄訪問日誌和處理異常的代碼,在各個子類中只是完成各自的業務邏輯代碼,僞代碼如下:

    public abstract class BaseServlet extends HttpServlet{
    public final void service(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException {
    記錄訪問日誌
    進行權限判斷
    if(具有權限){
    try{
    doService(request,response);
    }
    catch(Excetpion e) {
    記錄異常信息
    }
    }
    }
    protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException;
    //注意訪問權限定義成protected,顯得既專業,又嚴謹,因爲它是專門給子類用的
    }

    public class MyServlet1 extends BaseServlet
    {
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException
    {
    本Servlet只處理的具體業務邏輯代碼
    }

    }
    父類方法中間的某段代碼不確定,留給子類幹,就用模板方法設計模式。

246. abstract的method是否可同時是static,是否可同時是native,是否可同時是synchronized?

abstract的method 不可以是static的,因爲抽象的方法是要被子類實現的,而static與子類扯不上關係!
native方法表示該方法要用另外一種依賴平臺的編程語言實現的,不存在着被子類實現的問題,所以,它也不能是抽象的,不能與abstract混用。例如,FileOutputSteam類要硬件打交道,底層的實現用的是操作系統相關的api實現,例如,在windows用c語言實現的,所以,查看jdk 的源代碼,可以發現FileOutputStream的open方法的定義如下:
private native void open(String name) throws FileNotFoundException;
如果我們要用java調用別人寫的c語言函數,我們是無法直接調用的,我們需要按照java的要求寫一個c語言的函數,又我們的這個c語言函數去調用別人的c語言函數。由於我們的c語言函數是按java的要求來寫的,我們這個c語言函數就可以與java對接上,java那邊的對接方式就是定義出與我們這個c函數相對應的方法,java中對應的方法不需要寫具體的代碼,但需要在前面聲明native。
關於synchronized與abstract合用的問題,我覺得也不行,因爲在我幾年的學習和開發中,從來沒見到過這種情況,並且我覺得synchronized應該是作用在一個具體的方法上纔有意義。而且,方法上的synchronized同步所使用的同步鎖對象是this,而抽象方法上無法確定this是什麼。

247. 什麼是內部類?Static Nested Class 和 Inner Class的不同。

內部類就是在一個類的內部定義的類,內部類中不能定義靜態成員(靜態成員不是對象的特性,只是爲了找一個容身之處,所以需要放到一個類中而已,這麼一點小事,你還要把它放到類內部的一個類中,過分了啊!提供內部類,不是爲讓你幹這種事情,無聊,不讓你幹。我想可能是既然靜態成員類似c語言的全局變量,而內部類通常是用於創建內部對象用的,所以,把“全局變量”放在內部類中就是毫無意義的事情,既然是毫無意義的事情,就應該被禁止),內部類可以直接訪問外部類中的成員變量,內部類可以定義在外部類的方法外面,也可以定義在外部類的方法體中,如下所示:

public class Outer
{
		int out_x  = 0;
		public void method()
		{
			Inner1 inner1 = new Inner1();
			public class Inner2   //在方法體內部定義的內部類
			{
				public method()
				{
					out_x = 3;
				}
			}
			Inner2 inner2 = new Inner2();
		}

		public class Inner1   //在方法體外面定義的內部類
		{
		}
		
}

在方法體外面定義的內部類的訪問類型可以是public,protecte,默認的,private等4種類型,這就好像類中定義的成員變量有4種訪問類型一樣,它們決定這個內部類的定義對其他類是否可見;對於這種情況,我們也可以在外面創建內部類的實例對象,創建內部類的實例對象時,一定要先創建外部類的實例對象,然後用這個外部類的實例對象去創建內部類的實例對象,代碼如下:

Outer outer = new Outer();
Outer.Inner1 inner1 = outer.new Innner1();

在方法內部定義的內部類前面不能有訪問類型修飾符,就好像方法中定義的局部變量一樣,但這種內部類的前面可以使用final或abstract修飾符。這種內部類對其他類是不可見的其他類無法引用這種內部類,但是這種內部類創建的實例對象可以傳遞給其他類訪問。這種內部類必須是先定義,後使用,即內部類的定義代碼必須出現在使用該類之前,這與方法中的局部變量必須先定義後使用的道理也是一樣的。這種內部類可以訪問方法體中的局部變量,但是,該局部變量前必須加final修飾符。
對於這些細節,只要在eclipse寫代碼試試,根據開發工具提示的各類錯誤信息就可以馬上了解到。
在方法體內部還可以採用如下語法來創建一種匿名內部類,即定義某一接口或類的子類的同時,還創建了該子類的實例對象,無需爲該子類定義名稱:

public class Outer
{
		public void start()
		{
			new Thread(
new Runable(){
					public void run(){};
}
).start();
		}
}

最後,在方法外部定義的內部類前面可以加上static關鍵字,從而成爲Static Nested Class,它不再具有內部類的特性,所有,從狹義上講,它不是內部類。Static Nested Class與普通類在運行時的行爲和功能上沒有什麼區別,只是在編程引用時的語法上有一些差別,它可以定義成public、protected、默認的、private等多種類型,而普通類只能定義成public和默認的這兩種類型。在外面引用Static Nested Class類的名稱爲“外部類名.內部類名”。在外面不需要創建外部類的實例對象,就可以直接創建Static Nested Class,例如,假設Inner是定義在Outer類中的Static Nested Class,那麼可以使用如下語句創建Inner類:
Outer.Inner inner = new Outer.Inner();
由於static Nested Class不依賴於外部類的實例對象,所以,static Nested Class能訪問外部類的非static成員變量。當在外部類中訪問Static Nested Class時,可以直接使用Static Nested Class的名字,而不需要加上外部類的名字了,在Static Nested Class中也可以直接引用外部類的static的成員變量,不需要加上外部類的名字。
在靜態方法中定義的內部類也是Static Nested Class,這時候不能在類前面加static關鍵字,靜態方法中的Static Nested Class與普通方法中的內部類的應用方式很相似,它除了可以直接訪問外部類中的static的成員變量,還可以訪問靜態方法中的局部變量,但是,該局部變量前必須加final修飾符。

248. 內部類可以引用它的包含類的成員嗎?有沒有什麼限制?

完全可以。如果不是靜態內部類,那沒有什麼限制!
如果你把靜態嵌套類當作內部類的一種特例,那在這種情況下不可以訪問外部類的普通成員變量,而只能訪問外部類中的靜態成員,例如,下面的代碼:

class Outer
{
	static int x;
	static class Inner
	{
		void test()
		{
			syso(x);
		}
	}
}

249. Anonymous Inner Class (匿名內部類) 是否可以extends(繼承)其它類,是否可以implements(實現)interface(接口)?

可以繼承其他類或實現其他接口。不僅是可以,而是必須!

250. super.getClass()方法調用

下面程序的輸出結果是多少?

import java.util.Date;
public  class Test extends Date{
	public static void main(String[] args) {
		new Test().test();
	}
	
	public void test(){
		System.out.println(super.getClass().getName());
	}
}

很奇怪,結果是Test
在test方法中,直接調用getClass().getName()方法,返回的是Test類名
由於getClass()在Object類中定義成了final,子類不能覆蓋該方法,所以,在
test方法中調用getClass().getName()方法,其實就是在調用從父類繼承的getClass()方法,等效於調用super.getClass().getName()方法,所以,super.getClass().getName()方法返回的也應該是Test。
如果想得到父類的名稱,應該用如下代碼:
getClass().getSuperClass().getName();

251. String是最基本的數據類型嗎?

基本數據類型包括byte、int、char、long、float、double、boolean和short。
java.lang.String類是final類型的,因此不可以繼承這個類、不能修改這個類。爲了提高效率節省空間,我們應該用StringBuffer類

252. String是最基本的數據類型嗎?

基本數據類型包括byte、int、char、long、float、double、boolean和short。
java.lang.String類是final類型的,因此不可以繼承這個類、不能修改這個類。爲了提高效率節省空間,我們應該用StringBuffer類

253. String對象深入

沒有。因爲String被設計成不可變(immutable)類,所以它的所有對象都是不可變對象。在這段代碼中,s原先指向一個String對象,內容是 “Hello”,然後我們對s進行了+操作,那麼s所指向的那個對象是否發生了改變呢?答案是沒有。這時,s不指向原來那個對象了,而指向了另一個 String對象,內容爲"Hello world!",原來那個對象還存在於內存之中,只是s這個引用變量不再指向它了。
通過上面的說明,我們很容易導出另一個結論,如果經常對字符串進行各種各樣的修改,或者說,不可預見的修改,那麼使用String來代表字符串的話會引起很大的內存開銷。因爲 String對象建立之後不能再改變,所以對於每一個不同的字符串,都需要一個String對象來表示。這時,應該考慮使用StringBuffer類,它允許修改,而不是每個不同的字符串都要生成一個新的對象。並且,這兩種類的對象轉換十分容易。
同時,我們還可以知道,如果要使用內容相同的字符串,不必每次都new一個String。例如我們要在構造器中對一個名叫s的String引用變量進行初始化,把它設置爲初始值,應當這樣做:

public class Demo {
private String s;
...
public Demo {
s = "Initial Value";
}
...
}

而非

s = new String("Initial Value");

後者每次都會調用構造器,生成新對象,性能低下且內存開銷大,並且沒有意義,因爲String對象不可改變,所以對於內容相同的字符串,只要一個String對象來表示就可以了。也就說,多次調用上面的構造器創建多個對象,他們的String類型屬性s都指向同一個對象。
上面的結論還基於這樣一個事實:對於字符串常量,如果內容相同,Java認爲它們代表同一個String對象。而用關鍵字new調用構造器,總是會創建一個新的對象,無論內容是否相同。
至於爲什麼要把String類設計成不可變類,是它的用途決定的。其實不只String,很多Java標準類庫中的類都是不可變的。在開發一個系統的時候,我們有時候也需要設計不可變類,來傳遞一組相關的值,這也是面向對象思想的體現。不可變類有一些優點,比如因爲它的對象是隻讀的,所以多線程併發訪問也不會有任何問題。當然也有一些缺點,比如每個不同的狀態都要一個對象來代表,可能會造成性能上的問題。所以Java標準類庫還提供了一個可變版本,即 StringBuffer。

254. 是否可以繼承String類?

String類是final類故不可以繼承。

255. String s = new String(“xyz”);創建了幾個String Object? 二者之間有什麼區別?

兩個或一個,”xyz”對應一個對象,這個對象放在字符串常量緩衝區,常量”xyz”不管出現多少遍,都是緩衝區中的那一個。New String每寫一遍,就創建一個新的對象,它一句那個常量”xyz”對象的內容來創建出一個新String對象。如果以前就用過’xyz’,這句代表就不會創建”xyz”自己了,直接從緩衝區拿。

256. String 和StringBuffer的區別

JAVA平臺提供了兩個類:String和StringBuffer,它們可以儲存和操作字符串,即包含多個字符的字符數據。這個String類提供了數值不可改變的字符串。而這個StringBuffer類提供的字符串進行修改。當你知道字符數據要改變的時候你就可以使用StringBuffer。典型地,你可以使用StringBuffers來動態構造字符數據。另外,String實現了equals方法,new String(“abc”).equals(new String(“abc”)的結果爲true,而StringBuffer沒有實現equals方法,所以,new StringBuffer(“abc”).equals(new StringBuffer(“abc”)的結果爲false。
接着要舉一個具體的例子來說明,我們要把1到100的所有數字拼起來,組成一個串。

StringBuffer sbf = new StringBuffer();  
for(int i=0;i<100;i++)
{
	sbf.append(i);
}

上面的代碼效率很高,因爲只創建了一個StringBuffer對象,而下面的代碼效率很低,因爲創建了101個對象。

String str = new String();  
for(int i=0;i<100;i++)
{
	str = str + i;
}

在講兩者區別時,應把循環的次數搞成10000,然後用endTime-beginTime來比較兩者執行的時間差異,最後還要講講StringBuilder與StringBuffer的區別。
String覆蓋了equals方法和hashCode方法,而StringBuffer沒有覆蓋equals方法和hashCode方法,所以,將StringBuffer對象存儲進Java集合類中時會出現問題。

257. 如何把一段逗號分割的字符串轉換成一個數組?

如果不查jdk api,我很難寫出來!我可以說說我的思路:

  • 用正則表達式,代碼大概爲:String [] result = orgStr.split(“,”);

  • 用 StingTokenizer ,代碼爲:StringTokenizer tokener = StringTokenizer(orgStr,”,”);

    String [] result = new String[tokener .countTokens()];
    Int i=0;
    while(tokener.hasNext(){result[i++]=toker.nextToken();}

258. 下面這條語句一共創建了多少個對象:String s=“a”+“b”+“c”+“d”;

對於如下代碼:

String s1 = "a";
String s2 = s1 + "b";
String s3 = "a" + "b";
System.out.println(s2 == "ab");
System.out.println(s3 == "ab");

第一條語句打印的結果爲false,第二條語句打印的結果爲true,這說明javac編譯可以對字符串常量直接相加的表達式進行優化,不必要等到運行期去進行加法運算處理,而是在編譯時去掉其中的加號,直接將其編譯成一個這些常量相連的結果。
題目中的第一行代碼被編譯器在編譯時優化後,相當於直接定義了一個”abcd”的字符串,所以,上面的代碼應該只創建了一個String對象。寫如下兩行代碼,

String s = "a" + "b" + "c" + "d";
System.out.println(s == "abcd");

最終打印的結果應該爲true。

259. try {}裏有一個return語句,那麼緊跟在這個try後的finally {}裏的code會不會被執行,什麼時候被執行,在return前還是後?

也許你的答案是在return之前,但往更細地說,我的答案是在return中間執行,請看下面程序代碼的運行結果:

public  class Test {

	/**
	 * @param args add by zxx ,Dec 9, 2008
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println(new Test().test());;
	}

	static int test()
	{
		int x = 1;
		try
		{
			return x;
		}
		finally
		{
			++x;
		}
	}
	
}

---------執行結果 ---------
1

運行結果是1,爲什麼呢?主函數調用子函數並得到結果的過程,好比主函數準備一個空罐子,當子函數要返回結果時,先把結果放在罐子裏,然後再將程序邏輯返回到主函數。所謂返回,就是子函數說,我不運行了,你主函數繼續運行吧,這沒什麼結果可言,結果是在說這話之前放進罐子裏的。

260. 下面的程序代碼輸出的結果是多少?

public class  smallT
{
	public static void  main(String args[])
	{
		smallT t  = new  smallT();
		int  b  =  t.get();
		System.out.println(b);
	}
	
	public int  get()
	{
		try
		{
			return 1 ;
		}
		finally
		{
			return 2 ;
		}
	}
}
返回的結果是2。

我可以通過下面一個例子程序來幫助我解釋這個答案,從下面例子的運行結果中可以發現,try中的return語句調用的函數先於finally中調用的函數執行,也就是說return語句先執行,finally語句後執行,所以,返回的結果是2。Return並不是讓函數馬上返回,而是return語句執行後,將把返回結果放置進函數棧中,此時函數並不是馬上返回,它要執行finally語句後才真正開始返回。
在講解答案時可以用下面的程序來幫助分析:

public  class Test {

	/**
	 * @param args add by zxx ,Dec 9, 2008
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println(new Test().test());;
	}

	int test()
	{
		try
		{
			return func1();
		}
		finally
		{
			return func2();
		}
	}
	
	int func1()
	{
		System.out.println("func1");
		return 1;
	}
	int func2()
	{
		System.out.println("func2");
		return 2;
	}	
}
-----------執行結果-----------------

func1
func2
2

結論:finally中的代碼比return 和break語句後執行

261. final, finally, finalize的區別。

final 用於聲明屬性,方法和類,分別表示屬性不可變,方法不可覆蓋,類不可繼承。
內部類要訪問局部變量,局部變量必須定義成final類型,例如,一段代碼……
finally是異常處理語句結構的一部分,表示總是執行。
finalize是Object類的一個方法,在垃圾收集器執行的時候會調用被回收對象的此方法,可以覆蓋此方法提供垃圾收集時的其他資源回收,例如關閉文件等。JVM不保證此方法總被調用

262. 運行時異常與一般異常有何異同?

異常表示程序運行過程中可能出現的非正常狀態,運行時異常表示虛擬機的通常操作中可能遇到的異常,是一種常見運行錯誤。java編譯器要求方法必須聲明拋出可能發生的非運行時異常,但是並不要求必須聲明拋出未被捕獲的運行時異常。

263. error和exception有什麼區別?

error 表示恢復不是不可能但很困難的情況下的一種嚴重問題。比如說內存溢出。不可能指望程序能處理這樣的情況。 exception 表示一種設計或實現問題。也就是說,它表示如果程序運行正常,從不會發生的情況。

264. Java中的異常處理機制的簡單原理和應用。

異常是指java程序運行時(非編譯)所發生的非正常情況或錯誤,與現實生活中的事件很相似,現實生活中的事件可以包含事件發生的時間、地點、人物、情節等信息,可以用一個對象來表示,Java使用面向對象的方式來處理異常,它把程序中發生的每個異常也都分別封裝到一個對象來表示的,該對象中包含有異常的信息。
Java對異常進行了分類,不同類型的異常分別用不同的Java類表示,所有異常的根類爲java.lang.Throwable,Throwable下面又派生了兩個子類:Error和Exception,Error 表示應用程序本身無法克服和恢復的一種嚴重問題,程序只有死的份了,例如,說內存溢出和線程死鎖等系統問題。Exception表示程序還能夠克服和恢復的問題,其中又分爲系統異常和普通異常,系統異常是軟件本身缺陷所導致的問題,也就是軟件開發人員考慮不周所導致的問題,軟件使用者無法克服和恢復這種問題,但在這種問題下還可以讓軟件系統繼續運行或者讓軟件死掉,例如,數組腳本越界(ArrayIndexOutOfBoundsException),空指針異常(NullPointerException)、類轉換異常(ClassCastException);普通異常是運行環境的變化或異常所導致的問題,是用戶能夠克服的問題,例如,網絡斷線,硬盤空間不夠,發生這樣的異常後,程序不應該死掉。
java爲系統異常和普通異常提供了不同的解決方案,編譯器強制普通異常必須try…catch處理或用throws聲明繼續拋給上層調用方法處理,所以普通異常也稱爲checked異常,而系統異常可以處理也可以不處理,所以,編譯器不強制用try…catch處理或用throws聲明,所以系統異常也稱爲unchecked異常。
提示答題者:就按照三個級別去思考:虛擬機必須宕機的錯誤,程序可以死掉也可以不死掉的錯誤,程序不應該死掉的錯誤;

265. 請寫出你最常見到的5個runtime exception。

這道題主要考你的代碼量到底多大,如果你長期寫代碼的,應該經常都看到過一些系統方面的異常,你不一定真要回答出5個具體的系統異常,但你要能夠說出什麼是系統異常,以及幾個系統異常就可以了,當然,這些異常完全用其英文名稱來寫是最好的,如果實在寫不出,那就用中文吧,有總比沒有強!
所謂系統異常,就是……,它們都是RuntimeException的子類,在jdk doc中查RuntimeException類,就可以看到其所有的子類列表,也就是看到了所有的系統異常。我比較有印象的系統異常有:NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。

266. java中有幾種方法可以實現一個線程?用什麼關鍵字修飾同步方法? stop()和suspend()方法爲何不推薦使用?

java5以前,有如下兩種:
第一種:

new Thread(){}.start();這表示調用Thread子類對象的run方法,new Thread(){}表示一個Thread的匿名子類的實例對象,子類加上run方法後的代碼如下:
new Thread(){
	public void run(){
	}
}.start();

第二種:

new Thread(new Runnable(){}).start();這表示調用Thread對象接受的Runnable對象的run方法,new Runnable(){}表示一個Runnable的匿名子類的實例對象,runnable的子類加上run方法後的代碼如下:
new Thread(new Runnable(){
			public void run(){
			}	
		}
	).start();

從java5開始,還有如下一些線程池創建多線程的方式:

ExecutorService pool = Executors.newFixedThreadPool(3)
for(int i=0;i<10;i++)
{
 pool.execute(new Runable(){public void run(){}});
}
Executors.newCachedThreadPool().execute(new Runable(){public void run(){}});
Executors.newSingleThreadExecutor().execute(new Runable(){public void run(){}});

有兩種實現方法,分別使用new Thread()和new Thread(runnable)形式,第一種直接調用thread的run方法,所以,我們往往使用Thread子類,即new SubThread()。第二種調用runnable的run方法。
有兩種實現方法,分別是繼承Thread類與實現Runnable接口 ,用synchronized關鍵字修飾同步方法 ,反對使用stop(),是因爲它不安全。它會解除由線程獲取的所有鎖定,而且如果對象處於一種不連貫狀態,那麼其他線程能在那種狀態下檢查和修改它們。結果很難檢查出真正的問題所在。suspend()方法容易發生死鎖。調用suspend()的時候,目標線程會停下來,但卻仍然持有在這之前獲得的鎖定。此時,其他任何線程都不能訪問鎖定的資源,除非被"掛起"的線程恢復運行。對任何線程來說,如果它們想恢復目標線程,同時又試圖使用任何一個鎖定的資源,就會造成死鎖。所以不應該使用suspend(),而應在自己的Thread類中置入一個標誌,指出線程應該活動還是掛起。若標誌指出線程應該掛起,便用wait()命其進入等待狀態。若標誌指出線程應當恢復,則用一個notify()重新啓動線程。

267. sleep() 和 wait() 有什麼區別?

sleep是線程類(Thread)的方法,導致此線程暫停執行指定時間,給執行機會給其他線程,但是監控狀態依然保持,到時後會自動恢復。調用sleep不會釋放對象鎖。 wait是Object類的方法,對此對象調用wait方法導致本線程放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象發出notify方法(或notifyAll)後本線程才進入對象鎖定池準備獲得對象鎖進入運行狀態。
sleep就是正在執行的線程主動讓出cpu,cpu去執行其他線程,在sleep指定的時間過後,cpu纔會回到這個線程上繼續往下執行,如果當前線程進入了同步鎖,sleep方法並不會釋放鎖,即使當前線程使用sleep方法讓出了cpu,但其他被同步鎖擋住了的線程也無法得到執行。wait是指在一個已經進入了同步鎖的線程內,讓自己暫時讓出同步鎖,以便其他正在等待此鎖的線程可以得到同步鎖並運行,只有其他線程調用了notify方法(notify並不釋放鎖,只是告訴調用過wait方法的線程可以去參與獲得鎖的競爭了,但不是馬上得到鎖,因爲鎖還在別人手裏,別人還沒釋放。如果notify方法後面的代碼還有很多,需要這些代碼執行完後纔會釋放鎖,可以在notfiy方法後增加一個等待和一些代碼,看看效果),調用wait方法的線程就會解除wait狀態和程序可以再次得到鎖後繼續向下運行。對於wait的講解一定要配合例子代碼來說明,才顯得自己真明白。

package com.huawei.interview;

public class MultiThread {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Thread(new Thread1()).start();
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		new Thread(new Thread2()).start();		
	}
	
	
	private static class Thread1 implements Runnable
	{

		@Override
		public void run() {
			// TODO Auto-generated method stub

//由於這裏的Thread1和下面的Thread2內部run方法要用同一對象作爲監視器,我們這裏不能用this,因爲在Thread2裏面的this和這個Thread1的this不是同一個對象。我們用MultiThread.class這個字節碼對象,當前虛擬機裏引用這個變量時,指向的都是同一個對象。

		synchronized (MultiThread.class) {

			System.out.println("enter thread1...");
			
			System.out.println("thread1 is waiting");
			try {
		//釋放鎖有兩種方式,第一種方式是程序自然離開監視器的範圍,也就是離開了synchronized關鍵字管轄的代碼範圍,另一種方式就是在synchronized關鍵字管轄的代碼內部調用監視器對象的wait方法。這裏,使用wait方法釋放鎖。
				MultiThread.class.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println("thread1 is going on...");
			System.out.println("thread1 is being over!");			
		}
	}
	
}

private static class Thread2 implements Runnable
{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		synchronized (MultiThread.class) {
		
			System.out.println("enter thread2...");
			
			System.out.println("thread2 notify other thread can release wait status..");

//由於notify方法並不釋放鎖, 即使thread2調用下面的sleep方法休息了10毫秒,但thread1仍然不會執行,因爲thread2沒有釋放鎖,所以Thread1無法得不到鎖。

			MultiThread.class.notify();
			
			System.out.println("thread2 is sleeping ten millisecond...");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println("thread2 is going on...");
			System.out.println("thread2 is being over!");
			
		}
	}
	
}	

}

268. 同步和異步有何異同,在什麼情況下分別使用他們?舉例說明。

如果數據將在線程間共享。例如正在寫的數據以後可能被另一個線程讀到,或者正在讀的數據可能已經被另一個線程寫過了,那麼這些數據就是共享數據,必須進行同步存取。
當應用程序在對象上調用了一個需要花費很長時間來執行的方法,並且不希望讓程序等待方法的返回時,就應該使用異步編程,在很多情況下采用異步途徑往往更有效率。

269. 下面兩個方法同步嗎?(自己發明)

class Test
{
synchronized static void sayHello3()
		{
		
		}	
	
		synchronized void getX(){}
}

270. 多線程有幾種實現方法?同步有幾種實現方法?

多線程有兩種實現方法,分別是繼承Thread類與實現Runnable接口
同步的實現方面有兩種,分別是synchronized,wait與notify
wait():使一個線程處於等待狀態,並且釋放所持有的對象的lock。
sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要捕捉InterruptedException異常。
notify():喚醒一個處於等待狀態的線程,注意的是在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。
Allnotity():喚醒所有處入等待狀態的線程,注意並不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。

271. 啓動一個線程是用run()還是start()?

啓動一個線程是調用start()方法,使線程就緒狀態,以後可以被調度爲運行狀態,一個線程必須關聯一些具體的執行代碼,run()方法是該線程所關聯的執行代碼。

272. 當一個線程進入一個對象的一個synchronized方法後,其它線程是否可進入此對象的其它方法?

分幾種情況:

  • 其他方法前是否加了synchronized關鍵字,如果沒加,則能。
  • 如果這個方法內部調用了wait,則可以進入其他synchronized方法。
  • 如果其他個方法都加了synchronized關鍵字,並且內部沒有調用wait,則不能。
  • 如果其他方法是static,它用的同步鎖是當前類的字節碼,與非靜態的方法不能同步,因爲非靜態的方法用的是this。

273. 線程的基本概念、線程的基本狀態以及狀態之間的關係

一個程序中可以有多條執行線索同時執行,一個線程就是程序中的一條執行線索,每個線程上都關聯有要執行的代碼,即可以有多段程序代碼同時運行,每個程序至少都有一個線程,即main方法執行的那個線程。如果只是一個cpu,它怎麼能夠同時執行多段程序呢?這是從宏觀上來看的,cpu一會執行a線索,一會執行b線索,切換時間很快,給人的感覺是a,b在同時執行,好比大家在同一個辦公室上網,只有一條鏈接到外部網線,其實,這條網線一會爲a傳數據,一會爲b傳數據,由於切換時間很短暫,所以,大家感覺都在同時上網。
狀態:就緒,運行,synchronize阻塞,wait和sleep掛起,結束。wait必須在synchronized內部調用。
調用線程的start方法後線程進入就緒狀態,線程調度系統將就緒狀態的線程轉爲運行狀態,遇到synchronized語句時,由運行狀態轉爲阻塞,當synchronized獲得鎖後,由阻塞轉爲運行,在這種情況可以調用wait方法轉爲掛起狀態,當線程關聯的代碼執行完後,線程變爲結束狀態。

274. 簡述synchronized和java.util.concurrent.locks.Lock的異同 ?

主要相同點:Lock能完成synchronized所實現的所有功能
主要不同點:Lock有比synchronized更精確的線程語義和更好的性能。synchronized會自動釋放鎖,而Lock一定要求程序員手工釋放,並且必須在finally從句中釋放。Lock還有更強大的功能,例如,它的tryLock方法可以非阻塞方式去拿鎖。
舉例說明(對下面的題用lock進行了改寫):

package com.huawei.interview;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest {

	/**
	 * @param args
	 */
	
	private int j;
	private Lock lock = new ReentrantLock();
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadTest tt = new ThreadTest();
		for(int i=0;i<2;i++)
		{
			new Thread(tt.new Adder()).start();
			new Thread(tt.new Subtractor()).start();
		}
	}

	private class Subtractor implements Runnable
	{

		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(true)
			{
				/*synchronized (ThreadTest.this) {			
					System.out.println("j--=" + j--);
					//這裏拋異常了,鎖能釋放嗎?
				}*/
				lock.lock();
				try
				{
					System.out.println("j--=" + j--);
				}finally
				{
					lock.unlock();
				}
			}
		}
		
	}
	
	private class Adder implements Runnable
	{

		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(true)
			{
				/*synchronized (ThreadTest.this) {
				System.out.println("j++=" + j++);	
				}*/
				lock.lock();
				try
				{
					System.out.println("j++=" + j++);
				}finally
				{
					lock.unlock();
				}				
			}			
		}
		
	}
}

275. 設計4個線程,其中兩個線程每次對j增加1,另外兩個線程對j每次減少1。寫出程序。

以下程序使用內部類實現線程,對j增減的時候沒有考慮順序問題。

public class ThreadTest1 
{ 
private int j; 
public static void main(String args[]){ 
   ThreadTest1 tt=new ThreadTest1(); 
   Inc inc=tt.new Inc(); 
   Dec dec=tt.new Dec(); 
   for(int i=0;i<2;i++){ 
       Thread t=new Thread(inc); 
       t.start(); 
		   t=new Thread(dec); 
       t.start(); 
       } 
   } 
private synchronized void inc(){ 
   j++; 
   System.out.println(Thread.currentThread().getName()+"-inc:"+j); 
   } 
private synchronized void dec(){ 
   j--; 
   System.out.println(Thread.currentThread().getName()+"-dec:"+j); 
   } 
class Inc implements Runnable{ 
   public void run(){ 
       for(int i=0;i<100;i++){ 
       inc(); 
       } 
   } 
} 
class Dec implements Runnable{ 
   public void run(){ 
       for(int i=0;i<100;i++){ 
       dec(); 
       } 
   } 
} 
} 

----------隨手再寫的一個-------------
class A
{
JManger j =new JManager();
main()
{
	new A().call();
}

void call
{
	for(int i=0;i<2;i++)
	{
		new Thread(
			new Runnable(){ public void run(){while(true){j.accumulate()}}}
		).start();
		new Thread(new Runnable(){ public void run(){while(true){j.sub()}}}).start();
	}
}
}

class JManager
{
	private j = 0;
	
	public synchronized void subtract()
	{
		j--
	}
	
	public synchronized void accumulate()
	{
		j++;
	}
	
}

276. 子線程循環10次,接着主線程循環100,接着又回到子線程循環10次,接着再回到主線程又循環100,如此循環50次,請寫出程序。

最終的程序代碼如下:

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new ThreadTest().init();

	}

	public void init()
	{
		final Business business = new Business();
		new Thread(
				new Runnable()
				{

					public void run() {
						for(int i=0;i<50;i++)
						{
							business.SubThread(i);
						}						
					}
					
				}
		
		).start();
		
		for(int i=0;i<50;i++)
		{
			business.MainThread(i);
		}		
	}
	
	private class Business
	{
		boolean bShouldSub = true;//這裏相當於定義了控制該誰執行的一個信號燈
		public synchronized void MainThread(int i)
		{
			if(bShouldSub)
				try {
					this.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}		
				
			for(int j=0;j<5;j++)
			{
				System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);
			}
			bShouldSub = true;
			this.notify();
		
		}
		
		
		public synchronized void SubThread(int i)
		{
			if(!bShouldSub)
				try {
					this.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}	
				
			for(int j=0;j<10;j++)
			{
				System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);
			}
			bShouldSub = false;				
			this.notify();			
		}
	}
}
備註:不可能一上來就寫出上面的完整代碼,最初寫出來的代碼如下,問題在於兩個線程的代碼要參照同一個變量,即這兩個線程的代碼要共享數據,所以,把這兩個線程的執行代碼搬到同一個類中去:

package com.huawei.interview.lym;

public class ThreadTest {
	
	private static boolean bShouldMain = false;
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		/*new Thread(){
		public void run()
		{
			for(int i=0;i<50;i++)
			{
				for(int j=0;j<10;j++)
				{
					System.out.println("i=" + i + ",j=" + j);
				}
			}				
		}
		
	}.start();*/		
		
		
		//final String str = new String("");

		new Thread(
				new Runnable()
				{
					public void run()
					{
						for(int i=0;i<50;i++)
						{
							synchronized (ThreadTest.class) {
								if(bShouldMain)
								{
									try {
										ThreadTest.class.wait();} 
									catch (InterruptedException e) {
										e.printStackTrace();
									}
								}
								for(int j=0;j<10;j++)
								{
									System.out.println(
											Thread.currentThread().getName() + 
											"i=" + i + ",j=" + j);
								}
								bShouldMain = true;
								ThreadTest.class.notify();
							}							
						}						
					}
				}
		).start();
		
		for(int i=0;i<50;i++)
		{
			synchronized (ThreadTest.class) {
				if(!bShouldMain)
				{
					try {
						ThreadTest.class.wait();} 
					catch (InterruptedException e) {
						e.printStackTrace();
					}
				}				
				for(int j=0;j<5;j++)
				{
					System.out.println(
							Thread.currentThread().getName() + 						
							"i=" + i + ",j=" + j);
				}
				bShouldMain = false;
				ThreadTest.class.notify();				
			}			
		}
	}

}
下面使用jdk5中的併發庫來實現的:
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;

public class ThreadTest
{
	private static Lock lock = new ReentrantLock();
	private static Condition subThreadCondition = lock.newCondition();
	private static boolean bBhouldSubThread = false;
	public static void main(String [] args)
	{
		ExecutorService threadPool = Executors.newFixedThreadPool(3);
		threadPool.execute(new Runnable(){
			public void run()
			{
				for(int i=0;i<50;i++)
				{
					lock.lock();					
					try
					{					
						if(!bBhouldSubThread)
							subThreadCondition.await();
						for(int j=0;j<10;j++)
						{
							System.out.println(Thread.currentThread().getName() + ",j=" + j);
						}
						bBhouldSubThread = false;
						subThreadCondition.signal();
					}catch(Exception e)
					{						
					}
					finally
					{
						lock.unlock();
					}
				}			
			}
			
		});
		threadPool.shutdown();
		for(int i=0;i<50;i++)
		{
				lock.lock();					
				try
				{	
					if(bBhouldSubThread)
							subThreadCondition.await();								
					for(int j=0;j<10;j++)
					{
						System.out.println(Thread.currentThread().getName() + ",j=" + j);
					}
					bBhouldSubThread = true;
					subThreadCondition.signal();					
				}catch(Exception e)
				{						
				}
				finally
				{
					lock.unlock();
				}					
		}
	}
}

277. 介紹Collection框架的結構

天南海北誰便談

278. Collection框架中實現比較要實現什麼接口

comparable/comparator

279. ArrayList和Vector的區別

這兩個類都實現了List接口(List接口繼承了Collection接口),他們都是有序集合,即存儲在這兩個集合中的元素的位置都是有順序的,相當於一種動態的數組,我們以後可以按位置索引號取出某個元素,,並且其中的數據是允許重複的,這是HashSet之類的集合的最大不同處,HashSet之類的集合不可以按索引號去檢索其中的元素,也不允許有重複的元素(本來題目問的與hashset沒有任何關係,但爲了說清楚ArrayList與Vector的功能,我們使用對比方式,更有利於說明問題)。
接着才說ArrayList與Vector的區別,這主要包括兩個方面:

  • 同步性:Vector是線程安全的,也就是說是它的方法之間是線程同步的,而ArrayList是線程序不安全的,它的方法之間是線程不同步的。如果只有一個線程會訪問到集合,那最好是使用ArrayList,因爲它不考慮線程安全,效率會高些;如果有多個線程會訪問到集合,那最好是使用Vector,因爲不需要我們自己再去考慮和編寫線程安全的代碼。
    備註:對於Vector&ArrayList、Hashtable&HashMap,要記住線程安全的問題,記住Vector與Hashtable是舊的,是java一誕生就提供了的,它們是線程安全的,ArrayList與HashMap是java2時才提供的,它們是線程不安全的。所以,我們講課時先講老的。
  • 數據增長:ArrayList與Vector都有一個初始的容量大小,當存儲進它們裏面的元素的個數超過了容量時,就需要增加ArrayList與Vector的存儲空間,每次要增加存儲空間時,不是隻增加一個存儲單元,而是增加多個存儲單元,每次增加的存儲單元的個數在內存空間利用與程序效率之間要取得一定的平衡。Vector默認增長爲原來兩倍,而ArrayList的增長策略在文檔中沒有明確規定(從源代碼看到的是增長爲原來的1.5倍)。ArrayList與Vector都可以設置初始的空間大小,Vector還可以設置增長的空間大小,而ArrayList沒有提供設置增長空間的方法。
    總結:即Vector增長原來的一倍,ArrayList增加原來的0.5倍。

280. HashMap和Hashtable的區別

HashMap是Hashtable的輕量級實現(非線程安全的實現),他們都完成了Map接口,主要區別在於HashMap允許空(null)鍵值(key),由於非線程安全,在只有一個線程訪問的情況下,效率要高於Hashtable。
HashMap允許將null作爲一個entry的key或者value,而Hashtable不允許。
HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因爲contains方法容易讓人引起誤解。
Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現。
最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多個線程訪問Hashtable時,不需要自己爲它的方法實現同步,而HashMap 就必須爲之提供外同步。
Hashtable和HashMap採用的hash/rehash算法都大概一樣,所以性能不會有很大的差異。
就HashMap與HashTable主要從三方面來說。
一.歷史原因:Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現
二.同步性:Hashtable是線程安全的,也就是說是同步的,而HashMap是線程序不安全的,不是同步的
三.值:只有HashMap可以讓你將空值作爲一個表的條目的key或value

281. List 和 Map 區別?

一個是存儲單列數據的集合,另一個是存儲鍵和值這樣的雙列數據的集合,List中存儲的數據是有順序,並且允許重複;Map中存儲的數據是沒有順序的,其鍵是不能重複的,它的值是可以有重複的。

282. List, Set, Map是否繼承自Collection接口?

List,Set是,Map不是

283. List、Map、Set三個接口,存取元素時,各有什麼特點?

這樣的題屬於隨意發揮題:這樣的題比較考水平,兩個方面的水平:一是要真正明白這些內容,二是要有較強的總結和表述能力。如果你明白,但表述不清楚,在別人那裏則等同於不明白。
首先,List與Set具有相似性,它們都是單列元素的集合,所以,它們有一個功共同的父接口,叫Collection。Set裏面不允許有重複的元素,所謂重複,即不能有兩個相等(注意,不是僅僅是相同)的對象 ,即假設Set集合中有了一個A對象,現在我要向Set集合再存入一個B對象,但B對象與A對象equals相等,則B對象存儲不進去,所以,Set集合的add方法有一個boolean的返回值,當集合中沒有某個元素,此時add方法可成功加入該元素時,則返回true,當集合含有與某個元素equals相等的元素時,此時add方法無法加入該元素,返回結果爲false。Set取元素時,沒法說取第幾個,只能以Iterator接口取得所有的元素,再逐一遍歷各個元素。

  • List表示有先後順序的集合, 注意,不是那種按年齡、按大小、按價格之類的排序。當我們多次調用add(Obj e)方法時,每次加入的對象就像火車站買票有排隊順序一樣,按先來後到的順序排序。有時候,也可以插隊,即調用add(int index,Obj e)方法,就可以指定當前對象在集合中的存放位置。一個對象可以被反覆存儲進List中,每調用一次add方法,這個對象就被插入進集合中一次,其實,並不是把這個對象本身存儲進了集合中,而是在集合中用一個索引變量指向這個對象,當這個對象被add多次時,即相當於集合中有多個索引指向了這個對象,如圖x所示。List除了可以以Iterator接口取得所有的元素,再逐一遍歷各個元素之外,還可以調用get(index i)來明確說明取第幾個。
  • Map與List和Set不同,它是雙列的集合,其中有put方法,定義如下:put(obj key,obj value),每次存儲時,要存儲一對key/value,不能存儲重複的key,這個重複的規則也是按equals比較相等。取則可以根據key獲得相應的value,即get(Object key)返回值爲key 所對應的value。另外,也可以獲得所有的key的結合,還可以獲得所有的value的結合,還可以獲得key和value組合成的Map.Entry對象的集合。
  • List 以特定次序來持有元素,可有重複元素。Set 無法擁有重複元素,內部排序。Map 保存key-value值,value可多值。
  • HashSet按照hashcode值的某種運算方式進行存儲,而不是直接按hashCode值的大小進行存儲。例如,“abc” —> 78,“def” —> 62,“xyz” —> 65在hashSet中的存儲順序不是62,65,78,這些問題感謝以前一個叫崔健的學員提出,最後通過查看源代碼給他解釋清楚,看本次培訓學員當中有多少能看懂源碼。LinkedHashSet按插入的順序存儲,那被存儲對象的hashcode方法還有什麼作用呢?學員想想!hashset集合比較兩個對象是否相等,首先看hashcode方法是否相等,然後看equals方法是否相等。new 兩個Student插入到HashSet中,看HashSet的size,實現hashcode和equals方法後再看size。
    同一個對象可以在Vector中加入多次。往集合裏面加元素,相當於集合裏用一根繩子連接到了目標對象。往HashSet中卻加不了多次的。

284. 說出ArrayList,Vector, LinkedList的存儲性能和特性

ArrayList和Vector都是使用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增加和插入元素,它們都允許直接按序號索引元素,但是插入元素要涉及數組元素移動等內存操作,所以索引數據快而插入數據慢,Vector由於使用了synchronized方法(線程安全),通常性能上較ArrayList差,而LinkedList使用雙向鏈表實現存儲,按序號索引數據需要進行前向或後向遍歷,但是插入數據時只需要記錄本項的前後項即可,所以插入速度較快。
LinkedList也是線程不安全的,LinkedList提供了一些方法,使得LinkedList可以被當作堆棧和隊列來使用。

285. 去掉一個Vector集合中重複的元素

Vector newVector = new Vector();
For (int i=0;i<vector.size();i++)
{
Object obj = vector.get(i);
	if(!newVector.contains(obj);
		newVector.add(obj);
}
還有一種簡單的方式,HashSet set = new HashSet(vector); 

286. Collection 和 Collections的區別。

Collection是集合類的上級接口,繼承與他的接口主要有Set 和List.
Collections是針對集合類的一個幫助類,他提供一系列靜態方法實現對各種集合的搜索、排序、線程安全化等操作。

287. Set裏的元素是不能重複的,那麼用什麼方法來區分重複與否呢? 是用==還是equals()? 它們有何區別?

Set裏的元素是不能重複的,元素重複與否是使用equals()方法進行判斷的。
equals()和==方法決定引用值是否指向同一對象equals()在類中被覆蓋,爲的是當兩個分離的對象的內容和類型相配的話,返回真值。

288. 你所知道的集合類都有哪些?主要方法?

最常用的集合類是 List 和 Map。 List 的具體實現包括 ArrayList 和 Vector,它們是可變大小的列表,比較適合構建、存儲和操作任何類型對象的元素列表。 List 適用於按數值索引訪問元素的情形。
Map 提供了一個更通用的元素存儲方法。 Map 集合類用於存儲元素對(稱作"鍵"和"值"),其中每個鍵映射到一個值。

ArrayList/VectorList
                    Collection
HashSet/TreeSetSet

PropetiesHashTable
					Map
	Treemap/HashMap

我記的不是方法名,而是思想,我知道它們都有增刪改查的方法,但這些方法的具體名稱,我記得不是很清楚,對於set,大概的方法是add,remove, contains;對於map,大概的方法就是put,remove,contains等,因爲,我只要在eclispe下按點操作符,很自然的這些方法就出來了。我記住的一些思想就是List類會有get(int index)這樣的方法,因爲它可以按順序取元素,而set類中沒有get(int index)這樣的方法。List和set都可以迭代出所有元素,迭代時先要得到一個iterator對象,所以,set和list類都有一個iterator方法,用於返回那個iterator對象。map可以返回三個集合,一個是返回所有的key的集合,另外一個返回的是所有value的集合,再一個返回的key和value組合成的EntrySet對象的集合,map也有get方法,參數是key,返回值是key對應的value。

289. 兩個對象值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對?

對。
如果對象要保存在HashSet或HashMap中,它們的equals相等,那麼,它們的hashcode值就必須相等。
如果不是要保存在HashSet或HashMap,則與hashcode沒有什麼關係了,這時候hashcode不等是可以的,例如arrayList存儲的對象就不用實現hashcode,當然,我們沒有理由不實現,通常都會去實現的。

290. TreeSet裏面放對象,如果同時放入了父類和子類的實例對象,那比較時使用的是父類的compareTo方法,還是使用的子類的compareTo方法,還是拋異常!

(應該是沒有針對問題的確切的答案,當前的add方法放入的是哪個對象,就調用哪個對象的compareTo方法,至於這個compareTo方法怎麼做,就看當前這個對象的類中是如何編寫這個方法的)
實驗代碼:

public class Parent implements Comparable {
	private int age = 0;
	public Parent(int age){
		this.age = age;
	}
	public int compareTo(Object o) {
		// TODO Auto-generated method stub
		System.out.println("method of parent");
		Parent o1 = (Parent)o;
		return age>o1.age?1:age<o1.age?-1:0;
	}

}

public class Child extends Parent {

	public Child(){
		super(3);
	}
	public int compareTo(Object o) {

			// TODO Auto-generated method stub
			System.out.println("method of child");
//			Child o1 = (Child)o;
			return 1;

	}
}

public class TreeSetTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		TreeSet set = new TreeSet();
		set.add(new Parent(3));
		set.add(new Child());
		set.add(new Parent(4));
		System.out.println(set.size());
	}

}

291. 說出一些常用的類,包,接口,請各舉5個

要讓人家感覺你對java ee開發很熟,所以,不能僅僅只列core java中的那些東西,要多列你在做ssh項目中涉及的那些東西。就寫你最近寫的那些程序中涉及的那些類。
常用的類:BufferedReader BufferedWriter FileReader FileWirter String Integer
java.util.Date,System,Class,List,HashMap
常用的包:java.lang java.io java.util java.sql ,javax.servlet,org.apache.strtuts.action,org.hibernate
常用的接口:Remote List Map Document NodeList ,Servlet,HttpServletRequest,HttpServletResponse,Transaction(Hibernate)、Session(Hibernate),HttpSession

292. java中有幾種類型的流?JDK爲每種類型的流提供了一些抽象類以供繼承,請說出他們分別是哪些類?

字節流,字符流。字節流繼承於InputStream OutputStream,字符流繼承於InputStreamReader OutputStreamWriter。在java.io包中還有許多其他的流,主要是爲了提高性能和使用方便。

293. 字節流與字符流的區別

要把一片二進制數據數據逐一輸出到某個設備中,或者從某個設備中逐一讀取一片二進制數據,不管輸入輸出設備是什麼,我們要用統一的方式來完成這些操作,用一種抽象的方式進行描述,這個抽象描述方式起名爲IO流,對應的抽象類爲OutputStream和InputStream ,不同的實現類就代表不同的輸入和輸出設備,它們都是針對字節進行操作的。
在應用中,經常要完全是字符的一段文本輸出去或讀進來,用字節流可以嗎?計算機中的一切最終都是二進制的字節形式存在。對於“中國”這些字符,首先要得到其對應的字節,然後將字節寫入到輸出流。讀取時,首先讀到的是字節,可是我們要把它顯示爲字符,我們需要將字節轉換成字符。由於這樣的需求很廣泛,人家專門提供了字符流的包裝類。
底層設備永遠只接受字節數據,有時候要寫字符串到底層設備,需要將字符串轉成字節再進行寫入。字符流是字節流的包裝,字符流則是直接接受字符串,它內部將串轉成字節,再寫入底層設備,這爲我們向IO設別寫入或讀取字符串提供了一點點方便。
字符向字節轉換時,要注意編碼的問題,因爲字符串轉成字節數組,
其實是轉成該字符的某種編碼的字節形式,讀取也是反之的道理。

講解字節流與字符流關係的代碼案例:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class IOTest {
	public static void main(String[] args) throws Exception {
		String str = "中國人";
		/*FileOutputStream fos  = new FileOutputStream("1.txt");
		
		fos.write(str.getBytes("UTF-8"));
		fos.close();*/
		
		/*FileWriter fw = new FileWriter("1.txt");
		fw.write(str);
		fw.close();*/
		PrintWriter pw = new PrintWriter("1.txt","utf-8");
		pw.write(str);
		pw.close();
		
		/*FileReader fr = new FileReader("1.txt");
		char[] buf = new char[1024];
		int len = fr.read(buf);
		String myStr = new String(buf,0,len);
		System.out.println(myStr);*/
		/*FileInputStream fr = new FileInputStream("1.txt");
		byte[] buf = new byte[1024];
		int len = fr.read(buf);
		String myStr = new String(buf,0,len,"UTF-8");
		System.out.println(myStr);*/
		BufferedReader br = new BufferedReader(
				new InputStreamReader(
					new FileInputStream("1.txt"),"UTF-8"	
					)
				);
		String myStr = br.readLine();
		br.close();
		System.out.println(myStr);
	}

}

294. 什麼是java序列化,如何實現java序列化?或者請解釋Serializable接口的作用。

我們有時候將一個java對象變成字節流的形式傳出去或者從一個字節流中恢復成一個java對象,例如,要將java對象存儲到硬盤或者傳送給網絡上的其他計算機,這個過程我們可以自己寫代碼去把一個java對象變成某個格式的字節流再傳輸,但是,jre本身就提供了這種支持,我們可以調用OutputStream的writeObject方法來做,如果要讓java 幫我們做,要被傳輸的對象必須實現serializable接口,這樣,javac編譯時就會進行特殊處理,編譯的類纔可以被writeObject方法操作,這就是所謂的序列化。需要被序列化的類必須實現Serializable接口,該接口是一個mini接口,其中沒有需要實現的方法,implements Serializable只是爲了標註該對象是可被序列化的。
例如,在web開發中,如果對象被保存在了Session中,tomcat在重啓時要把Session對象序列化到硬盤,這個對象就必須實現Serializable接口。如果對象要經過分佈式系統進行網絡傳輸或通過rmi等遠程調用,這就需要在網絡上傳輸對象,被傳輸的對象就必須實現Serializable接口。

295. 描述一下JVM加載class文件的原理機制?

JVM中類的裝載是由ClassLoader和它的子類來實現的,Java ClassLoader 是一個重要的Java運行時系統組件。它負責在運行時查找和裝入類文件的類。 (內加載)

296. heap和stack有什麼區別。

java的內存分爲兩類,一類是棧內存,一類是堆內存。棧內存是指程序進入一個方法時,會爲這個方法單獨分配一塊私屬存儲空間,用於存儲這個方法內部的局部變量,當這個方法結束時,分配給這個方法的棧會釋放,這個棧中的變量也將隨之釋放。
堆是與棧作用不同的內存,一般用於存放不放在當前方法棧中的那些數據,例如,使用new創建的對象都放在堆裏,所以,它不會隨方法的結束而消失。方法中的局部變量使用final修飾後,放在堆中,而不是棧中。

297. GC是什麼? 爲什麼要有GC?

GC是垃圾收集的意思(Gabage Collection),內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會導致程序或系統的不穩定甚至崩潰,Java提供的GC功能可以自動監測對象是否超過作用域從而達到自動回收內存的目的,Java語言沒有提供釋放已分配內存的顯示操作方法。

298. 垃圾回收的優點和原理。並考慮2種回收機制。

Java語言中一個顯著的特點就是引入了垃圾回收機制,使c++程序員最頭疼的內存管理的問題迎刃而解,它使得Java程序員在編寫程序的時候不再需要考慮內存管理。由於有個垃圾回收機制,Java中的對象不再有"作用域"的概念,只有對象的引用纔有"作用域"。垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作爲一個單獨的低級別的線程運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清楚和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收。回收機制有分代複製垃圾回收和標記垃圾回收,增量垃圾回收。

299. 垃圾回收器的基本原理是什麼?垃圾回收器可以馬上回收內存嗎?有什麼辦法主動通知虛擬機進行垃圾回收?

對於GC來說,當程序員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。通常,GC採用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是"可達的",哪些對象是"不可達的"。當GC確定一些對象爲"不可達"時,GC就有責任回收這些內存空間。可以。程序員可以手動執行System.gc(),通知GC運行,但是Java語言規範並不保證GC一定會執行。

300. 什麼時候用assert。

assertion(斷言)在軟件開發中是一種常用的調試方式,很多開發語言中都支持這種機制。在實現中,assertion就是在程序中的一條語句,它對一個boolean表達式進行檢查,一個正確程序必須保證這個boolean表達式的值爲true;如果該值爲false,說明程序已經處於不正確的狀態下,assert將給出警告或退出。一般來說,assertion用於保證程序最基本、關鍵的正確性。assertion檢查通常在開發和測試時開啓。爲了提高性能,在軟件發佈後,assertion檢查通常是關閉的。

package com.huawei.interview;

public class AssertTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int i = 0;
		for(i=0;i<5;i++)
		{
			System.out.println(i);
		}
		//假設程序不小心多了一句--i;
		--i;
		assert i==5;		
	}

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章