Java编程最差实践
刘达人186
2026-09-19 10:54:22
原文
原文地址:http://www.odi.ch/prog/design/newbies.php
每天在写Java程序, 其实里面有一些细节大家可能没怎么注意, 这不, 有人总结了一个我们编程中常见的问题. 虽然一般没有什么大问题, 但是最好别这样做. 另外这里提到的很多问题其实可以通过Findbugs( http://findbugs.sourceforge.net/ )来帮我们进行检查出来.
转载说明:
); //remove first comma
正确的写法:
- StringBuilder sb = new StringBuilder(persons.size() * 16); // well estimated buffer
- for (Person p : persons) {
- if (sb.length() > 0) sb.append(", ");
- sb.append(p.getName);
- }
);
sb.append("Name: ");
sb.append(name);
sb.append("\n!");
String s = sb.toString();
或者这样写:
- String s = "Name: " + name + "\n!";
) ...
if (name == "John") ...
if (name.equals("John")) ...
if ("".equals(name)) ...
上面的代码没有错, 但是不够好. compareTo不够简洁, ==原义是比较两个对象是否一样. 另外比较字符是否为空, 最好判断它的长度.
正确的写法:
- if ("John".equals(name)) ...
- if (name.length() == 0) ...
- if (name.isEmpty()) ...
);
return Boolean.valueOf("true");
正确的写法:
- zero = Integer.valueOf(0);
- return Boolean.TRUE;
) {
...
}
上面的代码是一个byte一个byte的读取, 导致频繁的本地JNI文件系统访问, 非常低效, 因为调用本地方法是非常耗时的. 最好用BufferedInputStream包装一下. 曾经做过一个测试, 从/dev/zero下读取1MB, 大概花了1s, 而用BufferedInputStream包装之后只需要60ms, 性能提高了94%! 这个也适用于output stream操作以及socket操作.
正确的写法:
- InputStream in = new BufferedInputStream(new FileInputStream(file));
); // fail after 20s
InputStream in = socket.getInputStream();
socket.setSoTimeout(15000);
int i = in.read();
另外, 文件的读取(FileInputStream, FileChannel, FileDescriptor, File)没法指定超时时间, 而且IO操作均涉及到本地方法调用, 这个更操作了JVM的控制范围, 在分布式文件系统中, 对IO的操作内部实际上是网络调用. 一般情况下操作60s的操作都可以认为已经超时了. 为了解决这些问题, 一般采用缓存和异步/消息队列处理.
;
private String name = null;
private boolean important = false;
}
这里的变量会在初始化时使用默认值:0, null, false, 因此上面的写法有些多此一举.
正确的写法:
- public class B {
- private int count;
- private String name;
- private boolean important;
- }
+ (int) (collection.size() / 0.75));
));
codes.add(Integer.valueOf(20));
codes.add(Integer.valueOf(30));
codes.add(Integer.valueOf(40));
正确的写法:
- int[] codes = { 10, 20, 30, 40 };
错误的写法:
- // horribly slow and a memory waster if l has a few thousand elements (try it yourself!)
- List<Mergeable> l = ...;
- for (int i=0; i < l.size()-1; i++) {
- Mergeable one = l.get(i);
- Iterator<Mergeable> j = l.iterator(i+1); // memory allocation!
- while (j.hasNext()) {
- Mergeable other = l.next();
- if (one.canMergeWith(other)) {
- one.merge(other);
- other.remove();
- }
- }
- }
正确的写法:
- // quite fast and no memory allocation
- Mergeable[] l = ...;
- for (int i=0; i < l.length-1; i++) {
- Mergeable one = l[i];
- for (int j=i+1; j < l.length; j++) {
- Mergeable other = l[j];
- if (one.canMergeWith(other)) {
- one.merge(other);
- l[j] = null;
- }
- }
- }
实际上Sun也意识到这一点, 因此在JDK中, Collections.sort()就是将一个List拷贝到一个数组中然后调用Arrays.sort方法来执行排序.
);
date = cal.getTime();
这里主要是对date, time, calendar和time zone不了解导致. 而在一个时间上增加8小时, 跟time zone没有任何关系, 所以没有必要使用Calendar, 直接用Date对象即可, 而如果是增加天数的话, 则需要使用Calendar, 因为采用不同的时令制可能一天的小时数是不同的(比如有些DST是23或者25个小时)
正确的写法:
- date = new Date(date.getTime() + 8L * 3600L * 1000L); // add 8 hrs
);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
Date startOfDay = cal.getTime();
这里有两个错误, 一个是没有没有将毫秒归零, 不过最大的错误是没有指定TimeZone, 不过一般的桌面应用没有问题, 但是如果是服务器端应用则会有一些问题, 比如同一时刻在上海和伦敦就不一样, 因此需要指定的TimeZone.
正确的写法:
- Calendar cal = new GregorianCalendar(user.getTimeZone());
- cal.setTime(date);
- cal.set(Calendar.HOUR_OF_DAY, 0);
- cal.set(Calendar.MINUTE, 0);
- cal.set(Calendar.SECOND, 0);
- cal.set(Calendar.MILLISECOND, 0);
- Date startOfDay = cal.getTime();
, Calendar.JANUARY, 15);
Calendar.getInstance()依赖local来选择一个Calendar实现, 不同实现的2009年是不同的, 比如有些Calendar实现就没有January月份.
正确的写法:
- Calendar c = new GregorianCalendar(timeZone);
- c.set(2009, Calendar.JANUARY, 15);
;
String someQuery = "SELECT * FROM ...";
}
很多应用都会定义这样一个全局常量类或接口, 但是为什么这种做法不推荐? 因为这些常量之间基本没有任何关联, 只是因为公用才定义在一起. 但是如果其他组件需要使用这些全局变量, 则必须对该常量类产生依赖, 特别是存在server和远程client调用的场景.
比较好的做法是将这些常量定义在组件内部. 或者局限在一个类库内部.
;
System.out.println((int) b-a);
System.out.println((int) (b-a));
; f-=0.1) {
System.out.println(f);
}
上面的浮点数递减只会无限接近0而不会等于0, 这样会导致上面的for进入死循环. 通常绝不要对float和double使用==操作. 而采用大于和小于操作. 如果java编译器能针对这种情况给出警告. 或者在java语言规范中不支持浮点数类型的==操作就最好了.
正确的写法:
- for (float f = 10f; f>0; f-=0.1) {
- System.out.println(f);
- }
.0f;
for (OrderLine line : lines) {
total += line.price * line.count;
}
double a = 1.14 * 75; // 85.5 将表示为 85.4999...
System.out.println(Math.round(a)); // 输出值为85
BigDecimal d = new BigDecimal(1.14); //造成精度丢失
这个也是一个老生常谈的错误. 比如计算100笔订单, 每笔0.3元, 最终的计算结果是29.9999971. 如果将float类型改为double类型, 得到的结果将是30.000001192092896. 出现这种情况的原因是, 人类和计算的计数方式不同. 人类采用的是十进制, 而计算机是二进制.二进制对于计算机来说非常好使, 但是对于涉及到精确计算的场景就会带来误差. 比如银行金融中的应用.
因此绝不要用浮点类型来保存money数据. 采用浮点数得到的计算结果是不精确的. 即使与int类型做乘法运算也会产生一个不精确的结果.那是因为在用二进制存储一个浮点数时已经出现了精度丢失. 最好的做法就是用一个string或者固定点数来表示. 为了精确, 这种表示方式需要指定相应的精度值.
BigDecimal就满足了上面所说的需求. 如果在计算的过程中精度的丢失超出了给定的范围, 将抛出runtime exception.
正确的写法:
- BigDecimal total = BigDecimal.ZERO;
- for (OrderLine line : lines) {
- BigDecimal price = new BigDecimal(line.price);
- BigDecimal count = new BigDecimal(line.count);
- total = total.add(price.multiply(count)); // BigDecimal is immutable!
- }
- total = total.setScale(2, RoundingMode.HALF_UP);
- BigDecimal a = (new BigDecimal("1.14")).multiply(new BigDecimal(75)); // 85.5 exact
- a = a.setScale(0, RoundingMode.HALF_UP); // 86
- System.out.println(a); // correct output: 86
- BigDecimal a = new BigDecimal("1.14");
, plate);
rs = s.executeQuery();
if (rs.next()) {
car = new Car();
car.make = rs.getString(1);
car.color = rs.getString(2);
}
} finally {
if (rs != null) try { rs.close(); } catch (SQLException e) { }
if (s != null) try { s.close(); } catch (SQLException e) { }
if (c != null) try { c.close(); } catch (SQLException e) { }
}
return car;
}
);
} catch (InterruptedException e) {
// ok
}
or
while (true) {
if (Thread.interrupted()) break;
}
这里主要是interrupted静态方法除了返回当前线程的中断状态, 还会将当前线程状态复位.
正确的写法:
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- or
- while (true) {
- if (Thread.currentThread().isInterrupted()) break;
- }
在静态变量初始化时创建线程
错误的写法:
- class Cache {
- private static final Timer evictor = new Timer();
- }
Timer构造器内部会new一个thread, 而该thread会从它的父线程(即当前线程)中继承各种属性. 比如context classloader, threadlocal以及其他的安全属性(访问权限). 而加载当前类的线程可能是不确定的, 比如一个线程池中随机的一个线程. 如果你需要控制线程的属性, 最好的做法就是将其初始化操作放在一个静态方法中, 这样初始化将由它的调用者来决定.
正确的做法:
- class Cache {
- private static Timer evictor;
- public static setupEvictor() {
- evictor = new Timer();
- }
- }
已取消的定时器任务依然持有状态
错误的写法:
- final MyClass callback = this;
- TimerTask task = new TimerTask() {
- public void run() {
- callback.timeout();
- }
- };
- timer.schedule(task, 300000L);
- try {
- doSomething();
- } finally {
- task.cancel();
- }
上面的task内部包含一个对外部类实例的应用, 这将导致该引用可能不会被GC立即回收. 因为Timer将保留TimerTask在指定的时间之后才被释放. 因此task对应的外部类实例将在5分钟后被回收.
正确的写法:
- TimerTask task = new Job(this);
- timer.schedule(task, 300000L);
- try {
- doSomething();
- } finally {
- task.cancel();
- }
- static class Job extends TimerTask {
- private MyClass callback;
- public Job(MyClass callback) {
- this.callback = callback;
- }
- public boolean cancel() {
- callback = null;
- return super.cancel();
- }
- public void run() {
- if (callback == null) return;
- callback.timeout();
- }
- }
- Java编程最差实践常见问题详细说明(1)转
Java编程最差实践常见问题详细说明(1)转 原文地址:http://www.odi.ch/prog/design/newbies.php 每天在写Java程序, 其实里面有一些细节大家可能没 ...
- Java编程最差实践常见问题详细说明(2)转
Java编程最差实践常见问题详细说明(2)转 2012-12-13 13:57:20| 分类: JAVA | 标签:java |举报|字号 订阅 反射使用不当 错误的写法: Java代 ...
- Java编程最差实践(常见编程错误典范)
转载自 http://macrochen.iteye.com/blog/1393502 每天在写Java程序,其实里面有一些细节大家可能没怎么注意,这不,有人总结了一个我们编程中常见的问题.虽然一般 ...
- Java编程最差代码
字符串连接误用 错误的写法: String s = ""; for (Person p : persons) { s += ", " + p.getName( ...
- [Java并发编程(四)] Java volatile 的理论实践
[Java并发编程(四)] Java volatile 的理论实践 摘要 Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 ...
- Java 编程中关于异常处理的 10 个最佳实践
异常处理是Java 开发中的一个重要部分.它是关乎每个应用的一个非功能性需求,是为了处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java提供了几个异常处理特性,以try,catch 和 ...
- 零基础从实践出发学java编程【总结篇】
1.背景 很多人学习java的第一步就是系统的学习java基础语法,有的java基础语法还没学完就崩溃了,确实java基础语法太多太细,而且都是理论,学着让人很懵: 好不容易学完基础语法,又要学框架. ...
- 【转】JAVA学习笔记----PL/SQL最差实践
1. 超长的PL/SQL代码 影响:可维护性,性能 症状: 在复杂的企业应用中,存在动辄成百上千行的存储过程或上万行的包.为什么是最差: 太长的PL/SQL代码不利于阅读,第三方工 ...
- Java编程性能优化一
转自:http://my.oschina.net/xianggao/blog/77224 在JAVA程序中,性能问题的大部分原因并不在于JAVA语言,而是程序本身.养成良好的编码习惯非常重要,能够显著 ...
随机推荐
- 偶尔用得上的Git操作
Git 工作流 一个不错的工作流图示 创建一个空分支 git checkout --orphan 分支名 删除远程分支和tag git push origin :<branchName> ...
- MySQL--增加或修改列注释
##=============================================## ## 在创建表的时候为字段添加注释 ## CREATE TABLE TB003 ( ID INT P ...
- leetcode:Maximum Depth of Binary Tree【Python版】
# Definition for a binary tree node # class TreeNode: # def __init__(self, x): # self.val = x # self ...
- zstack(一)运行及开发环境搭建及说明(转载)
本篇介绍zstack的部署环境,以及二次开发环境 运行环境 讲真,ZStack的安装做的还是不错的,提供多种安装模式,如离线安装.在线安装.一键安装.分布式安装等.安装的过程其实都很简单,当然这也是z ...
- netty答题
1,介绍一下netty netty封装了Java原生的nio,是一个异步和数据驱动的网络编程框架, 与tcp: netty -> Java Runtime Socket (io.nio.nio2 ...
- Linux elasticsearch 安装 遇到的问题
备注:我的 Linux 测试机 是2G 内存的 ,估计内存小于 我的内存肯定会出这个问题 .(安装的最新版6.3.2) 1. 下载文件 解压 2 .试着 运行 bin 下面的 elasticse ...
- python 函数参数的传递(参数带星号的说明) 元组传递 字典传递
python中函数参数的传递是通过赋值来传递的.函数参数的使用又有俩个方面值得注意:1.函数参数是如何定义的 2.在调用函数的过程中参数是如何被解析 先看第一个问题,在python中函数参数的定义主要 ...
- python下爬某个网页的图片
#coding=utf-8 import re import urllib def getHtml(url): #获取url对应得源码 page = urllib.urlopen(url) html ...
- C# Web Service 初级教学
原文连接:http://www.codeproject.com/cs/webservices/myservice.asp作者:Chris Maunder Introduction Creating y ...
- <<精通正在表达式>> 书评
IT产业新技术日新月异,令人目不暇给,然而在这其中,真正称得上伟大东西的却寥寥无几.1998年,被誉为“软件世界的爱迪生”,发明了BSD. TCP/IP.csh.vi和NFS的SUN首席科学家Bil ...