JAVA核心类

字符串

字符串不可变性

概念: 在 Java 中,字符串(String)是不可变的,这意味着一旦创建,字符串的内容无法更改。任何对字符串的修改都会创建一个新的字符串对象,而原字符串保持不变。

示例:

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
String s = "Hello";
System.out.println(s); // 输出: Hello

s = s.toUpperCase();
System.out.println(s); // 输出: HELLO
}
}

解释:

  • 首先创建的 s 对象内容为 "Hello"
  • 调用 s.toUpperCase() 方法后,它并未修改原字符串 s,而是返回了一个新字符串 "HELLO" 并将 s 的引用指向这个新对象。原始的 "Hello" 字符串对象仍然存在于内存中,直至被垃圾回收。

字符串比较

概念: 在比较字符串时,== 比较的是两个引用是否指向同一个对象,而 equals() 方法比较的是两个字符串的内容是否相同。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";

// 用 == 比较
System.out.println(s1 == s2); // 输出: true

// 用 equals() 比较
System.out.println(s1.equals(s2)); // 输出: true

// 改变 s2 的创建方式
String s3 = "HELLO".toLowerCase();
System.out.println(s1 == s3); // 输出: false
System.out.println(s1.equals(s3)); // 输出: true
}
}

解释:

  • 在第一个例子中,s1s2 都是通过字符串字面量赋值的,它们指向常量池中的同一个字符串对象,因此 s1 == s2 返回 true
  • 在第三个例子中,s3 是通过方法 toLowerCase() 创建的,它虽然内容与 s1 相同,但引用的是不同的对象,所以 s1 == s3 返回 false。然而,由于 s1s3 的内容相同,s1.equals(s3) 返回 true

子字符串操作

概念: Java 提供了一些方法来操作字符串,比如提取子串、搜索子串等。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String[] args) {
String s = "Hello, World!";

// 提取子串
String sub = s.substring(7, 12);
System.out.println(sub); // 输出: World

// 搜索子串位置
int index = s.indexOf("World");
System.out.println(index); // 输出: 7
}
}

解释:

  • substring(7, 12) 提取从索引 7 到 11 的字符形成新的子串 "World"
  • indexOf("World") 返回子串 "World" 在字符串中的起始位置(索引 7)。

字符串的常用方法

  • 替换: replace() 可以用来替换字符串中的字符或子串。
  • 分割: split() 可以根据指定的正则表达式分割字符串,返回字符串数组。
  • 拼接: join() 用来将多个字符串拼接在一起,使用指定的分隔符。
  • 格式化: format() 用来创建带占位符的格式化字符串。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
public static void main(String[] args) {
// 替换字符
String replaced = "hello".replace('l', 'w');
System.out.println(replaced); // 输出: hewwo

// 分割字符串
String[] parts = "A,B,C".split(",");
for(String part : parts) {
System.out.println(part); // 输出: A, B, C
}

// 拼接字符串
String joined = String.join("-", "A", "B", "C");
System.out.println(joined); // 输出: A-B-C

// 格式化字符串
String formatted = String.format("Hi %s, your score is %d!", "Alice", 85);
System.out.println(formatted); // 输出: Hi Alice, your score is 85!
}
}

其他:

1. 字符串池(String Pool)

在Java中,字符串池是一个特殊的内存区域,用于存储字符串字面量。Java编译器会将相同的字符串字面量存储到字符串池中,以减少内存消耗。当你创建一个字符串字面量时,JVM会首先检查字符串池中是否已经存在相同内容的字符串。如果存在,则直接引用这个字符串;否则,会在池中创建一个新的字符串对象。

1
2
3
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // true,因为s1和s2引用同一个字符串池中的对象

然而,当使用new关键字创建字符串时,它会在堆上创建一个新的字符串对象,而不会使用字符串池。

1
2
3
String s1 = new String("Hello");
String s2 = "Hello";
System.out.println(s1 == s2); // false,因为s1是一个新的对象

2. 字符串的不可变性

字符串的不可变性不仅仅是因为它的内部char[]final,更深层次的原因包括:

  • 线程安全:不可变对象是天然的线程安全的,不需要额外的同步机制。
  • 字符串池的高效利用:由于字符串是不可变的,所以字符串池可以安全地被共享。
  • 性能优化:不可变对象可以更好地被编译器和JVM优化,比如在哈希表中作为键使用。

3. StringBuilder 和 StringBuffer

在需要对字符串进行大量修改时,StringBuilderStringBuffer是更好的选择,因为它们是可变的。与String不同,StringBuilderStringBuffer在修改内容时不会创建新的对象,而是直接在原对象上进行操作。

  • StringBuilder:非线程安全,适合单线程环境,性能较好。
  • StringBuffer:线程安全,适合多线程环境,性能稍差。
1
2
3
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
System.out.println(sb.toString()); // "Hello World"

4. 字符串的哈希码

String类的hashCode()方法是根据字符串的内容计算的,符合以下公式: \(\text{hashCode} = s[0] \times 31^{(n-1)} + s[1] \times 31^{(n-2)} + \ldots + s[n-1]\)

其中,s[i]是字符串的第i个字符,n是字符串的长度。由于这个公式的实现,字符串的哈希码在哈希表中有很好的分布特性。

1
2
String s = "Hello";
System.out.println(s.hashCode()); // 69609650

5. 字符串的比较性能

由于String类的equals()方法会逐字符比较两个字符串的内容,因此对于长字符串的比较,性能可能会受到影响。为此,可以在性能关键的部分使用compareTo()方法,它不仅比较内容,还返回一个整数表示字符串的相对顺序。

1
2
3
String s1 = "Hello";
String s2 = "World";
System.out.println(s1.compareTo(s2)); // -15,表示s1小于s2

6. 字符串的常见陷阱

  • 空字符串与null:调用null对象的任何方法都会抛出NullPointerException,所以在操作字符串前应该确保它不是null

    1
    2
    3
    4
    String s = null;
    if (s != null && s.isEmpty()) {
    System.out.println("Empty String");
    }
  • 拼接效率:频繁的字符串拼接会导致性能问题,因为每次拼接都会创建新的字符串对象。可以考虑使用StringBuilderStringBuffer来优化。

    1
    2
    3
    4
    String result = "";
    for (int i = 0; i < 1000; i++) {
    result += "a"; // 每次循环都会创建一个新的字符串对象
    }

    StringBuilder类是一个可变的字符串对象,适合高效的字符串拼接。

    1. StringBuilder的优势

      • 可变性StringBuilder的内容可以被修改,不会每次修改时都创建新的对象。
      • 预分配缓冲区:可以指定初始容量,避免了频繁的内存分配。
      • 链式操作:支持方法链调用,如appendinsert
    2. 例子

      1
      2
      3
      4
      5
      6
      StringBuilder sb = new StringBuilder(1024);
      for (int i = 0; i < 1000; i++) {
      sb.append(',');
      sb.append(i);
      }
      String s = sb.toString();

      在这个例子中,StringBuilder用于高效地拼接多个字符串,而不会在每次循环中创建新的字符串对象。

    3. 链式操作示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public class Main {
      public static void main(String[] args) {
      var sb = new StringBuilder(1024);
      sb.append("Mr ")
      .append("Bob")
      .append("!")
      .insert(0, "Hello, ");
      System.out.println(sb.toString());
      }
      }

      通过链式调用,StringBuilder可以在同一个对象上进行多个操作,提升了代码的可读性和流畅性。

    4. 自定义链式操作的类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      public class Main {
      public static void main(String[] args) {
      Adder adder = new Adder();
      adder.add(3)
      .add(5)
      .inc()
      .add(10);
      System.out.println(adder.value());
      }
      }

      class Adder {
      private int sum = 0;

      public Adder add(int n) {
      sum += n;
      return this;
      }

      public Adder inc() {
      sum ++;
      return this;
      }

      public int value() {
      return sum;
      }
      }

      这个例子演示了如何实现一个支持链式操作的类,类似于StringBuilder的设计模式。

    5. 编译器优化

      • Java编译器会将多个连续的+操作优化为StringConcatFactory操作,通常在运行时通过StringBuilder实现高效拼接。
    6. StringBuffer的历史

      • StringBufferStringBuilder的线程安全版本,但由于同步的开销,现在一般推荐使用StringBuilder,除非在多线程环境中需要线程安全的操作。

7. 字符串的编码转换

在国际化应用中,字符串的编码转换是一个重要问题。Java的String类提供了多种方法进行字符编码的转换。需要注意的是,编码转换时要使用正确的字符集,否则可能会导致乱码。

1
2
byte[] utf8Bytes = "Hello".getBytes(StandardCharsets.UTF_8);
String utf8String = new String(utf8Bytes, StandardCharsets.UTF_8);

8. 字符串的内存管理

Java字符串池的内存管理是通过垃圾收集机制实现的。字符串池中的字符串在JVM退出时会被释放,但是在运行时它们会一直保留在内存中。因此,在处理大量不同的字符串时,要注意内存的使用,可以通过手动调用intern()方法将字符串放入池中,以便重复使用。

1
2
3
String s1 = new String("Hello").intern();
String s2 = "Hello";
System.out.println(s1 == s2); // true

字符串拼接后话之StringJoiner:

StringJoiner 是 Java 8 引入的一个类,用于在不需要显式使用 StringBuilder 的情况下拼接字符串。它提供了一种简洁的方式来拼接多个字符串,并可以灵活地指定分隔符、前缀和后缀。

主要特性

  1. 分隔符
    • StringJoiner 允许指定一个分隔符,这个分隔符会被插入到拼接的每两个元素之间。
  2. 前缀和后缀
    • 可以指定一个前缀和一个后缀,前缀会加在拼接结果的开头,后缀会加在拼接结果的结尾。

构造方法

  1. 基本构造方法

    1
    StringJoiner(CharSequence delimiter)
    • 创建一个 StringJoiner,使用指定的分隔符。
  2. 带前缀和后缀的构造方法

    1
    StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
    • 创建一个 StringJoiner,使用指定的分隔符、前缀和后缀。

主要方法

  1. add()

    • 用于向 StringJoiner 中添加元素。

    • 示例:

      1
      2
      StringJoiner sj = new StringJoiner(", ");
      sj.add("Bob").add("Alice").add("Grace");
  2. toString()

    • 返回拼接后的字符串结果。

    • 示例:

      1
      String result = sj.toString(); // 结果: "Bob, Alice, Grace"

示例代码

  1. 简单用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import java.util.StringJoiner;

    public class Main {
    public static void main(String[] args) {
    StringJoiner sj = new StringJoiner(", ");
    sj.add("Bob");
    sj.add("Alice");
    sj.add("Grace");
    System.out.println(sj.toString()); // 输出: Bob, Alice, Grace
    }
    }
  2. 带前缀和后缀的用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import java.util.StringJoiner;

    public class Main {
    public static void main(String[] args) {
    StringJoiner sj = new StringJoiner(", ", "Hello ", "!");
    sj.add("Bob");
    sj.add("Alice");
    sj.add("Grace");
    System.out.println(sj.toString()); // 输出: Hello Bob, Alice, Grace!
    }
    }

注意事项

  • StringJoiner
    • 如果 StringJoiner 中没有添加任何元素,它的 toString() 方法会返回包含前缀和后缀的字符串,且中间不会有分隔符。
  • 线程安全
    • StringJoiner 不是线程安全的,如果需要在多线程环境中使用,应该采取相应的同步措施。

包装类型

Java中的基本类型和引用类型

在Java中,数据类型分为基本类型和引用类型:

  1. 基本类型:包括 byteshortintlongbooleanfloatdoublechar
  2. 引用类型:包括所有 classinterface 类型。

基本类型不能赋值为 null,但引用类型可以。例如:

1
2
String s = null; // 合法
int n = null; // 编译错误

包装类(Wrapper Classes)

为了将基本类型视为对象(引用类型),Java 提供了包装类。每种基本类型都有一个对应的包装类:

基本类型 对应的引用类型
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

自定义 Integer 类示例

1
2
3
4
5
6
7
8
9
10
11
public class Integer {
private int value;

public Integer(int value) {
this.value = value;
}

public int intValue() {
return this.value;
}
}

使用 Java 标准库中的包装类

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
int i = 100;
// 通过 new 操作符创建 Integer 实例(不推荐):
Integer n1 = new Integer(i);
// 通过静态方法 valueOf(int) 创建 Integer 实例:
Integer n2 = Integer.valueOf(i);
// 通过静态方法 valueOf(String) 创建 Integer 实例:
Integer n3 = Integer.valueOf("100");
System.out.println(n3.intValue());
}
}

自动装箱(Auto Boxing)和自动拆箱(Auto Unboxing)

  • 自动装箱:基本类型转换为包装类对象。
  • 自动拆箱:包装类对象转换为基本类型。

示例代码

1
2
3
4
5
6
public class Main {
public static void main(String[] args) {
Integer n = 100; // 自动装箱
int x = n; // 自动拆箱
}
}

注意:自动拆箱可能会导致 NullPointerException

1
2
3
4
5
6
public class Main {
public static void main(String[] args) {
Integer n = null;
int i = n; // 运行时抛出 NullPointerException
}
}

不变类

所有包装类型都是不变类(Immutable Classes)。例如,Integer 类的关键部分如下:

1
2
3
public final class Integer {
private final int value;
}

由于 Integer 是不变的,因此其对象一旦创建便不可更改。

比较两个 Integer 实例

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
Integer x = 127;
Integer y = 127;
Integer m = 99999;
Integer n = 99999;
System.out.println("x == y: " + (x == y)); // true
System.out.println("m == n: " + (m == n)); // false
System.out.println("x.equals(y): " + x.equals(y)); // true
System.out.println("m.equals(n): " + m.equals(n)); // true
}
}

由于 Integer 类内部有缓存机制,== 比较的结果可能依赖于数值范围,建议使用 equals() 方法进行比较。

最佳实践

  1. 使用静态工厂方法:例如 Integer.valueOf() 方法比 new Integer() 更推荐,因为它可能利用缓存来节省内存。
1
2
Integer n1 = new Integer(100); // 不推荐
Integer n2 = Integer.valueOf(100); // 推荐
  1. 进制转换Integer 类提供了将整数转换为不同进制表示的静态方法。

示例代码

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
System.out.println(Integer.toString(100)); // "100"(十进制)
System.out.println(Integer.toString(100, 36)); // "2s"(36进制)
System.out.println(Integer.toHexString(100)); // "64"(十六进制)
System.out.println(Integer.toOctalString(100)); // "144"(八进制)
System.out.println(Integer.toBinaryString(100)); // "1100100"(二进制)
}
}

处理无符号整型

Java 不支持原生无符号整型,但包装类提供了处理无符号整型的方法。

示例代码

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
byte x = -1;
byte y = 127;
System.out.println(Byte.toUnsignedInt(x)); // 255
System.out.println(Byte.toUnsignedInt(y)); // 127
}
}。
  • 使用自动装箱和自动拆箱简化基本类型与包装类之间的转换。
  • 了解和使用不变类的特性,尤其在比较对象时。
  • 优先使用静态工厂方法来创建实例。
  • 熟悉进制转换和处理无符号整型的工具和方法。

JavaBean

在Java中,JavaBean是一种遵循特定规范的Java类,旨在封装数据并提供对这些数据的访问。这种规范主要用于数据传递和工具支持,尤其在图形用户界面(GUI)设计和其他需要自动生成代码的场景中非常有用。

JavaBean规范

一个类要成为JavaBean,通常需要满足以下条件:

  1. 无参数构造函数:必须有一个无参数的构造函数。
  2. 私有属性:所有的属性(字段)应为private,以确保封装性。
  3. 公有getter和setter方法:对于每个属性,必须提供公有的getter和setter方法,方法名遵循特定的命名规范。
  4. 可序列化:通常,JavaBean实现java.io.Serializable接口,使其能够进行序列化和反序列化(虽然这不是强制要求,但在很多应用场景中是常见的实践)。

JavaBean命名规范

  • getter方法:用于获取属性值,方法签名为public Type getXyz(),其中Type是属性的类型,Xyz是属性名的首字母大写形式。

    1
    public String getName() { return this.name; }
  • setter方法:用于设置属性值,方法签名为public void setXyz(Type value),其中Type是属性的类型,Xyz是属性名的首字母大写形式。

    1
    public void setName(String name) { this.name = name; }
  • boolean属性:对于boolean类型的属性,getter方法通常以is开头,如public boolean isXyz()

    1
    public boolean isChild() { return age <= 6; }

示例:Person类

下面是一个符合JavaBean规范的Person类示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person {
private String name;
private int age;

// 无参数构造函数
public Person() {}

// getter方法
public String getName() { return this.name; }
public int getAge() { return this.age; }

// setter方法
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }

// 只读属性示例
public boolean isChild() {
return age <= 6;
}
}

JavaBean的作用

  1. 数据传递:JavaBean常用于封装一组相关的数据,方便在不同组件或系统之间传递数据。
  2. 工具支持:JavaBean被许多IDE(如Eclipse、IntelliJ IDEA)和框架(如Spring)广泛支持,工具可以自动生成代码、分析属性,简化开发过程。
  3. GUI设计:在图形用户界面设计中,JavaBean可以被可视化工具(如Java Beans Development Kit (BDK))用来生成界面组件和事件处理代码。

使用Introspector枚举JavaBean属性

Introspector类可以用来枚举JavaBean的所有属性。以下是一个示例代码,展示如何使用Introspector来获取Person类的属性及其getter和setter方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;

public class Main {
public static void main(String[] args) throws Exception {
// 获取Person类的BeanInfo
BeanInfo info = Introspector.getBeanInfo(Person.class);

// 遍历所有属性描述符
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
System.out.println("Property Name: " + pd.getName());
System.out.println(" Read Method: " + pd.getReadMethod());
System.out.println(" Write Method: " + pd.getWriteMethod());
}
}
}

class Person {
private String name;
private int age;

public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}

在运行上述代码后,控制台将显示Person类的属性名称及其对应的getter和setter方法。

  • JavaBean是一个封装数据的标准Java类,符合特定的规范。
  • 它通过私有属性和公有的getter、setter方法来实现数据封装。
  • JavaBean广泛应用于数据传递和图形用户界面设计,工具支持和自动生成代码是其主要应用场景。
  • 通过Introspector可以方便地获取JavaBean的属性和方法信息。

示例:

1. 简单的Person JavaBean

这是一个基本的JavaBean,包含一些常见的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Person {
private String name;
private int age;

// 无参数构造函数
public Person() {}

// 带参数构造函数
public Person(String name, int age) {
this.name = name;
this.age = age;
}

// getter和setter方法
public String getName() { return name; }
public void setName(String name) { this.name = name; }

public int getAge() { return age; }
public void setAge(int age) { this.age = age; }

// 只读属性
public boolean isAdult() {
return age >= 18;
}
}

2. Book JavaBean

展示了一个包含基本数据类型和对象类型属性的JavaBean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Book {
private String title;
private String author;
private double price;

// 无参数构造函数
public Book() {}

// 带参数构造函数
public Book(String title, String author, double price) {
this.title = title;
this.author = author;
this.price = price;
}

// getter和setter方法
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }

public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }

public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
}

3. Employee JavaBean

展示了如何处理布尔属性和计算属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Employee {
private String name;
private double salary;
private boolean isManager;

// 无参数构造函数
public Employee() {}

// 带参数构造函数
public Employee(String name, double salary, boolean isManager) {
this.name = name;
this.salary = salary;
this.isManager = isManager;
}

// getter和setter方法
public String getName() { return name; }
public void setName(String name) { this.name = name; }

public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }

public boolean isManager() { return isManager; }
public void setManager(boolean isManager) { this.isManager = isManager; }

// 只读属性:年薪
public double getAnnualSalary() {
return salary * 12;
}
}

4. Car JavaBean

展示了如何使用JavaBean来封装复杂数据类型和计算属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Car {
private String make;
private String model;
private int year;
private double price;

// 无参数构造函数
public Car() {}

// 带参数构造函数
public Car(String make, String model, int year, double price) {
this.make = make;
this.model = model;
this.year = year;
this.price = price;
}

// getter和setter方法
public String getMake() { return make; }
public void setMake(String make) { this.make = make; }

public String getModel() { return model; }
public void setModel(String model) { this.model = model; }

public int getYear() { return year; }
public void setYear(int year) { this.year = year; }

public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }

// 计算属性:车龄
public int getCarAge() {
return 2024 - year; // 假设当前年份是2024
}
}

5. Address JavaBean

展示了如何封装对象类型属性和使用嵌套的JavaBean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Address {
private String street;
private String city;
private String postalCode;

// 无参数构造函数
public Address() {}

// 带参数构造函数
public Address(String street, String city, String postalCode) {
this.street = street;
this.city = city;
this.postalCode = postalCode;
}

// getter和setter方法
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }

public String getCity() { return city; }
public void setCity(String city) { this.city = city; }

public String getPostalCode() { return postalCode; }
public void setPostalCode(String postalCode) { this.postalCode = postalCode; }
}

使用示例

可以在其他类中创建这些JavaBean的实例,并使用其getter和setter方法来操作数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
public static void main(String[] args) {
// 创建Person实例
Person person = new Person("Alice", 30);
System.out.println(person.getName() + " is adult: " + person.isAdult());

// 创建Book实例
Book book = new Book("Java Programming", "John Doe", 29.99);
System.out.println("Book title: " + book.getTitle());

// 创建Employee实例
Employee employee = new Employee("Bob", 5000, true);
System.out.println("Employee annual salary: " + employee.getAnnualSalary());

// 创建Car实例
Car car = new Car("Toyota", "Corolla", 2018, 20000);
System.out.println("Car age: " + car.getCarAge());

// 创建Address实例
Address address = new Address("123 Main St", "Springfield", "12345");
System.out.println("Address: " + address.getStreet() + ", " + address.getCity());
}
}

枚举类型

在Java中,使用enum来定义枚举类型比使用static final常量更加安全和灵活。

基本定义

  1. 定义枚举类型 使用enum关键字定义枚举类,枚举常量的定义以逗号分隔。

    1
    2
    3
    public enum Weekday {
    SUN, MON, TUE, WED, THU, FRI, SAT;
    }
  2. 使用枚举 枚举类型的常量可以直接使用==比较,因为它们是唯一的实例。

    1
    2
    3
    4
    5
    6
    Weekday day = Weekday.SUN;
    if (day == Weekday.SAT || day == Weekday.SUN) {
    System.out.println("Work at home!");
    } else {
    System.out.println("Work at office!");
    }

优势

  1. 类型安全 枚举类型提供了编译时的类型检查,避免了错误的值被使用。

    1
    2
    3
    int day = 1; // 错误:不能赋值给Weekday
    if (day == Weekday.SUN) { // 编译错误
    }
  2. 不可继承 枚举类自动继承自java.lang.Enum,并且不能被继承。

  3. 自定义字段和方法 可以为枚举添加字段、构造方法和自定义方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public enum Weekday {
    MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0);

    public final int dayValue;

    private Weekday(int dayValue) {
    this.dayValue = dayValue;
    }
    }
  4. 覆写toString() 可以覆写toString()方法来提供自定义的输出格式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public enum Weekday {
    MON(1, "Monday"), TUE(2, "Tuesday"), WED(3, "Wednesday"),
    THU(4, "Thursday"), FRI(5, "Friday"), SAT(6, "Saturday"), SUN(0, "Sunday");

    public final int dayValue;
    private final String dayName;

    private Weekday(int dayValue, String dayName) {
    this.dayValue = dayValue;
    this.dayName = dayName;
    }

    @Override
    public String toString() {
    return this.dayName;
    }
    }

枚举在switch语句中的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
Weekday day = Weekday.SUN;
switch(day) {
case MON: case TUE: case WED: case THU: case FRI:
System.out.println("Work at office!");
break;
case SAT: case SUN:
System.out.println("Work at home!");
break;
default:
throw new RuntimeException("Unexpected day: " + day);
}
}
}
  • enum提供了类型安全、不可继承和更加结构化的常量定义。
  • 枚举实例是唯一的,可以添加字段、方法和构造函数。
  • 使用switch语句时,枚举类型比intString更安全且易于维护。

不变类和 record

在Java中,不变类(Immutable Class)是指创建后其状态不能被修改的类。这种设计可以提高程序的安全性和可靠性。StringInteger 等类都是不变类。Java 14 引入了 record,使得定义不变类更加简洁和高效。

不变类的定义

一个不变类通常具有以下特点:

  1. 类本身使用 final 修饰,防止被继承。
  2. 所有字段都使用 final 修饰,确保创建实例后字段值不变。
  3. 必须正确覆写 equals()hashCode() 方法,以保证在集合中的正确行为。

例如,定义一个不变类 Point

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public final class Point {
private final int x;
private final int y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}

public int x() {
return this.x;
}

public int y() {
return this.y;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}

@Override
public int hashCode() {
return Objects.hash(x, y);
}

@Override
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}

equals()hashCode() 方法在 Java 中用于对象比较和哈希处理,它们在集合类和数据结构中的作用非常重要。

equals() 方法

  • 作用:用于比较两个对象是否相等。
  • 签名public boolean equals(Object obj)
  • 实现:通常,equals() 方法会根据对象的状态(字段值)来判断两个对象是否相等。例如,如果两个 Point 对象的 xy 坐标都相同,则它们是相等的。

重写 equals() 的基本原则

  1. 自反性:对于任何非 null 引用 xx.equals(x) 应返回 true
  2. 对称性:对于任何非 null 引用 xy,如果 x.equals(y)true,则 y.equals(x) 也应为 true
  3. 传递性:对于任何非 null 引用 xyz,如果 x.equals(y)true,且 y.equals(z)true,则 x.equals(z) 也应为 true
  4. 一致性:对于任何非 null 引用 xy,多次调用 x.equals(y) 应返回相同的结果,只要 xy 的状态没有改变。
  5. null 的比较:对于任何非 null 引用 xx.equals(null) 应返回 false

示例

1
2
3
4
5
6
7
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}

hashCode() 方法

  • 作用:用于返回对象的哈希码,这是一种用于快速查找和存储对象的整数值。
  • 签名public int hashCode()
  • 实现hashCode() 方法生成一个整数值,该值通常基于对象的状态。如果两个对象通过 equals() 方法比较相等,那么它们的 hashCode() 方法也应返回相同的哈希码。

重写 hashCode() 的基本原则

  1. 一致性:对于任何非 null 引用 x,只要对象的状态没有变化,调用 x.hashCode() 多次应该返回相同的结果。
  2. 相等性:如果两个对象通过 equals() 方法比较相等,那么它们的 hashCode() 方法必须返回相同的值。

示例

1
2
3
4
@Override
public int hashCode() {
return Objects.hash(x, y);
}

为什么需要重写 equals()hashCode()

在 Java 集合框架中,例如 HashSetHashMapequals()hashCode() 方法用于判断对象的相等性和存储位置。HashSet 使用 hashCode() 来确定对象存储的位置,然后使用 equals() 方法来检查是否存在重复对象。如果不正确重写这两个方法,可能会导致集合行为异常,例如无法正确查找或删除对象。

  • equals() 用于比较对象的相等性。
  • hashCode() 用于生成对象的哈希码,帮助快速存储和查找对象。
  • 在定义不变类时,正确重写 equals()hashCode() 方法非常重要,以确保对象在集合中的正确行为。

record 的简化

record 是 Java 14 引入的一种新特性,用于简化不变类的定义。使用 record 可以一行代码定义一个不变类,并自动生成构造方法、访问器方法、toString()equals()hashCode() 方法。

定义 record

使用 record 关键字定义 Point 类:

1
public record Point(int x, int y) {}

这个定义相当于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public final class Point extends Record {
private final int x;
private final int y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}

public int x() {
return this.x;
}

public int y() {
return this.y;
}

@Override
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}

@Override
public int hashCode() {
return Objects.hash(x, y);
}
}

Compact Constructor

如果需要在 record 的构造方法中添加参数验证,可以使用 Compact Constructor:

1
2
3
4
5
6
7
public record Point(int x, int y) {
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Coordinates cannot be negative");
}
}
}

编译器将自动生成带有验证的构造方法:

1
2
3
4
5
6
7
8
9
10
public final class Point extends Record {
public Point(int x, int y) {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Coordinates cannot be negative");
}
this.x = x;
this.y = y;
}
...
}

静态方法

可以为 record 添加静态工厂方法:

1
2
3
4
5
6
7
8
9
public record Point(int x, int y) {
public static Point of() {
return new Point(0, 0);
}

public static Point of(int x, int y) {
return new Point(x, y);
}
}

这使得创建 Point 实例变得更加灵活:

1
2
var p1 = Point.of();        // 创建 (0, 0)
var p2 = Point.of(123, 456); // 创建 (123, 456)
  • 不变类的特点是使用 final 修饰类和字段,确保实例状态不可修改。
  • 使用 record 可以更简洁地定义不变类,同时自动生成常用方法。
  • record 支持 Compact Constructor,用于添加参数验证逻辑。
  • 可以为 record 添加静态工厂方法,简化对象创建。

BigInteger

在 Java 中,BigInteger 是一个用于表示任意精度整数的类,它提供了超过 long 类型所能表示的整数范围的功能。BigInteger 是不可变的,并且继承自 Number 类,这使得它可以转换为基本数据类型如 byteshortintlongfloatdouble

BigInteger 的基本使用

  1. 创建 BigInteger 对象

    1
    BigInteger bi = new BigInteger("1234567890");
  2. 执行运算

    • 加法

      1
      2
      3
      BigInteger i1 = new BigInteger("1234567890");
      BigInteger i2 = new BigInteger("12345678901234567890");
      BigInteger sum = i1.add(i2); // 结果是 12345678902469135780
    • 乘法

      1
      BigInteger product = i1.multiply(i2);
    • 指数运算

      1
      BigInteger power = bi.pow(5); // 结果是 2867971860299718107233761438093672048294900000
    • 转换为 long 类型

      1
      2
      BigInteger i = new BigInteger("123456789000");
      long longValue = i.longValue(); // 123456789000

BigInteger 的转换方法

BigInteger 继承自 Number 类,提供了一些方法用于将其转换为基本数据类型:

  • 转换为 byte

    1
    byte byteValue = bi.byteValue();
  • 转换为 short

    1
    short shortValue = bi.shortValue();
  • 转换为 int

    1
    int intValue = bi.intValue();
  • 转换为 long

    1
    long longValue = bi.longValue();
  • 转换为 float

    1
    float floatValue = bi.floatValue();
  • 转换为 double

    1
    double doubleValue = bi.doubleValue();

精确转换与溢出

对于超出基本数据类型范围的 BigInteger 对象,使用 longValueExact()intValueExact() 等方法时,会抛出 ArithmeticException,如果值超出基本类型的范围。

  • 转换时溢出示例

    1
    2
    3
    4
    BigInteger bigValue = new BigInteger("123456789000");
    long longValueExact = bigValue.longValueExact(); // 123456789000
    BigInteger hugeValue = new BigInteger("12345678900000000000000000000000000000000000000000000000000000000000000000000000000000000000");
    long overflowValue = hugeValue.longValueExact(); // java.lang.ArithmeticException: BigInteger out of long range

BigInteger 转换为 floatdouble

BigInteger 的值超出 floatdouble 的最大表示范围时,结果可能会出现溢出或不准确:

  • 转换为 float

    1
    2
    3
    BigInteger hugeValue = new BigInteger("999999").pow(99);
    float floatValue = hugeValue.floatValue();
    System.out.println(floatValue); // Infinity

    由于 float 类型的最大值约为 \(3.4 \times 10^{38}\),当 BigInteger 的值超出这个范围时,floatValue() 方法会返回 Infinity

  • 转换为 double

    1
    2
    double doubleValue = hugeValue.doubleValue();
    System.out.println(doubleValue); // Infinity

    double 类型的最大值约为 \(1.8 \times 10^{308}\),当 BigInteger 的值超出这个范围时,doubleValue() 方法也会返回 Infinity

  • BigInteger 提供了对任意大小整数的支持,适用于需要超出 long 范围的整数计算。
  • 使用 BigInteger 时,操作是通过实例方法进行的,且结果不会受到固定大小限制。
  • 在转换 BigInteger 为基本数据类型时,如果数值超出范围,可能会出现数据丢失或溢出。
  • 对于极大的 BigInteger,在转换为 floatdouble 时,可能会返回 Infinity 或不准确的结果。

BigDecimal

BigDecimal 是 Java 中用于表示任意精度的浮点数的类,特别适合处理需要精确计算的场景,如金融计算。与 BigInteger 类似,BigDecimal 提供了比 doublefloat 更高的精度,并且不受浮点数计算中常见的精度损失问题的影响。

BigDecimal 的基本操作

创建 BigDecimal 对象

你可以通过构造函数创建 BigDecimal 对象:

1
BigDecimal bd = new BigDecimal("123.4567");

使用 String 构造函数是推荐的方式,因为它避免了浮点数转换中的精度损失。

基本运算

BigDecimal 提供了加法、减法、乘法和除法等方法。操作是通过实例方法进行的。

  • 加法

    1
    2
    3
    BigDecimal d1 = new BigDecimal("123.45");
    BigDecimal d2 = new BigDecimal("67.89");
    BigDecimal sum = d1.add(d2); // 191.34
  • 减法

    1
    BigDecimal difference = d1.subtract(d2); // 55.56
  • 乘法

    1
    BigDecimal product = d1.multiply(d2); // 8384.2355
  • 除法(需要指定精度和舍入模式):

    1
    2
    3
    BigDecimal d3 = new BigDecimal("123.456");
    BigDecimal d4 = new BigDecimal("23.456789");
    BigDecimal quotient = d3.divide(d4, 10, RoundingMode.HALF_UP); // 5.2644664375

    如果除法不能精确除尽,需要指定舍入模式,否则会抛出 ArithmeticException

    1
    2
    3
    BigDecimal d5 = new BigDecimal("1");
    BigDecimal d6 = new BigDecimal("3");
    BigDecimal quotient2 = d5.divide(d6, 10, RoundingMode.HALF_UP); // 0.3333333333
  • 求余数

    1
    2
    3
    BigDecimal[] result = d3.divideAndRemainder(d4);
    BigDecimal quotientResult = result[0]; // 商
    BigDecimal remainder = result[1]; // 余数

BigDecimalscalestripTrailingZeros 方法

  • 获取 scalescale 表示小数点后的位数。

    1
    2
    BigDecimal d1 = new BigDecimal("123.45");
    System.out.println(d1.scale()); // 2

    scale 的值可能为负数,表示整数部分末尾的零。例如:

    1
    2
    BigDecimal d3 = new BigDecimal("1234500");
    System.out.println(d3.scale()); // 0
  • 去除末尾的零stripTrailingZeros() 方法将 BigDecimal 对象格式化为一个相等的 BigDecimal,但去掉了末尾的零。

    1
    2
    3
    4
    BigDecimal d1 = new BigDecimal("123.4500");
    BigDecimal d2 = d1.stripTrailingZeros();
    System.out.println(d1.scale()); // 4
    System.out.println(d2.scale()); // 2

    如果 BigDecimal 是整数,stripTrailingZeros() 会返回负数的 scale 值,表示末尾的零:

    1
    2
    3
    4
    BigDecimal d3 = new BigDecimal("1234500");
    BigDecimal d4 = d3.stripTrailingZeros();
    System.out.println(d3.scale()); // 0
    System.out.println(d4.scale()); // -2

BigDecimal 的精度设置

  • 设置精度和舍入模式setScale() 方法可以用来设置 BigDecimal 的精度,并指定舍入模式。

    1
    2
    3
    BigDecimal d1 = new BigDecimal("123.456789");
    BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568
    BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567

比较 BigDecimal 对象

  • equals() 方法equals() 方法不仅要求值相等,还要求 scale 相等:

    1
    2
    3
    BigDecimal d1 = new BigDecimal("123.456");
    BigDecimal d2 = new BigDecimal("123.45600");
    System.out.println(d1.equals(d2)); // false, 因为 scale 不同
  • compareTo() 方法: 使用 compareTo() 方法比较 BigDecimal 对象,它只比较数值而忽略 scale

    1
    2
    3
    BigDecimal d1 = new BigDecimal("123.456");
    BigDecimal d2 = new BigDecimal("123.45600");
    System.out.println(d1.compareTo(d2)); // 0, 因为数值相等

    compareTo() 返回的值:

    • 0 表示相等
    • 负数表示小于
    • 正数表示大于

内部表示

BigDecimal 内部使用 BigInteger 来表示整数部分,使用 scale 来表示小数点位置:

1
2
3
4
public class BigDecimal extends Number implements Comparable<BigDecimal> {
private final BigInteger intVal;
private final int scale;
}
  • BigDecimal 提供了精确的浮点数运算,避免了传统浮点数计算的精度问题。
  • scale 表示小数点后的位数,可能是负数,表示整数部分的零。
  • 使用 stripTrailingZeros() 方法可以去除末尾的零。
  • 在进行除法运算时,必须处理精度和舍入模式。
  • 使用 compareTo() 进行数值比较,避免了 equals() 方法的 scale 问题。

Math 类

Math 类提供了许多静态方法,用于执行各种数学计算。常用方法包括:

  • 绝对值:

    1
    2
    Math.abs(-100); // 100
    Math.abs(-7.8); // 7.8
  • 最大值和最小值:

    1
    2
    Math.max(100, 99); // 100
    Math.min(1.2, 2.3); // 1.2
  • 幂运算:

    1
    Math.pow(2, 10); // 1024
  • 平方根:

    1
    Math.sqrt(2); // 1.414...
  • 指数:

    1
    Math.exp(2); // 7.389...
  • 对数:

    1
    2
    Math.log(4); // 1.386...
    Math.log10(100); // 2
  • 三角函数:

    1
    2
    3
    4
    5
    Math.sin(3.14); // 0.00159...
    Math.cos(3.14); // -0.9999...
    Math.tan(3.14); // -0.0015...
    Math.asin(1.0); // 1.57079...
    Math.acos(1.0); // 0.0
  • 常量:

    1
    2
    double pi = Math.PI; // 3.14159...
    double e = Math.E; // 2.7182818...
  • 生成随机数:

    1
    Math.random(); // 0 <= x < 1

    要生成区间 [MIN, MAX) 的随机数:

    1
    2
    3
    4
    5
    double x = Math.random();
    double min = 10;
    double max = 50;
    double y = x * (max - min) + min; // y 的范围是 [10, 50)
    long n = (long) y; // [10, 50) 的整数

1. 计算一个数的绝对值

有时你需要计算一个数的绝对值,例如在处理距离或误差时:

1
2
3
4
5
6
7
8
9
10
11
12
public class AbsoluteValueExample {
public static void main(String[] args) {
int negativeValue = -25;
double positiveValue = 15.75;

int absInt = Math.abs(negativeValue);
double absDouble = Math.abs(positiveValue);

System.out.println("Absolute value of -25: " + absInt);
System.out.println("Absolute value of 15.75: " + absDouble);
}
}

2. 计算一个数的平方根

如果你需要计算一个数的平方根,例如在几何计算中:

1
2
3
4
5
6
7
8
9
public class SquareRootExample {
public static void main(String[] args) {
double number = 16;

double sqrt = Math.sqrt(number);

System.out.println("Square root of 16: " + sqrt);
}
}

3. 计算最大值和最小值

有时你需要找到一组数中的最大值或最小值,例如在统计分析中:

1
2
3
4
5
6
7
8
9
10
11
12
public class MaxMinExample {
public static void main(String[] args) {
int a = 10, b = 20;
double x = 5.5, y = 2.2;

int maxInt = Math.max(a, b);
double minDouble = Math.min(x, y);

System.out.println("Maximum of 10 and 20: " + maxInt);
System.out.println("Minimum of 5.5 and 2.2: " + minDouble);
}
}

4. 生成区间内的随机数

如果你需要生成指定范围内的随机数,例如用于模拟或测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RandomNumberExample {
public static void main(String[] args) {
double min = 1.0;
double max = 10.0;

double random = Math.random();
double rangeRandom = random * (max - min) + min;

System.out.println("Random number between 1 and 10: " + rangeRandom);

// 生成范围内的整数
long integerRandom = (long) rangeRandom;
System.out.println("Random integer between 1 and 10: " + integerRandom);
}
}

5. 计算对数和指数

如果你需要计算对数或指数,例如在科学计算中:

1
2
3
4
5
6
7
8
9
10
11
public class LogExpExample {
public static void main(String[] args) {
double number = 10;

double logValue = Math.log(number);
double expValue = Math.exp(number);

System.out.println("Natural logarithm of 10: " + logValue);
System.out.println("Exponent of 10: " + expValue);
}
}

6. 使用三角函数

如果你需要计算三角函数的值,例如在图形学中:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TrigonometricFunctionsExample {
public static void main(String[] args) {
double angle = Math.PI / 4; // 45 degrees in radians

double sinValue = Math.sin(angle);
double cosValue = Math.cos(angle);
double tanValue = Math.tan(angle);

System.out.println("Sine of 45 degrees: " + sinValue);
System.out.println("Cosine of 45 degrees: " + cosValue);
System.out.println("Tangent of 45 degrees: " + tanValue);
}
}

HexFormat 类

HexFormat 用于处理 byte[] 和十六进制字符串的转换。

  • byte[] 转换为十六进制字符串:

    1
    2
    3
    4
    5
    import java.util.HexFormat;

    byte[] data = "Hello".getBytes();
    HexFormat hf = HexFormat.of();
    String hexData = hf.formatHex(data); // 48656c6c6f
  • 定制转换格式:

    1
    2
    HexFormat hf = HexFormat.ofDelimiter(" ").withPrefix("0x").withUpperCase();
    hf.formatHex("Hello".getBytes()); // 0x48 0x65 0x6C 0x6C 0x6F
  • 从十六进制字符串转换为 byte[]:

    1
    byte[] bs = HexFormat.of().parseHex("48656c6c6f");

Random 类

Random 用于生成伪随机数。可以指定种子来生成确定的随机序列。

  • 生成随机数:

    1
    2
    3
    4
    5
    6
    Random r = new Random();
    r.nextInt(); // 2071575453
    r.nextInt(10); // [0, 10)
    r.nextLong(); // 8811649292570369305
    r.nextFloat(); // [0, 1)
    r.nextDouble(); // [0, 1)
  • 指定种子:

    1
    2
    3
    4
    Random r = new Random(12345);
    for (int i = 0; i < 10; i++) {
    System.out.println(r.nextInt(100));
    }

SecureRandom 类

SecureRandom 用于生成安全的随机数,通常用于加密应用。

  • 生成安全随机数:

    1
    2
    SecureRandom sr = new SecureRandom();
    System.out.println(sr.nextInt(100));
  • 获取高强度安全随机数生成器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import java.security.SecureRandom;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;

    SecureRandom sr;
    try {
    sr = SecureRandom.getInstanceStrong(); // 高强度生成器
    } catch (NoSuchAlgorithmException e) {
    sr = new SecureRandom(); // 普通生成器
    }
    byte[] buffer = new byte[16];
    sr.nextBytes(buffer);
    System.out.println(Arrays.toString(buffer));

SecureRandom 使用操作系统提供的安全随机种子,确保生成的随机数不可预测。在密码学中,使用 SecureRandom 是确保安全的最佳实践。