java代码优化(下)

(21)将常量声明为static final,并以大写命名

这样在编译期间就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也可以方便区分出常量与变量

(22)不要创建一些不使用的对象,不要导入一些不使用的类

这毫无意义,如果代码中出现”The value of the local variable i is not used”、”The import java.util is never used”,那么请删除这些无用的内容

(23)程序运行过程中避免使用反射

反射是Java提供给用户一个很强大的功能,功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是Method的invoke方法,如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存—-用户只关心和对端交互的时候获取最快的响应速度,并不关心对端的项目启动花多久时间。

(24)使用数据库连接池和线程池

这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程

(25)使用带缓冲的输入输出流进行IO操作

带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升IO效率

(26)顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList

这个,理解ArrayList和LinkedList的原理就知道了

(27)不要让public方法中有太多的形参

public方法即对外提供的方法,如果给这些方法太多形参的话主要有两点坏处:

违反了面向对象的编程思想,Java讲求一切都是对象,太多的形参,和面向对象的编程思想并不契合

参数太多势必导致方法调用的出错概率增加

至于这个”太多”指的是多少个,3、4个吧。比如我们用JDBC写一个insertStudentInfo方法,有10个学生信息字段要插如Student表中,可以把这10个参数封装在一个实体类中,作为insert方法的形参

(28)字符串变量和字符串常量equals的时候将字符串常量写在前面

这是一个比较常见的小技巧了,如果有以下代码:

String str = "123";

if (str.equals("123"))

{

    ...

}

建议修改为:

String str = "123";

if ("123".equals(str))

{

    ...

}

这么做主要是可以避免空指针异常

(29)java中if (i == 1)和if (1 == i)是没有区别的,尽管Java的”if (i == 1)”和”if (1 == i)”在语义上没有任何区别,从阅读习惯上讲,建议使用前者会更好些。

(30)不要对数组使用toString()方法

看一下对数组使用toString()打印出来的是什么:

public static void main(String[] args)

{

    int[] is = new int[]{1, 2, 3};

    System.out.println(is.toString());

}

结果是:

[I@18a992f

本意是想打印出数组内容,却有可能因为数组引用is为空而导致空指针异常。不过虽然对数组toString()没有意义,但是对集合toString()是可以打印出集合里面的内容的,因为集合的父类AbstractCollections<E>重写了Object的toString()方法。

(31)不要对超出范围的基本数据类型做向下强制转型

这绝不会得到想要的结果:

public static void main(String[] args)

{

    long l = 12345678901234L;

    int i = (int)l;

    System.out.println(i);

}

我们可能期望得到其中的某几位,但是结果却是:

1942892530

解释一下。Java中long是8个字节64位的,所以12345678901234在计算机中的表示应该是:

0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010

一个int型数据是4个字节32位的,从低位取出上面这串二进制数据的前32位是:

0111 0011 1100 1110 0010 1111 1111 0010

这串二进制表示为十进制1942892530,所以就是我们上面的控制台上输出的内容。从这个例子上还能顺便得到两个结论:

1、整型默认的数据类型是int,long l = 12345678901234L,这个数字已经超出了int的范围了,所以最后有一个L,表示这是一个long型数。顺便,浮点型的默认类型是double,所以定义float的时候要写成”"float f = 3.5f”

2、接下来再写一句”int ii = l + i;”会报错,因为long + int是一个long,不能赋值给int

(32)公用的集合类中不使用的数据一定要及时remove掉

如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去remove掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。

(33)把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据+”"最慢

把一个基本数据类型转为一般有三种方式,我有一个Integer型数据i,可以使用i.toString()、String.valueOf(i)、i+”"三种方式,三种方式的效率如何,看一个测试:

public static void main(String[] args)

{

    int loopTime = 50000;

    Integer i = 0;

    long startTime = System.currentTimeMillis();

    for (int j = 0; j < loopTime; j++)

    {

        String str = String.valueOf(i);

    }   

    System.out.println("String.valueOf():" + (System.currentTimeMillis() - startTime) + "ms");

    startTime = System.currentTimeMillis();

    for (int j = 0; j < loopTime; j++)

    {

        String str = i.toString();

    }   

    System.out.println("Integer.toString():" + (System.currentTimeMillis() - startTime) + "ms");

    startTime = System.currentTimeMillis();

    for (int j = 0; j < loopTime; j++)

    {

        String str = i + "";

    }   

    System.out.println("i + \"\":" + (System.currentTimeMillis() - startTime) + "ms");

}

运行结果为:

String.valueOf():11ms

Integer.toString():5ms

i + "":25ms

所以以后遇到把一个基本数据类型转为String的时候,优先考虑使用toString()方法。至于为什么,很简单:

String.valueOf()方法底层调用了Integer.toString()方法,但是会在调用前做空判断

Integer.toString()方法就不说了,直接调用了

i + “”底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串

三者对比下来,明显是2最快、1次之、3最慢

(34)使用最有效率的方式去遍历Map

遍历Map的方式有很多,通常场景下我们需要的是遍历Map中的Key和Value,那么推荐使用的、效率最高的方式是:

public static void main(String[] args)

{

    HashMap<String, String> hm = new HashMap<String, String>();

    hm.put("111", "222");

    Set<Map.Entry<String, String>> entrySet = hm.entrySet();

    Iterator<Map.Entry<String, String>> iter = entrySet.iterator();

    while (iter.hasNext())

    {

        Map.Entry<String, String> entry = iter.next();

        System.out.println(entry.getKey() + "\t" + entry.getValue());

    }

}

如果你只是想遍历一下这个Map的key值,那用”Set<String> keySet = hm.keySet();”会比较合适一些

(35)对资源的close()建议分开操作

意思是,比如我有这么一段代码:

try

{

    XXX.close();

    YYY.close();

}

catch (Exception e)

{

    ...

}

建议修改为:

try

{

    XXX.close();

}

catch (Exception e)

{

    ...

}

try

{

    YYY.close();

}

catch (Exception e)

{

    ...

}

虽然有些麻烦,却能避免资源泄露。我们想,如果没有修改过的代码,万一XXX.close()抛异常了,那么就进入了catch块中了,YYY.close()不会执行,YYY这块资源就不会回收了,一直占用着,这样的代码一多,是可能引起资源句柄泄露的。而改为下面的写法之后,就保证了无论如何XXX和YYY都会被close掉

(36)对于ThreadLocal使用前或者使用后一定要先remove

当前基本所有的项目都使用了线程池技术,这非常好,可以动态配置线程数、可以重用线程。

然而,如果你在项目中使用到了ThreadLocal,一定要记得使用前或者使用后remove一下。这是因为上面提到了线程池技术做的是一个线程重用,这意味着代码运行过程中,一条线程使用完毕,并不会被销毁而是等待下一次的使用。我们看一下Thread类中,持有ThreadLocal.ThreadLocalMap的引用:

/* ThreadLocal values pertaining to this thread. This map is maintained

 * by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;

线程不销毁意味着上条线程set的ThreadLocal.ThreadLocalMap中的数据依然存在,那么在下一条线程重用这个Thread的时候,很可能get到的是上条线程set的数据而不是自己想要的内容。

这个问题非常隐晦,一旦出现这个原因导致的错误,没有相关经验或者没有扎实的基础非常难发现这个问题,因此在写代码的时候就要注意这一点,这将给你后续减少很多的工作量。

(37)切记以常量定义的方式替代魔鬼数字,魔鬼数字的存在将极大地降低代码可读性,字符串常量是否使用常量定义可以视情况而定

(38)long或者Long初始赋值时,使用大写的L而不是小写的l,因为字母l极易与数字1混淆,这个点非常细节,值得注意

(39)所有重写的方法必须保留@Override注解

这么做有三个原因:

  (1)清楚地可以知道这个方法由父类继承而来

  (2)getObject()和get0bject()方法,前者第四个字母是”O”,后者第四个子母是”0″,加了@Override注解可以马上判断是否重写成功

  (3)在抽象类中对方法签名进行修改,实现类会马上报出编译错误

(40)推荐使用JDK7中新引入的Objects工具类来进行对象的equals比较,直接a.equals(b),有空指针异常的风险

(41)循环体内不要使用”+”进行字符串拼接,而直接使用StringBuilder不断append

说一下不使用”+”进行字符串拼接的原因,假如我有一个方法:

public String appendStr(String oriStr, String... appendStrs) {

    if (appendStrs == null || appendStrs.length == 0) {

        return oriStr;

    }

    for (String appendStr : appendStrs) {

        oriStr += appendStr;

    }

    return oriStr;

}

(42)不捕获Java类库中定义的继承自RuntimeException的运行时异常类

异常处理效率低,RuntimeException的运行时异常类,其中绝大多数完全可以由程序员来规避,比如:

ArithmeticException可以通过判断除数是否为空来规避

NullPointerException可以通过判断对象是否为空来规避

IndexOutOfBoundsException可以通过判断数组/字符串长度来规避

ClassCastException可以通过instanceof关键字来规避

ConcurrentModificationException可以使用迭代器来规避

(43)避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed 导致的性能下降,JDK7之后,可以使用ThreadLocalRandom来获取随机数

解释一下竞争同一个seed导致性能下降的原因,比如,看一下Random类的nextInt()方法实现:

 public int nextInt() {

     return next(32);

 }

调用了next(int bits)方法,这是一个受保护的方法:

protected int next(int bits) {

    long oldseed, nextseed;

    AtomicLong seed = this.seed;

    do {

        oldseed = seed.get();

        nextseed = (oldseed * multiplier + addend) & mask;

    } while (!seed.compareAndSet(oldseed, nextseed));

    return (int)(nextseed >>> (48 - bits));

}

而这边的seed是一个全局变量:

/**

  * The internal state associated with this pseudorandom number generator.

  * (The specs for the methods in this class describe the ongoing

  * computation of this value.)

 */

 private final AtomicLong seed;

多个线程同时获取随机数的时候,会竞争同一个seed,导致了效率的降低。

(44)静态类、单例类、工厂类将它们的构造函数置为private

这是因为静态类、单例类、工厂类这种类本来我们就不需要外部将它们new出来,将构造函数置为private之后,保证了这些类不会产生实例对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值