Java-String和常用类

String和常用类

Java为开发者提供了丰富的类库,例如字符串、数学计算、日期处理等等,完全可以满足日常开发的需要

字符串

在Java中字符串就是连续的字符序列,Java提供了String,StringBuilder、StringBuffer 3个类来封装字符串,在日常开发时,有很大一部分操作就是在操作字符串。例如:在web开发中对前端页面提交的数据进行验证、过滤特殊字符、检测敏感字符等等。因此Java为开发者提供了大量的API,并且也出现了一系列帮助开发者操作数据库的第三方工具库,如国内开发者开发的hutool、和Google提供的Guava。

String

String类是一个不可变类,不可变类即类中的成员变量都使用final修饰,这也就说明一个String对象被创建以后,包含在这里对象中的字符序列是不可改变的,直到整个对象被销毁。并且在Java中所有字符串相关的类都是Charsquence接口的子类。
String类提供了大量的构造方法来创建String对象,先来看以下几个:

  • String():创建一个包含0个字符的String对象
  • String(byte[] bytes,Charset charset):使用指定的字符集将指定的byte[]解码成一个新的String对象
  • String(String original):根据字符串字面量来创建String对象。
  • String(StringBuffer buffer):根据StringBuffer对象来创建String
  • String(StringBuilder builder):根据StringBuilder来创建对应的String对象。

下面,通过示例来学习以上构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.bytecollege;

import java.nio.charset.Charset;

public class StringDemo {
public static void main(String[] args) {
//创建一个字符序列为空的字符串
String str1 = new String();
//使用指定的字符集将指定的byte[]解码成一个新的String对象
String s = "天苍苍,野茫茫";
System.out.println(Charset.defaultCharset());
String str2 = new String(s.getBytes(),Charset.defaultCharset());
System.out.println(str2);
String str3 = new String("张三");
System.out.println(str3);
String str4 = new String(new StringBuffer("张三丰"));
System.out.println(str4);
String str5 = new String(new StringBuilder("张无忌"));
System.out.println(str5);
}
}

除了以上创建字符串的方式,还可以通过字面量的方式创建字符串对象:

1
String s = "金毛狮王";

除此之外,String还提供了诸多API用来操作字符串。

方法名称 描述
equals(String string) 判断两个字符串是否相等
equalsIgnoreCase(String string) 忽略大小写判断两个字符串是否相等
length() 获取字符串长度
charAt(int index) 获取某个索引处的字符
indexOf(String string) 获取字符串第一次出现的位置
indexOf(String string,int startIndex) 从startIndex处查找第一次出现的位置
lastIndexOf(String string) 字符串最后一次出现的位置
startsWith(String string) 判断是否以string开始
endsWith(String string) 判断是否以string结尾
compareTo(String string) 比较字符串大小
toLowerCase() 字符串转小写
toUpperCase() 字符串转大写
subString(int index) 从index位置处截取到字符串末尾
subString(int startIndex,int endIndex) 从startIndex位置开始,到endIndex结束,前闭后开
trim() 去除字符串首尾空格
split(String string) 以string对字符串进行分割,此方法会省略末尾空字符
split(String string,int limit) 对字符串进行分割,此方法不会省略末尾空字符
join(String s,String…str) 以s为连接符,连接str内字符串
concat(String str) 连接字符串
valueOf() 基本类型转字符串
contains(String str) 判断是否包含str
toCharArray() 将字符串转换成字符数组
intern() 判断字符串在常量池中是否存在,如果不存在,则复制,1.6是将实例复制,1.7及以后是将引用复制。
isEmpty() 判断字符串是否为空
stripLeading() 去除字符串首部的空格(since11)
stripTrailing() 去除字符串尾部的空格(since11)
isBlank() 判断字符串是否为空及是否全是空格
repeate(int count) 重复字符串若干次(since11)

下面,通过示例学习String的常用API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package cn.bytecollege;
/**
* 本例将演示String常用API
* @author MR.W
*
*/
public class StringDemo2 {
public static void main(String[] args) {
//判断两个字符串相等
String a = "abc";
String b = "abc";
String c = "ABC";
System.out.println(a.equals(b));
//忽略大小写判断两个字符串是否相等
System.out.println(a.equalsIgnoreCase(c));
//获取字符串长度
String d = "HelloWorld";
System.out.println(d.length());
//获取索引处的字符,方法参数在0-字符串长度-1之间
System.out.println(d.charAt(0));
//从头开始查找获取字符串第一次出现的位置
int index = d.indexOf("lo");
System.out.println(index);
//从某个位置处查找获取字符串第一次出现的位置
System.out.println(d.indexOf("o",7));
//查找字符串最后一次出现的位置
System.out.println(d.lastIndexOf("o"));
//判断字符串是否是以某个字符串开始
System.out.println(d.startsWith("He"));
//判断字符串是否以某个字符串结束
System.out.println(d.endsWith("lo"));
//比较两个字符串大小,如果两个字符串相等则返回0
//如果字符串中的字符相等,但是长度不相等则返回长度差值
//如果字符不相等则返回字符差值
System.out.println(a.compareTo(b));
//字符串转大小
System.out.println(a.toUpperCase());
//字符串转小写
System.out.println(a.toLowerCase());
//截取字符串,从传入位置截取到字符串末尾
// System.out.println(a.substring(2));
//截取字符串,从传入位置截取到第二个参数处,前闭后开
// System.out.println(a.substring(1,2));
//去除字符串首尾空格
System.out.println(" Hello ".trim());
//分割字符串,默认会省略末尾的空
String s = "Hello,World";
String[] strs = s.split(",");
for (String str : strs) {
System.out.println(s);
}
//分割字符串,第二个参数传入负值,则不省略末尾的空
strs = s.split(",",-1);
//连接字符串
System.out.println(a.concat(b));
//将基本类型转成字符串
int k = 100;
System.out.println(String.valueOf(k));
//判断是否包含某个字符串
System.out.println(a.contains("@"));
//将字符串转换成数组
char[] cs = a.toCharArray();
//判断字符串是否为空
System.out.println(a.isEmpty());
}
}

上面的实例中演示了字符串的常用API,因为String是一个不可变类,因此要大量操作字符串时,并不建议使用String对象,而应该使用下一小节中的StringBuilder和StringBuffer

StringBuilder和StringBuffer

StringBuffer和StringBuilder通常也用于操作字符串,但是StringBuffer和StringBuilder中的字符数组不是用final修饰的,所有字符数组可以指向新的数组,也就是说用这两个类创建的字符串对象是可变的,StringBuffer和StringBuilder为开发者提供了以下几个方法。用于操作字符串:

方法 描述
append(String str) 字符串末尾追加字符串
delete(int start, int end) 删除区间内字符
deleteCharAt(int index) 删除索引处字符
replace(int start, int end, String str) 替换区间内的字符
substring(int start) 从start处截取到末尾
substring(int start, int end) 从start处截取到end
insert(int offset, String str) 索引处插入字符串
indexOf(String str) 字符串str第一次出现的位置
reverse() 字符序列反转
toString() 对象转成字符串

因为StringBuffer和StringBuilder提供的方法都一样,所以方法都是通用的,下面通过示例学习
首先,StringBuilder提供了一下几个构造方法:

  • StringBuilder StringBuilder():创建一个空的StringBuilder对象,一个空的字符序列
  • StringBuilder StringBuilder(StringBuilder builder):传入StringBuilder对象创建字符序列
  • StringBuilder StringBuilder(String str):根据字符串创建字符序列
  • StringBuilder(int capcity):指定容量的StringBuilder对象

下面通过示例学习StringBuilder的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package cn.bytecollege;
/**
* 本例将演示StringBuilder常用API
* @author MR.W
*
*/
public class StringBuilderDemo {
public static void main(String[] args) {
String a = "Hello";
StringBuilder sb = new StringBuilder(a);
//字符序列后追加字符串
System.out.println(sb.append("World").toString());
//删除区间内的字符
System.out.println(sb.delete(0, 3));
//删除某个索引处的字符
System.out.println(sb.deleteCharAt(5));
//替换区间内的字符
System.out.println(sb.replace(0, 1, "W"));
//索引处插入字符串
System.out.println(sb.insert(0, "false"));
//反转字符串
System.out.println(sb.reverse());
}
}

Java对String的优化

Java在底层对String进行了优化,节省了一定的内存空间,在前面的章节中可以看到,通过字面量的形式创建String对象,所谓字面量(也叫做直接量),就是在定义变量后直接给定的值。例如:

1
2
3
4
5
6
//下面的代码中10就是一个字面量
int a = 10;
//下面的代码中5.2就是字面量
double b = 5.2;
//字符串对象直接给的值也是字面量
String s = "张三";

因为String是一个不可变类,也就是说一旦定义好一个字符串后,字符串的值是不能被改变了,如果发生了改变一定是指向了一个新的对象。那么这样的话无疑会占用大量的内存空间。因此Java对String进行了一定的优化,在JVM中有一块区域叫做常量池,常量池里放着字符串的字面量和常量。当字面量出现一个字符串后,Java会将该字面量放入常量池,如果再有变量引用该字符串时,直接返回常量池中字符串的引用,而不创建对象。通过下面的示例可以验证以上结论:

1
2
3
4
String a = "张三";
String b = "张三";
//返回结果为true
System.out.print(a==b);

在前面的章节中知道,两个对象用“==”判断相等时,如果返回true,则这两个变量一定指向了相同的对象。从上面的结果可以看出变量a和变量b指向了同一个对象。总结一下就是常量池中的字符串有且只有一个,一旦创建后如果出现相同的字符串则直接返回字符串对象的引用,而不创建新的字符串。
下面深入学习Java对字符串的优化

1
2
3
4
5
6
String a = "张三";
String b = new String("张三");
//返回结果为false
System.out.print(a==b);
//返回结果为true
System.out.print(a.equals(b));

上面的结果可能会很疑惑,我们对上例中的代码逐行分析:当代码执行到第1行时,此时出现了字面量“张三”。因为已经将该对象放入常量池,因此,运行时直接该对象的引用保存在a中。
代码第2行,又出现了“张三”,此时常量池已经有了这个对象,此时就不再创建,此时先返回该对象的引用,返回该引用后,又new了一个String对象,此时在堆中分配了一块内存区域,这块内存中保存这刚才返回的引用。然后将堆中的引用保存在b中,因为a指向了常量池,变量b指向了堆中,所以a和b中保存的引用不同,所以用==判断时返回false,但是两个对象的内容相等。所以使用equals时两个变量的内容相等。
继续看下面的示例:

1
2
3
4
5
6
String a = "HelloWorld";
String d = "Hello"+"World";
//返回true
System.out.print(a==d);
//返回true
System.out.print(a.equals(d));

运行上面的示例,可以看到结果都为true,这又是为什么呢?这也是Java对字符串的一项优化,也就是说在编译期间就可以确定变量d的结果为“HelloWorld”,而在确定变量d的结果之前,变量a的值已经放入了常量池,此时不再创建对象,直接返回引用,因此就有了上例中的结果。
上面的示例可以看出字面量和字面量拼接结果是确定的,那么字面量和变量拼接,变量和变量拼接又会出现什么结果呢,针对上述的问题,JVM还对String做了优化,看下面的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String a = "HelloWorld";
String b = new String("Hello");
String c = new String("World");
String d = b+c;
//返回false
System.out.print(a==d);
//返回true
System.out.print(a.equals(d));
String e = "Hello";
String f = e + c;
//返回false
System.out.print(a==f);
//返回true
System.out.print(a.equals(f));

因为变量d是两个堆中的对象拼接,此时java会用StringBuilder对两个对象拼接,也就是说在堆中新创建一个对象,保存拼接后的字符串,并将引用返回给d保存,因为不是执行同一块内存,所有a中的地址和d中的地址不相同,所以返回false,两个对象的内容相同,所以使用equals方法时返回true,下面的代码也是一样,java会使用StringBuilder对两个对象进行拼接,所以返回的地址不同。

String和StringBuffer以及StringBuilder的区别

根据本小节的内容,三者的区别可以归纳如下:

  1. String、StringBuffer、StringBuilder都是Charsquence接口的子类
  2. String、StringBuffer、StringBuilder都是final修饰的,不能被继承,也就是说三者都没有子类
  3. String是不可变类,创建的字符串序列不能被修改,StringBuffer和StringBuilder是可以修改的
  4. StringBuffer是线程安全的,StringBuilder是非线程安全的,因此StringBuilder效率更高,在不考虑线程安全或者单线程情况下优先考虑使用StringBuilder。

Scanner获取键盘输入

在前面的章节中,我们反复遇到Scanner对象,并使用其方法接受从键盘输入的数据。Scanner可以从文件、输入流、字符串中解析处基本类型和字符串值,Scanner提供了多个构造器,可以分别接收文件、输入流、字符串作为数据源,并从中解析数据。Scanner主要提供以下方法来扫描输入。

  • hasNextXXX():是否还有下个输入项,其中XXX可以是Int,Long等代表基本类型的字符串。如果判断是否有 下一个字符串则直接使用hasNext()。
  • nextXxx():获取下个输入项,输入项可以是基本类型数据,如果是字符串则直接使用next()
1
2
3
4
5
6
7
8
9
10
11
12
package cn.bytecollege;
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
System.out.println("请输入:");
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()) {
int k = scanner.nextInt();
System.out.println(k);
}
}
}

系统相关

Java提供了System类和Runtime类来获取平台相关属性和运行平台进行交互。

System类

System类代表当前运行Java程序的平台。System为开发者提供了获取系统环境变量,系统属性等方法。下面通过示例来使用System类访问操作系统的环境变量和系统属性。

  • Map<String,String> getenv():获取系统所有的环境变量,保存在map集合中。
  • String getenv(String name):获取指定的环境变量
  • Properties getProperties():获取系统的属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package cn.bytecollege;

import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

public class SystemDemo {
public static void main(String[] args) {

//获取所有的环境变量
Map<String,String> map = System.getenv();
//遍历map
for(String s : map.keySet()) {
System.out.println(s+"======"+map.get(s));
}
//获取指定名称的环境变量值,如果环境变量的名称不存在则返回null
System.out.println(System.getenv("JAVA_HOME"));
//获取系统参数
Properties properties = System.getProperties();

Set<Entry<Object, Object>> entry = properties.entrySet();

for (Entry<Object, Object> e : entry) {
System.out.println(e.getKey()+"============="+e.getValue());
}
}
}

上例中使用了Map和Properties容器,在此处只为获取保存在其中的数据,详细内容讲在后续章节学习,此处不做深究。
除此以外,System还提供了两个使用频率比较高的方法:

  • long currentTimeMillis():获取当前时间的毫秒数,返回当前时间与UTC 1970年1月1日 00:00:00的时间差,以毫秒为单位
  • int identityHashCode(Object x):获取指定对象的精确hashCode值,关于hashCode会在Set集合中详细讲解,此处先做了解
1
2
3
4
5
6
7
8
9
package cn.bytecollege;

public class SystemDemo2 {
public static void main(String[] args) {
//获取当前时间的毫秒数
System.out.println(System.currentTimeMillis());
System.out.print(System.identityHashCode("Hello"));
}
}

System还提供了一个复制数组的方法:

  • void arraycopy(Object src, int srcPos, int destPos, int length):该方法的作用是复制数组,第一个src是指被复制的数组,srcPos是指定复制的起始位置,dest则是指将元素粘贴到哪个数组,destPos是指目标数组粘贴的起始位置,length则可以理解为复制元素的个数。
  • void exit(int status):退出虚拟机。
1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.bytecollege;

import java.util.Arrays;

public class CopyArray {
public static void main(String[] args) {
int[] a = {5,9,1,4,3};
int[] b = new int[10];
//复制数组a元素粘贴的b数组
System.arraycopy(a, 0, b, 3, 5);
System.out.println(Arrays.toString(b));
}
}

Runtime类

Runtime类代表Java程序的运行时环境,每个Java程序都有一个与之对应Runtime实例。可以访问JVM相关信息,处理器数量,内存信息等

  • int availableProcessors():获取处理器数量
  • long freeMemory():获取jvm空闲内存
  • long totalMemory():获取最大内存
  • long maxMemory():获取虚拟机可以用最大内存
1
2
3
4
5
6
7
8
9
10
11
package cn.bytecollege;

public class RuntimeDemo {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
System.out.println("处理器数量:"+runtime.availableProcessors());
System.out.println("总内寸数:"+runtime.totalMemory()/Math.pow(1024,2)+"MB");
System.out.println("JVM空闲内存:"+runtime.freeMemory()/Math.pow(1024, 2)+"MB");
System.out.println("可用最大内存:"+runtime.maxMemory()/Math.pow(1024, 3)+"GB");
}
}

不同的电脑可能会出现不同的结果:

Runtime还提供了exec()方法,用于启动一个进程来运行操作系统的命令。

1
runtime.exec("notepad.exe");

运行示例中的代码可以打开记事本,其他命令可以自行查找。

数学相关

Math工具类

Math提供了基本的加减乘除以为,还提供了一些复杂的数学运行,三角函数,对数运算等,Math类还提供了两个常量PI和E
Math的主要方法如下表:

方法 描述
Math.sqrt() 求平方根,参数为负数时返回NAN
Math.pow(x,a) 幂运算,x的a次方
Math.floor(a) 向下取整
Math.ceil(a) 向上取整
Math.floorMod(x,y) 求余,x%y
Math.toDegree() 弧度转换成角度
Math.toRadians() 角度转换成弧度
Math.sin() 求正弦
Math.cos() 求余弦
Math.tan() 求正切
Math.abs() 求绝对值
Math.max() 两个数的最大值
Math.min() 两个数最小值
Math.random() 0.0到1.0的随机数

下面通过示例学习Math的 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package cn.bytecollege;
public class MathDemo {
public static void main(String[] args) {
//求平方根
System.out.println(Math.sqrt(4));
//幂运算,x的a次方
System.out.println(Math.pow(3,3));
//求余,x%y
System.out.println(Math.floorMod(10,3));
//求正弦
System.out.println(Math.sin(Math.PI/2));
//求余弦
System.out.println(Math.cos(Math.PI));
//求正切
System.out.println(Math.tan(0));
//求绝对值
System.out.println(Math.abs(-101));
//求两个数的最大值
System.out.println(Math.max(-2,2));
//求两个数的最小值
System.out.println(Math.min(-2,2));
//0.0到1.0的随机数
System.out.println(Math.random());
}
}

Random生成随机数

Random类用于生成一个伪随机数,Random类包含两个构造方法:

  • Random():以当前时间作为种子
  • Random(long seed):需要开发者显式传入long型的种子

Random有以下几个生成伪随机数的方法:

  • nextDouble():生成0.0—1.0之间的伪随机double数。
  • nextFloat():生成0.0—1.0之间的伪随机float数。
  • nextInt():生成一个 int类型取值范围内的伪随机整数
  • nextInt(int a):生成0-a之间的伪随机整数。
  • nextLong():生成处于long整数取值范围的伪随机整数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package cn.bytecollege;

import java.util.Random;
/**
* 本例将演示随机数
* @author MR.W
*
*/
public class RandomDemo {
public static void main(String[] args) {

Random r1 = new Random();
System.out.println(r1.nextBoolean());
System.out.println(r1.nextDouble());
System.out.println(r1.nextFloat());
System.out.println(r1.nextInt());
System.out.println(r1.nextLong());
System.out.println(r1.nextInt(100));

Random r2 = new Random(100);
Random r3 = new Random(100);
System.out.println(r2.nextInt());
System.out.println(r2.nextInt());
}
}

通过上例的示例,产生了不同的随机数,需要注意的当种子相同时,产生的随机数是相同的。

BigDecimal

在学习数据类型时,其中浮点型有float和double两种,同时也了解到float和double在计算时很容易出现精度问题:
例如,如下代码:

1
2
double s = 0.05+0.01;
System.out.print(s);


如果程序对运算精度要求不高,double和float完全可以满足需要,但是有些时候对浮点型精度要求比较高,例如金融项目、电商项目等。这就需要使用BigDecimal类的方法了。
首先,查看BigDecimal的构造方法:

  • BigDecimal(String val):通过字符串创建BigDecimal对象,Java推荐使用该构造方法创建对象,因为如果传入浮点型的值时,因为本身该值就不精确,因此创建的BigDecimal也会不精确,所以推荐使用字符串的形式创建对象。

如果必须使用 double 浮点数作为 BigDecimal 构造器的参数时,不要直接将该 double 浮点数作为构造器参数创建 BigDecimal 对象,而是应该通过 BigDecimal.valueOf(double value)静态方法来创建 BigDecimal 对象。
BigDecimal类提供了add()、subtract()、multiply()、divide()、pow()等方法对精确浮点数进行常规算术运算。下面程序示范了 BigDecimal 的基本运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package cn.bytecollege;

import java.math.BigDecimal;

public class BigDecimalDemo {
public static void main(String[] args) {
BigDecimal d1 = new BigDecimal("0.05");
BigDecimal d2 = new BigDecimal("0.01");
//加法
System.out.println(d1.add(d2));
//减法
System.out.println(d1.subtract(d2));
//乘法
System.out.println(d1.multiply(d2));
//除法
System.out.println(d1.divide(d2));
}
}

运行结果如下:

包装类

Java是一门面向对象的语言,但是在前面的章节中的了解到,定义基本类型变量时,并没有new对象,也就是说8种基本数据类型并不支持面向对象,基本类型数据也就不具备对象的特性,没有成员变量和成员方法可以被调用。Java提供这8种基本数据类型其一是为了节省内存开支,开发者没必要为了定义一个整型变量而去创建一个对象,这样对内存也是一种浪费,另外也是为了使得开发者更好的在语言之间迁移。
Java针对每个基本类提供了对应的包装类,如下表:

基本类型 包装类
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean

有时候需要将基本类型转换成包装类,例如,将一个十进制数转换成二进制时,基本类型的变量没有方法可以调用,此时就需要进行转换,Java中有如下规定:

  1. 基本类型转换成包装类叫做自动装箱
  2. 包装类转换成基本类型时叫做自动拆箱
1
2
3
4
5
6
int a = 100;
//自动装箱
Integer b = Integer.valueOf(a);
//自动拆箱
Integer c = new Integer(100);
int d = c;

Integer类中的缓存

Integer类提供了缓存,将-128到127之间的Integer对象创建好放置在内存中,使用是直接返回对象的引用,通过下面示例来学习Integer中的缓存。

1
2
3
4
//Java会自动进行装箱
Integer a = 100;
Integer b = 100;
System.out.print(a==b);

引用类型的判断使用equals()方法,但是上面的示例运行结果是true,这就是缓存的作用,因为对象a和对象b的值相等,且在-128-127之间,此时100这个对象已经创建好了,当创建a、b两个对象时,直接从缓存中获取对象,也就是说a和b都指向了相同的内存区域。如果变量的值超出了缓存范围,则会创建新的对象。如下示例:

1
2
3
Integer a = 129;
Integer b = 129;
System.out.print(a==b);

因为对象a和b的值超出了缓存范围,因此是两个不同的对象,指向了不同的内存区域,因此返回false。
此外,当包装类和基本类型的值进行比较时,会进行自动拆箱

1
2
3
Integer a = 129;
int b = 129;
System.out.print(a==b);

上例的代码会返回true,这是因为当基本类型和包装类型比较时,会将包装类拆箱成基本类型,而基本类型则不存在缓存,就是两个真实的值在比较,因此返回true。
当 JDK 提供了自动装箱和自动拆箱功能后,大大简化了基本类型变量和包装类对象之间的转换过程。值得指出的是,进行自动装箱和自动拆箱时必须注意类型匹配,例如 Integer 只能自动拆箱成 int类型变量,不要试图拆箱成 boolean 类型变量; 与之类似的是,int 类型变量只能自动装箱成 Integer 对象(即使赋给 Obiect 类型变量,那也只是利用了Java 的向上自动转型特性),不要试图装箱成 Boolean对象。
除此之外,包装类还可实现基本类型变量和字符串之间的转换。把字符串类型的值转换为基本类型的值有两种方式。
利用包装类提供的 parseXxx(String s)静态方法(除了 Character 之外的所有包装类都提供了该方法。
另外需要注意的是,Byte、Short、Integer、Long这4个包装类,都具有-128到127之间的缓存,Float和Double类型则不具有。Boolean类型中表示true和false的对象则是两个常量

Integer类常用方法

日期时间类

Java提供了Date类和Calendar类用于处理日期,包括创建日期,获取当前日期等。

Date类

Date类为开发者提供了6个构造方法,但是有4个已经标记为Deprecated(过时的方法,不推荐使用,可能会在未来的某个版本彻底废弃)。因此我们查看剩余两个:

  • Date():创建一个代表当前时间的Date对象。
  • Date(long date):根据指定的long型整数生成一个Date对象。其中参数表示创建Date对象和GMT 1970年1月1日 08:00:00之间的时间差。单位是毫秒

下面通过示例学习两个构造方法的使用。

1
2
3
4
//当前时间
Date date = new Date();
//指定时间
Date date2 = new Date(1000000);

Date类还为开发者提供了一下4个方法:

  • boolean after(Date when)∶ 测试该日期是否在指定日期 when 之后。
  • boolean before(Date when)∶ 测试该日期是否在指定日期 when 之前。
  • long getTime()∶ 返回该时间对应的 long型整数,即从GMT1970-01-01 00∶00∶00 到该Date 对象
之间的时间差,以毫秒作为计时单位。
  • void setTime(long time):设置该Date对象的时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.bytecollege;
import java.util.Date;
public class DateDemo {
public static void main(String[] args) {
Date date = new Date(2020,10,01);
Date newdate = new Date(2021,10,02);
//测试该日期是否在指定日期 when 之后
boolean a = date.after(newdate);
//测试该日期是否在指定日期 when 之前
boolean b = date.before(newdate);
//返回该时间对应的 long型整数
date.setTime(86400);
//设置该Date对象的时间
long c = date.getTime();
}
}

Calendar类

因为Date类的设计缺陷,Java提供了Calendar类处理日期和时间。Calendar是一个抽象类。它用了几个静态方法用来获取Calendar对象。
Calendar 类提供了大量访问、修改日期时间的方法,常用方法如下。

  • void add(int field, int amount)∶ 根据日历的规则,为给定的日历字段添加或减去指定的时间量。
  • int get(int field)∶ 返回指定日历字段的值。
  • int getActualMaximum(int field)∶ 返回指定日历字段可能拥有的最大值。例如月,最大值为 11。
  • int getActualMinimum(int field)∶ 返回指定日历字段可能拥有的最小值。例如月,最小值为 0。
  • void roll(int field, int amount)∶与 add()方法类似,区别在于加上 amount 后超过了该字段所能表
示的最大范围时,也不会向上一个字段进位。
  • void set(int field, int value)∶将给定的日历字段设置为给定值。
  • void set(int year, int month, int date)∶ 设置 Calendar 对象的年、月、日三个字段的值。
  • void set(int year, int month,int date,int hourOfDay, int minute,int second)∶设置 Calendar 对象的年、
月、日、时、分、秒6个字段的值。
上面的很多方法都需要一个int类型的 field参数,field是 Calendar类的类变量,如 Calendar,YEAR、 CalendarMONTH等分别代表了年、月、日、小时、分钟、秒等时间字段。需要指出的是,Calendar.MONTH字段代表月份,月份的起始值不是1,而是0,所以要设置8 月时,用7 而不是8。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.bytecollege;
import java.util.Calendar;
public class CalenderDemo {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
//设置 Calendar 对象的年、月、日、时、分、秒6个字段的值
calendar.set(2020,10,1,20,23,59,59);
//返回指定日历字段的值
System.out.println(calendar.get(Calendar.HOUR_OF_DAY));
//返回指定日历字段可能拥有的最小值
System.out.println(calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
//返回指定日历字段可能拥有的最大值
calendar.add(Calendar.MONTH,3);
// calendar.roll(Calendar.MONTH,-10);
System.out.println(calendar.getTime());
}
}

add(int field,int amount)的功能非常强大,add 主要用于改变 Calendar 的特定字段的值。如果需要增加某字段的值,则让 amount 为正数;如果需要减少某字段的值,则让 amount 为负数即可。
add(int field, int amount)有如下两条规则:

  • 当被修改的字段超出它允许的范围时,会发生进位,即上一级字段也会增大。
  • 如果下一级字段也需要改变,那么该字段会修正到变化最小的值

roll()的规则与 add()的处理规则不同∶当被修改的字段超出它允许的范围时,上一级字段不会增大。

Java8 新增的日期时间类

Java 8专门新增了一个 java.time 包,该包下包含了如下常用的类。

  • LocalDate∶该类代表不带时区的日期,例如 2007-12-03。该类提供了静态的 now()方法来获取
当前日期,它还提供了 minusXxx()方法在当前年份基础上减去几年、几月、几周或几日等,也提供了 plusXxx()方法在当前年份基础上加上几年、几月、几周或几日等。
  • LocalTime∶ 该类代表不带时区的时间,例如 10∶15∶30。该类提供了静态的 now()方法来获取当
前时间,除此之外,它还提供了minusXxx()方法在当前年份基础上减去几小时、几分、几秒等,也提供了plusXxx()方法在当前年份基础上加上几小时、几分、几秒等。
  • LocalDateTime∶该类代表不带时区的日期、时间,例如 2007-12-03T10∶15∶30。该类提供了静态
的 now()方法来获取当前日期、时间,它还提供了 minusXxx()方法在当前年份基础上减去几年、几月、几日、几小时、几分、几秒等,也提供了plusXxx()方法在当前年份基础上加上几年、几月、几日、几小时、几分、几秒等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.bytecollege;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class LocalDemo {
public static void main(String[] args) {
LocalDate ld = LocalDate.now();
System.out.println(ld);
LocalTime lt = LocalTime.now();
System.out.println(lt);
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
}
}

格式化

在日常的开发中,开发者经常会遇到按照一定格式显示数据的情况,例如,文本、数字、日期等等,在本小节,我们将学习Java中常用的格式化类。

数字格式化

NumberFormat 是所有数值格式的抽象基类。此类提供格式化和解析数值的接口。NumberFormat提供了以下方法用于格式化。

  • getInstance()、getNumberInstance()。返回当前默认语言环境的通用数值格式。
  • getInstance(Locale)、getNumberInstance(Locale)。返回指定语言环境的通用数值格式。
  • NumberFormat.setMinimumIntegerDigits(int)。设置数的整数部分所允许的最小位数。
  • NumberFormat.setMaximumIntegerDigits(int)。设置数的整数部分所允许的最大位数。
  • NumberFormat.setMinimumFractionDigits(int)。设置最少小数点位数,不足的位数以0补位,超出的话按实际位数输出。
  • NumberFormat.setMaximumFractionDigits(int)。设置最多保留小数位数,不足不补0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package cn.bytecollege;

import java.text.NumberFormat;
import java.util.Locale;

public class NumberFormatDemo {
public static void main(String[] args) {
double d = 12345.676688000;
NumberFormat nf = NumberFormat.getNumberInstance();
System.out.println(nf.format(d));//12,345.677 默认只保留到小数点后三位
nf.setMinimumIntegerDigits(2);
System.out.println(nf.format(d));//12,345.677 整数部分大于2位按默认最小数位数3位输出
d = 1234.0;
nf.setMaximumIntegerDigits(3);
System.out.println(nf.format(d));//234
nf = NumberFormat.getInstance();
d = 12345.6766;
nf.setMinimumFractionDigits(1);
System.out.println(nf.format(d));//12,345.677 小数部分大于1位,按默认最大小数位数3位输出
nf.setMinimumFractionDigits(5);
System.out.println(nf.format(d));//12,345.67660 不够位数补0
nf.setMaximumFractionDigits(1);
System.out.println(nf.format(d));//12,345.7
nf = NumberFormat.getNumberInstance(Locale.CHINA);
d = 12345.6789;
System.out.println(nf.format(d));//12,345.679
nf = NumberFormat.getNumberInstance(Locale.US);
System.out.println(nf.format(d));//12 345,679
}
}

日期格式化

在前面的日期类当中当打印Date对象时发现,打印的格式并不是我们日常生活中看到的格式,因此,需要对日期进行格式化,Java为开发者提供了SimpleDateFormat对象格式化日期,当创建SimpleDateFormat对象时需要传入一个格式字符串。下面通过示例来学习。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.bytecollege;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateFormatDemo {
public static void main(String[] args) throws ParseException {
Date date = new Date(158764354);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = format.format(date);
System.out.println(str);
//将字符串转换成Date类型,字符串格式也要符合构造方法中的参数格式
String s = "1990-08-18 12:00:00";
//parse方法用于解析
Date d = format.parse(s);
System.out.println(d);
}
}

Java 8位开发者提供了更强大的格式化工具DateFormatter,格式化之前需要先获取DateFormatter对象。

1
2
3
4
5
6
7
8
9
10
11
package cn.bytecollege;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateFormatterDemo {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = formatter.format(LocalDateTime.now());
System.out.println(str);
}
}

正则表达式

正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

元字符

构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与运算符可以将小的表达式结合在一起来创建更大的表达式。正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为”元字符”)组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
在学习正则表达之前,首先需要了解一下元字符,也就是正则表达式的组成部分,每个元字符都有其特定的表示含义及范围。

字符 描述
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,’n’ 匹配字符 “n”。’\n’ 匹配一个换行符。序列 ‘\‘ 匹配 “" 而 “(“ 则匹配 “(“。
^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 ‘\n’ 或 ‘\r’ 之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 ‘\n’ 或 ‘\r’ 之前的位置。
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 或 “does” 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*’。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,’o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。
. 匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用像”(.|\n)“的模式。
(pattern) 匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 ‘(‘ 或 ‘)‘。
(?:pattern) 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 “或” 字符 (|) 来组合一个模式的各个部分是很有用。例如, ‘industr(?:y|ies) 就是一个比 ‘industry|industries’ 更简略的表达式。
(?=pattern) 正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,”Windows(?=95|98|NT|2000)”能匹配”Windows2000”中的”Windows”,但不能匹配”Windows3.1”中的”Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern) 正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如”Windows(?!95|98|NT|2000)”能匹配”Windows3.1”中的”Windows”,但不能匹配”Windows2000”中的”Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?<=pattern) 反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,”(?<=95&#124;98&#124;NT&#124;2000)Windows“能匹配”2000Windows“中的”Windows“,但不能匹配”3.1Windows“中的”Windows“。
(?<!pattern) 反向否定预查,与正向否定预查类似,只是方向相反。例如”(?<!95&#124;98&#124;NT&#124;2000)Windows“能匹配”3.1Windows“中的”Windows“,但不能匹配”2000Windows“中的”Windows“。
x|y 匹配 x 或 y。例如,’z|food’ 能匹配 “z” 或 “food”。’(z|f)ood’ 则匹配 “zood” 或 “food”。
[xyz] 字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’。
[^xyz] 负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’、’l’、’i’、’n’。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,’[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,’[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。
\B 匹配非单词边界。’er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
\cx 匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。
\d 匹配一个数字字符。等价于 [0-9]。
\D 匹配一个非数字字符。等价于 [^0-9]。
\f 匹配一个换页符。等价于 \x0c 和 \cL。
\n 匹配一个换行符。等价于 \x0a 和 \cJ。
\r 匹配一个回车符。等价于 \x0d 和 \cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于 \x09 和 \cI。
\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。
\w 匹配字母、数字、下划线。等价于’[A-Za-z0-9_]’。
\W 匹配非字母、数字、下划线。等价于 ‘[^A-Za-z0-9_]’。
\xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,’\x41’ 匹配 “A”。’\x041’ 则等价于 ‘\x04’ & “1”。正则表达式中可以使用 ASCII 编码。
\num 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,’(.)\1’ 匹配两个连续的相同字符。
\n 标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。
\nm 标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。
\nml 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。
\un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。

Java中为开发提供了2个类用于正则表达式:

  • Pattern 类:pattern 对象是一个正则表达式的编译表示。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,你必须首先调用其公共静态编译方法,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。
  • Matcher 类:Matcher 对象是对输入字符串进行解释和匹配操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。

下面我们通过一个简单的示例,来了解正则表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.bytecollege;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RexDemo {
public static void main(String[] args) {
String content = "884105019@qq.com";
Pattern p = Pattern.compile("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$");
Matcher m = p.matcher(content);
System.out.println(m.matches());
}
}