1、静态方法好处
静态方法见名知意
private volatile static Cat cat;
public static Cat getInstanceSingleton() {
if (cat == null) {
synchronized (Cat.class) {
if (cat == null) {
cat = new Cat();
}
}
}
return cat;
}
不用重复的建对象-单例、Gson打日志
可以返回子类型
public static Animal getInstance() {
a = new Cat();
return a;
}
可以根据静态方法的入参,返回不同类型的对象
public static Animal getInstance(String name) {
if ("Dog".equals(name)) {
return new Dog();
}
if ("Cat".equals(name)) {
return new Cat();
}
return new Animal();
}
2、缺点
1、使用场景
类的属性太多,各种参数的有参构造器,人为都记不住属性名称了
setter方法,在构造过程中JavaBeans可能处于不一致的状态
User user = new User();
user.setName("mjp");
user.setAge(1);
有些对象从创建到销毁需要保持一致性,但是JavaBean对象不符合这点需求。
JavaBean对象的构造过程则先是通过创建对象,随后在通过setter方法来设置必要的参数。
直到销毁前,JavaBean对象都是可变的,或者说JavaBean一直在构造过程中。
在需要一致性对象的程序使用JavaBean对象,会可能导致失败。
2、链式操作,都需要结合@Data注解
3、builder 和 Accessors
4、lombok注解
@Data : 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法
@AllArgsConstructor : 注在类上,提供类的全参构造
@NoArgsConstructor : 注在类上,提供类的无参构造
private volatile static Cat cat; // 01.懒汉模式 + 03.对象创建过程中防止指令重排序
public static Cat getInstanceSingleton() {
if (cat == null) {
synchronized (Cat.class) { // 02.防止并发都进来
if (cat == null) {
cat = new Cat();
}
}
}
return cat;
}
volatile:防止指令重排序
* instance = new Singleton();分为三个动作
1.堆内存开辟一片空间,比如0X01
2.堆中创建Singleton,有初始化赋值的话,赋值
3.将对象内存地址返回给对象引用变量instance,instance存有0X01,instance在栈帧的局部变量表中
cpu为了提高吞吐量,会自动的改变cpu流水线,即指令的操作先后顺序改变,对单线程没有影响,多线程有
eg:
执行顺序 2,3变成让你3,2,导致返回给instance引用对象地址,虽然不为Null,但是根本没有完成赋值
导致t1执行同步代码执行结束的时候,t2判读,Instance!=null,但是instance本身没赋值;t2会直接返回instance
1、为什么私有化
@UtilityClass
public class DateUtil {
public Integer getInt() {
return 1;
}
}
//默认添加了,私有化的构造器
private DateUtil(){
}
别的地方无法通过new的形式创建
GsonUtil
@Slf4j
public class GsonUtil {
private static final Gson GSON = new GsonBuilder().serializeNulls().create();
private GsonUtil() {
}
public static String toJson(Object object) {
try {
return GSON.toJson(object);
} catch (Exception e) {
log.error("序列化失败", e);
}
return StringUtils.EMPTY;
}
}
1、哪些方式会创建不必要的对象
new String、new Integer(建议使用Integr的valueOf()静态工厂方法)
循环中拆箱装箱
while循环,为了防止死循环、不断创建对象等。需要集合业务指定超过最大的循环次数
在高并发场景中,避免使用”等于”判断作为中断或退出的条件
2、包装类:占用更大的空间(但是包装类能表达 null 的语义)
正常情况下布尔值就是true和false,但是如果用户传递一个错误的skuId,那么此计算此skuId是否在灰度中,返回结果应该为null,因为true、false都不合适
交易额,异常的时候就因该为null,而非0
【推荐】自动转换(AutoBoxing)有一定成本,调用者与被调用函数间尽量使用同一类型,减少默认转换
//WRONG, sum 类型为Long, i类型为long,每次相加都需要AutoBoxing。
Long sum=0L;
for( long i = 0; i < 10000; i++) {
sum+=i;
}
1、sop
static变量属于类,类在变量在堆中内存就一致在。如果是集合,则元素对象也不会被回收,对象链GCRoot都不会被回收
尽量在方法内部,方法结束,变量也被回收了
数组元素,不用则及时置为null,array[i] = null,避免内存泄漏。(参考list的remove方法:elementData[–size] = null)
1、优点
使用正常的try-catch。在finally中关闭资源时,close方法也可以能出现异常导致资源关闭失败,所以需要再次try close方法。过于繁琐
FileInputStream fis = new FileInputStream("");
try {
int read = fis.read();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
2、使用前提-资源类实现了Closeable接口
try(FileInputStream fis = new FileInputStream(new File("a"))) {
} catch (IOException e) {
}
1、为什么要重写
2、重写了equals的类
String
public boolean equals(Object anObject) {
if (this == anObject) { //01.地址相同,则一定“逻辑”相同
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) { //02.字符串的长度不同,则一定“逻辑”不同
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) { //03.每个字符对比
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
Integer
public boolean equals(Object obj) {
if (obj instanceof Integer) { //01.不是同类型的,肯定“逻辑”不同
return value == ((Integer)obj).intValue();//02.是Integer类型的则比较值的大小 【-128,127】则相同,否则不同
}
return false;
}
1、Integer使用equals来比较值的大小
2、但是【-128,127】可以直接使用 == 来比较大小
Integer a = 127;
Integer b = 127;
System.out.println(a == b); //true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); //false
3、约定规范注意事项
animal.equals(cat)//true
cat.equals(animal)//false
Cat的equals中
if(obj instanceof Cat){
//显然这里animal不是Cat类型
}
Animal的equals中
if(obj instanceof Animal){
// Cat extends Animal,所以cat 是Animal类型
}
x.equals(y) 为true: 比较的是x和y的a、b的属性
y.equals(z) 为true: 比较的是y和z的b、c的属性
x.equals(z)如果比较的是x和z的a、c属性,则不能保证传递性
如果x.equasl(y)中equals方法代码中涉及random随机数、时间戳等可变的元素,则无法保证equals方法每次都返回同样的值
1、sop
1、sop
a = b和c属性的计算结果,则hashCode方法中,要么使用a,要么使用b和c。不要a和另外两个一起决定hash值
2、为什么重写hashcode
String s1 = "majinpeng02";
String s2 = "majinpeng02";
s1.equals(s2); //true
但是hashcode没有重写,二者的hashcode值可能不一样,假如不一样。则hashmap.put(s1,1); hashmap.put(s2,1)则二者都能存入map。和我们hashmap约定的key冲突不一致了
所以,hashmap也需要重写hashcode
3、重写hashcode的作用/好处
符合equals相同则hashcode也相同
比较字符串是否为相同字符串的时候,首先可以使用hashcode值进行比较,hash值不一样则equals一定不同。如果hash值相同,再使用equals,一个一个字符进行对比判断。所以,可以提高效率
4、方法特点
5、String的hashcode方法:Object本身的hashcode是native方法
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
1、对象调用clone方法,可以跳过构造器的调用!
2、clone是浅copy,copy出来的对象和原对象指向同一块堆内存,一个修改了内存元素,会影响另一个。
3、不可变类,一定不要提供clone重写方法
1、什么时候需要实现此接口
2、String、Integer实现此接口重写compareTo方法
挨个字符比较大小
// 挨个字符比较大小
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
//自定义compare方法,不使用减法,避免int溢出
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
//这里没有使用x - y,而是使用了x > y进行比较。就是防止如果y是负数,则Integer.MAX_VALUE - 一个负数,结果溢出int值
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
3、使用注意事项
4、compareTo方法返回值含义
5、区别Comparator接口
Comparable是类的语言特性,表明这个类具有比较、排序等功能。本质上Comparable属于内部排序,Comparator是外部排序。
属于java.util的接口,一般用于集合元素的排序
是函数式接口,只有一个方法:int compare*(T o1, T o2) :* 0相同、1大于、-1小于;
集合元素排序
//根据Dict对象的sort字段降序排序
dictList.sort(Comparator.comparing(Dict::getSort).reversed());
//根据Dict对象的sort字段升序排序
dictList.sort(Comparator.comparing(Dict::getSort));
//按照字段降序,相同的话,再按照另外一个字段降序
skuCategoryRuleDOS = skuCategoryRuleDOS.stream().
sorted(Comparator.comparing(SKUCategoryRuleDO::getSkuCategoryId).reversed().
thenComparing(SKUCategoryRuleDO::getSkuTemperatureZone,Comparator.reverseOrder())
)
.collect(Collectors.toList());
Comparator接口中其它reversed()、thenComparing()方法,则调用了本身的compare()方法进行再排序
-优先考虑使用private
1、封装的好处:
2、类属性为什么不建议public修饰
3、使用public getter、setter代替public成员变量:public类的实例,绝不能是public的
原因
public class User {
public String name;
}
1、这个类的name是可以直接被访问的,当域被访问的时候,我们将失去对这个域的控制权。后续,想要将name字段改为nickName,那么使用方就全部报错
2、通常包含public属性的类,是线程不安全的
User user = new User();
user.setName(null);
public void setName(String name) {
if (StringUtils.isBlank(name)) {
throw new NullPointerException();
}
this.name = name;
}
好处1:可以在setter方法中,对set的值进行逻辑校验
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
好处2
//好处2:可以修改属性的名称而不用关注调用方
//现在我觉得,name字段描述的不准确,换个名称为nickName,直接在本类中修改就ok,不用关注调用方
public class User {
private String nickName;
public String getName() {
return nickName;
}
public void setName(String name) {
this.nickName = name;
}
}
4、如果想要数组、集合,对象地址不可变 && 内容元素也不可变,建议使用 private final修饰,
public static final String[] ARRAY = new String[];
影响:
虽然ARRAY引用不能修改,但是ARRAY内部的元素是可以被修改的
解决:
假的final,最好private修饰[数组、集合]
//假的final,最好private修饰[数组、集合]
对象不可变了,但是元素内容还是可以被操作改变的。如果不想被外部的操作影响,则必须private
private static final Map<String,Long> map = new HashMap<>;
private static final List<Long> list = new ArrayList<>;
private static final String[] ARRAY = new String[];
可以深copy成员变量,防止改变影响成员变量值
//可以深copy成员变量,防止改变影响成员变量值
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];//02.这个value数组,在String内部被很多其它地方使用。所以,不能改变它的值
//01. 这样对toCharArray的返回结果数组进行操作,不会影响原本的value数组的元素内容
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
}
特殊情况:真final不可变,可以在类中提供public成员变量
private修饰和public修饰效果一样
public statci final Longsize = 200;
public statci final Integer code= 0;
public statci final String birthday = "2022-06-01";
-优先考虑使用final
1、不可变类特点
2、final类优点 & 缺点
可以重复使用、共享。线程安全
安全:(1.8中DateTimeFormatter不可变,线程安全、SimpleDateFormat可变并发不安全(线程A定义的合适为:YYYYMMDD,可能被线程B改为YYYY_MM_DD))
共享:不需要进行保护性拷贝(拷贝始终等于原始的对象)。因此,不需要为不可变类提供clone方法
唯一缺点:每一个不同的值都需要创建一个新的对象。
3、不可变类,需要遵循的原则
保证类不会被继承,final无法被继承
既不要从外部拿,要不要返给外部
确保在该类的外部不会获取(get)到可变对象的引用、也不要从外部拿,然后set 可变对象。同时建议:使所有的域都是private final ,参考3
4、类属性是集合、数组。使用注意事项
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer age;
private Map<String, Long> map = new HashMap<>();
}
Map<String, Long> source = new HashMap<>();
source.put("mjp", 1L);
// 01.外部数据源source,给类关键集合map赋值
User user = new User(18, source);
Map<String, Long> target = user.getMap();
System.out.println(target);//{mjp=1}
// 02.外部数据源发生了变化
source.put("wxx", 1L);
// 03.类集合也会内容也会发生变化
System.out.println(target);//{mjp=1, wxx=1}
//正确做法
1、不要给关键数组、集合提供get、set方法。要想get则深copy返回。即不要使用@Data注解
2、建议private final
//补充
上述,基本都是针对final类。我们日常开发,还是不这样做的,所以,要清楚你提供了get、set方法则类中的集合、数组内容会收到外部数据源的内容变化而变化
注意⚠️:Map
尽可能使用对象组合而不是继承的方式达到复用的目的
1、复合
@Data
@Accessors(chain = true)
public class SmallName {
private String foreverName;
@Resource
private User user;//符合添加了user类,可以使用user对象的方法
}
2、继承的缺点
子类只会比父类更大,权限也是,只能放大。类会越来越大。无法收缩了
特例:接口的实现类中实现的方法的修饰符,全部都是public
父类的方法,子类也全部拥有。有时候继承某个类,并不是想拥有此类的所有方法!
跨包的继承,则更加危险
违背了封装的原则,子类需求去了解父类的实现,否则随着不同版本父类的代码发生了改变,即使子类完全没有改变代码,也有可能被破坏
覆盖了父类的方法后,结果不符合预期:典型的:add元素的count统计
public class MyCountSet extends HashSet<Integer> {
/** 统计"有史以来"向该集合中添加过的元素个数 */
private int count = 0;
@Override
public boolean add(Integer num) {
count++;
return super.add(num);
}
@Override
public boolean addAll(Collection<? extends Integer> nums) {
count += nums.size();
return super.addAll(nums);
}
public int getCount() {
return count;
}
public static void main(String[] args) {
MyCountSet countingSet = new MyCountSet();
countingSet.addAll(Lists.newArrayList(1));
System.out.println(countingSet.getCount());//2
}
}
1、现象:
addAll方法,添加了集合中1个元素,count应该是2,但是实际输出2
2、原因:
子类重写了addAll方法,但是不知道父HashSet的addAll的具体实现
父类HashSet的addAll,调用了自身的add方法。子类也重写了add方法,就导致:子类的addAll方法中count += nums.size();算了一次,同时,子类的add方法中count++又算了一次
3、执行步骤
a、子类addAll,count += nums.size();此时,count值为1
b、super.addAll,调用HashSet的addAll
c、HashSet的addAll,调用了自身的add()
d、HashSet的add(),被子类重写了,所以,调用子类的add
e、子类的add中,执行count++;此时,count值为2
f、再调用父类add完成将元素加入
4、解决
将子类的addAll中的count += nums.size();删除
HashSet的addAll方法
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
父类构造器绝不能直接/间接调用可被覆盖的方法,否则可能不符合预期(程序失败、npe等)
Sup
public class Super {
public Super() {
overrideMe ();//02-造器能调用可了被覆盖的方法❌
}
public void overrideMe () {
}
}
Sub
public final class Sub extends Super {
private final Instant instant;
Sub() {
super();//01.
instant = Instant.now();//05.完成赋值
}
@Override
public void overrideMe () {//03.进入子类的重写方法
System.out.println(instant);//04.此时instant为null,还没有值!!!
}
public static void main(String[] args) {
Sub sub = new Sub();//00.执行顺序如上
sub.overrideMe ();//06.子类方法,有值了
}
}
1、接口和抽象类的区别
接口,所有的方法默认是public,属性都是;类默认是default
java8-default方法
1、作用
为了扩展接口的功能。接口新增方法,如果是public的,则所有实现类都需要实现,这样就不符合向下兼容
实现类自动拥有和接口一样的default方法,直接用,不需要再实现
2、eg
List extend Collection接口,此接口自己实现了removeIf方法
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
3、最好不要在接口已经存在的情况下(可以在接口第一次创建的时候添加),再添加新的default方法,这对于接口来说非常危险
eg:removeIf方法,对于大多数Collection接口接口的实现类,都没有影响,但是对于已经实现了Collection接口的org.apache.commons.collections4.collection.SynchronizedCollection
则可能存在问题。
如果客户端在SynchronizedCollection的实例上调用removeIf方法,同时另外一个线程对集合进行修改,就会导致并发修复爱Exception
接口中只能有常量【但是不建议,接口只提供行为规范】、抽象类中可以有成员变量
常量型接口-是接口的错误使用
1、原因
类实现常量接口,这对于这个类的用户来讲并没有实际的价值。实际上,这样做返回会让他们感到更糊涂
2、建议
使用Enum
抽象类单继承【但是了类可以实现多个接口】; 接口多继承接口可以同时继承B、C接口【但是接口B和接口C,不能出现冲突方法】
接口多继承注意事项
public interface BInterface {
void eat();
}
public interface CInterface {
String eat();
}
//这样A接口,就不知道eat方法具体是哪个【have unrelated return types】
public interface AInterface extends BInterface, CInterface{
}
接口是行为规范,具体实现逻辑由实现类自己定义 ;
抽象类定义了大部分公有的具体行为,根据不同子类定义了不同的abstract方法,由子类根据自己的特点实现即可
抽象类优于标签类
//01.标签类-eg:它能够表示圆形或者矩形
public class Figure {
enum Shape { RECTANGLE,CIRCLE };
final Shape shape;
double length;
double width;
double radius;
public Figure(double redius){
shape=Shape.CIRCLE;
this.radius=radius;
}
public Figure (double lenght, double width) {
shape=Shape.RECTANGLE;
this.length=lenght;
this.width=width;
}
//计算不同形状的面积
public double area(){
switch (shape){
case RECTANGLE:return length*width;
case CIRCLE:return Math.PI*(radius * radius);
default:throw new AssertionError();
}
}
}
缺点:
违背了开闭原则
新增图形表示,求面积则需要:添加新的case;同时也有可能添加新的成员变量【梯形:(上底+下底)*高/2】
//02.抽象类
public abstract class AbstractFigure {
public abstract double area();
}
// 圆形子类
public class CircleFigure extends AbstractFigure {
private double radius;
public CircleFigure(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
// 矩形子类
public class RectangleFigure extends AbstractFigure {
private double length;
private double width;
public RectangleFigure(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double area() {
return length * width;
}
}
优点:新增图形,则新增类即可,不需要修改原本的代码,遵行开闭原则
如何决定使用抽象类还是接口
要表示is-a(圆形是一个图形、三角形是图形----)的关系,并且是为了解决代码复用的问题,就用抽象类;
表示has-a关系,并且是为了解决抽象和解耦而非代码复用的问题,那就使用接口。
2、abstract方法注意事项
3、常用接口:
Cloneable克隆、Serializable可序列化、Comparable可比较、CharSequence、Runnable可执行
4、为什么接口优于抽象类
存在以下场景,A类即是可以克隆的、又是可以序列化的、又是可以比较大小的、又是可以作为任务执行的。如果上述常用接口都变成了抽象类,那么由于类的单继承,所以A类就不能同时具有以上功能。
除非把上述接口变成抽象类,而且彼此之间有父子继承关系。但是A类可以只可克隆 + 可序列化,不需要可比较大小 + 可执行
5、接口和抽象类混合
A extends B抽象类 implement C接口
HashMap和TreeMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
}
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable{
}
6、函数式编程
注意⚠️:这里的对象,本质是方法即策略【按age降序、按name降序、按skuId降序等等策略】
函数式接口特点:注解 + 单个方法
@FunctionalInterface//01.注解
public interface Comparator<T> {
int compare(T o1, T o2);//02.方法
//虽然接口还有其它Object对象的方法【equals等】
//以及接口本身的default方法【java8新增】
//以及静态方法static【java9新增】
}
除了default方法、static方法、Object类的方法外,有且仅有一个方法,这种就是函数式接口
方法作为对象传入方法eg1
//01.自定义函数式接口作为方法的入参对象:方法的作用是按照User的age降序排序方法
User u1 = new User(1, "mjp");
User u2 = new User(2, "wxx");
List<User> result = Lists.newArrayList(u1, u2);
//02.自定义函数式接口
Comparator<User> myComparator = new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o2.getAge().intValue() - o1.getAge().intValue();//按照age降序
}
};
result.sort(myComparator);//这里的myComparator,本质就是方法,当法的作用是按照age的大小降序
//02. 函数式接口,可以使用lamda表达式
Comparator<User> myComparator = (user1, user2) -> user2.getAge() - user1.getAge();
User u1 = new User(1, "mjp");
User u2 = new User(2, "wxx");
List<User> result = Lists.newArrayList(u1, u2);
result.sort(Comparator.comparing(User::getAge)); //03.这里的sort方法的参数对着,就是方法【函数式接口】,这个方法的内容按照User的age的大小升序排序
Lists.newArrayList(new User(1,"mjp")).stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList());
这里的sort方法就是传入一个比较大小的方法,然后返回一个Stream流
Stream<T> sorted(Comparator<? super T> comparator);
1、原因:
statci成员内部类属于Class类的;非static成员内部类属于对象的【必须先new出外部类对象】
static和非static
@Data
public class User {
private Integer age;
private String name;
private Emp emp;
@Data
public class Emp{
private Long skuId;
}
@Data
public static class Price{
private Double money;
}
}
@Test
public void t(){
Price price = new Price();
price.setMoney(1.0);//01.static成员内部类,可以直接new,不依赖外部类对象
User user = new User();
Emp emp = user.new Emp();//02.要想获得非static成员内部类的对象,必须先获取外部类对象
emp.setSkuId(1L);
}
非staic成员内部类对象,强绑定外部类对象【比如EntrySet对象和HashMap就是】,可能会影响GC
除此之外,非静态成员类的实例被创建的时候,它和外围类的关联关系也随之建立起来,这种关联关系,需要消耗非静态成员类实例的空间,并且增加构造的时间开销
Map-Entry
Entry的getKey、setValue等方法,都不需要访问Map,所以,使用非静态成员类表示Entry则会浪费
1、使用原生态可能存在的安全问题,因为缺少类型的检查。可能会在运行时导致异常
获取集合元素,并且强转时,会运行时才会报出ClassCastException异常【无法在编译时期IDEA就报出来】
原生态类型
List list = new ArrayList();
list.add(1);
String s = (String) list.get(0);
System.out.println(s);
@Test
public void t() {
List<Integer> list = new ArrayList();
add(list, "java");
for (Integer item : list) {// 02.遍历集合元素,使用Integr进行强转接收时,异常
System.out.println(item);
}
}
public static void add(List list, Object obj) { //01.方法入参,没有指定泛型
list.add(obj);
}
2、带有泛型的类型,传参到无泛型方法中,尽量只读区不写
public static void add(List list) {//为了接受参数的通用性,这里没有带泛型
//可以是原生态list,但是尽量只是读取list元素,不写(add方法等)
for (Object o : list) {
System.out.println(o);
}
}
3、泛型的擦除
本质:运行时期,都是class java.util.ArrayList
泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,即类型擦除。类型擦除主要是为了兼容之前没有泛型特性的代码
List<Integer> l1 = new ArrayList<>();
List<String> l2 = new ArrayList<>();
Class<? extends List> aClass1 = l1.getClass();
Class<? extends List> aClass2 = l2.getClass();
System.out.println(aClass1 == aClass2);//true
而数组在运行时,Integr[]和String[]对应的不同的class
在编译时期,List、List无任何关系。所以,他们作为方法的入参时,可以理解为方法的重载
1、@SuppressWarnings*(“unchecked”)*范围
1、原因
数组是协变的
ArrayStoreException
Object[] obj 是 String[]的父类型
List<Object>不是List<String>的父类型
数组 编译时期,不会报异常。运行时会
Object[] array = new String[3];
array[0] = 1;
System.out.println(array[0]);//运行时ArrayStoreException,编译时没问题
数组一但创建,大小不可变
2、泛型和可变参数一起使用注意事项
当调用可变参数时,将创建一个数组来保存参数
void foo(String... args);
void foo(String[] args); // 两种方法本质上没有区别
ArrayStoreException
@Test
public void t() {
func("mjp","wxx");
}
public static void func(String...args) {
String[] strArray = args; //01.可变参数,本质是数组
Object[] objArray = strArray;//02.数组的协变的,args、strArray、objArray三者都指向同一块堆内存地址
objArray[0] = 1; //03.堆地址内元素做了改变,相当于在字符串数组中添加了整型
String arg = args[0];// 04.ArrayStoreException
}
1、什么时候,使用泛型类方法
2、什么时候,建议直接使用Object
只读不写
eg:thrift中定义roc接口中BaseResponse中的数据Data
public class ThriftBaseTResponse {//02.删除T
public int code = Constants.SUCCESS;
public String message;
public T data;//01.这里,set给data值后,直接返回给前端了。后续,不再有读取操作了,其实可以直接使用Object
}
3、方法入参,不限制类型时,方法返回值返回Object还是泛型
- 外部交互:返回Object。由使用方自己强转换
```java
private final Map
private final Map<Object,Object> map = Maps.newHashMap();
public <T> T getValueByKey(Object key) {
return (T)map.get(key);
}
Integer res = getValueByKey("java");//这里就不用强转了。但是要求,内部使用方知道,key-“java”,对应的value类型
1、 ? extends A
则?代表A或者A的子类(类A被继承)或A的实现类(接口A被实现)
读(comparable 和 comparator都是读取)
List extends Number>,则?可以是Integr、Double、Long都可以
只可以读
@Test
public void t() {
List<Integer> l1 = Lists.newArrayList(1,2,3);
List<Double> l2 = Lists.newArrayList(1.0,2.0,3.0);
sum(l1);
sum(l2);
}
private Double sum(List<? extends Number> list) {
Double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
2、 ? super A
则?代表 A或者A的父类
private void add(List<? super Number> list, Number num) { // 这里的list,必须是List或List
list.add(num);
}
1、背景
为了存什么类型,就可以直接取出来什么类型,不用关心类型转换且不会存在强转错误
map本身不限制存入的对象,用户可通过代码将k-v关联起来
private static final Map<Class<?>, Object> map = Maps.newHashMap();
public static <T> void putInstance(Class<T> aclass, T instance) {
//这里可以加强校验,如果类型不一致,则throw。防止cast转换异常
map.put(aclass, aclass.cast(instance));//01.传入的Class和instance是一种类型的
}
public static <T> T getInstance(Class<T> aclass) {
return aclass.cast(map.get(aclass));// 02.取出来的实例一定也是这种类型的
}
@Test
public void t() {
putInstance(User.class, new User().setName("mjp"));
putInstance(Animal.class, new Animal().setColour("pink"));
User user = getInstance(User.class);
Animal animal = getInstance(Animal.class);//03.如果用User接收,会编译提示错误
}
2、无法保存List list这种形式。List.class编译不通过
List、List运行时期一样的class都是ArrayList
只能存、取原生态
putInstance(List.class, Lists.newArrayList(1, "a"));
List list = getInstance(List.class);
//无法->编译报错
putInstance(List<String>.class, Lists.newArrayList("a"));
List<String> list = getInstance(List<String>.class);
钻石形状的 <> 符号, 所以它有时也叫作“钻石语法
List和List本质一样
本质上,T,E,K,V,?都是通配符,没什么区别,只不过是编码时的一种约定俗成的东西
泛型方法
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
enum类父类是Enum,不是Object
三个值形式
@AllArgsConstructor
@Getter
public enum ExecuteTypeEnum {
UNKNOW(-1, "未知", "WZ"),
REVERSE_ALLOCATE(1, "逆向调拨", "HT"),
RETURN_SUPPLY(2, "退供", "TG");
private Integer code;
private String desc;
private String orderNoPrefix;
//01.根据code获取枚举
public static Optional<XtAllocateEnum> findByIntValue(Integer value) {
return Arrays.stream(XtAllocateEnum.values())
.filter(xtAllocateEnum -> xtAllocateEnum.getIntValue().equals(value)).findFirst();
}
//02.根据desc获取code
public static Integer resolveByDesc(String desc) {
return Arrays.stream(values())
.filter(executeTypeEnum -> StringUtils.equals(desc, executeTypeEnum.getDesc())).findAny()
.map(ExecuteTypeEnum::getCode)
.orElse(-1);
}
}
//03.使用
@Test
public void t() {
Optional<ExecuteTypeEnum> optional = ExecuteTypeEnum.findByIntValue(-99);
if (optional.isPresent()) {
System.out.println(optional.get().getDesc());
} else {
System.out.println("不存在的code");
}
Integer code = ExecuteTypeEnum.resolveByDesc("哈哈");
System.out.println(code);
}
上述这种形式,需要在每个枚举中,都定义查询desc和code方法。可以抽取出枚举工具类
枚举工具类
// 01.定义Value接口
public interface HaveValueEnum<T> {
T getValue();
}
// 01.定义Desc接口
public interface HaveDescEnum<T> {
T getDesc();
}
//03.定义工具类
@UtilityClass
public class EnumUtils {
/**
* 根据value获取对应的枚举
*
* @param value value
* @param enumType 枚举类型class
* @param 枚举类型
* @param value类型
* @return 对应的枚举Optional
*/
public static <E extends Enum<E> & HaveValueEnum<T>, T> Optional<E> getEnumByValue(T value, Class<E> enumType) {
for (E item : enumType.getEnumConstants()) {
if (item.getValue().equals(value)) {
return Optional.of(item);
}
}
return Optional.empty();
}
/**
* 根据value获取对应的枚举,获取不到则抛出异常
*
* @param value value
* @param enumType 枚举类型class
* @param exceptionSupplier 异常提供者
* @param 枚举类型
* @param value类型
* @param 异常类型
* @return 对应的枚举
* @throws X 异常
*/
public static <E extends Enum<E> & HaveValueEnum<T>, T, X extends Throwable> E getEnumByValueOrElseThrow(T value, Class<E> enumType, Supplier<? extends X> exceptionSupplier) throws X {
return getEnumByValue(value, enumType).orElseThrow(exceptionSupplier);
}
/**
* 根据value获取对应的枚举,获取不到则抛出异常
*
* @param value value
* @param enumType 枚举类型class
* @param 枚举类型
* @param value类型
* @return 对应的枚举
*/
public static <E extends Enum<E> & HaveValueEnum<T>, T> E getEnumByValueOrElseThrow(T value, Class<E> enumType) {
return getEnumByValueOrElseThrow(value, enumType, () -> new IllegalArgumentException("can't find " + value + " in " + enumType));
}
/**
* 根据value获取对应的枚举描述
*
* @param value value
* @param enumType 枚举类型class
* @param 枚举类型
* @param value类型
* @param desc类型
* @return 对应的枚举描述
*/
public static <E extends Enum<E> & HaveValueEnum<T> & HaveDescEnum<V>, T, V> Optional<V> getEnumDescByValue(T value, Class<E> enumType) {
// jdk8 bug, https://bugs.openjdk.java.net/browse/JDK-8141508
// http://mail.openjdk.java.net/pipermail/compiler-dev/2015-November/009824.html
return getEnumByValue(value, enumType).map(haveDescEnum -> haveDescEnum.getDesc());
}
/**
* 根据value获取对应的枚举描述,获取不到则返回默认值
*
* @param value value
* @param defaultDesc 默认值
* @param enumType 枚举类型class
* @param 枚举类型
* @param value类型
* @param desc类型
* @return 对应的枚举描述,获取不到则返回默认值
*/
public static <E extends Enum<E> & HaveValueEnum<T> & HaveDescEnum<V>, T, V> V getEnumDescByValueOrElseDefault(T value, V defaultDesc, Class<E> enumType) {
return getEnumDescByValue(value, enumType).orElse(defaultDesc);
}
}
//04.定义枚举,实现Value和Desc接口
@Getter
@RequiredArgsConstructor
public enum ExecuteTypeEnum implements HaveValueEnum<Integer>, HaveDescEnum<String> {
RETURN_WAREHOUSE(1,"逆向调拨"),
RETURN_SUPPLY(2,"退供"),
RETURN_WAREHOUSE_AND_SUPPLY(3,"逆向调拨+退供");
private final Integer value;
private final String desc;
}
//05.使用
String secondCategoryName = EnumUtils.getEnumDescByValueOrElseDefault(source.getSkuCategoryId(), "-", SkuSecondCategoryNameEnum.class);
1、场景
枚举值,全部使用2的幂次方的形式表示。当给出7,算出7 = 1 + 2 + 4,即枚举中的LO 和 L 和 A这三种枚举组合而成
7 等效 LO && L && A,所以在db中不用存"LO && L && A",直接存7就可以了
Integer的MAX_VALUE最多表示2的30次方
Long的MAX_VALUE最多表示2的62次方【当枚举值比较多的时候,建议使用Long,但是最多也只支持2的62次方即62种不同的枚举】
枚举类型的值,都是2的幂次方
@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum RuleEnum {
UNKNOW(-1L, "未知"),
LO(1L, "小于or值可修改"),
L(2L, "锁库不可更改"),
A(4L, "允许"),
NA(8L, "不允许"),
XT(16L, "自动加量允许修改");
private Long code;
private String desc;
//01.根据code获取枚举
public static Optional<RuleEnum> findByIntValue(Long value) {
return Arrays.stream(RuleEnum.values())
.filter(ruleEnum -> ruleEnum.getCode().equals(value)).findFirst();
}
//02.根据desc获取code
public static Long resolveByDesc(String desc) {
return Arrays.stream(values())
.filter(ruleEnum -> StringUtils.equals(desc, ruleEnum.getDesc())).findAny()
.map(RuleEnum::getCode)
.orElse(-1L);
}
}
判断集合整数,由哪些2的幂次方的数构成
@UtilityClass
public class BitwiseOperateUtil {
private final static List<Long> result = Lists.newArrayList();
private static Integer times = 0;
public List<Long> dividePositive2ListByBitwiseOperate(Long positive) {
bitwiseOperate(positive);
//list深度拷贝 & 拷贝后的结果可以再写
List<Long> newList = new ArrayList<>();
Collections.addAll(newList, new Long[result.size()]);
Collections.copy(newList, result);
return newList;
}
private void bitwiseOperate(Long num) {
Long splitNum;
if (num < 1) {
return;
}
if ((num & 1) == 1) {
result.add((long) Math.pow(2, times));
splitNum = (num - 1 ) >> 1;
} else {
splitNum = num >> 1;
}
times += 1;
bitwiseOperate(splitNum);
}
}
给出整数13,获得 小于or值可修改,允许,不允许 对应的枚举desc
@Test
public void t() {
List<Long> list = BitwiseOperateUtil.dividePositive2ListByBitwiseOperate(13L);
StringBuilder sb = new StringBuilder();
for (Long code : list) {
Optional<RuleEnum> optional = RuleEnum.findByIntValue(code);
if (optional.isPresent()) {
RuleEnum ruleEnum = optional.get();
String desc = ruleEnum.getDesc();
sb.append(desc);
sb.append(",");
}
}
String res = sb.substring(0, sb.length() - 1);
System.out.println(res);
}
接口
public interface Operate {
double operate(double x, double y);
}
加减枚举
public enum OperateEnum implements Operate {
ADD() {
@Override
public double operate(double x, double y) {
return x + y;
}
},
MINUS() {
@Override
public double operate(double x, double y) {
return x - y;
}
}
}
取模枚举- 不需要改变原有的OperateEnum枚举,只需要新增枚举
public enum ModOperateEnum implements Operate{
MOD() {
@Override
public double operate(double x, double y) {
return x % y;
}
}
}
@Test
public void t() {
System.out.println(OperateEnum.ADD.operate(1, 2)); //3
}
1、反射调用User类中的私有compare方法
@Test
public void t() {
Class<User> userClass = User.class;
try {
User newInstance = userClass.newInstance();
Method method = userClass.getDeclaredMethod("compare", Integer.class, Integer.class);
method.setAccessible(true);
Integer result = (Integer) method.invoke(newInstance, 2, 2);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
getDeclaredMethod 和 getMethod区别,后者是调用自身和父类中的public修饰的方法
获取当前执行方法的名称
当前线程的堆栈信息第一个
@Test
public void ttt() {
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[1];
System.out.println(stackTraceElement.getMethodName());//ttt
}
2、反射获取父类中的泛型
Dog类是Animal类的子类。获取Animal的发你泛型Integer
Dog类是Animal类的子类。获取Animal的发你泛型Integer
public class Dog extends Animal<Integer>{
}
@Test
public void t() {
Class<Dog> catClass = Dog.class;
Type genericSuperclass = catClass.getGenericSuperclass();
//System.out.println(genericSuperclass.getTypeName()); Dog类的父类:com.sankuai.wos.entity.Animal
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] types = parameterizedType.getActualTypeArguments();
System.out.println(types[0].getTypeName());//java.lang.Integer
}
3、反射读取配置文件内容
@Test
public void t() {
Class<Dog> dogClass = Dog.class;
ClassLoader classLoader = dogClass.getClassLoader();
InputStream is = classLoader.getResourceAsStream("application.properties");//resources下,使用类加载器加载
Properties properties = new Properties();
try {
properties.load(is);
String property = properties.getProperty("spring.jackson.time-zone", "/");
System.out.println(property);//Asia/Shanghai
} catch (IOException e) {
e.printStackTrace();
}
}
4、class类对象特点
5、反射缺点
损失了编译时类型检查的优势;执行反射访问所需要的代码非常笨拙和冗长;性能损失(反射方法比普通方法调用慢了很多)
1、原理
点击展开内容
一、注解的本质是实现了Annocation接口的接口:Override注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
//本质
public interface Override extends Annotation{
}
二、两种类型
1、编译时期,直接扫描的注解:@Retention(SOURCE)
2、运行时期,通过反射进行操作
三、注解 原理
1、定义注解
@Documented
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
String value();
}
2、使用注解
@Hello("go")
public class Dog {
}
3、获取注解
@Test
public void t() {
Class<Dog> dogClass = Dog.class;
Hello proxyHandle = dogClass.getAnnotation(Hello.class);
}
这里本质是,通过反射获取了作用在Dog类上的@Hello注解(接口) 的 代理类AnnotationInvocationHandler
4、AnnotationInvocationHandler代理类handle中内容
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Map<String, Object> memberValues; //01.是个map,key就是@Hello注解的属性名称value, 值是属性名称对应的值”go“
private transient volatile Method[] memberMethods = null;
//02.代理类handle实现了@Hello注解(接口)的所有方法,对任意方法的调用,都会走到代理类handle的invoke方法中
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();// 03.注解方法名称
Class[] var5 = var2.getParameterTypes();// 04.注解方法的参数
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) { //05.注解本质是extends Annocation接口的接口,Annocation自带4个方法,判断Hello注解调用的方法是不是这4个
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) { // 06.是这4个方法,则直接调用方法的impl实现
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4); //07.如果是@Hello注解本身的方法,eg:value()方法,则将key名称”value“,在map中对应的值"go"获取并返回
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
}
四、总结
1、先校验注解
校验注解的使用范围、保存周期等是否合理
2、反射获取注解的实现类
jvm将所有生命周期是Runtime的注解取出来,将名称和值放入map。并创建注解的代理类
3、任何对注解方法的调用,都会通过代理类的invoke,返回注解的属性值
4、根据属性值,进一步操作
2、注解的使用
元注解
RetentionPolicy:保存周期/生命周期
RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件(不符合的话,编译时期会报错)
RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件【不写默认】
RetentionPolicy.RUNTIME:永久保存,可以反射获取
//Target作用范围
public enum ElementType {
//类
TYPE,
//成员属性
FIELD,
//方法
METHOD,
//方法参数
PARAMETER,
//构造器
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
///注解
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
TYPE_USE
}
//Documented
@Documented//Retention保存周期必须是RUNTIME
@Retention(RetentionPolicy.RUNTIME)
//Inherited
继承性
成员变量可以赋默认值
String message() default "{javax.validation.constraints.NotNull.message}";
boolean proxyTargetClass() default false;
String[] excludeName() default {};
作用
在源文件中,通过反射添加一些补充信息
3、反射获取类上、方法上的注解:反射获取注解的方式
getAnnotation:返回指定的注解
isAnnotationPresent:判定当前元素是否被指定注解修饰
getAnnotations:返回所有的注解
getDeclaredAnnotation:返回本元素的指定注解
getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的
@Test
public void t() {
Class<Dog> dogClass = Dog.class;
try {
Method method = dogClass.getDeclaredMethod("func", null);
if (method.isAnnotationPresent(Hello.class)) {
Hello annotation = method.getAnnotation(Hello.class);//@com.sankuai.wos.entity.Hello(value=[go])
Class<? extends Annotation> aClass = annotation.annotationType();
String aClassName = aClass.getName();;//com.sankuai.wos.entity.Hello
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
4、Validator注解校验参数原理
参考:https://www.cnblogs.com/54chensongxia/p/14016179.html
点击展开内容
一、Validator接口和最佳实践:hibernate.validator
1、使用
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
</dependency>
Bean Validation中内置constraint
除了非空检验注解外,其他注解校验(时间、大小、范围、正负、邮箱)必须在属性不为null的时候才生效,才会去校验。所以,一般这些注解都会结合@NotNull、@NotBlank、@NotEmpty一起使用
值为null,不生效的注解校验
public abstract class AbstractMinValidator<T> implements ConstraintValidator<Min, T> {
protected long minValue;
public AbstractMinValidator() {}
public void initialize(Min maxValue) {this.minValue = maxValue.value();}
public boolean isValid(T value, ConstraintValidatorContext constraintValidatorContext) {
if (value == null) { //@Min注解,如果值为null,注解不会生效,直接返回true
return true;
} else {
return this.compare(value) >= 0;
}
}
protected abstract int compare(T var1);
}
常见校验注解
@Valid
需要验证的实体是另外一个实体的属性。则需要加上这个注解
public class RuleDTO {
// 你要用我,你就要在你用的地方加这个注解
@Valid //这个集合的元素实体中的另外一个实体NetPoiInfo属性也需要验证,则需要加这个注解
@NotEmpty(message = "网店信息(netPoiInfos)不能为空")
private List<NetPoiInfo> netPoiInfos;
}
public class NetPoiInfo {
@NotNull(message = "网店ID不能为空")
@Positive(message = "网店ID不合法")
private Long netPoiId;
}
/**
* 预警阈值,0.0000-1.0000
*/
@FieldDoc(description = "预警阈值,0.0000-1.0000", example = {}, requiredness = Requiredness.REQUIRED)
@NotBlank(message = "概率阈值不能为空")
@Digits(integer = 1, fraction = 4, message = "概率阈值最多4位精度")
@DecimalMax(value = "1", message = "概率阈值不能超过1")
@DecimalMin(value = "0", message = "概率阈值不能小于0", inclusive = false)
private String warnThreshold;
@Digits(integer,fraction) 带批注的元素必须是一个在可接受范围内的数字
@Email 顾名思义
@Future 将来的日期
@FutureOrPresent 现在或将来的日期
@Max 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Min 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(200)
@Min(1)
private Integer age;//age为null,不赋值,校验不会生效。
建议:
@NotNull
@Max(200)
@Min(1)
private Integer age;//这样null 和 值范围,都会检验了
@NotNull
@Min(value = 18, message = "年龄小于{value}禁止入内")//将value的18,通过el表达式赋给message信息
private Integer age;//这样null 和 值范围,都会检验了
@Negative 带注释的元素必须是一个严格的负数(0为无效值)
@NegativeOrZero 带注释的元素必须是一个严格的负数(包含0)<=0
@NotBlank 同StringUtils.isNotBlank
@NotEmpty 同StringUtils.isNotEmpty
@NotNull 不能是Null
@Past 被注释的元素必须是一个过去的日期
@PastOrPresent 过去和现在
@Pattern 被注释的元素必须符合指定的正则表达式
@Positive 被注释的元素必须严格的正数(0为无效值)
@PositiveOrZero 被注释的元素必须严格的正数(包含0)>=0
@Szie(max,min) 带注释的元素大小必须介于指定边界(包括)之间
@DecimalMin(value) :>=
@DecimalMax(value) :<=
@Pattern(regexp = "/^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/"):正则表达式(手机号等)
Hibernate Validator附加constraint
@Length(min=, max=) 被注释的字符串的大小必须在指定的范围内
@Range(min=, max=) 闭区间,被注释的元素必须在合适的范围内 作用等效 @Min() + @Max()
@URL 被注释的字符串必须是一个有效的url(protocol=,host=, port=, regexp=, flags=)
@CreditCardNumber被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性
ValidatorUti工具类
@UtilityClass
public class ValidateUtil {
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public static <T> void validateParam(T param, Class<?>... groups) throws IllegalArgumentException {
if (Objects.isNull(param)) {
throw new IllegalArgumentException("参数不能为空");
}
Set<ConstraintViolation<T>> validateResult = validator.validate(param, groups);
if (CollectionUtils.isEmpty(validateResult)) {
return;
}
String validateMsg = validateResult.stream().map(ConstraintViolation::getMessage).collect(
Collectors.joining(";"));
throw new IllegalArgumentException(validateMsg);
}
}
2、spi:服务提供接口:找到Validator接口的HibernateValidator厂商实现
创建Validator接口的实现类ValidatorImpl
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();//通过工厂创建Validator接口的Hibernate实现类
a、通过buildDefaultValidatorFactory()->configure()->getValidationProviders() ->run() ->loadProviders( classloader ) ->
ServiceLoader<ValidationProvider> loader = ServiceLoader.load( ValidationProvider.class, classloader );
b、这里的ServiceLoader.load( ValidationProvider.class, classloader )即spi服务提供接口:找到这个Interface接口的实现类
javaEE是java定义规范,即定义的各种接口。在rt.jar/javax包下,定义了各种的规范(sql、validator、xml)。这些规范由不同的厂商实现。
c、同样validator也是javax下的接口规范,查找ValidationProvider接口包名(javax.validation.spi.ValidationProvider),对应的实现类
d、hibernate-validator包下META-INF/services/有个文件名,就是javax.validation.spi.ValidationProvider,文件的内容就是HibernateValidator。找到了validator接口的实现类HibernateValidator
二、@注解NotBlank 和 校验类NotBlankValidator的绑定原理
1、NotBlankValidator实现类:
内含有@Constraint注解
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
public @interface NotBlank {
String message() default "{javax.validation.constraints.NotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* Defines several {@code @NotBlank} constraints on the same element.
*
* @see NotBlank
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface List {
NotBlank[] value();
}
}
@Constraint,作用在注解上 ,内含ConstraintValidator接口
@Documented
@Target({ ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface Constraint {
Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
}
NotBlankValidator实现了ConstraintValidator接口
public class NotBlankValidator implements ConstraintValidator<NotBlank, CharSequence> {
public NotBlankValidator() {
}
public boolean isValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext) {
if (charSequence == null) {
return false;
} else {
return charSequence.toString().trim().length() > 0;
}
}
}
2、看到注解NotBlank 如何 去找NotBlankValidator实现类
a、ConstraintHelper中,有个map,将注解和实现类绑定
Map<Class<? extends Annotation>, List<ConstraintValidatorDescriptor<?>>> tmpConstraints = new HashMap();
putConstraint(tmpConstraints, NotBlank.class, NotBlankValidator.class);
putConstraints(tmpConstraints, NotEmpty.class, notEmptyValidators);
b、具体流程
spi: 工厂创建Validator接口对应的实现类ValidatorImpl ->
执行ValidateUtil方法validator.validate*(param, groups) ->*
反射:获取param上对应的注解 (@NotBlank)->
ConstraintHelper:map将@NotBlank注解 和 NotBlankValidator实现类绑定 ->
判断是否是javax的内置44个注解,是的话则从map中拿到@NotBlank对应的NotBlankValidator实现类 ->
调用NotBlankValidator实现类的isValid方法【return charSequence.toString().trim().length() > 0】 ->
如果属性为null或者"",则校验valid方法返回false- > return !isValid ? executionContext.createConstraintViolations(valueContext, constraintValidatorContext) : Collections.emptySet()
创建一个对应的实现类Validator -> new ConstraintViolationImpl(messageTemplate, messageParameters, expressionVariables) ,则将@NotBlank注解的错误message(不指定默认是javax.validation.constraints.NotBlank.message)赋给NotBlankValidator即impl,存入set
ValidateUtil工具类中返回的set集合不为空,则说明校验不通过,将set中的NotBlankValidator拿出来,获取错误message并通过Illegal参数异常抛出去
三、分组校验
1、场景
db中的主键id,添加的时候不需要,修改的时候不能为空。这种就需要分组校验
实体类
@Data
@Accessors(chain = true)
public class User {
public interface Add {} //01.定义2个接口,用于指定Group
public interface Update{}
@NotNull(groups = Update.class,message = "修改User,主键id不能为null")// 02.更新操作,id不能为null
@Null(groups = Add.class, message = "新增user,主键id必须为null")
private Integer id;
@NotNull
@Min(value = 18, message = "未满{value}禁止入内")
private Integer age;
private String name;
}
工具类校验
User user = new User().setAge(3).setId(1);
ValidateUtil.validateParam(user, User.Add.class, Default.class);
1、工具类的validateParam方法,可以指定Group,当你是Add组,会去读取@Null注解
2、最后必须加上默认的Default组,否则,其他所有注解都不生效了,只会生效你自定义的组
四、级联校验【类依赖另外一个类的值】
@Data
@Accessors(chain = true)
public class User {
@Valid//01.级联校验
@NotNull(message = "Dog不能为null")
private Dog dog;
}
@Data
public class Dog{
@NotBlank(message = "dogName不能为空")
private String name;
}
@Test
public void t() {
User user = new User();
Dog dog = new Dog();
user.setDog(dog); //02.这里的dog病灭有赋值name,如果不加上@Valid注解校验,则不会报错message = "dogName不能为空"
ValidateUtil.validateParam(user);
}
五、自定义校验【针对性取值、日期格式 和 集合元素大小】
1、用户输入的status,只能是10、20、30三个值
点击展开内容
1、自定义注解
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
//@Constraint这个注解中的validatedBy属性含义:@MyStatus注解 和 哪个对应的实现类进行绑定
//因为javax自带的44个,通过Helper的map帮助我们绑定了,自定义的注解,则需要这种形式进行绑定
@Constraint(validatedBy = MyStatusValidatorImpl.class) //将自定义注解 和 对应的Validator实现类绑定
public @interface MyStatus {
String message() default "用户输入status只能为10、20、30";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
2、自定义注解对应Validator实现类
public class MyStatusValidatorImpl implements ConstraintValidator<MyStatus, Integer> { //将自定义注解 和 对应的Validator实现类绑定
//<第一个是实现类和哪个注解进行绑定, 第二个是这个注解作用在什么类型上,是作用在Integer,还是Collection(@NotEmpty)集合,还是String【@NotBlank】,还是Object【@NotNull】>
@Override
public void initialize(MyStatus constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) { //这里值为null,返回true校验成功的原因是:自定义注解或者javax的@Min、@Positive等注解,他们本身都不校验空,他们都认为值为null时符合校验的
//如果你想校验null或空,那就配合着@NotNull、@NotBlank、@NotEmpty一起使用。
//各个注解只定义自己的功能,不要包含@NotNull、@NotBlank、@NotEmpty的功能
return true;
}
//真正的校验逻辑
Set<Integer> set = new HashSet<>();
set.add(10);
set.add(20);
set.add(30);
return set.contains(value);
}
}
补充:
这个自定义注解逻辑处理类由于实现了ConstraintValidator接口,所以它默认被spring管理成bean
所以可以在这个逻辑处理类里面用@Autowiredu或者@Resources注入别的服务,而且不用在类上面用@Compent注解成spring的bean.
这样就可以rpc请求服务/查db获取数据,用这些数据,做复杂的用户输入校验。
适合,用户输入的数据 需要和后端交互一次后,做校验的场景
3、使用
@Data
@Accessors(chain = true)
public class User {
@NotNull
@MyStatus(message = "用户输入status只能为10、20、30")//本身不校验null的场景
private Integer status;
}
@Test
public void t() {
User user = new User();
user.setStatus(1);
ValidateUtil.validateParam(user);//用户输入status只能为10、20、30
}
2、用户输入的skuId集合最多30个元素
@Constraint(validatedBy = {CollectionSizeCheckConstraintValidatorImpl.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface CollectionSizeCheck {
int value();
String message() default "集合大小不合法";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface List {
CollectionSizeCheck[] value();
}
}
@Slf4j
public class CollectionSizeCheckConstraintValidatorImpl implements ConstraintValidator<CollectionSizeCheck,
Collection> {
private int collectionSizeThreshold;
@Override
public void initialize(CollectionSizeCheck constraintAnnotation) {
this.collectionSizeThreshold = constraintAnnotation.value();
}
@Override
public boolean isValid(Collection value, ConstraintValidatorContext context) {
if (Objects.isNull(value)) {
return true;
}
return value.size() <= collectionSizeThreshold;
}
}
@CollectionSizeCheck(value = 5,message = "当前用户输入的skuId集合最多{value}个")
@NotEmpty(message = "skuIds集合不能为空")
private List<Long> skuIds;
3、日期必须为yyyy-MM-dd格式且必须为T-28到T+1 (”2022-7-5“这种格式不行)
@DateFormatCheck
@Constraint(validatedBy = {DateFormatCheckConstraintValidatorImpl.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface DateFormatCheck {
String value();
String message() default "日期格式不合法";
/**
* T-N,小于0不做校验,时间范围校验
*
* @return T-N
*/
long beforeCurrent() default -1L;
/**
* T+N,小于0不做校验,时间范围校验
*
* @return T+N
*/
long afterCurrent() default -1L;
/**
* 过去或当前时间校验,优先级高于时间范围校验
*
* @return 过去或当前时间 true
*/
boolean pastOrPresent() default false;
/**
* 未来或当前时间校验,优先级高于时间范围校验
*
* @return 未来或当前时间校验 true
*/
boolean futureOrPresent() default false;
/**
* 当前时间
*
* @return 当前时间检验
*/
boolean onlyPresent() default false;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface List {
DateFormatCheck[] value();
}
}
DateFormatCheckConstraintValidatorImpl
@Slf4j
public class DateFormatCheckConstraintValidatorImpl implements ConstraintValidator<DateFormatCheck, String> {
private String formatDateString;
private long beforeCurrent;
private long afterCurrent;
private boolean pastOrPresent;
private boolean futureOrPresent;
private boolean onlyPresent;
@Override
public void initialize(DateFormatCheck constraintAnnotation) {
this.formatDateString = constraintAnnotation.value();
this.beforeCurrent = constraintAnnotation.beforeCurrent();
this.afterCurrent = constraintAnnotation.afterCurrent();
this.pastOrPresent = constraintAnnotation.pastOrPresent();
this.futureOrPresent = constraintAnnotation.futureOrPresent();
this.onlyPresent = constraintAnnotation.onlyPresent();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StringUtils.isBlank(value)) {
return true;
}
try {
LocalDate inputLocalDate = LocalDate.parse(value, DateTimeFormatter.ofPattern(formatDateString));
LocalDate now = LocalDate.now();
if (pastOrPresent) {
// a.isBefore(a) == false
return inputLocalDate.isBefore(now.plusDays(1L));
}
if (futureOrPresent) {
// a.isAfter(a) == false
return inputLocalDate.isAfter(now.minusDays(1L));
}
if (onlyPresent) {
// t-1.23:00:00--->t:22:59:29
return isCurrentSaleDate(inputLocalDate);
}
boolean validResult = true;
if (beforeCurrent >= 0) {
// a.isAfter(a) == false
LocalDate beforeLocalDate = now.minusDays(beforeCurrent + 1L);
validResult = inputLocalDate.isAfter(beforeLocalDate);
}
if (afterCurrent >= 0) {
// a.isBefore(a) == false
LocalDate afterLocalDate = now.plusDays(afterCurrent + 1L);
validResult = validResult && inputLocalDate.isBefore(afterLocalDate);
}
return validResult;
} catch (Exception e) {
log.debug("日期格式校验不合法", e);
}
return false;
}
/**
* 22:59:59.999
*/
public static final LocalTime SELL_END_LOCAL_TIME = LocalTime.parse("22:59:59.999",
DateTimeFormatter.ofPattern("HH:mm:ss.SSS"));
/**
* 23:00:00
*/
public static final LocalTime SELL_START_LOCAL_TIME = LocalTime.parse("23:00:00", DateTimeFormatter.ISO_LOCAL_TIME);
public static boolean isCurrentSaleDate(LocalDate sellLocalDate) {
LocalTime nowLocalTime = LocalTime.now();
LocalDate nowLocalDate = LocalDate.now();
LocalDateTime sellLocalDateTime = sellLocalDate.atTime(nowLocalTime);
return sellLocalDateTime.isAfter(
nowLocalDate.minusDays(1).atTime(SELL_START_LOCAL_TIME)) && sellLocalDateTime.isBefore(
nowLocalDate.atTime(SELL_END_LOCAL_TIME));
}
}
使用
@NotBlank(message = "sellTime(销售时间)不能为null")
@DateFormatCheck(value = "yyyy-MM-dd", message = "sellTime(销售时间)必须为yyyy-MM-dd格式且必须为T-28到T+1",
beforeCurrent = 28L, //可以通过before和after指定时间范围
afterCurrent = 1L)//这里before和after可以不指定
@FieldDoc(description = "当前用户选择的销售时间", example = {}, requiredness = Requiredness.REQUIRED)
private String sellTime;
4、用户输入的日期必须符合日期格式(”2022-7-5“这种格式也可以)
@DateCheck
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateCheckValidatorImpl.class)
public @interface DateCheck {
String message() default "日期格式错误";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
DateCheckValidatorImpl
public class DateCheckValidatorImpl implements ConstraintValidator<DateCheck, String> {
private static final DateTimeFormatter PARTITION_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd", Locale.CHINA);
private static final DateTimeFormatter dateFormatter = DATE_FORMATTER.withResolverStyle(ResolverStyle.STRICT);
private static final DateValidator validator = new DateValidatorUsingDateTimeFormatter(dateFormatter);
@Override
public void initialize(DateCheck constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(String date, ConstraintValidatorContext context) {
if (StringUtils.isBlank(date)) {
return true;
}
// 将2022/06/05转换为2022-06-05
if (StringUtils.isNotBlank(date) && date.contains("/")) {
date = date.replaceAll("/", "-");
}
// 格式转换,将字符串2022-6-5或者2022-6-05或者2022-06-5,转成2022-06-05
LocalDate timeLocal;
try {
timeLocal = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-M-d"));
} catch (RuntimeException e) {
return false;
}
date = PARTITION_DATE_FORMAT.format(timeLocal);
// 判断日期格式是否合规
if (!validator.isValid(date)) {
return false;
}
return true;
}
}
public interface DateValidator {
boolean isValid(String dateStr);
}
public class DateValidatorUsingDateTimeFormatter implements DateValidator {
private final DateTimeFormatter dateFormatter;
public DateValidatorUsingDateTimeFormatter(DateTimeFormatter dateFormatter) {
this.dateFormatter = dateFormatter;
}
@Override
public boolean isValid(String dateStr) {
try {
this.dateFormatter.parse(dateStr);
} catch (DateTimeParseException e) {
return false;
}
return true;
}
}
使用
@NotBlank(message = "日期不能为null")
@DateCheck(message = "日期格式不正确")
private String time;
补充:在注解校验的message中,使用EL表达式
@Max*(message = “年龄大小不能超过{value}”,value = 180)*
注解使用
@DateFormatCheck(value = "yyyy-MM-dd", message = "sellTime(销售时间)必须为yyyy-MM-dd格式且必须为T-28到T+1", beforeCurrent = 28L,afterCurrent = 1L)
将beforeCurrent值传递到impl中
@Constraint(validatedBy = {DateFormatCheckConstraintValidatorImpl.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface DateFormatCheck {
String value();
String message() default "日期格式不合法";
/**
* T-N,小于0不做校验,时间范围校验
*
* @return T-N
*/
long beforeCurrent() default -1L;
@Slf4j
public class DateFormatCheckConstraintValidatorImpl implements ConstraintValidator<DateFormatCheck, String> {
private long beforeCurrent;
@Override
public void initialize(DateFormatCheck constraintAnnotation) {
this.beforeCurrent = constraintAnnotation.beforeCurrent();
}
根据自定义校验格式,校验用户对应的输入值
定制格式校验,可以为"2023-10-23 12:00:00"、"2023-10-23 12:00:00"、"12:45:00"、"12:45"
1、注解
@Constraint(validatedBy = {CustomizeDateFormatConstraintValidatorImpl.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface CustomizeDateFormat {
/**
* 定制格式校验,可以为"2023-10-23 12:00:00"、"2023-10-23 12:00:00"、"12:45:00"、"12:45"
*/
String value();
String message() default "时间格式不正确";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
2、Impl
public class CustomizeDateFormatConstraintValidatorImpl implements ConstraintValidator<CustomizeDateFormat, String> {
/**
* 注解@CustomizeDateFormat中value值
*/
private String customizeDateFormat;
@Override
public void initialize(CustomizeDateFormat constraintAnnotation) {
this.customizeDateFormat = constraintAnnotation.value();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StringUtils.isBlank(value)) {
return true;
}
// 不同的customizeDateFormat,对应不同的校验
DateTimeFormatter dateFormatter = DateTimeFormatter
.ofPattern(customizeDateFormat, Locale.CHINA)
.withResolverStyle(ResolverStyle.STRICT);
DateValidator validator = new DateValidatorImpl(dateFormatter);
// 判断日期格式是否合规
if (!validator.isValid(value)) {
return false;
}
// 扩展:如果格式customizeDateFormat,是带有月和日的,validator可以对月值、日值进行校验
// 0 < 月 < 13
// 0 < 日 < 31(1、3、5、7、8、10、12是31天 ; 4、6、9、11是30天,2月闰月29天否则28天)
return true;
}
}
3、接口和实现类
public interface DateValidator {
boolean isValid(String dateStr);
}
public class DateValidatorImpl implements DateValidator {
private final DateTimeFormatter dateFormatter;
public DateValidatorImpl(DateTimeFormatter dateFormatter) {
this.dateFormatter = dateFormatter;
}
@Override
public boolean isValid(String dateStr) {
try {
this.dateFormatter.parse(dateStr);
} catch (DateTimeParseException e) {
return false;
}
return true;
}
}
4、注解使用
@CustomizeDateFormat(value = "HH:mm", message = "算法售罄加量时间有误,正确格式为{value}")
private String sellOutOrTime;
5、补充:如果想校验输入是否为20230501这种类型,直接可以
这种可以校验卡住20230229、20230230、20230431这种不合法的日期,因为DateTimeFormatter.BASIC_ISO_DATE里面写的
Preconditions.checkArgument(StringUtils.isNotBlank(triggerDate), "触发日期不能为空");
LocalDate triggerLocalDate;
try {
triggerLocalDate = LocalDate.parse(triggerDate, DateTimeFormatter.BASIC_ISO_DATE);
} catch (Exception e) {
throw new IllegalArgumentException("触发日期不合法");
}
1、不要相信前端的入参
2、不要相信依赖接口的返回值非空、值符合预期
User
@Data
@NoArgsConstructor
public class User {
private String name;
private Date inBirthday;
public void setInBirthday(Date outBirthday) {
this.inBirthday = outBirthday;
}
public Date getInBirthday() {
return this.inBirthday;
}
}
@Test
public void t() {
Date outBirthday = Date.valueOf("2022-08-16");
User user = new User();
user.setName("mjp");
user.setInBirthday(outBirthday);
outBirthday.setTime(1660747569532L);//2022-08-17
System.out.println(user.getInBirthday());//0817
}
1、outBirthday是0816,通过set方法,设置给inBirthday,二者都执行同一块内存地址0X01
2、outBirthday重新设置为0817了,0X01地址对应的值变为0817,所以inBirthday也是0817了
3、get方法,return的是inBirthday,自然就是0817了
@Test
public void t() {
long outBirthday = 1660661169000L;//0816
String name = "mjp";
User user = new User();
user.setName(name);
user.setInBirthday(outBirthday);
outBirthday = 1660747569532L;//2022-08-17
System.out.println(user.getInBirthday());//还是0816
}
解法二:保护性拷贝[参考3.1和3.2]
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];//02.这个value数组,在String内部被很多其它地方使用。所以,不能改变它的值
//01. 这样对toCharArray的返回结果数组进行操作,不会影响原本的value数组的元素内容
//其实这里的toCharArray方法,就是getXxx方法。内部进行了保护性拷贝,没有直接将value数组对象返回出去,而是新创建一块内存地址返回出去
//对新地址对应的内容进行操作,不会影响value对应内存地址的值
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
}
1、方法参数尽量 <=4,超过了建议使用类代替
2、对于boolean参数,优先使用两个元素的枚举类型来表示。使得代码更易于阅读和编写以及后续扩展。
1、重载方法的选择是在编译时期就确定的,而非运行时期确定的
@Test
public void t() {
String s = "mjp";
Object obj = s;
doSomeThing(obj);
}
private void doSomeThing(Object obj) {//运行时是字符串,但是编译时是Object,所以走这个方法。
//把这个方法删除会走下面重载方法。若二者的业务逻辑不一致,则有可能造成调用结果不符合预期
System.out.println("obj");
}
private void doSomeThing(String s) {
System.out.println("s");
}
@Test
public void t() {
Collection<?> coll = new ArrayList<>();
doSomeThing(coll);
}
private void doSomeThing(Collection<?> coll) {//编译时期是Collection类型,走这个方法
System.out.println("coll");
}
private void doSomeThing(List<?> list) {
System.out.println("list");
}
2、方法重载可能存在的问题
//A类中调用B类的重载方法doSomeThing,默认是调用方法1
//如果哪天,B类中方法1被删除了,则会走方法2。若方法2和1业务逻辑不一致,则有可能造成调用结果不符合预期(但是不会报错)
String s = "mjp";
Object obj = s;
doSomeThing(obj);
//B类
//方法1
private void doSomeThing(Object obj) {//运行时是字符串,但是编译时是Object,所以默认走这个方法。
System.out.println("obj");
}
//方法2
private void doSomeThing(String s) {
System.out.println("s");
}
方法报错
删除集合中的某个元素
@Test
public void t() {
List<String> strList = Lists.newArrayList("mjp","wxx");
List<Integer> list = Lists.newArrayList(18,23);
removeEle(strList, "mjp");
removeEle(list, 18);
}
private void removeEle(List<String> strList, String ele) {
if (strList.contains(ele)) {
strList.remove(ele);
}
}
private void removeEle(List<Integer> list, Integer ele) {
if (list.contains(ele)) {
list.remove(ele);
}
}
private void removeEle(List<Integer> list, int ele) {
if (list.contains(ele)) {
list.remove(ele);//这里是删除指定下标元素,remove(int index),会报数组越界异常java.lang.IndexOutOfBoundsException: Index: 18, Size: 2
}
}
因为会自动装箱,所以int ele重载方法在if判断时候等效list.contains(Integer.valueOf(ele));
list.remove(int index):删除指定下标的元素
list.remove(Object obj):删除指定元素
3、SOP
当方法背后的逻辑一致时,才应该使用重载。
eg:PC、小程序,web、h5不同端传递的方法入参类型不一样,但是业务逻辑都一样。这样就可以提供重载方法
1、可变参数特点
2、可能存在的问题
public void foo() {}
public void foo(int a1) {}
public void foo(int a1, int a2) {}
public void foo(int a1, int a2, int a3) {}
public void foo(int a1, int a2, int a3, int... rest) {}
1、可以返回长度为0的集合
return Collections.emptyList();
2、循环遍历查询,没有数据可以返回集合
return Lists.newArrayList()
1、Optional简介
点击展开内容
2、Optional使用
点击展开内容
方法名称 | 作用 | eg | 备注 |
---|---|---|---|
empty() | 返回空的 Optional 实例 | Optional*<Integer>* optional = Optional.empty();//Optional.empty | Optional集合中有一个为Null的元素,则ifPresent返回false |
ifPresent | 值存在则方法会返回true | Optional*<Integer>* optional2 = Optional.ofNullable(1);//Optional[1] | Optional集合中有一个不为Null的元素1,则ifPresent返回true |
ofNullable(T value) | 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional | Optional*<Integer>* optional2 = Optional.ofNullable(null);//Optional.empty Optional*<Integer>* optional3 = Optional.ofNullable(3);//Optional[3] | |
map | 如果调用方有值,则对其执行调用映射函数得到返回值。 如果返回值不为 null,则创建包含映射返回值的Optional作为map方法回值,调用方无值,否则返回空Optional。 | Optional*<Integer>* optional = Optional.ofNullable(3);//Optional[3]optional有值,且map映射后的返回值也不为null,则最终返回:Optional[“0011”]Optional*<String>* optionalByte = optional.map*(Integer::toBinaryString);Optional<Integer>* optional = Optional.ofNullable(null);//Optional.emptyOptional*<String>* optionalByte = optional.map*(*Integer::toBinaryString); optional无值则返回Optional.empty | Optional*<Integer>* optional = Optional.ofNullable(3);Optional*<String>* result = optional.map*(null)*; 报错npe |
orElse**(T other)** | 如果存在该值,返回值, 否则返回 other。 | Optional*<Integer>* optional = Optional.ofNullable(1);//Optional[3] Integer result = optional.orElse*(23); System.out.println(result);//1 Optional<Integer>* optional1 = Optional.empty(); Integer result1 = optional1.orElse*(23); System.out.println(result1)*;//23 |
优雅的取值
dto.setSupplierId(
Optional.ofNullable(source.getVendorDTO()).map(VendorDTO::getVendorId).orElse(null)
);
3、Optional注意事项:
Optional<Integer> optional = Optional.empty();
Optional<String> result = optional.map(Integer::toBinaryString);
System.out.println(result);//Optional.empty
Optional<Integer> optional = null;
Optional<String> result = optional.map(Integer::toBinaryString);
System.out.println(result);//npe
一眼看过去,如果无法看清逻辑,这不是好代码
好的代码不需要你思考太多
一定记住:代码更是写给别人看的
一流代码的特性
• 高效 (Fast)
• 鲁棒 (Solid and Robust)
• 简洁 (Maintainable and Simple)
• 简短 (Small)
• 可测试 (Testable)
• 可移植 (Portable)
• 可监控 (Monitorable)
• 可扩展(Scalable & Extensible):功能的单一是复用和扩展的基础
视频链接
1、使用专业的名词代替空洞的名次(maxAge而非age、height而非size、distribute而非send、compute而非get)
2、有单位的,需要带上单位:hex、Ms、Min、Secs、MB、CM
1、boolean的变量名称不要使用反义词:dis、not(disLock)
2、在定义类的属性xxx是boolean类型时,不建议属性名为isXXX
原因:isXXX自动生成的getter方法 ,方法名称就是isXXX。
常见的序列化反序列化工具:
只有Gson是通过反射遍历获取到属性,然后将其值进行序列化,
fastJson和JackJson(SpringBoot集成了jackson,默认使用jackson来进行json序列化)是反射遍历获取对象的getter方法
二者对属性赋值时,属性名称被解析为:
public class Mjp {
private boolean isNeedGood;
private Long skuId;
//isNeedGood属性对应的get方法为getNeedGood,会把is吃掉。
//正常情况下没有什么影响,但是在json序列化的时候,对于is开头的方法,会默认(即isNeedGood去掉is,然后第一个字母小写)needGood
//这样在序列化的时候,希望是将true赋值给isNeedGood,但是实际情况是 “needGood”:true,显然没有needGood属性,这么一来,isNeedGood就未被赋值了
public boolean isNeedGood() {
return isNeedGood;
}
public Long getSkuId() {
return skuId;
}
}
public class Demo {
private Boolean isNeedMater;
public Boolean getNeedMater() {
return isNeedMater;
}
public void setNeedMater(Boolean needMater) {
isNeedMater = needMater;
}
}
Demo demo = new Demo();
demo.setNeedMater(Boolean.TRUE);
System.out.println(GsonUtil.toJsonStr(demo)); //{"isNeedMater":true}
System.out.println(new ObjectMapper().writeValueAsString((demo)));//{"needMater":true}
System.out.println(JSON.toJSONString(demo));//{"needMater":true}
这里设置isNeedMater为true,当使用fastJson进行序列化后,再通过Gson进行反序列化,结果就会出问题。
本来给isNeedMater赋值的是true,但是反序列化以后的结果是false
public class Demo {
private boolean isNeedMater;
public boolean getNeedMater() {
return isNeedMater;
}
public void setNeedMater(boolean needMater) {
isNeedMater = needMater;
}
@Override
public String toString() {
return "Demo{" +
"isNeedMater=" + isNeedMater +
'}';
}
}
Demo demo = new Demo();
demo.setNeedMater(Boolean.TRUE);
System.out.println(GsonUtil.fromJson(JSON.toJSONString(demo), Demo.class));//Demo{isNeedMater=false}
fastJson通过反射遍历找到属性isNeedMater对应的getter方法,解析认为这个类的属性是needMater,然后获取其值,将其序列化为{“needMater”,true}
然后Gson解析字符串,通过needMater找该类的属性,结果发现该类就一个属性isNeedMater,没有needMater属性。
因此Gson反序列化后isNeedMater会使用其默认值false。同理如果Boolean isNeedMater则为Demo{isNeedMater=null}
解决方式:
1、布尔类型的属性名,不建议为isXXX
2、人为使用@Data注解,注解帮忙生成getter方法,因为其生成的方法名为:getIsNeedMater
@Data : 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法
@Data
public class Demo {
private Boolean isNeedMater;
}
@Test
public void t() {
Demo demo = new Demo();
demo.setIsNeedMater(Boolean.TRUE);
demo.getIsNeedMater();//这里的getter方法名称为:getIsNeedMater
System.out.println(GsonUtil.fromJson(JSON.toJSONString(demo), Demo.class));//Demo(isNeedMater=true)
}
1、什么场景下,这样命名会有问题
2、出现问题的原因
正常情况下: skuId属性,对应get方法为getSkuId,属性名称解析为去掉get,首字母小写,skuId。 和属性名一致
属性为boolean isNeedGood基本类时,对应的get方法为isNeedGood,属性名称解析为去掉is,首字符小写,needGood。和属性名称不一致
属性为Boolean isNeedGood包装类时,默认的get方法为getNeedGood,属性名称解析为去掉get,首字符小写,needGood。和属性名称不一致
fastJson、jackson在序列化和反序列化时,是通过反射遍历找到属性isNeedMater对应的getter方法,通过get方法解析得到对应属性名称。
认为这个类的属性名称为needMater即{“needMater”,true},我们期望的是{“isNeedMater”,true}
补充:使用Gson序列化和反序列化时,不会存在上述问题:Gson是通过反射遍历直接获取到属性(不是通过解析get方法名称),对其进行序列化和反序列化
public class Demo {
private Boolean isNeedMater;
public Boolean getNeedMater() {
return isNeedMater;
}
}
Demo demo = new Demo();
demo.setNeedMater(Boolean.TRUE);
//Gson
System.out.println(GsonUtil.toJsonStr(demo)); //{"isNeedMater":true}
//jackSon
System.out.println(new ObjectMapper().writeValueAsString((demo)));//{"needMater":true}
//fastJson
System.out.println(JSON.toJSONString(demo));//{"needMater":true}
3、问题复现
Boolean isNeedMater属性,使用set方法赋值后,使用fastJson序列化,再使用Gson进行反序列化,得到的属性isNeedMater无值
public class Demo {
private boolean isNeedMater;
public boolean getNeedMater() {
return isNeedMater;
}
public void setNeedMater(boolean needMater) {
isNeedMater = needMater;
}
@Override
public String toString() {
return "Demo{" +
"isNeedMater=" + isNeedMater +
'}';
}
}
Demo demo = new Demo();
demo.setNeedMater(Boolean.TRUE);
System.out.println(GsonUtil.fromJson(JSON.toJSONString(demo), Demo.class));//Demo{isNeedMater=false}
如果是Boolean isNeedMater,同理Demo{isNeedMater=null},isNeedMater都没值
4、如何解
方式1:布尔类型属性,不要以is开头命名
方式2:使用lombok的@Data注解,代替get、set方法
@Data
public class Demo {
private Boolean isNeedMater;
}
@Test
public void t() {
Demo demo = new Demo();
demo.setIsNeedMater(Boolean.TRUE);
//demo.getIsNeedMater();//这里的getter方法名称为:getIsNeedMater
System.out.println(GsonUtil.fromJson(JSON.toJSONString(demo), Demo.class));//Demo(isNeedMater=true)
}
5、mthrift这样命名会有问题么
namespace java com.sankuai.groceryscm.vmi.client.thrift
struct User{
1: i32 id= 0;
2: required string name;
3: bool isNeedMaster;
}
@Test
public void new_test(){
byte[] bytes = serial();
System.out.println("序列化以后的对象:" + Arrays.toString(bytes));
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
parse(bis);
}
/**
* 序列化方法
*/
private static byte[] serial() {
User user = new User();
user.setId(100);
user.setName("sss");
user.setIsNeedMaster(true);
System.out.println("序列化之前的对象:" + user);
// 序列化
ByteArrayOutputStream out = new ByteArrayOutputStream();
TTransport transport = new TIOStreamTransport(out);
TBinaryProtocol tp = new TBinaryProtocol(transport);//二进制编码格式进行数据传输
// TCompactProtocol tp = new TCompactProtocol (transport);
try {
user.write(tp);
} catch (TException e) {
e.printStackTrace();
}
byte[] buf = out.toByteArray();
return buf;
}
/**
* 反序列化方法
* @param bis
*/
private static void parse(ByteArrayInputStream bis) {
User user = new User();
TTransport transport = new TIOStreamTransport(bis);
TBinaryProtocol tp = new TBinaryProtocol(transport);
// TCompactProtocol tp = new TCompactProtocol(transport);
try {
user.read(tp);
System.out.println("反序列化后的对象:" + user);
} catch (TException e) {
e.printStackTrace();
}
}
序列化之前的对象:User(id:100, name:sss, isNeedMaster:true)
序列化以后的对象:[8, 0, 1, 0, 0, 0, 100, 11, 0, 2, 0, 0, 0, 3, 115, 115, 115, 2, 0, 3, 1, 0]
反序列化后的对象:User(id:100, name:sss, isNeedMaster:true)
1、对齐:注释参数、变量
2、相似的代码,格式要一样(注释要么都在一行,要么都在末尾)
3、使用空行将大段代码分为逻辑上的”段落“(处理req的、处理resp的)
1、好的名字 > 坏的名字 + 好的注释
2、想到什么先记录下来 -> 改进一下 -> 不断改进
3、在读者的立场思考
4、Map
5、 描述方法的业务行为,而非代码行为
6、可适当加入输入输出的example
1、if优先处理正向逻辑
2、do while -> while
3、提前return可以让代码更整洁
4、if里面判断条件如果过于复杂,要抽取出一个函数或者临时变量
5、if正向逻辑过于复杂的时,可以考虑反方向
1、 while控制变量可以抽取为boolean方法,提前return
2、在第一次使用的时候再定义变量
3、避免一个操作的局部变量出现在另一个操作方法中
1、 切分模块的一种角度
• 计算数据方法(数据为中心,面向对象面向数据)
• 过程方法
1、三种场景下只能使用普通For循环
List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);
for (int i = 0; i < list.size(); i++) {
list.removeIf(integer -> integer > 3);
}
//这里也可以使用迭代器进行边遍历边删除
List<Integer> list2 = Lists.newArrayList(1, 2, 3, 4, 5);
Iterator<Integer> iterator = list2.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
if (next > 3) {
iterator.remove();
}
}
System.out.println(list2);
Map<Integer, String> map = Maps.newHashMap();
List<Integer> list1 = Lists.newArrayList(1, 2, 3);
List<String> list2 = Lists.newArrayList("mjp","xyz","cc");
for (int i = 0; i < list1.size(); i++) {
Integer age = list1.get(i);
String name = list2.get(i);
map.put(age, name);
}
1、java8的LongAdder在高并发下优于AutomicLong
AtomicLong atomicInteger = new AtomicLong(0L);
long i = atomicInteger.addAndGet(1L);
System.out.println(i);
LongAdder adder = new LongAdder();
adder.add(7L);
System.out.println(adder);
1、缺点:金钱类的不要使用double回丢失精度
2、替代
double a = 1.0;
double b = 0.9;
double c = a - b;
System.out.println(c);//0.09999999999999998
BigDecimal b1 = new BigDecimal(1.0);
BigDecimal b2 = new BigDecimal(0.9);
BigDecimal subtract = b1.subtract(b2);
System.out.println(subtract);//0.09999999999999997779553950749686919152736663818359375
BigDecimal b3 = new BigDecimal("1.0");
BigDecimal b4 = new BigDecimal("0.1");
BigDecimal subtract1 = b3.subtract(b4);
System.out.println(subtract1);//0.9
1、s1 + s2 + s3会被自动优化为sb.append(s1).append(s2).append(s3).toString()
由于字符串的不可变性,连接 n 个字符串重复使用字符串连接操作,需要 n2 的时间。
sb 对象内部维护一个字符数组。操作都是在字符数组上进行,append 方法的时间是线性的
2、字符串不适合替代其他值类型,数据本质上确实是文本信息时,使用字符串才合理
3、参考:https://www.cnblogs.com/frankyou/p/9828555.html 和 唯品会的工具类https://github.com/vipshop/vjtools/blob/master/vjkit/src/main/java/com/vip/vjtools/vjkit/text/StringBuilderHolder.java
1、不要在 finally 块中使用 return(说明:finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句)
2、sop
可以使用warn日志级别来记录用户输入参数错误的情况。如非必要,请不要在此场景打出 error 级别,避免频繁报警(说明:注意日志输出的级别,error 级别只记录系统逻辑出错、异常或者重要的错误信息)
Business_error和interalError的区别:interal异常主要是一些无法预料的原因导致的rpc失败,比如网络抖动超时等。
catch匹配到异常后,会把异常吃掉。如果你在catch中打了相关信息,没有再向上抛出异常,则异常就在此处被吃掉了。如果是@Trasactional注解,异常就不能被吃掉,就需要在catch中再向上throw,这样事物才能一致。
调用者为前端的时候,如果你不想让前端在调用时抛出红色异常。那么你就不在最外层catch中再次throw一个异常,而是吃掉这个异常,并且给出相应的code值和message即可。打出error日志即可
1、不要忽略捕捉的异常
catch (NoSuchMethodException e) {
return null;
}
虽然捕捉了异常但是却没有做任何处理,除非你确信这个异常可以忽略,不然不应该这样做。这样会导致外面无法知晓该方法发生了错误,无法确定定位错误原因。
2、在你的方法里抛出定义具体的检查性异常
public void foo() throws Exception { //错误方式
}
推荐:
public void foo() throws SpecificException1, SpecificException2 { //正确方式
}
3、捕获具体的子类而不是捕获 Exception 类
try {
someMethod();
} catch (Exception e) { //错误方式
LOGGER.error("method has failed", e);
}
推荐:
try {
rpc();
} catch (TException e) {
LOGGER.error("method has failed", e);
}
4、始终正确包装自定义异常中的异常,以便堆栈跟踪不会丢失
catch (NoSuchMethodException e) {
throw new MyServiceException("Some information: " + e.getMessage()); //错误方式
}
推荐:
catch (NoSuchMethodException e) {
throw new MyServiceException("Some information: " , e); //正确方式
}
5、要么记录异常要么抛出异常,但不要一起执行
catch (NoSuchMethodException e) {
//错误方式
LOGGER.error("Some information", e);
throw e;
}
正如上面的代码中,记录和抛出异常会在日志文件中产生多条日志消息,代码中存在单个问题,并且对尝试分析日志的同事很不友好。
6、finally 块中永远不要抛出任何异常
7、始终只捕获实际可处理的异常
catch (NoSuchMethodException e) {
throw e; //避免这种情况,因为它没有任何帮助
}
不要为了捕捉异常而捕捉,只有在想要处理异常时才捕捉异常,或者希望在该异常中提供其他上下文信息。如果你不能在 catch 块中处理它,那么最好的建议就是不要只为了重新抛出它而捕获它。
8、不要使用 printStackTrace() 语句或类似的方法
最终别人可能会得到这些堆栈,并且对于如何处理它完全没有任何方法,因为它不会附加任何上下文信息。
9、记住早 throw 晚 catch 原则
应该尽快抛出(throw)异常,并尽可能晚地捕获(catch)它。应该等到有足够的信息来妥善处理它。
10、在异常处理后清理资源
则仍应使用 try-finally 块来清理资源。 在 try 模块里面访问资源,在 finally 里面最后关闭资源。即使在访问资源时发生任何异常,资源也会优雅地关闭。
11、尽早验证用户输入以在请求处理的早期捕获异常
12、一个异常只能包含在一个日志中,在日志文件中这两个日志消息可能会间隔 100 多行。应该这样做:
LOGGER.debug("Using cache sector A");
LOGGER.debug("Using retry sector B");
推荐:
LOGGER.debug("Using cache sector A, using retry sector B");
13、编写多重catch语句块注意事项:顺序问题:先小后大,即先子类后父类
否则,捕获底层异常类的catch子句将可能会被屏蔽。
14、多个异常的处理逻辑一致时,使用JDK7的语法避免重复代码
try {
...
} catch (AException | BException | CException ex) {
handleException(ex);
}
15、异常处理不能吞掉原异常,要么在日志打印,要么在重新抛出的异常里包含原异常
catch(XxxException e){
//WRONG
throw new MyException("message");
//RIGHT 记录日志后抛出新异常,向上次调用者屏蔽底层异常
logger.error("message", ex);
throw new MyException("message");
//RIGHT 传递底层异常
throw new MyException("message", ex);
}
16、如果处理过程中有抛出异常的可能,也要做try-catch,否则finally块中抛出的异常,将代替try块中抛出的异常
//WRONG
try {
...
throw new TimeoutException();
} finally {
file.close();//如果file.close()抛出IOException, 将代替TimeoutException
}
//RIGHT, 在finally块中try-catch
try {
...
throw new TimeoutException();
} finally {
IOUtil.closeQuietly(file); //该方法中对所有异常进行了捕获
}
17、不能在finally块中使用return,finally块中的return将代替try块中的return及throw Exception
//WRONG
try {
...
return 1;
} finally {
return 2; //实际return 2 而不是1
}
try {
...
throw TimeoutException();
} finally {
return 2; //实际return 2 而不是TimeoutException
}
若共享的可变数据只需要可见,则使用Volatile即可(不提供互斥)。多线程要注意互斥,正常情况下需要使用同步、锁
对字符串加锁,为了互斥性,需要使用synchronized(s.intern())
因为字符串常量池和堆内存中,地址不一样,不互斥。加上intern()就互斥了
并发集合,代替使用锁
同步区域内少执行任务,计算工作最好放在锁外部
Executors 返回的线程池对象的弊端如下:
\1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM
\2) CachedThreadPool 和 ScheduledThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM
4、在高并发场景中,避免使用”等于”判断作为中断或退出的条件(说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间 判断条件来代替)
Map | Key | Value |
---|---|---|
HashMap | Nullable | Nullable |
ConcurrentHashMap | NotNull | NotNull |
TreeMap | NotNull | Nullable |
Executor 框架;并发集合;同步器:CountDownLatch
@Test
public void t() throws InterruptedException {
// 01.创建门栓
int threadCount = 5;
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
AtomicInteger atomicInteger = new AtomicInteger(0);
// 02.创建线程执行
Integer baseScore = 10000;
Random random = new Random();
List<CompletableFuture> cfList = new ArrayList<>();
for (int i = 1; i < threadCount+1; i++) {
int finalI = i;
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
int score = baseScore + random.nextInt(2000);
atomicInteger.addAndGet(score);
System.out.println("第" + finalI + "个运动员的成绩:" + score + "");
countDownLatch.countDown();
});
cfList.add(cf);
}
cfList.forEach(CompletableFuture::join);
// 03.解开
countDownLatch.await();
System.out.println(atomicInteger.get() / threadCount);
}
countDownLatch-执行先后顺序【可实现分布式锁】
@Test
public void t() throws InterruptedException{
CountDownLatch u1 = new CountDownLatch(1);
CountDownLatch u2 = new CountDownLatch(1);
CountDownLatch u3 = new CountDownLatch(1);
CountDownLatch u4 = new CountDownLatch(1);
CountDownLatch u5 = new CountDownLatch(1);
// 0.1通过count和await定义执行顺序
Thread top = new Thread(() -> {
System.out.println("上单选择英雄完毕");
u1.countDown();
});
Thread jog = new Thread(() -> {
try {
u1.await();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
System.out.println("打野选择英雄完毕");
u2.countDown();
});
Thread mid = new Thread(() -> {
try {
u2.await();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
System.out.println("中单选择英雄完毕");
u3.countDown();
});
Thread adc = new Thread(() -> {
try {
u3.await();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
System.out.println("ADC选择英雄完毕");
u4.countDown();
});
Thread assist = new Thread(() -> {
try {
u4.await();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
System.out.println("辅助选择英雄完毕");
u5.countDown();
});
// 02.执行
assist.start();
mid.start();
top.start();
adc.start();
jog.start();
u5.await();
System.out.println("全部完成");
}
停止单条线程,执行Thread.interrupt()。
停止线程池:参考:唯品会工具类gracefulShutdown
public class InterrupTest implements Runnable{
@Override
public void run(){
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
boolean interrupted1 = Thread.interrupted();
System.out.println("t线程收到main的请求中断,但是t处于阻塞,所以抛出异常,并将中断信号变成: "+interrupted1+"");
//恢复中断状态,即恢复线程t被main线程告知应该中断的信号,以便main线程中能知道t线程的中断,并且对中断作出响应
//如果这里不恢复中断请求,等于停止了main要求t中断的请求,外层函数将收不到中断请求,继续原有循环(一直while循环)
Thread.currentThread().interrupt();
boolean interrupted2 = Thread.interrupted();
System.out.println("恢复线程t被main线程告知应该中断: "+interrupted2+"");
}
}
public static void main(String[] args) {
InterrupTest si = new InterrupTest();
Thread t = new Thread(si);
t.start();
// 01.主线程sleep 2s 后再执行对t线程的中断,让t执行一会
sleepSecond(2);
// 02.中断线程t
t.interrupt();
// 03.如果线程t未被中断,则xxx,中断了则结束
while (!t.isInterrupted()) {
System.out.println("t继续执行");
}
}
public static void sleepSecond(int time) {
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException exception) {
}
}
}
try {
...
} catch (AException | BException | CException ex) {
handleException(ex);
}
序列化不走构造器(clone也是),在构造器中进行了提前的安全检查,会被绕过
大大降低了灵活性:一旦确认了序列化的形式,后续任何变动都可能导致使用这个格式进行反序列化的程序报错
建议显示的指定serialVersionUID:版本控制,表明类的不同版本间的兼容性
不指定可能存在的问题
点击展开内容
User类实现了序列化,属性age和name【版本1】
但是未指定serialVersionUID,再序列化的时候JVM会根据age、name计算出一个id-A值,和属性一起,共同组成user1后,序列化,再进行网络传输并以二进制字节流的形式持久化到磁盘(数据的id位A值)
反序列化user1的时候,JVM会再根据属性name、age自动生成一个id-B,比较id-B和id-A,相同则反序列化成功,否则报错
问题:
现在user类新增了一个属性sex性别【版本2】
那么,在对旧版本1的持久化数据user1,进行反序列化操作时
JVM会再根据版本2的属性age、name、sex进行计算生成一个id-B2,比较id-B2和id-A,此时两个值明显不一样【计算时的属性个数都不一样】,所以反序列化use1时候,会报错
(反序列化时系统会自动检测二进制文件中的serialVersionUID,判断它是否与当前类中的serialVersionUID【用户定义了则使用定制值,没有定义则JVM根据类属性等实时计算出一个值】一致。如果一致说明序列化文件的版本与当前类的版本是一样的,可以反序列化成功,否则就失败)
解决问题:
User类实现了序列化,属性age和name【版本1】
指定serialVersionUID = 1
再序列化的时候JVM会id-A = 1值,和属性一起,共同组成user1后,序列化,再进行网络传输并以二进制字节流的形式持久化到磁盘(数据的id位A=1值)
反序列化user1的时候,JVM会再根据属性name、age自动生成一个id-B,比较id-B和id-A=1,相同则反序列化成功,否则报错InvalidClassExceptions
问题:
现在user类新增了一个属性sex性别【版本2】
那么,在对旧版本1的持久化数据user1,进行反序列化操作时
JVM会再根据版本2得到 id-B2 = 1,比较id-B2和id-A,此时两个值都是1,所以反序列化use1成功
建议自定义生成serialVersionUID而不是使用默认值1:https://blog.csdn.net/wufaqidong1/article/details/127295513
反序列化的对象,不会调用构造函数重新构造,而是基于二进制文件进行生成的新对象
序列化前的对象和序列后的对象,地址不一样,但是equals是ture,因为是是深copy
序列化和持久化的关系
前者是为了跨进程调用,后者为了写入磁盘