跳转至

5 核心类1

约 2538 个字 374 行代码 预计阅读时间 17 分钟

包括:字符串和编码、StringBuilder类、StringJoiner类、包装类型、缓存机制、JavaBean

输出结果都写在了注释中

1. 字符串和编码

1.1 基本特性

在Java中,String是引用类型,本身也是一个class(java.lang.String 类),可以使用字面量 "..." 或构造方法 new String(...)来表示,字符串内容存储在不可变的 char[]byte[] 中,一旦创建,字符串内容不可更改。

Java
String s1 = "Hello!";
String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'});

不可变性:是通过内部的private final char[]字段,以及没有任何修改char[]的方法实现的。

Java
public class demo26 {
    public static void main(String[] args){
        String s = "Hello";
        System.out.println(s);
        // Hello
        s = s.toUpperCase();
        System.out.println(s);
        // HELLO
    }
}

原字符串 s 并未被修改,而是返回了新字符串

1.2 字符串比较

比较方式 描述 示例 使用
== 比较引用是否相同 s1 == s2
equals() 比较内容是否相同 s1.equals(s2)
equalsIgnoreCase() 比较内容忽略大小写 s1.equalsIgnoreCase(s2)
Java
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
String s3 = "hello";
System.out.println(s1 == s3);
// true
System.out.println(s1 == s2);
// false        
System.out.println(s1.equals(s2));
// true

Java编译器在编译期,会自动把所有相同的字符串当作一个对象放入常量池,自然s1s3的引用就是相同的,所以,这种==比较返回true纯属巧合。

1.3 常用字符串方法

方法 作用 示例 返回值
contains() 判断是否包含子串 "Hello".contains("ll") true
indexOf() 查找首次出现位置 "Hello".indexOf("l") 2
lastIndexOf() 查找最后出现位置 "Hello".lastIndexOf("l") 3
startsWith() 是否以...开头 "Hello".startsWith("He") true
endsWith() 是否以...结尾 "Hello".endsWith("lo") true
substring() 从索引开始提取 "Hello".substring(2) "llo"
substring() 提取指定范围子串 "Hello".substring(2, 4) "ll"
trim() 去除首尾空白字符 " Hello ".trim() "Hello"
strip() 去除首尾Unicode空白 "\u3000Hello\u3000".strip() "Hello"
isEmpty() 是否为空字符串 "".isEmpty() true
isEmpty() 是否为空字符串 " ".isEmpty() false
isBlank() 是否全为空白字符 " \n".isBlank() true

1.4 字符串替换

方法 描述 示例 结果
replace() 替换字符 "hello".replace('l', 'w') "hewwo"
replace() 替换子串 "hello".replace("ll", "~~") "he~~o"
replaceAll(regex, replacement) 使用正则替换 "A,,B;C ,D".replaceAll("[,;\\s]+", ",") "A,B,C,D"

1.5 字符串分割与拼接

分割:split()

Java
String s = "A,B,C,D";
String[] parts = s.split(",");  // {"A", "B", "C", "D"}

拼接:join()

Java
String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"

1.6 字符串格式化

方法 示例
formatted(...) "Hi %s".formatted("Tom")
String.format(...) String.format("Score: %.2f", 88.5)
Java
public class Main {
    public static void main(String[] args) {
        String s = "Hi %s, your score is %d!";
        System.out.println(s.formatted("Alice", 80));
        // Hi Alice, your score is 80!
        System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));
        // Hi Bob, your score is 59.50!
    }
}

1.7 字符串类型转换

转换 示例 结果
任意类型 → String String.valueOf(123) "123"
String → int Integer.parseInt("123") 123
String → int (hex) Integer.parseInt("ff", 16) 255
String → boolean Boolean.parseBoolean("TRUE") true

Integer.getInteger("key") 是读取系统属性,不用于字符串转整数。

Java
Integer.getInteger("java.version"); // 版本号,17

String 与 char[] 相互转换:

Java
char[] cs = "Hello".toCharArray();     // String → char[]
String s = new String(cs);             // char[] → String

不可变性:

Java
char[] cs = "Hello".toCharArray();
String s = new String(cs);
cs[0] = 'X';
System.out.println(s); 
// Hello

这是因为通过new String(char[])创建新的String实例时,它并不会直接引用传入的char[]数组,而是会复制一份,所以,修改外部的char[]数组不会影响String实例内部的char[]数组,因为这是两个不同的数组。


String的不变性设计可以看出,如果传入的对象有可能改变,我们需要复制而不是直接引用。

Java
import java.util.Arrays;

public class demo27 {
    public static void main(String[] args){
        int[] scores = new int[]{88,77,51,66};
        Score s = new Score(scores);
        s.printScore();
        // [88, 77, 51, 66]
        scores[2] = 99;
        s.printScore();
        // [88, 77, 99, 66]
    }
}

class Score{
    private int[] scores;
    public Score(int[] scores){
        this.scores = scores;
    }

    public void printScore(){
        System.out.println(Arrays.toString(scores));
    }
}

由于Score内部直接引用了外部传入的int[]数组,这会造成外部代码对int[]数组的修改,影响到Score类的字段。如果外部代码不可信,这就会造成安全隐患。

正确示例:

Java
class Score {
    private int[] scores;
    public Score(int[] scores) {
        this.scores = Arrays.copyOf(scores, scores.length);
    }
}

1.8 字符编码与转换

编码 用途 举例
ASCII 英文、数字、基本符号 'A' → 0x41
GB2312 中文编码 '中' → 0xd6d0
Unicode 全球统一编码 '中' → 0x4e2d
UTF-8 Unicode变长编码(1-4字节) '中' → 0xe4b8ad

英文字符的Unicode编码高字节总是00,包含大量英文的文本会浪费空间,所以,出现了UTF-8编码,它是一种变长编码,用来把固定长度的Unicode编码变成1~4字节的变长编码。通过UTF-8编码,英文字符'A'UTF-8编码变为0x41,正好和ASCII码一致,而中文'中'UTF-8编码为3字节0xe4b8ad

编码转换方法:

Java的Stringchar在内存中总是以Unicode编码表示。

Java
byte[] b1 = "Hello".getBytes("UTF-8");  // String → byte[]
String s1 = new String(b1, "UTF-8");    // byte[] → String

建议使用 StandardCharsets.UTF_8 替代字符串常量:

Java
byte[] b = "Hello".getBytes(StandardCharsets.UTF_8);
String s = new String(b, StandardCharsets.UTF_8);

2. StringBuilder

普通字符串拼接:

Java
String s = "";
for (int i = 0; i < 1000; i++) {
    s = s + "," + i;
}

由于String在Java中是不可变对象,一旦创建就不能修改,所以每次拼接都会创建一个新的String对象,大量临时对象会占用内存并增加GC负担。

使用StringBuilder拼接:

Java
public class demo28 {
    public static void main(String[] args){
        StringBuilder sb = new StringBuilder(10);   // 预分配缓冲区,提高效率
        for(int i = 0;i<10;i++){
            sb.append(i);
            sb.append(',');
        }
        String s = sb.toString();
        System.out.println(s);
        // 0,1,2,3,4,5,6,7,8,9,

        //链式操作
        // append()、insert()方法返回的是this(当前对象),从而支持链式调用
        var sb1 = new StringBuilder(10);    // var?
        sb1.append("Mr ")
          .append("Bob")
          .append("!")
          .insert(0, "Hello, ");
        System.out.println(sb1.toString()); 
        // Hello, Mr Bob!
    }
}

StringBuilderjava.lang.StringBuilder类型,是可变对象,可以预分配缓冲区

var是什么?

项目 内容
引入版本 Java 10
是否是关键字 否,是保留类型名
是否强类型语言 是,var 只是简化书写,类型在编译时确定
使用位置限制 只能用于局部变量,不能用于字段、方法参数、返回值等
是否必须初始化 是,且不能赋 null,必须能推断出类型
不能使用的情况 var a; ❌(未初始化)
var x = null; ❌(类型不明确)
可读性建议 复杂泛型或长类型名建议使用 var,简单类型可写明类型提高可读性
常见等价示例 var name = "Tom"; 等价于 String name = "Tom";
错误示例 var x = 123; x = "str"; ❌(类型冲突)

仿照StringBuilder,我们也可以设计支持链式操作的类:

Java
public class demo29 {
    public static void main(String[] args) {
        Adder adder = new Adder();
        adder.add(3)     // sum = 0 + 3 = 3
             .add(5)     // sum = 3 + 5 = 8
             .inc()      // sum = 8 + 1 = 9
             .add(10);   // sum = 9 + 10 = 19
        System.out.println(adder.value());
        // 19
    }
}

class Adder{
    private int sum = 0;

    //该方法的返回类型是Adder
    public Adder add(int n){
        sum += n;
        // 返回当前对象,返回类型是Adder,从而实现链式调用
        return this;
    }

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

    public int value() {
        return sum;
    }
}

2.1 优化的字符串+拼接

对于普通的变量拼接,并不需要改写为StringBuilder

因为Java编译器在编译时就自动把多个连续的+操作编码为StringConcatFactory,优化为更高效的字节码(通常内部使用StringBuilder或数组复制)

Java
String s = "Hello, " + "world" + "!";

2.2 StringBuilderStringBuffer

特性 StringBuilder StringBuffer
可变性
线程安全 是(使用synchronized
性能 更高 较低(同步开销)
是否推荐使用 推荐 不推荐(除非必须线程安全)
接口 完全相同 完全相同

2.3 使用StringBuilder构造INSERT语句

Java
public class demo14 {
    public static void main(String[] args){
        String[] fields = {"name","position","salary"};
        String table = "employee";
        String insert = buildInsertSql(table,fields);
        System.out.println(insert);
        // INSERT INTO employee(name,position,salary) VALUES(?,?,?)
        String s = "INSERT INTO employee(name,position,salary) VALUES(?,?,?)";
        System.out.println(s.equals(insert) ? "测试成功":"测试失败");
        // 测试成功
    }

    static String buildInsertSql(String table,String[] fields){
        var sb = new StringBuilder(1024);
        sb.append("INSERT INTO ").append(table).append("(");
        for(int i = 0;i<fields.length;i++){
            sb.append(fields[i]);
            if(i != fields.length-1){
                sb.append(",");
            }
        }
        sb.append(") VALUES(");
         for(int i = 0;i<fields.length;i++){
            sb.append("?");
             if(i != fields.length-1){
                sb.append(",");
            }
         }
         sb.append(")");

         return sb.toString();
    }
}

3. StringJoiner

在 Java 中,频繁拼接字符串时如果使用 + 运算符,会因产生大量中间字符串而导致性能低下。为了提高效率,可以使用如下工具:

方法 描述 示例
StringBuilder 可变字符串容器,适合频繁拼接 sb.append(...)
StringJoiner 自动添加分隔符、可指定前缀/后缀 new StringJoiner(", ", "前缀", "后缀")
new StringJoiner("分隔符")
String.join() 静态方法,内部使用StringJoiner实现 String.join(", ", arr)
Java
import java.util.StringJoiner;

public class demo15 {
    public static void main(String[] args){
        String[] names = {"Bob","Alice","Grace"};

        // 用StringBuilder来打印:
        var sb = new StringBuilder();
        sb.append("Hello ");
        for (String name : names) {
            sb.append(name).append(", ");
        }
        // sb.delete(start, end) 会删除[start,end)之间的字符,start和end代表索引
        sb.delete(sb.length() - 2, sb.length());
        sb.append("!");
        System.out.println(sb.toString());
        // Hello Bob, Alice, Grace!

        // 用StringJoiner来打印:
        // new StringJoiner(分隔符,前缀,后缀)
        var sj = new StringJoiner(", ","Hello ","!");
        for(String name:names){
            sj.add(name);
        }
        System.out.println(sj.toString());
        // Hello Bob, Alice, Grace!

        // 用String.join()来打印:
        var result = String.join(", ", names);
        System.out.println(result);
        // Bob, Alice, Grace
    }
}

3.1 使用StringJoiner构造SELECT语句

Java
import java.util.StringJoiner;

public class demo16 {
    public static void main(String[] args){
        String[] fields = {"name","position","salary"};
        String table = "employee";
        String select = buildSelectSql(table,fields);
        System.out.println(select);
        // SELECT name, position, salary FROM employee
        System.out.println("SELECT name, position, salary FROM employee".equals(select) ? "测试成功" : "测试失败");
        // 测试成功
    }

    static String buildSelectSql(String table,String[] fields){
        var sj = new StringJoiner(", ","SELECT "," FROM " + table);
        for(String field:fields){
            sj.add(field);
        }

        return sj.toString();
    }
}

4. 包装类型

Java的数据类型分两种:

类别 类型说明
基本类型 byte,short,int,long,float,double,boolean,char
引用类型 所有classinterface类型

基本类型不能为 null,但引用类型可以

Java
String s = null; // OK
int n = null;    // 编译错误!

可以定义一个类,把一个基本类型视为对象(引用类型):

Java
public class Integer {
    private int value;

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

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

Integer对象只包含一个实例字段int,可以视为是int包装类(Wrapper Class)

实际上Java核心库为每个基本类型提供了对应的包装类,使其可以作为对象使用

基本类型 包装类型 所属包
boolean Boolean java.lang
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character

可以直接使用,并不需要自己去定义:

Java
int i = 100;

// 通过new操作符创建Integer实例(不推荐使用,会有编译警告):
Integer n1 = new Integer(i);

// 通过静态方法valueOf()创建Integer实例:
Integer n2 = Integer.valueOf(i);     
Integer n3 = Integer.valueOf("100");

System.out.println(n3.intValue());

Integer.valueOf()是创建包装类对象的一个静态工厂方法, 它尽可能地返回缓存的实例以节省内存

4.1 自动装箱与自动拆箱(Auto Boxing / Unboxing)

intInteger可以互相转换:

Java
int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();

所以,Java编译器可以帮助我们自动在intInteger之间转型,提高效率

操作 示例代码 编译器处理方式
自动装箱 Integer n = 100; 编译器转换为Integer.valueOf(100)
自动拆箱 int x = n; 编译器转换为n.intValue()

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

Java
Integer n = null;
int i = n; // 抛出 NullPointerException

4.2 不变类

包装类如 Integer 是不可变的,它的核心代码如下:

Java
public final class Integer {
    private final int value;
}

不要使用 == 比较包装类对象,应使用 equals()

Java
Integer x = 127;
Integer y = 127;
System.out.println(x == y);       // true (缓存优化)
System.out.println(x.equals(y));  // true

Integer m = 99999;
Integer n = 99999;
System.out.println(m == n);       // false
System.out.println(m.equals(n));  // true

因为Integer是不变类,编译器把Integer x = 127;自动变为Integer x = Integer.valueOf(127)

但它并不是每次都创建一个新的Integer对象,而是会在一定范围内复用已有的对象实例,也就是会使用缓存

4.3 缓存机制(Cache):

Java 标准库中 Integer.valueOf(int) 的实现逻辑大致如下:

Java
public static Integer valueOf(int i) {
    if (i >= -128 && i <= 127) {
        return IntegerCache.cache[i + 128];
    }
    return new Integer(i);
}

-128127 范围内的整数会被缓存在一个叫 IntegerCache 的数组中

这些值的 Integer 实例会提前创建并缓存

每次调用 valueOf() 都返回这个缓存数组中的实例

超过这个范围的值valueOf() 每次都会新建一个 Integer 对象的实例

缓存机制能够节省内存,提高性能,避免重复创建大量相同的 Integer 对象,减少垃圾回收压力,提升运行效率

== 比较的是对象的地址,不是值

Java
Integer x = 127;
Integer y = 127;
System.out.println(x == y); // true 因为使用缓存,x和y指向同一个对象(实例)
Java
Integer m = 128;
Integer n = 128;
System.out.println(m == n); // false 因为超出缓存范围,m和n是Integer对象的两个不同的实例

4.4 常用静态方法

  1. 字符串与整数互转
Java
int a = Integer.parseInt("100");
int b = Integer.parseInt("100", 16); // 256(按16进制解析)
  1. 整数转不同进制字符串
Java
Integer.toString(100);        // "100"

Integer.toString(100, 36);    // "2s",36进制

Integer.toHexString(100);     // "64",16进制

Integer.toOctalString(100);   // "144",8进制

Integer.toBinaryString(100);  // "1100100",2进制

4.5 常用静态字段

Java
// boolean只有两个值true/false,其包装类型只需要引用Boolean提供的静态字段:
Boolean t = Boolean.TRUE;
Boolean f = Boolean.FALSE;

// int可表示的最大/最小值:
int max = Integer.MAX_VALUE; // 2147483647
int min = Integer.MIN_VALUE; // -2147483648

// long类型占用的bit和byte数量:
int sizeOfLong = Long.SIZE; // 64 (bits)
int bytesOfLong = Long.BYTES; // 8 (bytes)

4.6 Number类

包装类 Integer, Float, Double 等都继承自 Number类,因此,可以非常方便地直接通过包装类型获取各种基本类型

Java
// 向上转型为Number:
Number num = new Integer(999);

// 获取byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();

4.7 处理无符号整型

在Java中,并没有无符号整型(Unsigned)的基本数据类型。byteshortintlong都是带符号整型,最高位是符号位

例如,byte是有符号整型,范围是-128~+127但如果把byte看作无符号整型,它的范围就是0 ~255

把负的byte按无符号整型转换为int

Java
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
    }
}

因为byte-1的二进制表示是11111111,以无符号整型转换后的int就是255

类型 转换目标 方法
byte int无符号 Byte.toUnsignedInt(byte)
short int无符号 Short.toUnsignedInt(short)
int long无符号 Integer.toUnsignedLong(int)

5. JavaBean

JavaBean是一种符合特定命名规范的 Java 类,主要用于数据封装和传输。

JavaBean 的特征:

  • 类必须是 public
  • 提供一个无参构造方法。
  • 所有字段为 private
  • 提供 gettersetter 方法来访问字段。

JavaBean 属性命名规范:

类型 命名格式 示例方法名
普通字段属性 getXxx()/setXxx(Type value) getName(),setName(...)
布尔类型属性 isXxx()/setXxx(boolean value) isChild(),setChild(...)

属性(Property)说明:

类型 特征 示例方法
读写属性 同时有 getter 和 setter getName(),setName(...)
只读属性 只有 getter getAge()
只写属性 只有 setter setPassword(...)(较少见)

标准的JavaBean:

Java
public 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; }

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

JavaBean 的作用:

用途 描述
数据封装 将一组数据组合成一个对象传输
IDE工具支持 可视化工具可以自动识别 getter/setter 并快速生成
属性反射处理 使用Introspector可反射获取属性信息,用于框架/工具自动配置

使用 Introspector枚举 JavaBean 的属性:

Java
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;

public class demo17 {
    public static void main(String[] args) throws Exception {
         BeanInfo info = Introspector.getBeanInfo(Person.class);
        for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
            System.out.println("属性: " + pd.getName());
            // 属性: age
            // 读方法: public int Exp03核心类.Person.getAge()
            // 写方法: public void Exp03核心类.Person.setAge(int)

            System.out.println("  读方法: " + pd.getReadMethod());
            // 属性: class
            // 读方法: public final native java.lang.Class java.lang.Object.getClass()
            // 写方法: null

            System.out.println("  写方法: " + pd.getWriteMethod());
            // 属性: name
            // 读方法: public java.lang.String Exp03核心类.Person.getName()
            // 写方法: public void Exp03核心类.Person.setName(java.lang.String)
        }
    }
}

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;
    }
}

简介 - Java教程 - 廖雪峰的官方网站