Java开发避坑指南——空指针、异常

目录

 

一、空指针

1)、没初始化的对象(万物皆对象,java实体类、数组、异常对象、空内容等都可以看出对象,一个对象如果没初始化,使用其内容时就会报空指针异常)

2)赋值时自动拆箱出现空指针

3)、字符串、数组、集合出现空指针异常

4)使用Optional避免空指针时,应该注意什么?

二、异常

1)、try  catch怎么解决好异常?

2)编码中常见的异常(并发修改、类型转换、枚举查找及其解决方法)

枚举类:

1)并发修改异常(注意点)

3)怎么解决try  finally资源泄露隐患(注意点)


一、空指针

1)、没初始化的对象(万物皆对象,java实体类、数组、异常对象、空内容等都可以看出对象,一个对象如果没初始化,使用其内容时就会报空指针异常)

public class UserDemo {

    private String username;
    private String[] arrys;

    public void pri(){
        System.out.println("这是UserDemo的方法");
    }

    public String readBook() {
        System.out.println("这是第五种情况");
        return null;
    }
    /**
     * <h2>自定义一个运行时异常</h2>
     * */
    public static class CustomException extends RuntimeException {}
    
    public static void main(String[] args) {
        UserDemo userDemo = null;
        //第一种情况:调用没初始化对象的方法
        userDemo.pri();

        //第二种情况:调用初始化对象的属性
        System.out.println(userDemo.username);

        //第三种情况:对象初始化了,但调用了对象中没初始化的数组
        //以万物皆对象的角度看,数组也是一个对象,该对象没初始化所以调用了也会报空指针异常
        UserDemo userDemo1 = new UserDemo();
        System.out.println(userDemo1.arrys.length);

        //第四种情况,自定义异常类且不进行初始化就抛出去该异常 // 第四种情况: null 当做 Throwable 的值
        CustomException exception = null;
        throw exception;
        
        // 第五种情况: 方法的返回值是 null, 调用方直接去使用
        //readBook返回void,而这里拿着一个空内容进行contains方法判断是否包含MySQL
        UserDemo userDemo2 = new UserDemo();
        System.out.println(userDemo.readBook().contains("MySQL"));
    }
}

我们在工作中常常会犯一下错误:

1、前端传来的request/其他参数等时,没有进行null值判断就直接默认是有值,所以就进行了有值情况下的逻辑判断,从而到最后造成了空指针的情况。

2、已经给user初始化了,后面再调用getUser进行赋值,此时可能会因为getUser返回的是null而形成空指针问题,这是就要修改为先判断getUser是否为null,为空则用new User(),不为空则使用getUser。
                

2)赋值时自动拆箱出现空指针

这里分析三种:1、变量赋值自动拆箱出现的空指针   2、方法传参时自动拆箱出现的空指针 3、 用于大小比较的场景

/**
 * <h1>自动拆箱引发的空指针问题</h1>
 * */
@SuppressWarnings("all")
public class UnboxingNpe {

    private static int add(int x, int y) {
        return x + y;
    }

    private static boolean compare(long x, long y) {
        return x >= y;
    }

    public static void main(String[] args) {

        // 1. 变量赋值自动拆箱出现的空指针
        // javac UnboxingNpe.java
        // javap -c UnboxingNpe.class
        Long count = null;
        long count_ = count;

        // 2. 方法传参时自动拆箱引发的空指针
//        Integer left = null;
//        Integer right = null;
//        System.out.println(add(left, right));

        // 3. 用于大小比较的场景
//        Long left = 10L;
//        Long right = null;
//        System.out.println(compare(left, right));
    }
}

那为什么会报空指针异常?
我们先将java文件编译成class文件,进入到该类的目录下,执行javac UnboxingNpe.java命令获取到class文件,然后再将class文件转换为汇编(命令:javap -c UnboxingNpe.class)

3)、字符串、数组、集合出现空指针异常

/**
 * <h1>字符串、数组、集合在使用时出现空指针</h1>
 * */
@SuppressWarnings("all")
public class BasicUsageNpe {

    private static boolean stringEquals(String x, String y) {
        return x.equals(y);
    }

    public static class User {
        private String name;
    }

    public static void main(String[] args) {

        // 1. 字符串使用 equals 可能会报空指针错误
//此时就只需要保证去调用equals的对象不是null即可
//        System.out.println(stringEquals("xyz", null));  //该方法返回flase,不报异常
//        System.out.println(stringEquals(null, "xyz"));  //返回异常,null去调用equals方法

        // 2. 对象数组 new 出来, 但是元素没有初始化
//此时只要在循环中加上users[i] = new User(); 即不会报空指针异常
//        User[] users = new User[10];
//        for (int i = 0; i != 10; ++i) {         
//            users[i].name = "imooc-" + i;
//        }

        // 3. List 对象 addAll 传递 null 会抛出空指针
        List<User> users = new ArrayList<User>();
        //空对象
        User user = null;
        //空集合
        List<User> users_ = null;
       //往List对象中分别加入
        users.add(user); //add方法不会报错
        users.addAll(users_);  //addAll会报错,因为addALL方法中会调用该形参的一个方法(未初始化对象调用方法是会报空指针异常的)
    }
}

调用equals的对象不能是null(未初始化对象调用方法会报异常),调用List对象的addAll方法不能传进一个null值(addAll方法中会使用形参(一般传进来一个LIst对象)调用其方法,未初始化对象调用方法会报异常)。

4)使用Optional避免空指针时,应该注意什么?

java新特性。之前用null代表一个对象存不存在,java后用Optional表示一个对象存不存在。

/**
 * <h1>学会 Optional, 规避空指针异常</h1>
 * */
@SuppressWarnings("all")
public class OptionalUsage {

    private static void badUsageOptional() {

        Optional<User> optional = Optional.ofNullable(null);
        User user = optional.orElse(null); // good
        user = optional.isPresent() ? optional.get() : null; // bad
    }

    public static class User {
        private String name;

        public String getName() {
            return name;
        }
    }

    private static void isUserEqualNull() {

        User user = null;
        if (user != null) {
            System.out.println("User is not null");
        } else {
            System.out.println("User is null");
        }

        Optional<User> optional = Optional.empty();
        if (optional.isPresent()) {
            System.out.println("User is not null");
        } else {
            System.out.println("User is null");
        }
    }

    private static User anoymos() {
        return new User();
    }

这个等我看完java8新特性后在回来

二、异常

1)、try  catch怎么解决好异常?

        

/**
 * <h1>Java 异常处理</h1>
 * */
@SuppressWarnings("all")
public class ExceptionProcess {

    private static class User {}

    /**
     * <h2>Java 异常本质 -- 抛出异常</h2>
     * */
    private void throwException() {

        User user = null;
        // ....
        if (null == user) {
            throw new NullPointerException();
        }
    }

    /**
     * <h2>不能捕获空指针异常</h2>
     * */
    private void canNotCatchNpeException() {

        try {
            throwException();
        } catch (ClassCastException cce) {
            System.out.println(cce.getMessage());
            System.out.println(cce.getClass().getName());
        }
    }

    /**
     * <h2>能够捕获空指针异常</h2>
     * */
    private void canCatchNpeException() {

        try {
            throwException();
        } catch (ClassCastException cce) {
            System.out.println(cce.getMessage());
            System.out.println(cce.getClass().getName());
        } catch (NullPointerException npe) {
            System.out.println(npe.getMessage());
            System.out.println(npe.getClass().getName());
        }
    }

    public static void main(String[] args) {

        ExceptionProcess process = new ExceptionProcess();
        process.canCatchNpeException();
        process.canNotCatchNpeException();
    }
}

2)编码中常见的异常(并发修改、类型转换、枚举查找及其解决方法)

/**
 * <h1>编码中的常见的异常</h1>
 * */
@SuppressWarnings("all")
public class GeneralException {

    public static class User {

        private String name;

        public User() {}

        public User(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    public static class Manager extends User {}

    public static class Worker extends User {}

    private static final Map<String, StaffType> typeIndex = new HashMap<>(
            StaffType.values().length
    );

    static {
        for (StaffType value : StaffType.values()) {
            typeIndex.put(value.name(), value);
        }
    }

    private static void concurrentModificationException(ArrayList<User> users) {

        // 直接使用 for 循环会触发并发修改异常
        // 边循环边进行删除操作。异常原因:快速失败机制
//        for (User user : users) {
//            if (user.getName().equals("imooc")) {
//                users.remove(user);
//            }
//        }

        // 使用迭代器则没有问题
        Iterator<User> iter = users.iterator();
        while (iter.hasNext()) {
            User user = iter.next();
            if (user.getName().equals("imooc")) {
                iter.remove();
            }
        }
    }

    private static StaffType enumFind(String type) {

//        return StaffType.valueOf(type);
//枚举类异常解决方案
        // 1. 最普通、最简单的实现
//        try {
//            return StaffType.valueOf(type);
//        } catch (IllegalArgumentException ex) {
//            return null;
//        }

        // 2. 改进的实现, 但是效率不高
//        for (StaffType value : StaffType.values()) {
//            if (value.name().equals(type)) {
//                return value;
//            }
//        }
//        return null;

        // 3. 静态 Map 索引, 只有一次循环枚举的过程
//        return typeIndex.get(type);

        // 4. 使用 Google Guava Enums, 需要相关的依赖
        return Enums.getIfPresent(StaffType.class, type).orNull();
    }

    public static void main(String[] args) {

        // 1. 并发修改异常
//        ArrayList<User> users = new ArrayList<User>(
//                Arrays.asList(new User("qinyi"), new User("imooc"))
//        );
//        concurrentModificationException(users);

        // 2. 类型转换异常
//两个子类
//        User user1 = new Manager();
//        User user2 = new Worker();
//一个成功一个失败(m1对,m2出错)
//        Manager m1 = (Manager) user1;
//        Manager m2 = (Manager) user2;
//解决方案
// 1、获取本身的类型              System.out.println(user2.getClass().getName());
// 2、用instanceof判断是否能转换       System.out.println(user2 instanceof Manager);

        // 3. 枚举查找异常
        System.out.println(enumFind("RD"));
        System.out.println(enumFind("abc"));
    }
}

枚举类:

/**
 * <h1>员工类型枚举类</h1>
 * */
public enum StaffType {

    RD,
    QA,
    PM,
    OP;
}

1)并发修改异常

这里的对于ArrayList的迭代过程进行修改时的情况要进行说明下:
对于迭代中修改会报错的情况:

public static void main(String[] args) {
      Collection<String> list = new ArrayList<String>();
      list.add("张三");
      list.add("李四");
       list.add("王武");

   iterator(list);
     // for1(list);
   }


private static void iterator(Collection<String> list){
     
    Iterator<String> it = list.iterator();
   while(it.hasNext()){
              String s = it.next();
              if("张三".equals(s)){
                    list.remove(s);
               }else{
           System.out.println(s);
          }
      }
}

private static void for1(Collection<String> list){
     for(String s :list){
           if("张三".equals(s)){
                    list.remove(s);
           }else{
               System.out.println(s);
           }
       }
   }
}

此时都会报错:(报错原因:快速失败机制)

至于快速失败:https://www.nowcoder.com/questionTerminal/95e4f9fa513c4ef5bd6344cc3819d3f7(快速失败、安全失败概念)

                         原理及解决方案:https://blog.csdn.net/zymx14/article/details/78394464(解决方案就是我们下面讲的那两个)

解决方案:1、使用迭代器对象,不要使用传进来的list对象     2、继续使用传进来的list对象,但是不用ArrayList用CopyOnWriteArrayList(CopyOnWriteArrayList能解决该问题)(利用了并发容器中迭代器的弱一致性)。

第一种方案:https://blog.csdn.net/dream_broken/article/details/9323157

第二种方案:https://www.cnblogs.com/geekdc/p/10289192.html

至于关于CopyOnWriteArrayList的可以看我写的:https://blog.csdn.net/qq_35599414/article/details/105477634

虽然上面可以解决迭代器1报错,但一般还是推荐使用java8的Stream流式操作。

 

其他异常的两个解决方案在代码中说明了

3)怎么解决try  finally资源泄露隐患

Main类:

/**
 * <h1>解决使用 try finally 的资源泄露隐患</h1>
 * */
public class Main {

    /**
     * <h2>传统的方式实现对资源的关闭</h2>
     * */
    private String traditionalTryCatch() throws IOException {

        // 1. 单一资源的关闭
//        String line = null;
//        BufferedReader br = new BufferedReader(new FileReader(""));
//        try {
//            line = br.readLine();
//        } finally {
//            br.close();
//        }
//        return line;

        // 2. 多个资源的关闭
        // 第一个资源
        InputStream in = new FileInputStream("");
        try {
            // 第二个资源
            OutputStream out = new FileOutputStream("");
            try {
                byte[] buf = new byte[100];
                int n;

                while ((n = in.read(buf)) >= 0)
                    out.write(buf, 0, n);
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }

        return null;
    }

    
}

上面中使用try finally对单个资源的释放是比较简单的,而当要释放多个资源时却会发现比较冗余凌乱。还有一个问题,如果在finally和try都会出现异常,而此时finally的异常会覆盖调try的异常,此时就会对调试过程比较难解决等问题。

至于怎么解决:https://blog.csdn.net/seanxwq/article/details/90175320

即使用了try with resources:将资源的创建写进try的小括号中,大括号再写相应的操作。操作完成后会自动对资源进行关闭——try(资源创建){资源执行逻辑}   。执行完后会自动进行相应资源的关闭。catch、finally等依旧可以使用。并且不会出现异常覆盖的情况(两个异常都会打印)

/**
     * <h2>java7 引入的 try with resources 实现自动的资源关闭</h2>
     * */
    private String newTryWithResources() throws IOException {

        // 1. 单个资源的使用与关闭

//将资源的创建写进try的括号中
//大括号再写相应的操作。操作完成后会自动对资源进行关闭
//        try (BufferedReader br = new BufferedReader(new FileReader(""))) {
//            return br.readLine();
//        }

        // 2. 多个资源的使用与关闭
        try (FileInputStream in = new FileInputStream("");
             FileOutputStream out = new FileOutputStream("")
        ) {
            byte[] buffer = new byte[100];
            int n = 0;
            while ((n = in.read(buffer)) != -1) {
                out.write(buffer, 0, n);
            }
        }
        return null;
    }

    public static void main(String[] args) throws MyException {

//        AutoClose autoClose = new AutoClose();
//        try {
//            autoClose.work();
//        } finally {
//            autoClose.close();
//        }

        try (AutoClose autoClose = new AutoClose()) {
            autoClose.work();
        }
    }

java中的坑很多,如果后续对于空指针和异常的坑,会在本文持续更新。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值