十三、异常、集合

1、异常

1.1 认识异常

异常就是代表程序出现的问题

/**
 * @author Tender
 * @date 2025/4/6 8:47
 */

package com.tender.d1_exception;

public class ExceptionDemo1 {
    public static void main(String[] args) {
        System.out.println("==开始==");
        int[] arr = {11,22,33};
        System.out.println(arr[0]);
        System.out.println(arr[1]);
        System.out.println(arr[2]);
        // System.out.println(arr[4]); // ArrayIndexOutOfBoundsExceptionshuzu 数组索引越界异常

        String name = null;
        // System.out.println(name.length()); // NullPointerException 空指针异常
        // System.out.println(10/0); // ArithmeticException 数学操作异常

        Object o = "张麻子";

        // Integer i = (Integer) o; // ClassCastException 类型转换异常

        String s = "23a";
        int it = Integer.valueOf(s); // NumberFormatException 数字转换异常
        System.out.println("==结束==");
    }
}

Error:代表系统级别错误(属于严重问题),也就是说系统一旦出现问题,sun公司会把这些问题封装成Eror对象给出来,说白了,Error是给sun公司自己用的,不是给我们程序员用的,因此我们开发人员不用管它。
Exception:叫异常,他代表的才是我们程序可能出现的问题 ,所以,我们程序员通常会用Exception以及它的孩子来封装程序出现的问题。
运行时异常:RuntimeException及其子类,编译阶段不会出现错误提醒,运行时出现的异常(如:数组索引越界异常)
编译时异常:编译阶段就会出现的错误提醒。(如:日期解析异常)

1.2 认识的作用,自定义异常

①异常是用来查询系统BUG的关键参考信息
②异常可以作为方法内部的一种特殊返回值,以便通知上层调用者底层的执行情况

/**
 * @author Tender
 * @date 2025/4/6 9:14
 */

package com.tender.d1_exception;

public class ExceptionDemo2 {
    public static void main(String[] args) {
        try {
            System.out.println(divide(10, 2));
            System.out.println(divide(10,0));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static int divide(int a, int b){
        if (b==0){
            System.out.println("参数异常");
            // 抛出一个异常作为返回值,表示出现了BUG
            throw new RuntimeException("/ by 0");
        }
        int c = a/ b;
        return c;
    }
}

1.3 自定义异常

Java无法为这个世界的全部问题都提供异常类来代表,如果企业自己的某种问题,想通过异常来表示,以便用异常来管理该问题,那就需要自己来定义异常类了。
自定义异常的种类:
自定义运行时异常
①定义一个异常类继承RuntimeExpection
②重写构造器
③通过throw new 异常类(xxx)来创建异常对象并抛出。编译阶段不报错,提醒不强烈,运行时才可能出现
自定义编译时异常
①定义一个异常类继承Exception
②重写构造器
③通过throw new 异常类(xxx)来创建异常对象并抛出。编译阶段就报错,提醒更加强烈!

运行时异常

/**
 * @author Tender
 * @date 2025/4/6 9:52
 */

package com.tender.d1_exception;
// 自定义运行时异常
public class tenderAgeIllegaRuntimeException extends RuntimeException{
    public tenderAgeIllegaRuntimeException() {
    }

    public tenderAgeIllegaRuntimeException(String message) {
        super(message);
    }
}

/**
 * @author Tender
 * @date 2025/4/6 9:50
 */

package com.tender.d1_exception;

public class ExceptionDemo3 {
    public static void main(String[] args) {
        // 目标:自定义异常
        System.out.println("开始");
        try {
            save(50);
            save(250);
        } catch (Exception e) {
            e.printStackTrace(); // 打印异常信息
            System.out.println("执行失败");
        }
        System.out.println("结束");
    }

    public static void save(int age){
        if(age<=0||age>150){
            // 这个年龄非法 ,创建异常对象并直接跑出去
            throw new tenderAgeIllegaRuntimeException("/ age is xiagao");
        }
        System.out.println("年龄保存成功");
    }
}

编译时异常

/**
 * @author Tender
 * @date 2025/4/6 9:52
 */

package com.tender.d1_exception;
// 自定义编译时异常
public class tenderAgeIllegaException extends Exception{
    public tenderAgeIllegaException() {
    }

    public tenderAgeIllegaException(String message) {
        super(message);
    }
}

/**
 * @author Tender
 * @date 2025/4/6 9:50
 */

package com.tender.d1_exception;

public class ExceptionDemo4 {
    public static void main(String[] args) {
        // 目标:自定义异常
        System.out.println("开始");
        try {
            save(50);
            save(250);
        } catch (tenderAgeIllegaException e) {
            e.printStackTrace();
            System.out.println("执行失败");
        }

        System.out.println("执行失败");
        System.out.println("结束");
    }

    public static void save(int age) throws tenderAgeIllegaException {
        // throw 方法内部使用的,创建异常并从此点抛出
        // throws 方法上,抛出方法内部异常给调用者
        if(age<=0||age>150){
            // 这个年龄非法 ,创建异常对象并直接跑出去
            throw new tenderAgeIllegaException("/ age is xiagao");
        }
        System.out.println("年龄保存成功");
    }
}

注意:
throw:方法内使用的,创建异常并从此点抛出去
throws:方法上,抛出方法内部异常给调用者

1.4 开发中对异常常见处理方式

①捕获异常,记录异常并响应合适的信息给用户
②捕获异常、尝试重新修复
开发中对异常常见的处理方式

2、集合进阶(一)

2.1 集合体系概述

集合是一种容器,用来装数据的,类似于数组,但集合的大小可变,开发中也非常常用
集合体系结构
集合体系结构
Collection集合体系
Collection集合体系

2.2 Collection的常用方法

Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的
Collection的常见方法如下:
Collection的常见方法

package com.tender.d3_collection_api;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class CollectionAPTDemo1 {
    public static void main(String[] args) {
        // 目标:掌握Collection提供的常用方法:是全部单列集合都可以直接用的。
        Collection<String> list = new ArrayList<>(); // 多态
        // 1、添加数据 boolean add(E e)
        list.add("java1");
        list.add("java1");
        list.add("赵敏");
        list.add("赵敏");
        list.add("小昭");
        list.add("灭绝师太");
        System.out.println(list); // [java1, java1, 赵敏, 赵敏, 小昭, 灭绝师太]

        // 2、清空集合
        //list.clear();
        //System.out.println(list); // []

        // 3、判断集合是否为空
        System.out.println(list.isEmpty());

        // 4、直接删除集合中的某个数据:默认只能删除第一个java1
        System.out.println(list.remove("java1"));
        System.out.println(list);


        // 5、判断集合中是否包含某个数据
        System.out.println(list.contains("java1")); // true
        System.out.println(list.contains("Java1")); // false

        // 6、获取集合的大小(元素个数)
        System.out.println(list.size());

        // 7、把集合转化成数组。
        Object[] array = list.toArray();
        System.out.println(Arrays.toString(array));

        // 拓展
        String[] arrays = list.toArray(String[]::new);
        System.out.println(Arrays.toString(arrays));

        // 8、拓展一下:把别人集合的数据加给自己
        Collection<String> c1 = new ArrayList<>();
        c1.add("java1");
        c1.add("java2");

        Collection<String> c2 = new ArrayList<>();
        c2.add("java2");
        c2.add("java3");
        // 把c2集合的数据全部倒入给c1集合
        c1.addAll(c2);

        System.out.println(c1);
        System.out.println(c2);

    }
}

2.3 迭代器概述

迭代器是用来遍历集合的专用方式(数组没有迭代器),在jiava中迭代器的代表是Iterator
在这里插入图片描述

/**
 * @author Tender
 * @date 2025/4/6 11:59
 */

package com.tender;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class d4_collection_travesal {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        list.add("eee");
        System.out.println(list);

        // 得到这个集合对象迭代器对象
        Iterator<String> it = list.iterator();
//        System.out.println(it.next());
//        System.out.println(it.next());
//        System.out.println(it.next());
        // 使用循环改造
        while (it.hasNext()){
            String ele = it.next();
            System.out.println(ele);
        }


    }
}

2.4 增强for

for(元素的数据类型 变量名 : 数组或者集合){

}

增强for可以用来遍历集合或者数组
增强for遍历集合,本质就是迭代器遍历集合的简化写法

/**
 * @author Tender
 * @date 2025/4/6 11:59
 */

package com.tender;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class d4_collection_travesal {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        list.add("eee");

        for (String s : list) {
            System.out.println(s);
        }

        int[] arr = {1,2,3,4,5};
        for (int i : arr) {
            System.out.println(i);
        }
    }
}

2.5 Lambda表达式遍历集合

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

  // 3、Lambda遍历集合并删除:一定会出错,而且无法解决。
        ArrayList<String> list3 = new ArrayList<>();
        list3.add("Java入门");
        list3.add("宁夏枸杞");
        list3.add("黑枸杞");
        list3.add("人字拖");
        list3.add("特技枸杞");
        list3.add("枸杞子");
        list3.forEach(name -> {
            if(name.contains("枸杞")){
                list3.remove(name);
            }
        });
        System.out.println(list3);

2.6 集合的并发修改异常

使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常错误。

     package com.tender.d4_collection_travesal;

import java.util.ArrayList;
import java.util.Iterator;

public class CollectionTest5 {
    public static void main(String[] args) {
        // 目标:三种遍历可能出现的并发修改异常问题。
        ArrayList<String> list = new ArrayList<>();
        list.add("Java入门");
        list.add("宁夏枸杞");
        list.add("黑枸杞");
        list.add("人字拖");
        list.add("特技枸杞");
        list.add("枸杞子");

        // 1、使用迭代器遍历集合并删除枸杞:
        // 注意1:如果使用迭代器遍历,并用集合删除数据,会出现并发修改异常,程序出现bug。
        // 注意2: 必须调用迭代器自己的删除方法,才不会出现bug
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String name = it.next();
            if(name.contains("枸杞")){
                // list.remove(name); // 如果使用迭代器遍历,并用集合删除数据,会出现并发修改异常,程序出现bug。
                it.remove(); // 必须调用迭代器自己的删除方法,才不会出现bug
            }
        }
        System.out.println(list);

        // 2、使用增强for遍历集合并删除枸杞:(本质就是迭代器) 一定会出错,而且无法解决。
        ArrayList<String> list2 = new ArrayList<>();
        list2.add("Java入门");
        list2.add("宁夏枸杞");
        list2.add("黑枸杞");
        list2.add("人字拖");
        list2.add("特技枸杞");
        list2.add("枸杞子");
//        for (String name : list2) {
//            if(name.contains("枸杞")){
//                list2.remove(name);
//            }
//        }
//        System.out.println(list2);


        // 3、Lambda遍历集合并删除:一定会出错,而且无法解决。
        ArrayList<String> list3 = new ArrayList<>();
        list3.add("Java入门");
        list3.add("宁夏枸杞");
        list3.add("黑枸杞");
        list3.add("人字拖");
        list3.add("特技枸杞");
        list3.add("枸杞子");
        list3.forEach(name -> {
            if(name.contains("枸杞")){
                list3.remove(name);
            }
        });
        System.out.println(list3);

        // 注意:如果是Arraylist带索引的集合,我们也可以使用for循环删除每次退一步,或者从后面倒着遍历并删除!
    }
}

3 List集合

List集合支持索引,所以多了很多与索引相关的方法,当然,Collection的功能List也都继承了。
List集合特有方法

/**
 * @author Tender
 * @date 2025/4/7 8:57
 */

package com.tender.d5_list;

import java.util.ArrayList;
import java.util.List;

public class ListDemo1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("小昭");

        list.add(1,"赵敏");
        System.out.println(list);

        System.out.println(list.remove(2));

        list.set(2,"殷素素");
        System.out.println(list);

        System.out.println(list.get(2));
    }
}

3.1 遍历

①for循环,因为List集合有索引
②迭代器
③增强for
④Lambda表达式

/**
 * @author Tender
 * @date 2025/4/7 9:01
 */

package com.tender.d5_list;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ListDemo2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("小昭");
        list.add("白眉");

        // for
        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }

        // 迭代器
        System.out.println("========================");
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
        System.out.println("========================");
        // 增强for
        for (String s : list) {
            System.out.println(s);
        }
        System.out.println("========================");
        // Lambda表达式
        list.forEach(s->System.out.println(s));
    }
}

3.2 ArrayList与LinkedList底层实现原理

3.2.1 ArrayList特点

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

3.2.2 ArrayList集合的底层原理

①利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
②添加第一个元素时,底层会创建一个新的长度为10的数组
③存满时,会扩容1.5倍
④如果一次添加多元素,1.5倍还放不下,则新创建数组的长度以实际为准
⑤size记住的是元素个数和下次存入的位置
注意:计算机的索引从0开始,对寻址是由帮助的

3.2.3 LinkedList集合的底层原理

基于双链表实现的:链表中的节点是独立的对象,在内存中不是连续的,每个节点包含数据和下一个节点的地址
特点:
查询慢,无论查询哪个数据都要从头开始找,增删相对较快,但对首尾元素进行增删改查的速度是极快的
LinkedList新增了:很多首位操作的特有方法
LinkedList方法
LinkedList的应用场景:
可以用来设计队列(先进先出,后进后出),只是在首位增删元素,用LinkedList来实现很合适

/**
 * @author Tender
 * @date 2025/4/7 10:38
 */

package com.tender.d5_list;

import java.util.LinkedList;

public class ListTest3 {
    public static void main(String[] args) {
        LinkedList<String> queue = new LinkedList<>();
//        queue.add("1号");
//        queue.add("2号");
//        queue.add("3号");
//        queue.add("4号");
//        queue.add("5号");
//        queue.add("6号");
//        queue.add("7号");

        // 入队
        queue.addLast("1号");
        queue.addLast("2号");
        queue.addLast("3号");
        queue.addLast("4号");
        queue.addLast("5号");

        System.out.println(queue);

        // 出队
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue);
    }
}

可以用来设计栈(后进先出,先进后出)数据进栈的过程称为:压/进栈(push)数据离开的过程称为:弹/出栈(pop),只是在首部增加元素,用LinkedList来实现很合适

        LinkedList<String> stack = new LinkedList<>();
        stack.addFirst("第1发");
        stack.addFirst("第2发");
        stack.addFirst("第3发");
        stack.addFirst("第4发");
        stack.addFirst("第5发");
        stack.addFirst("第6发");
        System.out.println(stack);

        System.out.println(stack.removeFirst());
        System.out.println(stack);

Linked的节点是Node

4、Set集合

4.1 HashSet

哈希值
就是一个int类型的数值,Java中每个对象都有一个哈希值
Java中所有的对象,都可以调用Object类提供的hashCode方法,返回该对象自己的哈希值
对象哈希值的特点:
同一个对象多次调用hashCode()方法返回的哈希值是相同的
不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)

4.2 HashSet集合的底层原理

基于哈希表实现
哈希表是一种增删改查数据,性能都较好的数据结构
JDK8之前HashSet集合的底层原理,基于哈希表:数组+链表
①创建一个默认长度16的数组,默认加载因子为0.75,数组名为table
②使用元素的哈希值对数组长度做运算计算出应存入的位置
③判断当前位置是否为null,如果是null直接存入
④如果不为null,表示有元素,则调用equals方法比较;相等,则不存;不相等,则存入数组
jdk8之前,新元素存入数组,占老元素位置,老元素挂下面
jdk8开始之后,新元素直接挂老元素下面
哈希表是一种增删改查数据性能都较好的结构
JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树

4.2 二叉查找树存在的问题

当数据已经是排好序的,导致查询的性能与单链表一样,查询速度变慢!
解决:平衡二叉树
在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能。
红黑树,就是可以自平衡的二叉树
红黑树是一种增删改查数据性能都相对较好的结构

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

/**
 * @author Tender
 * @date 2025/4/7 14:36
 */

package com.tender.d6_set;

import java.util.Objects;

public class SetDemo2 {
    private String name;
    private String sex;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SetDemo2 setDemo2 = (SetDemo2) o;
        return Objects.equals(name, setDemo2.name) && Objects.equals(sex, setDemo2.sex);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, sex);
    }

    public SetDemo2() {
    }

    public SetDemo2(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

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

/**
 * @author Tender
 * @date 2025/4/7 14:36
 */

package com.tender.d6_set;

import java.util.HashSet;
import java.util.Set;

public class Test2 {
    public static void main(String[] args) {
        Set<SetDemo2> setDemo2s = new HashSet<>();
        SetDemo2 s1 = new SetDemo2("li","nv");
        SetDemo2 s2 = new SetDemo2("li","nv");
        setDemo2s.add(new SetDemo2("wang","nan"));
        setDemo2s.add(s1);
        setDemo2s.add(s2);
        System.out.println(setDemo2s);
    }
}

结论:如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法

4.4 LinkedHashSet底层原理

依然是基于哈希表(数组,链表,红黑树)实现的。但是它的每个元素都额外的的多了一个双链表机制记录它前后元素的位置

4.5 TreeSet

特点:不重复、无索引、可排序(默认升序排序,按照元素大小,由小到大排序)
底层是基于红黑树实现的排序
注意:
①对于数值类型:Integer、Double,默认按照数值本身大小进行升序排序
②对于字符串类型:默认按照首字符的编号升序排序
③对于自定义类型如Student对象,TreeSet默认是无法直接排序的

/**
 * @author Tender
 * @date 2025/4/7 15:08
 */

package com.tender.d6_set;

public class Girl implements Comparable<Girl> {
    private String name;
    private int age;
    private double height;

    public Girl() {
    }

    public Girl(String name, int age, double height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "Girl{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}' + "\n";
    }

    @Override
    public int compareTo(Girl o) {
        return o.age - this.age;
    }
}

/**
 * @author Tender
 * @date 2025/4/7 15:08
 */

package com.tender.d6_set;

import java.util.Comparator;
import java.util.Set;

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

        Set<Girl> g = new java.util.TreeSet<>((o1, o2) -> Double.compare(o2.getHeight(),o1.getHeight()));
        g.add(new Girl("xxx",58,80));
        g.add(new Girl("yyy",52,81));
        g.add(new Girl("zzz",21,79));
        g.add(new Girl("ttt",62,68));

        System.out.println(g);

    }
}

应用场景:
collection应用场景总结

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐