Java编程最差实践常见问题详细说明(1)转

 
 
原文地址:http://www.odi.ch/prog/design/newbies.php 
每天在写Java程序, 其实里面有一些细节大家可能没怎么注意, 这不, 有人总结了一个我们编程中常见的问题. 虽然一般没有什么大问题, 但是最好别这样做. 另外这里提到的很多问题其实可以通过Findbugs(http://findbugs.sourceforge.net/ )来帮我们进行检查出来. 
字符串连接误用 
 错误的写法:

Java代码  
  1. String s = "";
  2. for (Person p : persons) {
  3. s += ", " + p.getName();
  4. }
  5. s = s.substring(2); //remove first comma

 正确的写法:

Java代码  
  1. StringBuilder sb = new StringBuilder(persons.size() * 16); // well estimated buffer
  2. for (Person p : persons) {
  3. if (sb.length() > 0) sb.append(", ");
  4. sb.append(p.getName);
  5. }

错误的使用StringBuffer 
 错误的写法:

Java代码  
  1. StringBuffer sb = new StringBuffer();
  2. sb.append("Name: ");
  3. sb.append(name + '\n');
  4. sb.append("!");
  5. ...
  6. String s = sb.toString();

问题在第三行, append char比String性能要好, 另外就是初始化StringBuffer没有指定size, 导致中间append时可能重新调整内部数组大小. 如果是JDK1.5最好用StringBuilder取代StringBuffer, 除非有线程安全的要求. 还有一种方式就是可以直接连接字符串. 缺点就是无法初始化时指定长度. 
 正确的写法:

Java代码  
  1. StringBuilder sb = new StringBuilder(100);
  2. sb.append("Name: ");
  3. sb.append(name);
  4. sb.append("\n!");
  5. String s = sb.toString();

或者这样写:

Java代码  
  1. String s = "Name: " + name + "\n!";

测试字符串相等性 
 错误的写法:

Java代码  
  1. if (name.compareTo("John") == 0) ...
  2. if (name == "John") ...
  3. if (name.equals("John")) ...
  4. if ("".equals(name)) ...

上面的代码没有错, 但是不够好. compareTo不够简洁, ==原义是比较两个对象是否一样. 另外比较字符是否为空, 最好判断它的长度. 
 正确的写法:

Java代码  
  1. if ("John".equals(name)) ...
  2. if (name.length() == 0) ...
  3. if (name.isEmpty()) ...

数字转换成字符串 
 错误的写法:

Java代码  
  1. "" + set.size()
  2. new Integer(set.size()).toString()

 正确的写法:

Java代码  
  1. String.valueOf(set.size())

利用不可变对象(Immutable) 
 错误的写法:

Java代码  
  1. zero = new Integer(0);
  2. return Boolean.valueOf("true");

 正确的写法:

Java代码  
  1. zero = Integer.valueOf(0);
  2. return Boolean.TRUE;

请使用XML解析器 
 错误的写法:

Java代码  
  1. int start = xml.indexOf("<name>") + "<name>".length();
  2. int end = xml.indexOf("</name>");
  3. String name = xml.substring(start, end);

 正确的写法:

Java代码  
  1. SAXBuilder builder = new SAXBuilder(false);
  2. Document doc = doc = builder.build(new StringReader(xml));
  3. String name = doc.getRootElement().getChild("name").getText();

请使用JDom组装XML 
 错误的写法:

Java代码  
  1. String name = ...
  2. String attribute = ...
  3. String xml = "<root>"
  4. +"<name att=\""+ attribute +"\">"+ name +"</name>"
  5. +"</root>";

 正确的写法:

Java代码  
  1. Element root = new Element("root");
  2. root.setAttribute("att", attribute);
  3. root.setText(name);
  4. Document doc = new Documet();
  5. doc.setRootElement(root);
  6. XmlOutputter out = new XmlOutputter(Format.getPrettyFormat());
  7. String xml = out.outputString(root);

XML编码陷阱 
 错误的写法:

Java代码  
  1. String xml = FileUtils.readTextFile("my.xml");

因为xml的编码在文件中指定的, 而在读文件的时候必须指定编码. 另外一个问题不能一次就将一个xml文件用String保存, 这样对内存会造成不必要的浪费, 正确的做法用InputStream来边读取边处理. 为了解决编码的问题, 最好使用XML解析器来处理 
未指定字符编码 
 错误的写法:

Java代码  
  1. Reader r = new FileReader(file);
  2. Writer w = new FileWriter(file);
  3. Reader r = new InputStreamReader(inputStream);
  4. Writer w = new OutputStreamWriter(outputStream);
  5. String s = new String(byteArray); // byteArray is a byte[]
  6. byte[] a = string.getBytes();

这样的代码主要不具有跨平台可移植性. 因为不同的平台可能使用的是不同的默认字符编码. 
 正确的写法:

Java代码  
  1. Reader r = new InputStreamReader(new FileInputStream(file), "ISO-8859-1");
  2. Writer w = new OutputStreamWriter(new FileOutputStream(file), "ISO-8859-1");
  3. Reader r = new InputStreamReader(inputStream, "UTF-8");
  4. Writer w = new OutputStreamWriter(outputStream, "UTF-8");
  5. String s = new String(byteArray, "ASCII");
  6. byte[] a = string.getBytes("ASCII");

未对数据流进行缓存 
 错误的写法:

Java代码  
  1. InputStream in = new FileInputStream(file);
  2. int b;
  3. while ((b = in.read()) != -1) {
  4. ...
  5. }

上面的代码是一个byte一个byte的读取, 导致频繁的本地JNI文件系统访问, 非常低效, 因为调用本地方法是非常耗时的. 最好用BufferedInputStream包装一下. 曾经做过一个测试, 从/dev/zero下读取1MB, 大概花了1s, 而用BufferedInputStream包装之后只需要60ms, 性能提高了94%! 这个也适用于output stream操作以及socket操作. 
 正确的写法:

Java代码  
  1. InputStream in = new BufferedInputStream(new FileInputStream(file));

无限使用heap内存 
 错误的写法:

Java代码  
  1. byte[] pdf = toPdf(file);

这里有一个前提, 就是文件大小不能讲JVM的heap撑爆. 否则就等着OOM吧, 尤其是在高并发的服务器端代码. 最好的做法是采用Stream的方式边读取边存储(本地文件或database). 
 正确的写法:

Java代码  
  1. File pdf = toPdf(file);

另外, 对于服务器端代码来说, 为了系统的安全, 至少需要对文件的大小进行限制. 
不指定超时时间 
错误的代码:

Java代码  
  1. Socket socket = ...
  2. socket.connect(remote);
  3. InputStream in = socket.getInputStream();
  4. int i = in.read();

这种情况在工作中已经碰到不止一次了. 个人经验一般超时不要超过20s. 这里有一个问题, connect可以指定超时时间, 但是read无法指定超时时间. 但是可以设置阻塞(block)时间. 
 正确的写法:

Java代码  
  1. Socket socket = ...
  2. socket.connect(remote, 20000); // fail after 20s
  3. InputStream in = socket.getInputStream();
  4. socket.setSoTimeout(15000);
  5. int i = in.read();

另外, 文件的读取(FileInputStream, FileChannel, FileDescriptor, File)没法指定超时时间, 而且IO操作均涉及到本地方法调用, 这个更操作了JVM的控制范围, 在分布式文件系统中, 对IO的操作内部实际上是网络调用. 一般情况下操作60s的操作都可以认为已经超时了. 为了解决这些问题, 一般采用缓存和异步/消息队列处理. 
频繁使用计时器 
错误代码:

Java代码  
  1. for (...) {
  2. long t = System.currentTimeMillis();
  3. long t = System.nanoTime();
  4. Date d = new Date();
  5. Calendar c = new GregorianCalendar();
  6. }

每次new一个Date或Calendar都会涉及一次本地调用来获取当前时间(尽管这个本地调用相对其他本地方法调用要快). 
如果对时间不是特别敏感, 这里使用了clone方法来新建一个Date实例. 这样相对直接new要高效一些. 
 正确的写法:

Java代码  
  1. Date d = new Date();
  2. for (E entity : entities) {
  3. entity.doSomething();
  4. entity.setUpdated((Date) d.clone());
  5. }

如果循环操作耗时较长(超过几ms), 那么可以采用下面的方法, 立即创建一个Timer, 然后定期根据当前时间更新时间戳, 在我的系统上比直接new一个时间对象快200倍:

Java代码  
  1. private volatile long time;
  2. Timer timer = new Timer(true);
  3. try {
  4. time = System.currentTimeMillis();
  5. timer.scheduleAtFixedRate(new TimerTask() {
  6. public void run() {
  7. time = System.currentTimeMillis();
  8. }
  9. }, 0L, 10L); // granularity 10ms
  10. for (E entity : entities) {
  11. entity.doSomething();
  12. entity.setUpdated(new Date(time));
  13. }
  14. } finally {
  15. timer.cancel();
  16. }

捕获所有的异常 
 错误的写法:

Java代码  
  1. Query q = ...
  2. Person p;
  3. try {
  4. p = (Person) q.getSingleResult();
  5. } catch(Exception e) {
  6. p = null;
  7. }

这是EJB3的一个查询操作, 可能出现异常的原因是: 结果不唯一; 没有结果; 数据库无法访问, 而捕获所有的异常, 设置为null将掩盖各种异常情况. 
 正确的写法:

Java代码  
  1. Query q = ...
  2. Person p;
  3. try {
  4. p = (Person) q.getSingleResult();
  5. } catch(NoResultException e) {
  6. p = null;
  7. }

忽略所有异常

Java代码  
  1. try {
  2. doStuff();
  3. } catch(Exception e) {
  4. log.fatal("Could not do stuff");
  5. }
  6. doMoreStuff();

这个代码有两个问题, 一个是没有告诉调用者, 系统调用出错了. 第二个是日志没有出错原因, 很难跟踪定位问题. 
 正确的写法:

Java代码  
  1. try {
  2. doStuff();
  3. } catch(Exception e) {
  4. throw new MyRuntimeException("Could not do stuff because: "+ e.getMessage, e);
  5. }

重复包装RuntimeException 
 错误的写法:

Java代码  
  1. try {
  2. doStuff();
  3. } catch(Exception e) {
  4. throw new RuntimeException(e);
  5. }

 正确的写法:

Java代码  
  1. try {
  2. doStuff();
  3. } catch(RuntimeException e) {
  4. throw e;
  5. } catch(Exception e) {
  6. throw new RuntimeException(e.getMessage(), e);
  7. }
  8. try {
  9. doStuff();
  10. } catch(IOException e) {
  11. throw new RuntimeException(e.getMessage(), e);
  12. } catch(NamingException e) {
  13. throw new RuntimeException(e.getMessage(), e);
  14. }

不正确的传播异常 
 错误的写法:

Java代码  
  1. try {
  2. } catch(ParseException e) {
  3. throw new RuntimeException();
  4. throw new RuntimeException(e.toString());
  5. throw new RuntimeException(e.getMessage());
  6. throw new RuntimeException(e);
  7. }

主要是没有正确的将内部的错误信息传递给调用者. 第一个完全丢掉了内部错误信息, 第二个错误信息依赖toString方法, 如果没有包含最终的嵌套错误信息, 也会出现丢失, 而且可读性差. 第三个稍微好一些, 第四个跟第二个一样. 
 正确的写法:

Java代码  
  1. try {
  2. } catch(ParseException e) {
  3. throw new RuntimeException(e.getMessage(), e);
  4. }

用日志记录异常 
 错误的写法:

Java代码  
  1. try {
  2. ...
  3. } catch(ExceptionA e) {
  4. log.error(e.getMessage(), e);
  5. throw e;
  6. } catch(ExceptionB e) {
  7. log.error(e.getMessage(), e);
  8. throw e;
  9. }

一般情况下在日志中记录异常是不必要的, 除非调用方没有记录日志. 
异常处理不彻底 
 错误的写法:

Java代码  
  1. try {
  2. is = new FileInputStream(inFile);
  3. os = new FileOutputStream(outFile);
  4. } finally {
  5. try {
  6. is.close();
  7. os.close();
  8. } catch(IOException e) {
  9. /* we can't do anything */
  10. }
  11. }

is可能close失败, 导致os没有close 
 正确的写法:

Java代码  
  1. try {
  2. is = new FileInputStream(inFile);
  3. os = new FileOutputStream(outFile);
  4. } finally {
  5. try { if (is != null) is.close(); } catch(IOException e) {/* we can't do anything */}
  6. try { if (os != null) os.close(); } catch(IOException e) {/* we can't do anything */}
  7. }

捕获不可能出现的异常 
 错误的写法:

Java代码  
  1. try {
  2. ... do risky stuff ...
  3. } catch(SomeException e) {
  4. // never happens
  5. }
  6. ... do some more ...

 正确的写法:

Java代码  
  1. try {
  2. ... do risky stuff ...
  3. } catch(SomeException e) {
  4. // never happens hopefully
  5. throw new IllegalStateException(e.getMessage(), e); // crash early, passing all information
  6. }
  7. ... do some more ...

transient的误用 
 错误的写法:

Java代码  
  1. public class A implements Serializable {
  2. private String someState;
  3. private transient Log log = LogFactory.getLog(getClass());
  4. public void f() {
  5. log.debug("enter f");
  6. ...
  7. }
  8. }

这里的本意是不希望Log对象被序列化. 不过这里在反序列化时, 会因为log未初始化, 导致f()方法抛空指针, 正确的做法是将log定义为静态变量或者定位为具备变量. 
 正确的写法:

Java代码  
  1. public class A implements Serializable {
  2. private String someState;
  3. private static final Log log = LogFactory.getLog(A.class);
  4. public void f() {
  5. log.debug("enter f");
  6. ...
  7. }
  8. }
  9. public class A implements Serializable {
  10. private String someState;
  11. public void f() {
  12. Log log = LogFactory.getLog(getClass());
  13. log.debug("enter f");
  14. ...
  15. }
  16. }

不必要的初始化 
 错误的写法:

Java代码  
  1. public class B {
  2. private int count = 0;
  3. private String name = null;
  4. private boolean important = false;
  5. }

这里的变量会在初始化时使用默认值:0, null, false, 因此上面的写法有些多此一举. 
 正确的写法:

Java代码  
  1. public class B {
  2. private int count;
  3. private String name;
  4. private boolean important;
  5. }

最好用静态final定义Log变量

Java代码  
  1. private static final Log log = LogFactory.getLog(MyClass.class);

这样做的好处有三:

  • 可以保证线程安全
  • 静态或非静态代码都可用
  • 不会影响对象序列化

选择错误的类加载器 
错误的代码:

Java代码  
  1. Class clazz = Class.forName(name);
  2. Class clazz = getClass().getClassLoader().loadClass(name);

这里本意是希望用当前类来加载希望的对象, 但是这里的getClass()可能抛出异常, 特别在一些受管理的环境中, 比如应用服务器, web容器, Java WebStart环境中, 最好的做法是使用当前应用上下文的类加载器来加载. 
 正确的写法:

Java代码  
  1. ClassLoader cl = Thread.currentThread().getContextClassLoader();
  2. if (cl == null) cl = MyClass.class.getClassLoader(); // fallback
  3. Class clazz = cl.loadClass(name);

Java编程最差实践常见问题详细说明(1)转的更多相关文章

  1. Java编程最差实践常见问题详细说明(2)转

    Java编程最差实践常见问题详细说明(2)转 2012-12-13 13:57:20|  分类: JAVA |  标签:java  |举报|字号 订阅     反射使用不当  错误的写法: Java代 ...

  2. Java编程最差实践(常见编程错误典范)

    转载自  http://macrochen.iteye.com/blog/1393502 每天在写Java程序,其实里面有一些细节大家可能没怎么注意,这不,有人总结了一个我们编程中常见的问题.虽然一般 ...

  3. Java编程最差实践

    原文地址:http://www.odi.ch/prog/design/newbies.php 每天在写Java程序, 其实里面有一些细节大家可能没怎么注意, 这不, 有人总结了一个我们编程中常见的问题 ...

  4. Java编程中的一些常见问题汇总

    转载自  http://macrochen.iteye.com/blog/1393502 每天在写Java程序,其实里面有一些细节大家可能没怎么注意,这不,有人总结了一个我们编程中常见的问题.虽然一般 ...

  5. Java编程最差代码

    字符串连接误用 错误的写法:  String s = ""; for (Person p : persons) { s += ", " + p.getName( ...

  6. [Java并发编程(四)] Java volatile 的理论实践

    [Java并发编程(四)] Java volatile 的理论实践 摘要 Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 ...

  7. Java 编程中关于异常处理的 10 个最佳实践

    异常处理是Java 开发中的一个重要部分.它是关乎每个应用的一个非功能性需求,是为了处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java提供了几个异常处理特性,以try,catch 和 ...

  8. 【转】JAVA学习笔记----PL/SQL最差实践

    1. 超长的PL/SQL代码   影响:可维护性,性能   症状:    在复杂的企业应用中,存在动辄成百上千行的存储过程或上万行的包.为什么是最差:    太长的PL/SQL代码不利于阅读,第三方工 ...

  9. 零基础从实践出发学java编程【总结篇】

    1.背景 很多人学习java的第一步就是系统的学习java基础语法,有的java基础语法还没学完就崩溃了,确实java基础语法太多太细,而且都是理论,学着让人很懵: 好不容易学完基础语法,又要学框架. ...

随机推荐

  1. DS博客作业02--栈和队列

    0.PTA得分截图 1.本周学习总结 1.1总结栈和队列内容 栈的存储结构及操作 栈的顺序存储结构 typedef struct { ElemType data[MaxSize]: int top: ...

  2. NFS作为根文件系统,挂载超时

    NFS服务器配置正确后,使用ramfs,通过mount能够正常挂载NFS,但是作为ROOTFS无法正常挂载,显示超时. 经查看log,RPC报错-120. 分析结果: 在Ubuntu1804上,nfs ...

  3. RMQ Tarjan的Sparse-Table算法

    参考博客:https://www.cnblogs.com/wenzhixin/p/9714760.html 预处理时间复杂度是O(nlogn),代码如下: void init(const vector ...

  4. MySQL----SQL操作

    1.什么是SQL? Structured Query Language:结构化查询语言 其实就是定义了操作所有关系型数据库的规则.每一种数据库操作的方式存在不一样的地方,称为“方言”. 2.SQL通用 ...

  5. 动态规划-Last Stone Weight II

    2020-01-11 17:47:59 问题描述: 问题求解: 本题和另一题target sum非常类似.target sum的要求是在一个数组中随机添加正负号,使得最终得到的结果是target,这个 ...

  6. 动态规划-划分数组的最大和 Split Array Largest Sum

    2019-10-14 22:13:18 问题描述: 问题求解: 解法一:动态规划 这种数组划分的题目基本都可以使用dp来解决,核心的思路就是先维护低的划分,再在中间找分割点加入新的划分. public ...

  7. [dfs] 2019牛客暑期多校训练营(第十场) Coffee Chicken

    题目地址: https://ac.nowcoder.com/acm/contest/890/B   时间限制:C/C++ 1秒,其他语言2秒空间限制:C/C++ 524288K,其他语言1048576 ...

  8. Hive常用命令及作用

    1-创建表 -- 内部表 create table aa(col1 string,col2 int) partitioned by(statdate int) ROW FORMAT DELIMITED ...

  9. Convert JS object to JSON string

    Modern browsers (IE8, FF3, Chrome etc.) have native JSON support built in (Same API as with JSON2). ...

  10. Building Applications with Force.com and VisualForce(Dev401)(十四):Implementing Business Processes:Auditing Processes

    Dev401-015:Implementing Business Processes:Auditing Processes Module Objectives1.list some of the fe ...