Java编程最差实践
刘达人186
2024-09-16 03:12:55
原文
原文地址: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语言,而是程序本身.养成良好的编码习惯非常重要,能够显著 ...
随机推荐
- LG1600 天天爱跑步
题意 分析 对一个(s,t)查询,令f=lca(s,t),则操作可化为(s,f),(f,t). 考虑观察到的情况,若x在s到t的路径上,且x观察到,则 \[ \textrm{dep}_s-\textr ...
- leetcode:Count and Say【Python版】
一次AC 字符串就是:count+char class Solution: # @return a string def countAndSay(self, n): str = " for ...
- statik golang 静态资源嵌入二进制文件工具使用(docker 构建)
将静态资源打包进二进制文件有好多方便的地方 方便客户演示 代码简单加密 运行方便 statik 就是一款在golang 中用的比较多,nodejs 有一款pkg (oclif 就推荐使用此工具) ...
- c#代码获取web.config配置文件里面设置的 <compilation debug="true"节点
== 在我们的web.config文件中,有像下面的这样的一个配置, <system.web> <identity impersonate="true" user ...
- 【转】每天一个linux命令(30): chown命令
原文网址:http://www.cnblogs.com/peida/archive/2012/12/04/2800684.html chown将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者 ...
- python版 google密码认证器
#!/usr/bin/env python # -*- coding:utf-8 -*- import hmac, base64, struct, hashlib, time def calGoogl ...
- 使用js提交form表单的两种方法
提交form表单的时候瑶族一些简单的验证,验证完后才能提交,避免无效提交. 1.当输入用户名和密码为空的时候,需要判断.这时候就用到了校验用户名和密码,这个需要在前端页面写:有两种方法,一种是用sub ...
- vqmod for opencart插件制作进阶与技巧
FROM: http://www.sdtclass.com/4799.html 15年的时候,我写过一篇文章<略谈 vqmod for opencart 插件制作过程>,也不知道被哪些人抄 ...
- WPF中控制窗口显示位置的三种方式
首先新建一个WPF工程,在主界面添加一个按钮,并给按钮添加点击事件button1_Click,然后新建一个用于测试弹出位置的窗口TestWindow.1.在屏幕中间显示,设置window.Window ...
- java Map实例
此段资料在自于<黑马程序员_毕向东_Java基础视频教程第16天-06-集合(Map练习)> HashMap 如果对象可能会产生很多对象,可能就会需要存储,就有可能会存到hashSet(哈 ...