共计 6054 个字符,预计需要花费 16 分钟才能阅读完成。
Java 外围类
字符串和编码
String
在 Java 中,String
是一个援用类型,它自身也是一个 class
。然而,Java 编译器对String
有非凡解决,即能够间接用 "..."
来示意一个字符串:
String s1 = "Hello!";
实际上字符串在 String
外部是通过一个 char[]
数组示意的,因而,按上面的写法也是能够的:
String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'});
因为 String
太罕用了,所以 Java 提供了 "..."
这种字符串字面量示意办法。
Java 字符串的一个重要特点就是字符串_不可变_。这种不可变性是通过外部的 private final char[]
字段,以及没有任何批改 char[]
的办法实现的。
字符串比拟
当咱们想要比拟两个字符串是否雷同时,要特地留神,咱们实际上是想比拟字符串的内容是否雷同。必须应用 equals()
办法而不能用 ==
。
从外表上看,两个字符串用 ==
和equals()
比拟都为 true
,但实际上那只是 Java 编译器在编译期,会主动把所有雷同的字符串当作一个对象放入常量池,天然s1
和s2
的援用就是雷同的。
所以,这种 ==
比拟返回 true
纯属巧合。换一种写法,==
比拟就会失败
论断:两个字符串比拟,必须总是应用 equals()
办法。
要疏忽大小写比拟,应用 equalsIgnoreCase()
办法。
String
类还提供了多种办法来搜寻子串、提取子串。罕用的办法有:
// 是否蕴含子串:
"Hello".contains("ll"); // true
留神到 contains()
办法的参数是 CharSequence
而不是 String
,因为CharSequence
是String
的父类。
搜寻子串的更多的例子:
"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true
提取子串的例子:
"Hello".substring(2); // "llo"
"Hello".substring(2, 4); "ll"
留神索引号是从 0
开始的。
去除首尾空白字符
应用 trim()
办法能够移除字符串首尾空白字符。空白字符包含空格,\t
,\r
,\n
:
"\tHello\r\n".trim(); // "Hello"
留神:trim()
并没有扭转字符串的内容,而是返回了一个新字符串。
另一个 strip()
办法也能够移除字符串首尾空白字符。它和 trim()
不同的是,相似中文的空格字符 \u3000
也会被移除。String
还提供了 isEmpty()
和isBlank()
来判断字符串是否为空和空白字符串:
"".isEmpty(); // true,因为字符串长度为 0" ".isEmpty(); // false,因为字符串长度不为 0" \n".isBlank(); // true,因为只蕴含空白字符" Hello ".isBlank(); // false,因为蕴含非空白字符
替换子串
要在字符串中替换子串,有两种办法。一种是依据字符或字符串替换:
String s = "hello";
s.replace('l', 'w'); // "hewwo",所有字符 'l' 被替换为 'w'
s.replace("ll", "~~"); // "he~~o",所有子串 "ll" 被替换为 "~~"
另一种是通过正则表达式替换:
String s = "A,,B;C ,D";
s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"
下面的代码通过正则表达式,把匹配的子串对立替换为","
。对于正则表达式的用法咱们会在前面具体解说。
宰割字符串
要宰割字符串,应用 split()
办法,并且传入的也是正则表达式:
String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}
拼接字符串
拼接字符串应用静态方法join()
,它用指定的字符串连贯字符串数组:
String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"
格式化字符串
字符串提供了 formatted()
办法和 format()
静态方法,能够传入其余参数,替换占位符,而后生成新的字符串
String s = "Hi %s, your score is %d!";
System.out.println(s.formatted("Alice", 80));
System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));
类型转换
要把任意根本类型或援用类型转换为字符串,能够应用静态方法valueOf()
。这是一个重载办法,编译器会依据参数主动抉择适合的办法:
String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 相似 java.lang.Object@636be97c
要把字符串转换为其余类型,就须要依据状况。例如,把字符串转换为 int
类型:
int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // 按十六进制转换,255
把字符串转换为 boolean
类型:
boolean b1 = Boolean.parseBoolean("true"); // true
boolean b2 = Boolean.parseBoolean("FALSE"); // false
要特地留神,Integer
有个 getInteger(String)
办法,它不是将字符串转换为int
,而是把该字符串对应的零碎变量转换为Integer
:
Integer.getInteger("java.version"); // 版本号,11
转换为 char[]
String
和 char[]
类型能够相互转换,办法是:
char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String
字符编码
在晚期的计算机系统中,为了给字符编码,美国国家标准学会制订了一套英文字母、数字和罕用符号的编码,它占用一个字节,编码范畴从 0
到127
,最高位始终为 0
,称为ASCII
编码。例如,字符 'A'
的编码是 0x41
,字符'1'
的编码是0x31
。
如果要把汉字也纳入计算机编码,很显然一个字节是不够的。GB2312
规范应用两个字节示意一个汉字,其中第一个字节的最高位始终为 1
,以便和ASCII
编码辨别开。例如,汉字 '中'
的GB2312
编码是0xd6d0
。
相似的,日文有 Shift_JIS
编码,韩文有 EUC-KR
编码,这些编码因为规范不对立,同时应用,就会产生抵触。
为了对立寰球所有语言的编码,寰球对立码联盟公布了 Unicode
编码,它把世界上次要语言都纳入同一个编码,这样,中文、日文、韩文和其余语言就不会抵触。
StringBuilder
Java 编译器对 String
做了非凡解决,使得咱们能够间接用 +
拼接字符串。
考查上面的循环代码:
String s = "";
for (int i = 0; i < 1000; i++) {s = s + "," + i;}
尽管能够间接拼接字符串,然而,在循环中,每次循环都会创立新的字符串对象,而后扔掉旧的字符串。这样,绝大部分字符串都是长期对象,岂但节约内存,还会影响 GC 效率。
为了能高效拼接字符串,Java 规范库提供了 StringBuilder
,它是一个可变对象,能够预调配缓冲区,这样,往StringBuilder
中新增字符时,不会创立新的长期对象:
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {sb.append(',');
sb.append(i);
}
String s = sb.toString();
StringBuilder
还能够进行链式操作:
var sb = new StringBuilder(1024);
sb.append("Mr")
.append("Bob")
.append("!")
.insert(0, "Hello,");
System.out.println(sb.toString());
如果咱们查看 StringBuilder
的源码,能够发现,进行链式操作的要害是,定义的 append()
办法会返回this
,这样,就能够一直调用本身的其余办法。
仿照StringBuilder
,咱们也能够设计反对链式操作的类。例如,一个能够一直减少的计数器:
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
,因为 Java 编译器在编译时就主动把多个间断的+
操作编码为 StringConcatFactory
的操作。在运行期,StringConcatFactory
会主动把字符串连贯操作优化为数组复制或者 StringBuilder
操作。
你可能还据说过 StringBuffer
,这是 Java 晚期的一个StringBuilder
的线程平安版本,它通过同步来保障多个线程操作 StringBuffer
也是平安的,然而同步会带来执行速度的降落。
StringBuilder
和 StringBuffer
接口完全相同,当初齐全没有必要应用StringBuffer
。
StringJoiner
相似用分隔符拼接数组的需要很常见,所以 Java 规范库还提供了一个 StringJoiner
来干这个事:
String[] names = {"Bob", "Alice", "Grace"};
var sj = new StringJoiner(",");
for (String name : names) {sj.add(name);
}
System.out.println(sj.toString());
包装类型
咱们曾经晓得,Java 的数据类型分两种:
- 根本类型:
byte
,short
,int
,long
,boolean
,float
,double
,char
- 援用类型:所有
class
和interface
类型
援用类型能够赋值为null
,示意空,但根本类型不能赋值为null
:
String s = null;
int n = null; // compile error!
那么,如何把一个根本类型视为对象(援用类型)?
比方,想要把 int
根本类型变成一个援用类型,咱们能够定义一个 Integer
类,它只蕴含一个实例字段 int
,这样,Integer
类就能够视为 int
的包装类(Wrapper Class):
public class Integer {
private int value;
public Integer(int value) {this.value = value;}
public int intValue() {return this.value;}
}
定义好了 Integer
类,咱们就能够把 int
和Integer
相互转换:
Integer n = null;
Integer n2 = new Integer(99);
int n3 = n2.intValue();
实际上,因为包装类型十分有用,Java 外围库为每种根本类型都提供了对应的包装类型:
根本类型
对应的援用类型
boolean — java.lang.Boolean
byte — java.lang.Byte
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
因为 int
和Integer
能够相互转换:
int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();
所以,Java 编译器能够帮忙咱们主动在 int
和Integer
之间转型:
Integer n = 100; // 编译器主动应用 Integer.valueOf(int)
int x = n; // 编译器主动应用 Integer.intValue()
这种间接把 int
变为 Integer
的赋值写法,称为主动装箱(Auto Boxing),反过来,把 Integer
变为 int
的赋值写法,称为主动拆箱(Auto Unboxing)。
留神:主动装箱和主动拆箱只产生在编译阶段,目标是为了少写代码。
装箱和拆箱会影响代码的执行效率,因为编译后的 class
代码是严格辨别根本类型和援用类型的。并且,主动拆箱执行时可能会报NullPointerException
不变类
所有的包装类型都是不变类。咱们查看 Integer
的源码可知,它的外围代码如下:
public final class Integer {private final int value;}
因而,一旦创立了 Integer
对象,该对象就是不变的。
对两个 Integer
实例进行比拟要特地留神:相对不能用 ==
比拟,因为 Integer
是援用类型,必须应用 equals()
比拟
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