11. Java 常用类

1. 包装类(wrapper)

包装类:八种基本数据类型对应的引用类型即包装类,包装类有了类的特点,就可以调用类中的方法。

1.1 八大包装类

在这里插入图片描述

各个包装类的继承和实现关系:
(1)Boolean
在这里插入图片描述

(2)Character
在这里插入图片描述

(3)Byte、Short、Integer、Long、Float、Double
在这里插入图片描述

idea 中显示上面关系图的方法(以 Integer 为例):
(1)在 Integer 上右键,依次选择 Diagram ➡ Show Diagram
在这里插入图片描述
(2)进去之后就能看到下图
在这里插入图片描述
(3)若想添加其他类,就选中相关父类,按下空格,输入要添加的类。如添加 Byte 包装类,就在 Number 类上按下空格,输入 Byte。
在这里插入图片描述
(4)添加 Byte 类后的效果
在这里插入图片描述
(5)拓展1:显示类中的方法,按下图中的图标
在这里插入图片描述
拓展2:显示类中的属性,按下图中的图标
在这里插入图片描述

1.2 装箱和拆箱(包装类与基本数据类型的转换)

装箱:基本类型 ➡ 包装类型
拆箱:包装类型 ➡ 基本类型

Jdk5 前采用手动装箱、拆箱的方式,Jdk5 及以后可以采用自动装箱、拆箱的方式。

下面演示装箱和拆箱,以 int 和 Integer 为例,其他包装类的用法类似。

int n1 = 100;
//Jdk5之前手动装箱: int -> Integer
Integer integer = new Integer(n1);
Integer integer1 = Integer.valueOf(n1);
//Jdk5之前手动拆箱: Integer -> int
int i = integer.intValue();
//Jdk5及之后自动装箱:底层调用的是valueOf方法
Integer integer2 = n1;
//Jdk5及之后自动装箱:底层调用的是intValue方法
int n2 = integer2;

【例】
(1)下面代码是否正确?

Double d = 100d;//ok,自动装箱Double.valueOf(100d);
Float f = 1.5f;//ok,自动装箱Float.valueOf(1.5f);

(2)下面两段代码输出结果分别是什么?

// 三元运算符中,精度最高的是double,所以整体会提高精度
// obj1为1.0
Object obj1 = true? new Integer(1) : new Double(2.0);
System.out.println(obj1);//1.0
Object obj2;
if(true)
    obj2 = new Integer(1);
else
    obj2 = new Double(2.0);
System.out.println(obj2);//1

1.3 包装类方法

1.3.1 包装类型与 String 类型相互转换

以 Integer 和 String 转换为例,其它类似:
(1)包装类(Integer) ➡ String

Integer i = 100;//自动装箱
// 方法1:
String str1 = i + "";
// 方法2:
String str2 = i.toString();
// 方法3:
//String类的valueOf方法,参数类型是Object
//其他包装类的valueOf方法,参数类型都是对应的基本数据类型
//String不在八大包装类范围内,没有对应的基本数据类型,所以valueOf方法的参数不太一样
String str3 = String.valueOf(i);

(2)String ➡ 包装类(Integer)

String str4 = "123456";
// 方法1:
//Integer.parseInt返回的是int,自动装箱后赋值给i1
Integer i1 = Integer.parseInt(str4);
// 方法2:Integer类有两个构造方法,参数分别是int和String类型
Integer i2 = new Integer(str4);

1.3.2 Integer 类和 Character 类的常用方法

System.out.println(Integer.MIN_VALUE);//返回最小值
System.out.println(Integer.MAX_VALUE);//返回最大值
System.out.println(Character.isDigit('a'));//判断是不是数字
System.out.println(Character.isLetter('a'));//判断是不是字母
System.out.println(Character.isUpperCase('a'));//判断是不是大写
System.out.println(Character.isLowerCase('a'));//判断是不是小写
System.out.println(Character.isWhitespace('a'));//判断是不是空格
System.out.println(Character.toUpperCase('a'));//转成大写
System.out.println(Character.toLowerCase('A'));//转成小写

输出结果:

-2147483648
2147483647
false
true
false
true
false
A
a

1.4 练习

看看下面代码,输出什么结果?

Integer i = new Integer(1);
Integer j = new Integer(1);
//"=="判断对象地址是否相等,即是否是同一个对象
System.out.println(i == j); //false

//自动装箱,底层调用Integer.valueOf
//若被装箱的值在-128~127,就直接返回(不太严谨,下面细讲)
//若被装箱的值不在-128~127,就会像上面一样new Integer()
Integer m = 1;
Integer n = 1;
System.out.println(m == n); //true

Integer x = 128;
Integer y = 128;
System.out.println(x == y);//false

可以下断点追踪源码来查看 valueOf 方法,也可以在 ”Integer x = 128“ 的 Integer 上 Ctrl + B,并打开类的 Structure,找到 valueOf 方法。
在这里插入图片描述
源码中,IntegerCache.low 是 -128,IntegerCache.high 是 127,可以通过 Ctrl + B 查看。
将鼠标放在 catch 上,点击 “+”,可以查看 catch 数组元素,catch 数组元素是 -128 ~ 127:
在这里插入图片描述
在这里插入图片描述

在源码中,若被装箱的值在-128~127 时,会从 catch 数组中返回对应元素,看到 catch 数组中的内容后,我们就能理解源码中的处理方式了。

理解了上面的内容后,来看下面几个例子:

//示例一
//只要都是new的,一定不相等
Integer i1=new Integer(127);
Integer i2=new Integer(127);
System.out.println(i1==i2);//F
//示例二
Integer i3=new Integer(128);
Integer i4=new Integer(128);
System.out.println(i3==i4);//F
//示例三
//-128~127,都是catch数组中的
Integer i5=127;
Integer i6=127;
System.out.println(i5==i6);//T
//示例四
//-128~127以外的,都是new出来的
Integer i7=128;
Integer i8=128;
System.out.println(i7==i8);//F
//示例五
//一个是从catch数组中的,一个是new出来的,是不同的对象
Integer i9=127; 
Integer i10=new Integer(127);
System.out.println(i9==i10);//F
//示例六
//只要有基本数据类型,判断的就是“值是否相等”
Integer i11=127;
int i12=127;
System.out.println(i11==i12);//T
//示例七
Integer i13=128;
int i14=128;
System.out.println(i13==i14);//T

2. String 类

2.1 String 类的结构

  • String 对象用于保存字符串,也就是一组字符序列。
    字符串常量对象是用双引号括起的字符序列。例如:“你好”、“12.97”、“boy" 等。
    字符串的字符使用 Unicode 编码,一个字符占两个字节(字母、汉字都是)。

  • 在前面介绍包装类时,很多类都实现了 Serializable 和 Comparable 接口。String 类同样也实现了这两个接口。那么,实现这两个接口的作用是什么呢?
    在这里插入图片描述
    (1)实现了 Serializable 接口,说明可以串行化 / 序列化,即:可以在网络上传输、保存到文件。
    (2)实现了 Comparable 接口,说明对象可以相互比较。

  • String类有很多构造器(构造器的重载),常用的有:

    String s1 = new String();
    String s2 = new String(String 初始化字符串);
    String s3 = new String(char[] a);
    String s4 = new String(char[] a,int startIndex,int count)
    String s5 = new String(byte[] b)
    
  • String 是 final 类,不能被其他的类继承。

  • String 类有属性 private final char value[],用于存放字符串内容,要注意:value 是 final 类型,不可以修改(指的是value的指向不能改变,但是单个字符可以改变)。

    final char[] value = {'a', 'b', 'c'};
    value[0] = 'A';//正确
    char[] value2 = {'c', 'd', 'e'};
    value = value2;//错误
    

2.2 String 创建过程及内存布局

创建 String 对象的两种方式:

方式一:直接赋值 String s = "hsp";
先从常量池查看是否有 “hsp” 。若有,直接指向;若没有,重新创建,然后指向。s 最终指向的是常量池的空间地址。

方式二:调用构造器 String s2 = new String("hsp");
先在堆中创建对象,对象内含 value 属性,用于指向常量池的 hsp 空间。若常量池没有 “hsp",重新创建;若有,直接通过 value 指向。s2 最终指向的是堆中的对象地址。

在这里插入图片描述

2.3 练习

在比较对象时,”==“ 比较是地址是否相等,即是否是同一个对象;equals 比较的是对象的内容是否相等。

【例1】

String a = "abc";
String b ="abc";
System.out.println(a.equals(b));//true
System.out.println(a==b);//true

【例2】

String a = "hsp";
String b =new String("hsp");
System.out.println(a.equals(b));//true
System.out.println(a==b);//false
System.out.println(a==b.intern());//true
System.out.println(b==b.intern());//false

注:对象 . intern() 时,会根据对象的内容去常量池中查找该内容的地址,并返回该地址。
【例3】

String s1 = "hspedu";
String s2 = "java";
String s4 = "java";
String s3 = new String("java");
System.out.println(s2 == s3);//false
System.out.println(s2 == s4);//true
System.out.println(s2.equals(s3));//true
System.out.println(s1 == s2);//false

【例4】

Person p1 = new Person();
p1.name = "hspedu";
Person p2 = new Person();
p2.name = "hspedu";
System.out.println(p1.name.equals(p2.name));//true
System.out.println(p1.name == p2.name);//true
System.out.println(p1.name == "hspedu");//true
String s1 = new String("bcde");
String s2 = new String("bcde");
System.out.println(s1==s2);//false

在这里插入图片描述

2.4 String 对象特性

【例1】画出内存

String s = "hello";
s = "haha";

在这里插入图片描述

【例2】下面的代码创建了几个对象?

String a = "hello"+"abc";

答:只创建了一个对象。编译器底层做了优化,会判断创建的常量池对象,是否有引用指向。所以不会将 “hello” 与 “abc” 放入常量池。上面的代码会被优化为String a = "helloabc;"

【例3】下面的代码创建了几个对象?画出内存图。

String a = "hello"; 
String b = "abc"; 
String c = a + b;

(1)先创建对象 StringBuilder sb = new StringBuilder()
(2)执行 sb.append(a);
(3)再执行 sb.append(b);
(4)最后 String c= sb.toString()
在 toString 源码中可以看到,最后返回的是一个新创建的字符串对象。
在这里插入图片描述
所以,内存结构图为:
在这里插入图片描述
【例4】分析下面代码的输出

String s1="hspedu";//s1指向池中的"hspedu"
String s2="java";//s2指向池中的"java”
String s5="hspedujava";//s5指向池中的“hspedujava"
String s6=(s1+s2).intern();//s6指向池中的 "hspedujava"
System.out.println(s5 == s6); //true
System.out.println(s5.equals(s6));//true

【例5】画出下列代码的内存结构图

public class Test1 {
    String str = new String("hsp");
    final char[] ch = {'j', 'a', 'v', 'a'};

    public void change(String str, char ch[]) {
        str = "java";
        ch[0] = 'h';
    }
    public static void main(String[] args){
        //在静态方法中访问本类的非静态成员,要先创建对象再访问
        //创建对象时,调用顺序:静态代码块和静态属性初始化,普通代码块和普通属性的初始化,调用构造方法
        //该类中只依次进行静态属性初始化、调用隐式构造方法(可不分析)
        //所以创建对象时,先为str的初始化创建对象,再初始化ch数组
        Test1 ex = new Test1();
        ex.change(ex.str, ex.ch);
        System.out.print(ex.str + " and ");
        System.out.println(ex.ch);
    }
}

在这里插入图片描述

2.5 String 类的常用方法

String类是保存字符串常量的。每次更新都需要重新开辟空间,效率较低,因此 java 设计者还提提供了 StringBuilder 和 StringBuffer 来增强 String 的功能,并提高效率(后面我们还会详细介绍 StringBuilder 和 StringBuffer)。

看一段代码:

String s = new String("");
for(int i = 0;i < 80000;i++) {
    s += "hello";
}

这段代码一直不停第地给 s 重新赋值,一直创建对象,效率低下。

(1) equals():区分大小写,判断内容是否相等。

String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));//false

(2) equalslgnoreCase():忽略大小写,判断内容是否相等。

boolean res = "john".equalsIgnoreCase("John");
System.out.println(res);//true

(3) length():获取字符串长度。(注意:获取数组长度用的是 arr.length

System.out.println("韩顺平".length());//3

(4) indexOf():获取字符或子串在字符串中第一次出现的位置索引(从0开始),若找不到,返回 -1。

String s1 = "wer@terwe@g";
System.out.println(s1.indexOf('@'));//3
System.out.println(s1.indexOf("we"));//0

(5) lastlndexOf():获取字符或子串在字符串中最后一次出现的位置索引(从0开始),若找不到,返回 -1。

String s2 = "wer@terwe@g@";
System.out.println(s2.lastIndexOf('@'));//11
System.out.println(s2.lastIndexOf("we"));//7

(6) substring():截取指定范围的子串。str.substring(n)从索引为 n 的字符开始,截取到最后;str.substring(n1,n2)从索引为 n1 的字符开始,截取到索引为 n2 -1 的字符。

String name="hello,张三";
System.out.println(name.substring(6));//输出“张三”
System.out.println(name.substring(2,5));//输出“llo”

(7) trim():去掉字符串前后的空白(空格、\t、\n)

String str = " \t  hello \n ";
System.out.println(str);
System.out.println(str.trim());

输出结果:

 	  hello 
 
hello

(8) charAt():获取某索引处的字符,注意不能使用 str[index] 这种方式.。

String str = "0123456";
System.out.println(str.charAt(4));//4

(9) toUpperCase():将字符串中的字母全部转变为大写

String str = "HeLlowOrld";
System.out.println(str.toUpperCase());//HELLOWORLD

(10) toLowerCase():将字符串中的字母全部转变为小写

String str = "HeLlowOrld";
System.out.println(str.toLowerCase());//helloworld

(11) concat():字符串拼接

String str1 = "hello ";
String str2 = "world";
System.out.println(str1.concat(str2).concat(" today"));
//输出:hello world today

(12) replace()s.replace(str1, str2) 将字符串 s 中的所有 str1 替换为 str2

String str = "jack tom jerry tom jerry";
System.out.println(str.replace("tom","smith"));
//输出:jack smith jerry smith jerry

(13) split():使用分割符对字符串进行分割,某些分割符需要转义,比如 “\” 等。
【例1】

String str = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
String[] strArr = str.split(",");
for (int i=0; i<strArr.length; i++){
    System.out.println(strArr[i]);
}

输出结果:

锄禾日当午
汗滴禾下土
谁知盘中餐
粒粒皆辛苦

【例2】

String filePath = "F:\\aaa\\bbb";
String[] fileArr = filePath.split("\\\\");
for (int i = 0; i < fileArr.length; i++) {
    System.out.println(fileArr[i]);
}

输出结果:

F:
aaa
bbb

(14) toCharArray():将字符串转成字符数组

String str = "hello";
char[] arr = str.toCharArray();
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

输出结果:

h
e
l
l
o

(15) compareTo(): 比较两个字符串的大小。若前者大,返回正数;若后者大,返回负数;若相等,返回 0。
细节:

  • 若两个字符串完全相同,就返回 0;
  • 若逐字符比较时,在某对字符处可以比较出两字符串的大小,就返回这对字符的差值。
  • 若比较到最后,发现前面字符都相同,就返回 str1.length()-str2.length()。
String str1 = "jack";
String str2 = "tom";
String str3 = "jackabc";
System.out.println(str1.compareTo(str2));//-10
System.out.println(str1.compareTo(str3));//-3

(16) format():将字符串格式化
%s、%d、%.nf、%c 等称为占位符,这些占位符会由后面变量来替换。其中,%.nf 可以被 float 或 double 类型来替换,且会四舍五入。

String name = "john";
int age = 10;
double score = 56.847;
char gender='男';
String info =
        "我的姓名是"+name+",年龄是"+age+",成绩是"+score+",性别是"+gender;
System.out.println("info = " + info);
String formatstr = "我的姓名是%s,年龄是%d,成绩是%.2f,性别是%c";
String info2 = String.format(formatstr, name, age, score, gender);
System.out.println("info2 = " + info2);

输出结果:

info = 我的姓名是john,年龄是10,成绩是56.847,性别是男
info2 = 我的姓名是john,年龄是10,成绩是56.85,性别是男

3. StringBuffer 类

3.1 StringBuffer 类基本介绍

StringBuffer 类的继承和实现关系:
在这里插入图片描述

  • StringBuffer 实现了 Serializable 接口,所以 StringBuffer 的对象是可串行化 / 序列化的,可以在网络上传输、保存到文件。
  • 父类 AbstractStringBuilder 中,有一个用于存放字符串内容的属性 char[] value,且不是 final 的。
  • StringBuffer 是一个 final 类,不能被继承。

3.2 String 与 StringBuffer 的对比

  • String 保存的是字符串常量,里面的值不能更改,每次 String 的更新实际上就是更改指向的地址(创建新对象),所以会产生很多废弃的对象,效率较低。(private final char value[] 的内容存放在常量池中)注:value 不可以更改指向,但可以更改所指位置的内容,但这个是源码层面的,程序员利用不了这个 “可更改内容” 的特点,所以只能当 String 是不可修改的。
  • StringBuffer 保存的是字符串变量,里面的值可以更改,每次 StringBuffer 的更新只更新内容,不更新地址(不创建新对象),效率较高。(char[] value 的内容存放放在堆中)例外的情况:只有 value 原来指向的位置不够用时,才会更改地址。
    在这里插入图片描述

3.3 StringBuffer 构造器

StringBuffer() :指定父类 AbstractStringBuilder 中的 value 字符数组大小为默认值16,内容为空。

StringBuffer stringBuffer = new StringBuffer();

指定父类 AbstractStringBuilder 中的 value 字符数组大小为100,内容为空。

StringBuffer stringBuffer1 = new StringBuffer(100);

指定父类 AbstractStringBuilder 中的 value 字符数组大小为 “指定的字符串长度”+16,内容为 “指定的字符串”。

StringBuffer stringBuffer2 = new StringBuffer("hello");

java.lang.StringBuffer 代表可变的字符序列,可以对字符串内容进行增删。
很多方法与 String 相同,但 StringBuffer 是可变长度的。
StringBuffer 是一个容器。

3.4 String 与 StringBuffer 的转换

(1) String ➡ StringBuffer

//方式1
String str = "hello tom";
StringBuffer stringBuffer = new StringBuffer(str);
//方式2
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1.append(str);

(2) StringBuffer ➡ String

StringBuffer stringBuffer2 = new StringBuffer("hello");
//方式1
String str1 = stringBuffer2.toString();
//方式2
String str2 = new String(stringBuffer2);

3.5 StringBuffer 类的常用方法

(1) append():追加

StringBuffer s = new StringBuffer("hello");
s.append(',');
s.append("jack").append(100).append(true).append(10.5);
System.out.println(s);//hello,jack100true10.5

(2) delete():删除 [start,end) 的字符

StringBuffer s1 = new StringBuffer("01234567");
s1.delete(2, 5);
System.out.println(s1);//01567

(3) replace():替换[start,end) 的字符

StringBuffer s2 = new StringBuffer("01234567");
s2.replace(2,5,"aaaa");
System.out.println(s2);//01aaaa567

(4) indexOf():查找指定的子串在字符串第一次出现的索引,如果找不到返回 -1

StringBuffer s3 = new StringBuffer("01234567");
System.out.println(s3.indexOf("456"));//4

(5) insert():在指定索引处插入字符串

StringBuffer s4 = new StringBuffer("01234567");
s4.insert(5, "aa");
System.out.println(s4);//01234aa567

(6) length():长度

StringBuffer s5 = new StringBuffer("01234567");
System.out.println(s5.length());//8

3.6 练习

【例1】下面代码输出什么

String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str);//把str直接当成”null“字符串追加到了sb的尾部
System.out.println(sb.length());//4
System.out.println(sb);//null

【例2】下面代码是否会抛出异常

String str = null;
StringBuffer sb1 = new StringBuffer(str);
System.out.println(sb1);

上面的代码会报空指针异常,原因是第二行在创建 StringBuffer 对象时,会将父类中的 value 字符数组的大小置为str.length()+16,而此时的 str 为空,所以报空指针异常。在 StringBuffer 类的构造器中可以看到:
在这里插入图片描述
【例3】给定一个数字,小数点前每 3 位用逗号隔开。

String str = "87123564.59";
StringBuffer stringBuffer = new StringBuffer(str);
for (int i = stringBuffer.indexOf(".")-3; i > 0 ; i-=3) {
    stringBuffer.insert(i, ",");
}
System.out.println(stringBuffer);//87,123,564.59

4. StringBuilder 类

4.1 StringBuilder 基本介绍

  • StringBuilder 用法与 StringBuffer 相同,但不保证同步(不是线程安全的),可以在单线程时用作 StringBuffer 的简易替换。如果可能,建议优先采用该类,因为它在大多数实现中比 StringBuffer 快。
  • StringBuilder 上的主要操作是 append 和 insert 方法,可以重载这些方法,来接受任意类型的数据。
  • StringBuilder 和 StringBuffer 均代表可变的字符序列,使用方法与 StringBuffer 相同。

StringBuilder 可以在一些场景下替换 StringBuffer 的原因是:两者的继承、实现关系相同:
在这里插入图片描述

4.2 StringBuilder 结构剖析

  • StringBuilder 继承 AbstractStringBuilder 类。
  • StringBuilder 实现了 Serializable 接口,说明 StringBuilder 对象是可串行化 / 序列化的(对象可以网络传输或保存到文件)
  • StringBuilder 是 final 类,不能被继承。
  • StringBuilder 对象字符序列仍存放在其父类 AbstractStringBuilder 的 char[] value中。因此,字符序列是存放在堆中的。
  • StringBuilder 的方法,没有做互斥处理,即没有 synchronized 关键字,因此只能在单线程的情况下使用。

4.3 String、StringBuffer、StringBuilder 的比较

  • String:不可变字符序列,效率低,但是复用率高(不同变量可以指向常量池中同一个字符串常量)。

  • StringBuffer:可变字符序列、效率较高(增删)、线程安全

  • StringBuilder:可变字符序列、效率最高、线程不安全(多个线程同时操作时,产生错误的结果)
    在这里插入图片描述

  • String 使用注意事项,先看一段代码:

    string s = "a";
    s += "b";
    

    上面代码中,先初始化 s 为 ”a“,之后又更新 s。这样 s 之前指向的字符串就被丢弃。如果是在循环中,就会导致大量废弃字符串对象占用空间,影响程序的性能。因此,如果要对 String 做大量修改,就不要使用 String。

  • 效率:StringBuilder > StringBuffer > String

String、StringBuffer和StringBuilder 的适用场景总结:

  • 如果字符串存在大量的修改操作,一般使用 StringBuffer 或 StringBuilder。
  • 如果字符串存在大量的修改操作,并在单线程的情况,使用 StringBuilder。
  • 如果字符串存在大量的修改操作,并在多线程的情况,使用 StringBuffer。
  • 如果我们字符串很少修改,被多个对象引用,使用 String,比如配置信息等(数据库的用户名、密码、id 等)。
  • StringBuilder 的方法使用和StringBuffer 一样,不再说。

5. Math类

5.1 基本介绍

Math 类有许多与数学运算有关的方法,如:指数、对数、平方根和三角函数。这些方法一般都是静态方法,直接使用即可。

5.2 方法一览(均为静态方法)

关于下列方法的返回类型,可以 Ctrl + B 定位到源码中去查看。

(1) abs(): 求绝对值。

int abs = Math.abs(-9);
System.out.println(abs);//9

(2).pow(): 求幂,返回 double 类型。

double pow = Math.pow(-2, 4);
System.out.println(pow);//16.0

(3) ceil():向上取整,返回 double 类型。

double ceil = Math.ceil(-3.0001);
System.out.println(ceil);//-3.0

(4) floor(): 向下取整,返回 double 类型。

double floor = Math.floor(-4.999);
System.out.println(floor);//-5.0

(5) round(): 四舍五入,返回 double 类型。相当于Math.floor(该参数+0.5)

long round = Math.round(-5.001);
System.out.println(round);//-5

(6) sqrt():开平方。

double sqrt = Math.sqrt(9);
System.out.println(sqrt);//3.0
System.out.println(Math.sqrt(-9));//NAN(not a number)

(7) random():生成随机数。Math.random()生成的是 [0, 1) 的随机数,生成 [a, b] 的随机整数:(int) (a + Math.random() * (b - a + 1))

int a = 2, b = 7;
for (int i = 0; i < 10; i++) {
    System.out.print((int) (a + Math.random() * (b - a + 1)) + " ");
}

输出结果:

2 2 6 4 2 7 4 2 4 7 

(8) max():求两个数的最大值。有重载的方法,可以求 int、float、double、long 类型的两个数的较大值。

int max = Math.max(2, 9);
System.out.println(max);//9

(9) min():求两个数的最小值。有重载的方法,可以求 int、float、double、long 类型的两个数的较小值。

int min = Math.min(2, 9);
System.out.println(min);//2

6. Arrays 类

6.1 Arrays 类的常用方法

Arrays 类包含一些静态方法,用于管理或操作数组(如:排序、搜索)。
(1) toString():返回数组的字符串形式

Integer[] arr = {1, 2, 3, 4, 5};
System.out.println(Arrays.toString(arr));//输出:[1, 2, 3, 4, 5]

为什么这么输出?看 Arrays.toString 方法的源码:
在这里插入图片描述
源码的含义是:将数组中的每个元素先转成字符串,再追加到一个 StringBuilder 对象中。在所有元素追加之前和之后,还追加了 “[”“]” 。最后将这个 StringBuilder 对象转成 String 返回。

(2) sort():排排序(默认排序和定制排序)

默认排序

Integer[] arr = {5, 1, 3, 2, 4};
//因为数组是引用类型,所以通过sort排序后,会直接影响到实参arr
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));//[1, 2, 3, 4, 5]

定制排序
sort 方法传入两个参数:① 待排序的数组 arr;② 实现了Comparator 接口的匿名内部类,该匿名内部类要实现 compare方法。

从小到大排序:

Integer[] arr = {5, 1, 3, 2, 4};
Arrays.sort(arr, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        Integer i1 = (Integer) o1;
        Integer i2 = (Integer) o2;
        return i1 - i2;
    }
});
System.out.println(Arrays.toString(arr));
//[1, 2, 3, 4, 5]

从大到小排序:将 compare 方法中的 return 语句改为return i2 - i1;

(3) binarySearch(): 在升序数组中,用二分查找的方式查找指定元素,并返回索引;若元素不存在,返回 -(low + 1)low 是该元素按照大小顺序应该所在的位置。

Integer[] arr = {1, 2, 3, 4, 5};
System.out.println(Arrays.binarySearch(arr, 4));//3
System.out.println(Arrays.binarySearch(arr, 100));//-6=-(5+1)

(4) copyOf():数组元素的复制
newArr = Arrays.copyOf(arr, n)表示从 arr 数组中,拷贝 n 个元素到 newArr 数组中。
若 n > arr.length,就在新数组后面补 null;
若 n < 0,就抛出异常 “NegativeArraySizeException”。

该方法的底层使用的是 System.arraycopy()。

Integer[] arr = {1, 2, 3, 4, 5};
Integer[] newArr1 = Arrays.copyOf(arr, arr.length);
System.out.println(Arrays.toString(newArr1));
Integer[] newArr2 = Arrays.copyOf(arr, arr.length-1);
System.out.println(Arrays.toString(newArr2));
Integer[] newArr3 = Arrays.copyOf(arr, arr.length+1);
System.out.println(Arrays.toString(newArr3));

输出结果:

[1, 2, 3, 4, 5]
[1, 2, 3, 4]
[1, 2, 3, 4, 5, null]

拷贝元素的个数 < 0 时,抛出 NegativeArraySizeException 异常:

Integer[] arr = {1, 2, 3, 4, 5};
Integer[] newArr4 = Arrays.copyOf(arr, -1);
System.out.println(Arrays.toString(newArr4));

在这里插入图片描述

(5) fill():数组元素的填充,可以理解为:替换原来的所有元素

Integer[] arr = new Integer[]{9, 3, 2};
Arrays.fill(arr, 99);
System.out.println(Arrays.toString(arr));
//输出:[99, 99, 99]

(6) equals():比较两个数组元素内容是否完全一致,完全一致返回 true;否则返回false。

Integer[] arr1 = {1, 2, 3, 4, 5};
Integer[] arr2 = {1, 2, 3, 4, 5};
System.out.println(Arrays.equals(arr1, arr2));//true

(7) asList():将一组值,转换成 list。

下面代码中,asList 方法会将(2, 3, 4, 5, 6, 1)数据转成一个 List 集合。
返回的 asList 编译类型为 List(一个接口),该接口下有很多实现子类,asList 运行类型就是其中一个实现子类 ArrayList(Arrays 类中的一个静态内部类)。

List asList = Arrays.asList(2,3,4,5,6,1);
System.out.println("asList="+ asList);
System.out.println("asList的运行类型:"+asList.getClass());

输出结果:

asList=[2, 3, 4, 5, 6, 1]
asList的运行类型:class java.util.Arrays$ArrayList

6.2 练习

自定义 Book 类,里面包含 name 和 price 属性。有 4 个书对象,按 price 降序排序。

public class Exercise {
    public static void main(String[] args) {
        Book[] books = new Book[4];
        books[0] = new Book("红楼梦",100);
        books[1] = new Book("金瓶梅",90);
        books[2] = new Book("青年文摘",5);
        books[3] = new Book("Java从入门到放弃",300);
        Arrays.sort(books, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Book book1 = (Book) o1;
                Book book2 = (Book) o2;
                //方法返回值已经固定为int,按原来的思路直接返回book1.getPrice() - book2.getPrice()不可行,因为这是double值
               	//可以转换思路,先用一个double变量接收该double值,根据double值的正负返回一个正整数、负整数或0
                double priceDiff = book1.getPrice() - book2.getPrice();
                //如果发现输出顺序与预期相反,调整返回的-1与1的顺序就行
                if(priceDiff > 0){
                    return -1;//只要是负数就行
                }else if(priceDiff < 0){
                    return 1;//只要是正数就行
                }else {
                    return 0;
                }
            }
        });
        System.out.println(Arrays.toString(books));
    }
}
class Book{
    private String name;
    private double price;

    public Book(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

输出结果:

[Book{name='Java从入门到放弃', price=300.0}, Book{name='红楼梦', price=100.0}, Book{name='金瓶梅', price=90.0}, Book{name='青年文摘', price=5.0}]

7. System 类

(1) exit():一般就用 exit(0) 表示程序退出。0表示一个状态,正常的状态。

System.out.println("ok1");
System.exit(0);
System.out.println("ok2");

输出结果:

ok1

(2) arraycopy():复制数组元素,比较适合底层调用。我们一般使用 Arrays.copyOf 完成复制数组(底层用的就是 System.arraycopy())。
用法:System.arraycopy(源数组, 源数组起始位置, 目标数组, 目标数组起始位置, 待拷贝的元素个数),如果拷贝的元素个数过多,就报数组越界异常。

int[] src={1,2,3};
int[] dest = new int[3];//初始时为{0, 0, 0}
System.arraycopy(src, 0, dest, 1, 2);
System.out.println(Arrays.toString(dest));

输出结果:

[0, 1, 2]

(3) currentTimeMillens():返回当前时间距离 1970 年 1 月 1 日 0 时的毫秒数。

System.out.println(System.currentTimeMillis());//1665405672194

(4) gc():运行垃圾回收机制 System.gc();

8. Biglnteger 和 BigDecimal 类

8.1 Biglnteger 类

当需要处理很大的整数时,long 会不够用:
在这里插入图片描述
这时,可以用 Biglnteger 类来解决(无论多大的数都可以)。

BigInteger bigInteger = new BigInteger("10000000000000000000");

BigInteger 不能直接加减乘除,要使用对应的方法。

BigInteger bigInteger1 = new BigInteger("10000000000000000000");
BigInteger bigInteger2 = new BigInteger("100");
System.out.println(bigInteger1.add(bigInteger2));
System.out.println(bigInteger1.subtract(bigInteger2));
System.out.println(bigInteger1.multiply(bigInteger2));
System.out.println(bigInteger1.divide(bigInteger2));

输出结果:

10000000000000000100
9999999999999999900
1000000000000000000000
100000000000000000

BigInteger 底层处理的是字符串,处理完之后再转成 BigInteger。

8.2 BigDecimal 类

当小数要求位数较多时,double 就不能满足需求了:

double num = 12.12345628746872354395483d;
System.out.println(num);

输出结果:

12.123456287468724

这时可以用 BigDecimal 类来解决。

BigDecimal bigDecimal = new BigDecimal("12.12345628746872354395483");
System.out.println(bigDecimal);

输出结果:

12.12345628746872354395483

BigDecimal 也不能直接加减乘除,要使用对应的方法。

BigDecimal bigDecimal1 = new BigDecimal("12.12345628746872354395483");
BigDecimal bigDecimal2 = new BigDecimal("2");

System.out.println(bigDecimal1.add(bigDecimal2));
System.out.println(bigDecimal1.subtract(bigDecimal2));
System.out.println(bigDecimal1.multiply(bigDecimal2));
//除法直接这样写,可能会撇出异常ArithmeticException
System.out.println(bigDecimal1.divide(bigDecimal2));

输出结果:

14.12345628746872354395483
10.12345628746872354395483
24.24691257493744708790966
6.061728143734361771977415

上面代码中,除法可能会抛出异常。原因是:可能出现除不尽的情况。若除数换成 3 ,就会抛出异常。解决办法是:在 divide 方法的第二个参数处指定精度 BigDecimal.ROUND_CEILING。这样的话,如果有除不尽的,就会保留分子的精度(小数位数)。

BigDecimal bigDecimal1 = new BigDecimal("12.12345628746872354395483");
BigDecimal bigDecimal2 = new BigDecimal("3");
System.out.println(bigDecimal1.divide(bigDecimal2, BigDecimal.ROUND_CEILING));

输出结果:

4.04115209582290784798495
//小数点后的位数与bigDecimal1相等

BigDecimal 底层处理的是字符串,处理完之后再转成 BigDecimal。

9. 日期类

9.1 题外话:idea 中类的结构图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

9.2 第一代日期类

(1) 获取当前系统时间

//不要引入 java.sql.Date,要引入 java.util.Date
Date d1 = new Date();
System.out.println("当前日期:" + d1);

输出结果:

当前日期:Mon Oct 10 22:12:37 CST 2022

默认输出的日期格式是国外的方式,因此通常需要对格式进行转换:

Date d1 = new Date();
SimpleDateFormat sdf= new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format =sdf.format(d1);//format:将日期转换成指定格式的字符串
System.out.println("当前日期:"+format);

输出结果:

当前日期:2022101010:15:58 星期一

(2) 通过指定毫秒数得到时间

Date d2 = new Date(9234567);
System.out.println("d2:" + d2);

输出结果:

d2=Thu Jan 01 10:33:54 CST 1970

(3) 把一个格式化的 String 转成对应的 Date

String s ="1996年01月01日 10:20:30 星期一";
//在把String->Date时,使用的sdf格式需要和你给的string的格式一样,否则会抛出转换异常
SimpleDateFormat sdf= new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
Date parse = sdf.parse(s);
System.out.println("parse:"+ parse);

输出结果:

parse:Mon Jan 01 10:20:30 CST 1996

9.3 第二代日期类

第二代日期类,主要就是 Calendar 类(日历)。继承和实现关系如下图:
在这里插入图片描述

  • Calendar 类是一个抽象类

  • Calendar 类的构造器是 protected,可以通过 getInstance()来获取实例(是Calendar子类的实例吗,Calendar 作为一个抽象类不能直接实例化啊)

    Calendar c = Calendar.getInstance();
    System.out.println(c);
    

    输出结果:

    java.util.GregorianCalendar[time=1667034489446,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai"...
    
  • Calendar 类提供了大量的方法和字段,但没有专门的格式化方法,所以需要自行组合显示。

    Calendar c = Calendar.getInstance();
    System.out.println("年:"+c.get(Calendar.YEAR));
    //月份从0开始
    System.out.println("月:"+(c.get(Calendar.MONTH)+1));
    System.out.println("日:"+ c.get(Calendar.DAY_OF_MONTH));
    //HOUR_OF_DAY是24进制,HOUR是12进制
    System.out.println("小时:"+c.get(Calendar.HOUR_OF_DAY));
    System.out.println("分钟:"+c.get(Calendar.MINUTE));
    System.out.println("秒:"+c.get(Calendar.SECOND));
    System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH));
    

    输出结果:

    :2022:10:29
    小时:17
    分钟:20
    秒:36
    2022-10-29
    

9.4 第三代日期类

9.4.1 前两代日期类的不足

JDK 1.0 中包含了一个 java.util.Date 类,但是它的大多数方法已经在 JDK1.1 引入 Calendar 类之后被弃用了。而 Calendar 也存在问题:
(1) 可变性:日期和时间这样的类应该是不可变的,但 Calendar 是可变的。
(2) 偏移性:月份都从 0 开始,比较奇怪。
(3) 格式化:没有用于格式化的方法。
(4) 不是线程安全的;不能处理闰秒等(每隔2天,多出1s)。

9.4.2 第三代日期类常见方法(JDK8 引入)

**(1) LocalDate、LocalTime、LocalDateTime **

LocalDate:获取日期字段,只包含年月日
LocalTime:获取时间字段,只包含时分秒
LocalDateTime:获取日期+时间

LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
System.out.println("年:"+ldt.getYear());
System.out.println("月:"+ldt.getMonth());//输出月份的英文
System.out.println("月:"+ldt.getMonthValue());//输出月份的值
System.out.println("日:"+ldt.getDayOfMonth());
System.out.println("时:"+ldt.getHour());
System.out.println("分:"+ldt.getMinute());
System.out.println("秒:"+ldt.getSecond());

输出结果:

2022-10-29T17:52:42.107
年:2022
月:OCTOBER
月:10
日:29
时:17
分:52
秒:42

LocalDate 与 LocalTime 的使用与 LocalDateTime 相同,方法调用方式也一样。

LocalDate ld = LocalDate.now();
LocalTime lt = LocalTime.now();

(2) DateTimeFormatter 格式日期类

LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
String strDate = dtf.format(ldt);
System.out.println(strDate);

DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String strDate2 = dtf2.format(ldt);
System.out.println(strDate2);

输出结果:

202210291847592022-10-29 18:47:59

(3) Instant 时间戳
Instant 时间戳与 Date 类转换的方式:

//通过静态方法now()获取当前的时间戳对象
Instant now = Instant.now();
System.out.println(now);
//Instant → Date
Date date = Date.from(now);
System.out.println(date);
//date → Instant
Instant instant = date.toInstant();
System.out.println(instant);

输出结果:

2022-10-29T10:57:23.719Z
Sat Oct 29 18:57:23 CST 2022
2022-10-29T10:57:23.719Z

(4) plus / minus 增加 / 减少日期的某个部分

LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

LocalDateTime plusLdt = ldt.plusDays(890);
System.out.println("890天后:"+dtf.format(plusLdt));

LocalDateTime minusLdt = ldt.minusMinutes(3456);
System.out.println("3456分钟前:"+dtf.format(minusLdt));

输出结果:

890天后:2025-04-06 19:09:55
3456分钟前:2022-10-27 09:33:55

10. 本章练习

【例1】将字符串中的指定部分进行反转。比如:将 ”abcdef“ 反转为 “aedcbf“。
通过编写方法public static String reverse(String str,int start,int end)实现。

public class StrReverse {
    public static void main(String[] args) {
		String str = "abcdef";
		str = reverse(str, 1, 4);
		System.out.println(str);
    }
    public static String reverse(String str, int start, int end){
    	//String转字符数组
        char[] charArr = str.toCharArray();
        for (int i = start, j = end; i < j; i++, j--) {
            char temp = charArr[i];
            charArr[i] = charArr[j];
            charArr[j] = temp;
        }
        //字符数组转String
        return new String(charArr);
    }
}

输出结果:

aedcbf

增加异常处理机制后的代码:

public class StrReverse {
    public static void main(String[] args) {
        String str = "abcdef";
        try {
            str = reverse(str, 1, 4);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return;
        }
        System.out.println(str);
    }
    public static String reverse(String str, int start, int end){
        if (!(str!=null && start >= 0 && start < end && end < str.length())){
            throw new RuntimeException("参数不正确");
        }
        //如果上面抛出异常,下面的代码就不再执行
        char[] charArr = str.toCharArray();
        for (int i = start, j = end; i < j; i++, j--) {
            char temp = charArr[i];
            charArr[i] = charArr[j];
            charArr[j] = temp;
        }
        return new String(charArr);
    }
}

【例2】输入用户名、密码、邮箱,如果信息录入正确,则提示注册成功,否则生成异常对象。要求:
(1)用户名长度为 2 或 3 或 4
(2)密码的长度为 6,要求全是数字
(3)邮箱中包含 @ 和 . 并且 @ 在 . 的前面

public class Register {
    public static void main(String[] args) {
        boolean res = false;
        try {
            //该句出现异常时,try中的剩余语句不再执行
            regs("tom", "123456", "111@qq.com");
            System.out.println("注册成功");
        } catch (Exception e) {
            System.out.println(e.getMessage());//输出错误信息
        }
    }
    public static void regs(String userName, String pwd, String email){
        //过关斩将法
        if (userName==null || pwd==null || email==null){
            throw new RuntimeException("信息不能为空");
        }
        if (!(userName.length()>=2 && userName.length()<=4)){
            throw new RuntimeException("用户名不正确");
        }
        if (!(pwd.length() == 6 && isDigit(pwd))){
            throw new RuntimeException("密码不正确");
        }
        int atIndex = email.indexOf('@');
        int dotIndex = email.indexOf('.');
        if (!(atIndex!=-1 && dotIndex!=-1 && atIndex<dotIndex)){
            throw new RuntimeException("邮箱不正确");
        }
    }

    private static boolean isDigit(String pwd) {
        char[] charArray = pwd.toCharArray();
        for (int i = 0; i < pwd.length(); i++) {
            if (charArray[i]<'0' || charArray[i]>'9'){
                return false;
            }
        }
        return true;
    }
}

【例3】编写 Java 程序,输入形式为:Willian Jefferson Clinton 的人名,以 Clinton,Willian.J 的形式打印出来。其中 .J 是中间单词的首字母。

public class OptName {
    public static void main(String[] args) {
        String str = "Willian Jefferson Clinton";
        try {
            printName(str);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    private static void printName(String str) {
        if (str == null){
            throw new RuntimeException("名字不能为空");
        }
        String[] strArr = str.split(" ");
        if (strArr.length != 3){
            throw new RuntimeException("名字应为三个单词");
        }
        String format = String.format("%s,%s.%c", strArr[2], strArr[0], strArr[1].toUpperCase().charAt(0));
        System.out.println(format);
    }
}

【例4】输入字符串,判断里面有多少个大写字母,多少个小写字母,多少个数字。

public class Homework4 {
    public static void main(String[] args) {
        String str = null;
        try {
            printNum(str);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    private static void printNum(String str) {
        if (str == null){
            throw new RuntimeException("字符串不能为null");
        }
        int upperCount = 0;
        int lowerCount = 0;
        int digitCount = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i)>='A' && str.charAt(i)<='Z'){
                upperCount++;
            }else if (str.charAt(i)>='a' && str.charAt(i)<='z'){
                lowerCount++;
            }else if (str.charAt(i)>='0' && str.charAt(i)<='9'){
                digitCount++;
            }
        }
        System.out.println(upperCount);
        System.out.println(lowerCount);
        System.out.println(digitCount);
    }
}

小结 String 的遍历:

  • 只是单纯地遍历:
    (1)先用 str.toCharArray() 将 String 转成字符数组,再遍历字符数组;
    (2)用 str.charAt(i)
  • 遍历并作出改动:只能用(1)
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值