Java Learning

Data Type

Primitive data type

Data Type Size Range
byte 1 byte \(-128\) to \(127\)
short 2 bytes \(-2^{16-1}\) to \(2^{16-1}-1\)
int 4 bytes \(-2^{32-1}\) to \(2^{32-1}-1\)
long 8 bytes \(-2^{64-1}\) to \(2^{64-1}-1\)
float 4 bytes
double 8 bytes
boolean 1 bit
char 2 bytes
int[] intArr = new int[3]; // 默认初始化为[0, 0, 0]
boolean[] boolArr = new boolean[3];  // 默认初始化为[false, false, false]

基本数据类型不能是null

十进制数转化为Java float类型举例。

\(2.25=\color{green}{10.01}=\color{green}{1.001}\times \color{green}{2^1}\)

符号位:\(\color{green}{0}\)

指数位:\(127+1 = \color{green}{01111111}+\color{green}{1}=\color{green}{10000000}\)

尾数:\(\color{green}{001}\mathop{\rightarrow}^\text{补位}\color{green}{00100000000000000000000}\)

将符号位、指数为、尾数按顺序拼接在一起就是32位float类型数据。

Array

  • Java语言原生支持。

  • 声明时需要指定长度,长度固定,使用连续的内存空间。

  • 可以存储基本数据类型(如intchar)以及对象(如IntegerString

代码样例:

import java.util.Arrays;
import java.util.List;

public class ArrayAnalysis {
    public static void main(String[] args) {
        // 声明Array
        int[] intArray = {1, 2, 3, 3, 9, 5, 4, 8};
        String[] stringArray = new String[10];
        int[][] intArray2D = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

        // 打印Array
        System.out.println(intArray);  // 打印出intArray的地址
        System.out.println(Arrays.toString(intArray));  // 打印出inArray的内容
        System.out.println(Arrays.deepToString(intArray2D));  // 打印出intArray2的内容
        System.out.println(intArray[1]);  // 打印出索引为1处的元素

        // 排序Array
        Arrays.sort(intArray);  // 这行代码会直接修改intArray,返回值为void

        // 浅拷贝Array
        int[][] newIntArray2D = Arrays.copyOf(intArray2D, intArray2D.length);  // 第二个参数的意思是截取Array中前intArray2.length个元素。这里是浅拷贝。
        intArray2D[0][0] = -99;
        System.out.println(Arrays.deepToString(newIntArray2D));  // [[-99, 2, 3], [4, 5, 6], [7, 8, 9]]
        // 深拷贝Array
        newIntArray2D = Arrays.stream(intArray2D).map(arr -> Arrays.copyOf(arr, arr.length)).toArray(int[][]::new);
        intArray2D[0][0] = 99;
        System.out.println(Arrays.deepToString(newIntArray2D));  // [[-99, 2, 3], [4, 5, 6], [7, 8, 9]]

        // 将int[]转化为List<Integer>
        List<Integer> myList = Arrays.stream(intArray).boxed().toList();
        System.out.println(myList);  // [1, 2, 3, 3, 4, 5, 8, 9]
    }
}

List

ArrayListLinkedList继承了List,它们具备以下特点。

  • 属于java.util包。

  • ArrayList具有动态扩容机制,当元素数量超过容量时会自动寻找新的连续内存空间并自动扩容1.5倍。LinkedList采用链表存储,便于元素的平频繁插入,但相比于ArrayList,它的元素查找速度较慢为\(O(n)\)

  • 仅能存储对象,不能存储基本数据类型。由于自动装箱机制,表面上看它们似乎也能直接存储基本数据类型。

  • 具有丰富的方法可供调用。

另外,ArrayList由于是“整块”,所以GC效率比LinkedList更高。

代码样例:

import java.util.ArrayList;
import java.util.Arrays;

public class ArrayListAnalysis {
    public static void main(String[] args) {
        ArrayList<Integer> integerList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6));
        ArrayList<Integer> newIntegerList = integerList;  // 这里是直接传递引用
        System.out.println(integerList);  // 由于ArrayList类重写了Object类中的toString()方法,所以这里直接打印出元素
        
        // List的增删查改
        integerList.set(0, 100);
        integerList.add(99);
        integerList.remove(0);
        System.out.println(integerList.get(integerList.size() - 1));
        System.out.println(newIntegerList);
        
        // List<Integer>转化为基本数据类型int[]
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        int[] arr = list.stream().mapToInt(Integer::intValue).toArray();
        
        // List<int[]>转化为基本数据类型int[][]
        List<int[]> list2D = new ArrayList<>();
        list2D.add(new int[] {1, 2, 3});
        list2D.add(new int[] {4, 5, 6});
        int[][] arr2D = list2D.toArray(new int[2][]);  //或 list2D.toArray(int[][]::new)
    }
}

LinkedListArrayList都实现了List接口,因此拥有完全相同的方法。

ArrayList占用的是连续空间,如果连续空间不足,则会新找一个足够的连续空间新创建一个ArrayList并删除原有的ArrayListArrayList的优势在于获取数据的速度更快。

LinkedList是链表的存储方式。LinkedList的优势在于善于存储频繁被修改的数据。

String

public final class String implements java.io.Serializable, Comparable<String>, CharSequence
  • Immutability

String 对象一旦被创建就不能被改变,这也意味着任何String实例方法的调用都不会改变实例本身。

  • String Pool

用相同的字面量创建String实例时,会直接返回池中的引用,不会创建新的对象,比如:

String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);  // true

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2);  // false

static methods

  • String.valueOf()
// 将基本数据类型转化为字符串
double num = 3.1415;
String numStr = String.valueOf(num);
// 将char[]类型转化为字符串
char[] charArr = {'A', 'B', 'C'};
String charArrString = String.valueOf(charArr);  // ABC
  • String.format()

String.format()用于创建格式化字符串。

格式说明符 描述
%s 字符串
%d 十进制整数
%o 八进制整数
%x 十六进制整数
%f 浮点数
%c 单个字符
%b 布尔值
%n 换行符
// 控制浮点数精度(四舍五入)
String.format("%.2f", 3.14559);  // 3.15
// 对齐与填充。例如:包含Hello在内一共是10个字符,右对齐
String.format("|%10s|%-10s|", "Hello", "World");  // |     Hello|World     |
// 将十进制数8转化为八进制数10
String.format("%o", 8);
// 补0
String.format("补零: %07.2f", (float) 123);  // 0123.00
  • String.join()
// 使用指定的分隔符连接多个String
String str = String.join(", ", "Hello", "World", "Java")

instance methods

  • .length()

Return the length of a String.

  • .charAt(int index)

Return the char at the index position.

  • .indexOf(String str) or .indexOf(char myChar)

Return the index of the String or char when it appears at the first time. Return \(-1\) when String or char is illegal.

  • .substring(int beginIndex, int endIndex)

Return a sub-string from the beginIndex position to the endIndex-1 position.

  • .replace(char oldChar, char newChar)

Return a new String in which all of the oldChar are replaced by newChar.

  • .toLowerCase()

  • .toUpperCase()

  • .toCharArray()

  • .hashCode()

计算一个字符串的Hash值,返回int类型

StringBuilder

  • 可变性:可追加、插入、删除、修改字符。

  • 线程不安全:例如在两个线程中同时给一个StringBuilder追加元素会导致该StringBuilder增加的元素总数小于两个线程中增加元素的总和。

StringBuilder在单线程环境中性能较好,多线程环境中不安全。

StringBuffer在单线程环境中性能较差,多线程环境中安全。因为它的所有方法都设置了synchronized同步锁,当它被一个线程访问时,其他线程必须等待。

StringBuilder sb = new StringBuilder("Hello, World");

static methods

instance methods

  • .length()

返回长度。

  • .append()

追加字符串。

  • .insert(int index, String myStr)

插入字符串。

  • .delete(int beginIndex, int endIndex)

删除字符串,从beginIndexendIndex-1

  • .deleteChatAt(int index)

删除指定位置的字符。

  • .toString()

转化为String对象。

Integer

static methods

  • Integer.parseInt()
// 将String转化为int
Integer.parseInt("13245");
// 将String按照其本身的进制转化为十进制int类型
Integer.parseInt("1000", 2);  // 8
  • Integer.toString()

Transfer int to String.

  • Integer.toBinaryString(int num)
// 将int转化为二进制字符串
Integer.toBinaryString(8);

instance methods

HashSet

不同于Python的字典类型,HashSet的键可以是可变类型,例如int[]List<?>。HashSet的键通过equals()hashCode()来判断键的唯一性。

HashSet<List<Integer>>

元素都相同且顺序也相同的List可以合并为同一个键。

// 情况1
Set<List<Integer>> set = new HashSet<>();
List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2));
List<Integer> list2 = new ArrayList<>(Arrays.asList(2, 1));
System.out.println(list1.equals(list2));  // false
System.out.println(list1.hashCode() == list2.hashCode());  // false
set.add(list1);
set.add(list2);
System.out.println(set);  // [[1, 2], [2, 1]]

// 情况2
Set<List<Integer>> set = new HashSet<>();
List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2));
List<Integer> list2 = new ArrayList<>(Arrays.asList(1, 2));
System.out.println(list1.equals(list2));  // true
System.out.println(list1.hashCode() == list2.hashCode());  // true
set.add(list1);
set.add(list2);
System.out.println(set);  // [[1, 2]]

HashSet<int[]>

即使是元素都相同且顺序也相同的Array也无法合并为同一个键。因为Array类型的.equals().hashCode()都继承自Object类,.equals()比较的是引用是否相等,.hashCode()是通过地址计算的。

int[] arr1 = {0, 0};
int[] arr2 = {0, 0};
System.out.println(arr1.equals(arr2));  // false
System.out.println(arr1.hashCode() == arr2.hashCode());  // false

Java Modifiers

Access Modifiers

For class,

Modifier Description
public The class is accessible by any other class
default The class is only accessible by classes in the same package.

For attribute, method, constructor,

Modifier Description
public The code is accessible for all classes.
protected The code is accessible in the same package. And the code is accessible in the subclass.
default The code is only accessible in the same package.
private The code is only accessible within the declared class.

Non-Access Modifier

For class,

Modifier Description
final The class cannot be inherited by other classes.
abstract The class is used to be inherited. The class cannot be used to create objects.

For attribute and method,

Modifier Description
final The attribute or method will not point to another object.
static The attribute or method belongs to the class , rather than an object
abstract Only be used on methods in an abstract class.
transient
synchronized
volatile

interface

interface is a special abstract class. Every attribute in it is treated as public, static and final; Every method in it is treated as public and abstract

Functional interface can only contain one abstract method. For example:

public interface MessageCreator {
    Message createMessage(Session session) throws JMSException;
}

And I can use Lambda expression to implement the abstract method in a functional interface. For example:

MessageCreator messageCreator = session -> session.createObjectMessage();

Common Used Class

Object

Java中所有类都默认继承自Object类。Object类中有两个重要的方法:public boolean equals()public int hashCode()

按照Java规范,equals()返回为true时,两个比较对象的hashCode()必须相同。因此在重写上述两个方式要注意这一点。

重写equals()hashCode()的模板代码:

import java.util.Objects;

class Student {
    private String name;
    private String address;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name) && Objects.equals(address, student.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, address); // 保证相同name和address的Student对象哈希码一致
    }
}

Arrays

Arrays like String[] or int[] is static array, the length of which is fixed.

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {

        String[] myArray = {"item1", "item2"};
        System.out.println(myArray[0]);  // Show the first element of myArray.
        System.out.println(myArray);  // Show the hash code of the memory address of an array object.
        System.out.println(Arrays.toString(myArray));  // Show all of the items in String daya type of an Array.
        System.out.println(myArray.length);  // Show the length of a String[]. 
        
    }
}
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {

        char[] charArray = s.toCharArray();
        Arrays.sort(charArray);  // Sort an Array.
    }
}

ArrayList and LinkedList

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {

        ArrayList<String> myList = new ArrayList<String>();
        myList.add("A");
        myList.add("B");
        myList.add("C");
        System.out.println(myList.get(1));// "B"
        myList.remove(0);
    }
}

.remove() method can remove an ArrayList’s item both by index and by value. If the items in an ArrayList are int data type, and I want to remove the item (for example \(5\)) by its value, then I should use .remove(Integer.valueOf(5)). If I use .remove(5), the item with index of \(5\) will be removed.

ArrayList is based on dynamic array. LinkedList is based on linked structure.

Use ArrayList for storing and accessing data.

Use LinkedList to manipulate data.

HashMap

import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<int, String> myHashTable = new HashMap<int, String>();
        myHashTable.put(0, "item1");
        System.out.println(myHashTable.get(0));
        myHashTable.remove(0);  // remove target item.
        myHashTable.clear();  // remove all items.
        myHashTable.keySet();  // java.util.HashMap$KeySet, used for loop through keys in a HashTable.
        myHashTable.values();  // java.uitl.HashMap$Values, used for loop through values in a HashTable.
        myHashTable.containsKey(0)  // to check if a key in a HashTable.
    }
}

HashMap是非线程安全的。ConcurrentHashMap是线程安全的。使用ConcurrentHashMap的.put()方法可以保证线程安全(不过貌似更应该使用.compute()方法?)

HashSet

A HashSet is a collection of items where every item is unique.

import java.util.HashSet;

public class Main {
    public static void main(String[] args) {
        HashSet<String> myHashSet = new HashSet<String>();
        myHashSet.add("item1");
        myHashSet.add("item2");
        myHashSet.contains("item3");  // check if "item3" in myHashSet.
        myHashSet.remove("item2");
        myHashSet.clear();
        myHashSet.size();
        // myHashSet1.euqals(myHashSet2)  check if two HashSets have same items.
    }
}

enum

enum is a special class.

enum myEnumName {
    CONSTANT1, CONSTANT2, CONSTANT3;
}

Template

Here is a senior example: add annotations to every enum value.

AggregateEnum.java

public enum AggregateEnum {
    MOST("最多", "max"),
    AVERAGE("平均", "avg");
    
    private String aggregateCH;
    private String aggregateEn;
    
    AggregateEnum(String aggregateCH, String aggreagteEN) {
        this.aggregateCH = aggregateCH;
        this.aggreagteEN = aggreagteEN;
    }
    
    public String getAggregateCH() { return aggregateCH; }
}

Main.java

public class Main {
    public static void main(String[] args) {
        AggregateEnum mymost = AggregateEnum.MOST;
        System.out.println(mymost.getAggregateCH());
        System.out.println(mymost.getAggregateEN());
    }
}

Define an enum and print its annotations.

For example, the parameters of “最多” and “max” in MOST() will be passed to constructor directly.

enum aggregateEnum = AggregateEnum.MOST;
System.out.println(aggregateEnum.aggregateCH);  // 最多

Java Polymorphism

When instantiating a subclass, I can declare the variable type as its parent class’ name. But when doing this, I cannot use attributes and methods unique to the subclass.

public class PolymorphismExample {
    public static void main(String[] args) {
        Animal myAnimal = new Animal();
        Animal myDog = new Dog();
        Animal myCat = new Cat();

        myAnimal.speak(); // Output: This animal speaks in its own way.
        myDog.speak();    // Output: Dog barks.
        myCat.speak();    // Output: Cat meows.
        
        Animal[] animals = {myAnimal, myDog, myCat};
        for (Animal animal : animals) {
            animal.speak(); // Call the speak method on each type of animal
        }
    }
}

Thread

创建线程

继承Thread

public class ThreadAnalysis extends Thread {
    public static int count = 0;

    @Override
    public void run() {
        for(int i = 0; i < 4000; i++) {
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadAnalysis threadAnalysis1 = new ThreadAnalysis();
        ThreadAnalysis threadAnalysis2 = new ThreadAnalysis();
        threadAnalysis1.start();
        threadAnalysis2.start();

        threadAnalysis1.join();
        threadAnalysis2.join();

        System.out.println(ThreadAnalysis.count);  // 打印初的结果可能小于8000
    }
}

实现Runnable

public class RunnableAnalysis implements Runnable {
    public static int count = 0;

    @Override
    public void run() {
        for(int i = 0; i < 4000; i++) {
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Runnable task = new RunnableAnalysis();

        Thread thread1 = new Thread(task, "my task1");
        Thread thread2 = new Thread(task, "my task2");
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();

        System.out.println(RunnableAnalysis.count);  // 打印初的结果可能小于8000
    }
}

实现Callable

前述的两个创建多线程的方法的run()方法都是没有返回结果的。而通过实现Callable接口可以获取线程的返回结果。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class CallableAnalysis {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);  // 创建一个最大包含五个线程的线程池
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            Callable<String> task = () -> {
                return "Task Id of " + taskId;
            };
            futures.add(executor.submit(task));
        }

        // 获取多线程的返回结果
        for (Future<String> future : futures) {
            try {
                System.out.println(future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        executor.shutdown();
    }
}

堆和元空间

JVM进程由main()函数启动,main()函数作为JVM进程中的主线程,同时还有一些其他的线程(Signal Dispatcher等)。方法区在JDK 8 之后叫元空间(MetaSpace)

多个线程共用进程的方法区。实例变量存储在中,静态变量(包括其他类的静态变量)存储在方法区中。

堆和元空间变量存储实例

public class ThreadAnalysis {
    // 存储在元空间中
    public static int staticCounter = 0;
    // 存储在堆中
    public int heapCounter = 0;

    public static void main(String[] args) throws InterruptedException {
        ThreadAnalysis threadAnalysis = new ThreadAnalysis();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                staticCounter++;
                threadAnalysis.heapCounter++;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                staticCounter++;
                threadAnalysis.heapCounter++;
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("staticCounter(方法区)最终值: " + staticCounter); // 可能小于10000
        System.out.println("heapCounter(堆)最终值: " + threadAnalysis.heapCounter); // 可能小于10000
    }
}

synchronized锁线程安全

public class ThreadAnalysis {
    // 存储在元空间中
    public static int staticCounter = 0;
    // 存储在堆中
    public int heapCounter = 0;

    public static void main(String[] args) throws InterruptedException {
        ThreadAnalysis threadAnalysis = new ThreadAnalysis();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (ThreadAnalysis.class) { staticCounter++; };
                synchronized (threadAnalysis) { threadAnalysis.heapCounter++; };
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (ThreadAnalysis.class) { staticCounter++; };
                synchronized (threadAnalysis) { threadAnalysis.heapCounter++; };
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("staticCounter(方法区)最终值: " + staticCounter);  // 10000
        System.out.println("heapCounter(堆)最终值: " + threadAnalysis.heapCounter);  // 10000
    }
}

AtomicInteger线程安全

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadAnalysis {
    // 使用AtomicInteger保证原子性
    public static AtomicInteger staticCounter = new AtomicInteger(0);
    public AtomicInteger heapCounter = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        ThreadAnalysis threadAnalysis = new ThreadAnalysis();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                staticCounter.incrementAndGet();
                threadAnalysis.heapCounter.incrementAndGet();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                staticCounter.incrementAndGet();
                threadAnalysis.heapCounter.incrementAndGet();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("staticCounter(方法区)最终值: " + staticCounter.get()); // 10000
        System.out.println("heapCounter(堆)最终值: " + threadAnalysis.heapCounter.get()); // 10000
    }
}
  1. 利用Runnable创建子线程。

Runnable的定义如下:

@FunctionalInterface
public interface Runnable {
    void run(); 
}

Runnable创建子线程。

Runnable task = () -> {System.out.println("This is thread 1");};
Thread thread1 = new Thread(task, "My thread 1");
thread1.start();  // start the thread1

try {
    thread1.join();  // stop the main thread and wait for thread1 to complete 
} catch (InterruptedException e) {
    e.printStackTrace();
}  // 要使用try-catch结构或在其所在的方法添加throws InterruptedException
  1. 利用Thread创建子线程
public class MyThread extends Thread {
    public void run() {
        System.out.println("This is my thread");
    }
}

volatile关键字

线程要想使用进程中的共享内存中的变量a=0时,一般先将其拷贝进自己本地线程内存,对其进行操作(a++)后再写回进程中的共享内存。在完成写回操作之前,其他线程看到的变量a始终是0。

volatile关键字可以确保变量a一直保持可见性,即它的值一但被修改,进程共享内存中的变量a会立即刷新。上述过程通过内存屏障实现,使得线程表现得像是直接在操作进程主内存。

volatile可以保证数据可见性,但不能保证数据原子性;synchronized两者均可保证。

volatile的使用样例:及时控制一个线程的启停:

public class VolatileExample {
    private volatile boolean running = true;

    public void start() {
        new Thread(() -> {
            while (running) {
                // 执行任务
            }
        }).start();
    }

    public void stop() {
        running = false;
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileExample example = new VolatileExample();
        example.start();
        Thread.sleep(1000);
        example.stop();  // 1秒后停止线程
    }
}

设计模式

“工厂方法”模式

工厂方法模式让子类决定要实例化的类是哪一个。

// FactoryMethodDemo.java
package com.example;

// Button 
interface Button {
    void paint();
}

// Windows Button
class WindowsButton implements Button {
    public void paint() {
        System.out.println("This is Windows button.");
    }
}

// Mac Button
class MacButton implements Button {
    public void paint() {
        System.out.println("This is Mac button.");
    }
}


// GUI Factory Button
interface GUIFactory {
    Button createButton();
}

// Windows Factory
class WindowsFactory implements GUIFactory {
    public Button createButton() {
        return new WindowsButton();
    }
}

// Mac Factory
class MacFactory implements GUIFactory {
    public Button createButton() {
        return new MacButton();
    }
}


public class FactoryMethodDemo {

    public static void main(String[] args) {

        // I don't need to specify a button. The factory will decide which button it is.
        GUIFactory factory1 = new WindowsFactory();
        Button newButton1 = factory1.createButton();
        newButton1.paint();

        GUIFactory factory2 = new MacFactory();
        Button newButton2 = factory2.createButton();
        newButton2.paint();
    }
}

“抽象工厂”模式

“工厂方法”模式:一个接口和多个实现类。 “抽象工厂”模式:多个接口和每个接口分别的多个实现类。

“建造者”模式

“建造者”设计模式用于创建复杂对象。尤其是当对象的属性很多,一部分属性必选,一部分属性可选。

public class User {
    private final String firstName; // 必需
    private final String lastName;  // 必需
    private final int age;          // 可选
    private final String phone;     // 可选
    private final String address;   // 可选

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    // UserBuilder静态类
    public static class UserBuilder {
        private final String firstName;
        private final String lastName;
        private int age = 0;
        private String phone = "";
        private String address = "";

        public UserBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public UserBuilder address(String address) {
            this.address = address;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }

    public static void main(String[] args) {
        User user = new User.UserBuilder("John", "Doe").age(30)
                            .phone("1234567890")
                            .address("Fake Address 1234")
                            .build();
    }
}

再构造函数中传入一个自定义的Builder静态类。这个Build类中的构造函数可以定义User类的必选属性;而其他方法可用于接收User类的可选属性,并返回Builder类。最终定义一个build方法用于实例化User类。

“适配器”模式

如果有一个第三方类不是开发者要使用的目标接口的实现,那么可以设计一个Adapter类作为中间件,使得调用目标接口的方法的同时能调用第三方类的方法。

具体来讲,Adapter类要实现目标接口,然后再Adapter类中:1. 接收第三方类的实例作为属性;2. 实现接口的一个方法以扩展这个实例方法。

package com.example;

// 目标接口
interface Target {
    void request();
}

// 第三方类(被适配者)
class ThirdPartyClass {
    public void specifiedRequest() {
        // 第三方类的具体实现
    }
}

// 适配器类
class AdapterClass implements Target {
    private ThirdPartyClass thirdPartyClass;

    public AdapterClass(ThirdPartyClass thirdPartyClass) {
        this.thirdPartyClass = thirdPartyClass;
    }

    @Override
    public void request() {
        // 调用被适配者的方法,此处可以添加一些额外的处理
        thirdPartyClass.specifiedRequest();
    }
}

public class AdapterClinet {
    public static void main(String[] args) {
        ThirdPartyClass thirdPartyClass = new ThirdPartyClass();
        Target target = new AdapterClass(thirdPartyClass);
        target.request();
    }
}

Java基础知识

JVM, JDK, JRE, 字节码

JVM是运行Java字节码的虚拟机,可以让Java实现“一次编译,随处可运行”

https://oss.javaguide.cn/github/javaguide/java/basis/jdk-include-jre.png

.java -> .class -> 机器码

javac编译器负责Java源代码到字节码的转换.java -> .class

解释器或JIT负责将字节码转换为机器码。

位移运算符

二进制转化

int类型转化为二进制。

先计算原码,再计算反码(正数反码不变,负数除符号位其余位取反),最后计算补码(正数补码+0,负数在反码的基础上+1)

样例1:5的二进制

Integer.toBinaryString(5)  // 101
  1. 原码:00000000,00000000,00000000,00000101
  2. 反码:00000000,00000000,00000000,00000101
  3. 补码:00000000,00000000,00000000,00000101 (二进制转化结果)

样例2:-5的二进制

Integer.toBinaryString(-5)  // 11111111,11111111,11111111,11111011
  1. 原码:10000000,00000000,00000000,00000101
  2. 反码:11111111,11111111,11111111,11111010
  3. 补码:11111111,11111111,11111111,11111011(二进制转化结果)

样例3:Interger.MIN_VALUE的二进制

\[\text{Integer.MIN_VALUE}=-2^{31}\] 由于除开符号位外的另外31位最大只能表示到\(2^{31}-1\),因此无法用原码表示Integer.MIN_VALUE。

Integer.toBinaryString(Integer.MIN_VALUE);  // 10000000000000000000000000000000

Integer.MIN_VALUE是补码系统的特殊值,直接构造它的补码:10000000,00000000,00000000,00000000

因此在Java中\(\text{Integer.MIN_VALUE}\times2=\text{Integer.MIN_VALUE}<<1=0\)

样例4:Interger.MIN_VALUE+1的二进制

Integer.toBinaryString(Integer.MIN_VALUE + 1);  // 10000000000000000000000000000001
  1. 原码:11111111,11111111,11111111,11111111
  2. 反码:10000000,00000000,00000000,00000000
  3. 补码:10000000,00000000,00000000,00000001

位移运算

位移运算是基于补码的位移。

\[5<<3=5\times2^3=40\] \[-5<<3=-5\times2^3=-40\]

\(<<\)为左移运算符,高位丢弃,低位补零。

\(>>\)为右移运算符,高位补符号位,低位丢弃。

\(>>>\)无符号右移,忽略符号位,空位以0补齐。

例:对int类型左移40位的处理过程为:40%32=8,即左移8位。

Java知识点记录

  1. 子类不能缩小父类的访问权限,不然有违多态性。例如父类的public方法不能被子类重写为private方法。

Java常见问题

内存泄漏

内存泄漏是指不再被使用的对象未能被GC机制自动回收。常见的内存泄漏如下。

静态集合变量的使用

public class UserController {
    private static Map<Long, User> cache = new HashMap<>();

    public void addUser(User user) {
            cache.put(user.getId, user);  // cache变量属于类,它始终被引用,不会被回收。
    }
}

未显示关闭连接池

public class OrderService {
    private DataSource dataSource;

    public void processOrder() {
            Connection conn = dataSource.getConnection();
                // 执行SQL但忘记conn.close()
    }
}

dataSource.getConnection()会指定一个连接池,将连接池标记为“使用中”。该连接池会被某种特殊的数据结构所引用,即使processOrder()执行完毕,局部变量conn被释放,连接池的引用也不会被释放。

改正后:

public class OrderService {
    private DataSource dataSource;

    public void processOrder() {
        try (Connection conn = dataSource.getConnection()) {
        // 执行SQL
        } catch (SQLException e) {
            // 处理异常
        }
    }
}