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

一. java空指针和异常:

1.什么是空指针异常(java.lang.NullPointException):
在这里插入图片描述
1.1常见的空指针异常案例:

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自动拆箱时出现的空指针异常:

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.classLong 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,否则会报空指针错误
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是容器类,代表存在与不存在,避免空指针异常

/**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); // gooduser = 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. 合理的设计自定义异常类
/*** <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) { // 类型转换异常 ClassCastExceptionSystem.out.println(cce.getMessage()); // 打印异常类信息System.out.println(cce.getClass().getName()); // 打印异常类名称}}/*** <h2>能够捕获空指针异常</h2>* */private void canCatchNpeException() {try {throwException(); //抛出异常} catch (ClassCastException cce) { // 类型转换异常 ClassCastExceptionSystem.out.println(cce.getMessage()); // 打印异常类信息System.out.println(cce.getClass().getName()); // 打印异常类名称} catch (NullPointerException npe) { // 空指针异常 NullPointerExceptionSystem.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. 枚举在查找时,如果枚举值不存在,不会返回空,而是直接抛出异常

枚举类:

package com.imooc.java.escape;/*** <h1>员工类型枚举类</h1>* */
public enum StaffType {RD,QA,PM,OP;
}
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方便,而且不容易出错,可以用在单个资源,多个资源中
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();}}
}
/*** 定义AutoClose类*/
public class AutoClose implements AutoCloseable {@Overridepublic 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()");}
}
/*** 自定义异常 MyException*/
public class MyException extends Exception {public MyException() {super();}public MyException(String message) {super(message);}
}

二,java计算,集合,接口

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


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只判断值 大于返回正数,小于返回负数,等于返回0System.out.println(bd1.compareTo(bd2) == 0); // true}public static void main(String[] args) throws Exception {
//        scaleProblem();
//        divideProblem();
//        equalProblem();}
}

2.SimpleDateFormat 注意点:


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 {// 定义时间SimpleDateFormatSimpleDateFormat 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() {// 定义时间SimpleDateFormatSimpleDateFormat 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循环处理


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,要注意不能一个字母小写,第二个字母大写的命名

import lombok.Data;/*** <h1>Java Object</h1>* */
@Data
public class Personal {private String iPhone;private String name;private String userName;
}

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不会比较父类的属性。


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class Computer {private Integer id;private String name;
}
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;}
}
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)序列化,反序列化例子:

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;}
}
/*** <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.泛型:参数化类型,就是将类型由原来的具体的类型参数化
泛型作用:在不创建新类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型
在这里插入图片描述


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进行了优化,主要表现在偏向锁,轻量级锁,重量级锁
public class MainActive implements Runnable {private int value = 0;@Overridepublic 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.阻塞队列的核心定义:支持两个附加操作的队列

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

四种处理方法:
在这里插入图片描述
生产者:


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!");
//    }@Overridepublic void run() {try {while (element < 100) {System.out.println("Produce: " + element);blockingQueue.put(element++);}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Produce Done!");}
}

消费者:

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;}@Overridepublic 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!");}
}

阻塞队列的应用:


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 更好
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() {@Overridepublic 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提供的线程池不符合需求,自己定义线程池,但是线程池参数很多,容易出错
    在这里插入图片描述

/*** <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;}@Overridepublic void run() {while (count > 0) {System.out.println(Thread.currentThread().getName() + " reading " + name);try {Thread.sleep(1000);} catch (InterruptedException ex) {ex.printStackTrace();}--count;}}
}
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 {@Overridepublic 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();}
}

怎么监控线程池的运行状态:
在这里插入图片描述


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 {// 线程池的关闭,统计已经执行的,正在执行的阻塞的的线程池@Overridepublic void shutdown() {System.out.println(String.format(this.poolName +"将会被关闭的. 已经执行的: %d," +"正在执行的: %d, 阻塞的: %d",this.getCompletedTaskCount(),this.getActiveCount(),this.getQueue().size()));super.shutdown();}// 线程池即将关闭,统计已经执行的,正在执行的阻塞的的线程池@Overridepublic List<Runnable> shutdownNow() {System.out.println(String.format(this.poolName +"将会被关闭的. 已经执行的: %d," +"正在执行的: %d, 阻塞的: %d",this.getCompletedTaskCount(),this.getActiveCount(),this.getQueue().size()));return super.shutdownNow();}@Overrideprotected void beforeExecute(Thread t, Runnable r) {// 当前线程的开始时间startTimes.put(String.valueOf(r.hashCode()), new Date());}@Overrideprotected 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";}@Overridepublic 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;}}
}
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的实现方式,维护线程与实例的映射:
方式一:
在这里插入图片描述
方式二:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/219264.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

项目一 分析并设计学生管理数据库

项目一 分析并设计学生管理数据库 1&#xff0c;做好管理数据库的知识准备 1.1&#xff0c;初识数据库 **1&#xff0c;DBMS&#xff1a;**数据库管理系统(Database Management System)。数据库 是通过DBMS创建和操作的 容器。 **2&#xff0c;DB&#xff1a;**数据库(data…

004 Windows NTFS文件夹权限

一、NTFS文件权限&#xff1a; NTFS&#xff08;New Technology File System&#xff09;是Windows NT内核的系列操作系统支持的、一个特别为网络和磁盘配额、文件加密等管理安全特性设计的磁盘格式&#xff0c;提供长文件名、数据保护和恢复&#xff0c;能通过目录和文件许可…

ffmpeg编解码——数据包(packet)概念(如何正确处理数据包中的显示时间戳pts与解码时间戳dts关系?)

文章目录 FFmpeg编解码——数据包&#xff08;Packet&#xff09;概念1. 数据包&#xff08;Packet&#xff09;简介2. 数据包&#xff08;Packet&#xff09;在FFmpeg中的应用2.1 从媒体文件读取数据包2.2 向媒体文件写入数据包 3. 数据包&#xff08;Packet&#xff09;相关问…

【EI会议征稿中|IEEE出版】第三届信息技术与当代体育国际学术会议(TCS 2023)

【IEEE出版】第三届信息技术与当代体育国际学术会议&#xff08;TCS 2023&#xff09; 2023 3rd International Conference on Information Technology and Contemporary Sports 2023年第三届信息技术与当代体育国际学术会议&#xff08;TCS 2023&#xff09;将于2023年12月2…

Dueling DQN 跑 Pendulum-v1

gym-0.26.1 Pendulum-v1 Dueling DQN 因为还是DQN,所以我们沿用double DQN,然后把 Qnet 换成 VAnet。 其他的不变&#xff0c;详情参考前一篇文章。 class VA(nn.Module):"""只有一层隐藏层的A网络和V网络"""def __init__(self, state_dim, hidd…

子目录文件夹图片汇总

import os import shutildef collect_images(source_folder, target_folder):# 遍历主文件夹及其所有子文件夹for root, dirs, files in

位1的个数

题目链接 位1的个数 题目描述 注意点 输入必须是长度为 32 的 二进制串 解答思路 位运算判断每一位是否为1 代码 public class Solution {// you need to treat n as an unsigned valuepublic int hammingWeight(int n) {int res 0;for (int i 0; i < 32; i) {res …

项目经理和产品经理该如何选择?

最近很多人咨询“项目经理跟产品经理该怎么选&#xff0c;我更适合哪个&#xff1f;”“项目经理跟产品经理哪个更有钱途 ”“项目经理转产品经理好转吗”等等&#xff0c;今天就一次性说清楚项目经理跟产品经理有什么区别&#xff0c;应该怎么选择。 不想看长篇大论的&#x…

Python+Pytest接口自动化之HTTP协议基础

HTTP协议简介 HTTP 即 HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09;&#xff0c;是互联网上应用最为广泛的一种网络协议。所有的 WWW 文件都必须遵守这个标准。 设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。HTTP 协议在 OSI 模型…

Kubernetes版本升级到v1.18.0方法

升级k8s版本才能使用kube-prometheus安装监控 1、查看集群状态 [rootk8s-master k8s-script]# kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master Ready master 5d22h v1.18.0 k8s-slave1 Ready <none> 4d10h v1.18.0 k…

ActiveMQ使用指南

介绍 ActiveMQ是Apache开源组织旗下的一个项目&#xff0c;是一个流行的开源消息中间件。它完全支持JMS1.1和J2EE1.4规范的JMS Provider实现&#xff0c;并且是纯Java开发的产品。ActiveMQ支持多种语言编写客户端&#xff0c;包括C,C,C#,Perl,PHP,Ruby,Ajax等&#xff0c;同时…

做数据分析为何要学统计学(6)——什么问题适合使用卡方检验?

卡方检验作为一种非常著名的非参数检验方法&#xff08;不受总体分布因素的限制&#xff09;&#xff0c;在工程试验、临床试验、社会调查等领域被广泛应用。但是也正是因为使用的便捷性&#xff0c;造成时常被误用。本文参阅相关的文献&#xff0c;对卡方检验的适用性进行粗浅…

【unity】如何用Unity获取Windows桌面

【背景】 默认的Unity可实现的屏幕共享仅仅针对Unity编辑器的编辑窗口中的Camera展现的内容。本篇研究如何实现用Unity实时反映Windows桌面窗口画面。 【准备插件】 下载地址&#xff1a; https://download.csdn.net/download/weixin_41697242/88623496 将解压后的文件夹直…

Feign-自定义配置

目录 一、自定义Feign配置 二、修改日志级别 方式一&#xff1a;application配置文件方式 方式二&#xff1a;java代码方式 三、总结 一、自定义Feign配置 二、修改日志级别 配置Feign日志有两种方式 方式一&#xff1a;application配置文件方式 &#xff08;1&#xff09…

目标检测DOTA数据集提取感兴趣类别数据

DOTA数据集 DOTA数据集包含2806张航空图像&#xff0c;尺寸大约从800x800到4000x4000不等&#xff0c;包含15个类别共计188282个实例。其标注方式为四点确定的任意形状和方向的四边形&#xff08;区别于传统的对边平行bbox&#xff09;。类别分别为&#xff1a;plane, ship, s…

前端八股文

前端八股文 目录 前端八股文1.css选择优先级&#xff1f;2.px与rem区别&#xff1f;3.重绘与重排的区别&#xff1f;4.元素水平垂直居中的方法&#xff1f;5.什么是闭包&#xff0c;闭包有什么特点&#xff1f;6.什么是事件委托&#xff1f;7.什么是原型链&#xff1f;8.new操作…

交易历史记录20231205 记录

昨日回顾&#xff1a; select top 10000 * from dbo.CODEINFO A left join dbo.全部&#xff21;股20231205010101 B ON A.CODE B.代码 left join dbo.全部&#xff21;股20231205CONF D on A.CODED.代码left join dbo.全部&#xff21;股20231205 G on A.CODEG.代码 left…

LeetCode-合并有序链表问题

1.合并两个有序链表 题目描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 思路&#xff1a; 首先建立一个头节点方便后续操作&#xff0c;然后开始循环将两个链表的节点值进行比较&#xff0c;如果list1…

Windows Subsystem for Linux (WSL) 安装与使用笔记

文章目录 Part.I IntroductionPart.II 安装Chap.I 安装流程Chap.II 迁移至其他盘 Part.III 使用Chap.I 一些信息Chap.II 配置下载软件的源Chap.III 安装 pip Reference Part.I Introduction Windows Subsystem for Linux 简写为 WSL&#xff0c;是 Windows 的一个 Linux 子系统…

常用的建表但范式、反规范化

规范化&#xff1a; 规范化是用于数据库设计的一系列原理和技术&#xff0c;它可以减少表中数据的冗余&#xff0c;增加数据完整性和一致性。通常有很多范式。 第一范式&#xff08;1NF&#xff09;&#xff1a; 常用的三种范式&#xff1a; 表中的字段都是不可再分割的原子属…