摘抄《编写高质量代码——改善JAVA程序的151个建议》,记录一下实用的一些知识点。
1、不要在常量和变量中出现易混淆的字母
包名全小写,类名首字母全大写,常量全部大写并用下划线分割,变量采用驼峰明明法。字母i务必大写。
2、静态常量用接口实现
好处是不会被new
interface Const{
public static final int CONST = 123;
}
3、静态导入
静态导入的目的是减少字符输入量,提高代码的可阅读性
如下引入PI,后面即可直接使用PI
import static java.lang.Math.PI;
class MathUtils{
public static double calCircleArea(double r){
return PI * r * r;
}
public static double calBallArea(double r){
return 4 * PI * r * r;
}
}
静态导入原则:
不适用*(星号)通配符,除非是导入静态常量类(只包含常量的类或接口)
方法名是具有明确、清晰表象意义的工具类。
import static org.junit.Assert.*;
class DaoTest{
@Test
public void testInsert(){
//断言
assertEquals("foo", "foo");
assertFalse(Boolean.FALSE);
}
}
我们从程序中很容易判断出assertEquals方法是用来断言两个值是否相等的,assertFalse方法则是断言表达式为假,如此确实减少了代码量,而且代码的可读性也提高了,这也是静态导入用到正确地方所带来的好处。
4、养成良好习惯,显式声明UID
若没有序列化,现在我们熟悉的远程调用、对象数据库都不能存在。
实现了Serializable接口,可以在网络上传输,也可以本地存储然后读取。
通过对象序列化过程,把一个对象从内存块转化为可传输的数据流,然后通过网络发送到消息消费者那里,并进行反序列化,生成实例对象。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Person implements Serializable{
private static final long serialVersionUID = -6249326059038913066L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Producer {
public static void main(String[] args) {
Person person = new Person();
person.setName("Test");
// 序列化,保存到磁盘上
SerializationUtils.writeObject(person);
}
}
class SerializationUtils{
private static String FILE_NAME = "C:/obj.bin";
// 序列化
public static void writeObject(Serializable s){
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
oos.writeObject(s);
oos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object readObject(){
Object obj = null;
// 反序列化
try {
ObjectInputStream input = new ObjectInputStream(new FileInputStream(FILE_NAME));
obj = input.readObject();
input.close();
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
class Constumer{
public static void main(String[] args) {
// 反序列化
Person p = (Person) SerializationUtils.readObject();
System.out.println("name=" + p.getName());
}
}
在这种序列化和反序列化的类不一致的情况下,反序列化时会报一个InvalidClassException异常,原因是序列化和反序列化所对应的类版本发生了变化,JVM不能把数据流转换为实例对象。JVM是通过SerialVersionUID来判断一个类版本,也叫做流标识符(Stream Unique Identifier)即为类的版本定义。
5、序列化时部分属性持久化问题
class Salary implements Serializable{
private static final long serialVersionUID = -7794166474998632540L;
private int basePay;
/**
* 第一种方法:在donus前加上
transient关键字
* 这是一个方法,但不是一个好方法,加上transient关键字就标志着Salary类失去了分布式部署的功能,
* 它是HR系统最核心的类了,一旦遇到性能瓶颈,想再实现分布式部署就不可能了,此方案否定。
*/
//
private transient int bonus;
private int bonus;
public Salary(int basePay, int bonus) {
super();
this.basePay = basePay;
this.bonus = bonus;
}
public int getBasePay() {
return basePay;
}
public void setBasePay(int basePay) {
this.basePay = basePay;
}
public int getBonus() {
return bonus;
}
public void setBonus(int bonus) {
this.bonus = bonus;
}
}
class Person implements Serializable{
private static final long serialVersionUID = -6249326059038913066L;
private String name;
// 禁止序列化
private transient Salary salary;
public Person(String name, Salary salary) {
super();
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Salary getSalary() {
return salary;
}
public void setSalary(Salary salary) {
this.salary = salary;
}
/**
* 第二种方法:实现了Serializable接口的类可以实现l两个私有方法,
* writeObject和readObject,以影响和控制序列化和反序列化的过程
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
// 清除绩效
salary.setBonus(0);
out.writeObject(salary);
}
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
in.defaultReadObject();
salary = (Salary)in.readObject();
}
}
测试代码
public static void main(String[] args) {
Salary salary = new Salary(1000, 2500);
Person person = new Person("张三", salary);
// 序列化,保存到磁盘上
SerializationUtils.writeObject(person);
Person p = (Person)SerializationUtils.readObject();
StringBuffer sb = new StringBuffer();
sb.append("行么:" + p.getName());
sb.append("\t 基本工资:" + p.getSalary().getBasePay());
sb.append("\t 绩效工资:" + p.getSalary().getBonus());
System.out.println(sb);
}
6、易变业务使用脚本语言编写
脚本语言:运行期解释执行的代码。比如Groovy,Java程序员若转到Groovy程序语言上,只需要两个小时,看完语法说明,看完Demo即可使用了。Java6开始正式支持脚本语言,默认支持JavaScript。
下面一个示例
function formula(var1,var2){
return var1 + var2 * factor;
}
这是一个简单的脚本语言,factor(因子)这个变量是一个运行的环境变量,从上下文来。该JavaScript保存在C:/model.js中。下一步Java需要调用JavaScript公式,代码如下:
public static void main(String[] args) throws Exception {
// 获取一个JavaScript的执行引擎
ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
// 建立上下文变量
Bindings bind = engine.createBindings();
bind.put("factor", 1);
// 绑定上下文,作用于是当前引擎范围。
engine.setBindings(bind, ScriptContext.ENGINE_SCOPE);
Scanner input = new Scanner(System.in);
while(input.hasNext()) {
int first = input.nextInt();
int sec = input.nextInt();
System.out.println("输入参数是:" + first + "," + sec);
// 执行js代码
engine.eval(new FileReader("C:/model.js"));
// 是否可调用方法
if(engine instanceof Invocable){
Invocable in = (Invocable)engine;
// 执行js中的函数
Double result = (Double)in.invokeFunction("furmula", first, sec);
System.out.println("运算结果:" + result.intValue());
}
}
}
上面示例我们可以修改脚本函数,JVM不需要重启,即可产生不同的结果。这就是脚本语言对系统设计最有利的地方:
可以随时发布而不用重新部署。
7、用整数类型处理货币
public static void main(String[] args) throws Exception {
System.out.println(10.00 - 9.60);
}
}
我们期望的结果是0.4,但是打印出来的却是0.40000000000000036。
这是为什么呢?这是由浮点数的存储规则所决定的,我们先来看0.4这个十进制小数如何转换成小数,使用“乘2取整,顺序排列”法,在二进制数世界里它是一个无限循环的小数。
要解决此问题有两种方法:
1、使用BigDecimal
BigDecimal是专门为弥补浮点数无法精确计算的缺憾而设计的类,并且它本身也提供了加减乘除的常用数学算法。特别是与数据库Decimal类型的字段映射时,BigDecimal是最优的解决方方案。
2、使用整型
把参与运算的值扩大100倍,并转变为整型,然后在展现时再缩小100倍,
这样处理的好处是计算简单、准确,一般在非金融行业(零售行业)应用较多。
8、不要让四舍五入亏了一方
四舍。舍弃的数值:0.000、0.001、0.002、0.003、0.004,因为是舍弃的,对银行家来说,就不用付款给储户了。那每舍弃一个数字就会赚取相应的金额:0.000、0.001、0.002、0.003、0.004。
五入。进位的数值:0.005、0.006、0.007、0.008、0.009,因为是进位,对银行家来说,每进一位就会多付款给储户,也就是亏损了,那亏损部分就是其对应的10进制补数:0.005、0.004、0.003、0.002、0.001
0.000+0.001+0.002+0.003+0.004-0.005-0.004-0.003-0.002-0.001=0.005
也就是说,每10笔的利息计算中就损失0.005元,即每笔利息计算损失0.0005元
这个算法误差由美国银行家发现,并提出了一个修正算法,叫做
银行家舍入。其规则如下;
舍去位的数值小于5时,直接舍去。
舍去位的数值大于等于6时,进位后舍去。
当舍去位的数值等于5时,分两种情况,5后面还有其他数字(非0),则进位后舍去;若5后面是0(即5是最后一个数字),则根据5前一位数的奇偶性来判断是否需要进位,奇数进位,偶数舍去。
以上规则汇总成一句话:四舍五入考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。
Java5后引入了银行家的舍入法则。直接使用RoudingMode.HALF_EVEN
BigDecimal d1 = new BigDecimal(10.846465416854);
d1.setScale(2, RoundingMode.HALF_EVEN);
9、不要覆写静态方法
public class Client {
public static void main(String[] args) throws Exception {
Base base = new Sub();
base.doAnything(); // 我是子类非静态方法
base.doSomething(); // 我是父类静态方法
}
}
class Base{
public void doAnything(){
System.out.println("我是父类非静态方法");
}
public static void doSomething(){
System.out.println("我是父类静态方法");
}
}
class Sub extends Base{
public void doAnything(){
System.out.println("我是子类非静态方法");
}
public static void doSomething(){
System.out.println("我是子类静态方法");
}
}
实例对象有两个类型:表面类型和实际类型,表面类型是声明时的类型,实际类型是对象产生时的类型。对于非静态方法,它是根据对象的实际类型执行的,对于静态方法是通过类名访问的。
10、构造代码块
1、构造代码块会在每个构造函数内首先执行(需要注意的是:构造代码块不是在构造函数之前运行的,它依托于构造函数的执行)
2、编译器会把构造代码块插入到没一个构造函数中,如果遇到this关键字(也就是构造函数调用自身其他的构造函数时)则不插入构造代码块。(放心使用构造代码块把,Java已经想你所想了)
public class Client {
public static void main(String[] args) throws Exception {
new Base();
new Base("");
new Base(0);
System.out.println("实例对象数量:" + Base.getNumOfObjects());
}
}
class Base{
private static int numOfObjects = 0;
{
System.out.println("构造代码块");
numOfObjects++;
}
public Base(){
System.out.println("无参构造函数");
}
public Base(String _str){
this();
}
public Base(int _i){
}
public static int getNumOfObjects(){
return numOfObjects;
}
}
11、使用匿名类的构造函数
public static void main(String[] args) throws Exception {
List l1 = new ArrayList();
List l2 = new ArrayList(){};// 匿名内部类
List l3 = new ArrayList(){ {}};// 匿名内部类+构造代码块
}
12、对象的浅拷贝
我们知道一个类实现了Cloneable接口就表达式它具备了被拷贝的能力,如果再覆写clone()方法就会完全具备拷贝能力。Object.clone()方法是一种浅拷贝,它的拷贝规则如下:
1、基本类型:拷贝其值
2、对象:拷贝地址引用
3、String字符串:拷贝地址,但String在修改时,它会从字符串池(String Pool)中重新生成新的字符串,原有的字符串对象保持不变,在此处我们可认为String是一个基本类型。
备注:若非必要,不要克隆对象。Java缔造者们已经对new进行了充分的性能优化,一般情况下new生成的对象比clone生成的性能要好很多。
13、推荐使用序列化实现对象的拷贝
如果一个项目中有大量的对象通过拷贝生成的,那我们该如何处理?
每个类都写一个clone方法,并且还要深拷贝?想想看这是何等巨大的工作量呀,是否有更好的呢?
其实,可以通过序列化方式来处理,在内存中通过字节流的拷贝来实现,也就是把母对象写到一个字节流中,再从字节流中将其读出来,这样就可以重建一个新对象了,该新对象与母对象之间不存在引用共享的问题,也就相当于深拷贝了一个新对象。
序列化可以使用Apache下的Commons工具包的SerializationUtils类。
14、在equals中使用getClass进行类型判断
Instanceof关键字是用来判断是否一个类的实例对象的,这很容易让子类“钻空子”。使用getClass来代替instanceof进行类型判断。
·public class Client {
public static void main(String[] args) throws Exception {
Employee e1 = new Employee(100, " 张三 ");
Employee e2 = new Employee(101, " 张三 ");
Person p1 = new Person(" 张三 ");
System.out.println(p1.equals(e1));// true
System.out.println(p1.equals(e2));// true
System.out.println(e1.equals(e2));// false
}
}
class Person{
private String name;
public Person() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(String name) {
super();
this.name = name;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Person){
Person p = (Person) obj;
return name.equalsIgnoreCase(p.getName());
}
return super.equals(obj);
}
}
class Employee extends Person{
private int id;
public Employee(int id, String name) {
super(name);
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Employee){
Employee e = (Employee) obj;
return super.equals(obj) && e.getId() == id;
}
return false;
}
}
15、使用package-info类为包服务
http://strong-life-126-com.iteye.com/blog/806246
16、String类intern方法
Intern会检查当前的对象在对象吃中是否有字面值相同的引用对象,如果有则返回池中对象,如果没有则放置到
对象池中,并返回当前对象。
17、正确使用String、StringBuffer、StringBuilder
String类:引用地址在变,值在变。
每次改变值都会重新生成一个对象。这就是为什么replace、concat方法返回的结果需要重新赋给原来的变量。
使用场景:在字符串不经常变化的场景中可以使用String类,例如常量的声明、少量的变量运算等。
StringBuffer类:引用地址不变,值在改变。
使用场景:在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程的环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装等。
StringBuilder类的场景,在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程的环境中,则可以考虑使用StringBuilder,例如SQL语句的拼装、JSON封装等。
18、自由选择字符串拼接方法
String str = "c";
long start = System.currentTimeMillis();
for(int i=0; i<50000; i++){
//
str += "c"; //执行时间946
str = str.concat("c"); // 475
}
System.out.println(System.currentTimeMillis() - start);
(1) ”+”方法拼接字符串(1)
例如中的“+”拼接的代码如下代码相同:
str = new StringBuffer(str).append("c").toString();
耗时原因:一是每次循环都会创建一个StringBuilder对象,二是每次执行完毕都要调用toString方法将其转换为字符串。
(2) concat方法拼接字符串
源码代码如下:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
其整体看上去就是一个数组拷贝,虽然在内存中的处理都是原子性操作,速度非常快。耗时原因是每次concat操作都会新创建一个String对象。
(3)append方法拼接字符串
StringBuilder sb = new StringBuilder("c");
long start = System.currentTimeMillis();
for(int i=0; i<50000; i++){
sb.append("c");//执行时间2
}
String str = sb.toString();
System.out.println(System.currentTimeMillis() - start);
StringBuffer的append方法直接由父类AbstractStringBuilder实现,看源码可知,整个append方法都在做字符串数组操作,加长,然后数组拷贝,这些都是基本的数据处理,没有新建任何对象,所以速度也就最快了。注意:例如中是在
最后通过StringBuilder的toString返回了一个字符串,也就是说在5万次循环结束后才生成了一个String对象。
19、对字符串排序持一种宽容的心态
对中文进行排序,字符排序默认按编码值
String[] strs = {"张三", "李四", "王五"};
Collator c = Collator.getInstance(Locale.CHINA);
Arrays.sort(strs, c);
int i=0;
for(String str : strs){
System.out.println((++i) + "、" +str);
}
如股票排序对象是经常使用的汉字,使用Collator类排序完全可以满足我们的需求,毕竟GB2312已经包含了大部分的汉字,如果需要严格排序,则要使用一些开源项目来自己实现了,比如
pinyin4j。
20、性能考虑,数组是首选
在Java中数组没有List、Set、Map这些集合类用起来方便,但是在基本类型处理方面,数组还是占优势的,而且集合类的底层也都是通过数组实现的。
众所周知:基本类型是在栈内存中操作的,而对象则是在堆内存中操作的;
栈内存的特点是速度快、容量小,堆内存的特点是速度慢,容量大(从性能上来讲,基本类型的处理占优势)
21、若有必要,使用变长数组
public void test(){
String[] datas = new String[5];
datas = expandCapacity(datas, 10);
System.out.println(datas.length);
}
public static
T[] expandCapacity(T[] datas, int newLen){
newLen = newLen<0?0:newLen;
return Arrays.copyOf(datas, newLen);
}
在实际开发中,如果确实需要变长的数据集,数组也是考虑范围之内,不能因固定长度而将其否定之。
22、警惕数组的浅拷贝
Arrays.copyOf方法产生的数组是一个浅拷贝,这与序列化的浅拷贝完全相同;基本类型是直接拷贝值,其他都是拷贝引用地址。数组、集合的clone方法也是与此相同。
23、在明确的场景下,为集合指定初始容量
如果不设置初始容量,系统就按照1.5倍的规则扩容,每次扩容都是一次数组的拷贝,如果数据量大,这样的拷贝会非常耗费资源,而且效率非常低下。设置初始容量,我们可以按照明确数量的1.5倍设置(
在大数据量下,是否制定容量会使性能相差5倍以上)。
24、asList方法产生的List对象不可更改
返回的是Arrays的内部类ArrayList,该内部类没有实现add方法
25、频繁插入和删除时使用LinkedList
26、子列表只是原列表的一个视图
通过阅读源码,可以看到
List的实现原理:它返回的SubList类也是AbstractList的子类,其所有的方法如get、set、add、remove等都是在原始列表上的操作,它自身并没有生成一个数组或是链表,也就是子列表只是原列表的一个视图,所有的修改动作都反映在了原列表上。子列表所有操作都会检查原来的大小是否改变,因此生成列表后再操作原列表会抛并发修改异常。
新技能:设置列表为只读
List list = new ArrayList<>();
list = Collections.unmodifiableList(list);
新技能:apache-commons各种builder
@Override
public int compareTo(Employee o) {
return new CompareToBuilder().append(position, o.position)
.append(id, o.id).toComparison();
}
@Override
public boolean equals(Object obj) {
return new EqualsBuilder().append(code, city.code)
.isEquals();
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
27、集合相关操作
并集:lisit1.addAll(list2)
交集:lisit1.retainAll(list2)
差集:list1.removeAll(list2)
无重复的并集:list2.removeAll(list1);list1.addAll(list2);
28、使用shuffle打乱列表
Collections.shuffle(list);
使用场景:
可以用在程序的“伪装”上
比如我们例子中的标签云,或者是游戏中的打怪、修行、群殴时宝物的分配策略。
可以用在抽奖程序中
比如年会的抽奖程序中,先使用shuffle把员工排序打乱,每个员工的中奖几率就是相等的,然后就可以抽取第一名、第二名。
可以用在安全传输方面
比如发送端发送一组数据,先随机打乱顺序,然后加密发送,接收端解密,然后自行排序,即可实现是相同的数据源,也会产生不同密文的效果,加强了数据的安全性。
29、HashMap存储结构
先计算key的hash码,如果要hash码相同则通过链表存储

30、队列
阻塞队列:ArrayBlockingQueue、PriorityBlockingQueue、LinkedBlockingDeque,
这里说明一下PriorityBlockingQueue可以指定排序比较器。
31、Java泛型擦拭
List、List、List擦拭后的类型为List
List[]擦拭后的类型为List[]
List Extends E>、List Super E> 擦拭后的类型为List
泛型不支持协变
Number[] n = new Integer[10]; // 数组支持协变
List ln = new ArrayList(); // 编译不通过,泛型不支持协变
严格限定泛型类型采用多重界限
Class Me implements Staff,Passenger{}
public static void discount(T t){}
使用“&”符号设定多重边界,指定泛型类型T必须是Staff和Passenger的共用子类型。
新技能:动态加载数组
String[] strs = (String[])Array.newInstance(String.class, 8);
int[][] ints = (int[][])Array.newInstance(int.class, 2, 3);
32、反射让模板方法模式更强大
abstract class AbsPopulator{
private boolean isInitDataMethod(Method method) {
return method.getName().startsWith("init") // init开始
&& Modifier.isPublic(method.getModifiers()) // 公开方法
&& method.getReturnType().equals(Void.TYPE) // 返回值是void
&& !method.isVarArgs() // 输入参数为空
&& !Modifier.isAbstract(method.getModifiers()); // 不能是抽象方法
}
}
class UserPopulator extends AbsPopulator{
public void initUser(){
/*初始化用户,如创建表,加载数据等*/
}
public void initPassword(){
/*初始化密码*/
}
public void initJobs(){
/*初始化工作任务*/
}
}
如果读者熟悉Junit的话,就会看出此处的实现与JUnit非常类似,JUnit之前要求测试的方法必须是test开头的,并且无返回值、无参数,而且有public修饰,其实现的原理与此非常相似,读者有兴趣可以看看JUnit的源代码。
备注:反射的效率相对于正常的代码执行确实低很多(经过测试,相差15倍左右)
33、线程优先级只使用三个等级
模拟:run方法有一个比较占用CPU的计算,只是为了保证一个线程尽可能多的消耗CPU资源,目的是为了观察CPU繁忙时不同优先级线程的执行顺序。
CPU并不繁忙时,线程调度不会遵循优先级顺序来进行调度。
public class Test {
public static void main(String[] args) throws FileNotFoundException, IOException {
for(int i=0; i<20; i++){
new TestThread().start(i%10 + 1);
}
}
}
class TestThread implements Runnable{
public void start(int _priority){
Thread t = new Thread(this);
t.setPriority(_priority);
t.start();
}
@Override
public void run() {
//
for(int i=0; i<100000; i++){
//
Math.hypot(Math.pow(924526789, i), Math.cos(i));
//
}
System.out.println("Priority:" + Thread.currentThread().getPriority());
}
}
注释打开的执行结果:


备注:
(1)并不是严格遵照线程优先级别来执行的
(2)优先级差别越大,运行机会差别越明显
(3)线程优先级推荐使用Thread类型定义的三个优先级
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
34、优先选择线程池
线程状态:新建(New)、可运行(Runnable)、阻塞(Blocked )、等待(Waiting)、结束(Terminated)
Thread.State定义了这个5个状态
线程运行时间分为三部分:T1为线程启动时间、T2为线程体的运行时间、T3为线程销毁时间
线程池的实现涉及三个名称
(1)工作线程(Worker)
线程池中的线程,只有两个状态:可运行状态和等待状态,在没有任务时他们处于等待状态,运行时可以循环地执行任务。
(2)任何接口(Task)
(3)任务队列(Work Queue)
Java线程池实现从最根本上来说只有两个:ThreadPoolExecutor类和Scheduled-ThreadPoollExecutor类,这两个类还是父子关系,但是Java为了简化并行计算,还提供了一个Executors的静态类,它可以直接生成多种不同的线程池执行器,比如单线程执行器、带缓冲功能的执行器等,但归根结底还是使
ThreadPoolExecutor类或
ScheduledThreadPoolExecutor类的封装类。
35、提升Java性能的基本方法
(1)不要再循环条件中计算
如果在循环条件中计算,则每循环一次就要计算一次,这会降低系统效率
(2)尽可能把变量、方法声明为final、static类型
(3)缩小变量的作用范围,加快GC的回收
(4)频繁字符串操作使用StringBuilder/StringBuffer
(5)覆写Exception的fillInStackTrace方法
fillInStackTrace方法是用来记录异常时的栈信息,这是非常耗时的动作,如果我们在开发时不需要关注栈信息,则可以覆盖之。如下覆盖fillInStackTrace的自定义异常会使性能提升10倍以上。
class MyException extends Exception{
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
备注:Java的异常处理机制确实比较慢,这个“慢”是相对于诸如String、Integer等对象来说的,单单从对象的创建上来说,new一个IOException会比String慢5倍,这从异常的处理机制上可以解释:因为它要执行fillInStackTrace方法,要记录当前栈的快照,而String类则是直接申请一个内存创建对象,异常类慢一筹也就在所难免了。
// native 本地方法:一个Native Method就是一个java调用非java代码的接口。
private native Throwable fillInStackTrace(int dummy);
t
t
t
图片
图片
图片
图片
图片
图片
图片