《Java开发手册(嵩山版)》-知其所以然
阅读手册后虽然知道手册上说的比较好, 但是其中原理还是想琢磨清楚, 所以整理了以下一些点, 供解惑
serialVersionUID
【强制】序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;如果 完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。 说明:注意 serialVersionUID 不一致会抛出序列化运行时异常。
所以然:
为什么阿里巴巴要求程序员谨慎修改serialVersionUID 字段的值
修复推荐:
依据所以然
包装类对象(Integer、String…)之间值的 == 判断
【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。 说明:对于 Integer var = ? 在-128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache 产生, 会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都 会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。
所以然:
基础知识回顾
判断引用类型相等
在Java中,判断值类型的变量是否相等,可以使用
==
运算符。但是,判断引用类型的变量是否相等,==
表示“引用是否相等”,或者说,是否指向同一个对象。例如,下面的两个String类型,它们的内容是相同的,但是,分别指向不同的对象,用==
判断,结果为false
:
触发问题场景:
public static void main(String[] args) {
Integer testA = 300;
Integer testB = 300;
System.out.println(testA == testB);
}
触发问题场景微调修复:
public static void main(String[] args) {
Integer testA = 300;
Integer testB = 300;
System.out.println(testA.intValue() == testB.intValue());
}
触发问题场景修复思路:
来源于equals方法
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
修复推荐:
还是继续使用equals
方法方便快捷
DO 类时,属性类型要与数据库字段类型相匹配。
【强制】定义数据对象 DO 类时,属性类型要与数据库字段类型相匹配。
正例:数据库字段的 bigint 必须与类属性的 Long 类型相对应。
反例:某个案例的数据库表 id 字段定义类型 bigint unsigned,实际类对象属性为 Integer,随着 id 越来 越大,超过 Integer 的表示范围而溢出成为负数。
所以然:
java中int的取值范围为-2147483648到+2147483648
mysql中int、bigint、smallint 和 tinyint的区别详细介绍
修复推荐:
依据正例
POJO 类必须写 toString 方法
【强制】POJO 类必须写 toString 方法。使用 IDE 中的工具:source> generate toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。 说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题
所以然:
普通toString
java.lang.Object@7852e922
重写toString
toStringTest [name=zout, sex=man, No=1]
重写后直接打印或者使用log时候会显示其中值而不是内存地址
修复推荐:
使用Lombok中的@ToString或使用@Data
慎用 Object 的 clone 方法来拷贝对象。
【推荐】慎用 Object 的 clone 方法来拷贝对象。
说明:对象 clone 方法默认是浅拷贝,若想实现深拷贝,需覆写 clone 方法实现域对象的深度遍历式拷贝。
所以然
为什么阿里Java手册推荐慎用 Object 的 clone 方法来拷贝对象
修复推荐
依据所以然
hashCode 和 equals 的处理
【强制】关于 hashCode 和 equals 的处理,遵循如下规则:
1) 只要覆写 equals,就必须覆写 hashCode。
2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写 这两种方法。
3) 如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。 说明:String 因为覆写了 hashCode 和 equals 方法,所以可以愉快地将 String 对象作为 key 来使用。
所以然:
Java的Object.hashCode()的返回值到底是不是对象内存地址?
修复推荐
依据我平时的代码测试,如果实现了Lombok中的@EqualsAndHashCode
则会重写hashCode以及equals, 对象中的值变动hashCode也会跟随变动, 如果使用并且场景有限, 可安稳使用.
在引用对象中也会重写(Integer,String)这两个方法,且可用, 所以也不用担心触发对比上的问题
其他场景还需要遵循
判断所有集合内部的元素是否为空,使用 isEmpty()方法
【强制】判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式。 说明:在某些集合中,前者的时间复杂度为 O(1),而且可读性更好。 正例: Map map = new HashMap<>(16); if(map.isEmpty()) { System.out.println("no element in this map."); }
所以然:
ConcurrentLinkedQueue.java这个集合的size()时间复杂度就不是o(1)
/**
* Returns {@code true} if this queue contains no elements.
*
* @return {@code true} if this queue contains no elements
*/
public boolean isEmpty() {
return first() == null;
}
/**
* Returns the number of elements in this queue. If this queue
* contains more than {@code Integer.MAX_VALUE} elements, returns
* {@code Integer.MAX_VALUE}.
*
* <p>Beware that, unlike in most collections, this method is
* <em>NOT</em> a constant-time operation. Because of the
* asynchronous nature of these queues, determining the current
* number of elements requires an O(n) traversal.
* Additionally, if elements are added or removed during execution
* of this method, the returned result may be inaccurate. Thus,
* this method is typically not very useful in concurrent
* applications.
*
* @return the number of elements in this queue
*/
public int size() {
restartFromHead: for (;;) {
int count = 0;
for (ConcurrentLinkedQueue.Node<E> p = first(); p != null;) {
if (p.item != null)
if (++count == Integer.MAX_VALUE)
break; // @see Collection.size()
if (p == (p = p.next))
continue restartFromHead;
}
return count;
}
}
而一般则为:
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
修复推荐
统一使用 isEmpty()方法,而不是 size()==0 的方式
toMap()方法转为 Map 集合时,一定要注 意当 value 为 null 时会抛 NPE 异常
/*【强制】在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要注 意当 value 为 null 时会抛 NPE 异常。 说明:在 java.util.HashMap 的 merge 方法里会进行如下的判断:*/ if (value == null || remappingFunction == null) throw new NullPointerException();
所以然:
public static void main(String[] args) {
List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 12.10));
pairArrayList.add(new Pair<>("version", 12.19));
pairArrayList.add(new Pair<>("version", null));
Map<String, Double> map = pairArrayList.stream().collect(Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
System.out.println(map);
}
以上运行会报错
//java.util.HashMap#merge
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (value == null)
throw new NullPointerException();//null报错来源util
if (remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
...
修复推荐
确保value没有null
ArrayList 的 subList 结果不可强转成 ArrayList
【强制】ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异 常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
所以然
subList()返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 本身,而是 ArrayList 的一个视 图,对于 SubList 的所有操作最终会反映到原列表上。
修复推荐
ArrayList分为深拷贝和浅拷贝:https://www.cnblogs.com/luxd/p/11933686.html
另外延伸出来对象的拷贝:https://www.cnblogs.com/qian123/p/5710533.html
测试代码:
public static void main(String[] args) {
class UserTest implements Serializable,Cloneable {
String id;
int age;
public UserTest(String id, int age) {
this.id = id;
this.age = age;
}
public UserTest() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "UserTest{" +
"id='" + id + '\'' +
", age=" + age +
'}';
}
@Override
public UserTest clone(){
UserTest clone = null;
try {
clone = (UserTest) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
UserTest user1 = new UserTest("小明", 18);
UserTest user2 = new UserTest("小红", 16);
List<UserTest> list = new ArrayList<>();
list.add(user1);
list.add(user2);
System.out.println("原List:" + list);
// 进行深度复制
List<UserTest> listNew = new ArrayList<>();
for (int i = 0; i < list.size(); i += 1) {
listNew.add((UserTest) list.get(i).clone());
}
//
// List<UserTest> listNew = null;
// try {
// listNew = deepCopy(list);
// } catch (IOException e) {
// e.printStackTrace();
// } catch (ClassNotFoundException e) {
// e.printStackTrace();
// }
//
System.out.println("对新list进行操作");
for (UserTest userTest : listNew) {
userTest.setAge(99);
}
System.out.println("原list" + list);
System.out.println("新list" + listNew);
}
//关键代码 运行序列化和反序列化 进行深度拷贝
public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
try {
out.writeObject(src);
} catch (IOException e) {
e.printStackTrace();
}
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
@SuppressWarnings("unchecked")
List<T> dest = (List<T>) in.readObject();
return dest;
}
使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法, 它的 add/remove/clear 方法会抛出异常
【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法, 它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。 说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配 器模式,只是转换接口,后台的数据仍是数组。 String[] str = new String[] { "chen", "yang", "hao" }; List list = Arrays.asList(str); 第一种情况:list.add("yangguanbao"); 运行时异常。 第二种情况:str[0] = "change"; 也会随之修改,反之亦然。
所以然:
// 正例
if(CollectionUtil.isEmpty(this.warehouseWarrantDocs)) {
List<WarehouseWarrantDoc> warehouseWarrantDocs = new ArrayList<>();
warehouseWarrantDocs.add(warehouseWarrantDoc);
this.warehouseWarrantDocs = warehouseWarrantDocs;
} else {
this.warehouseWarrantDocs.add(warehouseWarrantDoc);
}
//反例1
if(CollectionUtil.isEmpty(this.warehouseWarrantDocs)) {
this.warehouseWarrantDocs = Arrays.asList(warehouseWarrantDoc);
} else {
this.warehouseWarrantDocs.add(warehouseWarrantDoc);
}
//反例2
if(CollectionUtil.isEmpty(this.warehouseWarrantDocs)) {
this.warehouseWarrantDocs = Collections.singletonList(warehouseWarrantDoc);
} else {
this.warehouseWarrantDocs.add(warehouseWarrantDoc);
}
反例会在接下来的add时候抛异常, 正例则不会
修复推荐
根据使用场景来选择
不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。正例:List
list =newArrayList<>();list.add("1");list.add("2");Iterator iterator =list.iterator();while(iterator.hasNext()){String item =iterator.next();if(删除元素的条件){iterator.remove();}}反例:for(String item :list){if("1".equals(item)){list.remove(item);}}
所以然
https://blog.csdn.net/qq_36827957/article/details/88415168
简单总结一下,之所以会抛出ConcurrentModificationException异常,是因为我们的代码中使用了增强for循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常,用来提示用户,可能发生了并发修改。
修复推荐
根据所以然
SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。
所以然
https://blog.csdn.net/zxh87/article/details/19414885
修复推荐
根据所以然
这也同时提醒我们在开发和设计系统的时候注意下一下三点:
1.自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明
2.对线程环境下,对每一个共享的可变变量都要注意其线程安全性
3.我们的类和方法在做设计的时候,要尽量设计成无状态的
【参考】HashMap在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中注意规避此风险。
【参考】HashMap在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中注意规避此风险。
所以然
//todo 仔细研读
https://juejin.cn/post/6844903554264596487
修复推荐
根据所以然
所以在并发的情况,发生扩容时,可能会产生循环链表,在执行get的时候,会触发死循环,引起CPU的100%问题,所以一定要避免在并发环境下使用HashMap。
曾经有人把这个问题报给了Sun,不过Sun不认为这是一个bug,因为在HashMap本来就不支持多线程使用,要并发就用ConcurrentHashmap。
作者:占小狼
链接:https://juejin.cn/post/6844903554264596487
来源:稀土掘金
服务器内部重定向必须使用forward;外部重定向地址必须使用URL统一代理模块生成
【强制】服务器内部重定向必须使用forward;外部重定向地址必须使用URL统一代理模块生成,否则会因线上采用HTTPS协议而导致浏览器提示“不安全”,并且还会带来URL维护不一致的问题。
所以然
理解跳转:https://www.liaoxuefeng.com/wiki/1252599548343744/1328761739935778
修复推荐
根据场景修复
在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度
在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。说明:不要在方法体内定义:Pattern pattern = Pattern.compile(“规则”);
所以然
java预编译功能可以有效加快正则匹配速度
修复推荐
https://blog.csdn.net/qq_20051535/article/details/113572624
private static final Pattern pattern = Pattern.compile(regexRule);
private void func(...) {
Matcher m = pattern.matcher(content);
if (m.matches()) {
...
}
}
避免用Apache Beanutils进行属性的copy
避免用Apache Beanutils进行属性的copy。说明:Apache BeanUtils性能较差,可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier,注意均是浅拷贝。
所以然
分析: https://segmentfault.com/a/1190000019356477
修复推荐
根据所以然
如果JDK7及以上,可以使用try-with-resources方式对资源对象、流对象进行关闭
【强制】finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。说明:如果JDK7及以上,可以使用try-with-resources方式。
所以然
理解try-with-resources语句及示例: https://blog.csdn.net/frgod/article/details/53414813
修复推荐
根据所以然理解并使用try-with-resources
不要在finally块中使用return。
【强制】不要在finally块中使用return。
说明:try块中的return语句执行成功后,并不马上返回,而是继续执行finally块中的语句,如果此处存在return语句,则在此直接返回,无情丢弃掉try块中的返回点。
所以然
java异常体系---不要在finally块中使用return、throw
当在finally块中使用return、throw时,编译器不会再对try、catch块中的非运行时异常进行检查,JVM不会再去捕获try块、catch块中的异常,程序的输出以finally块为准,即finally块的返回值或者finally块中抛出的异常。
当在try块或catch块中遇到return语句时,finally块将在方法返回之前被执行。finally块中的return语句会覆盖try块、catch块中的return语句。合理的做法是在 finally 块之后使用return语句。
修复推荐
根据所以然和规范
避免重复打印日志,浪费磁盘空间,务必在日志配置文件中设置additivity=false
【强制】避免重复打印日志,浪费磁盘空间,务必在日志配置文件中设置additivity=false。正例:
所以然
测试代码
public static void main(String[] args) {
try {
int a;
a = 1;
int b = a / 0;
} catch (Exception e) {
for (int i = 0; i < 10000; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println(e.getMessage() + getRandomString(10000));
e.printStackTrace();
}
}
}
public static String getRandomString(int length){
String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random=new Random();
StringBuffer sb=new StringBuffer();
for(int i=0;i<length;i++){
int number=random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
additivity属性简介:
它是子Logger是否继承父Logger的输出源(appender)的标志位,默认情况下子Logger会继承父Logger的appender,也就是说子Logger会在父Logger的appender里输出。把additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。
修复推荐
根据所以然和规范
Java代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的结果。
【强制】用户请求传入的任何参数必须做有效性验证。说明:忽略参数校验可能导致:⚫page size过大导致内存溢出⚫恶意order by导致数据库慢查询⚫缓存击穿⚫SSRF⚫任意重定向⚫SQL注入,Shell注入,反序列化注入⚫正则输入源串拒绝服务ReDoSJava代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的结果。
所以然
主要讨论正则回溯计算导致的cpu占用问题: https://www.cnblogs.com/Eleven-Liu/p/10826488.html
修复推荐
根据所以然的理解改进正则
如果有order by的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查询性能
如果有order by的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查询性能。正例:where a=? and b=? order by c; 索引:a_b_c反例:索引如果存在范围查询,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引a_b无法排序。
所以然
根据手册
修复推荐
根据所以然的理解改进
利用覆盖索引来进行查询操作,避免回表
利用覆盖索引来进行查询操作,避免回表。说明:如果一本书需要知道第11章是什么标题,会翻开第11章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用explain的结果,extra列会出现:using index。
所以然
根据实际情况和相关教程改为覆盖索引如何避免回表查询?什么是索引覆盖? | 1分钟MySQL优化系列
修复推荐
根据所以然的理解改进
所有pom文件中的依赖声明放在语句块中,所有版本仲裁放在语句块中。
【推荐】所有pom文件中的依赖声明放在
语句块中,所有版本仲裁放在 语句块中。说明: 里只是声明版本,并不实现引入,因此子项目需要显式的声明依赖,version和scope都读取自父pom。而 所有声明在主pom的 里的依赖都会自动引入,并默认被所有的子项目继承。
所以然
dependencyManagement使用简介
参考:https://blog.csdn.net/weixin_42114097/article/details/81391024
dependencyManagement 意义及与dependencies的区别
参考:https://www.cnblogs.com/zhangmingcheng/p/10984036.html
修复推荐
根据所以然的理解改进
高并发服务器建议调小TCP协议的time_wait超时时间。
【推荐】高并发服务器建议调小TCP协议的time_wait超时时间。说明:操作系统默认240秒后,才会关闭处于time_wait状态的连接,在高并发访问下,服务器端会因为处于time_wait的连接数太多,可能无法建立新的连接,所以需要在服务器上调小此等待值。
所以然
理解TIME_WAIT,彻底弄清解决TCP: time wait bucket table overflow
解读TIME_WAIT--你在网上看到的大多数帖子可能都是错误的
修复推荐
根据所以然的理解改进
在线上生产环境,JVM的Xms和Xmx设置一样大小的内存容量,避免在GC后调整堆大小带来的压力。
【推荐】高并发服务器建议调小TCP协议的time_wait超时时间。说明:操作系统默认240秒后,才会关闭处于time_wait状态的连接,在高并发访问下,服务器端会因为处于time_wait的连接数太多,可能无法建立新的连接,所以需要在服务器上调小此等待值。
所以然
https://blog.csdn.net/qq_34556414/article/details/112648113
面对上面的问题,为了避免在生产环境由于heap内存扩大或缩小导致应用停顿,降低延迟,同时避免每次垃圾回收完成后JVM重新分配内存。所以,-Xmx和-Xms一般都是设置相等的。
当然,如果生产系统上线前有一段预热时间的话,也可以不设置相等。对于需要高吞吐量的应用来说,可以不在乎这种停顿,比如一些后台的应用之类,那么内存可以适当调大一些。(停顿时间越长,吞吐量反而越大),需要根据具体情况权衡。
注意事项
其实虽然设置为相同值有很多好处,但也会有一些不足。比如,如果两个值一样,会减少GC的操作,也意味着只有当JVM即将使用完时才会进行回收,此前内存会不停的增长。
并且同一JDK的GC策略也有很多种,不能一概而论。另外,对于Hotspot虚拟机,Xms和Xmx设置为一样的,可以减轻伸缩堆大小带来的压力。但对于IBM虚拟机,设置为一样会增大堆碎片产生的几率,并且这种负面影响足以抵消前者产生的益处。
修复推荐
根据所以然的理解改进
设计规约
多做设计, 理清思路, 方便你我他
- 感谢你赐予我前进的力量