目录
1)、没初始化的对象(万物皆对象,java实体类、数组、异常对象、空内容等都可以看出对象,一个对象如果没初始化,使用其内容时就会报空指针异常)
2)编码中常见的异常(并发修改、类型转换、枚举查找及其解决方法)
1)并发修改异常(注意点)
一、空指针
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中的坑很多,如果后续对于空指针和异常的坑,会在本文持续更新。