管理 Java 多個版本
sudo update-alternatives --config java
sudo update-alternatives --config javac
(一)在Linux系統(tǒng)里
什么是環(huán)境變量?
系統(tǒng)級或用戶級的變量. 類型與程序/腳本中的變量, 只不過作用域是整個系統(tǒng)或當(dāng)前用戶.
如 /etc/profile.d/
對所有用戶都有效.
~/.bashrc
: 只對當(dāng)前用戶有效
環(huán)境變量PATH的作用坠韩?
當(dāng)輸入命令 grep
等不完整路徑的命令時(shí), 系統(tǒng)處理會在當(dāng)前路徑下搜索該程序, 還會到 PATH 中的路徑進(jìn)行搜索. 如: /usr/bin
怎么修改PATH?怎么持久化這個修改(避免被重置)?
臨時(shí)修改: export PATH=$PATH:your_path
持久化修改:
-
/etc/environment
中修改; 影響所有用戶. -
/etc/profile.d/
中創(chuàng)建相應(yīng)的 bash 腳本, 影響所有用戶. -
~/.bashrc
中修改, 只影響當(dāng)前用戶. - 重啟系統(tǒng)或
source /etc/environment, source ~/.bashrc
.會立即生效
(二)關(guān)于Java
Java之父是誰?
James Gosling
什么是字節(jié)碼?
字節(jié)碼: bytecode
javac 會將 .java 文件編譯成字節(jié)碼文件 .class, 可由 jvm 執(zhí)行.
同一個字節(jié)碼文件, 可以由不同系統(tǒng)上的 JVM 執(zhí)行.
其他語言(如 Scala, Kotlin) 也會將源碼編譯成 bytecode.
什么是JVM扯键?
JVM: Java Virtual Machine
將字節(jié)碼程序編譯成機(jī)器碼(machine code), 并執(zhí)行.
包含 JIT compiler(also called HotSpot)
什么是JRE?請說明JRE和JVM的關(guān)系珊肃。
JVM: 把 bytecode 編譯成機(jī)器碼
JRE: JVM + 核心類庫
核心類庫需要舉例
- 類庫: 多個Class文件打包, 就形成了類庫.
- dt.jar: Design Time, GUI 相關(guān)類庫
- tools.jar: javac, java 就是直接調(diào)用了 tools.jar
- rt.jar: Run Time,
什么是JDK荣刑?請說明JDK和JRE的關(guān)系。
JDK: JRE + 開發(fā)工具(編譯器 javac, jdb, jar)
什么是JDK發(fā)行版伦乔?請舉二例厉亏。
JDK 的不同實(shí)現(xiàn). 源碼相同, 構(gòu)建方式不同.
- Oracle JDK
- Microsoft Build of OpenJDK
- Liberica JDK
備注: OpenJDK 不屬于發(fā)行版, 類似于 Linux 與 Ubuntu.
[圖片上傳失敗...(image-19a79c-1687163467119)]
// (三)先只使用JDK、命令行烈和,不用Maven爱只、IDE等工具……
不使用包管理器,手動安裝JDK 11
- 下載 jdk 文件 下載鏈接
sudo dpkg -i jdk-11.0.16_linux-x64_bin.deb
使用 apt 安裝: sudo apt install openjdk-11-jdk
JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/
確定 JAVA_HOME 的方法: ll /usr/bin/ | grep java
: 便可查看 java 的真實(shí)安裝路徑
# 創(chuàng)建存放的文件夾
sudo mkdir /usr/java && cd /usr/java
# 下載 jdk 文件
curl https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz -o jdk-17_linux-x64_bin.tar.gz
tar -xvf jdk-17_linux-x64_bin.tar.gz
在 /etc/profile 文件中添加以下內(nèi)容
# java17
export JAVA_HOME=/usr/java/jdk-17.0.5
export PATH=$JAVA_HOME/bin:$PATH
什么是 main 方法招刹?
每個類的入口方法, 每個類中最多只能定義一個 main 方法. 執(zhí)行Java程序時(shí), 會首先尋找 main 方法. 如果沒有找到該方法, 會拋出異常 Error: Main method not found in class
.
如果定義多個 main 方法, 會拋出以下報(bào)錯:
$ javac p/A.java
p/A.java:13: error: method main(String[]) is already defined in class A
public static void main(String[] args) {
^
1 error
不定義包(package)恬试,寫個類“A”,實(shí)現(xiàn)main方法輸出Hello World疯暑。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
怎么編譯前一步所寫Java程序训柴?
javac HelloWorld.java
怎么執(zhí)行編譯得到的Java字節(jié)碼?
java HelloWorld
改寫程序缰儿,把“A”類放到包(類名變?yōu)閜.A)里畦粮,編譯、執(zhí)行
文件路徑: p/A.java
package p;
public class A {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
在“A”類源代碼(A.java)所在的目錄里,另寫個類“B”宣赔,在“B”類上添加個方法预麸,由“A”類調(diào)用
p/A.java
package p;
import p.B;
public class A {
public static void main(String[] args) {
System.out.println("Hello World!");
B.hello(args);
}
}
class B {
public static void hello(String[] args) {
System.out.println("I am class B.");
}
}
什么是classpath?
類似于環(huán)境變量中的 PATH 變量, 告訴 jvm 需要搜索 class 文件的路徑, 可通過參數(shù) -cp, -classpath, --class-path
指定.
如 java -cp ./testJava p.A
, jvm 會到 當(dāng)前目前下的 testJava 子路徑下 p 文件夾下所搜 A.class 文件.
如果找不到會報(bào)錯:
$ java p.A
Error: Could not find or load main class p.A
Caused by: java.lang.ClassNotFoundException: p.A
把“B”類儒将,改變其包名吏祸,挪到“A”類所在目錄外面去,做必要修改使程序能運(yùn)行
文件路徑為:
$ tree
.
├── p
│ ├── A.class
│ └── A.java
└── q
├── B.class
└── B.java
A.java
package p;
import q.B;
public class A {
public static void main(String[] args) {
System.out.println("Hello World!");
B.hello(args);
}
}
B.java
package q;
public class B {
public static void hello(String[] args) {
System.out.println("I am class B.");
}
}
什么是Jar钩蚊?
Jar 文件就是打包的 class 文件, 并且可以保持層級結(jié)構(gòu). 本質(zhì)就是 zip 壓縮包.
怎么把代碼打包成Jar贡翘?
- 如果不包含 manifest 文件:
$ jar -cf first.jar p/A.class q/B.class
(會使用默認(rèn)的 manifest 文件) - 不從 manifest 文件指定入口類:
jar -cvfe first.jar p.A p/A.class q/B.class
- 如果包含 manifest 文件:
jar -c --manifest manifest.mf -f first.jar p/A.class q/B.class
manifest.mf 文件內(nèi)容Manifest-Version: 1.0 Main-Class: p.A
- 將指定文件夾中的文件全部打包進(jìn) jar 文件:
$ jar --manifest manifest.mf -c -f first.jar -C ./ .
怎么執(zhí)行一個Jar?
- 如果 Jar 文件中沒有指定 Main-Class, 可以這樣執(zhí)行:
$ java -cp code.jar p.A Hello World! I am class B.
- 如果 Jar 文件中指定了 Main-Class, 可以這樣執(zhí)行:
java -jar first.jar
執(zhí)行 .java .class 文件的方法(帶有依賴)
總原則:
- 如果有依賴砰逻,可以通過 -cp 指定依賴的 jar包鸣驱,可以使用 *.jar 通配符;
- 執(zhí)行單個 .java .class .jar(沒有指定主函數(shù)時(shí)), java 會從 -cp 中的第一個文件中尋找主函數(shù)蝠咆,因此要將主函數(shù)所在文件放到 -cp 的第一個踊东;
guang@pc77:~/projects/demo_java/src/main/java/xintek
$ java Jdbc.java -cp /home/guang/.m2/repository/mysql/mysql-connector-java/8.0.30/mysql-connector-java-8.0.30.jar
guang@pc77:~/projects/demo_java/src/main/java
java -cp /home/guang/.m2/repository/mysql/mysql-connector-java/8.0.30/*.jar Jdbc.java
java -cp ./:/home/guang/.m2/repository/mysql/mysql-connector-java/8.0.30/* Jdbc.java
guang@pc77:~/projects/demo_java/src/main/java
$ java -cp ./:/home/guang/.m2/repository/mysql/mysql-connector-java/8.0.30/mysql-connector-java-8.0.30.jar xintek.Jdbc
guang@pc77:~/projects/demo_java/src/main/java
$ java -cp ./:/home/guang/.m2/repository/mysql/mysql-connector-java/8.0.30/* xintek.Jdbc
guang@pc77:~/projects/demo_java
$ java -cp target/demo_java-1.0.jar:/home/guang/.m2/repository/mysql/mysql-connector-java/8.0.30/* xintek.Jdbc
guang@pc77:~/projects/demo_java
$ java -cp target/demo_java-1.0.jar:/home/guang/.m2/repository/mysql/mysql-connector-java/8.0.30/mysql-connector-java-8.0.30.jar xintek.J
dbc
Maven 操作
// (四)先不用IDE,用Maven重做(三)
用archetype創(chuàng)建項(xiàng)目
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
備注:
- 如果將
DarchetypeVersion
去掉, 或者將其設(shè)置為其他值, 都會遇到以下報(bào)錯:
備注: DarchetypeVersion: 表示模板的版本. 1.4 表示其版本號.[ERROR] COMPILATION ERROR : [INFO] ------------------------------------------------------------- [ERROR] Source option 5 is no longer supported. Use 6 or later. [ERROR] Target option 1.5 is no longer supported. Use 1.6 or later.
-
-D
: 表示通過 maven 向 archetype:generate 傳參. -
groupId, artifactId
: archetype:generate 的參數(shù).
使用mvn編譯項(xiàng)目
- compile: 將 .java 文件編譯成 .class 文件
- package: 將 .class 文件及需要的配置文件打包成 .jar 文件
- install: 將打包成的 .jar 文件進(jìn)行本地安裝. 其他項(xiàng)目就可以直接調(diào)用.
使用 mvn 運(yùn)行項(xiàng)目
java -cp target/classes/ com.mycompany.app.App
對于非可執(zhí)行 jar 文件, 需要指定 Main-Class:
java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
: 手動指定 Main-Class..
如果不指定 Mian-Class 會報(bào)錯: no main manifest attribute, in target/my-app-1.0-SNAPSHOT.jar
對于可執(zhí)行 jar 文件, 不需要再次指定:
直接執(zhí)行 jar 包: java -jar target/my-app-1.0-SNAPSHOT.jar
使用 maven 制作可執(zhí)行 jar 包的方法(即指定 package 時(shí)指定 Main-Class):
- 向
pom.xml
文件添加以下內(nèi)容:<plugin> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>主函數(shù)路徑</mainClass> </manifest> </archive> </configuration> </plugin>
- 重新打包:
mvn clean package
- 執(zhí)行jar文件:
java -jar target/my-app-1.0-SNAPSHOT.jar
列出所有依賴的 classpath
mvn dependency:build-classpath
查看當(dāng)前項(xiàng)目生效的 POM 配置
mvn help:effective-pom
查看當(dāng)前生效的配置
mvn help:effectice-settings
制作包含依賴的可執(zhí)行 jar 包
-
向 pom.xml 文件中添加:
<build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>主函數(shù)路徑</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> </plugins> </build>
如果去掉 <archive></archive>片段刚操,生成的是非可執(zhí)行jar包闸翅。
-
編譯項(xiàng)目:mvn clean package assembly:single
- 會生成2個jar包:
- demo_java-1.0.jar: 不包含依賴的 jar包;
- demo_java-1.0-jar-with-dependencies.jar 的可執(zhí)行 jar 包(包含依賴的 jar與主函數(shù))菊霜。
- 會生成2個jar包:
正則匹配
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public static void main(String[] args) {
String content = "I'm Bob, my phone is 123";
Pattern pattern = Pattern.compile("(\\d*)");
Matcher result = pattern.matcher(content);
if (result.find()) {
System.out.println(result.group(1));
}
}
逐行讀取/寫入文件
import java.io.*;
import java.nio.charset.Charset;
public static void main(String[] args) throws IOException {
File fr = new File("target/classes/first.txt");
File fw = new File("target/classes", "second.txt");
if (fr.exists()) {
BufferedReader br = new BufferedReader(new FileReader(fr));
// 第一個參數(shù)表示文件路徑; 第二個參數(shù)表示編碼格式, UTF8, GBK; 第三個參數(shù)表示是否追加, true: a, false(默認(rèn)): w
BufferedWriter bw = new BufferedWriter(new FileWriter(fw, Charset.forName("UTF8"), true));
for (String line; (line = br.readLine()) != null;) {
System.out.println(line);
bw.append(line);
bw.newLine();
}
br.close();
bw.close();
}
}
備注:可使用 try 語句實(shí)現(xiàn)文件的打開與自動關(guān)閉坚冀。
將 byte[] 類型轉(zhuǎn)換成字符串:new String(byte[], StandardCharsets.UTF_8)
.
逐行讀取 gzip 文件
try (BufferedReader br = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(filePath.toFile())), StandardCharsets.UTF_8))
) {
for (String line; (line = br.readLine()) != null; ) {
System.out.println(line);
}
}
Json 序列化與反序列化
https://www.baeldung.com/java-org-json
重載
要求: 形參列表不同
- 參數(shù)個數(shù)不同;
- 參數(shù)類型不同;
方法重載與形參名稱, 權(quán)限修飾符(public, private等), 返回值類型無關(guān).
可變參數(shù)個數(shù)方法
int sum(int... nums) 等價(jià)于 int sum(int[] nums).
可以有 0, 1 .. 個參數(shù).
應(yīng)用場景:
String sql = "update customers set name = ?, email=? where id=?";
String sql = "update customers set name = ? where id=?";
public void update(String sql, object... objs)
值傳遞
形參: 定義方法時(shí), 參數(shù)列表中的變量;
實(shí)參: 調(diào)用方法時(shí), 參數(shù)列表中的變量;
Java 中調(diào)用方法時(shí)參數(shù)的傳遞方式: 值傳遞.
- 基本數(shù)據(jù)類型, 傳遞變量值;
- 應(yīng)用數(shù)據(jù)類型, 傳遞變量地址(2側(cè)會同步改動);
靜態(tài)字段和靜態(tài)方法
使用 static 修飾;
靜態(tài)字段
所有變量共享一個靜態(tài)字段, 可以使用 instance.field 或 class.field(推薦方法);
interface 是一個純抽象類, 不能定義實(shí)例字段. 但是 interface 可以靜態(tài)字段, 并且必須是 final static.
public interface Person{
public final static int MALE = 1;
public final static int FEMALE = 2;
}
因?yàn)?interface 只能定義 public final static 類型的字段, 所以可以省略 public final static, 編譯器會自動加上 public final static.
public interface Person{
int MALE = 1;
int FEMALE = 2;
}
靜態(tài)方法:
特點(diǎn):
- class.method(), 即 類名.靜態(tài)方法();
- 靜態(tài)方法內(nèi)部只能訪問靜態(tài)字段, 無法訪問實(shí)例字段;
- 程序入口 main 也是 靜態(tài)方法;
- 常用用于工具類, 如 Array.sort();
繼承
重寫 override
@override 修飾符只是起校驗(yàn)的作用, 并影響該方法是否是重寫, 也可以不寫.
- 方法名, 形參列表必須相同.
- 子類中該方法的權(quán)限>=父類中該方法的權(quán)限, 權(quán)限由大到小: public > package > protect > private.
- 子類無法重寫父類中 private 類型的方法.
- 返回值類型:
- 如果父類的返回值類型是 void 或者基本數(shù)據(jù)類型, 那么子類必須和父類一致;
- 如果父類的返回值類型是 應(yīng)用型類型, 那么子類需要是相同類型, 或者是其子類;
- 拋出的異常: 子類需要相同類型異常, 或者其異常類型的子類.
多態(tài)
多態(tài)是指程序在編譯和執(zhí)行時(shí)表現(xiàn)出不同的行為.
先決條件:
- 父類和子類;
- 子類重寫了父類的某些方法;
因此需要有以下假如條件: 有 2 個 class: Person, Man.
其中 Man 都繼承于 Person. 并且重寫了方法: walk, 同時(shí)有自己的專有方法: isSmoke.
Person a = new Man();
a.walk;
a.id;
在編譯時(shí), a.walk, a.id 調(diào)用的都是 Person 類的方法和屬性.
但是在運(yùn)行時(shí), a.walk 調(diào)用的是 Man 類的 walk 方法, Person 類的屬性.
這種調(diào)用父類方法, 但在執(zhí)行時(shí)調(diào)用的卻是子類方法, 叫虛方法調(diào)用(Virtual Method Invocation). 調(diào)用屬性時(shí)不受影響.
優(yōu)點(diǎn)
降低代碼冗余.
如果有多個子類繼承父類, 如 Woman, Girl, Boy 等繼承 Person, 并且都重寫了 walk 方法.
需要測試(或其他操作) walk 時(shí), 可以直接將被測試對象定義為 Person 類, 并且調(diào)用 Person.walk(). 這樣實(shí)際使用時(shí), 可根據(jù)需要傳入 Man, Woman 等類, 不會出現(xiàn)語法錯誤, 實(shí)際調(diào)用時(shí), 調(diào)用的也就是 Man.walk, Woman.walk. 這就不用再對 Man, Woman 類單獨(dú)寫類似的代碼了.
在開發(fā)中: 使用父類作為方法的形參, 是多態(tài)使用最多的場景. 即便增加了新子類, 也無需修改代碼. 提高了拓展性.
開閉性: 對拓展功能開放, 對修改代碼關(guān)閉.
缺點(diǎn)
在定義Person a = new Man()
時(shí), 會在內(nèi)存中定義一個 Man 類型的實(shí)例(具有 Man 類所有的屬性和方法), 但是該實(shí)例卻不能調(diào)用 Man 類專有的方法, 如 isSmoker().
向上轉(zhuǎn)型/向下轉(zhuǎn)型
[圖片上傳失敗...(image-785821-1687163467119)]
多態(tài)就是向上轉(zhuǎn)型.
剛才提到的"多態(tài)"的缺點(diǎn), 可以向下轉(zhuǎn)型 Person -> Man 之后, 再調(diào)用 .isSmoker() 方法.
Person p = new Man();
Man m = (Man) p;
m.isSmoker();
這里可能會有一個問題, 如果 Person 類還有一個子類 Woman, 并且該類沒有 isSmoker() 方法.
Person p = new Woman();
if (p instanceof Man){
Man m = (Man)p;
m.isSmoker();
}
強(qiáng)制轉(zhuǎn)換時(shí), 需要先驗(yàn)證類型, 再轉(zhuǎn)換, 否則調(diào)用專有方法時(shí), 如果類不匹配, 就會報(bào)錯(ClassCastException).
雖然 p 聲明是 Person, 但使用 instanceof 判斷時(shí), 卻是 Woman.
常用方法
finalize()
GC 要回收該對象時(shí), 會執(zhí)行該方法(JDK9 之后就建議不再使用)
將一個變量執(zhí)行 null 時(shí), 該變量之前指向的變量就可以被 GC 回收了.
Person p = new Person();
p = null;
代碼塊
抽象類 抽象方法
abstract 不能與以下關(guān)鍵詞共用:
- private: private 類型的方法不能被子類重寫, 抽樣方法要求必須要重寫;
- static: 靜態(tài)方法可以被類調(diào)用, 抽樣類不能被類/實(shí)例調(diào)用;
- final: final 修飾的 類/方法 不能被重寫.
抽象類
abstract 修飾 類
- 抽樣類不能進(jìn)行實(shí)例化.
- 可以包含構(gòu)造器;
- 可以沒有抽象方法. 如果有抽象方法, 就一定是抽象類.
抽象方法
abstract 修飾 方法
- 包含抽樣方法的類必須是抽樣類.
- 子類必須重寫/實(shí)現(xiàn)父類中所有的抽樣方法, 否則該子類也必須是抽象類.
接口
接口是一種規(guī)范, 實(shí)現(xiàn)了某個接口, 就是具有了該功能. 如筆記本使用了 Type-C 接口, 那么該筆記本就具備了相應(yīng)的功能.
類是表示范圍大小, 關(guān)系的從屬.
可以用于聲明
- 屬性: 必須使用 public static final 修飾, 因此可以將 public static final 省略;
- 方法: JDK8 之前必須可以使用 public abstract 修飾, 因此可以將 publish abstract 省略;
- JDK8 之后可以使用 default 修飾, 實(shí)現(xiàn)類不要實(shí)現(xiàn) default 修飾的抽象方法.
不可以以用于聲明
除 屬性和方法之外的, 如 構(gòu)造器, 代碼塊.
格式
class A extends SupserA implements B, C {}
- A 是 SuperA 的子類;
- A 是 B, C 的實(shí)現(xiàn)類;
接口與接口的關(guān)系
接口可以繼承另一個接口, 并且運(yùn)行多繼承. 如:
interface A {}
interface B {}
interface C extends A, B{} # C 中會自動包含 A, B 中所有的方法和屬性;
class D implements C{} # 需要重寫/實(shí)現(xiàn) A, B, C 中所有的方法
接口的多態(tài)性
和類的多態(tài)性類似: 接口I 變量i = new 類C;
其中: 類名C 實(shí)現(xiàn)了 接口I. 變量i只能調(diào)用 接口I 中定義的方法.
編譯是 變量i 屬于接口I, 但是運(yùn)行時(shí), 卻是屬于 類C.
List list = new ArrayList(); // 用List接口引用具體子類的實(shí)例
Collection coll = list; // 向上轉(zhuǎn)型為Collection接口
Iterable it = coll; // 向上轉(zhuǎn)型為Iterable接口
List 是接口, ArrayList 是實(shí)現(xiàn)類. 變量 list 只能調(diào)用 接口List 的抽象方法. 變量 coll 也只能調(diào)用 接口Collection 的抽象方法.
注意事項(xiàng)
- 類可以實(shí)現(xiàn)多個接口(如果不能實(shí)現(xiàn)該接口中所有的方法, 那么該類只能定義為抽象類, 因?yàn)榻涌谥卸x的方法是抽象方法).
- 一定程度上彌補(bǔ)了類的單繼承的限制.
public class Main {
public static void main(String[] args) {
Person p = new Student("Xiao Ming");
p.run();
}
}
interface Person {
String getName();
default void run() {
System.out.println(getName() + " run");
}
}
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
實(shí)現(xiàn)類不需要實(shí)現(xiàn) default 修飾的抽象方法, default 方法的目的: 當(dāng)我們給一個接口新增一個方法時(shí), 需要修改所有涉及的實(shí)現(xiàn)類. 如果新增的是 default 方法, 子類就不需要全部修改, 可以根據(jù)需要選擇性地重寫.
- 如某個版本新增一個方法, 可以先定義為 default 類型, 并發(fā)出 deprecated 警告, 并在某個后續(xù)版本中將 default 類型改為 public abstract 類型.
抽象類和接口
區(qū)別點(diǎn) | 抽象類 | 接口 |
---|---|---|
定義 | 可以包含抽象方法的類 | 主要是全局常量和抽象方法的集合 |
組成 | 構(gòu)造方法, 抽象方法, 普通方法, 常量, 變量 | 常量, 抽象方法 |
使用 | 子類繼承抽象類 | 子類實(shí)現(xiàn)接口 |
關(guān)系 | 抽象類可以實(shí)現(xiàn)多個接口 | 接口不能繼承抽象類, 可以繼承多個接口 |
常見設(shè)計(jì)模式 | 模版方法 | 簡單工廠, 工廠方法, 代理模式 |
對象 | 都可以通過對象的多態(tài)性產(chǎn)生實(shí)例化對象 | |
局限 | 單繼承 | 多個多繼承 |
實(shí)際 | 作為模版 | 作為一種標(biāo)準(zhǔn), 表示一中功能 |
選擇 | 如果抽象類和接口都可以實(shí)現(xiàn), 優(yōu)先使用實(shí)現(xiàn)接口, 可以突破單繼承的限制. |
Lambda 表達(dá)式
- () -> 5
- x -> 2 * x
- (x, y) -> x – y
- (int x, int y) -> x + y
- (String s) -> {System.out.print(s);}
如果使用外部變量,該變量需要 不可修改(即被 final 修飾):
final int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
包
包沒有父子繼承關(guān)系, java.util 和 java.utils.zip 是不同的包, 兩者沒有任何繼承關(guān)系;
處于同一個包的類, 可以訪問包作用域內(nèi)的字段和方法. 不使用 public, protected, private 等修改的字段和方法就是包作用域.
引入包的幾種方法:
- 不使用 import 引入, 使用時(shí)寫成完整類名: java.util.Arrays;(比較麻煩)
- 使用 import 引入某個類名, import java.util.Arrays;(推薦)
- 使用 import 引入某個類的全部子類, import java.util.*;(不推薦)
- import static 可以引入 一個類的靜態(tài)字段和方法; (很少使用)
package main; // 導(dǎo)入System類的所有靜態(tài)字段和靜態(tài)方法: import static java.lang.System.*; public class Main { public static void main(String[] args) { // 相當(dāng)于調(diào)用System.out.println(…) out.println("Hello, world!"); } }
類名查找順序
- 如果是完整類名, 就根據(jù)完整類名查找該類;
- 如果是簡單類名:
- 當(dāng)前 package; (會自動引入當(dāng)前 package 內(nèi)的所有類)
- import 的包是否包含該類;
- java.lang 是否包含該類; (會自動引入 java.lang)
- 無法依然無法找到, 就會報(bào)錯;
javac -d ./bin src/*/.java: 會編譯 src 文件夾下所有的 .java 文件, 包括任意深度的子文件夾.
win 下不支持該語法 src/*/.java, 所以需要列出所有的 .java 文件.
作用域
訪問權(quán)限修飾符
修飾符有: public, package(default), protected, private
public
- 修飾類和接口時(shí): 該類可以被任何類訪問 (一個文件中最多有一個該類);
- 修飾字段和方法時(shí): 可以被任何類訪問, 前提: 有所屬類的訪問權(quán)限;
package
沒有 public, protected, private 等修飾的類, 方法, 字段都是 package 類型的.
- 修飾類時(shí):可以被當(dāng)前包內(nèi)的所有類訪問鉴逞;
- 修飾方法和屬性時(shí):可以被當(dāng)前包內(nèi)所有的類訪問记某;
注意:
- 包名必須完全一致, com.apache 和 com.apache.abc 不是一個包.
protected
只能修飾 屬性、方法构捡;
- 訪問的前提:可以訪問所屬類辙纬;
- 可以被當(dāng)前包內(nèi)的所有類訪問;
- 可以被子類(子類的子類等)訪問叭喜;
private
只能在當(dāng)前類和嵌套類內(nèi)使用(與方法聲明的順序無關(guān)).
- 只能被當(dāng)前類和當(dāng)前類的嵌套類訪問贺拣;
推薦將其他類型(如 public 等)的方法放在前面, 應(yīng)當(dāng)首先關(guān)注, private 的方法放在后面.
嵌套類: 在當(dāng)前類的實(shí)現(xiàn)中, 又定義了其他類.
public class Main {
public static void main(String[] args) {
Inner i = new Inner();
i.hi();
}
// private方法:
private static void hello() {
System.out.println("private hello!");
}
// 靜態(tài)內(nèi)部類:
static class Inner {
public void hi() {
Main.hello();
}
}
}
final
- 不屬于訪問權(quán)限修飾符.
- 修飾 class, 可以防止該類被繼承;
- 修飾 method, 可以防止該方法被覆寫 (override);
- 修改 field 或 變量, 可以防止被重新賦值;
常用數(shù)據(jù)類型
- 基本類型: byte,short捂蕴,int譬涡,long,boolean啥辨,float涡匀,double,char;
- 引用類型: 所有 String class, array, interface 類型;
引用類型可以賦值為 null, 但是基本類型不能賦值為 null;
基本類型存儲的是該變量的值, 應(yīng)用類型存儲的是對應(yīng)的地址.
賦值時(shí)都是值傳遞, 即基本類型傳遞數(shù)值, 應(yīng)用類型數(shù)據(jù)地址(2者指向相同.)
String s = null;
int n = null; // compile error!
基本類型 | 對應(yīng)的引用類型 |
---|---|
boolean | java.lang.Boolean |
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
char | java.lang.Character |
按照語義編程溉知,而不是針對特定的底層實(shí)現(xiàn)去“優(yōu)化”陨瘩。
例如: 使用 == 比較 2 個同樣大小的 Integer.
- 當(dāng)數(shù)據(jù)較小時(shí), 返回 true; 因?yàn)闉榱斯?jié)省內(nèi)存, 對于較小的數(shù)值, 始終返回相同的實(shí)例;
- 當(dāng)數(shù)據(jù)較大時(shí), 返回 false;
比較 2 個 Integer 類型的變量時(shí), 要使用 equals(), 而非 ==. 絕不能因?yàn)镴ava標(biāo)準(zhǔn)庫的 Integer 內(nèi)部有緩存優(yōu)化就使用 ==.
三目運(yùn)算符
(cond)?exp1:exp2
如果 cond 為 true, 就取 exp1, 否則就取 exp2.
exp1, exp2 既可以是變量, 也可以是表達(dá)式.
Boolean 布爾型
只有 true, false. 不能用 1, 非1(如0) 表示 boolean.
String
String 是引用類型, 并且具有不可變性 (因?yàn)閮?nèi)部通過 private final 做了限定).
比較大小
要使用 equals() 或 equalsIgnoreCase() 比較大小, 不能使用 == .
- 只有 2 個 String 引用的是同一個對象時(shí), 使用 == 比較時(shí), 才是 true;
- 使用 equals() 或 equalsIgnoreCase() 做對比時(shí), 只要 2 個對象的值相同即可;
"".isEmpty(); // true腕够,因?yàn)樽址L度為0
" ".isEmpty(); // false,因?yàn)樽址L度不為0
" \n".isBlank(); // true舌劳,因?yàn)橹话瞻鬃址?" Hello ".isBlank(); // false帚湘,因?yàn)榘强瞻鬃址?
類型轉(zhuǎn)換
轉(zhuǎn)換成字符串
String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 類似java.lang.Object@636be97c
字符串和二進(jìn)制編碼的轉(zhuǎn)換
byte[] b = "hello".getBytes(StandardCharsets.UTF_8);
System.out.println(Arrays.toString(b));
String b_str = new String(b, StandardCharsets.UTF_8);
System.out.println(b_str);
StringBuilder
該數(shù)據(jù)類型線程不安全. String 類型線程安全.
String s = "";
for (int i = 0; i < 1000; i++) {
s = s + "," + i;
}
雖然可以這樣直接拼接字符串, 但是每次循環(huán)中都會創(chuàng)建新字符串對象, 然后扔掉舊字符串, 這樣大部分字符串都是臨時(shí)對象, 不僅浪費(fèi)內(nèi)存, 還會相應(yīng)GC效率.
為了高效拼接字符串, java 標(biāo)準(zhǔn)庫提供了 StringBuilder, 它是可變對象, 可以預(yù)分配緩沖區(qū), 這樣往 StringBuiler 對象新增字符時(shí), 不會創(chuàng)建新的臨時(shí)對象.
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
// 可以使用鏈?zhǔn)讲僮? sb.append(',').append(i);
}
String s = sb.toString();
備注:
- 對于普通的字符串 + 操作, 不需要將其改寫為 StringBuilder, Java 編譯器在編譯時(shí)會自動將多個 + 操作優(yōu)化成 StringConcatFactory 操作, 在執(zhí)行期間, StringConcatFactory 會自動把字符串連接操作優(yōu)化為數(shù)組復(fù)制或 StringBuilder 操作.
- 之前的 StringBuffer 是 StringBuilder 的線程安全版本, 屬于 Java 的早期版本, 現(xiàn)在很少使用.
StringJoiner
使用分隔符拼接數(shù)組的需求很常見, 可以使用 StringJoiner 解決.
String[] names = { "Bob", "Alice", "Grace" };
StringJoiner sj = new StringJoiner(",", "hello ", "!");
for (String name : names) {
sj.add(name);
}
System.out.println(sj.toString());
日期 時(shí)間
獲取當(dāng)前時(shí)間的 Unix 時(shí)間戳
Instant now = Instant.now()
常用轉(zhuǎn)換
// 獲取當(dāng)前時(shí)間
LocalDateTime dt = LocalDateTime.now(); // 當(dāng)前日期和時(shí)間
// 將 Unix 時(shí)間戳轉(zhuǎn)換成 datetime 類型
Instant ins = Instant.ofEpochSecond(1683979214);
ZonedDateTime zdt = ins.atZone(ZoneId.of("UTC"));
ZonedDateTime zdt = ins.atZone(ZoneId.of("Asia/Shanghai"));
// 使用系統(tǒng)時(shí)區(qū)
ZonedDateTime zdt = ins.atZone(ZoneId.systemDefault());
// 計(jì)算 datetime 對應(yīng)的 Unix 時(shí)間戳
long timestamp = zdt.toEpochSecond();
System.out.println(timestamp);
// 將 datetime 類型按照指定格式轉(zhuǎn)換成字符串
var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(formatter.format(zdt));
// 將特定格式的字符串解析為 Datetime
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime dt2 = LocalDateTime.parse("2019/11/30 15:16:17", dtf);
// Localdatetime 與 ZonedDatetTime 之間的轉(zhuǎn)換
LocalDateTime ldt = LocalDateTime.of(2019, 9, 15, 15, 16, 17);
ZonedDateTime zbj = ldt.atZone(ZoneId.systemDefault());
ZonedDateTime zny = ldt.atZone(ZoneId.of("America/New_York"));
LocalDateTime ldt = zbj.toLocalDateTime();
// 同一時(shí)間在不同時(shí)區(qū)之間轉(zhuǎn)換
// 以中國時(shí)區(qū)獲取當(dāng)前時(shí)間:
ZonedDateTime zbj = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
// 轉(zhuǎn)換為紐約時(shí)間:
ZonedDateTime zny = zbj.withZoneSameInstant(ZoneId.of("America/New_York"));
異常處理
斷言
assert x >= 0 : "x must >= 0";
如果前面的斷言條件 為 False, 就會拋出 AssertionError, 并帶上 "x must >= 0" 信息.
拋出 AssertionError 會導(dǎo)致程序退出. 因此. 斷言不能用于可恢復(fù)的程序錯誤(應(yīng)該使用"拋出異常"), 只應(yīng)該應(yīng)用在開發(fā)和測試階段.
JVM 會默認(rèn)關(guān)閉斷言指令, 跳過斷言語句. 如果要執(zhí)行斷言, 需要給 JVM 傳遞參數(shù) -enableassertions (簡寫 -ea), 如: java -ea Main.java
.
多線程
創(chuàng)建子線程
- 從Thread派生一個自定義類,然后覆寫run()方法
public class Main {
public static void main(String[] args) {
Thread t = new MyThread();
t.start(); // 啟動新線程
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("start new thread!");
}
}
- 創(chuàng)建 Thread 實(shí)例時(shí)甚淡,傳入一個Runnable實(shí)例
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start(); // 啟動新線程
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("start new thread!");
}
}
- 使用 lambda 表達(dá)式
public class Main {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("start new thread!");
});
t.start(); // 啟動新線程
}
}
- 使用匿名類
public class Main {
public static void main(String[] args) {
System.out.println("main start...");
Thread t = new Thread() {
public void run() {
System.out.println("thread run...");
System.out.println("thread end.");
}
};
t.start();
System.out.println("main end...");
}
}
等待子進(jìn)程結(jié)束
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("hello");
});
System.out.println("start");
t.start();
// 會等待子進(jìn)程 t 結(jié)束再向下執(zhí)行
t.join();
System.out.println("end");
}
}
在 JVM 中所有的變量都保存在 主內(nèi)存中大诸,子線程訪問時(shí),需要先將該變量復(fù)制到自己的工作內(nèi)存贯卦。這樣资柔,多個線程同時(shí)使用一個變量時(shí),可能會有沖突撵割』哐撸可以使用 volatile 關(guān)鍵詞,有2個作用:
- 每次訪問變量時(shí)啡彬,總是獲取主內(nèi)存的最新值官边;
- 每次修改后,立刻會寫至主內(nèi)存外遇;
X86 框架,JVM 會寫內(nèi)存速度很快契吉,ARM 架構(gòu)下就會有顯著的延遲跳仿。
線程安全
鎖住的是當(dāng)前實(shí)例 this,創(chuàng)建多個實(shí)例時(shí)捐晶,又不會相互影響菲语。
public class Counter {
private int counter = 0;
public synchronized void add(int n) { // 鎖住this
count += n;
}
// 與上面語句等價(jià)
// public void add(int n) {
// synchronized (this) {
// counter += n;
// }
// }
public synchronized void dec(int n) { // 鎖住this
count -= n;
}
// public void dec(int n) {
// synchronized (this) {
// counter -= n;
// }
// }
public int get() {
return counter;
}
}
現(xiàn)成安全的類:
- java.lang.StringBuffer;
- String, Interger, LocalDate 所有成員變量都是 final惑灵,所以也線程安全山上。
- Math 只提供靜態(tài)方法,沒有成員變量英支,也是線程安全佩憾。
可重入鎖
JVM 允許同一個線程重復(fù)獲取同一把鎖,這種鎖叫做可重入鎖干花。
由于Java的線程鎖是可重入鎖妄帘,所以,獲取鎖的時(shí)候池凄,不但要判斷是否是第一次獲取抡驼,還要記錄這是第幾次獲取。每獲取一次鎖肿仑,記錄+1致盟,每退出synchronized塊碎税,記錄-1,減到0的時(shí)候馏锡,才會真正釋放鎖雷蹂。
日志
通常使用 org.apache.commons.logging 模塊記錄日志. 可以通過以下 2 種方法:
// 1. 在靜態(tài)方法中引用 Log:
public class Main {
static final Log log = LogFactory.getLog(Main.class);
static void foo() {
log.info("foo");
}
}
// 2. 在實(shí)例方法中引用 Log:
public class Person {
protected final Log log = LogFactory.getLog(getClass());
void foo() {
log.info("foo");
}
}
第一種方式定義的 log 在靜態(tài)方法和實(shí)例方法中都可以使用;
第二種方式定義的 log 可以用于繼承中, 子類的 log 會自動根據(jù) getClass() 判斷類名, 但是只能在實(shí)例方法中使用;
Java 中記錄日志一般使用 "日志 API + 底層實(shí)現(xiàn)" 的方式.
日志 API
日志 API 主要是對底層實(shí)現(xiàn)進(jìn)行封裝, 對外提供統(tǒng)一的調(diào)用接口.
常用的有: apache.common-logging, SLF4j;
common-logging 的 jar 包:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
SLF4J 的 jar 包:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
</dependency>
底層實(shí)現(xiàn)
不同的底層實(shí)現(xiàn)有不同的功能和性能, 常用的有 log4j, logback. 配置文件分別為:log4j2.xml, logback.xml. 推薦將配置文件放到 src/main/resources 文件夾內(nèi), 這樣使用 maven 編譯后, 配置文件的路徑為 target/classes (class 文件對應(yīng)的 classpath).
log4j 的 jar 文件:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
logback 的 jar 包:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
</dependency>
common-logging + log4j
- common-logging 的 jar 包;
- log4j 的 jar 包;
- common-logging 與 log4j 之間的連接器
common-logging 與 log4j 之間的連接器:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.19.0</version>
</dependency>
如果 common-logging 沒有在 classpath 中發(fā)現(xiàn) log4j 和 連接器的 jar 包, 就會自動調(diào)用內(nèi)置的 java.util.logging; 如果發(fā)現(xiàn)了, 就會自動調(diào)用 log4j 作為底層.
SLF4J + LOG4J
slf4j 對應(yīng)的 jar 包:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.6</version>
</dependency>
slf4j 與 log4j 的連接器的 jar 包:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>