【Java基础】集合(Collection、List、Set)

集合分类

  1. 单列集合(Collection)。元素是一个一个的。
  2. 双列集合(Map)。元素是一对一的。

单列集合(Collection)

Collection集合体系

  1. 子接口List
    1. 实现类ArrayList
    2. 实现类LinkedList
  2. 子接口Set。
    1. 实现类HashSet
      1. LinkedHashSet
    2. 实现类TreeSet

学习时要掌握的:

  1. 有什么特点?
  2. 是否有特有功能?
  3. 适合什么业务场景?

Collection集合特点

List系列

添加的元素序、重复、索引。

  1. ArrayList、LinkedList有序、可重复、有索引。

Set系列

添加的元素序、重复、索引。

  1. HashSet。无序、不重复、无索引。
  2. LinkedHashSet。序、不重复、无索引。
  3. TreeSet。按照大小默认升序排序、不重复、无索引。

Collection常用方法

为什么要学?

  • 因为Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的。

常用方法:

  1. public boolean add(E e)。把给定的对象添加到当前集合中 。由于允许数据重复,所以一定返回true。
  2. public void clear() 。清空集合中所有的元素。
  3. public boolean remove(E e)。把给定的对象在当前集合中删除。如果有多个重复元素只能删除第一个
  4. public boolean contains(Object obj)。判断当前集合中是否包含给定的对象。是精确匹配。
  5. public boolean isEmpty()。判断当前集合是否为空。
  6. public int size()。返回集合中元素的个数。
  7. public Object[] toArray()。把集合中的元素,存储到数组中。Object类型是为了兼容各种类型的数据。
  8. addAll(Collection<? extends String> c)。把另一个集合中的全部数据倒入,数据类型要一样。

Collection遍历方式

1. 迭代器

迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator。

Collection集合获取迭代器的方法:

  • **Iterator iterator()**。返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素。
    1. 从集合对象中获取迭代器对象。
    2. 判断当前位置是否有元素可以获取。不判断的话可能出现NoSuchElementException异常。
    3. 获取当前位置的元素,然后自动指向下一个元素。
1
2
3
4
5
6
7
8
9
10
11
Collection<String> c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");
System.out.println(c); //[赵敏, 小昭, 素素, 灭绝]
Iterator<String> it = c.iterator();
while(it.hasNext()){
String e = it.next();
System.out.println(s);
} //判断一次,取一次

Iterator迭代器中的常用方法:

  • boolean hasNext()。询问当前位置是否有元素存在,存在返回true ,不存在返回false。
  • E next()。获取当前位置的元素,并同时将迭代器对象指向下一个元素处。

2. 增强for

为什么用增强的?因为Collection中没有规定集合的索引,只有List集合才支持索引。

  • 格式:for (元素的数据类型 变量名 : 数组或者集合) { }。变量名相当于游标。(类似于Python中的for img in imgs这种写法。)
  • 可以遍历集合或者数组。
  • 遍历集合本质是迭代器遍历集合的简化写法。
  • 简化写法:数组或集合**.for再回车**。

3. lambda表达式

得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的方式来遍历集合。

方法:default void forEach(Consumer<? super T> action) 。结合lambda遍历集合。

举例:

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
Collection<String> c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");

//调用forEach方法
//由于参数是一个Consumer接口,所以可以传递匿名内部类
c.forEach(new Consumer<String>{
@Override
public void accept(String s){
System.out.println(s);
}
});

//也可以使用lambda表达式对匿名内部类进行简化
c.forEach((String s) ->
System.out.println(s);
});

c.forEach(s ->
System.out.println(s);
);

c.forEach(s -> System.out.println(s));

c.forEach(System.out::println); //[赵敏, 小昭, 素素, 灭绝]

lambda表达式遍历Collection集合不算简洁,之后遍历Map才是简洁。

(一)List集合

ArrayList、LinkedList有序、可重复、有索引。但是底层数据结构不同(数据结构:存储、组织数据的方式),应用场景也不同。继承了Collection的功能。

创建(List是接口,所以需要指定具体的类):

1
List<String> list = new ArrayList<>();//经典代码,多态

常用方法

List集合因为支持索引,所以多了很多索引相关的方法:

  1. void add(int index,E element)。在此集合中的指定位置插入指定的元素。不写索引的话,默认是插入到最后。
  2. E remove(int index)。删除指定索引处的元素,返回被删除的元素
  3. E set(int index,E element)。修改指定索引处的元素,返回被修改的元素
  4. E get(int index)。返回指定索引处的元素。

List集合支持的遍历方式

  1. 普通for循环(只因为List有索引)。集合名.fori自动完成。
  2. 增强for/foreach遍历。集合名.for自动完成。
  3. 迭代器。先创建迭代器,再判断并遍历。
  4. Lambda表达式。forEach方法。
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
List<String> list = new ArrayList<>();
list.add("蜘蛛精");
list.add("至尊宝");
list.add("糖宝宝");

//1.普通for循环
for(int i = 0; i< list.size(); i++){
//i = 0, 1, 2
String e = list.get(i);
System.out.println(e);
}

//2.增强for遍历
for(String s : list){
System.out.println(s);
}

//3.迭代器遍历
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}

//4.lambda表达式遍历
list.forEach(s->System.out.println(s));

(1)ArrayList集合的底层原理

基于数组实现的。

特点(查询快、增删慢):

  1. 查询速度快(根据索引查询数据快)。查询数据通过地址值和索引定位,查询任意数据耗时相同。
  2. 删除效率低。可能需要把后面很多的数据进行前移。
  3. 添加效率极低。可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容。

底层原理:

  1. 利用无参构造器创建的集合,会在底层创建一个默认查高难度为0的数组。
  2. 添加第一个元素时,底层会创建一个新的长度为10的数组。
  3. 存满时(添加第11个元素时),扩容1.5倍。
  4. 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准。

应用场景:

  1. 适合:根据索引查询数据,比如根据随机索引取数据(高效),或者数据量不是很大时。
  2. 不适合:数据量大的同时,又要频繁地进行增删操作。

(2)LinkedList集合的底层原理

基于双链表实现的(Java中大多数情况都使用双向链表)。

什么是链表?

  • 链表结构是由一个一个的节点组成,一个节点由数据值、下一个元素的地址组成。
  • 链表中的结点是独立的对象,在内存中是不连续的,每个节点包含数据值和下一个节点的地址。
  • 特点1:查询慢,无论查询哪个数据都要从头开始。
  • 特点2:增删相对较快。但是对于首尾元素进行增删改查的速度是极快的。

LinkedList新增了一些可以针对头尾进行操作的方法:

  1. public void addFirst(E e)。在该列表开头插入指定的元素。
  2. public void addLast(E e)。将指定的元素追加到此列表的末尾。
  3. public E getFirst()。返回此列表中的第一个元素。
  4. public E getLast()。返回此列表中的最后一个元素。
  5. public E removeFirst()。从此列表中删除并返回第一个元素。
  6. public E removeLast()。从此列表中删除并返回最后一个元素。

应用场景:

  1. 设计队列(特点:先进先出,后进后出)。队列只是在首尾增删元素。
    1. 入队。addLast方法。
    2. 出队。removeFirst方法。
  2. 设计栈(特点:先进后出,后进先出)。
    1. 进栈(压栈)。push方法(其实就是调用的addFirst方法)。
    2. 出站(弹栈)。pop方法(其实就是调用的removeFirst方法)。

(二)Set集合

添加的元素序、重复、索引。

  1. HashSet。无序、不重复、无索引。
  2. LinkedHashSet。序、不重复、无索引。(是HashSet的子孙类。)
  3. TreeSet。按照大小默认升序排序、不重复、无索引。

创建(Set是接口,所以需要指定具体的类):

1
2
3
4
//一行经典代码
Set<Integer> set = new HashSet<>(); //无序、无索引、不重复(无序是指和添加元素的顺序无关)
Set<Integer> set = new LinkedHashSet<>(); //有序、无索引、不重复(有序是指和添加元素的顺序有关)
Set<Integer> set = new TreeSet<>(); //可排序(升序)、无索引、不重复

常用方法

要用的常用方法,基本上都是其父类Collection提供的。

(1)HashSet集合

需要掌握的:

  1. 为什么添加的元素无序、不重复、无索引?
  2. 增删改查数据有什么特点,适合什么场景?

哈希值:

  • 一个int类型的数值,Java中每个对象都有一个哈希值。
  • Java中的所有对象,都可以调用Object类提供的hashCode方法,返回该对象自己的哈希值。

对象哈希值的特点:

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
  • 不同的对象,哈希值一般不同,但也有可能相同(哈希碰撞)。

HashSet集合的底层原理

  • 基于哈希表实现。
  • 是一种增删改查性能都较好的数据结构。
  • JDK8以前:哈希表 = 数组+链表
  • JDK8以后:哈希表 = 数组+链表+红黑树

JDK8以前HashSet集合的底层原理:

  1. 创建一个默认长度为16的数组,默认加载因子为0.75,数组名table;
  2. 使用元素的哈希值对数组的长度求余计算出应存入的位置;
  3. 判断当前位置是否为null,如果是null直接存入;
  4. 如果不为null,表示有元素,则调用equals方法比较相等,则不存;不相等,则存入数据。
    • JDK8以前,新元素存入数组,占用老元素的位置,老元素挂下面;
    • JDK8以后,新元素挂在老元素下面。

往HashSet集合中存储元素时,底层调用了元素的两个方法:一个是hashCode方法获取元素的hashCode值(哈希值);另一个是调用了元素的equals方法,用来比较新添加的元素和集合中已有的元素是否相同。

  • 只有新添加元素的hashCode值和集合中以后元素的hashCode值相同、新添加的元素调用equals方法和集合中已有元素比较结果为true, 才认为元素重复。
  • 如果hashCode值相同,equals比较不同,则以链表的形式连接在数组的同一个索引为位置。–>导致的问题:如果数组快占满了,链表过长,导致查询性能降低。–>哈希表的扩容机制(占满加载因子0.75*长度就会扩容,扩容成原来数组的两倍)。

在JDK8以后的优化:

  • 当链表的长度超过8,且数组长度超过64时,就会自动把链表转换为红黑树。

深入理解HashSet集合去重的机制

注意:HashSet集合默认不能对内容一样的两个不同对象去重复!(因为不同对象哈希值不一样,因此存储位置不同,就会被认为不是重复的。)

如何让HashSet集合能够实现对内容一样的两个不同对象也能去重复???

  • 如果希望Set集合认为2个内容一样的对象是重复的,
    必须重写对象的hashCode()和equals()方法。
  • 在类中**右键-generate-euqals() and hashCode()**,一路next。这里euqals方法只要两个对象内容一样就返回true,hashCode方法只要两个对象内容一样,返回的哈希值就是一样的。

(2)LinkedHashSet集合

  • 有序、不重复、无索引。
  • 基于哈希表(数组+链表+红黑树)实现的。
  • 每个元素都额外的多了一个双链表的机制记录它前后元素的位置。增删改查比较快,但是也更占内存。

(3)TreeSet集合

  • 排序、不重复、无索引。
  • 基于红黑树实现的排序。增删改查性能较好。
  • 对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。
  • 对于字符串类型:默认按照首字符的编号升序排序。
  • 对于自定义类型如Student对象,TreeSet默认是无法直接排序的。

自定义排序规则

  • TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定比较规则。

排序方式1:让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则。但是要注意,如果值相等,就认为是重复的,只会保存一个对象!

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
//第一步:先让Student类,实现Comparable接口
//注意:Student类的对象是作为TreeSet集合的元素的
public class Student implements Comparable<Student>{
private String name;
private int age;
private double height;
//无参数构造方法
public Student(){}
//全参数构造方法
public Student(String name, int age, double height){
this.name=name;
this.age=age;
this.height=height;
}
//...get、set、toString()方法自己补上..

//第二步:重写compareTo方法
//按照年龄进行比较,只需要在方法中让this.age和o.age相减就可以。
/*
原理:
在往TreeSet集合中添加元素时,add方法底层会调用compareTo方法,根据该方法的
结果是正数、负数、还是零,决定元素放在后面、前面还是不存。
*/
@Override
public int compareTo(Student o) {
//this:表示将要添加进去的Student对象
//o: 表示集合中已有的Student对象
return this.age-o.age;
}
}

排序方式2:通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则)。TreeSet自动选择自己自带的比较器对象进行排序。

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
//创建TreeSet集合时,传递比较器对象排序
/*
原理:当调用add方法时,底层会先用比较器,根据Comparator的compare方是正数、负数、还是零,决定谁在后,谁在前,谁不存。
*/
//下面代码中是按照学生的年龄升序排序
Set<Student> students = new TreeSet<>(new Comparator<Student>{
@Override
public int compare(Student o1, Student o2){
//需求:按照学生的身高排序
return Double.compare(o1,o2);
}
});

//可以用lambda表达式简化
//Set<Student> students = new TreeSet<>(o1,o2)->Double.compare(o1.getHeight(), o2.getHeight()));

//创建4个Student对象
Student s1 = new Student("至尊宝",20, 169.6);
Student s2 = new Student("紫霞",23, 169.8);
Student s3 = new Student("蜘蛛精",23, 169.6);
Student s4 = new Student("牛魔王",48, 169.6);

//添加Studnet对象到集合
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
System.out.println(students);

总结

  1. 如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据
    • ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
  2. 如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
    • LinkedList集合(有序、可重复、有索引),底层基于双链表实现的。
  3. 如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快
    • HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。 (常用)
  4. 如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
    • LinkedHashSet集合(有序,不重复,无索引), 底层基于哈希表和双链表。
  5. 如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快
    • TreeSet集合,基于红黑树实现。

注意事项:集合的并发修改异常问题

集合的并发修改异常

并发:多件事正在进行。

  • 使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
    • 原因:其实和之前用for循环的时候差不多,索引没有减1的话会导致有些元素没被删掉,但是迭代器知道程序员有可能出现这个错误,所以报了一个善意的提醒。
    • 解决方法:不能用集合对象自己去删除数据。使用迭代器的remove方法。(现在一共学了3种方法:1. 每次减1,2. 倒着遍历,3. 用迭代器去删。)
  • 由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误。
    • 原因:增强for循环相当于迭代器简化写法,但是又拿不到迭代器。类似地,使用lambda表达式也会出现这个问题,因为其内部使用的是增强for循环。

怎么保证遍历集合同时删除数据时不出bug?

  • 使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。
  • 如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i–操作。

Collection集合其他知识

可变参数

就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型…参数名称

可变参数的特点和好处

  • 特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它。
  • 好处:常常用来灵活的接收数据。

可变参数的注意事项

  • 可变参数在方法内部就是一个数组
  • 一个形参列表中可变参数只能有一个
  • 可变参数必须放在形参列表的最后面

Collections

一个用来操作集合的工具类。

提供的静态方法

  1. public static boolean addAll(Collection<? super T> c, T… elements)。给集合批量添加元素。
  2. public static void shuffle(List<?> list) 。打乱List集合中的元素顺序。
  3. public static void sort(List list)。对List集合中的元素进行升序排序。
  4. public static void sort(List list,Comparator<? super T> c)。对List集合中元素,按照比较器对象指定的规则进行排序。

后两个是排序方法,3可以直接对自定义类型的List集合排序,但自定义类型必须实现了Comlarable接口,指定了比较规则才可以。

综合案例

斗地主。

【Java基础】面向对象进阶:static、继承、单例

静态(static)

修饰成员变量或成员方法。

static修饰成员变量

按照有无static修饰,成员变量分为:

  1. 有–>类变量(静态变量)。属于类,在计算机中只有一份,被类的全部对象共享。通过类名调用:类名.静态变量
  2. 无–>实例变量。属于每个对象。

static修饰成员方法

【Java基础】lambda表达式、方法引用、算法、正则表达式

Arrays类(了解)

操作数组的一个工具类。

常见方法:

  1. public static String toString(类型[] arr)。可以接收任意类型的数据,返回数组的内容。
  2. public static 类型[] copyOfRange(类型[] arr, 起始索引, 结束索引)。拷贝数组(指定范围,包前不包后)。
  3. public static copyOf(类型[] arr, int newLength)。拷贝数组。通常用作数组扩容。
  4. public static setAll(double[] array, IntToDoubleFunction generator)。把数组中的原数据改为新数据。注意 IntToDoubleFunction generator程序可以自动帮忙重写,重写时的参数value就是数组的索引。
  5. public static void sort(类型[] arr)。对数组进行排序(默认是升序排序)。
    1. 如果数组是对象数组。直接调用sort会报错。需要指定排序规则。
    2. 方式1:让该对象的类实现Comparable(比较规则)接口,然后重写compareTo方法,自己来制定比较规则。官方约定:①左边对象大于右边对象,返回正整数,②小于,负整数,③等于,返回0。可以用if+else语句,或者直接返回this.x-0.x(前提是int类型)。
    3. 方式2:使用下面这个sort方法,创建Comparator比较器接口的匿名内部类对象,然后自己制定比较规则。public static void sort(T[] arr, Comparator<? super T> c) :对数组进行排序(支持自定义排序规则)。
      1. 接收参数1:需要排序的数组;
      2. 接受参数2:Comparator比较器对象(用来指定对象的比较规则)。程序可以自动帮忙写。对象为o1、o2。约定和实现方法方式1中一样。补充一点,如果是Double类型的比较,可以返回Double.compare(o1.getxx(), o2.getxx())。

JDK8新特性:Lambda表达式

Lambda表达式是JDK 8开始新增的一种语法形式。

  • 作用:用于简化匿名内部类的代码写法。
  • 格式:(被重写方法的形参列表)->{被重写方法的方法体代码}。
  • 注意:Lambda表达式并不能简化全部匿名内部类的写法,只能简化函数式接口的匿名内部类。

什么是函数式接口?

  • 有且只有一个抽象方法的接口
  • 注意:将来我们见到的大部分函数式接口,上面都可能会有一个@FunctionalInterface的注解,有该注解的接口就必定是函数式接口。

Lambda表达式的省略规则:

  • 参数类型可以省略不写。
  • 如果只有一个参数,参数类型可以省略,同时()也可以省略。
  • 如果Lambda表达式中的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写。

JDK8新特性:方法引用

【Java基础】API高级

Object类

类层次结构的根。所有类都默认继承Object类。

public String toString()

返回对象的字符串表达形式。

  • 直接打印对我们意义不大,一般是重写toString()以便返回对象的内容。
  • 重写方法:①右键-generate-toString(),②写toS自动生成。

public boolean equals(Object o)

指示某个其他对象是否“等于”此对象。

  • 默认是判断两个对象的地址是否相等。但在开发中,这个功能用“==”也能实现。
  • 在开发中也是为了让子类重写,比较两个对象的内容一样。
  • 重写方法:写eq自动生成,反复回车即可。
  • 原理:比较者是this,被比较者是o。①首先判断两个对象是否地址一样,一样直接返回true,②判断o是null直接返回false,或者比较者和被比较者的类型不同,返回false,③o不是null,且o一定是相同类型的对象,就开始比较内容。将o强制转换为比较者类型,然后比较。

public String clone()

创建并返回对象的副本。

  • 由protected修饰,也就是只能在当前包(java.lang)、Object子类中才能访问。
  • 要使用clone()方法,必须要在子类中重写该方法。再通过它中转调用父类的克隆方法。
  • 重写方法:在子类中输入“clon”自动创建。这里是super去调用父类Object。克隆出来返回Object对象。
  • 注意:不是所有类都能克隆。克隆的话还要让子类实现一个接口(implements Clonable)。这个接口里面啥都没,称为标记接口(规则)。实例调用clone()的时候会报错,暂时可以“Alt+Enter”选择第一个,此时自动在main方法后面加上了“throws CloneNotSupportedException”。

Java中有两种克隆:浅克隆(上述)、深克隆。

  • 浅克隆:拷贝出的新对象和原对象中的数据一模一样(引用类型拷贝的只是地址,所以指向的是一样的内容)。
  • 深克隆:
    • 对象中的基本类型的数据直接拷贝
    • 对象中的字符串数据拷贝地址
    • 对象中包含的其他对象,不拷贝地址,创建新对象

Objects类

一个工具类,提供了很多操作对象的静态方法。

equals(Object a, Object b)

先做非空判断,再比较两个对象。

  • 为什么官方选择Objects.euqals(s1,s2)而不是s1.equals(s2)呢?避免s1为null时的报错。

isNull(Object obj)

判断对象是否为null,为null返回true,反之。

  • 其实和“对象==null”效果一样,看起来高级。

nonNull(Object obj)

判断对象是否为null,为null返回true,反之。

  • 其实和“对象!=null”效果一样,看起来高级。

包装类

为什么要有?

  • 因为基本类型数据不是对象。为了实现“万物皆对象”,所以使用包装类用来把基本类型的数据包装成对象。
基本数据类型 对应的包装类(引用数据类型)
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean

如何包装的?

  • valueOf(int i):例如,Integer a1 = Integer.valueOf(12);

Java中为了方便把基本类型的数据转换成对象,提供了两种方案:

  1. 自动装箱机制(自动把基本类型数据转换为对象)。例如,Integer a2 = 12;
  2. 自动拆箱(自动把包装类型对象转换为基本数据类型)。例如,int a3 = a2;

为什么重要?

  • 泛型和集合不支持基本数据类型,只支持引用数据类型。例如,Arraylist对而Arraylist错误。

包装类有什么特殊的方法

  1. 把基本类型的数据转换为字符串类型。例如,Integer a = 23; String rs1 = Integer.toString(a);和String rs2 = a.toString();的效果一样。(也可以用String rs3 = a + “”,所以这个方法应用不多。)

  2. 字符串类型的数值转换成数值本身对应的数据类型。例如,String ageStr = “29”; int ageI = Integer.parseInt(ageStr);//String scoreStr = “99.5”; double score = Double.parseDouble(scoreStr );。这样其实是麻烦的,直接使用valueOf,例如,int ageI = Integer.valueOf(ageStr);只需要记住一个函数即可。

    注意:转换前的数值一定要是对应类型,否则报错。

StringBuilder类

可变字符串对象。相当于一个容器,里面装的字符串是可以变的。用来操作字符串。

为什么要有(好处)?

  • 比String更适合做字符串修改的操作,效率更高,代码更简洁。

构造器

  1. 无参构造器。public StringBuilder()。创建一个空白的可变字符串对象,不包含任何内容。
  2. 有参构造器。public StringBuilder(String str)。创建一个指定字符串内容的可变字符串对象。

常用方法

  1. public StringBuilder append(任意类型)。添加数据并返回StringBuilder 对象本身。

    拼接内容。支持链式编程,例如,s.append(666).append(“hello”);。

  2. public StringBuilder reverse()。将对象的内容反转。

    反转操作。

  3. public int length()。返回对象内容长度。

  4. public String toString()。把StringBuilder对象又转换成String类型。

为什么操作字符串建议使用StringBuilder,而不用原来学过的String?

  • 效率高。
  • 对于字符串相关操作,如果频繁拼接、修改等,建议用StringBuilder,效率高。如果字符串较少,或者不需要操作,以及定义字符串变量,建议用String。

案例

设计一个方法,用于返回任意整型数组的内容,要求返回的数组内容格式如下:[11, 22, 33]。(需要用if判断是否到最后一个元素以确定数字后面的符号。)

StringBuffer类

与StringBuilder用法一样。但是StringBuilder是线程不安全的,StringBuffer是线程安全的。(就是很多人进入系统来用StringBuffer编程时不会出bug。)

StringJoiner类

解决的问题:StringBuiler虽然速度上去了,但是代码层面很麻烦(如前例,还要if来判断)。怎么高效、方便地拼接?

  • JDK8开始才有,和StringBuiler类似。
  • 好处:提高字符串的操作效率,在某些场景下代码更加简洁

构造器

  1. public StringJoiner(间隔符号)。创建StringJoiner对象,指定拼接时的间隔符号。
  2. public StringJoiner(间隔符号,开始符号,结束符号)。创建StringJoiner对象,指定拼接时的间隔符号、开始符号、结束符号。

常用方法

  1. public StringJoiner add(添加的内容)。添加数据,返回对象本身。
  2. public int length()。返回长度(字符串出现的个数)。
  3. public String toString()。返回一个字符串(该字符串就是拼接之后的结果)。

因此,可以将输出数组格式的代码简化。

Math类

数学,是一个工具类。提供对数据进行操作的静态方法。

常见方法

  1. public static int abs(int a)、public static double abs(double a)。获取参数绝对值。
  2. public static double ceil(double a)。向上取整。
  3. public static double floor(double a)。向下取整。
  4. public static int round(float a)。四舍五入。
  5. public static int max(int a, int b)。获取两个int值中的较大值。
  6. public static double pow(double a, double b)。返回a的b次幂的值。
  7. public static double random()。返回值为double的随机值,范围[0.0, 1.0)。

System类(了解)

程序所在的系统。工具类。

常见方法

  1. public static void exit(int status)。终止当前运行的Java虚拟机。
    1. 该参数用作状态代码;按照惯例,非零状态代码表示异常终止。
    2. System.exit(0); //人为地终止虚拟机(不要使用)。
  2. public static long currentTimeMills()。返回当前系统的时间毫秒值形式。从1970-1-1 0:0:0开始(小知识:为啥是这个时间开始?C语言的生日。)到此刻的毫秒值。
    1. 通常用作代码的性能分析。
    2. 想转换成秒数:time/1000.0。

Runtime类(了解)

程序所在的运行环境。是单例类。通过调用getRuntime()方法来得到对象。

常见方法

  1. public static Runtime getRuntime()。返回与当前Java应用程序关联的运行时对象。
  2. public void exit(int status)。终止当前运行的虚拟机。该参数用作状态代码。按照管理,非零状态码表示异常终止。(其实System类里面的exit方法就是调用的这里。)
  3. public int availableProcessors()。返回Java虚拟机可用的处理器数。
  4. public long totalMemory()。返回Java虚拟机中的内存总量。返回的是字节数,想看多少K,除以1024.0,多少M,再除以1024.0。
  5. public long freeMemory()。返回Java虚拟机中的可用内存。返回的是字节数。
  6. public Process exec(String command)。启动某个程序,并返回代表该程序的对象。
    1. 用法:拷贝exe程序的路径(右键-属性-安全-对象名称),作为参数传给exec。此时报异常,用“Alt+Enter”选第一个,自动抛出异常(在类定义处增加throw IOException)。程序就会帮我们打开指定的exe程序。
    2. 多说一点,可以直接传程序名,前提是系统环境变量里面配置程序的路径。
    3. 用Process类定义一个变量来接返回的程序对象,接到之后用destroy()方法关闭程序。

BigDecimal类

解决浮点型运算时,出现结果失真的问题(如,0.1+0.2的结果不是0.3)。

构造器

public BigDecimal(String val)。把String转成BigDecimal。是按照对应位置运算的。

常用方法

  1. public static BigDecimal valueOf(double val)。转换一个double成BigDecimal。
  2. public BigDecimal add(BigDecimal b)。加法。
  3. public BigDecimal substract(BigDecimal b)。减法。
  4. public BigDecimal multiply(BigDecimal b)。乘法。
  5. public BigDecimal devide(BigDecimal b)。除法。
  6. public BigDecimal devide(另一个BigDecimal对象,精确几位,舍入模式)。除法,可以控制精确到小数几位。
  7. public BigDecimal doubleValue()。将BigDecimal转换为double。
  • 推荐方式:使用时先toString()变成字符串对象,封装成BogDecimal对象,再运算。//或者调用valueOf进行简化。即BigDecimal a1 = new BigDecimal (Double.toString(a))简化为BigDecimal a1 = BigDecimal.valueOf(a)。
  • 做除法运算时,如果不能正好表达,就会报无法精确表达结果的错,所以就要用第二种除法方法。这只是解决精度问题的手段,最后还要转换为double类型的数据。
  • 注意:一般不用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象,因为存在精度损失风险,可能导致业务逻辑异常。有限推荐入参为String的构造方法,或者使用BigDecimal的valueOf方法,此方法内部执行了Double的toString,而Double的toString按double的实际能表达的精度对尾数进行了截断。

日期和时间(JDK8之前)

Date类

系统的日期和时间。(可以直接打印出来。)

构造器

  1. public Date()。 创建一个Date对象,代表的是系统当前此刻日期时间。
  2. public Date(long time)。 把时间毫秒值转换成Date日期对象。

常用方法

  1. public long getTime()。返回从1970年1月1日 00:00:00走到此刻的总的毫秒数。
  2. public void setTime(long time)。设置日期对象的时间为当前时间毫秒值对应的时间。

SimpleDateFormat类

简单日期格式化,可以用来把日期对象、时间毫秒值格式化成我们想要的形式。

  • 允许格式化(日期-文本)、解析(文本-日期)和规范化。
  • 时间格式: xxxx-xx-xx xx:xx:xx。
  • 可以将Date或者时间毫秒值转化为上述格式。

构造器

public SimpleDateFormat(String pattern)。创建简单日期格式化对象,并封装时间的格式。可以自己看文档组装成想要的格式,例如,“yyyy-MM-dd HH:mm:ss EEE a”。

常用方法

  1. public final String format(Date date)。将日期格式化成日期/时间字符串。

  2. public final String format(Object time)。将时间毫秒值式化成日期/时间字符串。

    即,这个函数既可以扔进来一个SimpleDateFormat类,也可以直接扔进来系统时间毫秒值。

  3. public Date parse(String source)。把字符串时间解析成日期对象。

    使用:①创建SimpleDateFormat对象,指定的时间格式必须与被解析的时间格式一模一样;②把字符串扔到SimpleDateFormat对象中用parse()方法解析。此时还是要用“Alt+Enter”把异常抛出去。

常见符号

y年,M月,d日,H时,m分,s秒,EEE星期几,a上午/下午。

Calendar类

系统此刻时间对应的日历。

  • 通过它可以单独获取、修改时间中的年、月、日、时、分、秒等。(注意月是从0开始的。)
  • 是一个抽象类。要用的话需要找它的子类(实现类)。
  • calendar是可变对象,一旦修改后其对象本身表示的时间将产生变化。

为什么要学习?

  • 从一个案例说起,需求是将2023年09月10日增加一个月。常用做法是:1、记住字符串,2、解析成Date日期对象,3、通过日期对象获取毫秒值,4、增加一个月的毫秒值,5、格式化得到结果。

常用方法

  1. public static Calendar getInstance()。获取当前日历对象。
  2. public int get(int field)。获取日历中的某个信息。例如,.get(Calendar .YEAR);
  3. public final Date getTime()。获取日期对象。
  4. public long getTimeInMillis()。获取时间毫秒值。
  5. public void set(int field,int value)。修改日历的某个信息。
  6. public void add(int field,int amount)。为某个信息增加/减少指定的值。

JDK8开始新增的日期、时间

为什么要学?(Java开发者的bug。)

  • JDK8之前传统的时间API:1、设计不合理,使用不方便,很多都被淘汰了。2、都是可变对象,修改后会丢失最开始的时间信息。3、线程不安全。4、只能精确到毫秒。
  • JDK8开始之后新增的时间API:1、设计更合理,功能丰富,使用更方便。2、都是不可变对象,修改后会返回新的时间对象,不会丢失最开始的时间。3、线程安全。4、能精确到毫秒、纳秒。

代替Calendar类

  1. LocalDate:本地日期(年、月、日、星期)。这是一个不可变对象。
    1. public static Xxxx now()。获取系统当前时间对应的该对象。
    2. public static Xxxx of(…)。获取指定时间的对象。例如,getYear()、getMonthValue()、getDayOfMonth()、getDayOfYear()、getDayOfWeek()等。
    3. withYear、withMonth、withDayOfMonth、withDayOfYear。直接修改某个信息,返回新日期对象。
    4. plusYears、plusMonths、plusDays、plusWeeks。把某个信息加多少,返回新日期对象。
    5. minusYears、minusMonths、minusDays,minusWeeks。把某个信息减多少,返回新日期对象。
    6. equals、isBefore、isAfter:判断两个日期对象,是否相等,在前还是在后 。LocalDate.of:获取指定日期的LocalDate对象。public static LocalDate of(int year, int month, int dayOfMonth)。
  2. LocalTime:本地时间(时、分、秒、纳秒)。这是一个不可变对象。
    1. public int getHour()、public int getMinute()、public int getSecond()、public int getNano()。获取小时、秒、纳秒。
    2. withHour、withMinute、withSecond、withNano。修改时间,返回新时间对象。
    3. plusHours、plusMinutes、plusSeconds、plusNanos。把某个信息加多少,返回新时间对象。
    4. minusHours、minusMinutes、minusSeconds、minusNanos。把某个信息减多少,返回新时间对象。
    5. equals、isBefore、isAfter:判断2个时间对象,是否相等,在前还是在后。
  3. LocalDateTime:本地日期、时间(年、月、日、星期、时、分、秒、纳秒)。这是一个不可变对象。
    1. 方法综合前两个。
    2. 特殊的:可以把LocalDateTime转换成LocalDate和LocalTime,使用toLocalDate()和toLocalTime()方法。而用LocalDateTime.of(LocalDate ld, LocalTime lt)可以再把它们合起来。
  4. ZoneId:时区Id。
    1. 常识:世界标准时间(UTC)是0时区,中国标准时间是世界标准时间(UTC) + 8小时。
    2. 表达方式:洲名/城市名,国家名/城市名。
    3. 常见方法:
      1. public static Set getAvailableZoneIds()。获取Java中支持的所有时区。(如果不记得时区Id怎么写可以调用这个看一下。)
      2. public static ZoneId systemDefault()。获取系统默认时区。然后可以用getId()方法/直接打印获取当前系统的默认时区。
      3. public static ZoneId of(String zoneId)。将指定时区Id封装成ZoneId对象。结合下面的ZonedDateTime使用。
  5. ZonedDateTime:带时区的时间。
    1. 常见方法:
      1. public static ZonedDateTime now() 。获取当前时区的ZonedDateTime对象。
      2. public static ZonedDateTime now(ZoneId zone) 。获取指定时区的ZonedDateTime对象。例如传入Clock.systemUTC()参数获取世界标准时间。
      3. getYear、getMonthValue、getDayOfMonth、getDayOfYeargetDayOfWeek、getHour、getMinute、getSecond、getNano。获取年月日、时分秒、纳秒等。
      4. public ZonedDateTime withXxx(时间) 。修改时间系列的方法。
      5. public ZonedDateTime minusXxx(时间) 。减少时间系列的方法。
      6. public ZonedDateTime plusXxx(时间)。增加时间系列的方法。

代替Date类

  1. Instant:时间戳/时间线。是一个不可变对象。
    1. 通过获取Instant的对象可以拿到此刻的时间,该时间由两部分组成:从1970-01-01 00:00:00 开始走到此刻的总秒数 + 不够1秒的纳秒数。
    2. 可以用来记录代码的执行时间,或用于记录用户操作某个事件的时间点。传统的Date类,只能精确到毫秒,并且是可变对象;新增的Instant类,可以精确到纳秒,并且是不可变对象,推荐用Instant代替Date。
  2. 常用方法:
    1. public static Instant now()。获取当前时间的Instant对象(标准时间)。
    2. public long getEpochSecond()。获取从1970-01-01T00:00:00开始记录的秒数。
    3. public int getNano()。从时间线开始,获取从第二个开始的纳秒数 。
    4. plusMillis plusSeconds plusNanos。增加时间系列的方法。
    5. minusMillis minusSeconds minusNanos。减少时间系列的方法。
    6. equals、isBefore、isAfter。判断时间系列的方法。

代替SimpleDateFormat类

  1. DateTimeFormatter:用于时间的格式化和解析。(线程安全。)
  2. 常用方法:
    1. public static DateTimeFormatter ofPattern(时间格式)。获取格式化器对象。
    2. public String format(时间对象)。格式化时间。
    3. public String format(DateTimeFormatter formatter)。格式化时间。
    4. public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter)。解析时间。
  3. 正向格式化:formatter.format(now),反向格式化:now.format(formatter)。得到的结果是一样的。
  4. LocalDateTime提供的格式化、解析时间的方法:
    1. public String format(DateTimeFormatter formatter)。格式化时间。
    2. public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter)。解析时间。

其他补充

  1. Period:时间间隔(年,月,日)。用于计算两个 LocalDate对象相差的年数、月数、天。注意先写较前的时间,再写较后的时间。
    1. public static Period between(LocalDate start, LocalDate end)。传入2个日期对象,得到Period对象。
    2. public int getYears()。计算隔几年,并返回。
    3. public int getMonths()。计算隔几个月,年返回。
    4. public int getDays()。计算隔多少天,并返回。
  2. Duration:时间间隔(时、分、秒,纳秒)。计算两个时间对象相差的天数、小时数、分数、秒数、纳秒数;支持LocalTime、LocalDateTime、Instant等时间。
    1. public static Duration between(开始时间对象1,截止时间对象2)。传入2个时间对象,得到Duration对象。
    2. public long toDays()。计算隔多少天,并返回。
    3. public long toHours()。计算隔多少小时,并返回。
    4. public long toMinutes()。计算隔多少分,并返回。
    5. public long toSeconds()。计算隔多少秒,并返回。
    6. public long toMillis()。计算隔多少毫秒,并返回。
    7. public long toNanos()。计算隔多少纳秒,并返回。