目录
一、空指针
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) {
= 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);
}
}
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 (().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(快速失败、安全失败概念)
原理及解决方案:(解决方案就是我们下面讲的那两个)
解决方案:1、使用迭代器对象,不要使用传进来的list对象 2、继续使用传进来的list对象,但是不用ArrayList用CopyOnWriteArrayList(CopyOnWriteArrayList能解决该问题)(利用了并发容器中迭代器的弱一致性)。
第一种方案:
第二种方案:
至于关于CopyOnWriteArrayList的可以看我写的:
虽然上面可以解决迭代器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的异常,此时就会对调试过程比较难解决等问题。
至于怎么解决:
即使用了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中的坑很多,如果后续对于空指针和异常的坑,会在本文持续更新。
































