1.去掉 main 方法的 static 修飾符,程序會怎樣?
A:程序無法編譯
B:程序正常編譯,正常運行
C:程序正常編譯,正常運行一下馬上退出
D:程序正常編譯,運行時報錯
答:D
題目解析:運行時異常如下:
錯誤: main 方法不是類 xxx 中的 static, 請將 main 方法定義爲:
public static void main(String[] args)
2.以下程序運行的結果是?
public class TestClass {
public static void main(String[] args) {
System.out.println(getLength());
}
int getLength() {
private String s = "xyz";
int result = s.length();
return result;
}
}
A:3
B:2
C:4
D:程序無法編譯
答:D
題目解析:成員變量 s 不能使用任何修飾符(private/protected/public)修飾,否則編譯會報錯。
3.以下程序有幾處錯誤?
abstract class myAbstractClass
private abstract String method(){};
}
A:1
B:2
C:3
D:4
答:C
題目解析:類少一個“{”類開始標籤、抽象方法不能包含方法體、抽象方法訪問修飾符不能爲 private,因此總共有 3 處錯誤。
4.以下程序執行的結果是?
class A {
public static int x;
static {
x = B.y + 1;
}
}
public class B {
public static int y = A.x + 1;
public static void main(String[] args) {
System.out.println(String.format("x=%d,y=%d", A.x, B.y));
}
}
A:程序無法編譯
B:程序正常編譯,運行報錯
C:x=1,y=2
D:x=0,y=1
答:C
5.switch 語法可以配合 return 一起使用嗎?return 和 break 在 switch 使用上有何不同?
答:switch 可以配合 return 一起使用。return 和 break 的區別在於 switch 結束之後的代碼,比如以下代碼:
String getColor(String color) {
switch (color) {
case "red":
return "紅";
case "blue":
return "藍";
}
return "未知";
}
String getColor(String color) {
String result = "未知";
switch (color) {
case "red":
result = "紅";
break;
case "blue":
result = "藍";
}
return result;
}
對於以上這種 switch 之後沒有特殊業務處理的程序來說,return 和 break 的效果是等效的。然而,對於以下這種代碼:
String getColor(String color) {
switch (color) {
case "red":
return "紅";
case "blue":
return "藍";
}
return "未知";
}
String getColor(String color) {
String result = "未知";
switch (color) {
case "red":
result = "紅";
break;
case "blue":
result = "藍";
}
if (result.equals("未知")) {
result = "透明";
} else {
result += "色";
}
return result;
}
如果 switch 之後還有特殊的業務處理,那麼 return 和 break 就有很大的區別了。
6.一個棧的入棧順序是 A、B、C、D、E 則出棧不可能的順序是?
A:E D C B A
B:D E C B A
C:D C E A B
D:A B C D E
答:C
題目解析:棧是後進先出的,因此:
- A 選項:入棧順序 A B C D E 出棧順序就是 E D C B A 是正確的;
- B 選項:A B C D 先入棧,D 先出棧,這個時候 E 在入棧,E 在出棧,順序 D E C B A 也是正確的;
- C 選項:D 先出棧,說明 A B C 一定已入棧,因爲題目說了入棧的順序是 A B C D E,所以出棧的順序一定是 C B A,而 D C E A B 的順序 A 在 B 前面是永遠不可能發生的,所以選擇是 C;
- D 選項 A B C D E 依次先入棧、出棧,順序就是 A B C D E。
7.可以在 finally 塊中使用 return嗎?
答:不可以,finally 塊中的 return 返回後方法結束執行,不會再執行 try 塊中的 return 語句。
8.FileInputStream 可以實現什麼功能?
A:文件夾目錄獲取
B:文件寫入
C:文件讀取
D:文件夾目錄寫入
答:C
題目解析:FileInputStream 是文件讀取,FileOutputStream 纔是用來寫入文件的,FileInputStream 和 FileOutputStream 很容易搞混。
9.以下程序打印的結果是什麼?
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("I'm T1.");
}
};
t1.setPriority(3);
t1.start();
Thread t2 = new Thread(){
@Override
public void run() {
System.out.println("I'm T2.");
}
};
t2.setPriority(0);
t2.start();
答:程序報錯 java.lang.IllegalArgumentException,setPriority(n) 方法用於設置程序的優先級,優先級的取值爲 1-10,當設置爲 0 時,程序會報錯。
10.如何設置守護線程?
答:設置 Thead 類的 setDaemon(true) 方法設置當前的線程爲守護線程。
守護線程的使用示例如下:
Thread daemonThread = new Thread(){
@Override
public void run() {
super.run();
}
};
// 設置爲守護線程
daemonThread.setDaemon(true);
daemonThread.start();
11.以下說法中關於線程通信的說法錯誤的是?
A:可以調用 wait()、notify()、notifyAll() 三個方法實現線程通信
B:wait() 必須在 synchronized 方法或者代碼塊中使用
C:wait() 有多個重載的方法,可以指定等待的時間
D:wait()、notify()、notifyAll() 是 Object 類提供的方法,子類可以重寫
答:D
題目解析:wait()、notify()、notifyAll() 都是被 final 修飾的方法,不能再子類中重寫。選項 B,使用 wait() 方法時,必須先持有當前對象的鎖,否則會拋出異常 java.lang.IllegalMonitorStateException。
12.ReentrantLock 默認創建的是公平鎖還是非公平鎖?
答:默認創建的是非公平鎖,看以下源碼可以得知:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
Nonfair 爲非公平的意思,ReentrantLock() 等同於代碼 ReentrantLock(false)。
13.ReentrantLock 如何在一段時間內無阻塞嘗試訪問鎖?
答:使用 tryLock(long timeout, TimeUnit unit) 方法,就可以在一段時間內無堵塞的訪問鎖。
14.枚舉比較使用 equals 還是 ==?
答:枚舉比較調用 equals 和 == 的結果是一樣,查看 Enum 的源碼可知 equals 其實是直接調用了 ==,源碼如下:
public final boolean equals(Object other) {
return this==other;
}
15.在 Spring 中使用 @Value 賦值靜態變量爲什麼 null?怎麼解決?
答:因爲在 Springframework 框架中,當類加載器加載靜態變量時,Spring 上下文尚未加載,因此類加載器不會在 bean 中正確注入靜態類,導致了結果爲 null。可使用 Setter() 方法給靜態變量賦值,代碼如下:
@Component
public class ConfigValue {
private static String accessKey;
public String getAccessKey() {
return accessKey;
}
@Value("${accessKey}")
public void setAccessKey(String accessKey) {
ConfigValue.accessKey = accessKey;
}
}
/*
* 調用賦值變量
*/
@Component
public class TestClass {
@Autowired
private ConfigValue configValue;
public void method() {
// 讀取配置文件
configValue.getAccessKey();
}
}
16.如何自己實現一個定時任務?
答:啓動一個後臺線程,循環執行任務。代碼示例如下:
Thread getTocketThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
// 執行業務方法
TimeUnit.HOURS.sleep(2); // 每兩小時執行一次
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
if (!getTocketThread.isAlive()) {
System.out.println("啓動線程");
getTocketThread.start();
}
17.如何定義一個不定長度的數組?
答:在 Java 中使用數組必須要指定長度,如果長度不固定可使用 ArrayList、LinkedList 等容器接收完數據,再使用 toArray() 方法轉換成固定數組。
18.如何優雅的格式化百分比小數?
答:使用數字格式化類 DecimalFormat 來處理,具體實現代碼如下:
double num = 0.37500;
DecimalFormat df = new DecimalFormat("0.0%");
System.out.println(df.format(num)); // 執行結果:37.5%
19.什麼是跨域問題?爲什麼會產生跨域問題?
答:跨域問題指的是不同站點直接,使用 ajax 無法相互調用的問題。跨域問題是瀏覽器的行爲,是爲了保證用戶信息的安全,防止惡意網站竊取數據,所做的限制,如果沒有跨域限制就會導致信息被隨意篡改和提交,會導致不可預估的安全問題,所以也會造成不同站點間“正常”請求的跨域問題。
20.跨域的解決方案有哪些?
答:常見跨域問題的解決方案如下:
- jsonp(只支持 get 請求);
- nginx 請求轉發,把不同站點應用配置到同一個域名下;
服務器端設置運行跨域訪問,如果使用的是 Spring 框架可通過 @CrossOrigin 註解的方式聲明某個類或方法運行跨域訪問,或者配置全局跨域配置,請參考以下代碼:
// 單個跨域配置
@CrossOrigin(origins = "http://xxx", maxAge = 3600)
@RestController
public class testController{
}
// 全局配置
@Configuration
public class CorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**").allowedOrigins("https://xxx");
}
};
}
}
21.什麼原因會導致 Nginx 轉發時丟失部分 header 信息?該如何解決?
答:部分 header 信息丟失的原因是,丟失的 header 的 key 值中有下劃線,因爲 Nginx 轉發時,默認會忽略帶下劃線的 header 信息。
解決方案有兩個,一是去掉 key 值中的下劃線,二是在 Nginx 的配置文件 http 中添加“underscoresinheaders on;” 不忽略有下劃線的 header 信息。
22.如何設計一個高效的系統?
答:要設計一個高效的系統,通常要包含以下幾個方面。
(1)優化代碼
代碼優化分爲兩種情況:
- 代碼問題導致系統資源消耗過多的問題,比如,某段代碼導致內存溢出,往往是將 JVM 中的內存用完了,這個時候系統的內存資源消耗殆盡了,同時也會引發 JVM 頻繁地發生垃圾回收,導致 CPU 100% 以上居高不下,這個時候又消耗了系統的 CPU 資源。這種情況下需要使用相應的排查工具 VisualVM 或 JConsole,找到對應的問題代碼再進行優化;
- 還有一種是非問題代碼,這種代碼不容易發現,比如,LinkedList 集合如果使用 for 循環遍歷,則它的效率是很低的,因爲 LinkedList 是鏈表實現的,如果使用 for 循環獲取元素,在每次循環獲取元素時,都會去遍歷一次 List,這樣會降低讀的效率,這個時候應該改用 Iterator (迭代器)迭代循環該集合。
(2)設計優化
有很多問題可以通過我們的設計優化來提高程序的執行性能,比如,使用單例模式來減少頻繁地創建和銷燬對象所帶來的性能消耗,從而提高了程序的執行性能。
(3)參數調優
JVM 和 Web 容器的參數調優,對系統的執行性能也是也很大幫助的。比如,我們的業務中會創建大量的大對象,我們可以通過設置,將這些大對象直接放進老年代,這樣可以減少年輕代頻繁發生小的垃圾回收(Minor GC),減少 CPU 佔用時間,從而提升了程序的執行性能。Web 容器的線程池設置以及 Linux 操作系統的內核參數設置,也對程序的運行性能有着很大的影響,我們根據自己的業務場景優化這兩項內容。
(4)使用緩存
緩存的使用分爲前端和後端:
- 前端可使用瀏覽器緩存或者 CDN,CDN 的全稱是 Content Delivery Network,即內容分發網絡。CDN 是構建在現有網絡基礎之上的智能虛擬網絡,依靠部署在各地的邊緣服務器,通過中心平臺的負載均衡、內容分發、調度等功能模塊,使用戶就近獲取所需內容,降低網絡擁塞,提高用戶訪問響應速度和命中率。CDN 的關鍵技術主要有內容存儲和分發技術;
- 後端緩存,使用第三方緩存 Redis 或 Memcache 來緩存查詢結果,以提高查詢的響應速度。
(5)優化數據庫
數據庫是最寶貴的資源,通常也是影響程序響應速度的罪魁禍首,它的優化至關重要,通常分爲以下六個方面:
- 合理使用數據庫引擎
- 合理設置事務隔離級別,合理使用事務
- 正確使用 SQL 語句和查詢索引
- 合理分庫分表
- 使用數據庫中間件實現數據庫讀寫分離
- 設置數據庫主從讀寫分離
(6)屏蔽無效和惡意訪問
前端禁止重複提交:用戶提交之後按鈕置灰,禁止重複提交;用戶限流,在某一時間段內只允許用戶提交一次請求,比如,採取 IP 限流。
(7)搭建分佈式環境,使用負載分發
可以把程序部署到多臺服務器上,通過負載均衡工具,比如 Nginx,將併發請求分配到不同的服務器,從而提高了系統處理併發的能力。