6 枚举和注解
建议83 推荐使用枚举定义常量
枚举常量主要表现在以下四个方面
- 枚举常量更简单
- 枚举常量属于稳定态
- 枚举具有内置方法
- 枚举可以自定义方法
建议84 使用构造函数协助描述枚举项
一般来说,我们进程使用的枚举项只有一个属性,即排序号,其默认值是0,1,2 。但是除了排序号之外,枚举还有一个属性:枚举描述,它的含义是通过枚举的构造函数,声明每个枚举项必须具有的恶属性和行为,这是对枚举项的描述和补充,目的是使枚举项表述的意义更加清晰准确。
static enum Season {Spring("春"), Summer("夏"), Autumn("秋"), Winter("冬");private String desc;Season(String desc) {this.desc = desc;}public String getDesc() {return desc;}}
建议85 小心switch带来的空值异常
建议87 使用valueOf前必须进行校验
每个枚举都是java.lang.Enum的子类,都可以访问Enum类提供的方法,比如hashCode,name,valueOf。其中valueOf方法会把一个String类型的名称转变成枚举项,也就是在枚举项中查找出字面值与该参数相等的枚举项。
public static void main(String[] args) {//summer 是小写final List<String> params = Arrays.asList("Spring", "summer");for (String name : params) {final Season season = Season.valueOf(name);if (season != null) {System.out.println(season);} else {System.out.println("没有该枚举项");}}}
上述代码发生了IllegalArgumentException 异常
建议90 小心注解继承
一个不常用的元注解(Meta-Annotation):@Inherited,它表示一个注解是否可以自动被继承。
建议91 枚举和注解结合使用威力更大
注解的写法和接口很类似,都采用了关键字interface,而且都不能有实现代码,常量定义默认都是public static final类型的等,他们的而主要不同点是:注解要在interface前加上@字符,而且不能继承,不能实现。
interface Identifier {//无权访问时的礼貌语String REFUSE_WORD = "您无权访问";//鉴权boolean identify();}enum CommonIdentifer implements Identifier {//权限级别Reader, Author, Admin;@Overridepublic boolean identify() {return false;}}//注解标注在类上面,但是会保留到运行期@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface Access {//什么级别可以访问,默认是管理员CommonIdentifer level() default CommonIdentifer.Admin;}@Access(level = CommonIdentifer.Author)class Foo {}public class Client091 {public static void main(String[] args) {//初始化逻辑final Foo b = new Foo();//获取注解final Access access = b.getClass().getAnnotation(Access.class);if (access == null || !access.level().identify()) {//没有注解或者鉴权失败System.out.println(access.level().REFUSE_WORD);}}}
我们想使用Identifier接口的identify鉴权方法和REFUSE_WORD常量,但注解是不能继承的,那怎么办?此处,可通过枚举类型CommonIdentifier从中间做一个委派动作(Delegate),委派?没看到!你可以让identify返回一个对象,或者在Identifier上直接定义一个常量对象,那就是“赤裸裸”的委派了!
7 泛型和反射
建议93 Java的泛型是类型擦除的
8 异常
建议110 提倡异常封装
9 多线程和并发
建议118 不推荐覆写start方法
建议127 Lock与synchronized是不一样的
Lock类和synchronized关键字在代码块的并发性和内存上时语义是一样的,都是保持代码块同时只有一个线程具有执行权。
public class Client127 {public static void main(String[] args) throws Exception {runTasks(TaskWithLock.class);runTasks(TaskWithSync.class);}public static void runTasks(Class<? extends Runnable> clz) throws Exception {final ExecutorService es = Executors.newCachedThreadPool();System.out.println("***开始执行***" + clz.getSimpleName() + "任务***");//启动三个线程for (int i = 0; i < 3; i++) {es.submit(clz.newInstance());}//等待足够长的时间,关闭执行器TimeUnit.SECONDS.sleep(10);System.out.println("******" + clz.getSimpleName() + "任务执行完毕***\n");}}class Task {public void doSomething() {try {TimeUnit.SECONDS.sleep(2l);} catch (InterruptedException e) {e.printStackTrace();}StringBuffer sb = new StringBuffer();sb.append("线程名:" + Thread.currentThread().getName());sb.append(",执行时间:" + Calendar.getInstance().get(13) + " s");System.out.println(sb);}}//显示锁class TaskWithLock extends Task implements Runnable {private final Lock lock = new ReentrantLock();@Overridepublic void run() {lock.lock();try {doSomething();} finally {lock.unlock();}}}class TaskWithSync extends Task implements Runnable {@Overridepublic void run() {synchronized ("A") {doSomething();}}}
上述代码中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可以用如下的参数:
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左右,设置命令如下
-XX:NewSize=32m -XX:MaxNewSize=640M -XX:MaxPerMsise=1280m -XX:NewRatio=5
该配置指定新生代初始化为32MB(也就是新生代最小内存为32M),最大不超过640MB,养老区最大不超过1280M,新生代和养老区的比例为1:5
**
3 改变GC垃圾回收策略
我们不知道合适会发生垃圾回收,也不知道它执行有多长时间,但是我们可以想办法改变它对系统的影响,比如启用并行垃圾回收,规定并行回收的线程数量,命令格式如下
-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)更容易使用,效率更高,而且占用内存更少。
//不可变列表ImmutableList<String> list = ImmutableList.of("A", "B", "C");//不可变MAPImmutableMap<Integer, String> map = ImmutableMap.of(1, "map", 2, "age");//多值MapMultimap<String, String> phoneBook = ArrayListMultimap.create();phoneBook.put("张三","110");phoneBook.put("张三","119");
Table表
GIS(地理信息系统)
Table<Double, Double, String> g = HashBasedTable.create();//定义人民广场的经纬度坐标g.put(31.23, 121.48, "人民广场");//输出坐标点的建筑物g.get(31.23, 121.48);
建议141 Apache扩展包
Apache Commons通用扩展包基本上是每个项目都会使用的。
1 lang
字符串操作工具类
3 Collections
Collections工具包提供了ListUtils、MapUtils等基本集合操作工具。
双向Map
//双向Map//key value都不允许重复的MapBidiMap bidiMap = new TreeBidiMap();bidiMap.put("1", "A");bidiMap.put("2", "B");bidiMap.getKey("2");
