本文更多关注平时容易忽略的技巧或者细节,不是条条框框的JAVA入门教程。想到什么或者看到什么比较合适就写下来了,不定期更新

文章目录

一、序列化
二、Final不可继承类
三、Enum
四、双重检查单实例
五、InterruptedException异常处理
六、StringUtils\CollectionUtils\RestTemplate
七、异常处理
八、Finally
九、Maven管理多模块
十、Assembly plugin自定义打包
十一、线程池
十二、读写锁ReadWriteLock
十三、覆写、重载、泛型
十四、并发容器
十五、lost wake up问题
十六、Thread.sleep(0)的妙用
十七、高效合理设计RPC接口
1、RPC接口异常显式返回
2、使用Specification规格模式解决查询接口过多的问题
十八、String对象
十九、单例模式与垃圾回收
二十、Arrays#asList容易踩的坑

一、序列化

随着微服务的推行,越来越多的服务转变成远程服务调用,中间最重要的就是序列化和反序列化,那么数据传输的安全性就不可轻视,不少黑客就是通过序列化漏洞获取到敏感数据,我们应该对重要字段进行过滤

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Person implements Serializable {private static final  long serialVersionUID = 1L;private String firstName;private String lastName;private static String job;                      // 静态字段不参与序列化private transient String birthday;              // 敏感数据private transient String socialSecurityNumber;  // 敏感数据//    private static final ObjectStreamField[]
//            serialPersistentFields = {
//            new ObjectStreamField("firstName", Person.class),
//            new ObjectStreamField("lastName", Person.class)
//    };
}

1、POJO一般都实现Serializable并且设置serialVersionUID的值,以便对象传递和保证完整性,如果有父子类则父类必须实现

2、必须实现set\get\toString方法,但是不要在set\get方法上处理业务逻辑,因为如果直接访问属性,后续扩展权限非常难处理

3、int和Integer也就是基本类型和对象类型的使用,虽然有装箱和拆箱机制,但是Integer是有缓存的,是没有默认值的,原生的int运算时可能会出现溢出

4、private static final long serialVersionUID = 1L; 在可兼容的前提下,可以保留旧版本号,如果不兼容,或者想让它不兼容,就手工递增版本号 private static final long serialVersionUID = -2805284943658356093L;是根据类的结构产生的hash值,增减一个属性、方法等,都可能导致这个值产生变化

5、在同一个流ObjectOutputStream中writeObject相同对象,序列化后的对象长度不会叠加,只是会多了引用长度

6、深克隆与浅克隆,默认实现Cloneable的POJO的clone()是浅克隆,可以通过序列化和反序列实现深克隆

7、设计实现了Serializable接口的单例,序列化会通过反射调用无参构造器返回新对象,我们可以添加readResolve()方法,自定义返回对象策略

二、Final不可继承类

当我们为父类添加新方法、变更方法的规范、改变未继承实现的方法都会对子类有影响。比如说我们有个子类继承了HashTable类进行敏感数据的处理,在get\set时进行鉴权,突然某天HashTable添加了EntrySet方法,那么就有可能通过它绕过权限校验造成问题。

三、Enum

对于数量有限的对象,应该优先考虑使用Enum,减少实例的数量,减少内存的使用。另外一种好处就是减少用数字表示,代码可读性更好

package com.front.ops.soa;public enum HelloWords {ENGLISH ("ENGLISH","Hello"),SPANISH ("Spanish","hola");final String language;final  String greeting;HelloWords(String  language,String greeting) {this.language = language;this.greeting = greeting;}
}

四、双重检查单实例

public class Singleton {//volatile禁止指令重排序优化,因为初始化Singleton和将对象赋给//instance字段的顺序是不确定的,容易出现NPE异常private volatile static Singleton instance = null;private Singleton(){}public static Singleton getInstance() {// 第一次校验,保证只需要使用重量级锁synchronized一次if (instance == null) {   synchronized (Singleton.class) {// 第二次校验,避免线程并发出现多次创建if (instance == null) { instance = new Singleton();}}}return instance;}
}

五、InterruptedException异常处理

需要知道的一点是在触发InterruptedException异常的同时,JVM会同时把线程的中断标记位清除掉

Thread th = Thread.currentThread();
while(true) {if(th.isInterrupted()) {break;}// 省略业务代码无数try {Thread.sleep(100);}catch (InterruptedException e){// 必须重新设置中断标记位th.interrupt();e.printStackTrace();}
}

六、StringUtils\CollectionUtils\RestTemplate

多用工具库,少造轮子,比如常见的List转成有特殊分隔符的String、Curl请求,下面是RestTemplate比较常见的用法

示例一

 HttpEntity<String> entity = null;try {entity = new HttpEntity<>(objectMapper.writeValueAsString(data));} catch (IOException e) {e.printStackTrace();}ResponseEntity<String> response = restTemplate.postForEntity(getAllInterfaceAliasesByIpsUrl, entity, String.class);

示例二

    String entity = null;try {entity = objectMapper.writeValueAsString(data);} catch (IOException e) {e.printStackTrace();}ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<String>() {};RequestEntity<String> request = RequestEntity.post(URI.create(getServerStatusListByInterfaceUrl)).body(entity);ResponseEntity<String> response = restTemplate.exchange(request, responseType);

七、异常处理

  1. 尽量不要捕获类似Exception这样的通用异常,否则会被隐藏掉一些信息,而是应该捕获特定异常,如InterruptedException
  2. 尽量不要捕获Error或者Throwable这种异常,因为很难保证程序可以正确处理OOM问题
  3. 尽量不要生吐异常
  4. NPE异常。推荐方法的返回值可以是null,但是必须充分注释说明什么情况下会返回null值

八、Finally

需要关闭的连接资源等等,更加推荐使用try-with-resources语句,因为可以更好处理异常,另外需要了解的一点是,下面这种情况的finally是不会执行的。另外现在finally也不推荐使用了

try {// do somethingSystem.exit(1);
} finally {System.out.println("finally run");
}

final是在return表达式运行后执行的,此时要将return的结果暂存起来,待finally代码块执行完成后再返回缓存的结果

九、Maven管理多模块

尽量一早就开始使用maven管理多模块和依赖

    project
|-- pom.xml
|-- module-dao/
|   `-- pom.xml
|-- module-service/
|   `-- pom.xml
|-- module-scraper/
|   `-- pom.xml
`-- webapp/`-- pom.xml 

十、Assembly plugin自定义打包

<assembly xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/assembly-1.0.0.xsd"><id>package</id><formats><format>dir</format></formats><includeBaseDirectory>false</includeBaseDirectory><fileSets><fileSet><directory>src/main/bin</directory><outputDirectory>bin</outputDirectory></fileSet><fileSet><directory>src/main/config</directory><outputDirectory>resources</outputDirectory></fileSet><fileSet><directory>src/main/resources</directory><outputDirectory>resources</outputDirectory></fileSet></fileSets><dependencySets><dependencySet><outputDirectory>lib</outputDirectory><scope>runtime</scope></dependencySet></dependencySets>
</assembly>

十一、线程池

常见线程池:

  • new CachedThreadPool()-无线程可用时就创建新的
  • new FixedThreadPool()-任务数量超出后进入等待
  • new SingleThreadExecutor()-只有一个线程从而保证任务顺序执行
  • new SingleThreadScheduledExecutor() 定时或周期性工作调度

线程池使用注意事项:

  1. 避免任务堆积,newFixedThreadPool是创建指定数目线程的,但是其工作队列是无限的,如果任务处理不及时,会长时间占用资源,非常容易出现OOM
  2. 避免过度扩展线程。如果任务逻辑有任务,抛出异常但是线程池类没有捕获到它,那么线程就会退出,但是由于线程的GCROOT是Thread,所以是无法被GC回收的,当这种情况足够多时,线程池将没有可用的线程来处理任/务
  3. 避免在线程池中使用ThreadLocal。ThreadLocal在整个线程生命周期都有效,用来保存线程私有的信息,常用来传递事务、上下文等等信息。但是在线程池中线程是复用的,那么在使用ThreadLocal后再次调用get()返回的变量根本不是当前线程的设定的变量,就会出现脏数据,除非在线程处理结束时及时进行remove清理操作

十二、读写锁ReadWriteLock

1、只有写锁支持条件变量(比如队列是否为空是否已满),读锁调用newCondition()会抛出异常

2、先获取读锁再获取写锁是不允许的,但是获取到写锁后可以降级为读锁

锁升级是不允许的,如下代码会阻塞

// 读缓存
r.lock();
try {v = m.get(key); if (v == null) {w.lock();try {// 再次验证并更新缓存// 省略详细代码} finally{w.unlock();}}
} finally{r.unlock();
}

锁的降级是可以的

class CachedData {Object data;volatile boolean cacheValid;final ReadWriteLock rwl =new ReentrantReadWriteLock();// 读锁  final Lock r = rwl.readLock();// 写锁final Lock w = rwl.writeLock();void processCachedData() {// 获取读锁r.lock();if (!cacheValid) {// 释放读锁,因为允许读锁的升级r.unlock();// 获取写锁w.lock();try {// 再次检查状态  if (!cacheValid) {data = ...cacheValid = true;}// 释放写锁前,降级为读锁// 降级是可以的r.lock();} finally {// 释放写锁w.unlock(); }}// 此处仍然持有读锁try {use(data);} finally {r.unlock();}}
}

十三、覆写、重载、泛型

 Father father = new Son();//Son覆写了此方法father.doSomething();

覆写要注意两大一小原则,子类的访问权限只能相同或者变大,抛出异常和返回类型只能变小,方法名和参数必须完全相同,一般情况下我们都会使用override注解,以便编译器检测是否符合覆写条件

JVM在重载方法中,选择合适的目标方法的顺序如下:

  1. 精确匹配
  2. 如果是基本类型,自动转换为更大表示范围的基本类型
  3. 通过自动折箱和装箱
  4. 通过子类向上转型继承路线依次匹配(比如参数null)
  5. 通过可变参数匹配

泛型可以避免重复定义方法,也可以避免使用Object作为输入输出,带来强制转换的风险(ClassCastException)

十四、并发容器

List list = Collections.
synchronizedList(new ArrayList());
synchronized (list) {  //这行必须添加Iterator i = list.iterator(); while (i.hasNext()) {foo(i.next());}
}    

实际上Collections.synchronizedList类似封装如下

SafeArrayList<T>{List<T> c = new ArrayList<>();synchronizedT get(int idx){return c.get(idx);}synchronizedvoid add(int idx, T t) {c.add(idx, t);}synchronizedboolean addIfNotExist(T t){if(!c.contains(t)) {c.add(t);return true;}return false;}
}

上面的addIfNotExist其实包含了组合操作,每个操作是原子性的,但是组合操作往往是非原子性的。

CopyOnWriteArrayList就是在写的时候不对原集合进行修改,而是重新复制一份修改完后,再修改指针,所以会导致有短暂的数据不一致。另外它的迭代器是只读的,不支持增删改,因为遍历的只是快照。

十五、lost wake up问题

假如有两个线程,一个消费者线程,一个生产者线程。生产者线程的任务可以简化成将count加一,而后唤醒消费者;消费者则是将count减一,而后在减到0的时候陷入睡眠:

生产者伪代码:

count+1
notify()

消费者伪代码:

while(count<=0){wait()
}
count--

但是实际上可能消费者在检查count到调用wait()之间,count就可能被改掉了,导致消费者无法执行业务操作,这是很常见的一种竞态条件,所以需要把wait和notify的调用放在同步代码块中,否则会出现IllegalMonitorStateException问题

十六、Thread.sleep(0)的妙用

thread_fun() {prepare_word.....//没有sleep(0)的话,这里会一直浪费CPU时间做死循环的轮询,无用功while (1) {if (A is finish) {break;} else {//这里会交出B的时间片,下一次调度B的时候,接着执行这个循环sleep(0); }}process A's data
}

Thread.Sleep(0)的作用,就是“触发操作系统立刻重新进行一次CPU竞争”,竞争的结果也许是当前线程仍然获得CPU控制权。因为0的原因,线程直接回到就绪队列,而非进入等待队列,只要进入就绪队列,那么它就参与cpu竞争

十七、高效合理设计RPC接口

1、RPC接口异常显式返回

Exception应该也是返回值的一部分,应该设计成Checked Exception,尽量让调用方能够显式的处理

2、使用Specification规格模式解决查询接口过多的问题

设计者应该避免太多findBy方法和各自的重载,正确的打开方式应该类似组合模式

public interface StudentApi{Student findBySpec(StudentSpec spec);List<Student> findListBySpec(StudentListSpec spec);Page<Student> findPageBySpec(StudentPageSpec spec);
}

十八、String对象

1、直接使用双引号声明出来的String对象会直接存储在常量池中。

2、如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中

3、String s = new String(“abc”),创建了2个对象,第一个对象是”abc”字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象,然后指向运行时常量池中的”abc”

4、jdk6常量池在Perm区,jdk7以后在Java Heap区

5、Intern对”java”、”int…”基本类型、”void”关键字字符串都不适用

public class TestDemo {public static void main(String[] args ) {String str = new StringBuilder("计算机软件").append("软件").toString();String str1 = new StringBuilder("ja").append("va").toString();String str2 = new StringBuilder("jaa").append("va").toString();String str3 = new StringBuilder("cl").append("ass").toString();String str4 = new StringBuilder("Inte").append("ger").toString();String st5 = new StringBuilder("in").append("t").toString();System.out.println(str3=="jaava");//falseSystem.out.println(str.intern()==str);//trueSystem.out.println(str1.intern().equals(str1));//trueSystem.out.println(str1.intern()==str1);//falseSystem.out.println(str2.intern()==str2);//trueSystem.out.println(str3.intern()==str3);//trueSystem.out.println(str4.intern()==str4);//trueSystem.out.println(st5.intern()==st5);//false//        String str = "计算机"+"软件";
//        String str1 = "ja"+"va";
//        String str2 = "jaa"+"va";
//        String str3 = "cl"+"ass";
//        String str4 = "en"+"um";
//        String st5 = "in"+"t";
//        System.out.println(str.intern()==str);//true
//        System.out.println(str1.equals(str1.intern()));//true
//        System.out.println(str1.intern()==str1);//true
//        System.out.println(str2.intern()==str2);//true
//        System.out.println(str3.intern()==str3);//true
//        System.out.println(str4.intern()==str4);//true
//        System.out.println(st5.intern()==st5);//true}

十九、单例模式与垃圾回收

class Singleton {private byte[] a = new byte[6*1024*1024];private static Singleton singleton = new Singleton();private Singleton(){}public static Singleton getInstance(){return singleton;}
}class Obj {private byte[] a = new byte[3*1024*1024];
}public class Client{public static void main(String[] args) throws Exception{Singleton.getInstance();while(true){new Obj();}}
}

运行结果:

……
[Full GC 18566K->6278K(20352K), 0.0101066 secs]
[GC 18567K->18566K(20352K), 0.0001978 secs]
[Full GC 18566K->6278K(20352K), 0.0088229 secs]

hotspot虚拟机的垃圾收集算法使用根搜索算法,可以作为根的对象有:

  1. 虚拟机栈(栈桢中的本地变量表)中的引用的对象。
  2. 方法区中的类静态属性引用的对象。
  3. 方法区中的常量引用的对象。
  4. 本地方法栈中JNI的引用的对象。

方法区是jvm的一块内存区域,用来存放类相关的信息。很明显java中单例模式创建的对象被自己类中的静态属性所引用,符合第二条,因此单例对象不会被jvm垃圾收集

二十、Arrays#asList容易踩的坑

我们习惯使用Arrays#asList将Array转成ArrayList,但是其实Arrays#asList返回的其实是java.util.ArrayList,它是Arrays的定长集合类,它实现了set\get\contains方法,但是没有实现add\remove方法,调用它的add\remove方法实际上会调用父类AbstractList的方法,但是没有具体实现,仅仅抛出UnsupportedOperationException异常。同时java.util.ArrayList参数为可变长泛型,当调用其size方法时得到的将会是泛型对象个数,也就是一。另外asList得到的ArrayList其实是引用赋值,也就是说当外部数组或集合改变时,数组和集合会同步变化

码出高效JAVA代码相关推荐

  1. 码出高效:java开发手册_Java 11手册:最聪明的技巧来简化Java 11导航

    码出高效:java开发手册 Java 11:提示和技巧,日常陷阱及更多 为了庆祝Java 11的发布,我们邀请了八位Java专家与他们分享最新版本的最佳和最差体验. 由于本系列旨在作为Java 11的 ...

  2. 码出高效:java开发手册_Java 11手册:Java专家分享他们在Java 11方面的最佳和最差的经验

    码出高效:java开发手册 Java 10标志着Java生态系统新时代的开始,但最新版本证明仍有一些里程碑可言. Java 11是Oracle新的六个月周期中的第一个LTS版本. 您可以在此处下载Ja ...

  3. 《码出高效 Java开发手册》第八章 单元测试 (未整理)

    待整理 转载于:https://www.cnblogs.com/52liming/p/10686235.html

  4. 《码出高效:Java 开发手册》正式发布,83行代码计划启动

    可爱的Java开发者们,让你们久等了! 9月22日杭州云栖大会,众所期待的新书<码出高效:Java 开发手册>正式发布,并宣布将所有图书收益捐赠于技术公益项目. 本次新书发布,邀请了来自阿 ...

  5. 《码出高效:Java开发手册》背后的故事

    2018年12月22日,由博文视点组织的<码出高效:Java开发手册>作者见面会暨签售仪式在北京举行,InfoQ对书籍作者孤尽(杨冠宝).鸣莎(高海慧)进行了采访,了解了此书出版背后的一些 ...

  6. 让你久等了!《码出高效:Java 开发手册》正式发布

    可爱的Java开发者们,让你们久等了! 9月22日杭州云栖大会,众所期待的新书<码出高效:Java 开发手册>正式发布,并宣布将所有图书收益捐赠于公益项目. 此书从立意到付梓,历时超过两年 ...

  7. 阿里巴巴Java 开发手册 码出高效,码出质量 1.4.0

    前言 <阿里巴巴Java 开发手册>是阿里巴巴集团技术团队的集体智慧结晶和经验总结,经历了多次大规模一线实战的检验及不断完善,系统化地整理成册,回馈给广大开发者.现代软件行业的高速发展对开 ...

  8. 码出高效:Java开发手册笔记(线程池及其源码)

    码出高效:Java开发手册笔记(线程池及其源码) 码出高效:Java开发手册笔记(线程池及其源码) 码出高效:Java开发手册笔记(线程池及其源码) 前言 一.线程池的作用 线程的生命周期 二.线程池 ...

  9. 两位阿里大牛联合敬献,码出高效的Java学习笔记,你值得拥有

    写在前面 成长并没有直线式的捷径,"不走弯路就是捷径" 这个观点未必正确.弯路是成长的必经之路,我们在成长的路上需要注意的是保证弯路的前进大方向与直线的行进方向基本一致. 南辕北辙 ...

最新文章

  1. java sqlhelper_java版sqlhelper(转)
  2. 案例二十、自动化运维-代码上线
  3. 表单绑定 v-model —— :value @input || v-model原理
  4. element-ui踩坑
  5. c语言实现结构体变量private,C语言中结构体变量私有化详解
  6. windows - mysql
  7. Windos下用setx.exe命令行模式下永久设置系统环境变量
  8. 【填坑中】学生信息管理系统
  9. JavaEE编码规范
  10. python列表数据运算_Python基础(2)——数据类型:Numbers、列表、元组、字典、字符串、数据运算...
  11. webpack 4.0 配置方法以及错误解决
  12. 网络工程师的人生之路是这样的开始的!
  13. 18.2.28阿里前端实习生内推面补坑
  14. 安装qt qmake assistant 错误:could not find a Qt installation of ''
  15. Oracle数据脱敏
  16. java工程师考试要求_java初级工程师考试内容
  17. GT9xxxxx系列------如何加入电源管理模块
  18. 计算机教师所需技能,信息技术教师应具备哪些教学技能
  19. xp系统工作组计算机没有权限访问权限,XP系统工作组计算机没有法访问如何处理?...
  20. 什么是友情链接?友情链接的好处及写法(图文)

热门文章

  1. 银行项目外包专题系列之一:经常遇到的外包/驻场/实施/To B/POC这些概念
  2. DRE-SLAM论文笔记
  3. VS2015强制卸载
  4. 5g网络优化先培训是真的吗?
  5. 西子奥的斯电梯服务器使用教程方法_奥的斯电梯服务器TT使用说明
  6. 删除链表的结点——《剑指offer》
  7. 自己对win10虚拟内存的理解,不一定对
  8. Array.from和Array.of
  9. 关于python内置雅虎内置财经接口
  10. Android官方API文档完整版(分享)