6 枚举和注解

建议83 推荐使用枚举定义常量

枚举常量主要表现在以下四个方面

  1. 枚举常量更简单
  2. 枚举常量属于稳定态
  3. 枚举具有内置方法
  4. 枚举可以自定义方法

建议84 使用构造函数协助描述枚举项

一般来说,我们进程使用的枚举项只有一个属性,即排序号,其默认值是0,1,2 。但是除了排序号之外,枚举还有一个属性:枚举描述,它的含义是通过枚举的构造函数,声明每个枚举项必须具有的恶属性和行为,这是对枚举项的描述和补充,目的是使枚举项表述的意义更加清晰准确。

  1. static enum Season {
  2. Spring("春"), Summer("夏"), Autumn("秋"), Winter("冬");
  3. private String desc;
  4. Season(String desc) {
  5. this.desc = desc;
  6. }
  7. public String getDesc() {
  8. return desc;
  9. }
  10. }

建议85 小心switch带来的空值异常

建议87 使用valueOf前必须进行校验

每个枚举都是java.lang.Enum的子类,都可以访问Enum类提供的方法,比如hashCode,name,valueOf。其中valueOf方法会把一个String类型的名称转变成枚举项,也就是在枚举项中查找出字面值与该参数相等的枚举项。

  1. public static void main(String[] args) {
  2. //summer 是小写
  3. final List<String> params = Arrays.asList("Spring", "summer");
  4. for (String name : params) {
  5. final Season season = Season.valueOf(name);
  6. if (season != null) {
  7. System.out.println(season);
  8. } else {
  9. System.out.println("没有该枚举项");
  10. }
  11. }
  12. }

上述代码发生了IllegalArgumentException 异常

建议90 小心注解继承

一个不常用的元注解(Meta-Annotation):@Inherited,它表示一个注解是否可以自动被继承。

建议91 枚举和注解结合使用威力更大

注解的写法和接口很类似,都采用了关键字interface,而且都不能有实现代码,常量定义默认都是public static final类型的等,他们的而主要不同点是:注解要在interface前加上@字符,而且不能继承,不能实现。

  1. interface Identifier {
  2. //无权访问时的礼貌语
  3. String REFUSE_WORD = "您无权访问";
  4. //鉴权
  5. boolean identify();
  6. }
  7. enum CommonIdentifer implements Identifier {
  8. //权限级别
  9. Reader, Author, Admin;
  10. @Override
  11. public boolean identify() {
  12. return false;
  13. }
  14. }
  15. //注解标注在类上面,但是会保留到运行期
  16. @Retention(RetentionPolicy.RUNTIME)
  17. @Target(ElementType.TYPE)
  18. @interface Access {
  19. //什么级别可以访问,默认是管理员
  20. CommonIdentifer level() default CommonIdentifer.Admin;
  21. }
  22. @Access(level = CommonIdentifer.Author)
  23. class Foo {
  24. }
  25. public class Client091 {
  26. public static void main(String[] args) {
  27. //初始化逻辑
  28. final Foo b = new Foo();
  29. //获取注解
  30. final Access access = b.getClass().getAnnotation(Access.class);
  31. if (access == null || !access.level().identify()) {
  32. //没有注解或者鉴权失败
  33. System.out.println(access.level().REFUSE_WORD);
  34. }
  35. }
  36. }

我们想使用Identifier接口的identify鉴权方法和REFUSE_WORD常量,但注解是不能继承的,那怎么办?此处,可通过枚举类型CommonIdentifier从中间做一个委派动作(Delegate),委派?没看到!你可以让identify返回一个对象,或者在Identifier上直接定义一个常量对象,那就是“赤裸裸”的委派了!

7 泛型和反射

建议93 Java的泛型是类型擦除的

8 异常

建议110 提倡异常封装

9 多线程和并发

建议118 不推荐覆写start方法

建议127 Lock与synchronized是不一样的

Lock类和synchronized关键字在代码块的并发性和内存上时语义是一样的,都是保持代码块同时只有一个线程具有执行权。

  1. public class Client127 {
  2. public static void main(String[] args) throws Exception {
  3. runTasks(TaskWithLock.class);
  4. runTasks(TaskWithSync.class);
  5. }
  6. public static void runTasks(Class<? extends Runnable> clz) throws Exception {
  7. final ExecutorService es = Executors.newCachedThreadPool();
  8. System.out.println("***开始执行***" + clz.getSimpleName() + "任务***");
  9. //启动三个线程
  10. for (int i = 0; i < 3; i++) {
  11. es.submit(clz.newInstance());
  12. }
  13. //等待足够长的时间,关闭执行器
  14. TimeUnit.SECONDS.sleep(10);
  15. System.out.println("******" + clz.getSimpleName() + "任务执行完毕***\n");
  16. }
  17. }
  18. class Task {
  19. public void doSomething() {
  20. try {
  21. TimeUnit.SECONDS.sleep(2l);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. StringBuffer sb = new StringBuffer();
  26. sb.append("线程名:" + Thread.currentThread().getName());
  27. sb.append(",执行时间:" + Calendar.getInstance().get(13) + " s");
  28. System.out.println(sb);
  29. }
  30. }
  31. //显示锁
  32. class TaskWithLock extends Task implements Runnable {
  33. private final Lock lock = new ReentrantLock();
  34. @Override
  35. public void run() {
  36. lock.lock();
  37. try {
  38. doSomething();
  39. } finally {
  40. lock.unlock();
  41. }
  42. }
  43. }
  44. class TaskWithSync extends Task implements Runnable {
  45. @Override
  46. public void run() {
  47. synchronized ("A") {
  48. doSomething();
  49. }
  50. }
  51. }

上述代码中Lock锁不出现互斥?
这是因为对于同步资源来说,显示锁是对象级别的锁,而内部锁是类级别的锁,也就是说Lock锁是跟随对象的。synchronized锁是跟随类的。更简单地说把Lock定义为所有线程共享变量。

建议128 预防线程死锁

10 性能和效率

建议132 提升Java性能的基本方法

建议137 调整JVM参数以提升性能

1 调整堆内存大小

JVM中有两种内存内存:栈内存(Stack)和堆内存(Heap),栈内存的特点是空间比较小,速度快,用来存放对象的一弄以及程序中的基本类型;而堆内存的特点是空间比较大,速度慢,一般对象都会在这里生成、使用和消亡。
栈空间是由线程开辟,线程结束,栈空间由JVM回收,因此它的大小一般不会对性能有太大的影响,但是它会影响系统的稳定性,在超过栈内存的空间时,系统会报StackOverflowError错误,可以通过“java-Xss ”设置栈内存的大小来解决此类问题。
堆内存的调整不能太随意,调整得太小,会导致Full GC频繁执行,轻则导致系统性能急剧下降,重则导致系统根本无法使用;调整得太大,一则是浪费资源,二则是产生系统不稳定的情况,例如在32位机器上设置超过1.8GB的内存就有可能会产生莫名其妙的错误。设置初始化堆内存为1GB(也就是最小堆内存),最大堆内存为1.5G可以用如下的参数:

  1. java -Xmx1536m-Xms1024m

2 调整堆内存中各分区的比例

JVM的堆内存包括三部分:新生区(Young Generation Space)、养老区(Tenure generation space)、永久存储区(Permanent Space)。其中新生成的对象都在新生区,它又分为伊甸区(Eden Space)、幸存0区(Survivor 0 space)和幸存1区(Survivor 1 space),当在程序中使用new 关键字时,首先在伊甸区生成该对象,如果伊甸区满了,则用垃圾收集器先进行回收,然后把剩余的对象移动到幸存区(0区或1区),如果幸存区也满了,垃圾回收器再回收一次,然后把剩余的对象移动到养老区,养老区也满了,此时会触发Full GC(则是一个非常危险的动作,JVM会停止所有的执行,所有系统资源都会让位给垃圾回收器),会对所有的对象过滤一遍。检查是否有可回收的对象,如果还是没有的话,就抛出OutOfMemoryError。
一般情况下新生区和养老区的比例为1:3左右,设置命令如下

  1. -XX:NewSize=32m -XX:MaxNewSize=640M -XX:MaxPerMsise=1280m -XX:NewRatio=5

该配置指定新生代初始化为32MB(也就是新生代最小内存为32M),最大不超过640MB,养老区最大不超过1280M,新生代和养老区的比例为1:5

**

3 改变GC垃圾回收策略

我们不知道合适会发生垃圾回收,也不知道它执行有多长时间,但是我们可以想办法改变它对系统的影响,比如启用并行垃圾回收,规定并行回收的线程数量,命令格式如下

  1. -XX:+UseParallelGC -XX:ParallelGCThread=20

这里启用了并行垃圾收集器,并且定义了20个收集线程(默认收集线程等于CPU的数量),这对多CPU的系统是非常有帮助的。

11 开源世界

建议140 推荐使用Guava扩展工具包

1 Collections

com.google.commons.collect主要包括四部分:不可变集合、多值Map,Table表和集合工具类。

不可变集合

不可变集合包括:ImmutableList、ImmutableMap,ImmutableSet、ImmutableSortedMap、ImmutableSortedSet等,它比不可修改集合(Unmodifiable Collections)更容易使用,效率更高,而且占用内存更少。

  1. //不可变列表
  2. ImmutableList<String> list = ImmutableList.of("A", "B", "C");
  3. //不可变MAP
  4. ImmutableMap<Integer, String> map = ImmutableMap.of(1, "map", 2, "age");
  5. //多值Map
  6. Multimap<String, String> phoneBook = ArrayListMultimap.create();
  7. phoneBook.put("张三","110");
  8. phoneBook.put("张三","119");

Table表

GIS(地理信息系统)

  1. Table<Double, Double, String> g = HashBasedTable.create();
  2. //定义人民广场的经纬度坐标
  3. g.put(31.23, 121.48, "人民广场");
  4. //输出坐标点的建筑物
  5. g.get(31.23, 121.48);

建议141 Apache扩展包

Apache Commons通用扩展包基本上是每个项目都会使用的。

1 lang

字符串操作工具类

  • StringUtils 基本的String操作类
  • StringEscapeUtils(String 转义工具)
  • RandomStringUtisl 随机字符串工具

    2 BeanUtils

3 Collections

Collections工具包提供了ListUtils、MapUtils等基本集合操作工具。

双向Map

  1. //双向Map
  2. //key value都不允许重复的Map
  3. BidiMap bidiMap = new TreeBidiMap();
  4. bidiMap.put("1", "A");
  5. bidiMap.put("2", "B");
  6. bidiMap.getKey("2");