来自:疯狂的菠菜 - ITeye技术网站 链接:http://macrochen./blog/1393502(点击尾部阅读原文前往) 原文:http://www./prog/design/newbies.php
每天在写Java程序, 其实里面有一些细节大家可能没怎么注意, 这不, 有人总结了一个我们编程中常见的问题. 虽然一般没有什么大问题, 但是最好别这样做. 另外这里提到的很多问题其实可以通过Findbugs(http://findbugs./ )来帮我们进行检查出来. 字符串连接误用 错误的写法:
String s = ""; for (Person p : persons) { s += ", " + p.getName(); } s = s.substring(2); //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); }
错误的使用StringBuffer 错误的写法:
StringBuffer sb = new StringBuffer(); sb.append("Name: "); sb.append(name + '\n'); sb.append("!"); ... String s = sb.toString();
问题在第三行, append char比String性能要好, 另外就是初始化StringBuffer没有指定size, 导致中间append时可能重新调整内部数组大小. 如果是JDK1.5最好用StringBuilder取代StringBuffer, 除非有线程安全的要求. 还有一种方式就是可以直接连接字符串. 缺点就是无法初始化时指定长度. 正确的写法:
StringBuilder sb = new StringBuilder(100); sb.append("Name: "); sb.append(name); sb.append("\n!"); String s = sb.toString();
或者这样写:
String s = "Name: " + name + "\n!";
测试字符串相等性 错误的写法:
if (name.compareTo("John") == 0) ... if (name == "John") ... if (name.equals("John")) ... if ("".equals(name)) ...
上面的代码没有错, 但是不够好. compareTo不够简洁, ==原义是比较两个对象是否一样. 另外比较字符是否为空, 最好判断它的长度. 正确的写法:
if ("John".equals(name)) ... if (name.length() == 0) ... if (name.isEmpty()) ...
数字转换成字符串 错误的写法:
"" + set.size() new Integer(set.size()).toString()
正确的写法:
String.valueOf(set.size())
利用不可变对象(Immutable) 错误的写法:
zero = new Integer(0); return Boolean.valueOf("true");
正确的写法:
zero = Integer.valueOf(0); return Boolean.TRUE;
请使用XML解析器 错误的写法:
int start = xml.indexOf("<name>") + "<name>".length(); int end = xml.indexOf("</name>"); String name = xml.substring(start, end);
正确的写法:
SAXBuilder builder = new SAXBuilder(false); Document doc = doc = builder.build(new StringReader(xml)); String name = doc.getRootElement().getChild("name").getText();
请使用JDom组装XML 错误的写法:
String name = ... String attribute = ... String xml = "<root>" +"<name att=\""+ attribute +"\">"+ name +"</name>" +"</root>";
正确的写法:
Element root = new Element("root"); root.setAttribute("att", attribute); root.setText(name); Document doc = new Documet(); doc.setRootElement(root); XmlOutputter out = new XmlOutputter(Format.getPrettyFormat()); String xml = out.outputString(root);
XML编码陷阱 错误的写法:
String xml = FileUtils.readTextFile("my.xml");
因为xml的编码在文件中指定的, 而在读文件的时候必须指定编码. 另外一个问题不能一次就将一个xml文件用String保存, 这样对内存会造成不必要的浪费, 正确的做法用InputStream来边读取边处理. 为了解决编码的问题, 最好使用XML解析器来处理 未指定字符编码 错误的写法:
Reader r = new FileReader(file); Writer w = new FileWriter(file); Reader r = new InputStreamReader(inputStream); Writer w = new OutputStreamWriter(outputStream); String s = new String(byteArray); // byteArray is a byte[] byte[] a = string.getBytes();
这样的代码主要不具有跨平台可移植性. 因为不同的平台可能使用的是不同的默认字符编码. 正确的写法:
Reader r = new InputStreamReader(new FileInputStream(file), "ISO-8859-1"); Writer w = new OutputStreamWriter(new FileOutputStream(file), "ISO-8859-1"); Reader r = new InputStreamReader(inputStream, "UTF-8"); Writer w = new OutputStreamWriter(outputStream, "UTF-8"); String s = new String(byteArray, "ASCII"); byte[] a = string.getBytes("ASCII");
未对数据流进行缓存 错误的写法:
InputStream in = new FileInputStream(file); int b; while ((b = in.read()) != -1) { ... }
上面的代码是一个byte一个byte的读取, 导致频繁的本地JNI文件系统访问, 非常低效, 因为调用本地方法是非常耗时的. 最好用BufferedInputStream包装一下. 曾经做过一个测试, 从/dev/zero下读取1MB, 大概花了1s, 而用BufferedInputStream包装之后只需要60ms, 性能提高了94%! 这个也适用于output stream操作以及socket操作. 正确的写法:
InputStream in = new BufferedInputStream(new FileInputStream(file));
无限使用heap内存 错误的写法:
byte[] pdf = toPdf(file);
这里有一个前提, 就是文件大小不能讲JVM的heap撑爆. 否则就等着OOM吧, 尤其是在高并发的服务器端代码. 最好的做法是采用Stream的方式边读取边存储(本地文件或database). 正确的写法:
File pdf = toPdf(file);
另外, 对于服务器端代码来说, 为了系统的安全, 至少需要对文件的大小进行限制. 不指定超时时间 错误的代码:
Socket socket = ... socket.connect(remote); InputStream in = socket.getInputStream(); int i = in.read();
这种情况在工作中已经碰到不止一次了. 个人经验一般超时不要超过20s. 这里有一个问题, connect可以指定超时时间, 但是read无法指定超时时间. 但是可以设置阻塞(block)时间. 正确的写法:
Socket socket = ... socket.connect(remote, 20000); // fail after 20s InputStream in = socket.getInputStream(); socket.setSoTimeout(15000); int i = in.read();
另外, 文件的读取(FileInputStream, FileChannel, FileDescriptor, File)没法指定超时时间, 而且IO操作均涉及到本地方法调用, 这个更操作了JVM的控制范围, 在分布式文件系统中, 对IO的操作内部实际上是网络调用. 一般情况下操作60s的操作都可以认为已经超时了. 为了解决这些问题, 一般采用缓存和异步/消息队列处理. 频繁使用计时器 错误代码:
for (...) { long t = System.currentTimeMillis(); long t = System.nanoTime(); Date d = new Date(); Calendar c = new GregorianCalendar(); }
每次new一个Date或Calendar都会涉及一次本地调用来获取当前时间(尽管这个本地调用相对其他本地方法调用要快). 如果对时间不是特别敏感, 这里使用了clone方法来新建一个Date实例. 这样相对直接new要高效一些. 正确的写法:
Date d = new Date(); for (E entity : entities) { entity.doSomething(); entity.setUpdated((Date) d.clone()); }
如果循环操作耗时较长(超过几ms), 那么可以采用下面的方法, 立即创建一个Timer, 然后定期根据当前时间更新时间戳, 在我的系统上比直接new一个时间对象快200倍:
private volatile long time; Timer timer = new Timer(true); try { time = System.currentTimeMillis(); timer.scheduleAtFixedRate(new TimerTask() { public void run() { time = System.currentTimeMillis(); } }, 0L, 10L); // granularity 10ms for (E entity : entities) { entity.doSomething(); entity.setUpdated(new Date(time)); } } finally { timer.cancel(); }
捕获所有的异常 错误的写法:
Query q = ... Person p; try { p = (Person) q.getSingleResult(); } catch(Exception e) { p = null; }
这是EJB3的一个查询操作, 可能出现异常的原因是: 结果不唯一; 没有结果; 数据库无法访问, 而捕获所有的异常, 设置为null将掩盖各种异常情况. 正确的写法:
Query q = ... Person p; try { p = (Person) q.getSingleResult(); } catch(NoResultException e) { p = null; }
忽略所有异常
try { doStuff(); } catch(Exception e) { log.fatal("Could not do stuff"); } doMoreStuff();
这个代码有两个问题, 一个是没有告诉调用者, 系统调用出错了. 第二个是日志没有出错原因, 很难跟踪定位问题. 正确的写法:
try { doStuff(); } catch(Exception e) { throw new MyRuntimeException("Could not do stuff because: "+ e.getMessage, e); }
重复包装RuntimeException 错误的写法:
try { doStuff(); } catch(Exception e) { throw new RuntimeException(e); }
正确的写法:
try { doStuff(); } catch(RuntimeException e) { throw e; } catch(Exception e) { throw new RuntimeException(e.getMessage(), e); } try { doStuff(); } catch(IOException e) { throw new RuntimeException(e.getMessage(), e); } catch(NamingException e) { throw new RuntimeException(e.getMessage(), e); }
不正确的传播异常 错误的写法:
try { } catch(ParseException e) { throw new RuntimeException(); throw new RuntimeException(e.toString()); throw new RuntimeException(e.getMessage()); throw new RuntimeException(e); }
主要是没有正确的将内部的错误信息传递给调用者. 第一个完全丢掉了内部错误信息, 第二个错误信息依赖toString方法, 如果没有包含最终的嵌套错误信息, 也会出现丢失, 而且可读性差. 第三个稍微好一些, 第四个跟第二个一样. 正确的写法:
try { } catch(ParseException e) { throw new RuntimeException(e.getMessage(), e); }
用日志记录异常 错误的写法:
try { ... } catch(ExceptionA e) { log.error(e.getMessage(), e); throw e; } catch(ExceptionB e) { log.error(e.getMessage(), e); throw e; }
一般情况下在日志中记录异常是不必要的, 除非调用方没有记录日志.
异常处理不彻底 错误的写法:
try { is = new FileInputStream(inFile); os = new FileOutputStream(outFile); } finally { try { is.close(); os.close(); } catch(IOException e) { /* we can't do anything */ } }
is可能close失败, 导致os没有close 正确的写法:
try { is = new FileInputStream(inFile); os = new FileOutputStream(outFile); } finally { try { if (is != null) is.close(); } catch(IOException e) {/* we can't do anything */} try { if (os != null) os.close(); } catch(IOException e) {/* we can't do anything */} }
捕获不可能出现的异常 错误的写法:
try { ... do risky stuff ... } catch(SomeException e) { // never happens } ... do some more ...
正确的写法:
try { ... do risky stuff ... } catch(SomeException e) { // never happens hopefully throw new IllegalStateException(e.getMessage(), e); // crash early, passing all information } ... do some more ...
transient的误用 错误的写法:
public class A implements Serializable { private String someState; private transient Log log = LogFactory.getLog(getClass()); public void f() { log.debug("enter f"); ... } }
这里的本意是不希望Log对象被序列化. 不过这里在反序列化时, 会因为log未初始化, 导致f()方法抛空指针, 正确的做法是将log定义为静态变量或者定位为具备变量. 正确的写法:
public class A implements Serializable { private String someState; private static final Log log = LogFactory.getLog(A.class); public void f() { log.debug("enter f"); ... } } public class A implements Serializable { private String someState; public void f() { Log log = LogFactory.getLog(getClass()); log.debug("enter f"); ... } }
不必要的初始化 错误的写法:
public class B { private int count = 0; private String name = null; private boolean important = false; }
这里的变量会在初始化时使用默认值:0, null, false, 因此上面的写法有些多此一举. 正确的写法:
public class B { private int count; private String name; private boolean important; }
最好用静态final定义Log变量
private static final Log log = LogFactory.getLog(MyClass.class);
这样做的好处有三:
可以保证线程安全 静态或非静态代码都可用 不会影响对象序列化
选择错误的类加载器 错误的代码: Class clazz = Class.forName(name); Class clazz = getClass().getClassLoader().loadClass(name);
这里本意是希望用当前类来加载希望的对象, 但是这里的getClass()可能抛出异常, 特别在一些受管理的环境中, 比如应用服务器, web容器, Java WebStart环境中, 最好的做法是使用当前应用上下文的类加载器来加载. 正确的写法:
ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) cl = MyClass.class.getClassLoader(); // fallback Class clazz = cl.loadClass(name);
反射使用不当 错误的写法:
Class beanClass = ... if (beanClass.newInstance() instanceof TestBean) ...
这里的本意是检查beanClass是否是TestBean或是其子类, 但是创建一个类实例可能没那么简单, 首先实例化一个对象会带来一定的消耗, 另外有可能类没有定义默认构造函数. 正确的做法是用Class.isAssignableFrom(Class) 方法. 正确的写法:
Class beanClass = ... if (TestBean.class.isAssignableFrom(beanClass)) ...
不必要的同步 错误的写法:
Collection l = new Vector(); for (...) { l.add(object); }
Vector是ArrayList同步版本. 正确的写法:
Collection l = new ArrayList(); for (...) { l.add(object); }
错误的选择List类型 根据下面的表格数据来进行选择
| ArrayList | LinkedList | add (append) | O(1) or ~O(log(n)) if growing | O(1) | insert (middle) | O(n) or ~O(n*log(n)) if growing | O(n) | remove (middle) | O(n) (always performs complete copy) | O(n) | iterate | O(n) | O(n) | get by index | O(1) | O(n) |
输入编号 211 可以获得后续文章
●本文编号210,以后想阅读这篇文章直接输入210即可。 ●输入m可以获取到文章目录
更多推荐《15个技术类公众微信》
涵盖:程序人生、算法与数据结构、黑客技术与网络安全、大数据技术、前端开发、Java、Python、Web开发、安卓开发、iOS开发、C/C++、.NET、Linux、数据库、运维等。传播计算机学习经验、推荐计算机优秀资源:点击前往《值得关注的15个技术类微信公众号》!
|