一. 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 处理异常
- 使用异常不要只是返回码,应该返回更加详细的内容
- 主动捕获检查性异常,并对异常详细打印日志或者短信
- 保持代码整洁,一个方法中不要有多个try catch 或者嵌套try catch
- 捕获更加具体的异常,而不是通用的Exception
- 合理的设计自定义异常类
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.编码中常见的异常(并发修改,类型转换,枚举查找):
- 可迭代对象在遍历的同时做修改,则会报并发修改异常
- 类型转换不符合java的继承关系,则会报类型转换异常
- 枚举在查找时,如果枚举值不存在,不会返回空,而是直接抛出异常
枚举类:
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的实现方式,维护线程与实例的映射:
方式一:
方式二: