---
# System prepended metadata

title: java與springboot面試

---

# java
## Java 基礎
1. Java 的主要特性有哪些？
    1. 平台無關性
    2. 面向對象
    3. 多執行緒
    4. 垃圾回收機制
    5. 安全性高

2. 解釋 Java 的平臺無關性特性:
Java 編譯後生成的字節碼（Bytecode）可以在任何支持 JVM 的平臺上執行，實現“一次編寫，到處運行”。

3. 什麼是 JDK、JRE 和 JVM？它們有什麼區別？
    1. JDK（Java Development Kit）：提供開發 Java 應用程序的工具集，包括編譯器（javac）。
    2. JRE（Java Runtime Environment）：提供運行 Java 應用程序所需的環境（包括 JVM 和標準庫）。
    3. JVM（Java Virtual Machine）：負責執行字節碼，進行內存管理和垃圾回收。

4. 什麼是重載（Overloading）和覆蓋（Overriding）？
    1. 重載：同一類中同名方法參數不同（數量、類型或順序）。
    2. 覆蓋：子類對父類方法進行重新實現，方法簽名必須相同。

4. 什麼是接口（interface）？如何使用？
接口是一組抽象方法的集合，定義行為而不具體實現。可以使用 implements 關鍵字實現接口。

## Java 多執行緒
1. 什麼是執行緒（Thread）？如何創建執行緒？
執行緒是程序執行的最小單元，可以通過繼承 Thread 或實現 Runnable 接口來創建。

2. 什麼是同步（synchronization）？如何實現？
    1. 同步用於防止多執行緒同時訪問共享資源，避免數據不一致。
    2. 可以使用 synchronized 關鍵字ReentrantLock。

3. 什麼是死鎖？如何避免？
    1. 死鎖：多個執行緒相互等待對方的資源，導致程序無限阻塞。
    2. 避免：
        1. 遵循固定的資源請求順序。
        2. 使用 tryLock 設置超時。
4. 什麼是 ThreadLocal？
ThreadLocal 提供了每個執行緒的專屬本地變量，執行緒之間互不影響。

## Java 高級
1. 什麼是垃圾回收（Garbage Collection）？如何運行？
    1. 垃圾回收是自動管理內存的機制，通過標記-清除算法釋放不再被引用的對象。
    2. 可以通過 System.gc() 或 Runtime.getRuntime().gc() 建議回收，但無法強制執行。

2. 什麼是反射（Reflection）？有什麼用途？
    1. 反射是運行時動態檢查和操作類和對象的方法。
    2. 用於框架設計（如 Spring）、動態代理、運行時動態加載類。

3. Java 中的設計模式有哪些？
    1. 創建型模式：單例（Singleton）、工廠（Factory）、建造者（Builder）。
    2. 結構型模式：適配器（Adapter）、裝飾器（Decorator）。
    3. 行為型模式：策略（Strategy）、觀察者（Observer）。

4.什麼是序列化（Serialization）？如何實現？
    1. 序列化是將對象轉換為字節流以便存儲或傳輸。
    2. 實現 Serializable 接口，並使用 ObjectOutputStream 和 ObjectInputStream。

5. 什麼是泛型（Generics）？有什麼好處？
泛型允許在編譯時確定類型，避免強制轉型，增強程式碼的可讀性和安全性。

6. 什麼是 lambda 表達式？有何用途？
Lambda 表達式是一種簡潔的匿名函數表達形式，主要用於簡化函數式編程（如 Stream API）。

## Java Web
1. 什麼是 Servlet？工作流程如何？
    1. Servlet 是 Java 的後端組件，用於處理 HTTP 請求和響應。
    2. 流程：
        1. 客戶端發送請求。
        2. 容器加載和初始化 Servlet。
        3. Servlet 處理請求並返回響應。

2. Spring 框架的核心模塊有哪些？
    1. Spring Core：IOC 和依賴注入。
    2. Spring MVC：構建 Web 應用程序。
    3. Spring Data：數據訪問模塊。
    4. Spring Security：安全框架。

3. 什麼是 Hibernate？有什麼優勢？
    1. Hibernate 是一種 ORM 框架，將數據庫表映射為 Java 對象。
    2. 優勢：
        1. 減少 SQL 編寫。
        2. 提供緩存機制。
        3. 支持多數據庫。


# springboot基本知識
## ApplicationContextInitializer
ApplicationContextInitializer是一個在ApplicationContext刷新之前對其進行配置的接口。通常，當我們希望在Spring容器完全初始化之前做一些初始化操作，例如為某些bean設置環境變數或系統屬性，就會使用這個接口。這個接口在SpringApplication啟動時加載，可以通過applicationContext.initializer.classes屬性來註冊自定義的ApplicationContextInitializer。
1. 作用：在容器初始化過程中修改ApplicationContext，比如設置系統屬性或自定義環境配置。
2. 範例：為Spring容器設定一個默認的屬性值。
```java=
public class MyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext context) {
        context.getEnvironment().getSystemProperties().put("propertyKey", "value");
    }
}
```
使用方式：在Spring Boot的SpringApplication中加入此ApplicationContextInitializer，如下：
```java=
SpringApplication app = new SpringApplication(MyApplication.class);
app.addInitializers(new MyInitializer());
app.run(args);
```
## ApplicationListener
ApplicationListener是用於監聽Spring容器內部的事件。當容器中發生特定事件（例如上下文刷新、上下文關閉、bean初始化等）時，可以使用ApplicationListener來執行相應的邏輯。Spring內部自帶了一些基本事件，如ContextRefreshedEvent、ContextClosedEvent等，應用程序也可以自定義事件。
1. 作用：監聽Spring容器的事件，並在事件發生時執行相應邏輯。
2. 範例：監聽上下文刷新事件並執行邏輯。
```java=
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("上下文已刷新！");
    }
}
```
使用方式：將MyListener註冊為Spring的bean即可自動生效，或直接通過配置文件註冊。

##  BeanFactory
BeanFactory是Spring IoC容器的核心接口，負責創建和管理bean。它通過getBean()方法來獲取bean實例，是最基礎的容器之一。常見的實現包括XmlBeanFactory和ApplicationContext，ApplicationContext是BeanFactory的高級擴展。
1. 作用：負責創建、管理和調用Spring中的bean，實現對bean的懶加載和依賴注入。
2. BeanFactory常見的兩個實現:
一. ApplicationConfigServletServerApplicationContext
二. DefaultListableBeanFactory

3. 範例：使用BeanFactory來獲取bean。
```java=
BeanFactory factory = new ClassPathXmlApplicationContext("beans.xml");
MyBean myBean = (MyBean) factory.getBean("myBean");
```
BeanFactory在請求時創建bean，有助於減少啟動時間，適合大批量加載時使用。

## BeanDefinition
BeanDefinition用來描述Spring中的bean配置信息。每個bean在Spring中都有一個對應的BeanDefinition，它包含了bean的類型、作用域（singleton或prototype）、依賴、初始化方法等屬性。BeanDefinition允許你在容器創建bean之前定義其行為。
![螢幕擷取畫面 2024-11-14 150257](https://hackmd.io/_uploads/BJIAeXQz1e.png)

1. 作用：用來定義bean的元數據，描述bean的屬性、依賴和初始化配置。
2. 範例：自定義一個bean的BeanDefinition。
```java=
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(MyBean.class);
definition.setScope("singleton");
```
通常通過配置文件（如XML或Java Config）來自動生成BeanDefinition，而不是手動創建。


## BeanFactoryPostProcessor
BeanFactoryPostProcessor是一個擴展點，允許在容器加載bean定義後、實例化bean之前修改bean的屬性或進行額外的初始化。BeanFactoryPostProcessor通常用於修改或自定義bean的配置，像是修改某個bean的屬性值或添加一些依賴。
![螢幕擷取畫面 2024-11-14 150528](https://hackmd.io/_uploads/BJFwWQmfJl.png)

1. 作用：在bean定義被加載但bean尚未實例化時修改bean的屬性，適合做一些全局的bean配置。
2. 範例：修改bean定義的屬性。
```java=
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("myBean");
        beanDefinition.getPropertyValues().add("property", "newValue");
    }
}
```
使用方式：將MyBeanFactoryPostProcessor註冊為Spring的bean。

## Aware接口
Aware接口是一組接口，允許bean在Spring容器內部獲取Spring的某些屬性或資源，比如ApplicationContextAware、BeanFactoryAware等。實現這些接口的bean可以得到Spring容器中的一些上下文信息或其他bean資源。
![螢幕擷取畫面 2024-11-14 150658](https://hackmd.io/_uploads/rkJ6WXmzJl.png)

1. 作用：允許bean獲取Spring容器內的資源或上下文，方便bean與容器交互。
2. 範例：實現BeanFactoryAware來獲取BeanFactory。
```java=
public class MyBean implements BeanFactoryAware {
    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}
```
使用方式：Spring會自動注入相關的資源或上下文信息。

##  InitializingBean / DisposableBean
這兩個接口是Spring提供的生命週期回調接口。InitializingBean用於在bean的屬性設置完成後執行初始化邏輯，DisposableBean用於bean銷毀前執行清理操作。這些接口有助於確保bean在適當的時機完成初始化或清理工作。
1. 作用：提供在bean初始化或銷毀時執行的回調方法。
2. 範例：實現InitializingBean和DisposableBean來執行初始化和銷毀邏輯。
```java=
public class MyBean implements InitializingBean, DisposableBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Bean 初始化完成！");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("Bean 被銷毀！");
    }
}
```
使用方式：當bean實例化完成或容器銷毀時自動執行這些方法。

## BeanPostProcessor
BeanPostProcessor是一個重要的接口，允許在bean初始化過程中的不同階段進行操作。BeanPostProcessor在每個bean初始化之前和之後都會被調用，可以用來修改、包裝或代理bean，是Spring AOP的基礎。Spring內部有多種BeanPostProcessor實現，用來支持事務、驗證等功能。
![螢幕擷取畫面 2024-11-14 150835](https://hackmd.io/_uploads/HkOXz7QGkg.png)

1. 作用：在bean初始化過程中進行攔截或修改，實現AOP或其他特殊處理。
2. 範例：實現BeanPostProcessor來在bean初始化前後進行操作。
```java=
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean 初始化之前：" + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean 初始化之後：" + beanName);
        return bean;
    }
}
```
使用方式：將MyBeanPostProcessor註冊到容器中，適用於所有bean。

# 面試題
## SpringBoot啟動流程
new SpringApplication()
1. 確認web應用的類型
2. 載入ApplicationContextInitializer
3. 載入ApplicationListener
4. 記錄主啟動類

run()
1. 準備環境物件Environment，用於載入系統屬性等等
2. 印製Banner
3. 實例化容器Context
4. 準備容器，為容器設定Environment、BeanFactoryPostProcessor，並載入主類別5. 對應的BeanDefinition
6. 刷新容器(建立Bean實例)
7. 返回容器
![image](https://hackmd.io/_uploads/r1lIDiYfyx.png)

### 回答 
![螢幕擷取畫面 2024-11-14 151622](https://hackmd.io/_uploads/Sy6kVX7z1x.png)

## IOC容器初始化流程
1. 準備BeanFactory（DefaultListableBeanFactory）
 設定ClassLoader
 設定Environment
2. 掃描要放入容器中的Bean，得到對應的BeaDefinition(只掃描，不建立)
3. 註冊BeanPostProcessor
4. 處理國際化
5. 初始化事件多播器ApplicationEventMulticaster
6. 啟動tomcat
7. 綁定事件監聽器和事件多播器
8. 實例化非懶載入的單例Bean
9. 掃尾工作，例如清空實例化時所佔用的快取等
![螢幕擷取畫面 2024-11-14 151846](https://hackmd.io/_uploads/H1GqVQmzJg.png)
### 回答 

![螢幕擷取畫面 2024-11-14 152018](https://hackmd.io/_uploads/rkt04Q7fJg.png)

## Bean生命週期
AbstractAutowireCapableBeanFacotry.doCreateBean()
1. 建立對象:
一.實例化(建構方法)
二.依賴注入
2. 初始化
一. 執行Aware介面回呼
二. 執行BeanPostProcessor. postProcessBeforeInitialization
三. 執行InitializingBean回呼(先執行@PostConstruct)
四. 執行BeanPostProcessor. postProcessAfterInitialization
使用對象
銷毀對象:執行DisposableBean回呼(先執行@PreDestory)
![1000006146](https://hackmd.io/_uploads/rk7vUkWSkg.jpg)


![螢幕擷取畫面 2024-11-14 152644](https://hackmd.io/_uploads/r1tD8Qmz1x.png)
### 回答 
![螢幕擷取畫面 2024-11-14 152813](https://hackmd.io/_uploads/BJBnIX7zye.png)

## Bean循環依賴
循環依賴指的是依賴閉環的問題
![螢幕擷取畫面 2024-11-14 153225](https://hackmd.io/_uploads/rJbnPQXfkx.png)
![螢幕擷取畫面 2024-11-14 153451](https://hackmd.io/_uploads/ryC4_XXzke.png)
![螢幕擷取畫面 2024-11-14 153615](https://hackmd.io/_uploads/rJW9_7Qz1x.png)
### 回答 
![螢幕擷取畫面 2024-11-14 153722](https://hackmd.io/_uploads/rJrC_7QMkl.png)

## SpringMvc執行流程
Mvc接收到請求開始,到給瀏覽器回應之間的過程
![螢幕擷取畫面 2024-11-14 153847](https://hackmd.io/_uploads/SyPQtXmMJg.png)
![螢幕擷取畫面 2024-11-14 153951](https://hackmd.io/_uploads/B15DF77f1l.png)
### 回答
![螢幕擷取畫面 2024-11-14 154104](https://hackmd.io/_uploads/H1N2K7QfJl.png)

# 看題目寫答案
## 變數作用域
```java=
public class ScopeTest {
    public static void main(String[] args) {
        int x = 5;
        {
            int x = 10; // 錯誤嗎？
            System.out.println(x);
        }
    }
}
```
答案：
這段程式碼會編譯錯誤。
因為在 Java 中，內部區塊無法宣告與外部區塊同名的變數 x，這會導致變數名稱衝突。

## 字串不可變性
```java=
public class StringTest {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = s1;
        s1 = s1 + " World";
        System.out.println(s1); // 結果？
        System.out.println(s2); // 結果？
    }
}
```
答案：
```
Hello World
Hello
```
因為 String 是不可變的，s1 = s1 + " World" 創建了一個新的字串物件並賦值給 s1，但 s2 還指向原來的 "Hello"。

##  物件比較
```java=
public class ObjectTest {
    public static void main(String[] args) {
        Integer a = 127;
        Integer b = 127;
        Integer c = 128;
        Integer d = 128;
        System.out.println(a == b); // 結果？
        System.out.println(c == d); // 結果？
    }
}
```
答案：
```
true
false
```
因為 Java 對 Integer 進行了緩存，範圍是 -128 到 127。當數值在此範圍內時，會重用相同的物件，因此 a == b 為 true。而 128 超出緩存範圍，c 和 d 是不同的物件，因此 c == d 為 false。

## 多執行緒
```java=
public class ThreadTest {
    public static void main(String[] args) {
        Thread t = new Thread(() -> System.out.println("Running"));
        t.run();
        t.start();
    }
}
```

答案：
```
Running
Running
```
t.run() 是直接呼叫 run 方法，等同於執行一個普通的方法；t.start() 會啟動新執行緒，執行 run 方法，因此 Running 會被印出兩次。

## 自增運算
```java=
public class IncrementTest {
    public static void main(String[] args) {
        int x = 1;
        int y = x++;
        System.out.println(x); // 結果？
        System.out.println(y); // 結果？
    }
}
```
答案：
```
2
1
```
x++ 是後置遞增運算，表示先返回 x 的值，然後再遞增。因此，y 得到的是 x 原來的值 1，而 x 自增後變成 2。

## 遞迴陷阱
```java=
public class RecursionTest {
    public static void main(String[] args) {
        System.out.println(factorial(5));
    }

    public static int factorial(int n) {
        return n * factorial(n - 1); // 有錯嗎？
    }
}
```
答案：
這段程式會導致 StackOverflowError。
原因是遞迴沒有終止條件，導致無限遞迴。

修正版本：
```
public static int factorial(int n) {
    if (n == 1) return 1; // 終止條件
    return n * factorial(n - 1);
}
```
## switch 陷阱
```java=
public class SwitchTest {
    public static void main(String[] args) {
        int num = 2;
        switch (num) {
            case 1:
                System.out.println("One");
            case 2:
                System.out.println("Two");
            case 3:
                System.out.println("Three");
            default:
                System.out.println("Default");
        }
    }
}
```
答案：
```
Two
Three
Default
```
因為 switch 中沒有 break，所以匹配到 case 2 後，會繼續執行後續的 case 和 default 區塊。

修正版本（加上 break）：
```java=
switch (num) {
    case 1:
        System.out.println("One");
        break;
    case 2:
        System.out.println("Two");
        break;
    case 3:
        System.out.println("Three");
        break;
    default:
        System.out.println("Default");
}
```
## 浮點數比較
```java=
public class FloatTest {
    public static void main(String[] args) {
        float f1 = 0.1f;
        float f2 = 0.2f;
        float f3 = 0.3f;
        System.out.println(f1 + f2 == f3); // 結果？
    }
}
```
答案：false
浮點數在計算機中以二進位儲存，可能產生精度誤差，因此 f1 + f2 的結果不完全等於 f3。

解決方法：使用 BigDecimal 進行精確計算。

## 自增順序
```java=
public class IncrementOrder {
    public static void main(String[] args) {
        int i = 0;
        i = i++ + ++i;
        System.out.println(i); // 結果？
    }
}
```
答案：2
解析：
i++：返回 0，但 i 自增變成 1。
++i：在加法之前執行，自增後變成 2。
加法：0 + 2 = 2，最後將結果賦值給 i。

## 覆寫與多態
```java=
class Parent {
    public void show() {
        System.out.println("Parent");
    }
}

class Child extends Parent {
    public void show() {
        System.out.println("Child");
    }
}

public class PolymorphismTest {
    public static void main(String[] args) {
        Parent p = new Child();
        p.show(); // 結果？
    }
}
```
答案:Child
因為在執行階段，方法的調用由物件的實際類型決定，而不是引用的類型

## 靜態方法覆蓋
```java=
class Parent {
    public static void show() {
        System.out.println("Parent");
    }
}

class Child extends Parent {
    public static void show() {
        System.out.println("Child");
    }
}

public class StaticTest {
    public static void main(String[] args) {
        Parent p = new Child();
        p.show(); // 結果？
    }
}
```
答案：Parent
靜態方法不支援多態，方法的調用取決於變數的引用類型，而非物件的實際類型。

## 過載與覆寫
```java=
class Test {
    public void print(String str) {
        System.out.println("String");
    }

    public void print(Object obj) {
        System.out.println("Object");
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.print(null); // 結果？
    }
}
```
答案：String
因為在過載方法中，Java 優先選擇參數類型最具體的方法。String 是 Object 的子類，因此選擇了 print(String str)

## finally 執行順序
```java=
public class FinallyTest {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static int test() {
        try {
            return 1;
        } finally {
            return 2;
        }
    }
}
```
答案:2
因為 finally 區塊在 try 或 catch 返回之前執行，並覆蓋了 try 中的返回值。

## 集合問題
```java=
import java.util.HashSet;

public class HashSetTest {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("A");
        set.add(new String("A"));
        set.add("B");
        System.out.println(set.size()); // 結果？
    }
}
```
答案：2
因為 HashSet 使用 equals() 和 hashCode() 判斷重複項，兩個 "A" 的內容相同，視為重複。

## StringBuilder 與執行緒安全
```java=
public class StringBuilderTest {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");
        sb.append(" World");
        System.out.println(sb.toString()); // 結果？
    }
}
```
答案：Hello World
StringBuilder 是非執行緒安全的，但在單執行緒環境下使用沒有問題。如果需要執行緒安全，應使用 StringBuffer。

## JVM 相關問題
## Java 垃圾回收 (Garbage Collection)
```java=
public class GCTest {
    public static void main(String[] args) {
        GCTest obj1 = new GCTest();
        GCTest obj2 = new GCTest();

        obj1 = obj2; // obj1 原本的物件會被回收嗎？
    }
}
```
答案：obj1 原本的物件會被回收。
當 obj1 = obj2; 時，obj1 原本指向的物件沒有其他引用，會被判定為垃圾，在垃圾回收器運行時被清除。

## 方法區與堆內存
```java=
public class MethodAreaTest {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello";
        String s3 = new String("Hello");

        System.out.println(s1 == s2); // 結果？
        System.out.println(s1 == s3); // 結果？
    }
}
```
答案：
```
true
false
```
1. s1 和 s2 指向的是字符串池中的同一個常量，== 比較地址，相等。
2. s3 是用 new 創建的新物件，位於堆內存，與字符串池中的地址不同。

## 直接內存 (Direct Memory)
問題：
Java 中的直接內存是什麼？如何使用？

答案：
直接內存是 JVM 之外的一塊內存，通過 java.nio.ByteBuffer 管理，適合處理大文件或頻繁 I/O 操作。

範例：
```java=
import java.nio.ByteBuffer;

public class DirectMemoryTest {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        buffer.putInt(100);
        buffer.flip();
        System.out.println(buffer.getInt()); // 結果：100
    }
}
```
# 設計模式相關問題
## 單例模式 (Singleton Pattern)
實現一個線程安全的單例模式。
範例：
```java=
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
```
問題：
為什麼使用 volatile 和雙重檢查？

答案：
1. volatile 確保多執行緒訪問時，變數的可見性，防止指令重排序。
2. 雙重檢查 避免每次都進行同步，提高效率。

## 工廠模式 (Factory Pattern)
```java=
interface Shape {
    void draw();
}

class Circle implements Shape {
    public void draw() {
        System.out.println("Drawing Circle");
    }
}

class Rectangle implements Shape {
    public void draw() {
        System.out.println("Drawing Rectangle");
    }
}

class ShapeFactory {
    public static Shape getShape(String type) {
        if (type.equalsIgnoreCase("Circle")) {
            return new Circle();
        } else if (type.equalsIgnoreCase("Rectangle")) {
            return new Rectangle();
        }
        return null;
    }
}

public class FactoryTest {
    public static void main(String[] args) {
        Shape shape = ShapeFactory.getShape("Circle");
        shape.draw(); // 結果？
    }
}
```
答案:Drawing Circle

# 框架相關問題
問題：Spring 中有哪些常見的 Bean 作用域？區別是什麼？

答案：
1. Singleton (預設)：容器內每個 Bean 只有一個實例。
2. Prototype：每次請求都會返回一個新的實例。
3. Request：每個 HTTP 請求生成一個 Bean 實例。
4. Session：每個 HTTP session 生成一個 Bean 實例。
5. GlobalSession：為全局 Session (少見)。
範例：
```java=
@Component
@Scope("prototype") // 配置為原型作用域
public class PrototypeBean {
}
```
## Hibernate 實體緩存 (Entity Cache)
問題：Hibernate 中的一級緩存與二級緩存的區別是什麼？
答案：
1. 一級緩存：
    1. 預設啟用，與 Session 綁定。
    2. 範圍是 Session，Session 關閉後緩存失效。
2. 二級緩存：
    1. 需手動配置。
    2. 與 SessionFactory 綁定，跨 Session 可重用。
範例：
```java=
// Hibernate 配置二級緩存
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
    </session-factory>
</hibernate-configuration>
```

## Spring AOP 切面
```java=
@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
        System.out.println("Method execution starts");
    }
}
```
問題：這段程式會攔截什麼樣的方法？
答案：它會攔截 com.example.service 包下的所有類，所有方法，無論參數如何。

## MyBatis 動態 SQL
```java=
<select id="findUserByCondition" parameterType="Map" resultType="User">
    SELECT * FROM user
    <where>
        <if test="username != null">
            AND username = #{username}
        </if>
        <if test="email != null">
            AND email = #{email}
        </if>
    </where>
</select>
```
問題：傳入 username = "John" 且 email = null，生成的 SQL 是什麼？

答案：
```
SELECT * FROM user WHERE username = 'John'
```

## Spring Boot 啟動流程
問題：Spring Boot 啟動的核心類是什麼？其作用是什麼？

答案：核心類是 SpringApplication，它負責：
1. 加載應用上下文。
2. 啟動內嵌伺服器（例如 Tomcat）。
3. 自動配置（基於 @SpringBootApplication）。
範例：
```java=
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
```

# 邏輯分析題目答案
## null 參數處理
```java=
public class NullTest {
    public static void main(String[] args) {
        print(null);
    }

    public static void print(String s) {
        System.out.println("String version");
    }

    public static void print(Object o) {
        System.out.println("Object version");
    }
}
```
問題：程式會執行哪個方法？為什麼？
分析方法：
1. 分析參數的類型：null 可以適配任意參考型別。
2. 比較重載方法的匹配度：Java 選擇匹配度更高的版本。

答案：String version
因為 print(String s) 比 print(Object o) 更具體，null 優先匹配 String。

## 陣列的協變
```java=
public class ArrayCovarianceTest {
    public static void main(String[] args) {
        Object[] array = new String[5];
        array[0] = "Hello";
        array[1] = 123; // 是否會拋出異常？
    }
}
```
問題：上述程式碼執行會發生什麼？
分析方法：
1. 確認陣列型別：Object[] 被初始化為 String[]。
2. 判斷插入元素時是否符合陣列元素型別。

答案：程式會拋出 ArrayStoreException。

因為 array 實際上是 String[]，插入非 String 類型（如 Integer）會違反類型安全。

## Integer 比較陷阱
```
public class IntegerTest {
    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        Integer c = 200;
        Integer d = 200;

        System.out.println(a == b); // 結果？
        System.out.println(c == d); // 結果？
    }
}
```
問題：為什麼 a == b 和 c == d 的結果不同？

分析方法：
1. 分析 Integer 的緩存機制：-128 到 127 會被緩存。
2. 超出範圍的數值會創建新對象。

答案: 
```
true
false
```
1. a 和 b 指向相同的緩存對象。
2. c 和 d 是不同的對象。

## 流程控制中的條件順序
```
public class ConditionalOrder {
    public static void main(String[] args) {
        int x = 0;
        if (x == 1 && ++x > 0) {
            System.out.println("Inside if");
        }
        System.out.println("x = " + x);
    }
}
```
問題：x 的值為什麼沒有改變？

分析方法：
1. 分析條件短路特性：&& 遇到 false 時不執行右側條件。
2. 確認 ++x 是否被執行。

答案：x = 0
由於 x == 1 為 false，&& 的右側 ++x 不會被執行。

## try-catch-finally 的執行順序
```
public class TryCatchTest {
    public static void main(String[] args) {
        System.out.println(getValue());
    }

    public static int getValue() {
        try {
            return 1;
        } catch (Exception e) {
            return 2;
        } finally {
            return 3;
        }
    }
}
```
問題：最後返回的值是什麼？為什麼？

分析方法：
1. 分析 finally 區塊的特性：即使有 return，finally 仍會執行。
2. 判斷 finally 是否覆蓋其他區塊的返回值。

答案：3 
finally 覆蓋了 try 和 catch 區塊的返回值。
## 靜態初始化的順序
```
public class StaticInitTest {
    static {
        System.out.println("Static block");
    }

    public StaticInitTest() {
        System.out.println("Constructor");
    }

    public static void main(String[] args) {
        StaticInitTest test = new StaticInitTest();
    }
}
```
問題：執行順序是什麼？為什麼？

分析方法：
1. 分析類的加載時機：static 區塊在類加載時執行。
2. 分析建構函數的執行：實例化時才會執行建構函數。

答案：
```
Static block
Constructor
```
static 區塊先於建構函數執行。
## 執行緒安全問題
```
public class ThreadSafetyTest {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) {
        ThreadSafetyTest test = new ThreadSafetyTest();

        for (int i = 0; i < 1000; i++) {
            new Thread(test::increment).start();
        }

        System.out.println("Final count: " + test.getCount());
    }
}
```
問題：
執行結果是否一定為 1000？為什麼？

分析方法：
1. 確認多執行緒中同步方法是否涵蓋所有操作。
2. 分析 getCount 是否同步。

答案：結果可能小於 1000。
increment 方法是同步的，但 getCount 不是，可能導致非同步讀取的值不準確。

## 資源釋放與 try-with-resources
```
import java.io.Closeable;

public class ResourceTest implements Closeable {
    public void doSomething() {
        System.out.println("Doing something");
    }

    @Override
    public void close() {
        System.out.println("Resource closed");
    }

    public static void main(String[] args) {
        try (ResourceTest resource = new ResourceTest()) {
            resource.doSomething();
        }
    }
}
```
問題：上述程式的執行順序是什麼？

分析方法：
1. 分析 try-with-resources 自動關閉機制。
2. 確認 close 方法的執行時機。

答案：
```
Doing something
Resource closed
```
try-with-resources 保證 close 方法在 try 區塊結束後執行。
## 死鎖問題
```java=
public class DeadlockTest {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock1) {
                try { Thread.sleep(50); } catch (InterruptedException e) { }
                synchronized (lock2) {
                    System.out.println("Thread 1 acquired lock2");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lock2) {
                synchronized (lock1) {
                    System.out.println("Thread 2 acquired lock1");
                }
            }
        }).start();
    }
}
```
問題：程式是否有死鎖？如何解決？

分析方法：
1. 確認鎖的獲取順序是否一致。
2. 尋找可能的競爭條件。

答案：
1. 程式會發生死鎖。
2. 兩個執行緒分別持有 lock1 和 lock2，互相等待釋放。

解決方法： 確保所有執行緒以相同順序獲取鎖。

# java常見手寫題
1. 反轉字串
題目： 寫一個方法來反轉一個字串，例如輸入 "hello"，返回 "olleh"。
解題思路：
    1. 使用雙指針。
    2. 使用 StringBuilder 或 char[]。
    3. 範例程式碼：
```java=
public class ReverseString {
    public static String reverse(String str) {
        if (str == null || str.length() <= 1) return str;
        return new StringBuilder(str).reverse().toString();
    }

    public static void main(String[] args) {
        System.out.println(reverse("hello")); // 輸出：olleh
    }
}
```
2. 判斷迴文
題目： 寫一個方法判斷一個字串是否是迴文，例如 "level" 是迴文，但 "hello" 不是。
解題思路：
    1. 雙指針比較頭尾字符。
    2. 迴圈檢查。
    3. 範例程式碼：
```java=
public class Palindrome {
    public static boolean isPalindrome(String str) {
        int left = 0, right = str.length() - 1;
        while (left < right) {
            if (str.charAt(left) != str.charAt(right)) return false;
            left++;
            right--;
        }
        return true;
    }

    public static void main(String[] args) {
        System.out.println(isPalindrome("level")); // 輸出：true
        System.out.println(isPalindrome("hello")); // 輸出：false
    }
}
```
3. 實現單例模式
題目： 實現一個單例類別，確保全局只有一個實例。
解題思路：
    1. 使用懶漢式或餓漢式。
    2. 確保線程安全。
    3. 範例程式碼：
```java=
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
```
4. 實現簡單的生產者消費者模型
題目： 使用 wait() 和 notify() 實現生產者消費者模型。
解題思路：
    1. 使用 Object 作為鎖。
    2. 配合 wait 和 notify 控制。
    3. 範例程式碼：
```java=
import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumer {
    private static final Queue<Integer> queue = new LinkedList<>();
    private static final int CAPACITY = 5;

    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            int value = 0;
            while (true) {
                synchronized (queue) {
                    while (queue.size() == CAPACITY) {
                        try {
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.add(value);
                    System.out.println("Produced: " + value++);
                    queue.notifyAll();
                }
            }
        });

        Thread consumer = new Thread(() -> {
            while (true) {
                synchronized (queue) {
                    while (queue.isEmpty()) {
                        try {
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    int value = queue.poll();
                    System.out.println("Consumed: " + value);
                    queue.notifyAll();
                }
            }
        });

        producer.start();
        consumer.start();
    }
}
```
5. 兩數相加
題目： 寫一個方法實現兩個數字相加，輸入為兩個非空的鏈結串列，輸出為相加後的鏈結串列。
解題思路：
    1. 使用鏈結串列模擬加法。
    2. 注意進位處理。
    3. 範例程式碼：
```java=
class ListNode {
    int val;
    ListNode next;

    ListNode(int val) {
        this.val = val;
    }
}

public class AddTwoNumbers {
    public static ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode p = l1, q = l2, current = dummy;
        int carry = 0;
        while (p != null || q != null) {
            int x = (p != null) ? p.val : 0;
            int y = (q != null) ? q.val : 0;
            int sum = x + y + carry;
            carry = sum / 10;
            current.next = new ListNode(sum % 10);
            current = current.next;
            if (p != null) p = p.next;
            if (q != null) q = q.next;
        }
        if (carry > 0) {
            current.next = new ListNode(carry);
        }
        return dummy.next;
    }
}
```
6. 實現二分搜尋法
題目： 寫一個方法實現二分搜尋法，查找一個排序數組中的目標值，返回其索引，若不存在返回 -1。
解題思路：
    1. 使用雙指針 (left 和 right)。
    2. 每次比較中間值與目標值。
    3. 範例程式碼：
```java=
public class BinarySearch {
    public static int binarySearch(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4, 5, 6};
        System.out.println(binarySearch(nums, 4)); // 輸出：3
    }
}
```
7. 實現快排（Quicksort）
題目： 寫一個方法實現快速排序。
解題思路：
    1. 選擇基準點進行劃分。
    2. 遞迴排序左右子陣列。
    3. 範例程式碼：
```java=
public class QuickSort {
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pivot = partition(arr, low, high);
            quickSort(arr, low, pivot - 1);
            quickSort(arr, pivot + 1, high);
        }
    }

    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        for (int j = low; j < high; j++) {
            if (arr[j] < pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, high);
        return i + 1;
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {4, 2, 6, 5, 1, 3};
        quickSort(arr, 0, arr.length - 1);
        for (int num : arr) {
            System.out.print(num + " ");
        }
        // 輸出：1 2 3 4 5 6
    }
}
```
8. 計算字串中每個字符出現的次數
題目： 計算輸入字串中每個字符的出現次數。
解題思路：
    1. 使用 HashMap 儲存字符及其計數。
    2. 範例程式碼：
```java=
import java.util.HashMap;

public class CharacterCount {
    public static void countCharacters(String str) {
        HashMap<Character, Integer> map = new HashMap<>();
        for (char c : str.toCharArray()) {
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        map.forEach((key, value) -> System.out.println(key + ": " + value));
    }

    public static void main(String[] args) {
        countCharacters("hello world");
        // 輸出：h: 1, e: 1, l: 3, o: 2, w: 1, r: 1, d: 1
    }
}
```
9. 合併兩個排序數組
題目： 合併兩個已排序的數組，並返回新的排序數組。
解題思路：
    1. 使用雙指針。
    2. 比較並插入到新數組。
    3. 範例程式碼：
```java=
import java.util.Arrays;

public class MergeSortedArrays {
    public static int[] merge(int[] nums1, int[] nums2) {
        int[] result = new int[nums1.length + nums2.length];
        int i = 0, j = 0, k = 0;
        while (i < nums1.length && j < nums2.length) {
            if (nums1[i] < nums2[j]) {
                result[k++] = nums1[i++];
            } else {
                result[k++] = nums2[j++];
            }
        }
        while (i < nums1.length) {
            result[k++] = nums1[i++];
        }
        while (j < nums2.length) {
            result[k++] = nums2[j++];
        }
        return result;
    }

    public static void main(String[] args) {
        int[] nums1 = {1, 3, 5};
        int[] nums2 = {2, 4, 6};
        System.out.println(Arrays.toString(merge(nums1, nums2)));
        // 輸出：[1, 2, 3, 4, 5, 6]
    }
}
```
10. 設計 LRU Cache
題目： 設計一個 LRU（最近最少使用）快取系統，實現 get 和 put 操作。
解題思路：
    1. 使用 LinkedHashMap。
    2. 保證快取容量固定，當超出容量時移除最舊的元素。
    3. 範例程式碼：
```java=
import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(3);
        cache.put(1, "one");
        cache.put(2, "two");
        cache.put(3, "three");
        System.out.println(cache.keySet()); // 輸出：[1, 2, 3]

        cache.get(1); // 使用 key=1
        cache.put(4, "four"); // 插入 key=4，移除最舊的 key=2
        System.out.println(cache.keySet()); // 輸出：[3, 1, 4]
    }
}
```
11. 計算費波那契數列
題目： 寫一個方法計算費波那契數列的第 N 項。
解題思路：
    1. 使用遞迴（低效）。
    2. 使用動態規劃（高效）。
    3. 範例程式碼：
```java=
public class Fibonacci {
    public static int fibonacci(int n) {
        if (n <= 1) return n;
        int a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
            int sum = a + b;
            a = b;
            b = sum;
        }
        return b;
    }

    public static void main(String[] args) {
        System.out.println(fibonacci(10)); // 輸出：55
    }
}
```
12. 判斷一個數是否為質數
題目： 寫一個方法判斷是否為質數。
解題思路：
    1. 從 2 開始遍歷到平方根。
    2. 範例程式碼：
```java=
public class PrimeNumber {
    public static boolean isPrime(int num) {
        if (num <= 1) return false;
        for (int i = 2; i <= Math.sqrt(num); i++) {
            if (num % i == 0) return false;
        }
        return true;
    }

    public static void main(String[] args) {
        System.out.println(isPrime(29)); // 輸出：true
        System.out.println(isPrime(10)); // 輸出：false
    }
}
```
13. 找出數組中的最大值和最小值
題目： 寫一個方法找出數組中的最大值和最小值，並返回一個包含兩者的陣列。
解題思路：
    1. 遍歷數組，更新最大值與最小值。
    2. 範例程式碼：
```java=
public class MinMax {
    public static int[] findMinMax(int[] nums) {
        if (nums == null || nums.length == 0) throw new IllegalArgumentException("Array is empty");
        int min = nums[0], max = nums[0];
        for (int num : nums) {
            if (num < min) min = num;
            if (num > max) max = num;
        }
        return new int[]{min, max};
    }

    public static void main(String[] args) {
        int[] nums = {3, 5, 1, 9, 2};
        int[] result = findMinMax(nums);
        System.out.println("Min: " + result[0] + ", Max: " + result[1]);
        // 輸出：Min: 1, Max: 9
    }
}
```
14. 合併區間
題目： 給定一個區間的集合，合併重疊的區間。例如，[[1,3],[2,6],[8,10],[15,18]] 會合併為 [[1,6],[8,10],[15,18]]。
解題思路：
    1. 按起始點排序。
    2. 遍歷並合併重疊區間。
    3. 範例程式碼：
```java=
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MergeIntervals {
    public static int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
        List<int[]> merged = new ArrayList<>();
        for (int[] interval : intervals) {
            if (merged.isEmpty() || merged.get(merged.size() - 1)[1] < interval[0]) {
                merged.add(interval);
            } else {
                merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], interval[1]);
            }
        }
        return merged.toArray(new int[merged.size()][]);
    }

    public static void main(String[] args) {
        int[][] intervals = {{1, 3}, {2, 6}, {8, 10}, {15, 18}};
        int[][] result = merge(intervals);
        for (int[] interval : result) {
            System.out.println(Arrays.toString(interval));
        }
        // 輸出：[[1, 6], [8, 10], [15, 18]]
    }
}
```
15. 實現斐波那契數列的記憶化遞迴
題目： 使用記憶化的方式計算第 N 項斐波那契數。
解題思路：
    1. 使用一個陣列來記錄已計算的結果。
    2. 範例程式碼：
```java=
public class FibonacciMemoization {
    private static int[] memo;

    public static int fib(int n) {
        if (n <= 1) return n;
        if (memo[n] != 0) return memo[n];
        memo[n] = fib(n - 1) + fib(n - 2);
        return memo[n];
    }

    public static void main(String[] args) {
        int n = 10;
        memo = new int[n + 1];
        System.out.println(fib(n)); // 輸出：55
    }
}
```
16. 實現簡單的 HashMap
題目： 用陣列和鏈結串列實現一個簡單的 HashMap，支持 put 和 get。
解題思路：
    1. 使用陣列存放桶，鏈結串列解決碰撞。
    2. 範例程式碼：
```java=
import java.util.LinkedList;

class MyHashMap<K, V> {
    private static final int SIZE = 16;
    private LinkedList<Entry<K, V>>[] buckets;

    public MyHashMap() {
        buckets = new LinkedList[SIZE];
        for (int i = 0; i < SIZE; i++) {
            buckets[i] = new LinkedList<>();
        }
    }

    public void put(K key, V value) {
        int index = key.hashCode() % SIZE;
        LinkedList<Entry<K, V>> bucket = buckets[index];
        for (Entry<K, V> entry : bucket) {
            if (entry.key.equals(key)) {
                entry.value = value;
                return;
            }
        }
        bucket.add(new Entry<>(key, value));
    }

    public V get(K key) {
        int index = key.hashCode() % SIZE;
        LinkedList<Entry<K, V>> bucket = buckets[index];
        for (Entry<K, V> entry : bucket) {
            if (entry.key.equals(key)) {
                return entry.value;
            }
        }
        return null;
    }

    static class Entry<K, V> {
        K key;
        V value;

        Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    public static void main(String[] args) {
        MyHashMap<String, Integer> map = new MyHashMap<>();
        map.put("one", 1);
        map.put("two", 2);
        System.out.println(map.get("one")); // 輸出：1
        System.out.println(map.get("three")); // 輸出：null
    }
}
```
17. 多線程打印奇數和偶數
題目： 使用兩個線程交替打印 1 到 N 的奇數和偶數。
解題思路：
    1. 使用 synchronized 和共享鎖。
    2. 範例程式碼：
```java=
public class PrintOddEven {
    private static final Object lock = new Object();
    private static int number = 1;

    public static void main(String[] args) {
        Thread oddThread = new Thread(() -> {
            while (number <= 10) {
                synchronized (lock) {
                    if (number % 2 == 1) {
                        System.out.println("Odd: " + number);
                        number++;
                        lock.notify();
                    } else {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        Thread evenThread = new Thread(() -> {
            while (number <= 10) {
                synchronized (lock) {
                    if (number % 2 == 0) {
                        System.out.println("Even: " + number);
                        number++;
                        lock.notify();
                    } else {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        oddThread.start();
        evenThread.start();
    }
}
```
18. 實現線程安全的 Singleton
題目： 實現一個線程安全的單例模式，確保多線程環境下的安全性。
解題思路：
    1. 使用雙重檢查鎖（Double-Checked Locking）。
    2. 範例程式碼：
```java=
public class ThreadSafeSingleton {
    private static volatile ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {}

    public static ThreadSafeSingleton getInstance() {
        if (instance == null) {
            synchronized (ThreadSafeSingleton.class) {
                if (instance == null) {
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
    }
}
```

用到的資源:bilibili的黑馬程式設計師的影片整理的重點