Java_核心类
Java核心类
1.字符串和编码
字符串比较
当我们想要比较两个字符串是否相同时,要特别注意,我们实际上是想比较字符串的内容是否相同。必须使用equals()
方法而不能用==
要忽略大小写比较,使用equalsIgnoreCase()
方法
去除首尾空白字符
使用trim()
方法可以移除字符串首尾空白字符。空白字符包括空格,\t
,\r
,\n
;另一个strip()
方法也可以移除字符串首尾空白字符。它和trim()
不同的是,类似中文的空格字符\u3000
也会被移除
替换子串
要在字符串中替换子串,有两种方法。一种是根据字符或字符串替换,使用replace方法;
另一种是通过正则表达式替换:
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()
静态方法,可以传入其他参数,替换占位符,然后生成新的字符串:
public class Main {
public static void main(String[] args) {
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()
。要把字符串转换为其他类型,就需要根据情况使用诸如parseInt,parseBoolean
等一系列方法,也可以通过toCahrArray
转换为字符数组。
字节编码
在Java中,char
类型实际上就是两个字节的Unicode
编码。如果我们要手动把字符串转换成其他编码,可以这样做:
byte[] b1 = "Hello".getBytes(); // 按系统默认编码转换,不推荐
byte[] b2 = "Hello".getBytes("UTF-8"); // 按UTF-8编码转换
byte[] b2 = "Hello".getBytes("GBK"); // 按GBK编码转换
byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8); // 按UTF-8编码转换
注意:转换编码后,就不再是char
类型,而是byte
类型表示的数组。
如果要把已知编码的byte[]
转换为String
,可以这样做:
byte[] b = ...
String s1 = new String(b, "GBK"); // 按GBK转换
String s2 = new String(b, StandardCharsets.UTF_8); // 按UTF-8转换
始终牢记:Java的String
和char
在内存中总是以Unicode编码表示。
2.StringBuilder
为了能高效拼接字符串,Java标准库提供了StringBuilder
,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder
中新增字符时,不会创建新的临时对象。
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
sb.append(',');
sb.append(i);
}
String s = sb.toString();
3.StringBuilder
使用StringBuilder按照指定分隔符拼接字符串
String[] names = {"Bob", "Alice", "Grace"};
var sj = new StringJoiner(", ", "Hello ", "!");
for (String name : names) {
sj.add(name);
}
System.out.println(sj.toString());
String.join()
String
还提供了一个静态方法join()
,这个方法在内部使用了StringJoiner
来拼接字符串,在不需要指定“开头”和“结尾”的时候,用String.join()
更方便:
String[] names = {"Bob", "Alice", "Grace"};
var s = String.join(", ", names);
4.包装类型
Java的数据类型分两种:
- 基本类型:
byte
,short
,int
,long
,boolean
,float
,double
,char
- 引用类型:所有
class
和interface
类型
如果想要把int
基本类型变成一个引用类型,我们可以定义一个Integer
类,它只包含一个实例字段int
,这样,Integer
类就可以视为int
的包装类(Wrapper Class)。
实际上,因为包装类型非常有用,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
的源码可知,它的核心代码如下:
public final class Integer {
private final int value;
}
因此,一旦创建了Integer
对象,该对象就是不变的。
对两个Integer
实例进行比较要特别注意:绝对不能用==
比较,因为Integer
是引用类型,必须使用equals()
比较
因为Integer.valueOf()
可能始终返回同一个Integer
实例,因此,在我们自己创建Integer
的时候,以下两种方法:
- 方法1:
Integer n = new Integer(100);
- 方法2:
Integer n = Integer.valueOf(100);
方法2更好,因为方法1总是创建新的Integer
实例,方法2把内部优化留给Integer
的实现者去做,即使在当前版本没有优化,也有可能在下一个版本进行优化。
我们把能创建“新”对象的静态方法称为静态工厂方法。Integer.valueOf()
就是静态工厂方法,它尽可能地返回缓存的实例以节省内存。
创建新对象时,优先选用静态工厂方法而不是new操作符。
5.JavaBean
在Java中,有很多class
的定义都符合这样的规范:
- 若干
private
实例字段; - 通过
public
方法来读写实例字段。
如果读写方法符合以下这种命名规范:
// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)
那么这种class
被称为JavaBean
,而boolean
字段比较特殊,它的读方法一般命名为isXyz()
JavaBean的作用
JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。此外,JavaBean可以方便地被IDE工具分析,生成读写属性的代码,主要用在图形界面的可视化设计中。
枚举JavaBean属性
要枚举一个JavaBean的所有属性,可以直接使用Java核心库提供的Introspector
BeanInfo info = Introspector.getBeanInfo(Person.class);
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
System.out.println(pd.getName());
System.out.println(" " + pd.getReadMethod());
System.out.println(" " + pd.getWriteMethod());
}
5.枚举
在Java中,我们可以通过static final
来定义常量。无论是int
常量还是String
常量,使用这些常量来表示一组枚举值的时候,有一个严重的问题就是,编译器无法检查每个值的合理性。
enum
为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum
来定义枚举类
enum
常量本身带有类型信息,即Weekday.SUN
类型是Weekday
,编译器会自动检查出类型错误。- 不可能引用到非枚举的值,因为无法通过编译。
- 不同类型的枚举不能互相比较或者赋值,因为类型不符。
enum的比较
使用enum
定义的枚举类是一种引用类型。前面我们讲到,引用类型比较,要使用equals()
方法,如果使用==
比较,它比较的是两个引用类型的变量是否是同一个对象。因此,引用类型比较,要始终使用equals()
方法,但enum
类型可以例外。
这是因为enum
类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==
比较。
-
通过
name()
获取常量定义的字符串,注意不要使用toString()
-
通过
ordinal()
返回常量定义的顺序(无实质意义)
6.记录类
使用String
、Integer
等类型的时候,这些类型都是不变类,一个不变类具有以下特点:
- 定义class时使用
final
,无法派生子类; - 每个字段使用
final
,保证创建实例后无法修改任何字段。
从Java 14开始,提供新的record
关键字,可以非常方便地定义Data Class:
- 使用
record
定义的是不变类; - 可以编写Compact Constructor对参数进行验证;
- 可以定义静态方法。
构造方法
编译器默认按照record
声明的变量顺序自动创建一个构造方法,并在方法内给字段赋值。那么问题来了,如果我们要检查参数,应该怎么办?
假设Point
类的x
、y
不允许负数,我们就得给Point
的构造方法加上检查逻辑:
public record Point(int x, int y) {
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException();
}
}
}
7.BigInteger
对BigInteger
做运算的时候,只能使用实例方法,例如,加法运算:
BigInteger i1 = new BigInteger("1234567890");
BigInteger i2 = new BigInteger("12345678901234567890");
BigInteger sum = i1.add(i2); // 12345678902469135780
和long
型整数运算比,BigInteger
不会有范围限制,但缺点是速度比较慢。
通过形如doubleValue()
方法,可以把BigInteger
转换成基本类型。如果BigInteger
表示的范围超过了基本类型的范围,转换时将丢失高位信息,即结果不一定是准确的。如果需要准确地转换成基本类型,可以使用intValueExact()
、longValueExact()
等方法,在转换时如果超出范围,将直接抛出ArithmeticException
异常。
8.BigDecimal
BigDecimal
用scale()
表示小数位数,如果一个BigDecimal
的scale()
返回负数,例如,-2
,表示这个数是个整数,并且末尾有2个0。可以对一个BigDecimal
设置它的scale
,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断
通过BigDecimal
的stripTrailingZeros()
方法,可以将一个BigDecimal
格式化为一个相等的,但去掉了末尾0的BigDecimal
比较BigDecimal
在比较两个BigDecimal
的值是否相等时,要特别注意,使用equals()
方法不但要求两个BigDecimal
的值相等,还要求它们的scale()
相等:
必须使用compareTo()
方法来比较,它根据两个值的大小分别返回负数、正数和0
,分别表示小于、大于和等于。
9.常用工具
Java提供的常用工具类有:
- Math:数学计算
- Random:生成伪随机数
- SecureRandom:生成安全的随机数