java 隨筆

管理 Java 多個版本

  1. sudo update-alternatives --config java
  2. 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
持久化修改:

  1. /etc/environment 中修改; 影響所有用戶.
  2. /etc/profile.d/ 中創(chuàng)建相應(yīng)的 bash 腳本, 影響所有用戶.
  3. ~/.bashrc 中修改, 只影響當(dāng)前用戶.
  4. 重啟系統(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

  1. 下載 jdk 文件 下載鏈接
  2. 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?

  1. 如果 Jar 文件中沒有指定 Main-Class, 可以這樣執(zhí)行:
    $ java -cp code.jar p.A
    Hello World!
    I am class B.    
    
  2. 如果 Jar 文件中指定了 Main-Class, 可以這樣執(zhí)行:
    java -jar first.jar

執(zhí)行 .java .class 文件的方法(帶有依賴)

總原則:

  1. 如果有依賴砰逻,可以通過 -cp 指定依賴的 jar包鸣驱,可以使用 *.jar 通配符;
  2. 執(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)錯:
    [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.
    
    備注: DarchetypeVersion: 表示模板的版本. 1.4 表示其版本號.
  • -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):

  1. pom.xml文件添加以下內(nèi)容:
    <plugin>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
            <archive>
            <manifest>
                <mainClass>主函數(shù)路徑</mainClass>
            </manifest>
            </archive>
        </configuration>
    </plugin>  
    
  2. 重新打包: mvn clean package
  3. 執(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 包

  1. 向 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包闸翅。

  2. 編譯項(xiàng)目:mvn clean package assembly:single

    1. 會生成2個jar包:
      1. demo_java-1.0.jar: 不包含依賴的 jar包;
      2. demo_java-1.0-jar-with-dependencies.jar 的可執(zhí)行 jar 包(包含依賴的 jar與主函數(shù))菊霜。

正則匹配

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

重載

要求: 形參列表不同

  1. 參數(shù)個數(shù)不同;
  2. 參數(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)的作用, 并影響該方法是否是重寫, 也可以不寫.

  1. 方法名, 形參列表必須相同.
  2. 子類中該方法的權(quán)限>=父類中該方法的權(quán)限, 權(quán)限由大到小: public > package > protect > private.
  3. 子類無法重寫父類中 private 類型的方法.
  4. 返回值類型:
    1. 如果父類的返回值類型是 void 或者基本數(shù)據(jù)類型, 那么子類必須和父類一致;
    2. 如果父類的返回值類型是 應(yīng)用型類型, 那么子類需要是相同類型, 或者是其子類;
  5. 拋出的異常: 子類需要相同類型異常, 或者其異常類型的子類.

多態(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!");
        }
    }    
    

類名查找順序

  1. 如果是完整類名, 就根據(jù)完整類名查找該類;
  2. 如果是簡單類名:
    1. 當(dāng)前 package; (會自動引入當(dāng)前 package 內(nèi)的所有類)
    2. import 的包是否包含該類;
    3. java.lang 是否包含該類; (會自動引入 java.lang)
  3. 無法依然無法找到, 就會報(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

  1. common-logging 的 jar 包;
  2. log4j 的 jar 包;
  3. 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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市眷篇,隨后出現(xiàn)的幾起案子萎河,更是在濱河造成了極大的恐慌,老刑警劉巖蕉饼,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虐杯,死亡現(xiàn)場離奇詭異,居然都是意外死亡昧港,警方通過查閱死者的電腦和手機(jī)擎椰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來创肥,“玉大人达舒,你說我怎么就攤上這事√局叮” “怎么了巩搏?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長趾代。 經(jīng)常有香客問我贯底,道長,這世上最難降的妖魔是什么撒强? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任禽捆,我火速辦了婚禮,結(jié)果婚禮上飘哨,老公的妹妹穿的比我還像新娘胚想。我一直安慰自己,他們只是感情好芽隆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布浊服。 她就那樣靜靜地躺著,像睡著了一般胚吁。 火紅的嫁衣襯著肌膚如雪臼闻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天囤采,我揣著相機(jī)與錄音述呐,去河邊找鬼。 笑死蕉毯,一個胖子當(dāng)著我的面吹牛乓搬,可吹牛的內(nèi)容都是我干的思犁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼进肯,長吁一口氣:“原來是場噩夢啊……” “哼激蹲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起江掩,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤学辱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后环形,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體策泣,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年抬吟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了萨咕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡火本,死狀恐怖危队,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钙畔,我是刑警寧澤茫陆,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站擎析,受9級特大地震影響簿盅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜叔锐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望见秽。 院中可真熱鬧愉烙,春花似錦、人聲如沸解取。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禀苦。三九已至蔓肯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間振乏,已是汗流浹背蔗包。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留慧邮,地道東北人调限。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓舟陆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耻矮。 傳聞我的和親對象是個殘疾皇子秦躯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容