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