适用于有其他编程语言基础的Java小白零基础教程(五)

QcrTiMo 发布于 9 天前 16 次阅读


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

输出结果都写在了注释中

字符串和编码

基本特性

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

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

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

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​ 并未被修改,而是返回了新字符串

字符串比较

比较方式描述示例使用
==比较引用是否相同s1 == s2
equals()比较内容是否相同s1.equals(s2)
equalsIgnoreCase()比较内容忽略大小写s1.equalsIgnoreCase(s2)
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编译器在编译期,会自动把所有相同的字符串当作一个对象放入常量池,自然s1​和s3​的引用就是相同的,所以,这种==​比较返回true​纯属巧合。

常用字符串方法

方法作用示例返回值
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空白"u3000Hellou3000".strip()"Hello"
isEmpty()是否为空字符串"".isEmpty()true
isEmpty()是否为空字符串" ".isEmpty()false
isBlank()是否全为空白字符" n".isBlank()true

字符串替换

方法描述示例结果
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"

字符串分割与拼接

分割:split()

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

拼接:join()

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

字符串格式化

方法示例
formatted(...)"Hi %s".formatted("Tom")
String.format(...)String.format("Score: %.2f", 88.5)
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!
    }
}

字符串类型转换

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

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

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

String 与 char[] 相互转换:

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

不可变性:

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​的不变性设计可以看出,如果传入的对象有可能改变,我们需要复制而不是直接引用。

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​类的字段。如果外部代码不可信,这就会造成安全隐患。

正确示例:

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

字符编码与转换

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

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

编码转换方法:

Java的String​和char​在内存中总是以Unicode​编码表示。

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

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

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

StringBuilder

普通字符串拼接:

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

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

使用StringBuilder拼接:

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

StringBuilder​是java.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​,我们也可以设计支持链式操作的类:

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

优化的字符串+​拼接

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

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

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

StringBuilder​ 和 StringBuffer

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

使用StringBuilder​构造INSERT​语句

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

StringJoiner

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

方法描述示例
StringBuilder可变字符串容器,适合频繁拼接sb.append(...)
StringJoiner自动添加分隔符、可指定前缀/后缀new StringJoiner(", ", "前缀", "后缀")
new StringJoiner("分隔符")
String.join()静态方法,内部使用StringJoiner​实现String.join(", ", arr)
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
    }
}

使用StringJoiner​构造SELECT​语句

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

包装类型

Java的数据类型分两种:

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

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

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

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

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核心库为每个基本类型提供了对应的包装类,使其可以作为对象使用

基本类型包装类型所属包
booleanBooleanjava.lang
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter

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

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()​是创建包装类对象的一个静态工厂方法, 它尽可能地返回缓存的实例以节省内存

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

int​和Integer​可以互相转换:

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

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

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

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

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

不变类

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

public final class Integer {
    private final int value;
}

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

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​对象,而是会在一定范围内复用已有的对象实例,也就是会使用缓存

缓存机制(Cache):

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

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

-128​ 到 127​ 范围内的整数会被缓存在一个叫 IntegerCache​ 的数组中

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

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

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

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

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

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

常用静态方法

  1. 字符串与整数互转
int a = Integer.parseInt("100");
int b = Integer.parseInt("100", 16); // 256(按16进制解析)
  1. 整数转不同进制字符串
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进制

常用静态字段

// 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)

Number类

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

// 向上转型为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();

处理无符号整型

在Java中,并没有无符号整型(Unsigned)的基本数据类型。byte​、short​、int​和long​都是带符号整型,最高位是符号位

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

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

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

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

JavaBean

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

JavaBean 的特征:

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

JavaBean 属性命名规范:

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

属性(Property)说明:

类型特征示例方法
读写属性同时有 getter 和 settergetName()​,setName(...)
只读属性只有 gettergetAge()
只写属性只有 settersetPassword(...)​(较少见)

标准的JavaBean:

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 的属性:

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教程 - 廖雪峰的官方网站

斯哈斯哈斯哈,佳代子啊啊啊啊啊啊ᕕ(◠ڼ◠)ᕗᕕ(◠ڼ◠)ᕗᕕ(◠ڼ◠)ᕗᕕ(◠ڼ◠)ᕗᕕ(◠ڼ◠)ᕗ
最后更新于 2025-05-19