• 20天深度复习JavaSE的详细笔记(十四)——不可变集合、Stream、异常


    Demo14- 不可变集合、Stream、异常

    1.不可变集合

    • 集合的数据项在创建的时候就提供了,并且在整个生命周期中都不可改变(增删改),否则报UnsupportedOparationException异常
    • 为什么要创建不可变集合:
      • 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践
      • 或者当集合对象被不可信的库调用时,不可变形式是安全的
    • 如果创建不可变集合:在List,Set,Map接口中,都存在of方法,可以创建一个不可变的集合(JDK9以后才支持这种创建不可变集合的方式)
    方法名称说明
    static List of(E…elements)创建一个具有指定元素的List集合对象
    static Set of(E…elements)创建一个具有指定元素的Set集合对象
    static Map of(E…elements)创建一个具有指定元素的Map集合对象
    public class CollectionDemo {
        public static void main(String[] args) {
            // 1、不可变的List集合
            List<Double> lists = List.of(569.5, 700.5, 523.0,  570.5);
            // lists.add(689.0);//报错
            System.out.println(lists);
            System.out.println(score);
    
            // 2、不可变的Set集合
            //Set names = Set.of("迪丽热巴", "马尔扎哈", "卡尔眨巴", "卡尔眨巴");//不会自动扔掉一个重复的卡尔眨巴,而是直接报IllegalArgumentException异常
            Set<String> names = Set.of("迪丽热巴", "马尔扎哈", "卡尔眨巴" );
            // names.add("三少爷");//报错
            System.out.println(names);
    
            // 3、不可变的Map集合
            Map<String, Integer> maps = Map.of("huawei",2, "Java开发", 1 , "手表", 1);
            // maps.put("衣服", 3);//报错
            System.out.println(maps);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.Stream流

    在JDK8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念,用于简化集合和数组操作的API

    2.1先演示一下怎么使用Stream流

    案例需求:

    • 创建一个集合,存储多个字符串元素
    • 把集合中所有以"张"开头的元素存储到一个新的集合
    • 把"张"开头的集合中的长度为3的元素存储到一个新的集合
    • 遍历上一步得到的集合中的元素并输出
    public class StreamTest {
        public static void main(String[] args) {
            List<String> names = new ArrayList<>();
            Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
            System.out.println(names);
            //方法一:用以前学过的知识解决:
            // 1、从集合中找出姓张的放到新集合
            List<String> zhangList = new ArrayList<>();
            for (String name : names) {
                if(name.startsWith("张")){
                    zhangList.add(name);
                }
            }
            System.out.println(zhangList);
            // 2、找名称长度是3的姓名
            List<String> zhangThreeList = new ArrayList<>();
            for (String name : zhangList) {
                if(name.length() == 3){
                    zhangThreeList.add(name);
                }
            }
            System.out.println(zhangThreeList);
    
            //方法二:使用Stream实现
            names.stream().filter(s -> s.startsWith("张") && s.length() == 3).forEach(s -> System.out.println(s));
            //stream流是支持链式编程的
            names.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
        }
    }
    
    • 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

    Stream流式思想的核心:

    • 先得到集合或者数组的Stream流(就是一根传送带,将元素放到这个传送带上)
    • 然后就可以用这个Stream流简化的API来方便的操作元素

    Stream流的三类方法:

    • 获取Stream流
      • 创建一条流水线,并把数据放到流水线上准备进行操作
    • 中间方法
      • 流水线上的操作.一次操作完毕后,还可以继续进行其他操作
    • 终结方法
      • 一个Stream流只能有一个终结方法,是流水线上的最后一个操作

    2.2Stream流的获取

    • 集合获取Stream流的方式

      • 使用Collection接口中的默认方法stream()生成流(为啥这个方法用default修饰呢,因为接口中的默认方法的修饰丰富就必须有default然后由该接口的实现类对象调用)
      名称说明
      default Stream stream()获取当前集合对象对的Stream流
    • 数组获取Stream流的方式

      • 使用Arrays工具类的静态方法stream(T[] array)生成流
      • 使用Stream的静态方法of(T…values)生成流
      名称说明
      public static Stream stream(T[] array)获取当前数组的Stream流
      public static Stream of(T…values)获取当前数组/可变数据的Stream流
    public class StreamDemo02 {
        public static void main(String[] args) {
            /** --------------------Collection集合获取流-------------------------------   */
            Collection<String> list = new ArrayList<>();
            Stream<String> s =  list.stream();
    
            /** --------------------Map集合获取流-------------------------------   */
            Map<String, Integer> maps = new HashMap<>();
            //这种是不对的,Map集合没有stream方法
            //maps.stream()
    
            // 键流
            Stream<String> keyStream = maps.keySet().stream();
            // 值流
            Stream<Integer> valueStream = maps.values().stream();
            // 键值对流(拿整体)
            Stream<Map.Entry<String,Integer>> keyAndValueStream =  maps.entrySet().stream();
    
            /** ---------------------数组获取流------------------------------   */
            String[] names = {"赵敏","小昭","灭绝","周芷若"};
            Stream<String> nameStream = Arrays.stream(names);
            Stream<String> nameStream2 = Stream.of(names);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.3Stream流常用API

    1.中间操作方法

    名称说明
    Stream filter(Predicate predicate)用于对流中的数据进行过滤
    Stream limit(long maxSize)获取前几个元素
    Stream skip(long n)跳过前几个元素
    Stream distinct()去除流中重复的元素,依赖hashCode和equals方法
    static Stream concat(Stream a, Stream b)合并a和b两个流为一个流
    long count()流中还有几个元素
    Stream map(Function mapper);加工数据

    2.终结方法

    名称说明
    void forEach(Consumer action);对此流的每个元素执行遍历操作
    long count()返回此流中的元素数
    public class StreamDemo03 {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("123");
            list.add("1456");
            list.add("26");
            list.add("666");
            list.add("14666");
            //1.filter
            list.stream().filter(new Predicate<String>() {
                @Override
                public boolean test(String s) {
                    return s.startsWith("1");
                }
            }).forEach(new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            });
            //Lambda表达式简化格式
            list.stream().filter(s -> s.startsWith("1")).forEach(s -> System.out.println(s));
    
            //2.count(终止方法)
            long size = list.stream().filter(s -> s.startsWith("1")).count();
            System.out.println(size);
    
            //3.limit
            list.stream().filter(s -> s.startsWith("1")).limit(1).forEach(s -> System.out.println(s));
            //4.skip
            list.stream().filter(s -> s.startsWith("1")).skip(2).forEach(s -> System.out.println(s));
    
            //5.map方法用来加工数据
            list.stream().map(new Function<String, Integer>() {//String是原材料类型,Integer是加工后类型
                @Override
                public Integer apply(String s) {
                    return Integer.parseInt("9" + s);
                }
            }).forEach(s -> System.out.println(s));
            //Lambda表达式简化格式
            list.stream().map(s -> Integer.parseInt("9" + s)).forEach(a -> System.out.println(a));
    
            // 需求:把所有的String都加工成一个学生对象。
            list.stream().map(s -> new Student(s)).forEach(s -> System.out.println(s));
            list.stream().map(Student::new).forEach(System.out::println); // 构造器引用,方法引用
    
            //6.concat用来合并流(期间用distinct去除重复元素)
            //static  Stream concat(Stream a, Stream b)
            Stream<String> s1 = list.stream().filter(s -> s.startsWith("1"));
            Stream<String> s2 = Stream.of("2", "5", "5");
            Stream<String> s3 = Stream.concat(s1, s2);
            s3.distinct().forEach(s -> System.out.println(s));
    
            //如果两个流类型不同,必须用Object类型的流来接
            Stream<String> s4 = list.stream().filter(s -> s.startsWith("1"));
            Stream<Integer> s5 = Stream.of(12, 11, 11);
            Stream<Object> s6 = Stream.concat(s4, s5);
            s6.distinct().forEach(s -> System.out.println(s));
        }
    }
    
    class Student {
        private String a;
        public Student(String a) {
            this.a = a;
        }
        @Override
        public String toString() {
            return "Student{" +
                    "a=" + a +
                    '}';
        }
    }
    
    • 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
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    注意事项:

    • 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程
    • Stream流只是将集合,数组中的元素copy过来,所以在Stream流中无法直接修改集合,数组中的数据
    • 终结操作方法调用完成后就无法继续使用流了,因为不会返回Stream了

    2.4收集Stream流

    收集Stream流就是把Stream流操作后的结果数据转回到集合或者数组中

    Stream流和StringBuilder的作用类似,只是方便我们操作的手段,我们最终还是要将Stream流转换为集合/数组

    Stream流的收集方法:

    名称说明
    R collect(Collector collector)开始收集Stream流,指定收集器

    Collections工具类提供了具体的收集方式

    名称说明
    public static Collector toList()把元素收集到List集合中
    public static Collector toSet()把元素收集到Set集合中
    public static Collector toMap(Function keyMapper, Function valueMapper)把元素收集到Map集合中
    public class StreamDemo05 {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("张无忌");
            list.add("周芷若");
            list.add("赵敏");
            list.add("张强");
            list.add("张三丰");
            list.add("张三丰");
    
            Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));//不知道为啥变量写为s就不对了,我寻思着filter的s应该是局部变量啊,为什么会变量名冲突呢
            List<String> zhangList = s1.collect(Collectors.toList());
            zhangList.add("java1");
            System.out.println(zhangList);
    
            //Set zhangSet = s1.collect(Collectors.toSet());//这行代码会报IllegalStateException异常,因为流只能使用一次
            Stream<String> s2 = list.stream().filter(s -> s.startsWith("张"));
            Set<String> zhangSet = s2.collect(Collectors.toSet());
            System.out.println(zhangSet);
    
            Stream<String> s3 = list.stream().filter(s -> s.startsWith("张"));
            Object[] arrs1 = s3.toArray();//toArray方法返回的是Object类型的数组,因为流中可能会有不同类型的数据
            System.out.println("Arrays数组内容:" + Arrays.toString(arrs1));
            /**
             * 如果我已经确定我这个流里数据是同种类型(如String类型),我就想要它给我返回字符串数组怎么办:
             * 1.先调用toArray方法遍历流,会清楚知道流中所有数据的类型和流中元素个数
             * 2.如果确定了全部是String类型,就会调用apply(value)方法new一个长度为value的字符串数组并作为参数传给toArray方法
             * 3.遍历流将元素全加到字符串数组中并返回给变量arrs
             */
            Stream<String> s4 = list.stream().filter(s -> s.startsWith("张"));
            String[] arrs2 = s4.toArray(new IntFunction<String[]>() {
                @Override
                public String[] apply(int value) {
                    return new String[value];
                }
            });
            //简化写法
    //        String[] arrs2 = s4.toArray(String[]::new);
            System.out.println("Arrays数组内容:" + Arrays.toString(arrs2));
    
        }
    }
    
    • 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

    说一下第12行List zhangList = s1.collect(Collectors.toList())是怎么执行的(粗略说一下,具体怎么执行还要深究源码):

    • Collectors.toList()用来new一个ArrayList对象传参给collect方法
    • collect方法的底层会遍历流,再依次将遍历到的数据扔到ArrayList对象中
    • 遍历完成后将ArrayList对象返回给变量zhangList

    3.异常处理

    3.1异常概述及体系

    • 异常是程序在"编译"或者"运行"的过程中可能出现的问题语法错误不算在异常体系中,这是自己水平不行
    • 为什么要学习异常:
      • 异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止
      • 研究异常并且避免异常,然后提前处理异常,体现的是程序的安全性健壮性
    • 异常体系:
      • Error:系统级别问题,JVM退出等,代码无法控制.这种系统级别的问题我们没办法控制
      • Exception:java.lang包下,称为异常类,它表示了程序本身可以处理的问题
        • RuntimeException及其子类:运行时异常,编译阶段不会报错(如空指针异常,数组越界异常)
        • 除RuntimeException之外的所有异常:编译时异常,编译期必须处理的,否则程序不能通过编译(如日期格式化异常)

    3.2常见运行时异常

    直接继承自RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误,一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误

    运行时异常示例:

    • 数组索引越界异常:ArrayIndexOutOfBoundsException

    • 空指针异常:NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错

    • 数学操作异常:ArithmeticException

    • 类型转换异常:ClassCastException

      Object o = 23;
      String s = (String) o;
      
      • 1
      • 2
    • 数字转换异常:NumberFormatException

    3.3常见编译时异常

    不是RuntimeException或者其子类的异常,编译阶段就报错,必须处理,否则代码不通过

    public class ExceptionDemo {
        public static void main(String[] args) throws ParseException {
            String date = "2015-01-12 10:23:21";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
            Date d = sdf.parse(date);
            System.out.println(d);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    编译时异常的作用:

    • java担心程序员的技术不行在编译阶段就爆出一个错误,目的在于提醒不要出错
    • 遇见编译时异常后我们看看代码自认为无误后可以抛出异常,这样就可以通过编译阶段,但是如果运行时代码真的有问题,就会报错
    • 编译时异常可遇不可求,我们在写某一段代码时java如果觉得可能我这里会出错,此时就会编译时异常

    3.4常见的默认处理流程

    • 默认会在出现异常的代码那里出现自动创建的一个异常对象:ArithmeticException
    • 异常会从方法中出现的位置抛出给方法调用者,方法又将异常抛给main方法,main方法最终抛出给JVM虚拟机
    • 虚拟机接收到异常对象后
      • 先在控制台直接输出异常栈信息数据
      • 直接从当前执行的异常点干掉当前程序
    • 后续代码没有机会执行了,因为程序已经死亡

    默认的异常处理机制并不好,一旦真的出现异常,程序立即死亡!

    3.5编译时异常的处理机制

    • 出现异常直接抛出去给调用者,调用者也继续抛出去,和java的默认处理异常流程一样
    • 出现异常自己捕获处理,不麻烦别人
    • 前两者结合,出现异常直接抛出去给调用者,调用者捕获处理

    异常处理方式一:

    • throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理

    • 这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡

    • 抛出异常格式

      方法 throws 异常1, 异常2, 异常3…{

      }

      这是不规范的做法,因为如果一个方法要抛出10个异常,那总不能throws后面写10个异常类型吧,这里有一种规范做法:

      方法 throws Exception{

      }

    异常处理方式二:try…catch

    • 监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理
    • 这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行
    public class ExceptionDemo02 {
        public static void main(String[] args) {
            System.out.println("程序开始。。。。");
            parseTime("2011-11-11 11:11:11");
            System.out.println("程序结束。。。。");
        }
        public static void parseTime(String date) {
            //    public static void parseTime(String date) {
    //        try {
    //            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
    //            Date d = sdf.parse(date);
    //            System.out.println(d);
    //        } catch (ParseException e) {
    //            // 解析出现问题
    //            System.out.println("出现了解析时间异常哦,走点心!!");
    //        }
    //
    //        try {
    //            InputStream is = new FileInputStream("E:/meinv.jpg");
    //        } catch (FileNotFoundException e) {
    //            System.out.println("您的文件根本就没有啊,不要骗我哦!!");
    //        }
    //    }
    
            //    public static void parseTime(String date) {
    //        try {
    //            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
    //            Date d = sdf.parse(date);
    //            System.out.println(d);
    //
    //            InputStream is = new FileInputStream("E:/meinv.jpg");
    //        } catch (FileNotFoundException e) {
    //           e.printStackTrace(); // 打印异常栈信息
    //        } catch (ParseException e) {
    //           e.printStackTrace();
    //        }
    //    }
            
            //    public static void parseTime(String date) {
    //        try {
    //            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
    //            Date d = sdf.parse(date);
    //            System.out.println(d);
    //
    //            InputStream is = new FileInputStream("E:/meinv.jpg");
    //        } catch (FileNotFoundException|ParseException e) {
    //            e.printStackTrace(); // 打印异常栈信息
    //        }
    //    }
            
            try {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
                Date d = sdf.parse(date);
                System.out.println(d);
    
                InputStream is = new FileInputStream("E:/meinv.jpg");
            } catch (Exception e) {
                e.printStackTrace(); // 打印异常栈信息
            }
        }
    }
    
    • 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

    下面分析一下最终的代码是怎么演变来的:

    • 8-23演变为25-37:

      • 首先当我们捕获异常后不要用自己写的处理异常,而是应该打印异常栈信息,因为异常栈信息是很详细的,可以告诉你哪里出错了,出了什么错
      • 8-23会输出两条语句,因为第一次捕获异常打印输出语句后程序又接着往下执行了,但实际开发中出了异常我们肯定是会让程序终止的呀,前面的已经出错了,继续执行下去又有什么意义呢,所以会把这两段可能会出现异常的代码放在一个try…catch中
    • 25-37演变为51-59:

      规范格式是:

      try{

      ​ //可能出现异常的代码

      }catch(Exception e){//Exception可以捕获一切异常类型

      ​ e.printStackTrace();//直接打印异常栈信息

      }

      所以就演变为了51-59的最终格式

    异常处理方式三:前两者结合

    • 方法直接将异常通过throws抛出去给调用者
    • 调用者收到异常后直接捕获处理
    • 第二种方式那样,方法内部有异常直接自己处理,那么调用者就不知道方法到底执行成功了没有,而多数业务场景中,调用者是需要知道方法是否执行成功,所以方式三的异常处理方式用的最多
    public class ExceptionDemo03 {
        public static void main(String[] args) {
            System.out.println("程序开始。。。。");
            try {
                parseTime("2011-11-11 11:11:11");
                System.out.println("功能操作成功~~~");
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("功能操作失败~~~");
            }
            System.out.println("程序结束。。。。");
        }
    
        public static void parseTime(String date) throws Exception {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date d = sdf.parse(date);
            System.out.println(d);
            InputStream is = new FileInputStream("D:/meinv.jpg");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    运行上述代码会发现,打印的栈信息是在最后而不是在"程序开始。。。。"和"功能操作失败~~~"之间,老师说这是人家底层用了线程机制

    3.6运行时异常的处理机制

    运行时异常可以不处理,编译阶段又不报错。按照理论规则:建议还是处理,只需要在**最外层(main方法中如果还不处理异常,就会抛给虚拟机,所以最外层也就是在main方法中)**捕获处理即可

    public class Test {
        public static void main(String[] args) {
            System.out.println("程序开始。。。。。。。。。。");
            try {
                chu(10, 0);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("程序结束。。。。。。。。。。");
        }
    
        public static void chu(int a , int b) { // throws RuntimeException{
            System.out.println(a);
            System.out.println(b);
            int c = a / b;
            System.out.println(c);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    为什么chu不用写throws RuntimeException呢:

    • 因为异常的默认处理流程就是往上抛,也就是说这里默认就是有throws RuntimeException,我们可以省略不写
    • 运行时异常可以省略抛出,编译阶段不会报错,而编译时异常必须抛出或处理,否则就会编译阶段报错

    3.7异常处理使代码更稳健的案例

    public class Test2 {
        public static void main(String[] args) {
            Scanner sc  = new Scanner(System.in);
            while (true) {
                try {
                    System.out.println("请您输入合法的价格:");
                    String priceStr = sc.nextLine();
                    // 转换成double类型的价格
                    double price = Double.valueOf(priceStr);
    
                    // 判断价格是否大于 0
                    if(price > 0) {
                        System.out.println("定价:" + price);
                        break;
                    }else {
                        System.out.println("价格必须是正数~~~");
                    }
                } catch (Exception e) {
                    System.out.println("输的什么破东西,能不能好好输?");
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    第7行String priceStr = sc.nextLine();中的nextLine()不能用nextDouble()

    • 针对这段代码,如果用nextDouble(),然后用户输入的不是Double类型的数据,原先下一次循环时直接取用户刚刚输入的数据,然后直接报"输的什么破东西,能不能好好输?",while循环会不给用户再次输入的机会而永远无限循环下去
    • nextLine()取得是一行数据,即使用户胡乱输入,也会取得数据执行接下来的操作

    3.8自定义异常

    3.8.1自定义异常概述

    为什么要自定义异常:

    • java无法为这个世界上全部的问题提供异常类
    • 如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了

    自定义异常的好处:

    • 可以使用异常的机制管理业务问题,如提醒程序员注意或提醒用户正确操作
    • 同时一旦出现bug,可以用异常的形式清晰的指出出错的地方
    3.8.2自定义异常的分类:
    3.8.2.1自定义编译时异常
    • 步骤:
      • 定义一个异常类继承Exception
      • 重写构造器
      • 在出现异常的地方用throw new自定义对象抛出
    • 作用:编译时异常是编译阶段就报错,相比较运行时异常提醒更加强烈,**一定需要处理(抛出或捕获)**例如下面的案例第5行调用checkAge();方法就是选择了捕获异常
    
    public class ExceptionDemo {
        public static void main(String[] args) {
            try {
                checkAge(-34);
            } catch (AgeIlleagalException e) {
                e.printStackTrace();
            }
    
        public static void checkAge(int age) throws AgeIlleagalException {
            if(age < 0 || age > 200){
                // 抛出去一个异常对象给调用者
                // throw :在方法内部直接创建一个异常对象,并从此点抛出
                // throws : 用在方法申明上的,抛出方法内部的异常
                throw new AgeIlleagalException(age + " is illeagal!");
            }else {
                System.out.println("年龄合法:推荐商品给其购买~~");
            }
        }
    }
    
    public class AgeIlleagalException extends Exception{
        public AgeIlleagalException() {
        }
        public AgeIlleagalException(String message) {
            super(message);
        }
    }
    
    • 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

    注意点:

    • throw和throws区别
      • throw:在方法内部直接创建一个异常对象,并从此点抛出
      • throws:用在方法申明上,抛出方法内部的异常
    • 写完第15行的throw new AgeIlleagalException(age + " is illeagal!");后会编译报错,因为new的这个异常是编译时异常,所以必须捕获或抛出,既然是自定义,方法内部出现的异常通常一直抛出直到在最外层了再捕获,所以这里我们选择抛出异常:在checkAge的方法申明上加throws AgeIlleagalException
    • 当我们在main方法内调用checkAge()方法时,因为checkAge方法抛出了编译时异常,所以必须处理,要么抛出给JVM要么捕获,这里肯定是用捕获异常
    3.8.2.2自定义运行时异常
    • 步骤:
      • 定义一个异常类继承RuntimeException
      • 重写构造器
      • 在出现异常的地方用throw new自定义对象抛出
    • 作用:提醒不强烈,编译阶段不报错,运行阶段才可能报错
    public class ExceptionDemo {
        public static void main(String[] args) {
            try {
                checkAge2(-23);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void checkAge2(int age) {
            if(age < 0 || age > 200){
                throw new AgeIlleagalRuntimeException(age + " is illeagal!");
            }else {
                System.out.println("年龄合法:推荐商品给其购买~~");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    注意:

    • 因为自定义的这一个是运行时异常,所以在checkAge2内部new该异常对象后不需要处理,默认就会抛出
    • 因为自定义的这一个是运行时异常,所以在main方法内部调用checkAge2方法时可以不用处理该对象,默认会抛给JVM,但规范情况下还是建议在最外层(即main方法中)捕获异常
  • 相关阅读:
    maven3.8.6的仓库镜像配置导致发布失败
    我的 React 最佳实践
    【软件测试】一个边界值事故,领导leader心里苦季度奖金没了还被罚3K......
    ReadingTime-十二月
    K8S之使用Deployment实现滚动更新
    你与年薪百万的项目经理,可能就差一个字母
    基础复习——内容共享——通过ContentProvider封装数据——通过ContentResolver访问数据...
    数据产品读书笔记——认识数据产品经理
    redhat7.6安装weblogic12c
    第四章 ObjectScript 宏预处理器指令
  • 原文地址:https://blog.csdn.net/maxiangyu_/article/details/126843998