业务代码-整合框架-存储-缓存常见错误详解一

一. java空指针和异常:

1.什么是空指针异常(java.lang.NullPointException):

1.1常见的空指针异常案例:

bash 复制代码
public class WhatIsNpe {

    public static class User {

        private String name;
        private String[] address;

        public void print() {
            System.out.println("This is User Class!");
        }

        public String readBook() {
            System.out.println("User Read Imooc Escape!");
            return null;
        }
    }

    /**
     * <h2>自定义一个运行时异常</h2>
     * */
    public static class CustomException extends RuntimeException {}

    public static void main(String[] args) {

        // 第一种情况: 调用了空对象的实例方法
//        User user = null;
//        user.print();

        // 第二种情况: 访问了空对象的属性
//        User user = null;
//        System.out.println(user.name);

        // 第三种情况: 当数组是一个空对象的时候, 取它的长度
//        User user = new User();
//        System.out.println(user.address.length);

        // 第四种情况: null 当做 Throwable 的值
//        CustomException exception = null;
//        throw exception;

        // 第五种情况: 方法的返回值是 null, 调用方直接去使用
        User user = new User();
        System.out.println(user.readBook().contains("MySQL"));
    }
}

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

2.1需要装箱拆箱的类型有:

2.2自动拆箱时出现的空指针异常:

bash 复制代码
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. 变量赋值自动拆箱出现的空指针,Long赋值给long会自动拆箱
        // javac UnboxingNpe.java
        // javap -c UnboxingNpe.class
        Long count = null;
        long count_ = count;

        // 2. 方法传参时自动拆箱引发的空指针,add()方法里的参数是基本数据类型,传入的参数是Integer类型,会自动拆箱
//        Integer left = null;
//        Integer right = null;
//        System.out.println(add(left, right));

        // 3. 用于大小比较的场景,compare()方法里的参数是基本数据类型,传入的参数是Integer类型,会自动拆箱
//        Long left = 10L;
//        Long right = null;
//        System.out.println(compare(left, right));
    }
}

2.3规避自动拆箱应发空指针类型:

  • 基本数据类型优于包装器类型,优先考虑使用基本类型
  • 对于不确定的包装器类型,一定要校验是否是null
  • 对于值为null的包装器类型,考虑是否可以赋值为0

3.字符串,数组,集合在使用时出现空指针:

  • 字符串使用equals时报空指针错误
  • 对象数组虽然new出来了,但是如果没有初始化,一样会报空指针错误
  • List对象add null不报错,但是addAll不能添加null,否则会报空指针错误
bash 复制代码
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 可能会报空指针错误
//        System.out.println(stringEquals("xyz", null));  // 不会报错,打印false
//        System.out.println(stringEquals(null, "xyz"));  // 会报错,因为equals前面是null,null引用会报错

        // 2. 对象数组 new 出来, 但是元素没有初始化
//        User[] users = new User[10];
//        for (int i = 0; i != 10; ++i) {
//            users[i] = new User();
//            users[i].name = "imooc-" + i;  // 会报错,users[i]元素没有初始化,就是null
//        }

        // 3. List对象ddAll传递null会抛出空指针;add 传递null不会报错
        List<User> users = new ArrayList<User>();
        User user = null;
        List<User> users_ = null;
        users.add(user);        // 不会报错
        users.addAll(users_);   //会报错
    }
}

4.optional是容器类,代表存在与不存在,避免空指针异常

bash 复制代码
/**
 1. <h1>学会 Optional, 规避空指针异常</h1>
 2. */
@SuppressWarnings("all")
public class OptionalUsage {

    private static void badUsageOptional() {

        Optional<User> optional = Optional.ofNullable(null);  // 创建optional实例
        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();  // 创建optional实例
        if (optional.isPresent()) {
            System.out.println("User is not null");
        } else {
            System.out.println("User is null");
        }
    }

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

    public static void main(String[] args) {

        // 没有意义的使用方法
        isUserEqualNull();

        User user = null;
        Optional<User> optionalUser = Optional.ofNullable(user);
        // 存在即返回, 空则提供默认值
        optionalUser.orElse(new User());
        // 存在即返回, 空则由函数去产生
        optionalUser.orElseGet(() -> anoymos());
        // 存在即返回, 否则抛出异常
        optionalUser.orElseThrow(RuntimeException::new);

        // 存在才去做相应的处理
        optionalUser.ifPresent(u -> System.out.println(u.getName()));

        // map 可以对 Optional 中的对象执行某种操作, 且会返回一个 Optional 对象
        optionalUser.map(u -> u.getName()).orElse("anymos");

        // map 是可以无限级联操作的
        optionalUser.map(u -> u.getName()).map(name -> name.length()).orElse(0);
    }
}

5.try catch 处理异常

  1. 使用异常不要只是返回码,应该返回更加详细的内容
  2. 主动捕获检查性异常,并对异常详细打印日志或者短信
  3. 保持代码整洁,一个方法中不要有多个try catch 或者嵌套try catch
  4. 捕获更加具体的异常,而不是通用的Exception
  5. 合理的设计自定义异常类
bash 复制代码
/**
 * <h1>Java 异常处理</h1>
 * */
@SuppressWarnings("all")  //屏蔽编译器警告
public class ExceptionProcess {

    private static class User {}

    /**
     * <h2>Java 异常本质 -- 抛出异常</h2>
     * */
    private void throwException() {
        User user = null;
        // 对user进行业务处理
        if (null == user) {
            // 抛出异常
            throw new NullPointerException();
        }
    }

    /**
     * <h2>不能捕获空指针异常</h2>
     * */
    private void canNotCatchNpeException() {
        try {
            throwException(); //抛出异常
        } catch (ClassCastException cce) { // 类型转换异常 ClassCastException
            System.out.println(cce.getMessage()); // 打印异常类信息
            System.out.println(cce.getClass().getName()); // 打印异常类名称
        }
    }

    /**
     * <h2>能够捕获空指针异常</h2>
     * */
    private void canCatchNpeException() {
        try {
            throwException(); //抛出异常
        } catch (ClassCastException cce) { // 类型转换异常 ClassCastException
            System.out.println(cce.getMessage()); // 打印异常类信息
            System.out.println(cce.getClass().getName()); // 打印异常类名称
        } catch (NullPointerException npe) { // 空指针异常 NullPointerException
            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();
    }
}

结果如下:

6.编码中常见的异常(并发修改,类型转换,枚举查找):

  1. 可迭代对象在遍历的同时做修改,则会报并发修改异常
  2. 类型转换不符合java的继承关系,则会报类型转换异常
  3. 枚举在查找时,如果枚举值不存在,不会返回空,而是直接抛出异常

枚举类:

bash 复制代码
package com.imooc.java.escape;

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

    RD,
    QA,
    PM,
    OP;
}
bash 复制代码
package com.imooc.java.escape;

import com.google.common.base.Enums;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

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

    /**
     * 定义user类型
     */
    public static class User {

        private String name;

        /**
         * 构造函数
         */
        public User() {}

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

        public String getName() {
            return name;
        }
    }

    /**
     * 定义继承User类的Manager类
     */
    public static class Manager extends User {}

    /**
     * 定义继承User类的Worker类
     */
    public static class Worker extends User {}

    /**
     * 定义typeIndex常量
     */
    private static final Map<String, StaffType> typeIndex = new HashMap<>(
            StaffType.values().length
    );

    /**
     * 静态代码块,对枚举进行一次遍历,并把遍历结果放在常量typeIndex上
     */
    static {
        for (StaffType value : StaffType.values()) {
            typeIndex.put(value.name(), value);
        }
    }

    /**
     * 并发修改异常
     * @param users
     */
    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();
            }
        }
    }

    /**
     * 根据类型值获取枚举类对象
     * @param type
     * @return
     */
    private static StaffType enumFind(String type) {

        //根据类型值获取枚举类对象
//        return StaffType.valueOf(type);

        // 1. 最普通、最简单的实现
//        try {
//            return StaffType.valueOf(type);
//        } catch (IllegalArgumentException ex) { // IllegalArgumentException参数异常
//            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();

//        Manager m1 = (Manager) user1; //不会产生类型转换异常
//        Manager m2 = (Manager) user2; //会产生类型转换异常,因为user2是Worker类,Worker类与Manager类无联系

//        System.out.println(user2.getClass().getName()); //查看user2对象的类名称
//        System.out.println(user2 instanceof Manager); //instanceof确定user2是否是Manager类

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

7.解决使用try finally的资源没有关闭隐患
资源释放 :打开了资源,使用完之后手动释放(关闭)
资源泄露 :打开了资源,使用之后由于某种原因忘记或出现异常等等没有手动释放

try finally的问题与改进方案:

  • 对单个资源的操作基本不会有问题
  • 当同时操作多个资源时,代码沉余,且存在资源泄露的风险
  • try-with-resources不仅比try-finally方便,而且不容易出错,可以用在单个资源,多个资源中
bash 复制代码
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.Buffer;

/**
 * <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;
    }

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

        // 1. 单个资源的使用与关闭
//        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();
        }
    }
}
bash 复制代码
/**
 * 定义AutoClose类
 */
public class AutoClose implements AutoCloseable {

    @Override
    public void close() {
        System.out.println(">>> close()");
        throw new RuntimeException("Exception in close()");
    }

    public void work() throws MyException {
        System.out.println(">>> work()");
        throw new MyException("Exception in work()");
    }
}
bash 复制代码
/**
 * 自定义异常 MyException
 */
public class MyException extends Exception {

    public MyException() {
        super();
    }

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

二,java计算,集合,接口

1.Bigdecimal 用于精确计算的类:

bash 复制代码
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * <h1>数值计算</h1>
 */
@SuppressWarnings("all")
public class NumberAndTime {

    /**
     * <h2> scale 需要与小数位匹配 </h2>
     */
    private static void scaleProblem() {
        BigDecimal decimal = new BigDecimal("12.222");
        // 给decimal设置精度为2,因为12.222精度是3,设置为2会丢失精度,下面这样写会报错
        BigDecimal result = decimal.setScale(2);
        // 给decimal设置精度为12,因为12.222精度是3,设置为12不会丢失精度,下面这样不会报错,不够后面会用0补
        BigDecimal result = decimal.setScale(12);
        System.out.println(result);
        // 给decimal设置精度为2,设置BigDecimal.ROUND_HALF_UP舍入方式,四舍五入,不会报错
        BigDecimal result = decimal.setScale(2, BigDecimal.ROUND_HALF_UP);
        System.out.println(result);
    }

    /**
     * <h2> BigDecimal 做除法时出现除不尽的情况 </h2>
     */
    private static void divideProblem() {
        // 30除以7,因为30除不尽7,会报错
        System.out.println(new BigDecimal(30).divide(new BigDecimal(7)));
        // 30除以7,虽然30除不尽7,但是进行了四舍五入,保留2两位小数,不会报错
        System.out.println(
                new BigDecimal(30).divide(new BigDecimal(7), 2, BigDecimal.ROUND_HALF_UP)
        );
    }

    /**
     * <h2> 精度问题导致比较结果和预期的不一致 </h2>
     */
    private static void equalProblem() {
        BigDecimal bd1 = new BigDecimal("0");
        BigDecimal bd2 = new BigDecimal("0.0");
        // equals会比较精度是否一致,再判断数字
        System.out.println(bd1.equals(bd2));  // false
        // compareTo只判断值 大于返回正数,小于返回负数,等于返回0
        System.out.println(bd1.compareTo(bd2) == 0); // true
    }

    public static void main(String[] args) throws Exception {
//        scaleProblem();
//        divideProblem();
//        equalProblem();

    }
}

2.SimpleDateFormat 注意点:

bash 复制代码
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * <h1>时间计算</h1>
 */
@SuppressWarnings("all")
public class NumberAndTime {
    /**
     * <h2>
     *     SimpleDateFormat 可以解析大于/等于它定义的时间精度
     *     但是不能解析小于它定义的时间精度
     * </h2>
     */
    private static void formatPrecision() throws Exception {
        // 定义时间SimpleDateFormat
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String time_x = "2020-03-01 10:00:00";
        String time = "2020-03";
        // 可以解析大于/等于它定义的时间精度,把string转换为时间格式
        System.out.println(sdf.parse(time_x));  // Sun Mar 01 0:00:00 CST2020
        // 不能解析小于它定义的时间精度,会报错
        System.out.println(sdf.parse(time)); // 报错
    }

    /**
     * <h2> SimplleDateFormat 存在线程安全问题 </h2>
     */
    private static void threadSafety() {
        // 定义时间SimpleDateFormat
        SimpleDateFormat sdf = new SimpleDateFormat(
                "yyyy-MM-dd HH:mm:ss");
        // 定义线程池
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(
                        10, 100, 1, TimeUnit.MINUTES,
                        new LinkedBlockingDeque<>(1000)
                );
        while (true) {
            threadPoolExecutor.execute(() -> {
                String dateString = "2020-03-01 10:00:00";
                try {
                    // 把string类型转换为时间格式
                    Date parseDate = sdf.parse(dateString);
                    // 再把时间格式转换为string类型
                    String dateString2 = sdf.format(parseDate);
                    // 比较最后和开始的string是否一致
                    System.out.println(dateString.equals(dateString2));
                } catch (ParseException ex) {
                    ex.printStackTrace();
                }
            });
        }
    }

    public static void main(String[] args) throws Exception {
//        formatPrecision();
        threadSafety();
    }
}

3.for循环与迭代器 :

任何实现Iterable接口的对象,都可以使用for-each循环处理

bash 复制代码
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

/**
 * <h1>小小 for 循环, 沾上集合出大问题</h1>
 * */
@SuppressWarnings("all")
public class ForeachOptimize {

    private static Collection<Integer> left =
            Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    private static Collection<Integer> right =
            Arrays.asList(1, 2, 3, 4, 5);

    /**
     * <h2>集合迭代经常犯的错误</h2>
     * */
    private static void wrongIterator() {

//        // 传统方式 - 使用索引(没问题)
//        int[] xyz = new int[]{1, 2, 3, 4, 5};
//        for (int i = 0; i != xyz.length; ++i) {
//            System.out.println(xyz[i]);
//        }
//
//        // 传统方式 - 迭代器(没问题)
//        for (Iterator<Integer> i = left.iterator(); i.hasNext(); ) {
//            System.out.println(i.next());
//        }

        // 嵌套迭代容易出现问题(有问题,l.next()会在循环right时被next,会照成left会被提前next,导致报错
//        for (Iterator<Integer> l = left.iterator(); l.hasNext(); ) {
//            for (Iterator<Integer> r = right.iterator(); r.hasNext(); ) {
//                System.out.println(l.next() * r.next());
//            }
//        }

        // 正确的用法, 嵌套迭代(没问题)
//        for (Iterator<Integer> l = left.iterator(); l.hasNext(); ) {
//            Integer tmp = l.next();
//            for (Iterator<Integer> r = right.iterator(); r.hasNext(); ) {
//                System.out.println(tmp * r.next());
//            }
//        }

        // 直接用foreach更简单如下(没问题)
        for (Integer l : left) {
            for (Integer r : right) {
                System.out.println(l * r);
            }
        }
    }

    private static void square(int value) {
        System.out.println(value * value);
    }

    public static void main(String[] args) {

        wrongIterator();

        // Java8 Iterable.forEach vs for-each(没问题)
        for (Integer l : left) {
            square(l);
        }
        left.forEach(l -> square(l));
        left.forEach(ForeachOptimize::square);
    }
}

4,Lombox常用注解:

注解解析:编译器使用javac对代码进行编译的过程中会先对源代码进行分析,生成一个抽象语法树,然后去调用实现了lombox程序,抽象语法树进行处理,在使用了@Get,@Set注解的时候,会对抽象语法树进行修改,追加get,set方法,然后用修改后的抽象语法树去生成java字节码。

4.1.iPhone属性命名使用@Data会变成iphone,要注意不能一个字母小写,第二个字母大写的命名

bash 复制代码
import lombok.Data;

/**
 * <h1>Java Object</h1>
 * */
@Data
public class Personal {

    private String iPhone;
    private String name;
    private String userName;
}
bash 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * <h1>lombok 工具的使用以及需要避免的坑</h1>
 * */
public class Main {

    /**
     * <h1>lombok 第一个坑</h1>
     * */
    private static void singleAlphabetHump() throws Exception {

        ObjectMapper mapper = new ObjectMapper();

        Personal personal = new Personal();
        personal.setIPhone("8.1");

        // {"name":null,"userName":null,"iphone":"8.1"}
//        System.out.println(mapper.writeValueAsString(personal));

        String json = "{\"name\": \"qinyi\"," +
                "\"userName\": \"qinyi-imooc\",\"iphone\":\"8.1\"}";

        Personal personal1 = mapper.readValue(json, Personal.class);
        System.out.println(personal1);
    }
}

4.2 AppleComputer类的id,name属性是继承父类的,这种要使用equals要在子类AppleComputer中加@EqualsAndHashCode(callSuper = true),表示也比较继承分类的id,name。否则在子类中用equals不会比较父类的属性。

bash 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Computer {

    private Integer id;
    private String name;
}
bash 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AppleComputer extends Computer {

    private long price;
    private String color;

    public AppleComputer(Integer id, String name, long price, String color) {

        super(id, name);
        this.price = price;
        this.color = color;
    }
}
bash 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * <h1>lombok 工具的使用以及需要避免的坑</h1>
 * */
public class Main {
    /**
     * <h2>lombok 的第二个坑</h2>
     * */
    private static void equalsAndHashCodeBug() {

        AppleComputer computer1 = new AppleComputer(
                1, "Mac Pro", 1L, "yellow"
        );
        AppleComputer computer2 = new AppleComputer(
                2, "Mac Air", 1L, "yellow"
        );

        System.out.println(computer1.equals(computer2));
    }

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

//        singleAlphabetHump();

        equalsAndHashCodeBug();
    }
}

5.抽象类和接口:
抽象类 :子类的通用特性,就是子类都有的特性,包含了属性和行为。对类本质的抽象,表达的是is a的关系。
接口 :定义行为,并不关心谁去实现,不是所有子类都有的行为。对行为的抽象,表达的是like a 的关系。

**抽象类与接口的相同点:**接口中的方法(java8改变了这一语法)和抽象类中的抽象方法都不能有方法体,并且必须在子类中实现。都可以被继承,但是不能被实例化。
抽象类与接口的不同点:使用的语法不同,抽象类用extends,接口用implements。接口中只能定义常量,不能表达对象状态,抽象类可以。接口中的方法必须是public类型的,抽象类没有限制。类可以同时实现多个接口,但是只能继承一个抽象类

三.泛型,反射,编译优化

1. Serializable接口:一个标记接口,不用实现任何方法,标记当前类对象是可以序列化的

序列化:将对象写入到IO流中

反序列化:从Io流中恢复对象

(1)序列化,反序列化例子:

bash 复制代码
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.Serializable;

/**
 * <h1>Java Object</h1>
 * */
@Setter
@Getter
@ToString
public class People implements Serializable {

    private Long id;

    public People() {}

    public People(Long id) {
        this.id = id;
    }
}
bash 复制代码
/**
 * <h1>序列化和反序列化</h1>
 * */
@SuppressWarnings("all")
public class Main {

    /**
     * <h1>序列化和反序列化 People 对象</h1>
     * */
    private static void testSerializablePeople() throws Exception {

        //1.序列化的步骤
        // 用于存储序列化的文件
        File file = new File("/tmp/people_10.java_");
        People p = new People(10L);
        // 创建一个输出流
        ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(file)
        );
        // 输出可序列化对象
        oos.writeObject(p);
        // 关闭输出流
        oos.close();

        // 2.反序列化的步骤
        // 创建一个输入流
        ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream(file)
        );
        // 得到反序列化的对象
        Object newPerson = ois.readObject();
        // 关闭输入流
        ois.close();
        System.out.println(newPerson);
    }
    
public static void main(String[] args) throws Exception {
        testSerializablePeople();
    }

注意:

  • 子类实现序列化接口,父类没有实现,如果父类有无参构造方法,子类可以实现序列化,否则会报错
  • 类中存在引用对象,这个引用对象是可序列化的才能实现序列化
  • 同一个对象多次序列化,只会序列化一次,后面的保存序列化的编号,所以如果在第一次序列化后,在改对象属性值再进行序列化,这样还是保存的是第一次序列化数据。

2.泛型:参数化类型,就是将类型由原来的具体的类型参数化

泛型作用:在不创建新类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型

bash 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * <h1>理解泛型</h1>
 * */
@SuppressWarnings("all")
public class Genericity {

    /**
     * <h2>简单使用泛型</h2>
     * */
    private static void easyUse() throws Exception {

        List<String> left = new ArrayList<>();
        List<Integer> right = new ArrayList<>();

//        System.out.println(left.getClass());
//        System.out.println(left.getClass() == right.getClass());

//        if (left instanceof ArrayList<Double>) {}
//        if (left instanceof ArrayList) {
//
//        }
//
//        if (left instanceof ArrayList<?>) {}

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.getClass().getMethod("add", Object.class).invoke(list, "abcd");
        list.getClass().getMethod("add", Object.class).invoke(list, 1.2);

        for (int i = 0; i != list.size(); ++i) {
            System.out.println(list.get(i));
        }
    }

    /**
     * <h2>泛型是先检查再编译</h2>
     * */
    private static void checkAndCompile() {

        ArrayList<String> list = new ArrayList<>();
        list.add("1234");
//        list.add(123);
    }

    /**
     * <h2>泛型不支持继承</h2>
     * */
    private static void genericityCanNotExtend() {

        // 第一类错误
//        ArrayList<String> first = new ArrayList<Object>();
//
//        ArrayList<Object> list1 = new ArrayList<>();
//        list1.add(new Object());
//        ArrayList<String> list2 = list1;

        // 第二类错误
//        ArrayList<Object> second = new ArrayList<String>();
//
//        ArrayList<String> list1 = new ArrayList<>();
//        list1.add(new String());
//        ArrayList<Object> list2 = list1;
    }

    /**
     * <h2>泛型类型变量不能是基本数据类型</h2>
     * */
    private static void baseTypeCanNotUseGenericity() {

//        List<int> invalid = new ArrayList<>();
    }

    /**
     * <h2>泛型的类型参数只能是类类型, 不能是简单类型</h2>
     * */
    private static <T> void doSomething(T... values) {
        for (T value : values) {
            System.out.println(value);
        }
    }

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

//        easyUse();

        Integer[] ints1 = new Integer[]{1, 2, 3};
        int[] ints2 = new int[]{1, 2, 3};

        doSomething(ints1);
        System.out.println("----------------");
        doSomething(ints2);
    }
}

3.不是所有的字符串拼接都使用StringBuilder:

(1)循环里面使用"+"去拼接字符串,会造成空间浪费,每次拼接的结果都需要创建新的不可变类;时间浪费,创建的新不可变类需要初始化,产生大量临时对象,影响young gc,full gc。

(2)StringBuffer和StringBuilder的区别:

线程安全:StringBuffer线程安全和StringBuilder不安全

缓冲区:StringBuffer使用缓冲区和StringBuilder不使用缓冲区

性能:StringBuffer性能远大于StringBuilder

四.java线程安全

多线程操作变量过程:

1.Synchronized关键字:

  • Synchronized方法不会被继承,需要在子类中重新指定
  • Synchronized可以标注在方法声明,方法体中
  • jdk对Synchronized进行了优化,主要表现在偏向锁,轻量级锁,重量级锁
bash 复制代码
public class MainActive implements Runnable {

    private int value = 0;

    @Override
    public synchronized void run() {

        String name = Thread.currentThread().getName();

        while (true) {

            if (value < 1000) {
                System.out.println(name + " start : " + value);
                value++;
                System.out.println(name + " done : " + value);
            } else {
                break;
            }
        }
    }
}

2.阻塞队列的核心定义:支持两个附加操作的队列

  • 队列空则等
  • 队列满则等

四种处理方法:

生产者:

bash 复制代码
import java.util.concurrent.BlockingQueue;

/**
 * <h1>生产者</h1>
 * */
// 实现Runnable接口,让Producer交给一个独立的线程去执行
public class Producer implements Runnable {

    // 定义阻塞队列
    private final BlockingQueue<Integer> blockingQueue;
    private static int element = 0;

    // 构造函数,给阻塞队列初始化
    public Producer(BlockingQueue<Integer> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

//    @Override
//    public void run() {
//
//        while (element < 100) {
//            System.out.println("Produce: " + element);
              // 给阻塞队列添加element,在队列满时不会等待,会返回false,会导致没有消费所有生成的生产者
//            blockingQueue.offer(element++);
//        }
//
//        System.out.println("Produce Done!");
//    }

    @Override
    public void run() {

        try {
            while (element < 100) {
                System.out.println("Produce: " + element);
                blockingQueue.put(element++);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Produce Done!");
    }
}

消费者:

bash 复制代码
import java.util.concurrent.BlockingQueue;

/**
 * <h1>消费者</h1>
 * */
// 实现Runnable接口,让Producer交给一个独立的线程去执行
public class Consumer implements Runnable {

    // 定义阻塞队列
    private final BlockingQueue<Integer> blockingQueue;

    // 构造函数,给阻塞队列初始化
    public Consumer(BlockingQueue<Integer> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void run() {

        try {
            while (true) {
                // 在阻塞队列中取元素
                int value = blockingQueue.take();
                System.out.println("Consume: " + value);
                if (value >= 99) {
                    break;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Consumer Done!");
    }
}

阻塞队列的应用:

bash 复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * <h1>阻塞队列的应用</h1>
 * */
public class Main {

    public static void main(String[] args) {

        // 3是容量
        BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(
                3, true
        );

        Producer producer = new Producer(blockingQueue);
        Consumer consumer = new Consumer(blockingQueue);

        // 创建线程
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

3.CopyOnWrite 是一种优化策略,是一种延时懒惰策略,是对读写分离思想的实现

优点:并发读不需要加锁,提高了程序的并发读

缺点:内存占用问题(因为会复制,再写),一致性问题(因为读是读旧容器,写是在新容器,只能保证最终一致性,不能保证实时一致性)

适应场景:适合读多写少的场景
CopyOnWrite 对比 Collections.synchronizedList:

  • 它们都可以实现线程安全的集合(列表)
  • CopyOnWrite 的写操作不仅需要加锁,而且内部对数组进行了 copy,所以,写性能比Collections.synchronizedList 要差
  • Collections.synchronizedList 读操作有 synchronized 关键字修饰,而 CopyOnWrite是直接读,所以,读性能CopyOnWrite 更好
bash 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * <h1>使用 CopyOnWrite 并发读写不会抛出异常</h1>
 * */
public class TaskPoolNoProblem {

    private static final List<String> tasks = new CopyOnWriteArrayList<>();

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

        for (int i = 0; i != 10; ++i) {
            tasks.add("task-" + i);
        }

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    tasks.add("task-x");
                }
            }
        });
        thread.setDaemon(true);
        thread.start();

        Thread.sleep(1000L);

        for (String task : tasks) {
            System.out.println(task);
        }
    }
}

4.jdk线程池的核心思想:

jdk中线程池的核心实现类是ThreadPoolExecutor

ThreadPoolExecutor 怎么同时维护线程以及执行任务:

线程池的运行状态:

线程池的状态转换图:

jdk线程池的错误用法:

  • 固定大小的线程池会导致内存急剧上升
  • jdk提供的线程池不符合需求,自己定义线程池,但是线程池参数很多,容易出错
bash 复制代码
/**
 * <h1>读书的任务</h1>
 * */
public class Reading implements Runnable {

    private int count;
    private String name;

    public Reading(int count, String name) {
        this.count = count;
        this.name = name;
    }

    @Override
    public void run() {

        while (count > 0) {

            System.out.println(Thread.currentThread().getName() + " reading " + name);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }

            --count;
        }
    }
}
bash 复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class EasyUseThreadPool {

    /**
     * <h1>简单使用线程池</h1>
     * */
    private static void useFixedThreadPool(int threadCount) {

        ExecutorService executor = Executors.newFixedThreadPool(threadCount);

        Runnable runnable01 = new Reading(3, "Java 编程思想");
        Runnable runnable02 = new Reading(2, "Spring 实战");
        Runnable runnable03 = new Reading(3, "SpringBoot 实战");
        Runnable runnable04 = new Reading(1, "MySQL 权威指南");
        Runnable runnable05 = new Reading(2, "SpringCloud 实战");

        executor.execute(runnable01);
        executor.execute(runnable02);
        executor.execute(runnable03);
        executor.execute(runnable04);
        executor.execute(runnable05);

        executor.shutdown();
    }

    /**
     * <h2>自定义线程池</h2>
     * */
    private static void customThreadPool() {

        // 自定义线程池
        ThreadPoolExecutor custom1 = new ThreadPoolExecutor(
                1, 1, 30, TimeUnit.MINUTES,
                new ArrayBlockingQueue<Runnable>(2)
        );
        // 自定义线程池
        ThreadPoolExecutor custom2 = new ThreadPoolExecutor(
                1, 1, 30, TimeUnit.MINUTES,
                new ArrayBlockingQueue<Runnable>(2),
                // 拒绝策略
                new CustomRejectHandler()
        );

        for (int i = 0; i != 5; ++i) {
            custom2.execute(new Reading(3, "Java 编程思想"));
        }

        custom2.shutdown();
    }

    // 自定义拒绝策略
    private static class CustomRejectHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

            try {
                // executor.getQueue() 拿到阻塞队列
                executor.getQueue().put(r);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

//        useFixedThreadPool(3);
        customThreadPool();
    }
}

怎么监控线程池的运行状态:

bash 复制代码
import java.util.Date;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * <h1>自定义线程池工厂, 带有监控功能</h1>
 * */
public class ExecutorsUtil extends ThreadPoolExecutor {

    // 线程池的关闭,统计已经执行的,正在执行的阻塞的的线程池
    @Override
    public void shutdown() {
        System.out.println(
                String.format(this.poolName +
                                "将会被关闭的. 已经执行的: %d," +
                        "正在执行的: %d, 阻塞的: %d",
                        this.getCompletedTaskCount(),
                        this.getActiveCount(),
                        this.getQueue().size()
                )
        );
        super.shutdown();
    }

    // 线程池即将关闭,统计已经执行的,正在执行的阻塞的的线程池
    @Override
    public List<Runnable> shutdownNow() {
        System.out.println(
                String.format(this.poolName +
                                "将会被关闭的. 已经执行的: %d," +
                                "正在执行的: %d, 阻塞的: %d",
                        this.getCompletedTaskCount(),
                        this.getActiveCount(),
                        this.getQueue().size()
                )
        );
        return super.shutdownNow();
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        // 当前线程的开始时间
        startTimes.put(String.valueOf(r.hashCode()), new Date());
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        // 获取开始时间
        Date startDate = startTimes.remove(String.valueOf(r.hashCode()));
        // 结束的时间
        Date finishDate = new Date();
        // 任务执行的时间
        long diff = finishDate.getTime() - startDate.getTime();
        System.out.println(String.format("任务执行的时间: %d", diff));
    }

    // 固定大小线程池方法
    public static ExecutorService newFixedThreadPool(int nThreads, String poolName) {
        return new ExecutorsUtil(
                nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>(), poolName
        );
    }

    // 存储每个线程任务开始执行的时间
    private ConcurrentHashMap<String, Date> startTimes;
    // 线程池的名称
    private String poolName;

    // 编写构造方法
    public ExecutorsUtil(int corePoolSize, int maximumPoolSize, long keepAliveTime,
                         TimeUnit unit, BlockingQueue<Runnable> workQueue,
                         String poolName) {
        // 调用父类的构造函数
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit,
                workQueue, new ExecutorsUtil.EventThreadFactory(poolName));
        this.startTimes = new ConcurrentHashMap<>();
        this.poolName = poolName;
    }

    // 自定义线程池工厂
    static class EventThreadFactory implements ThreadFactory {

        // 原子类AtomicInteger,poolNumber存储线程池的编号
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        // 把线程池的线程放在线程池组里
        private final ThreadGroup group;
        // 线程的编号
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        // 用来给线程池起一个名字
        private final String namePrefix;

        EventThreadFactory(String poolName) {
            SecurityManager s = System.getSecurityManager();
           // 线程池组 s.getThreadGroup()获取线程池组;Thread.currentThread().getThreadGroup()获取当前线程的线程池组
            group = (s != null) ? s.getThreadGroup()
                    : Thread.currentThread().getThreadGroup();
            namePrefix = poolName + "-pool-" + poolNumber.getAndIncrement()
                    + "-thread";
        }

        @Override
        public Thread newThread(Runnable r) {
            // 新增线程
            Thread t = new Thread(
                    // group线程池组 r要执行的任务 namePrefix + threadNumber.getAndIncrement()当前线程的名称
                    group, r, namePrefix + threadNumber.getAndIncrement(),
                    // 0 默认值为0,当前线程指定栈的大小
                    0
            );
            // 判断是否是守护方式
            if (t.isDaemon()) {
                // 设置不是守护方式
                t.setDaemon(false);
            }
            // 检验优先级
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                // 设置为一个正常优先级的线程
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }
}
bash 复制代码
import java.util.concurrent.ExecutorService;

/**
 * <h1>可监控的线程池</h1>
 * */
public class Main {

    public static void main(String[] args) {

        ExecutorService executorService = ExecutorsUtil.newFixedThreadPool(
                10, "imooc-qinyi-"
        );

        Runnable runnable01 = new Reading(3, "Java 编程思想");
        Runnable runnable02 = new Reading(2, "Spring 实战");
        Runnable runnable03 = new Reading(3, "SpringBoot 实战");
        Runnable runnable04 = new Reading(1, "MySQL 权威指南");
        Runnable runnable05 = new Reading(2, "SpringCloud 实战");

        executorService.execute(runnable01);
        executorService.execute(runnable02);
        executorService.execute(runnable03);
        executorService.execute(runnable04);
        executorService.execute(runnable05);

        executorService.shutdown();
    }
}

4.1ThreadLocal :每个线程需要自己独立的实例且该实例需要在多个方法中被使用

ThreadLocal误区:不支持继承,遇到线程池,如果不及时清理现场,会造成数据混乱

ThreadLocal的实现方式,维护线程与实例的映射:

方式一:

方式二:

相关推荐
吾日三省吾码3 小时前
JVM 性能调优
java
弗拉唐4 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi774 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3434 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀4 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20205 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深5 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
shuangrenlong5 小时前
slice介绍slice查看器
java·ubuntu
牧竹子5 小时前
对原jar包解压后修改原class文件后重新打包为jar
java·jar