第1章 java开发中通用的方法和准则
1.字母“l”和数字1、字母“o”和数字0易混淆
应该避免这种弄易混淆的组合一起出现,如果必须出现,“l”务必大写,“L”;字母“o”或者“O”加注释。
public void suggest_1(){
long i = 1l;
System.out.println(i+i);//会打印出22吗?
String str1 = "hell0";
String str2 = "hello";
System.out.println(str1.equals(str2));
}
2
false
Process finished with exit code 0
3.三元操作符的类型务必一致
三元操作符是if-else的简化写法
public void suggest_3() {
Integer i = 80;
String s1 = String.valueOf(i < 100 ? 90 : 100);
String s2 = String.valueOf(i < 100 ? 90 : 100.0);
System.out.println(s1.equals(s2));
System.out.println(s1);
System.out.println(s2);
}
false
90
90.0
Process finished with exit code 0
7.警惕自增陷阱
自增指的是i++、++i.这两种写法都可以实现i自增
i++先赋值后++、++i先自增后赋值
掉进陷阱的代码
public void suggest_4() {
int count = 0;
for (int i=0;i<10;i++){
count = count ++;
}
System.out.println(count);//会打印出10?
}
0
Process finished with exit code 0
为什么会打印出0呢?因为count++是有返回值的,原理如下:
int temp = i;
i = i + 1;
return temp;
11.好习惯是显示声明UID
为什么要加UID?
持久化 - 持久化意味着对象的“生存时间”不依赖于程序是否在执行。
远程调用,其他机器上的对象好像在本地机器一样,就是使用了序列化技术持久化其他机器的对象。
UID是怎么计算出来的?
SerialVersionUID也叫流标识符,即类的版本定义,他可以显示声明也可以隐式声明。隐式声明时编译期在编译的时候帮我声明。生成的依据是包名、类名、继承关系、参数、返回值等等,极其复杂,计算出一个唯一值。
有什么用?
以远程调用为例。一个远程调用的参数bean在api中定义,consumer和provider引用不同的api版本,consumer端发送UID=1,provider在反序列化这个bean的时候发现和自己引用的bean的UID不相等,因此抛出一个InvalidClassException。
显示声明UID有什么好处?
可实现反序列化版本向上兼容。提高代码健壮性
以远程调用为例。api版本api_v=1.0,某天在bean中增加了一个属性age,版本升级为api_v=2.0。但是bean显式声明了UID,所以在provider反序列化是还是可以序列化成功的,只是反序列化出来没有age属性。
几个点
- 显示声明的UID应设置为private,避免继承而流放到子类。
- 数组无法显示声明序列化ID,java的序列化也不会比较数组。
12.Serializable类中的final成员变量的赋值
api中的bean包含一个final属性name=“andy”。
public class Person implements Serializable{
private static final long serialVersionUID = 91282334L;
public final String name="andy";
consumer单方面升级api改动name=“fiona”,那么provider反序列化这个bean的时候name反序列化成什么值?
答案是反序列化成fiona。
这个case,name在反序列化时被重新计算,但并不是所有的情况都会重新计算。
不重新计算的case1:通过构造方法赋值
public class Person implements Serializable{
private static final long serialVersionUID = 91282334L;
public final String name;
//构造函数为不变量赋值
public Person(){
name="andy";
}
}
provider执行反序列化的时候,根据类描述信息发现name是final类型,需要重新计算,然后找到Person类,发现name根本没有被赋值,所以JVM直接取网络传输过来的name值。
不重新计算的case2:通过方法赋值
public class Person implements Serializable{
private static final long serialVersionUID = 91282334L;
//通过方法返回值为final变量赋值
public final String name = initName();
//初始化方法名
public String initName(){
return "andy";
}
}
provider执行反序列化的时候,根据类描述信息发现name是final类型,需要重新计算,然后找到Person类,发现name通过方法赋值,所以JVM直接取网络传输过来的name值。
可能有人会问,为什么不执行initName()来重新计算?
答案是,当然不能执行initName(),因为initName()里边除了做name赋值可能还有其他事情要做。
反序列化原理:
序列化后的对象包含以下信息:
- 包括包路径、继承关系、访问权限、变量描述、变量访问权限、方法签名、返回值,以及变量的关联类信息。要注意的一点是,它并不是class文件的翻版,它不记录方法、构造函数、static变量等的具体实现
- 非瞬态(transient关键字)和非静态(static关键字)的实例变量值
注意,这里的值如果是一个基本类型,好说,就是一个简单值保存下来;如果是复杂对象,也简单,连该对象和关联类信息一起保存,并且持续递归下去(关联类也必须实现Serializable接口,否则会出现序列化异常),也就是说递归到最后,其实还是基本数据类型的保存。
总结一下,final不会被重新计算的case:
- 通过构造方法为final变量赋值
- 通过方法返回值为fianl变量赋值
- final修饰的属性不是基本类型
final会被重新计算的case:
- 8个基本类型,以及数组、字符串(字符串情况很复杂,不通过new关键字生成String对象的情况下,final变量的赋值与基本类型相同),但是不能方法赋值。
14.影响序列化和反序列化
在一些保密场景,我们不希望一些字段暴露出去。
可以用readObject(ObjectInputStream inputStream)、writeObject(ObjectOutputStream outputStream)来影响序列化和反序列化。
16.易变业务使用脚本语言编写
避免不断的发布java变更
脚本语言很强发
17.instanceof的使用
public void suggest_17() {
//System.out.println('A' instanceof Character);//编译异常,instanceof左右都不能是基本类型
System.out.println((String)null instanceof String);//null强转之后也还是null
System.out.println(new GenericClass<String>().isDateInstance(""));
}
class GenericClass<T>{
public boolean isDateInstance(T t){
return t instanceof Date;
}
}
false
false
Process finished with exit code 0
泛型是为编码服务的,在编译成字节码时,T已经是Object类型。
20.发布系统禁止使用文件替换的方式
public void suggest_20() {
System.out.println(Constant.MAX_AGE);
}
class Constant{
public final static int MAX_AGE = 150;
}
开发人员直接修改Constant.MAX_AGE=180替换线上Constant.class文件,然后重启服务器。但是发现suggest_20中输出的还是150,原因是
- fianl修饰基本类型和String时,编译器认为它是稳定态,所以在编译是把值写在了字节码中。避免运行时引用,提高效率。在上边的例子中,suggest_20中的Constant.MAX_AGE在编译是就被置换为150而不是引用。
- final修饰类,编译器认为是不稳定态,在编译期建立的是引用关系。
所以发布时,整体WAR包发布才是万全之策。
第2章 基本类型
21.用i%2 == 0判断奇偶
public void suggest_21() {
System.out.println("-1 是" + (-1 % 2 == 1? "奇数" :"偶数"));
}
-1 是偶数
Process finished with exit code 0
可见,用i%2 == 1判断奇偶是有问题的。%取余的原理:
public static int remainder(int dividend,int divisor) {
return dividend - dividend / divisor * divisor;
}
22.用整数处理货币
public void suggest_22() {
System.out.println(10.00 - 9.60);//会输出0.4吗?
}
0.40000000000000036
Process finished with exit code 0
原因是:在计算机中,浮点数可能是不准确的,它只能无限接近准确值。因为0.4无法用二进制准确表示。在二进制世界中,0.4是无限循环的小数,就像十进制中的1/3。
表示货币的方法:
- 使用BigDecimal
- 使用整型:吧参与运算的值扩大100倍,转为整型,展现时再缩小100倍。
23.不要让类型默认转换
public void suggest_23() {
int LIGHT_SPEED = 30 * 10000 * 1000;//光速
long dis = LIGHT_SPEED * 60 * 8;
System.out.println("太阳与地球的距离是:"+dis);
}
太阳与地球的距离是:-2028888064
Process finished with exit code 0
在程序第4行,已经考虑到会超过int表示范围,改用long,但是还是得到了错误的结果。这是因为这里是把计算后的值转成了long类型。
第4行可以这样写
long dis = LIGHT_SPEED * 60L * 8;
integer越界了为什么得到负值?因为越界了就会从头开始。
24.边界检查
场景:某个商品搞促销,每个人最多可以购买2000件。
public void suggest_24() {
int from_web = 2147483647;//本次购买的数量 Integer.MAX_VALUE = 2147483647
int from_db = 800;//已经购买的数量
int MAX_COUNT = 2000;//阈值
if(from_web + from_db <= MAX_COUNT){
System.out.println("购买成功");
}else {
System.out.println("购买失败");
}
}
购买成功
Process finished with exit code 0
前端传过来Integer.MAX_VALUE,Integer.MAX_VALUE+800超过Integer最大表示数其结果是负数,所以这里校验失败了。
Integer的取值范围是-2^31~ 2^31-1
public void integer_range() {
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);
System.out.println(Integer.MAX_VALUE + 1);
System.out.println(Integer.MAX_VALUE + 2);
}
2147483647
-2147483648
-2147483648
-2147483647
Process finished with exit code 0
25.不要让四舍五入亏了一方
java支持7中四舍五入模式。
public void suggest_25() {
BigDecimal d = new BigDecimal(1);
BigDecimal r1 = new BigDecimal(10.115);
BigDecimal r2 = new BigDecimal(10.125);
BigDecimal i1 = d.multiply(r1).setScale(2,BigDecimal.ROUND_HALF_EVEN);
System.out.println("3个月的利息是"+i1);
BigDecimal i2 = d.multiply(r2).setScale(2,BigDecimal.ROUND_HALF_EVEN);
System.out.println("3个月的利息是"+i2);
}
3个月的利息是10.12
3个月的利息是10.12
Process finished with exit code 0
七种舍入模式:
- ROUND_UP:原理零方向舍入
- ROUND_DOWN:趋向零方向舍入
- ROUND_CEILINE:正无穷方向舍入。math.round使用的模式。
- ROUND_FLOOR:负无穷方向舍入
- HALF_UP:经典四舍五入(5近)
- HALF_DOWN:5舍
- HALF_EVEN:银行家算法。四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇应进一。
29.优先使用基本类型
30.不要随便设置随机种子
public void suggest_30() {
Random r = new Random(100);//设置随即种子=100
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());
}
-1193959466
-1139614796
837415749
Process finished with exit code 0
设置随即种子后,每次运行都返回这三个值。原因在于:
- 种子不同产生不同的随机数
- 种子相同,产生相同的随即数
Random r = new Random(100);//种子固定,产生相同的随机数
Random r = new Random();//默认的构造方法,种子=System.nanoTime距离一个时间的纳秒数。产生不同的随机数。
第3章 类、对象及方法
33.不要复写静态方法
因为静态方法不能复写,只能隐藏。
静态方法的两种引用方式
- 类名访问。
- 实例访问。JVM通过实例的表面类型查找静态方法的入口。(不要用这种方式引用静态方法)
34.简化构造函数
构造函数应该简化再简化,应该达到“一眼洞穿”的境界
35.避免在构造函数中初始化其他类
这段代码会报异常,StackOverFlowError。因为初始化子类之前要先初始化父类,这里死循环了。
36.使用构造代码块精简程序
等价于
可见,构造代码块的执行时机是在构造方法的开始。
这样的case:如果一个构造方法A用this.b()调用构造方法B,代码块在构造方法B不执行。遇到this不执行代码块。
构造代码块使用场景:
- 每个构造方法都需要的初始化实例变量
- 初始化实例环境
38.使用静态内部类提高封装性
静态内部类和普通内部类有什么区别:
- 静态内部类不持有外部类的引用
- 静态内部类不依赖外部类
- 静态内部类可以声明static的方法和变量
39.使用匿名类的构造函数
public void suggest_39() {
List list = new ArrayList(){{}};
}
这是使用了匿名类,和空构造代码块,等价于
class SubList extends ArrayList{
{
}
}
list = new SubList();
现在,也能看懂下面这段代码了吧
public void suggest_39() {
List list = new ArrayList(){{}{}{}{}{}};
}
注意:有参数的匿名类先调用父类的同参构造再执行自己的构造代码块。这一点与普通继承是不一样的,普通继承是下调用父类的无参构造。
41.用内部类/匿名内部类实现多重继承
42.让工具类不可实例化
两个办法:
- 将构造方法声明为private。java反射修改访问权限就控制不住了。
- 在构造方法中抛出异常
43.避免对象浅拷贝
实现cloneable接口就具备了copy能力。
这就是浅copy。也是cloneable默认的拷贝方式,拷贝规则如下:
- 基本类型copy值
- 引用类型copy引用
- String类型copy引用,但是修改是会生成新的字符串。可认为是基本类型。
推荐使用序列化的方式实现对象的深拷贝:
使用Apache下的commons中的工具类SerializationUtils
45.复写equals是不要识别不出自己
46.复写equals要考虑null
47.复写equals用getClass作类型判断不要使用instanceof
第4章 字符串
52.直接量赋值String是在常量池中创建
52.replaceAll传递的第一个参数是正则表达式。
53.注意字符串的位置。
public void suggest_53() {
String str1 = 1 + 2 + "apple";
String str2 = "apple" + 1 + 2;
System.out.println(str1);
System.out.println(str2);
}
3apple
apple12
Process finished with exit code 0
54字符串拼接。
三种方式:+、concat、StringBuilder.
性能从高到底:StringBuilder、concat、+
Str = new StringBuilder(str).append("c").toString();
耗时在toString();
concat的实现原理:
还是在最后的new String()
57.推荐在复杂字符串中使用正则表达式
第5章 数组、集合
62警惕数组的浅拷贝。
Arrays.copyOf是shallow clone
64用treeSet实现去重后排序很不错。
65原始类型数组不能作为asList的输入参数。
public void suggest_65() {
int[] date = {1,2,3,4,5};
List list = Arrays.asList(date);
System.out.println("元素类型"+list.get(0).getClass());
System.out.println(date.equals(list.get(0)));
}
元素类型class [I
true
Process finished with exit code 0
我们期望通过List list = Arrays.asList(date);得到一个长度为5的List,得到的却是一个长度为1的list。并且第一个元素是一个数组。原因是Arrays.asList的原理:
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
因为asList接收多个可泛型化的参数,数组是对象是可泛型化的。上边的程序改为Integer数组就可以正常转换了,如下:
public void suggest_65() {
Integer[] date = {1,2,3,4,5};
List list = Arrays.asList(date);
System.out.println("元素类型"+list.get(0).getClass());
System.out.println(date.equals(list.get(0)));
}
元素类型class java.lang.Integer
false
Process finished with exit code 0
// An highlighted block
var foo = 'bar';
为什么元素类型class [I?
因为java没有Array的定义,Array是通过java.lang.reflect得到的,所以JVM无法输出Array的类型。
asList()还有一个需要注意的地方:asList()返回一个长度不可变的list。也就是说只有在只读场景才用的到asList()。
因为asList()返回的不是java.lang.ArrayList,而是java.util.Arrays.ArrayList。
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
}
元素数组是final类型看到没,而且,这个内部类也没没有实现add()、remove等修改元素个数的方法。
67不同的列表选择不同的遍历方法。
ArrayList 基于数组实现
LinkedList 基于链表实现
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
ArrayList实现了RandomAccess,元素之间无关联,可通过下标随即访问。
LinkedList 双向链表实现,元素之间有关联,不能通过下标快速访问。
list的访问方式
- 下标访问。for(int i=0;i<len;i++)
- Iterator访问。(forEach也是用的Iterator)。
Iterator做遍历时,元素之间需要有序,如果无需,则需要建立顺序。
. |
ArrayList |
LinkedList |
下标访问 |
推荐 |
|
Iterator |
|
推荐 |
LinkedList用下标访问为什么慢呢?
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
//下标小于中间值,从头开始搜索
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
//下标大于中间值 从尾开始搜索
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
看到了吧,因为要遍历查找index的值,所以慢。
69 集合的equals只关心元素是否equals。
public boolean equals(Object o) {
if (o == this)
return true;
//只要实现list接口即可
if (!(o instanceof List))
return false;
ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
while (e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
//只要一个元素不equals就退出
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
元素个数以及同索引上的元素equals才能说两个list equals。
public void suggest_69() {
ArrayList<String> arrayList1 = new ArrayList();
arrayList1.add("apple");
arrayList1.add("banana");
ArrayList<String> arrayList2 = new ArrayList();
arrayList2.add("banana");
arrayList2.add("apple");
System.out.println(arrayList1.equals(arrayList2));
ListIterator<String> e1 = arrayList1.listIterator();
while(e1.hasNext()){
String next = e1.next();
System.out.println(next);
}
ListIterator<String> e2 = arrayList2.listIterator();
while(e2.hasNext()){
String next = e2.next();
System.out.println(next);
}
}
false
apple
banana
banana
apple
Process finished with exit code 0
70 子列表只是原列表的一个视图。
public void suggest_70() {
ArrayList<String> arrayList1 = new ArrayList();
arrayList1.add("apple");
arrayList1.add("banana");
List<String> subList = arrayList1.subList(0, 1);
subList.add("orange");
System.out.println(subList.size());
System.out.println(arrayList1.size());
}
2
3
Process finished with exit code 0
看一下arrayList1.subList(0, 1)的源码
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
再看一下SubList
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
//操作子list
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
//操作list
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
public int size() {
checkForComodification();
return this.size;
}
//操作的原数组
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
//操作的原数组
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
arrayList1.subList的使用场景:
public void suggest_70() {
ArrayList<String> arrayList1 = new ArrayList();
arrayList1.add("apple");
arrayList1.add("java");
arrayList1.add("php");
arrayList1.add("banana");
List<String> subList = arrayList1.subList(1, 3);
subList.clear();
for (String item : arrayList1) {
System.out.println(item);
}
}
apple
banana
Process finished with exit code 0
arrayList1.subList调用后要设置原列表只读,否则会出问题
public void suggest_71() {
ArrayList<String> arrayList1 = new ArrayList();
arrayList1.add("apple");
arrayList1.add("banana");
List<String> subList = arrayList1.subList(1,2);
arrayList1.add("java");
arrayList1.add("php");
System.out.println(subList.size());
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)
at java.util.ArrayList$SubList.size(ArrayList.java:1040)
at EffectiveJava.suggest_71(EffectiveJava.java:45)
at EffectiveJava.main(EffectiveJava.java:31)
Process finished with exit code 1
subList.size()的第一件事实调用checkForComodification
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
modCount记录着原list的修改次数。这比较sublist创建时的modCount值和当前值是否相等,如果原list修改过,这里必然不等。
正确的姿势应该是
public void suggest_71() {
ArrayList<String> arrayList1 = new ArrayList();
arrayList1.add("apple");
arrayList1.add("banana");
List<String> subList = arrayList1.subList(1,2);
//将原list置为只读
List<String> unmodifiableList = Collections.unmodifiableList(arrayList1);
arrayList1.add("java");
arrayList1.add("php");
System.out.println(subList.size());
}
73.Comparable,Comparator和策略模式
实现Comparable 可以实现默认排序
实现Comparator可以实现实现多种排序
74.列表中检索的选择
public void suggest_73() {
ArrayList<Student> students = new ArrayList();
Student student1 = new Student(60,60);
Student student2 = new Student(90,30);
Student student3 = new Student(50,90);
Student student4 = new Student(80,10);
students.add(student1);
students.add(student2);
students.add(student3);
students.add(student4);
int i = students.indexOf(student3);//遍历查找。一般的场景,用这个足够。依据equals
Collections.sort(students);//用默认排序
int i1 = Collections.binarySearch(students, student3);//要先排序再查找。高性能要求的地方用这个。依据compareTo。实现compareTo就要复写equals
System.out.println(i);
System.out.println(i1);
}
重写compareTo一定要重新equals,重写equals又要冲洗hashCode
77.集合乱序shuffle
Collections.shuffle(students);
实现随即打乱列表顺序。
用途:
- 程序的伪装,比如游戏中群殴野怪的宝物分配策略
- 用在抽奖程序
- 用子安全传输
78.hashMap比ArrayList占内存
相同的元素个数hashMap比ArrayList占用内存更快,原因有两点:
- hashMap把元素封装成entry
- hashMap扩容是2倍扩容,List是1.5倍扩容
带有hash的集合查找速度更快。
第6章 枚举、注解
87.枚举用valueOf抛异常
传入的值不是枚举项的时候会抛出异常,解决办法有:
- 在枚举中定义一个contains()方法
- try-catch
87.枚举实现工厂模式
第7章 泛型、反射
99.泛型的多重界限
100.反射中选择getDeclaredxxx和getxxx
getDeclaredMethod:获得自身类的所有方法,不受访问权限限制
getMethod:获得public级别的方法,包括从父类继承来的
103.反射访问属性或者方法时将Accessible设置为true
在反射中,我们在invoke之前习惯先设置Accessible=true,这是为什么呢?
AccessibleObject是Field、Method、Constructor的父类,决定其是否可以快速访问而不进行访问权限检查。
public class AccessibleObject implements AnnotatedElement {
//定义反射的默认操作权限
static final private java.security.Permission ACCESS_PERMISSION =
new ReflectPermission("suppressAccessChecks");
//是否重置了安全检查 默认false
boolean override;
public boolean isAccessible() {
return override;
}
}
在大量反射情况下,设置Accessibl=true可以提高性能20倍以上。
104.使用forName动态加载类文件
动态加载的意义:
加载一个类表示要初始化该类的static变量,特别是static代码块,在这里我们可以做大量工作,比如注册自己、初始化环境等。
经典的加载数据库驱动程序
看Driver类的源码
需要说明的是,forName只是把一个类加载到内存,之后初始化static代码是由类的加载机制决定的。
104.forName无法动态加载数组
在java中数组是一个非常特殊的类,虽然它是一个类,但是没有定义类的路径,所以forName无法加载。
那么数组的动态加载怎么搞?java提供了数组工具Array可以通过反射创建数组。
107.动态代理实现装饰模式
第8章 异常
107.关于try、catch、finally
看一个经典题目:
public static int demo5() {
try {
return printX();
}
finally {
System.out.println("finally trumps return... sort of");
}
}
public static int printX() {
System.out.println("X");
return 0;
}
X
finally trumps return... sort of
0
参考:https://blog.csdn.net/u013309870/article/details/73498860
114.在构造函数中不要抛出异常
115.使用Throwable获取占信息
public void suggest_114() {
Throwable throwable = new Throwable();
StackTraceElement[] stackTrace = throwable.getStackTrace();
for (StackTraceElement st:stackTrace){
System.out.println(st.getClassName());
}
}
系统运行时异常或者new Throwable()时,jvm会生成Throwable对象,把当前的调用栈保存下来。因此我们可以new Throwable()来获得栈信息,比如调用者信息。
此方法常用作离线注册码校验,当破解者视图暴力破解时,由于主执行者不是期望的值,破解失败。
第9章 多线程和并发
118.不要复写start
看下start里边都干了啥
public synchronized void start() {
//判断线程状态、必须是未启动状态
if (threadStatus != 0)
throw new IllegalThreadStateException();
//加入线程组
group.add(this);
boolean started = false;
try {
start0();//分配线程内存、启动线程、运行run
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//本地方法,实现启动线程、申请栈内存、运行run
private native void start0();
121.使用常量优先级
线程由10个优先级1-10。优先级越高获得执行的机会越大。
- 优先级差别越大获得运行机会的差别越明显
- Thread类中有三个优先级常量,够我们平时使用了。
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
122.用Thread.UncaughtExceptionHandler提高线程健壮性
如果我们希望线程出现异常后自动重启,怎么实现呢?
class MultiThreadExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
e.printStackTrace();
new MultiThread().start();
}
}
但是也要注意线程是因为什么原因挂掉的,有些原因,暴力重启只会徒增系统负担。
123.volatile只能保证更新的原子性
volatile不能保证数据同步
两个线程都更新volatile count
可能两个线程同时读取到count=1,然后都执行count++
执行了两次count++我们期望count=3,单count=2
124.推荐使用callable
实现callable表示可调度。
用callable实现异步调用比runnable有点在于
127.lock和Synchronized
第10章 性能、效率
132.提升java性能的基本方法
//每次循环都要计算count*2
while(i < count*2){
//do somthing
}
应该替换为
int total = count*2;
while(i < total){
//do somthing
}
public String toChineseNum(int num){
//中文
String[] cns = {"壹","贰","叁","肆","伍"};
return cns[num+1];
}
每次调用该方法都要生成cns数组。其实这里将cns声明为final static更合适。
public void doSomething(){
//异常信息
String msg = "help me";
try{
Thread.sleep(10);
}catch (Exception e){
throw new MyException(e,msg);
}
}
这段代码汇中,msg在catch中创建才合适。
133.clone对象并不比new高效
clone也可以生成对象,但是性能并不比new好。因为80%的对象都是new出来的,所以java的缔造者对new做了大量优化工作,那么clone的使用场景是什么呢?
用于构造函数比较复杂,对象属性比较多,通过new创建对象比较耗时间的场景。
137.JVM调优
-
调整堆内存大小
栈空间:线程开辟、线程结束、JVM回收,一般不会对性能有大影响,不足时会报StackOverFlowError,可以通过java -Xss 设置大熊啊
堆空间:太小会频繁full GC甚至系统无法使用,太大会产生系统不稳定状况。
可通过java -Xmx1024m -Xms512m设置初始堆内存512m,最大堆内存1G
-
调整堆内存中个分区的比例
堆内存包括三部分:新生区、老年区、永久区。
新生区又包含三部分:伊甸区、幸存区1、幸存区2
一般新生区:老年区比例1:3左右
用以下命令设置
java -XX:NewSize=32m -XX:MaxNewSize=640m -XX:MaxPermSize=1280m -XX:NewRatio=5
该配置指定新生区最小内存32m,最大不超过640m,养老区最大不超过1280m,新生区和养老区比例1:5.
-
变更垃圾的回收策略
-
更换JVM
目前市面上比较流行的JVM有三个产品
java HotSpot VM
Oracle JRocle JVM
IBM JVM
第11章 开源世界
141.Apache扩展包
- Bag
是Collections的中一种,可以容纳重复元素,可统计相同元素的数量
Bag bag = new HashBag(Lists.newArrayList("red","red","red","blue"));
bag.getCount("red");
LazyMap 意思就是这个Map中的键/值对一开始并不存在,当被调用到时才创建。
我们这样来理解,我们需要一个Map,但是由于创建成员的方法很“重”(比如数据库访问)
我们只有在调用get()时才知道如何创建,或者Map中出现的可能性很多。
我们无法在get()之前添加所有可能出现的键/值对。
我们觉得没有必要去初始化一个Map而又希望它在必要时自动处理数据。
关于LazyMap的使用再说两点,一般需要使用LazyMap.decorate(Map,Factory)的方式来创建,实现Factory中的create方法。2章 思想为源