文章目錄
- java基礎知識
- this變量
- 方法可變參數
- 構造方法
- 繼承的關鍵字
- protected
- super
- 阻止繼承
- 方法重載
- 向上轉型和向下轉型
- 多態
- 抽象
- 接口
- static靜態字段
- default方法
- 包
- final
- 內部類
- java序列化與反序列化
- 反射
- urldns鏈
- 動態代理
- 類加載器(ClassLoader)
- 雙親委派模型
- Spring Boot 2
- 1.Spring Boot 2
- 2. Maven
- 特點
- 依賴管理
- 在 Spring Boot 2 中的具體例子:
- 自動版本仲裁
- 自動配好Tomcat
- **3.Tomcat 是什么?**
- **Tomcat 的主要功能**
- **在 Spring Boot 中的作用**
- 4.自動配好SpringMVC
- **Spring MVC 和 Tomcat 的關系**
- 配置生成對應的類
- 容器
- **容器的組件添加功能如何工作?**
- **組件添加的常見方式**
- **使用注解(最常見)**
- 0字節截斷
跟隨Y4的路線學習
https://github.com/Y4tacker/JavaSec
java基礎知識
中途看到一個很好的網站
https://liaoxuefeng.com/books/java/quick-start/index.html
this變量
this 是一個變量,指向當前正在執行代碼的那個對象。
class Person {String name;void setName(String name) {this.name = name; // this.name 是成員變量,name 是參數}
}
方法可變參數
可變參數用類型...
定義,可變參數相當于數組類型:
class Group {private String[] names;public void setNames(String... names) {this.names = names;}
}Group g = new Group();
g.setNames("Xiao Ming", "Xiao Hong", "Xiao Jun"); // 傳入3個String
g.setNames("Xiao Ming", "Xiao Hong"); // 傳入2個String
構造方法
如果既要能使用帶參數的構造方法,又想保留不帶參數的構造方法,那么只能把兩個構造方法都定義出來:
public Person() {}public Person(String name, int age) {this.name = name;this.age = age;
}
將屬性定義為private,外部無法直接訪問,通過方法才可以訪問,而idea有自動生成get方法和set方法的快捷鍵,將鼠標移動到類里,按下alt+insert。蛋疼的是新鍵盤沒有insert…于是改成contrl+shift+G了
繼承的關鍵字
protected
一個protected
字段和方法可以被其子類,以及子類的子類所訪問
super
class Person {protected String name;protected int age;public Person(String name, int age) {this.name = name;this.age = age;}
}class Student extends Person {protected int score;public Student(String name, int age, int score) {this.score = score;}
}
運行上面的代碼,會得到一個編譯錯誤,大意是在Student
的構造方法中,無法調用Person
的構造方法。
這是因為在Java中,任何class
的構造方法,第一行語句必須是調用父類的構造方法。如果沒有明確地調用父類的構造方法,編譯器會幫我們自動加一句super();
,所以,Student
類的構造方法實際上是這樣:
class Student extends Person {protected int score;public Student(String name, int age, int score) {super(); // 自動調用父類的構造方法this.score = score;}
}
但是,Person
類并沒有無參數的構造方法,因此,編譯失敗。
解決方法是調用Person
類存在的某個構造方法。例如:
class Student extends Person {protected int score;public Student(String name, int age, int score) {super(name, age); // 調用父類的構造方法Person(String, int)this.score = score;}
}
阻止繼承
從Java 15開始,允許使用sealed
修飾class,并通過permits
明確寫出能夠從該class繼承的子類名稱。
例如,定義一個Shape
類:
public sealed class Shape permits Rect, Circle, Triangle {...
}
方法重載
直接寫方法就行,要求參數個數或位置不能完全一致
向上轉型和向下轉型
向上轉型是可以的,即子類可以轉為父類,但向下轉型是不允許的,在類型轉換時,父類->子類無法憑空變出多的那部分功能。
Person p = new Student();
可以理解為將新建的Student實例轉化為person類,可以對person類進行相關的操作
為了避免向下轉型出錯,Java提供了instanceof
操作符,可以先判斷一個實例究竟是不是某種類型:
Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false
instanceof判斷一個實例是否是指定類或指定類的子類,從Java 14開始,判斷instanceof
后,可以直接轉型為指定變量,避免再次強制轉型。
在邏輯關系不適合使用繼承時可以使用組合,類可以擁有其他類的實例,嵌套之后可以拿到其他類的方法和屬性
class Student extends Person {protected Book book;protected int score;
}
多態
Override(重寫)和Overload(重載)是兩個與方法相關的概念
在 Java 中,方法簽名由 方法名 和 參數列表(包括參數的類型、數量和順序)組成。
Override 發生在子類中,當子類重新定義(重寫)父類中已有的方法時,稱為重寫。重寫的目的是讓子類提供自己的方法實現,覆蓋父類的實現。條件:
-
方法簽名(方法名 + 參數列表)必須與父類中的方法完全相同。
-
返回類型必須相同(或子類方法返回類型是父類方法返回類型的子類,稱為協變返回類型)。
-
訪問修飾符不能比父類更嚴格(例如,父類是 public,子類不能是 protected 或 private)。
-
子類方法拋出的異常不能比父類方法拋出的異常范圍更廣。
-
通常使用 @Override 注解來明確表示這是一個重寫方法,防止錯誤。
Overload 發生在同一個類中,當定義多個方法名相同但參數列表不同的方法時,稱為方法重載。重載方法是獨立的新方法,互不干擾。
條件:
- 方法名必須相同。
- 參數列表必須不同(參數數量或參數類型不同)。
- 返回類型、訪問修飾符、異常聲明可以不同,但這些不影響重載的判斷。
多態的特性就是,運行期才能動態決定調用的子類方法。對某個類型調用某個方法,執行的實際方法可能是某個子類的覆寫方法。
抽象
使用abstract
修飾的類就是抽象類。抽象類無法實例化
接口
如果抽象類所有方法都是抽象方法,就可以把該類改寫為接口
interface Person {void run();String getName();
}
接口定義的所有方法默認都是public abstract
的,所以這兩個修飾符寫不寫效果都一樣。接口不能定義實例字段
static靜態字段
特性 | 實例字段 | 靜態字段 |
---|---|---|
修飾符 | 無static | 使用static |
所屬 | 屬于對象 | 屬于類 |
內存分配 | 每個對象一份,存儲在堆中 | 類加載時分配,存儲在方法區 |
訪問方式 | 通過對象實例(如obj.field) | 通過類名或對象(如Class.field) |
生命周期 | 與對象一致 | 與類的生命周期一致 |
static修飾的屬性可以直接通過類名訪問,修改會影響到所有用到該屬性的實例,因為所有實例都會共享該字段
default方法
定義:在接口中使用default關鍵字修飾的方法,提供了方法的具體實現,而非抽象方法。
向接口添加default方法不會破壞現有實現類的代碼,因為它們自動繼承默認實現。可以用 @Override重寫
包
包沒有父子關系。java.util和java.util.zip是不同的包,兩者沒有任何繼承關系。
查找class的順序,先查當前package,再查import的包,最后查java.lang包
final
final修飾class可以阻止被繼承
final修飾method可以阻止被子類覆寫
final修飾局部變量可以阻止被重新賦值
package abc;public class Hello {protected void hi(final int t) {t = 1; // error!}
}
內部類
定義在一個類內部的類,要實例化一個內部類,首先需要實例化它的外部類
Outer.Inner inner = outer.new Inner();
內部類除了可以引用外部類實例,還可以修改外部類的private字段
匿名類(Anonymous Class)是:Java 中一種沒有顯式名稱的類,通常用于一次性使用的場景
定義的時候就{}加入類體,new后一般跟父類名或接口名
interface MyInterface {void doSomething();
}public class Test {public static void main(String[] args) {// 匿名類實現 MyInterfaceMyInterface obj = new MyInterface() {@Overridepublic void doSomething() {System.out.println("Doing something");}};obj.doSomething();}
}
靜態內部類:static
修飾的內部類和Inner Class有很大的不同,它不再依附于Outer
的實例,而是一個完全獨立的類,因此無法引用Outer.this
,但它可以訪問Outer
的private
靜態字段和靜態方法。如果把StaticNested
移到Outer
之外,就失去了訪問private
的權限。
java序列化與反序列化
序列化的類必須實現Serializable接口,實例可以序列化,類不可以,所以類中的static修飾的成員變量也不可以,transient標記的變量同樣不可以反序列化
可以重寫writeObject和readObject方法來自定義序列化和反序列化
安全問題的產生:只要反序列化數據,重寫的readObject方法肯定就會自動執行
- 入口類的readObject方法里本身就存在危險方法
- 入口類參數中含有其他可控類,該類有危險方法
- 入口類參數中包含可控類,該類繼續調用其他類
反射
可以通過反射的機制修改某些成員的屬性
獲取對象的類
public class ReflectionTest {public static void main(String[] args) {Person p=new Person("abc",20);Class c=p.getClass();//反射就是操作Class}
}
從原型Class實例化對象(無參構造和有參構造)
//無參構造方法
Class c=p.getClass();
c.newInstance();
//有參構造方法
Constructor tmp=c.getConstructor(String.class,int.class);
Person person2=(Person) tmp.newInstance("cc",10);
獲取類里面屬性
c.getField(p.name);
c.getFields();
c.getDeclaredFields();//獲取到所有修飾符修飾的屬性
修改private成員屬性
Field age=c.getDeclaredField("age");
age.setAccessible(true);
age.set(person2,21);
System.out.println(person2);
訪問類里面方法
Method[] m=c.getDeclaredMethods();
for(Method m1:m){System.out.println(m1);
}
調用類里面方法
Method m=c.getMethod("act1");
m.invoke(person2);//如果函數需要接收參數
Method m=c.getMethod("act1",String.class);
m.invoke(person2,"aaa");//私有方法訪問與私有屬性一樣
urldns鏈
沒錯,來到了入門必見的一條鏈子
這條鏈子雖然簡單,但是對我這樣的小白來說,一下子有點想不明白hashmap的反序列化為什么會跟url類產生關系,ai的過程中突然頓悟了,hashmap在反序列化時是不是會調用hashcode()方法計算鍵的哈希值(后面會跟這條鏈子),而如果這個鍵是url類呢?url類有自己重寫的hashcode方法,所以到這一步它會調用自己的hashcode方法,它自己的方法中會對域名發起一次解析請求,從而留下記錄。
先跟一下hashmap的實現
先進readObject方法
再進最下面的hash方法
發現調用了hashcode方法計算key,也就是計算url,那么到這里就可以知道如果傳入的類是url類,那么就會在反序列化時調用url類自己的hashcode方法了,那么它自己的hashcode方法又是什么樣的呢。
進入url類,找到hashcode方法
發現當hashcode==-1時(應該是一個默認值),會調用handle類的hashcode方法,跟進handle類
繼續跟進
發現在URLStreamHandler類的hashcode方法中,調用了getHostAddress方法,所以會對當前url進行一次解析
再來一個簡單的序列化反序列化驗證一手
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Scanner;public class Hello {public static void main(String[] args) throws Exception {HashMap<URL,Integer> h=new HashMap<URL,Integer>();URL url=new URL("http://zf8tay.dnslog.cn");h.put(url,20);// 序列化到文件FileOutputStream fos = new FileOutputStream("exploit.ser");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(h);oos.close();// 反序列化(假設目標系統執行)FileInputStream fis = new FileInputStream("exploit.ser");ObjectInputStream ois = new ObjectInputStream(fis);ois.readObject(); // 反序列化時觸發 URL 的 hashCode()ois.close();}}
這個時候發現有兩條記錄,這是為什么呢?
跟進put方法,發現同樣會調用hash方法,從而調用url類的hashcode方法,所以在序列化之前就會發起解析請求,想要阻止也很簡單,在put前通過反射修改hashcode的值不是-1,put后再改回來即可
動態代理
代理就是加入一個代理類,即可以用原來類的方法,又可以自己加入方法,避免了直接對原生類的修改。
靜態代理就是每一次調用方法都把底層邏輯寫上,而如果很多方法的調用是相似的,就可以使用動態代理簡化過程。不過要注意動態代理只能用于接口類
先寫一個接口和接口的實現
interface Myservice{void sayhello(String name);
}
class Myserviceimp implements Myservice{@Overridepublic void sayhello(String name){System.out.println("hello,"+name);}
}
動態代理的InvocationHandler,invoke方法就像readObject方法一樣,會在動態代理時自動執行
class Myhandle implements InvocationHandler{private Object target;public Myhandle(){}public Myhandle(Object o){this.target=o;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{Object res=method.invoke(target,args);return res;}
}
主函數測試
public class DynamicProxyTest {public static void main(String[] args) {
// 創建真實對象MyService realService = new MyServiceImpl();// 創建動態代理MyService proxyService = (MyService) Proxy.newProxyInstance(MyService.class.getClassLoader(),new Class<?>[]{MyService.class},new MyInvocationHandler(realService));// 調用代理對象的方法proxyService.sayHello("Alice");}
}
類加載器(ClassLoader)
首先得知道什么是類加載器
Java的類加載器(ClassLoader)就像一個“搬運工”,負責把編譯好的.class文件(字節碼)加載到JVM(Java虛擬機)中,讓程序能用。簡單來說,它的工作是找到類文件、讀進來、然后交給JVM處理。
它先去找到類文件,然后把類文件轉換為字節流,然后JVM把這些字節流轉換為可執行的類對象。java有幾種類加載器
-
Bootstrap ClassLoader:最頂層,加載Java核心庫(比如java.lang.String)。
-
Extension ClassLoader:加載擴展庫。
-
Application ClassLoader:加載你自己寫的程序的類。
在類初始化時會執行靜態代碼塊,類實例化時會執行構造代碼塊和無參構造函數
動態加載類方法Class.forname可以自行選擇是否初始化
雙親委派模型
聽著高大上,其實就是加載類時,先試著讓上級(父加載器)加載,干不了自己再干,比如Object類,所有的類都會向上讓Bootstrap ClassLoader來加載這個類,所以無論在哪里調用這個類都是一樣的,避免了沖突與混亂。
- 動態性:可以動態加載類,比如從網絡下載個類直接用。利用URLClassLoader類
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;public class loader {public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {URLClassLoader u=new URLClassLoader(new URL[]{new URL("file://F:\\web\\idea&vs腳本\\")});Class<?> c=u.loadClass("test");c.newInstance();}
}
同時,我們在test.java類的static區里可以加入方法,驗證類在初始化時會自動執行靜態代碼塊
public class test {static {System.out.println("已執行靜態代碼塊");}
}
注意test.java要先編譯為test.class文件
Spring Boot 2
1.Spring Boot 2
Spring Boot 2 是基于 Spring 框架 的一個開源 Java 開發框架,旨在簡化企業級應用的開發。它通過提供“開箱即用”的配置、自動配置和嵌入式服務器,減少了開發者手動配置的工作量。Spring Boot 2 是 Spring Boot 的第二個主要版本(發布于 2018 年)
2. Maven
Maven 是一個廣泛使用的 構建自動化工具,主要用于 Java 項目管理。它通過一個中央配置文件(pom.xml,Project Object Model)來管理項目的依賴、構建、測試和部署流程。Maven 簡化了依賴管理和構建過程,避免了手動下載 JAR 文件的麻煩。
此后可以通過maven下載依賴和進行相關測試等操作,后續把項目打成jar包,直接在目標服務器執行即可。
myapp/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/Application.java
│ │ ├── resources/
│ │ │ └── application.properties
│ ├── test/
│ │ └── java/
├── pom.xml
├── target/ # 編譯和打包輸出
特點
依賴管理
父項目是一個用來管理和組織其他項目的“模板”或“藍圖”項目,通常以一個 Maven 或 Gradle 配置文件(比如 pom.xml)的形式存在。在 Spring Boot 中,父項目主要用來統一管理依賴版本、配置和構建規則,讓子項目(實際的開發項目)可以直接“繼承”這些設置,簡化開發。
通俗點說,父項目就像一個“家里的管家”,它幫你把常用的工具、規則和資源(比如依賴庫的版本、編譯方式等)都整理好,寫在一本“家規”里。你的具體項目(子項目)只要認這個“管家”為“家長”,就能直接用它準備好的東西,不用自己從頭配置。
在 Spring Boot 2 中的具體例子:
Spring Boot 提供了一個官方的父項目,叫 spring-boot-starter-parent。它的 pom.xml 文件里定義了:
- 一堆常用依賴(比如 Spring 框架、測試庫、日志庫等)的推薦版本。
- 項目構建的默認配置(比如 Java 版本、Maven 插件等)。
- 一些通用的屬性(比如編碼格式)。
你在子項目的 pom.xml 里通過以下方式“繼承”這個父項目:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <!-- 假設用這個版本 --> </parent>
自動版本仲裁
“自動版本仲裁”指的是:當你使用 Spring Boot 的父項目(比如 spring-boot-starter-parent)或依賴管理工具(比如 spring-boot-dependencies)時,Spring Boot 會幫你“自動挑選”一些常用依賴的版本號。你在子項目的 pom.xml 文件里添加這些依賴時,不需要手動寫版本號,因為父項目已經預先定義好了這些依賴的推薦版本。
那些不在 Spring Boot 父項目或 spring-boot-dependencies 預定義列表中的依賴。因為 Spring Boot 不知道這些依賴的推薦版本,所以你需要手動指定版本號。
自動配好Tomcat
3.Tomcat 是什么?
Apache Tomcat 是一個開源的 Web 服務器和 Servlet 容器,主要用于運行 Java 開發的 Web 應用。它由 Apache 軟件基金會維護,是 Java 生態中最常用的服務器之一。
通俗解釋:Tomcat 就像一個“餐廳服務員”,專門負責接收客戶的請求(比如用戶在瀏覽器訪問你的網站),然后把請求交給你的 Java 程序(比如 Spring Boot 應用)處理,再把處理結果(網頁、數據)送回給客戶。
Tomcat 的主要功能
- 處理 HTTP 請求:接收用戶通過瀏覽器發送的請求(比如 GET、POST),并返回響應(網頁、JSON 數據等)。
- 運行 Servlet 和 JSP:支持 Java 的 Servlet(處理動態請求的 Java 程序)和 JSP(Java 服務器頁面)技術,讓開發者可以構建動態網站。
- 嵌入式或獨立運行
- 獨立運行:可以單獨安裝 Tomcat 作為一個服務器,把你的應用(WAR 文件)部署上去。
- 嵌入式運行:Spring Boot 把 Tomcat 打包到應用里,運行項目時 Tomcat 直接啟動,省去部署步驟。
在 Spring Boot 中的作用
-
Spring Boot 默認把 Tomcat 作為內置 Web 服務器,包含在 spring-boot-starter-web 依賴中。
-
你寫好 Spring Boot 應用(比如 REST API 或網頁),運行 main 方法,Tomcat 就會啟動,監聽 HTTP 請求(默認端口 8080)。
-
你可以用瀏覽器或工具(比如 Postman)訪問 http://localhost:8080,Tomcat 會把請求交給你的代碼處理。
4.自動配好SpringMVC
Spring MVC 是 Spring 框架的一個模塊,全稱是 Spring Model-View-Controller,用于構建基于 Java 的 Web 應用程序。它是一個基于 MVC 設計模式的框架,幫助開發者以清晰的方式組織和開發 Web 項目。
MVC 模式:
- Model(模型):表示數據和業務邏輯,比如數據庫中的用戶信息或計算結果。
- View(視圖):用戶看到的內容,比如網頁、JSON 響應。
- Controller(控制器):接收用戶請求,協調 Model 和 View,決定“做什么”和“返回什么”。
Spring MVC 幫你把這三部分組織好,讓開發 Web 應用更簡單。
Spring MVC 和 Tomcat 的關系
-
Tomcat:是 Web 服務器,負責接收 HTTP 請求和發送響應,像“快遞員”。
-
Spring MVC:是 Web 框架,運行在 Tomcat 里,負責處理請求的邏輯,像“餐廳服務團隊”。
-
Spring Boot 把兩者結合,Tomcat 作為嵌入式服務器,Spring MVC 作為請求處理核心,開發者只管寫業務代碼。
配置生成對應的類
默認配置:Spring Boot 像個貼心的管家,給你準備了一堆默認設置(比如 Tomcat 端口、文件上傳大小),存在各種 Properties 類里(比如 MultipartProperties)。
自定義配置:你覺得默認值不合適,就在 application.properties 里寫自己的值(比如改文件上傳大小為 10MB)。
綁定到類:Spring Boot 看到你的配置文件,把值“貼”到對應的 Properties 類上(比如 MultipartProperties 的 maxFileSize 變成 10MB)。
創建對象:Spring Boot 把這個配置好的 Properties 類變成一個對象(Bean),放進 Spring 的“工具箱”(IOC 容器),程序隨時可以拿來用。
容器
在 Spring Boot(或更廣義的 Spring 框架)中,容器指的是 Spring 的 IOC 容器(Inversion of Control Container,控制反轉容器),也叫 ApplicationContext。它是一個核心機制,負責管理應用程序中的對象(稱為 Bean),包括創建、配置、組裝和生命周期管理。
容器就像一個“智能管家”或“工具箱”,專門負責存放和管理你的程序里用到的各種“工具”(對象/Bean)。這些工具可能是你的業務邏輯類、配置類(如 MultipartProperties)、數據庫連接等。容器不僅保管這些工具,還會自動幫你把它們組裝好、配置好,并在需要時拿出來用。
Spring Boot 基于 Spring 框架,使用的容器是 ApplicationContext(具體實現如 AnnotationConfigApplicationContext)。
當你啟動 Spring Boot 應用(運行 main 方法),Spring Boot 會自動創建一個容器。
容器會掃描你的代碼(比如標有 @Component、@Service、@Controller 的類)和配置(比如 application.properties),把需要的對象創建并放進去。
容器的組件添加功能如何工作?
Spring 容器通過以下步驟將組件(Bean)添加到自身:
-
掃描代碼
- 容器啟動時,會掃描項目中的類,尋找標有特定注解的類(比如 @Component、@Controller、@Service、@Repository)或通過配置指定的類。
- Spring Boot 默認掃描主類(帶 @SpringBootApplication 的類)所在包及其子包。
-
識別組件
- 容器識別哪些類需要變成 Bean。通常通過注解(如 @Component)或 XML/Java 配置指定。
- 比如,標有 @Controller 的類會被識別為 Web 控制器,標有 @Service 的類會被識別為業務邏輯組件。
-
創建 Bean
- 容器為每個識別到的組件創建對象(Bean),并根據需要配置它的屬性(比如通過配置文件或依賴注入)。
- 比如,MultipartProperties 類會被創建為一個 Bean,配置文件中的 spring.servlet.multipart.max-file-size 會綁定到它的字段。
-
依賴注入
- 如果一個 Bean 需要依賴其他 Bean(比如 Controller 需要 Service),容器會自動把依賴的 Bean 注入。
- 比如,用 @Autowired 注解標記的字段,容器會找到對應的 Bean 填充。
-
存儲到容器
- 創建好的 Bean 會被存到容器中,容器給每個 Bean 分配一個名字(通常是類名首字母小寫),程序可以通過名字或類型獲取 Bean。
-
管理生命周期
:
- 容器不僅負責添加組件,還管理 Bean 的生命周期(創建、初始化、使用、銷毀)。
組件添加的常見方式
Spring 提供了多種方式讓類被容器識別并添加為 Bean。以下是主要的添加方式,配以通俗解釋:
使用注解(最常見)
Spring 提供了一系列注解,標記在類上后,容器會自動識別并創建 Bean。
- 常用注解
- @Component:通用組件,表示這個類需要被容器管理。
- @Controller / @RestController:用于 Web 控制器,處理 HTTP 請求。
- @Service:用于業務邏輯層。
- @Repository:用于數據訪問層(比如數據庫操作)。
- @Configuration:用于定義配置類,里面可以聲明更多的 Bean。
0字節截斷
在JDK7u40后這個得已修復,簡單來說這個00截斷其實就是因為Java對文件系統部分的實現是用C語言做的處理,C語言中對字符串來說都認為遇到0字節\0
就是字符串末尾,因此造成了這個問題。
\0 是 C 語言中表示 空字符(null character)的寫法,對應 ASCII 碼值為 0 的字符。在 Java 中,它等價于 Unicode 字符 \u0000。