怎样理解字符串的不可变性?
在Java中,String是通过private final char[]实现的,没有任何修改char[]的方法,所以是不可变的。
在字符串操作中,其实是复制了一份新的字符串,然后再进行操作,返回的是新的字符串,不是在原有的字符串上进行操作。
如果想要改变的话,可以用char[] cs = "hello".toCharArray()
把字符串转换成字符型数组,然后改变字符型数组。
为什么字符串不能用==比较?
因为字符串默认是一种引用,如果直接使用==就相当于是在比较两个字符串的地址,必须使用equal()
方法,才能比较字符串的内容。
String s1 = "hello";
String s2 = "hello";
System.out.println(s1==s2); // true
System.out.println(s1.equals(s2)); // true
例如在这段代码中,其实line3的本质上,java只分配了一个字符串,然后s1和s2都指向这个字符串,所以地址一样,第二个才是真正比较两个字符串的内容。
字符串常用的一些方法
method | 方法说明 | 输入 | 输出 |
---|---|---|---|
== | 比较两个字符串地址 | 布尔值 | |
s1.equals(s2) | 比较两个字符串内容 | 布尔值 | |
s1.equalsIngnoreCase(s2) | 比较两个字符串内容,忽略大小写 | 布尔值 | |
s1.contains(s2) | 查找是否包含子串,类似于word里的find | 子串字符串 | 布尔值 |
s1.substring(2,6) | 截取子串,坐标是左闭右开 | 区间坐标[2,6) | 截取的字符串 |
trim(s1) | 修剪首尾的空白字符,包括tab,回车 | 新的字符串 | |
strip(s1) | 同上,还能移除一些特殊字符 (还可以单独去除头部或者尾部) |
新的字符串 | |
isempty(s1) | 检查长度是否为0 | 布尔值 | |
isblank(s1) | 检查内容是否为空 | 布尔值 | |
s1.split(",") | 字符串分割 | 分隔符 | 多个字符串 |
String.join("***",arr) | 使用分隔符***将字符串数组连接 | 字符串数组和连接符 | 一个新的字符串 |
S1.replace(“a”,“b”) | 字符串替换,后面的替换前面 | 被替换和替换字符串 | 新的字符串 |
“%s%d”.formatted(“ming”,20) | 将s1中的%s和%d用内容填充 | ||
String.format("%s%d",“ming”,20) | 静态方法 | ||
String.valueOf() | 将类型转换成字符串 | 各种类型 | String |
Integer.parseInt(“123”) | 将类型转换成int | 各种类型 | int |
Boolean.parseBoolean(“true”) | 将字符串类型转换成布尔类型 | String | bool |
char[] cs = “s1”.toCharArray() | 将字符串转换成字符数组形式 | String | char[] |
String c = new String(cs) | 将字符串数组转换成字符串格式 | char[] | String |
cs.clone() | char[]的克隆,防止被改变 | 原始char[] | 新的char[] |
StringBuilder
是一个可变对象,在往里面增加字符的时候,不会一直复制创建新的对象,本质上一直在return this
,所以可以进行链式操作,例如不断地使用append()
方法。
public class Main {
public static void main(String[] args) {
String[] fields = { "name", "position", "salary" };
String table = "employee";
String select = buildSelectSql(table, fields);
System.out.println(select);
System.out.println("SELECT name, position, salary FROM employee".equals(select) ? "测试成功" : "测试失败");
}
static String buildSelectSql(String table, String[] fields) {
// TODO:
// 新建一个StringBuilder对象
var sb = new StringBuilder();
sb.append("SELECT ");
for (String field : fields)
sb.append(field + ", ");
// 使用delete方法去掉多加的,和空格
sb.delete(sb.length()-2,sb.length());
sb.append(" FROM "+table);
// 转换成字符串形式
return sb.toString();
}
}
最终输入效果为
SELECT name, position, salary FROM employee
测试成功
考虑到这种操作非常频繁,Java中还有一种更快的方式,即使用StringJoiner
在新建StringJoiner
对象的时候,可以设置3个参数,连接符,首字符串,尾字符串。
import java.util.StringJoiner;
public class Main {
public static void main(String[] args) {
String[] fields = { "name", "position", "salary" };
String table = "employee";
String select = buildSelectSql(table, fields);
System.out.println(select);
System.out.println("SELECT name, position, salary FROM employee".equals(select) ? "测试成功" : "测试失败");
}
static String buildSelectSql(String table, String[] fields) {
// TODO:
var sj = new StringJoiner(", ","SELECT "," FROM "+table);
for (String field : fields)
sj.add(field);
return sj.toString();
}
}
最终效果和上面一样,但是看起来更加简洁,需要注意的是,开头需要添加import java.util.StringJoiner
语句,猜测StringJoiner
是对StringBuilder
的一种封装。
Java的数据类型主要有两种:
如何把基本类型转换成引用类型呢?
利用包装,或者叫装箱(Auto Boxing),将基本类型装到类里面。
这个操作在Java中已经定义好了,直接调用即可。
反过来,把类还原成基本类型,又叫拆箱(Auto Unboxing)。
在创建例如Integer
的时候,使用Integer.valueOf(100)
,这个非常类似于String.valueOf()
,不要去新建,因为在使用.valueOf()
时系统会自动调用缓存进行优化。
另外需要注意的是,由于包装生成的都是引用类型,所以必须使用equals()
方法比较是否相等。
例如这样的一段代码,可以通过JavaBean
快速生成读写代码
public class Person {
private String name;
private int age;
}
在项目文件夹中找到对应的class
,然后选择source - generate getters and setters
。
这样就能快速得到两个字段的读写方法。
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;
}
}
使用Introspector.getBeanInfo()
可以获取属性列表。
enum
枚举类本质上是一个public final
类型的class
,不能被继承,常量不能改变。
public final class Color extends Enum { // 继承自Enum,标记为final class
// 每个实例均为全局唯一:
public static final Color RED = new Color();
public static final Color GREEN = new Color();
public static final Color BLUE = new Color();
// private构造方法,确保外部无法调用new操作符:
private Color() {}
}
通过name()
获取字符串,不是使用toString()
。
适合放在switch
语句中。
Math类有哪些方法?
常见的Math
类有如下方法,类似于MATLAB或python的一些操作,Math
不需要手动import
库
Math.abs(-100); // 100
Math.max(100, 99); // 100
Math.min(1.2, 2.3); // 1.2
Math.pow(2, 10); // 2的10次方=1024
Math.sqrt(2); // 1.414...
Math.exp(2); // 7.389...
Math.log(4); // e为底,1.386...
Math.log10(100); // 2
Math.sin(3.14); // 0.00159...
Math.cos(3.14); // -0.9999...
Math.tan(3.14); // -0.0015...
Math.asin(1.0); // 1.57079...
Math.acos(1.0); // 0.0
double pi = Math.PI; // 3.14159...
double e = Math.E; // 2.7182818...
Math.sin(Math.PI / 6); // sin(π/6) = 0.5
Math.random(); // 0.53907... 每次都不一样,在[0,1)之间
可以结合random()
和max,min来生成区间范围内的随机数。
StrictMath
也可以提供和Math
一模一样的方法,前者保证全平台计算结果一直,后者会针对计算平台进行速度优化。
Random类怎么使用?
Random类用来创建伪随机数,首先需要播下一个种子,然后开始产生随机序列。
为什么Random叫伪随机?
默认的种子是系统时间,如果每次种子一样,那么随机数序列也一样,所以称为伪随机数。
// 利用系统时间来播下随机数种子
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 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
是通过CPU的热噪声,磁盘字节,网络流量等随机时间产生的“熵”,因此是安全的。
SecureRandom sr = new SecureRandom(); // 无法指定种子
System.out.println(sr.nextInt(100));