# 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之前定義其行為。

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的屬性值或添加一些依賴。

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資源。

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實現,用來支持事務、驗證等功能。

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. 返回容器

### 回答

## IOC容器初始化流程
1. 準備BeanFactory(DefaultListableBeanFactory)
設定ClassLoader
設定Environment
2. 掃描要放入容器中的Bean,得到對應的BeaDefinition(只掃描,不建立)
3. 註冊BeanPostProcessor
4. 處理國際化
5. 初始化事件多播器ApplicationEventMulticaster
6. 啟動tomcat
7. 綁定事件監聽器和事件多播器
8. 實例化非懶載入的單例Bean
9. 掃尾工作,例如清空實例化時所佔用的快取等

### 回答

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


### 回答

## Bean循環依賴
循環依賴指的是依賴閉環的問題



### 回答

## SpringMvc執行流程
Mvc接收到請求開始,到給瀏覽器回應之間的過程


### 回答

# 看題目寫答案
## 變數作用域
```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的黑馬程式設計師的影片整理的重點