收藏 分享(赏)

Java编程:常见问题汇总.doc

上传人:dreamzhangning 文档编号:2732462 上传时间:2018-09-26 格式:DOC 页数:27 大小:255.55KB
下载 相关 举报
Java编程:常见问题汇总.doc_第1页
第1页 / 共27页
Java编程:常见问题汇总.doc_第2页
第2页 / 共27页
Java编程:常见问题汇总.doc_第3页
第3页 / 共27页
Java编程:常见问题汇总.doc_第4页
第4页 / 共27页
Java编程:常见问题汇总.doc_第5页
第5页 / 共27页
点击查看更多>>
资源描述

1、 软件英才网 软件行业驰名招聘网站有需要请联系我们Java 编程:常见问题汇总每天在写 Java 程序,其实里面有一些细节大家可能没怎么注意,这不,有人总结了一个我们编程中常见的问题。虽然一般没有什么大问题,但是最好别这样做。另外这里提到的很多问题其实可以通过 Findbugs( http:/ )来帮我们进行检查出来。字符串连接误用错误的写法: String s = “; for (Person p : persons) s += “, “ + p.getName(); s = s.substring(2); /remove first comma 正确的写法: StringBuilder s

2、b = 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();

3、问题在第三行,append char 比 String 性能要好,另外就是初始化 StringBuffer 没有指定 size,导致中间 append 时可能重新调整内部数组大小。如果是 JDK1.5最好用StringBuilder 取代 StringBuffer,除非有线程安全的要求。还有一种方式就是可以直接连接字符串。缺点就是无法初始化时指定长度。软件英才网 软件行业驰名招聘网站有需要请联系我们正确的写法: StringBuilder sb = new StringBuilder(100); sb.append(“Name: “); sb.append(name); sb.append(“

4、n!“); String s = sb.toString(); 或者这样写: String s = “Name: “ + name + “n!“; 测试字符串相等性错误的写法: if (pareTo(“John“) = 0) . if (name = “John“) . if (name.equals(“John“) . if (“.equals(name) . 上面的代码没有错,但是不够好。compareTo 不够简洁,=原义是比较两个对象是否一样。另外比较字符是否为空,最好判断它的长度。正确的写法: if (“John“.equals(name) . if (name.length() =

5、 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 解析器错误的写法:

6、 int start = xml.indexOf(“) + “.length(); int end = xml.indexOf(“); 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错误的写

7、法: String name = . String attribute = . String xml = “ +“+ name +“ +“; 正确的写法: 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

8、= out.outputString(root); XML 编码陷阱错误的写法: String xml = FileUtils.readTextFile(“my.xml“); 因为 xml 的编码在文件中指定的,而在读文件的时候必须指定编码。另外一个问题不能一次就将一个 xml 文件用 String 保存,这样对内存会造成不必要的浪费,正确的做法用软件英才网 软件行业驰名招聘网站有需要请联系我们InputStream 来边读取边处理。为了解决编码的问题, 最好使用 XML 解析器来处理。未指定字符编码错误的写法: Reader r = new FileReader(file); Writer

9、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 FileInput

10、Stream(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“); 未对

11、数据流进行缓存错误的写法: 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

12、 操作。正确的写法: InputStream in = new BufferedInputStream(new FileInputStream(file); 软件英才网 软件行业驰名招聘网站有需要请联系我们无限使用 heap 内存错误的写法: byte pdf = toPdf(file); 这里有一个前提,就是文件大小不能讲 JVM 的 heap 撑爆。否则就等着 OOM 吧,尤其是在高并发的服务器端代码。最好的做法是采用 Stream 的方式边读取边存储(本地文件或database)。正确的写法: File pdf = toPdf(file); 另外,对于服务器端代码来说,为了系统的安全,至

13、少需要对文件的大小进行限制。不指定超时时间错误的代码: 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 In

14、putStream in = socket.getInputStream(); socket.setSoTimeout(15000); int i = in.read(); 另外,文件的读取(FileInputStream, FileChannel, FileDescriptor, File)没法指定超时时间, 而且 IO 操作均涉及到本地方法调用, 这个更操作了 JVM 的控制范围,在分布式文件系统中,对 IO 的操作内部实际上是网络调用。一般情况下操作60s 的操作都可以认为已经超时了。为了解决这些问题,一般采用缓存和异步/消息队列处理。频繁使用计时器软件英才网 软件行业驰名招聘网站有需要

15、请联系我们错误代码: 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

16、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(

17、) 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(Exce

18、ption 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(); 这个代码有两个问题

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

20、f(); 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

21、 e) throw new RuntimeException(); throw new RuntimeException(e.toString(); throw new RuntimeException(e.getMessage(); throw new RuntimeException(e); 主要是没有正确的将内部的错误信息传递给调用者. 第一个完全丢掉了内部错误信息, 第二个错误信息依赖 toString 方法, 如果没有包含最终的嵌套错误信息, 也会出现丢失, 而且可读性差. 第三个稍微好一些, 第四个跟第二个一样。软件英才网 软件行业驰名招聘网站有需要请联系我们正确的写法: try

22、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 = ne

23、w FileOutputStream(outFile); finally try is.close(); os.close(); catch(IOException e) /* we cant do anything */ is 可能 close 失败, 导致 os 没有 close正确的写法: try 软件英才网 软件行业驰名招聘网站有需要请联系我们 is = new FileInputStream(inFile); os = new FileOutputStream(outFile); finally try if (is != null) is.close(); catch(IOExce

24、ption e) /* we cant do anything */ try if (os != null) os.close(); catch(IOException e) /* we cant 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 I

25、llegalStateException(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“); . 软件英才网 软件行业驰名招聘网站有需要请

26、联系我们这里的本意是不希望 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 Serializ

27、able 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 priva

28、te 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

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

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

31、 . 软件英才网 软件行业驰名招聘网站有需要请联系我们不必要的同步错误的写法: Collection l = new Vector(); for (.) l.add(object); Vector 是 ArrayList 同步版本。正确的写法: Collection l = new ArrayList(); for (.) l.add(object); 错误的选择 List 类型根据下面的表格数据来进行选择ArrayList LinkedListadd (append) O(1) or O(log(n) if growing O(1)insert (middle) O(n) or O(n*lo

32、g(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 陷阱错误的写法: Map map = new HashMap(collection.size(); for (Object o : collection) map.put(o.key, o.value); 这里可以参考 guava 的 Maps.newHashMapWithExpectedSize 的实现. 用户的本意是希望给 HashMa

33、p 设置初始值, 避免扩容(resize)的开销. 但是没有考虑当添加的元素数量达到 HashMap 容量的75%时将出现 resize。正确的写法:软件英才网 软件行业驰名招聘网站有需要请联系我们 Map map = new HashMap(1 + (int) (collection.size() / 0.75); 对 Hashtable, HashMap 和 HashSet 了解不够这里主要需要了解 HashMap 和 Hashtable 的内部实现上, 它们都使用 Entry 包装来封装 key/value, Entry 内部除了要保存 Key/Value 的引用, 还需要保存 hash

34、 桶中 next Entry 的应用, 因此对内存会有不小的开销, 而 HashSet 内部实现其实就是一个 HashMap. 有时候 IdentityHashMap 可以作为一个不错的替代方案. 它在内存使用上更有效(没有用Entry 封装, 内部采用 Object). 不过需要小心使用. 它的实现违背了 Map 接口的定义. 有时候也可以用 ArrayList 来替换 HashSet.这一切的根源都是由于 JDK 内部没有提供一套高效的 Map 和 Set 实现。对 List 的误用建议下列场景用 Array 来替代 List:list 长度固定,比如一周中的每一天对 list 频繁的遍历

35、,比如超过1w 次需要对数字进行包装(主要 JDK 没有提供基本类型的 List)比如下面的代码。错误的写法: List codes = new ArrayList(); codes.add(Integer.valueOf(10); 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

36、few thousand elements (try it yourself!) List l = .; for (int i=0; i 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

37、l = .; for (int i=0; i Integer.MAX_VALUE) throw new IllegalStateException(“int overflow“); return (int) l; 另一个溢出 bug 是 cast 的对象不对, 比如下面第一个 println. 正确的应该是下面的那个。 long a = System.currentTimeMillis(); long b = a + 100; System.out.println(int) b-a); System.out.println(int) (b-a); 对 float 和 double 使用=操作错

38、误的写法: for (float f = 10f; f!=0; f-=0.1) System.out.println(f); 软件英才网 软件行业驰名招聘网站有需要请联系我们上面的浮点数递减只会无限接近0而不会等于0, 这样会导致上面的 for 进入死循环. 通常绝不要对 float 和 double 使用=操作. 而采用大于和小于操作. 如果 java 编译器能针对这种情况给出警告. 或者在 java 语言规范中不支持浮点数类型的=操作就最好了。正确的写法: for (float f = 10f; f0; f-=0.1) System.out.println(f); 用浮点数来保存 mone

39、y错误的写法: float total = 0.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 类

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

41、如果在计算的过程中精度的丢失超出了给定的范围, 将抛出 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 immuta

42、ble! 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“); 不使用 finally 块释放资源错误的写法: public void

43、save(File f) throws IOException OutputStream out = new BufferedOutputStream(new FileOutputStream(f); out.write(.); out.close(); public void load(File f) throws IOException InputStream in = new BufferedInputStream(new FileInputStream(f); in.read(.); in.close(); 上面的代码打开一个文件输出流, 操作系统为其分配一个文件句柄, 但是文件句柄是

44、一种非常稀缺的资源, 必须通过调用相应的 close 方法来被正确的释放回收. 而为了保证在异常情况下资源依然能被正确回收, 必须将其放在 finally block 中. 上面的代码中使用了 BufferedInputStream 将 file stream 包装成了一个 buffer stream, 这样将导致在调用 close 方法时才会将 buffer stream 写入磁盘. 如果在 close 的时候失败, 将导致写入数据不完全. 而对于 FileInputStream 在 finally block 的 close 操作这里将直接忽略。如果 BufferedOutputStream.close()方法执行顺利则万事大吉,

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

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

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


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

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

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