收藏 分享(赏)

Java编程最差实践.doc

上传人:dreamzhangning 文档编号:2273827 上传时间:2018-09-09 格式:DOC 页数:31 大小:677.50KB
下载 相关 举报
Java编程最差实践.doc_第1页
第1页 / 共31页
Java编程最差实践.doc_第2页
第2页 / 共31页
Java编程最差实践.doc_第3页
第3页 / 共31页
Java编程最差实践.doc_第4页
第4页 / 共31页
Java编程最差实践.doc_第5页
第5页 / 共31页
点击查看更多>>
资源描述

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

2、e 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(“N

3、ame: “); 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. StringBu

4、ilder 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 (pareTo(“John“) = 0) . 2. if (name = “John“) . 3. if (name.equals(“John“) . 4. if (“.equa

5、ls(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(

6、) 利用不可变对象(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(“) + “.length(); 2. int end = xml.indexOf(“); 3. String name = xml.substring(sta

7、rt, 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 = “ 4. +“+ na

8、me +“ 5. +“; 正确的写法: 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

9、 编码陷阱 错误的写法: 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(

10、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 Fil

11、eInputStream(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.

12、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

13、 包装之后只需要 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 的方式边读取边存储(本地文件或 d

14、atabase). 正确的写法: 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 可以指定超时时间, 但是 r

15、ead 无法指定超时时间. 但是可以设置阻塞(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 操作均涉

16、及到本地方法调用, 这个更操作了 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

17、 或 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, 然后定期根

18、据当前时间更新时间戳 , 在我的系统上比直接 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

19、 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 的一个查询操作, 可能出现异常的原因是: 结果不

20、唯一; 没有结果; 数据库无法访问, 而捕获所有的异常, 设置为 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(); 这个代

21、码有两个问题, 一个是没有告诉调用者 , 系统调用出错了 . 第二个是日志没有出错原因, 很难跟踪定位问题 . 正确的写法: 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 RuntimeE

22、xception(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. th

23、row 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. 主要是没有正确的将内部的错误信息传递给调用者. 第一个完全丢掉了内

24、部错误信息, 第二个错误信息依赖 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(Ex

25、ceptionB e) 7. log.error(e.getMessage(), e); 8. throw e; 9. 一般情况下在日志中记录异常是不必要的, 除非调用方没有记录日志. 异常处理不彻底 错误的写法: Java 代码 try is = new FileInputStream(inFile); os = new FileOutputStream(outFile); finally try is.close(); os.close(); catch(IOException e) /* we cant do anything */ is 可能 close 失败, 导致 os 没有 cl

26、ose 正确的写法: 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 cant do anything */ 6. try if (os != null) os.close(); catch(IOException e) /* we cant do anything */ 7. 捕获不可能出现的异常 错误的写法: Ja

27、va 代码 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

28、. . do some more . transient 的误用 错误的写法: Java 代码 1. public class A implements Serializable 2. private String someState; 3. private transient Log log = LogFactory.getLog(getClass(); 4. 5. public void f() 6. log.debug(“enter f“); 7. . 8. 9. 这里的本意是不希望 Log 对象被序列化. 不过这里在反序列化时, 会因为 log 未初始化, 导致 f()方法抛空指针,

29、正确的做法是将 log 定义为静态变量或者定位为具备变量. 正确的写法: Java 代码 1. public class A implements Serializable 2. private String someState; 3. private static final Log log = LogFactory.getLog(A.class); 4. 5. public void f() 6. log.debug(“enter f“); 7. . 8. 9. 10. public class A implements Serializable 11. private String so

30、meState; 12. 13. public void f() 14. Log log = LogFactory.getLog(getClass(); 15. log.debug(“enter f“); 16. . 17. 18. 不必要的初始化 错误的写法: Java 代码 1. public class B 2. private int count = 0; 3. private String name = null; 4. private boolean important = false; 5. 这里的变量会在初始化时使用默认值:0, null, false, 因此上面的写法有些多此

31、一举. 正确的写法: 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 = Clas

32、s.forName(name); 2. Class clazz = getClass().getClassLoader().loadClass(name); 这里本意是希望用当前类来加载希望的对象, 但是这里的 getClass()可能抛出异常, 特别在一些受管理的环境中, 比如应用服务器, web 容器, Java WebStart 环境中, 最好的做法是使用当前应用上下文的类加载器来加载. 正确的写法: Java 代码 1. ClassLoader cl = Thread.currentThread().getContextClassLoader(); 2. if (cl = null)

33、cl = MyClass.class.getClassLoader(); / fallback 3. Class clazz = cl.loadClass(name); 反射使用不当 错误的写法: Java 代码 1. Class beanClass = . 2. if (beanClass.newInstance() instanceof TestBean) . 这里的本意是检查 beanClass 是否是 TestBean 或是其子类, 但是创建一个类实例可能没那么简单, 首先实例化一个对象会带来一定的消耗, 另外有可能类没有定义默认构造函数. 正确的做法是用 Class.isAssign

34、ableFrom(Class) 方法. 正确的写法: Java 代码 1. Class beanClass = . 2. if (TestBean.class.isAssignableFrom(beanClass) . 不必要的同步 错误的写法: Java 代码 1. Collection l = new Vector(); 2. for (.) 3. l.add(object); 4. Vector 是 ArrayList 同步版本. 正确的写法: Java 代码 1. Collection l = new ArrayList(); 2. for (.) 3. l.add(object);

35、4. 错误的选择 List 类型 根据下面的表格数据来进行选择 ArrayList LinkedListadd (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)HashMap size 陷阱 错误的写法: Java 代码 1. Map map = new HashMap(c

36、ollection.size(); 2. for (Object o : collection) 3. map.put(o.key, o.value); 4. 这里可以参考 guava 的 Maps.newHashMapWithExpectedSize 的实现. 用户的本意是希望给 HashMap 设置初始值, 避免扩容(resize)的开销. 但是没有考虑当添加的元素数量达到 HashMap 容量的 75%时将出现 resize. 正确的写法: Java 代码 1. Map map = new HashMap(1 + (int) (collection.size() / 0.75); 对 H

37、ashtable, HashMap 和 HashSet 了解不够 这里主要需要了解 HashMap 和 Hashtable 的内部实现上, 它们都使用 Entry 包装来封装 key/value, Entry 内部除了要保存 Key/Value 的引用, 还需要保存 hash 桶中 next Entry 的应用, 因此对内存会有不小的开销, 而 HashSet 内部实现其实就是一个 HashMap. 有时候 IdentityHashMap 可以作为一个不错的替代方案. 它在内存使用上更有效(没有用 Entry 封装, 内部采用 Object). 不过需要小心使用. 它的实现违背了 Map 接口

38、的定义. 有时候也可以用ArrayList 来替换 HashSet. 这一切的根源都是由于 JDK 内部没有提供一套高效的 Map 和 Set 实现. 对 List 的误用 建议下列场景用 Array 来替代 List: list 长度固定, 比如一周中的每一天 对 list 频繁的遍历, 比如超过 1w 次 需要对数字进行包装(主要 JDK 没有提供基本类型的 List)比如下面的代码. 错误的写法: Java 代码 3. List codes = new ArrayList(); 4. codes.add(Integer.valueOf(10); 5. codes.add(Integer.

39、valueOf(20); 6. codes.add(Integer.valueOf(30); 7. codes.add(Integer.valueOf(40); 正确的写法: Java 代码 4. int codes = 10, 20, 30, 40 ; 错误的写法: Java 代码 3. / horribly slow and a memory waster if l has a few thousand elements (try it yourself!) 4. List l = .; 5. for (int i=0; i j = l.iterator(i+1); / memory al

40、location! 8. while (j.hasNext() 9. Mergeable other = l.next(); 10. if (one.canMergeWith(other) 11. one.merge(other); 12. other.remove(); 13. 14. 15. 正确的写法: Java 代码 3. / quite fast and no memory allocation 4. Mergeable l = .; 5. for (int i=0; i Integer.MAX_VALUE) throw new IllegalStateException(“int

41、overflow“); 4. return (int) l; 5. 另一个溢出 bug 是 cast 的对象不对, 比如下面第一个 println. 正确的应该是下面的那个. Java 代码 1. long a = System.currentTimeMillis(); 2. long b = a + 100; 3. System.out.println(int) b-a); 4. System.out.println(int) (b-a); 对 float 和 double 使用=操作 错误的写法: Java 代码 1. for (float f = 10f; f!=0; f-=0.1) 2

42、. System.out.println(f); 3. 上面的浮点数递减只会无限接近 0 而不会等于 0, 这样会导致上面的 for 进入死循环. 通常绝不要对 float 和 double 使用=操作. 而采用大于和小于操作. 如果 java 编译器能针对这种情况给出警告. 或者在 java 语言规范中不支持浮点数类型的=操作就最好了. 正确的写法: Java 代码 1. for (float f = 10f; f0; f-=0.1) 2. System.out.println(f); 3. 用浮点数来保存 money 错误的写法: Java 代码 1. float total = 0.0f

43、; 2. for (OrderLine line : lines) 3. total += line.price * line.count; 4. 5. double a = 1.14 * 75; / 85.5 将表示为 85.4999. 6. System.out.println(Math.round(a); / 输出值为 85 7. BigDecimal d = new BigDecimal(1.14); /造成精度丢失 这个也是一个老生常谈的错误. 比如计算 100 笔订单, 每笔 0.3 元, 最终的计算结果是 29.9999971. 如果将 float 类型改为 double 类型,

44、 得到的结果将是 30.000001192092896. 出现这种情况的原因是, 人类和计算的计数方式不同. 人类采用的是十进制, 而计算机是二进制.二进制对于计算机来说非常好使, 但是对于涉及到精确计算的场景就会带来误差 . 比如银行金融中的应用. 因此绝不要用浮点类型来保存 money 数据. 采用浮点数得到的计算结果是不精确的. 即使与 int 类型做乘法运算也会产生一个不精确的结果.那是因为在用二进制存储一个浮点数时已经出现了精度丢失. 最好的做法就是用一个string 或者固定点数来表示. 为了精确, 这种表示方式需要指定相应的精度值. BigDecimal 就满足了上面所说的需求.

45、 如果在计算的过程中精度的丢失超出了给定的范围, 将抛出 runtime exception. 正确的写法: Java 代码 1. BigDecimal total = BigDecimal.ZERO; 2. for (OrderLine line : lines) 3. BigDecimal price = new BigDecimal(line.price); 4. BigDecimal count = new BigDecimal(line.count); 5. total = total.add(price.multiply(count); / BigDecimal is immuta

46、ble! 6. 7. total = total.setScale(2, RoundingMode.HALF_UP); 8. BigDecimal a = (new BigDecimal(“1.14“).multiply(new BigDecimal(75); / 85.5 exact 9. a = a.setScale(0, RoundingMode.HALF_UP); / 86 10. System.out.println(a); / correct output: 86 11. BigDecimal a = new BigDecimal(“1.14“); 不使用 finally 块释放资

47、源 错误的写法: Java 代码 1. public void save(File f) throws IOException 2. OutputStream out = new BufferedOutputStream(new FileOutputStream(f); 3. out.write(.); 4. out.close(); 5. 6. public void load(File f) throws IOException 7. InputStream in = new BufferedInputStream(new FileInputStream(f); 8. in.read(.); 9. in.close(); 10. 上面的代码打开一个文件输出流, 操作系统为其分配一个文件句柄, 但是文件句柄是一种非常稀缺的资源, 必须通过调用相应的 close 方法来被正确的释放回收. 而为了保证在异常情况下资源依然能被正确回收, 必须将其放在finally block 中 . 上面的代码中使用了 BufferedInputStream 将 file stream 包装成了一个 buffer stream, 这样将导致在调用 close 方法时才会将 buf

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 大学课件

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报