# 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的黑馬程式設計師的影片整理的重點