-
字符串和编码
-
字符串在
String
内部是通过一个char[]
数组表示的,因此,可以按下面的写法:String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'});
-
Java字符串的一个重要特点就是字符串不可变。这种不可变性是通过内部的
private final char[]
字段,以及没有任何修改char[]
的方法实现的。 - Java编译器在编译期,会自动把所有相同的字符串当作一个对象放入常量池。
-
两个字符串比较,必须总是使用
equals()
方法。 -
要忽略大小写比较,使用
equalsIgnoreCase()
方法。 -
String
还提供了isEmpty()
和isBlank()
来判断字符串是否为空和空白字符串。 -
拼接字符串使用静态方法
join()
,它用指定的字符串连接字符串数组。String[] arr = {"A", "B", "C"}; String s = String.join("***", arr); // "A***B***C"
-
字符串提供了
formatted()
方法和format()
静态方法,可以传入其他参数,替换占位符,然后生成新的字符串。1 public class Main { 2 public static void main(String[] args) { 3 String s = "Hi %s, your score is %d!"; 4 System.out.println(s.formatted("Alice", 80)); 5 System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5)); 6 } 7 }
-
要把任意基本类型或引用类型转换为字符串,可以使用静态方法
valueOf()
。1 String.valueOf(123); // "123" 2 String.valueOf(45.67); // "45.67" 3 String.valueOf(true); // "true" 4 String.valueOf(new Object()); // 类似java.lang.Object@636be97c
-
new String(char[])
创建新的String
实例时,它并不会直接引用传入的char[]
数组,而是会复制一份,所以,修改外部的char[]
数组不会影响String
实例内部的char[]
数组,因为这是两个不同的数组。 -
那我们经常使用的
UTF-8
又是什么编码呢?因为英文字符的Unicode
编码高字节总是00
,包含大量英文的文本会浪费空间,所以,出现了UTF-8
编码,它是一种变长编码,用来把固定长度的Unicode
编码变成1~4字节的变长编码。通过UTF-8
编码,英文字符'A'
的UTF-8
编码变为0x41
,正好和ASCII
码一致,而中文'中'
的UTF-8
编码为3字节0xe4b8ad
。
-
字符串在
-
StringBuilder
- String虽然可以直接拼接字符串,但是,在循环中,每次循环都会创建新的字符串对象,然后扔掉旧的字符串。
-
为了能高效拼接字符串,Java标准库提供了
StringBuilder
,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder
中新增字符时,不会创建新的临时对象。 -
StringBuilder
还可以进行链式操作。1 public class Main { 2 public static void main(String[] args) { 3 var sb = new StringBuilder(1024); 4 sb.append("Mr ") 5 .append("Bob") 6 .append("!") 7 .insert(0, "Hello, "); 8 System.out.println(sb.toString()); 9 } 10 }
-
查看
StringBuilder
的源码,可以发现,进行链式操作的关键是,定义的append()
方法会返回this
,这样,就可以不断调用自身的其他方法。 -
对于普通的字符串
+
操作,并不需要我们将其改写为StringBuilder
,因为Java编译器在编译时就自动把多个连续的+
操作编码为StringConcatFactory
的操作。在运行期,StringConcatFactory
会自动把字符串连接操作优化为数组复制或者StringBuilder
操作。 -
public StringBuilder delete(int start, int end)
-
StringJoiner
- StringJoiner(CharSequence delimiter)
- StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
-
包装类型
- 自动装箱(Auto Boxing)
-
所有的包装类型都是不变类。
1 //源码 2 public final class Integer { 3 private final int value; 4 }
-
我们把能创建“新”对象的静态方法称为静态工厂方法。
Integer.valueOf()
就是静态工厂方法,它尽可能地返回缓存的实例以节省内存。 - 处理无符号整型
-
JavaBean
-
class
的定义都符合这样的规范:-
若干
private
实例字段; -
通过
public
方法来读写实例字段。// 读方法: public Type getXyz() // 写方法: public void setXyz(Type value)
-
boolean
字段比较特殊,它的读方法一般命名为isXyz()。
// 读方法: public boolean isChild() // 写方法: public void setChild(boolean value)
-
把一组对应的读方法(
getter
)和写方法(setter
)称为属性(property
)。例如,name
属性:-
对应的读方法是
String getName()
-
对应的写方法是
setName(String)
-
只有
getter
的属性称为只读属性(read-only) -
只有
setter
的属性称为只写属性(write-only枚举一个JavaBean的所有属性,可以直接使用Java核心库提供的Introspector。
-
对应的读方法是
-
1 import java.beans.*; 2 3 public class Main { 4 public static void main(String[] args) throws Exception { 5 BeanInfo info = Introspector.getBeanInfo(Person.class); 6 for (PropertyDescriptor pd : info.getPropertyDescriptors()) { 7 System.out.println(pd.getName()); 8 System.out.println(" " + pd.getReadMethod()); 9 System.out.println(" " + pd.getWriteMethod()); 10 } 11 } 12 } 13 14 class Person { 15 private String name; 16 private int age; 17 18 public String getName() { 19 return name; 20 } 21 22 public void setName(String name) { 23 this.name = name; 24 } 25 26 public int getAge() { 27 return age; 28 } 29 30 public void setAge(int age) { 31 this.age = age; 32 } 33 } 输出: age public int Person.getAge() public void Person.setAge(int) class public final native java.lang.Class java.lang.Object.getClass() null name public java.lang.String Person.getName() public void Person.setName(java.lang.String)
-
若干
-
-
枚举类
-
Java中,我们可以通过
static final
来定义常量。但使用这些常量来表示一组枚举值的时候,有一个严重的问题就是,编译器无法检查每个值的合理性。定义的常量仍可与其他变量比较。1 class Weekday { 2 public static final int SUN = 0; 3 public static final int MON = 1; 4 public static final int TUE = 2; 5 public static final int WED = 3; 6 public static final int THU = 4; 7 public static final int FRI = 5; 8 public static final int SAT = 6; 9 } 10 11 //可编译下列代码 12 if (weekday == 6 || weekday == 7) { //
Weekday
定义的常量范围是0
~6
,并不包含7
,编译器无法检查不在枚举中的int
值; 13 if (tasks == Weekday.MON) { 14 // TODO: 15 } 16 } -
定义枚举类是通过关键字
enum
实现的,只需依次列出枚举的常量名。enum
常量本身带有类型信息,即Weekday.SUN
类型是Weekday(第二代码块)
,编译器会自动检查出类型错误。1 public class Main { 2 public static void main(String[] args) { 3 Weekday day = Weekday.SUN; 4 if (day == Weekday.SAT || day == Weekday.SUN) { 5 System.out.println("Work at home!"); 6 } else { 7 System.out.println("Work at office!"); 8 } 9 } 10 } 11 12 enum Weekday { 13 SUN, MON, TUE, WED, THU, FRI, SAT; 14 }
1 public class Main { 2 public static void main(String[] args) { 3 Weekday day = Weekday.SUN; 4 System.out.println(day.getClass()); 5 } 6 } 7 8 enum Weekday { 9 SUN, MON, TUE, WED, THU, FRI, SAT; 10 }
-
使用
enum
定义的枚举类是一种引用类型。比较要使用equals()
方法,但enum
类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==
比较。 -
enum
定义的类型就是class
,有以下几个特点:-
定义的
enum
类型总是继承自java.lang.Enum
,且无法被继承; -
只能定义出
enum
的实例,而无法通过new
操作符创建enum
的实例; - 定义的每个实例都是引用类型的唯一实例;
-
可以将
enum
类型用于switch
语句。
-
定义的
-
enum
是一个class
,每个枚举的值都是class
实例,因此,这些实例有一些方法:-
name() 返回常量名
String s = Weekday.SUN.name(); // "SUN"
-
ordinal() 返回定义的常量的顺序,从0开始计数
int n = Weekday.MON.ordinal(); // 1
- values() 返回枚举类中所有的值。
-
枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。
enum
的构造方法要声明为private
,字段强烈建议声明为final。
1 public class Main { 2 public static void main(String[] args) { 3 Weekday day = Weekday.SUN; 4 if (day.dayValue == 6 || day.dayValue == 0) { 5 System.out.println("Today is " + day + ". Work at home!"); 6 } else { 7 System.out.println("Today is " + day + ". Work at office!"); 8 } 9 } 10 } 11 12 enum Weekday { 13 MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日"); 14 15 public final int dayValue; 16 private final String chinese; 17 18 private Weekday(int dayValue, String chinese) { 19 this.dayValue = dayValue; 20 this.chinese = chinese; 21 } 22 23 @Override 24 public String toString() { 25 return this.chinese; 26 } 27 }
-
枚举类中的抽象方法实现,需要枚举类中的每个对象都对其进行实现。
1 public class Test{ 2 public static void main(String[] args) { 3 for (Color c:Color.values()){ 4 System.out.print(c.getColor() + "、"); 5 } 6 } 7 } 8 9 enum Color{ 10 RED{ 11 public String getColor(){//枚举对象实现抽象方法 12 return "红色"; 13 } 14 }, 15 GREEN{ 16 public String getColor(){//枚举对象实现抽象方法 17 return "绿色"; 18 } 19 }, 20 BLUE{ 21 public String getColor(){//枚举对象实现抽象方法 22 return "蓝色"; 23 } 24 }; 25 public abstract String getColor();//定义抽象方法 26 }
-
switch内使用(枚举开关的大小写标签必须是枚举常量的非限定名称,通俗的讲,就是不带类名,如不能是Weekday.MON,而是MON)
1 public class Main { 2 public static void main(String[] args) { 3 Weekday day = Weekday.SUN; 4 switch(day) { 5 case MON: 6 case TUE: 7 case WED: 8 case THU: 9 case FRI: 10 System.out.println("Today is " + day + ". Work at office!"); 11 break; 12 case SAT: 13 case Weekday.SUN: 14 System.out.println("Today is " + day + ". Work at home!"); 15 break; 16 default: 17 throw new RuntimeException("cannot process " + day); 18 } 19 } 20 } 21 22 enum Weekday { 23 MON, TUE, WED, THU, FRI, SAT, SUN; 24 }
-
name() 返回常量名
-
Java中,我们可以通过
-
纪录类
-
String
、Integer
等类型都是不变类,一个不变类具有以下特点:-
定义class时使用
final
,无法派生子类; -
每个字段使用
final
,保证创建实例后无法修改任何字段。
-
定义class时使用
-
Java 14开始,引入了新的
Record
类。1 public class Main { 2 public static void main(String[] args) { 3 Point p = new Point(123, 456); 4 System.out.println(p.x()); 5 System.out.println(p.y()); 6 System.out.println(p); 7 } 8 } 9 10 public record Point(int x, int y) {}
public record Point(int x, int y) {} //把上述定义改写为class,相当于以下代码: 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; } public String toString() { return String.format("Point[x=%s, y=%s]", x, y); } public boolean equals(Object o) { ... } public int hashCode() { ... } }
-
编译器默认按照
record
声明的变量顺序自动创建一个构造方法,并在方法内给字段赋值。假设Point
类的x
、y
不允许负数,我们就得给Point
的构造方法加上检查逻辑。public record Point(int x, int y) { public Point { if (x < 0 || y < 0) { throw new IllegalArgumentException(); } } } //方法public Point {...}被称为Compact Constructor,它的目的是让我们编写检查逻辑,编译器最终生成的构造方法如下: public final class Point extends Record { public Point(int x, int y) { // 这是我们编写的Compact Constructor: if (x < 0 || y < 0) { throw new IllegalArgumentException(); } // 这是编译器继续生成的赋值代码: this.x = x; this.y = y; } ... }
-
作为
record
的Point
仍然可以添加静态方法。一种常用的静态方法是of()
方法,用来创建Point。
1 public record Point(int x, int y) { 2 public static Point of() { 3 return new Point(0, 0); 4 } 5 public static Point of(int x, int y) { 6 return new Point(x, y); 7 } 8 } 9 10 //这样我们可以写出更简洁的代码: 11 12 var z = Point.of(); 13 var p = Point.of(123, 456);
-
-
BigInteger
-
使用的整数范围超过了
long
型时。java.math.BigInteger
用来表示任意大小的整数。BigInteger
内部用一个int[]
数组来模拟一个非常大的整数。BigInteger bi = new BigInteger("1234567890"); System.out.println(bi.pow(5)); // 2867971860299718107233761438093672048294900000
-
对
BigInteger
做运算的时候,只能使用实例方法。BigInteger i1 = new BigInteger("1234567890"); BigInteger i2 = new BigInteger("12345678901234567890"); BigInteger sum = i1.add(i2); // 12345678902469135780
-
BigInteger
和Integer
、Long
一样,也是不可变类,并且也继承自Number
类。-
转换为
byte
:byteValue()
-
转换为
short
:shortValue()
-
转换为
int
:intValue()
-
转换为
long
:longValue()
-
转换为
float
:floatValue()
-
转换为
double
:doubleValue()
-
转换为
-
BigInteger
的值甚至超过了float
的最大范围(3.4x1038),那么返回的float是 InfinityBigDecimal
-
使用的整数范围超过了
-
BigDecimal
-
和
BigInteger
类似,BigDecimal
可以表示一个任意大小且精度完全准确的浮点数。BigDecimal bd = new BigDecimal("123.4567"); System.out.println(bd.multiply(bd)); // 15241.55677489
-
BigDecimal
用scale()
表示小数位数。BigDecimal d1 = new BigDecimal("123.45"); BigDecimal d2 = new BigDecimal("123.4500"); BigDecimal d3 = new BigDecimal("1234500"); System.out.println(d1.scale()); // 2,两位小数 System.out.println(d2.scale()); // 4 System.out.println(d3.scale()); // 0
-
通过
BigDecimal
的stripTrailingZeros()
方法,可以将一个BigDecimal
格式化为一个相等的,但去掉了末尾0的BigDecimal。
1 BigDecimal d1 = new BigDecimal("123.4500"); 2 BigDecimal d2 = d1.stripTrailingZeros(); 3 System.out.println(d1.scale()); // 4 4 System.out.println(d2.scale()); // 2,因为去掉了00 5 6 BigDecimal d3 = new BigDecimal("1234500"); 7 BigDecimal d4 = d3.stripTrailingZeros(); 8 System.out.println(d3.scale()); // 0 9 System.out.println(d4.scale()); // -2 10 //如果一个BigDecimal的scale()返回负数,例如,-2,表示这个数是个整数,并且末尾有2个0。
-
可以对一个
BigDecimal
设置它的scale
,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断。import java.math.BigDecimal; import java.math.RoundingMode; public class Main { public static void main(String[] args) { 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 System.out.println(d2); System.out.println(d3); } }
-
对
BigDecimal
做加、减、乘时,精度不会丢失,但是做除法时,存在无法除尽的情况,这时,就必须指定精度以及如何进行截断。BigDecimal d1 = new BigDecimal("123.456"); BigDecimal d2 = new BigDecimal("23.456789"); BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留10位小数并四舍五入 BigDecimal d4 = d1.divide(d2); // 报错:ArithmeticException,因为除不尽
-
可以对
BigDecimal
做除法的同时求余数。1 public class Main { 2 public static void main(String[] args) { 3 BigDecimal n = new BigDecimal("12.345"); 4 BigDecimal m = new BigDecimal("0.12"); 5 BigDecimal[] dr = n.divideAndRemainder(m); 6 System.out.println(dr[0]); // 102 7 System.out.println(dr[1]); // 0.105 8 } 9 }
-
比较两个
BigDecimal
的值是否相等时,要特别注意,使用equals()
方法不但要求两个BigDecimal
的值相等,还要求它们的scale()
相等。BigDecimal d1 = new BigDecimal("123.456"); BigDecimal d2 = new BigDecimal("123.45600"); System.out.println(d1.equals(d2)); // false,因为scale不同 System.out.println(d1.equals(d2.stripTrailingZeros())); // true,因为d2去除尾部0后scale变为2 System.out.println(d1.compareTo(d2)); // 0
-
必须使用
compareTo()
方法来比较,它根据两个值的大小分别返回负数、正数和0
,分别表示小于、大于和等于。(总是使用compareTo()比较两个BigDecimal的值,不要使用equals()!) -
查看
BigDecimal
的源码,可以发现,实际上一个BigDecimal
是通过一个BigInteger
和一个scale
来表示的,即BigInteger
表示一个完整的整数,而scale
表示小数位数。1 public class BigDecimal extends Number implements Comparable<BigDecimal> { 2 private final BigInteger intVal; 3 private final int scale; 4 }
-
BigDecimal
也是从Number
继承的,也是不可变对象。
-
和
-
常用工具类
-
Math
-
Math
类是用来进行数学计算的,提供了大量的静态方法来便于实现数学计算。 -
Math还提供了几个数学常量
Math.PI
Math.E
-
生成一个随机数x,x的范围是
0 <= x < 1
Math.random(); // 0.53907... 每次都不一样
-
如果要生成一个区间在
[MIN, MAX)
的随机数,可以借助Math.random()
实现。// 区间在[MIN, MAX)的随机数 public class Main { public static void main(String[] args) { double x = Math.random(); // x的范围是[0,1) double min = 10; double max = 50; double y = x * (max - min) + min; // y的范围是[10,50) long n = (long) y; // n的范围是[10,50)的整数 System.out.println(y); System.out.println(n); } }
-
Java标准库还提供了一个
StrictMath
,它提供了和Math
几乎一模一样的方法。这两个类的区别在于,由于浮点数计算存在误差,不同的平台(例如x86和ARM)计算的结果可能不一致(指误差不同),因此,StrictMath
保证所有平台计算结果都是完全相同的,而Math
会尽量针对平台优化计算速度,绝大多数情况下,使用Math
就足够。Random
-
-
Random
-
Random
用来创建伪随机数。所谓伪随机数,是指只要给定一个初始的种子,产生的随机数序列是完全一样的。要生成一个随机数,可以使用nextInt()
、nextLong()
、nextFloat()
、nextDouble()。
Random r = new Random(); r.nextInt(); // 2071575453,每次都不一样 r.nextInt(10); // 5,生成一个[0,10)之间的int r.nextLong(); // 8811649292570369305,每次都不一样 r.nextFloat(); // 0.54335...生成一个[0,1)之间的float r.nextDouble(); // 0.3716...生成一个[0,1)之间的double
-
创建
Random
实例时,如果不给定种子,就使用系统当前时间戳作为种子,因此每次运行时,种子不同,得到的伪随机数序列就不同。如果我们在创建Random
实例时指定一个种子,就会得到完全确定的随机数序列。import java.util.Random; public class Main { public static void main(String[] args) { Random r = new Random(12345); for (int i = 0; i < 10; i++) { System.out.println(r.nextInt(100)); } // 51, 80, 41, 28, 55... } }
-
前面我们使用的
Math.random()
实际上内部调用了Random
类,所以它也是伪随机数,只是我们无法指定种子。
-
-
SecureRandom
-
有伪随机数,就有真随机数。实际上真正的真随机数只能通过量子力学原理来获取,而我们想要的是一个不可预测的安全的随机数,
SecureRandom
就是用来创建安全的随机数的。SecureRandom sr = new SecureRandom(); System.out.println(sr.nextInt(100));
-
SecureRandom
无法指定种子,它使用RNG(random number generator)算法。JDK的SecureRandom
实际上有多种不同的底层实现,有的使用安全随机种子加上伪随机数算法来产生安全的随机数,有的使用真正的随机数生成器。实际使用的时候,可以优先获取高强度的安全随机数生成器,如果没有提供,再使用普通等级的安全随机数生成器。1 import java.util.Arrays; 2 import java.security.SecureRandom; 3 import java.security.NoSuchAlgorithmException; 4 5 public class Main { 6 public static void main(String[] args) { 7 SecureRandom sr = null; 8 try { 9 sr = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器 10 } catch (NoSuchAlgorithmException e) { 11 sr = new SecureRandom(); // 获取普通的安全随机数生成器 12 } 13 byte[] buffer = new byte[16]; 14 sr.nextBytes(buffer); // 用安全随机数填充buffer 15 System.out.println(Arrays.toString(buffer)); 16 } 17 }
-
-