java开发常见问题总结

来源:互联网 发布:康迪k10和知豆哪个好 编辑:程序博客网 时间:2024/06/05 00:50

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



..本文列举了我在周围同事的Java代码中看到的一些比较典型的错误。显然,静态代码分析(我们团队用的是qulice)不可能发现所有的问题,这也是为什么我要在这里列出它们的原因。


如果你觉得少了什么,请不吝赐教,我会很乐意把它们加上。


下面列出的所有这些错误基本都与面向对象编程有关,尤其是Java的OOP。


类名


读下这篇短文“什么是对象”。类应该是真实生活中的一个抽象实体,而不是什么“validators”,“controller”, “managers”这些东西。如果你的类名以”er”结尾的话——那它就是个糟糕的设计。


当然了,工具类也是反模式,比如说Apache的StringUtils, FileUtils, 以及IOUtils。上面这些都是糟糕设计的代表。延伸阅读:OOP中工具类的替代方案。


当然,不要使用前缀或者后缀来区分类和接口。比方说,这些名字就是错误的:IRecord, IfaceEmployee, 或者RecordInterface。通常来说,接口名应该是真实生活中的实体的名字,类名应该可以说明它的实现细节。如果这个实现没有什么特别可说明的,可以把它叫作Default, Simple或者类似的什么。比如说:


代码如下:
class SimpleUser implements User {};
class DefaultRecord implements Record {};
class Suffixed implements Name {};
class Validated implements Content {};


方法名


方法可以返回值也可以返回void。如果方法返回值的话,它的名字应该能说明它返回了什么,比如说(永远也不要使用get前缀):


代码如下:
boolean isValid(String name);
String content();
int ageOf(File file);


如果它返回void,那么它的名字应该要说明它做了什么。比如:


代码如下:
void save(File file);
void process(Work work);
void append(File file, String line);


刚才提到的这些规则只有一个例外——JUnit的test方法不算。下面将会说到这个。


test方法的名字


在JUnit的测试用例中,方法名应该是没有空格的英文语句。用一个例子来说明会更清楚一些:


代码如下:
/**
 * HttpRequest can return its content in Unicode.
 * @throws Exception If test fails
 */
public void returnsItsContentInUnicode() throws Exception {
}


你的JavaDoc里的第一句话的开头应该是你要测试的那个类的名字,然后是一个can。因此,你的第一句话应该是类似于“somebody can do something”。


方法名也是一样的,只是没有主题而已。如果我在方法名中间加一个主题的话,我就能得到一个完整的句子,正如上面那个例子中那样:“HttpRequest returns its content in unicode”。


请注意test方法的名字是不以can开头的。只有JavaDoc里的的注释会以can开头。除此之外,方法名不应该以动词开头。


实践中最好将测试方法声明为抛出Exception的。


变量名


避免组合的变量名,比如说timeOfDay, firstItem,或者httpRequest。类变量及方法内的变量都是如此。变量名应该足够长,避免在它的可见作用域内产生歧义,但是如果可以的话也不要太长。名字应该是单数或复数形式的名词,或者是一个适当的缩写。比如:


代码如下:
List<String> names;
void sendThroughProxy(File file, Protocol proto);
private File content;
public HttpRequest request;


有的时候,如果构造方法要将入参保存到一个新初始化的对象中的时候,它的参数和类属性的名字可能会冲突。这种情况,我建议是去掉元音,使用缩写。


示例:


代码如下:
public class Message {
  private String recipient;
  public Message(String rcpt) {
    this.recipient = rcpt;
  }
}


很多时候,看一下变量的类名就知道变量该取什么名字了。就用它的小写形式就好了,像这样就很靠谱:


代码如下:
File file;
User user;
Branch branch;


然而,基础类型的话,永远不要这么做,比如Integer number或者String string。


如果存在多个不同性质的变量的话,可以考虑下使用形容词。比如:


代码如下:
String contact(String left, String right);


构造方法


不考虑异常的话,应该只有一个构造方法用来将数据存储到对象变量中。其它构造方法则使用不同的参数来调用这个构造方法。比如说:


代码如下:
public class Server {
  private String address;
  public Server(String uri) {
    this.address = uri;
  }
  public Server(URI uri) {
    this(uri.toString());
  }
}


一次性变量


无论如何都应该避免使用一次性变量。这里我所说的“一次性“指的是只使用一次的变量。比如下面这个:


代码如下:
String name = "data.txt";
return new File(name);


上述的变量只会使用一次,因此这段代码可以重构成这样:


代码如下:
return new File("data.txt");


有的时候,比较罕见的情况中——主要是为了格式更好看些——可能会用到一次性变量。然而,还是应当尽量避免这种情况。


异常


毋庸赘言,永远不要自己吞掉异常,而是应该当它尽量往上传递。私有方法应该始终把受检查异常往外面抛。


不要使用异常来进行流程控制。比方说下面这段代码就是错误的:


代码如下:
int size;
try {
  size = this.fileSize();
} catch (IOException ex) {
  size = 0;
}


那如果IOException提示“磁盘已满”的话该怎么办?你还会认为这个文件大小为0,然后继续往下处理?


缩进


关于缩进,主要的规则就是左括号要么在该行的末尾,要么就在同一行上闭合(对于右括号来说则相反)。比如说,下面这个就不正确,因为第一个左括号没有在同一行上闭合,而它后面还有别的字符。第二个括号也有问题,因为它前面有字符,但对应的开括号又没在同一行上:


代码如下:
final File file = new File(directory,
  "file.txt");


正确的缩进应该是这样的:


代码如下:
StringUtils.join(
  Arrays.asList(
    "first line",
    "second line",
    StringUtils.join(
      Arrays.asList("a", "b")
    )
  ),
  "separator"
);


关于缩进,第二条重要的规则就是同时一行中应该尽量多写一些——上限是80个字符。上面的那个例子并不满足这点,它还可以收缩一下:


代码如下:
StringUtils.join(
  Arrays.asList(
    "first line", "second line",
    StringUtils.join(Arrays.asList("a", "b"))
  ),
  "separator"
);


多余的常量


当你希望在类的方法中共享信息的时候,应当使用类常量,这些信息应该是你这个类所特有的。不要把常量当作字符串或数值字面量的替代品来使用——这是非常糟糕的实践方式,它会对代码造成污染。常量(正如OOP中的任何对象一样)应当在真实世界中有它自己的含义。看下这些常量在真实生活中的意思是什么:


代码如下:
class Document {
  private static final String D_LETTER = "D"; // bad practice
  private static final String EXTENSION = ".doc"; // good practice
}


另一个常见的错误就是在单元测试中使用常量来避免测试方法中出现冗余的字符串或者数值的字面量。不要这么做!每个测试方法都应该有自己专属的输入值。


在每个新的测试方法中使用新的文本或者数值。它们是相互独立的。那么为什么它们还要共享同样的输入常量呢?


测试数据耦合


下面是测试方法中数据耦合的一个例子:


代码如下:
User user = new User("Jeff");
// maybe some other code here
MatcherAssert.assertThat(user.name(), Matchers.equalTo("Jeff"));


最后一行中,”Jeff”和第一行中的同一个字符串字面值发生了耦合。如果过了几个月,有人想把第三行这个值换一下,那么他还得花时间找出同一个方法中哪里也使用了这个”Jeff”。


为了避免这种情况,你最好还是引入一个变量。
========

Java程序员们最常犯的10个错误



1.将数组转化为列表


将数组转化为一个列表时,程序员们经常这样做:


List<String> list = Arrays.asList(arr);
Arrays.asList()会返回一个ArrayList对象,ArrayList类是Arrays的一个私有静态类,而不是java.util.ArrayList类,java.util.Arrays.ArrayList类有set()、get()、contains()方法,但是没有增加元素的方法,所以它的大小是固定的,想要创建一个真正的ArrayList类,你应该这样做:


ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));
ArrayList的构造方法可以接受一个集合类型,刚好它也是java.util.Arrays.ArrayList的超类。


2.判断一个数组是否包含一个值
程序员们经常这样做:
Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);
这段代码起作用,但是没有必要把一个数组转化成列表,转化为列表需要额外的时间。它可以像下面那样简单:
Arrays.asList(arr).contains(targetValue);
或者是:
for(String s:arr){
    if(s.equals(targetValue)){
        return true;
    }
}
return false;
第一种方法比第二种更容易读


3.在一个循环中删除一个列表中的元素
思考下面这一段在循环中删除多个元素的的代码


ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));
for(int i=0;i<list.size();i++){
    list.remove(i);
}
System.out.println(list);
输出结果是:


1
[b,d]
在这个方法中有一个严重的错误。当一个元素被删除时,列表的大小缩小并且下标变化,所以当你想要在一个循环中用下标删除多个元素的时候,它并不会正常的生效。


你也许知道在循环中正确的删除多个元素的方法是使用迭代,并且你知道java中的foreach循环看起来像一个迭代器,但实际上并不是。考虑一下下面的代码:
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));
for(String s:list){
    if(s.equals("a")){
        list.remove(s);
    }
}
它会抛出一个ConcurrentModificationException异常。
相反下面的显示正常:
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));
Iterator<String> iter = list.iterator();
while(iter.hasNext()){
        String s = iter.next();
        if(s.equals("a")){
            iter.remove();
    }
}
.next()必须在.remove()之前调用。在一个foreach循环中,编译器会使.next()在删除元素之后被调用,因此就会抛出ConcurrentModificationException异常,你也许希望看一下ArrayList.iterator()的源代码。


4.Hashtable与HashMap的对比


就算法而言,哈希表是数据结构的一个名字。但是在java中,这个数据结构的名字是HashMap。Hashtable与HashMap的一个重要不同点是Hashtable是同步的。所以你经常不需要Hashtable,相反HashMap经常会用到。
HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap
Top 10 questions about Map


5.在集合中使用原始类型


在Java中原始类型与无界通配符类型很容易混合在一起,拿Set来说,Set是一个原始类型,而Set<?>是无界的通配符类型。
考虑下面使用原始类型List作为参数的代码:


public static void add(List list,Object o){
    list.add(o);
}
pulbic static void main(String[] args){
    List<String> list = new ArrayList<String>();
    add(list,10);
    String s = list.get(0);
这段代码会抛出一个异常:


Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at ...
使用原生类型集合是危险的,这是因为原生类型集合跳过了泛型类型检查,并且不是安全的,在Set、Set<?>和Set<Object>中有很大的不同,具体请看Raw type vs. Unbounded wildcard和Type Erasure。


6.访问级别


程序员们经常使用public作为类字段的修饰符,可以很简单的通过引用得到值,但这是一个坏的设计,按照经验,分配给成员变量的访问级别应该尽可能的低。


public, default, protected, and private


7.ArrayList与LinkedList的对比


当程序员们不知道ArrayList与LinkedList的区别时,他们经常使用ArrayList,因为它看起来比较熟悉。然而,它们之前有巨大的性能差别。简而言之,如果有大量的增加删除操作并且没有很多的随机访问元素的操作,应该首先LinkedList。如果你刚接触它们,请查看 ArrayList vs. LinkedList来获得更多关于它们性能的信息。


8.可变与不可变


不可变对象有许多的优点,比如简单,安全等等。但是对于每一个不同的值都需要一个独立的对象,太多的对象可能会造成大量的垃圾回收。当选择可变与不可变时应该有一个平衡。
一般的,可变对象用来避免产生大量的中间对象。一个典型的例子是连接大量的字符串。如果你用一个不可变的字符串,你会产生很多需要进行垃圾回收的对象。这很浪费CPU的时间,使用可变对象是正确的解决方案(比如StringBuilder)。


String result="";
for(String s: arr){
    result = result + s;
}
有时在某些情况下也是需要可变对象的,比如将可变对象作为参数传入方法,你不用使用很多语句便可以得到多个结果。另外一个例子是排序和过滤:当然,你可以写一个方法来接收原始的集合,并且返回一个排好序的集合,但是那样对于大的集合就太浪费了。(来自StackOverFlow的dasblinkenlight’s的答案)


Why String is Immutable?


9.父类与子类的构造函数


这个编译期错误的出现是父类默认的构造方法未定义,在java中,如果一个类没有定义构造方法,编译器会默认的为这个类添加一个无参的构造方法。如果在父类中定义了构造方法,在这个例子中是Super(String s),编译器就不会添加默认的无参构造方法,这就是上面这个父类的情形。
子类的构造器,不管是无参还有有参,都会调用父类的无参构造器。因为编译器试图在子类的两个构造方法中添加super()方法。但是父类默认的构造方法未定义,编译器就会报出这个错误信息。
想要修复这个问题,可以简单的通过1)在父类中添加一个Super()构造方法,像下面这样:


public Super(){
    System.out.println("Super");
}
或者2)移除父类自定义的构造方法,或者3)在子类的构造方法中调用父类的super(value)方法。


Constructor of Super and Stub


10.”"还是构造器


有两种方式可以创建字符串


//1.使用字符串
String x = "abc";
//2.使用构造器
String y = new String("abc");
有什么区别?
下面的例子会给出一个快速的答案:


String a = "abc";
String b = "abc";
System.out.println(a==b);//true
System.out.println(a.equals(b));//true
 
String c = new String("abc");
String d = new String("abc");
System.out.println(c==d);//false
System.out.println(c.equals(d));//true
关于它们内存分配的更多信息,请参考Create Java String Using ” ” or Constructor?.


将来的工作


这个列表是我基于大量的github上的开源项目,Stack overflow上的问题,还有一些流行的google搜索的分析。没有明显示的评估证明它们是前10,但它们绝对是很常见的。如果您不同意任一部分,请留下您的评论。如果您能提出其它一些常见的错误,我将会非常感激。


原文链接: programcreek 翻译: ImportNew.com - 林林
译文链接: http://www.importnew.com/12074.html
========

Java编程常见问题汇总



每天在写Java程序,其实里面有一些细节大家可能没怎么注意,这不,有人总结了一个我们编程中常见的问题。虽然一般没有什么大问题,但是最好别这样做。


每天在写Java程序,其实里面有一些细节大家可能没怎么注意,这不,有人总结了一个我们编程中常见的问题。虽然一般没有什么大问题,但是最好别这样做。另外这里提到的很多问题其实可以通过Findbugs( http://findbugs.sourceforge.net/ )来帮我们进行检查出来。


字符串连接误用


错误的写法:
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 growingO(1)
insert (middle) O(n) or ~O(n*log(n)) if growingO(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的实现. 用户的本意是希望给HashMap设置初始值, 避免扩容(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桶中next Entry的应用, 因此对内存会有不小的开销, 而HashSet内部实现其实就是一个HashMap. 有时候IdentityHashMap可以作为一个不错的替代方案. 它在内存使用上更有效(没有用Entry封装, 内部采用Object[]). 不过需要小心使用. 它的实现违背了Map接口的定义. 有时候也可以用ArrayList来替换HashSet.


这一切的根源都是由于JDK内部没有提供一套高效的Map和Set实现。


对List的误用


建议下列场景用Array来替代List:


list长度固定,比如一周中的每一天
对list频繁的遍历,比如超过1w次
需要对数字进行包装(主要JDK没有提供基本类型的List)
比如下面的代码。


错误的写法:


List<Integer> codes = new ArrayList<Integer>();  
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 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方法来执行排序。


用数组来描述一个结构


错误用法:


/**   
* @returns [1]: Location, [2]: Customer, [3]: Incident   
*/   
Object[] getDetails(int id) {...  


这里用数组+文档的方式来描述一个方法的返回值. 虽然很简单, 但是很容易误用, 正确的做法应该是定义个类。


正确的写法:


Details getDetails(int id) {...}   
private class Details {   
public Location location;   
public Customer customer;   
public Incident incident;   
}  
对方法过度限制


错误用法:


public void notify(Person p) {   
...   
sendMail(p.getName(), p.getFirstName(), p.getEmail());   
...   
}   
class PhoneBook {   
String lookup(String employeeId) {   
Employee emp = ...   
return emp.getPhone();   
}   
}  
第一个例子是对方法参数做了过多的限制, 第二个例子对方法的返回值做了太多的限制。


正确的写法:


public void notify(Person p) {   
...   
sendMail(p);   
...   
}   
class EmployeeDirectory {   
Employee lookup(String employeeId) {   
Employee emp = ...   
return emp;   
}   

对POJO的setter方法画蛇添足


错误的写法:


private String name;   
public void setName(String name) {   
this.name = name.trim();   
}   
public void String getName() {   
return this.name;   
}  
有时候我们很讨厌字符串首尾出现空格, 所以在setter方法中进行了trim处理, 但是这样做的结果带来的副作用会使getter方法的返回值和setter方法不一致, 如果只是将JavaBean当做一个数据容器, 那么最好不要包含任何业务逻辑. 而将业务逻辑放到专门的业务层或者控制层中处理。


正确的做法:


person.setName(textInput.getText().trim());  
日历对象(Calendar)误用


错误的写法:


Calendar cal = new GregorianCalender(TimeZone.getTimeZone("Europe/Zurich"));   
cal.setTime(date);   
cal.add(Calendar.HOUR_OF_DAY, 8);   
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  
TimeZone的误用


错误的写法:


Calendar cal = new GregorianCalendar();   
cal.setTime(date);   
cal.set(Calendar.HOUR_OF_DAY, 0);   
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();  
时区(Time Zone)调整的误用


错误的写法:


public static Date convertTz(Date date, TimeZone tz) {   
Calendar cal = Calendar.getInstance();   
cal.setTimeZone(TimeZone.getTimeZone("UTC"));   
cal.setTime(date);   
cal.setTimeZone(tz);   
return cal.getTime();   
}  
这个方法实际上没有改变时间, 输入和输出是一样的. 关于时间的问题可以参考这篇文章: http://www.odi.ch/prog/design/datetime.php 这里主要的问题是Date对象并不包含Time Zone信息. 它总是使用UTC(世界统一时间). 而调用Calendar的getTime/setTime方法会自动在当前时区和UTC之间做转换。


Calendar.getInstance()的误用


错误的写法:


Calendar c = Calendar.getInstance();   
c.set(2009, Calendar.JANUARY, 15);  
Calendar.getInstance()依赖local来选择一个Calendar实现, 不同实现的2009年是不同的, 比如有些Calendar实现就没有January月份。


正确的写法:


Calendar c = new GregorianCalendar(timeZone);   
c.set(2009, Calendar.JANUARY, 15);  
Date.setTime()的误用


错误的写法:


account.changePassword(oldPass, newPass);   
Date lastmod = account.getLastModified();   
lastmod.setTime(System.currentTimeMillis());  
在更新密码之后, 修改一下最后更新时间, 这里的用法没有错,但是有更好的做法: 直接传Date对象. 因为Date是Value Object, 不可变的. 如果更新了Date的值, 实际上是生成一个新的Date实例. 这样其他地方用到的实际上不在是原来的对象, 这样可能出现不可预知的异常. 当然这里又涉及到另外一个OO设计的问题, 对外暴露Date实例本身就是不好的做法(一般的做法是在setter方法中设置Date引用参数的clone对象). 另外一种比较好的做法就是直接保存long类型的毫秒数。


正确的做法:


account.changePassword(oldPass, newPass);   
account.setLastModified(new Date());  
SimpleDateFormat非线程安全误用


错误的写法:


public class Constants {   
public static final SimpleDateFormat date = new SimpleDateFormat("dd.MM.yyyy");   
}  
SimpleDateFormat不是线程安全的. 在多线程并行处理的情况下, 会得到非预期的值. 这个错误非常普遍! 如果真要在多线程环境下公用同一个SimpleDateFormat, 那么做好做好同步(cache flush, lock contention), 但是这样会搞得更复杂, 还不如直接new一个实在。


使用全局参数配置常量类/接口


public interface Constants {   
String version = "1.0";   
String dateFormat = "dd.MM.yyyy";   
String configFile = ".apprc";   
int maxNameLength = 32;   
String someQuery = "SELECT * FROM ...";   
}  
很多应用都会定义这样一个全局常量类或接口, 但是为什么这种做法不推荐? 因为这些常量之间基本没有任何关联, 只是因为公用才定义在一起. 但是如果其他组件需要使用这些全局变量, 则必须对该常量类产生依赖, 特别是存在server和远程client调用的场景。


比较好的做法是将这些常量定义在组件内部. 或者局限在一个类库内部。


忽略造型溢出(cast overflow)


错误的写法:


public int getFileSize(File f) {   
long l = f.length();   
return (int) l;   
}  
这个方法的本意是不支持传递超过2GB的文件. 最好的做法是对长度进行检查, 溢出时抛出异常。


正确的写法:


public int getFileSize(File f) {   
long l = f.length();   
if (l > 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使用==操作


错误的写法:


for (float f = 10f; f!=0; 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);   
}  
用浮点数来保存money


错误的写法:


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类型, 得到的结果将是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");  
不使用finally块释放资源


错误的写法:


public void 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();   
}  
上面的代码打开一个文件输出流, 操作系统为其分配一个文件句柄, 但是文件句柄是一种非常稀缺的资源, 必须通过调用相应的close方法来被正确的释放回收. 而为了保证在异常情况下资源依然能被正确回收, 必须将其放在finally block中. 上面的代码中使用了BufferedInputStream将file stream包装成了一个buffer stream, 这样将导致在调用close方法时才会将buffer stream写入磁盘. 如果在close的时候失败, 将导致写入数据不完全. 而对于FileInputStream在finally block的close操作这里将直接忽略。


如果BufferedOutputStream.close()方法执行顺利则万事大吉, 如果失败这里有一个潜在的bug(http://bugs.sun.com/view_bug.do?bug_id=6335274): 在close方法内部调用flush操作的时候, 如果出现异常, 将直接忽略. 因此为了尽量减少数据丢失, 在执行close之前显式的调用flush操作。


下面的代码有一个小小的瑕疵: 如果分配file stream成功, 但是分配buffer stream失败(OOM这种场景), 将导致文件句柄未被正确释放. 不过这种情况一般不用担心, 因为JVM的gc将帮助我们做清理。


// code for your cookbook   
public void save() throws IOException {   
File f = ...   
OutputStream out = new BufferedOutputStream(new FileOutputStream(f));   
try {   
out.write(...);   
out.flush(); // don't lose exception by implicit flush on close   
} finally {   
out.close();   
}   
}   
public void load(File f) throws IOException {   
InputStream in = new BufferedInputStream(new FileInputStream(f));   
try {   
in.read(...);   
} finally {   
try { in.close(); } catch (IOException e) { }   
}   
}  
数据库访问也涉及到类似的情况:


Car getCar(DataSource ds, String plate) throws SQLException {   
Car car = null;   
Connection c = null;   
PreparedStatement s = null;   
ResultSet rs = null;   
try {   
c = ds.getConnection();   
s = c.prepareStatement("select make, color from cars where plate=?");   
s.setString(1, 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;   
}  
finalize方法误用


错误的写法:


public class FileBackedCache {   
private File backingStore;   
 
...   
 
protected void finalize() throws IOException {   
if (backingStore != null) {   
backingStore.close();   
backingStore = null;   
}   
}   
}  
这个问题Effective Java这本书有详细的说明. 主要是finalize方法依赖于GC的调用, 其调用时机可能是立马也可能是几天以后, 所以是不可预知的. 而JDK的API文档中对这一点有误导:建议在该方法中来释放I/O资源。


正确的做法是定义一个close方法, 然后由外部的容器来负责调用释放资源。


public class FileBackedCache {   
private File backingStore;   
 
...   
 
public void close() throws IOException {   
if (backingStore != null) {   
backingStore.close();   
backingStore = null;   
}   
}   
}  
在JDK 1.7 (Java 7)中已经引入了一个AutoClosable接口. 当变量(不是对象)超出了try-catch的资源使用范围, 将自动调用close方法。


try (Writer w = new FileWriter(f)) { // implements Closable   
w.write("abc");   
// w goes out of scope here: w.close() is called automatically in ANY case   
} catch (IOException e) {   
throw new RuntimeException(e.getMessage(), e);   
}  
Thread.interrupted方法误用


错误的写法:


try {   
Thread.sleep(1000);   
} 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();   
}   
}  
原文链接:http://macrochen.iteye.com/blog/1393502
========

Java编程中最容易忽略的10个问题



在Java编码中,我们容易犯一些错误,也容易疏忽一些问题,因此笔者对日常编码中曾遇到的一些经典情形归纳整理成文,以共同探讨。


1. 纠结的同名


现象


很多类的命名相同(例如:常见于异常、常量、日志等类),导致在import时,有时候张冠李戴,这种错误有时候很隐蔽。因为往往同名的类功能也类似,所以IDE不会提示warn。


解决


写完代码时,扫视下import部分,看看有没有不熟悉的。替换成正确导入后,要注意下注释是否也作相应修改。


启示


命名尽量避开重复名,特别要避开与JDK中的类重名,否则容易导入错,同时存在大量重名类,在查找时,也需要更多的辨别时间。


2. 想当然的API


现象


有时候调用API时,会想当然的通过名字直接自信满满地调用,导致很惊讶的一些错误:


示例一:flag是true?


boolean flag = Boolean.getBoolean("true");
可能老是false。


示例二:这是去年的今天吗(今年是2012年,不考虑闰年)?结果还是2012年:


Calendar calendar = GregorianCalendar.getInstance();
calendar.roll(Calendar.DAY_OF_YEAR, -365);
下面的才是去年:


calendar.add(Calendar.DAY_OF_YEAR, -365);
解决办法


问自己几个问题,这个方法我很熟悉吗?有没有类似的API? 区别是什么?就示例一而言,需要区别的如下:


Boolean.valueOf(b) VS Boolean.parseBoolean(b) VS Boolean.getBoolean(b);
启示


名字起的更详细点,注释更清楚点,不要不经了解、测试就想当然的用一些API,如果时间有限,用自己最为熟悉的API。


3. 有时候溢出并不难


现象


有时候溢出并不难,虽然不常复现:


示例一:


long x=Integer.MAX_VALUE+1;
System.out.println(x);
x是多少?竟然是-2147483648,明明加上1之后还是long的范围。类似的经常出现在时间计算:


数字1×数字2×数字3… 
示例二:


在检查是否为正数的参数校验中,为了避免重载,选用参数number, 于是下面代码结果小于0,也是因为溢出导致:


Number i=Long.MAX_VALUE;
System.out.println(i.intValue()>0);
解决


让第一个操作数是long型,例如加上L或者l(不建议小写字母l,因为和数字1太相似了);
不确定时,还是使用重载吧,即使用doubleValue(),当参数是BigDecimal参数时,也不能解决问题。
启示


对数字运用要保持敏感:涉及数字计算就要考虑溢出;涉及除法就要考虑被除数是0;实在容纳不下了可以考虑BigDecimal之类。


4. 日志跑哪了?


现象


有时候觉得log都打了,怎么找不到?


示例一:没有stack trace!


 } catch (Exception ex) {
    log.error(ex);
 }
示例二:找不到log!


} catch (ConfigurationException e) {
    e.printStackTrace();
}
解决


替换成log.error(ex.getMessage(),ex);
换成普通的log4j吧,而不是System.out。
启示


API定义应该避免让人犯错,如果多加个重载的log.error(Exception)自然没有错误发生
在产品代码中,使用的一些方法要考虑是否有效,使用e.printStackTrace()要想下终端(Console)在哪。
5. 遗忘的Volatile


现象


在DCL模式中,总是忘记加一个Volatile。


private static CacheImpl instance;  //lose volatile
public static CacheImpl getInstance() {
    if (instance == null) {
        synchronized (CacheImpl.class) {
            if (instance == null) {
                instance = new CacheImpl (); 
            }
        }
    }
    return instance;
}
解决


毋庸置疑,加上一个吧,synchronized 锁的是一块代码(整个方法或某个代码块),保证的是这”块“代码的可见性及原子性,但是instance == null第一次判断时不再范围内的。所以可能读出的是过期的null。


启示


我们总是觉得某些低概率的事件很难发生,例如某个时间并发的可能性、某个异常抛出的可能性,所以不加控制,但是如果可以,还是按照前人的“最佳实践”来写代码吧。至少不用过多解释为啥另辟蹊径。


6. 不要影响彼此


现象


在释放多个IO资源时,都会抛出IOException ,于是可能为了省事如此写:


public static void inputToOutput(InputStream is, OutputStream os,
           boolean isClose) throws IOException {
    BufferedInputStream bis = new BufferedInputStream(is, 1024);
    BufferedOutputStream bos = new BufferedOutputStream(os, 1024);  
    ….
    if (isClose) {
       bos.close();
       bis.close();
    }
}
假设bos关闭失败,bis还能关闭吗?当然不能!


解决办法


虽然抛出的是同一个异常,但是还是各自捕获各的为好。否则第一个失败,后一个面就没有机会去释放资源了。


启示


代码/模块之间可能存在依赖,要充分识别对相互的依赖。


7. 用断言取代参数校验


现象


如题所提,作为防御式编程常用的方式:断言,写在产品代码中做参数校验等。例如:


private void send(List< Event> eventList)  {
    assert eventList != null;
}
解决


换成正常的统一的参数校验方法。因为断言默认是关闭的,所以起不起作用完全在于配置,如果采用默认配置,经历了eventList != null结果还没有起到作用,徒劳无功。


启示


有的时候,代码起不起作用,不仅在于用例,还在于配置,例如断言是否启用、log级别等,要结合真实环境做有用编码。


8. 用户认知负担有时候很重


现象


先来比较三组例子,看看那些看着更顺畅?


示例一:


public void caller(int a, String b, float c, String d) {
    methodOne(d, z, b);
    methodTwo(b, c, d);
}
public void methodOne(String d, float z, String b)  
public void methodTwo(String b, float c, String d)
示例二:


public boolean remove(String key, long timeout) {
             Future< Boolean> future = memcachedClient.delete(key);
public boolean delete(String key, long timeout) {
             Future< Boolean> future = memcachedClient.delete(key);
示例三:


public static String getDigest(String filePath, DigestAlgorithm algorithm)
public static String getDigest(String filePath, DigestAlgorithm digestAlgorithm)
解决


保持参数传递顺序;
remove变成了delete,显得突兀了点, 统一表达更好;
保持表达,少缩写也会看起来流畅点。
启示


在编码过程中,不管是参数的顺序还是命名都尽量统一,这样用户的认知负担会很少,不要要用户容易犯错或迷惑。例如用枚举代替string从而不让用户迷惑到底传什么string, 诸如此类。


9. 忽视日志记录时机、级别


现象


存在下面两则示例:


示例一:该不该记录日志?


catch (SocketException e)
{
    LOG.error("server error", e);
    throw new ConnectionException(e.getMessage(), e);
}
示例二:记什么级别日志?


在用户登录系统中,每次失败登录:


LOG.warn("Failed to login by "+username+");
解决


移除日志记录:在遇到需要re-throw的异常时,如果每个人都按照先记录后throw的方式去处理,那么对一个错误会记录太多的日志,所以不推荐如此做;但是如果re-throw出去的exception没有带完整的trace( 即cause),那么最好还是记录下。
如果恶意登录,那系统内部会出现太多WARN,从而让管理员误以为是代码错误。可以反馈用户以错误,但是不要记录用户错误的行为,除非想达到控制的目的。
启示


日志改不改记?记成什么级别?如何记?这些都是问题,一定要根据具体情况,需要考虑:


是用户行为错误还是代码错误?
记录下来的日志,能否能给别人在不造成过多的干扰前提下提供有用的信息以快速定位问题。
10. 忘设初始容量


现象


在JAVA中,我们常用Collection中的Map做Cache,但是我们经常会遗忘设置初始容量。


cache = new LRULinkedHashMap< K, V>(maxCapacity);
解决


初始容量的影响有多大?拿LinkedHashMap来说,初始容量如果不设置默认是16,超过16×LOAD_FACTOR,会resize(2 * table.length),扩大2倍:采用 Entry[] newTable = new Entry[newCapacity]; transfer(newTable),即整个数组Copy, 那么对于一个需要做大容量CACHE来说,从16变成一个很大的数量,需要做多少次数组复制可想而知。如果初始容量就设置很大,自然会减少resize, 不过可能会担心,初始容量设置很大时,没有Cache内容仍然会占用过大体积。其实可以参考以下表格简单计算下, 初始时还没有cache内容, 每个对象仅仅是4字节引用而已。


memory for reference fields (4 bytes each);
memory for primitive fields
Java type Bytes required
boolean 1
byte
char 2
short
int 4
float
long 8
double
启示


不仅是map, 还有stringBuffer等,都有容量resize的过程,如果数据量很大,就不能忽视初始容量可以考虑设置下,否则不仅有频繁的 resize还容易浪费容量。


在Java编程中,除了上面枚举的一些容易忽视的问题,日常实践中还存在很多。相信通过不断的总结和努力,可以将我们的程序完美呈现给读者。
========

Java编程语言中常见的编程错误预防提醒



1
学习编程时,可以通过“破坏”正确的程序来熟悉编译器的语法错误信息。这些消息并非总能指名代码中的确切问题。但是以后遇到类似的语法错误消息时,至少可以知道这以错误出现的原因。例如,图1程序中删除一个分号或者花括号,再次编译该程序,看看这样会產生什么错误。
2
当编译器报告一条错误语法时,该错误有时并不出现在错误消息所指示的行号上。因此,首先要检查报告错误的那一行。如果该行并没有任何语法错误,再检查前几行代码。
3
当编译某个程序时,如果得到类似这样的消息“badcommandorfilenam,javac:com-mandnotfound或者“javacisnotrecogniazasaninternorexterncommand,operprogramorbatchfile,表明没有正确安装Java软件。如果使用J2SE开发工具包,这表明没有正确设置系统的PA TH环境变量。对于某些系统而言,设置PA TH环境变量后,还必须重新啟动计算机或者打开一个新的命令窗口,才能使这些设置生效。
4
当程序的语法不正确时,Java编译器会发生语法错误消息。每条错误消息包含了发生错误的文件名和行号。例如,Welcome1.java6表明在Welcome1.java文件的第6行发生错误。该条错误信息中的其他内容提供了关于该语法错误的其他信息。
5
编译器错误消息“PublicclassClassNammustbedefininafilecallClassName.java.表明了该文件名与文件中的public类名无法正确匹配,或者在编译该类是没有正确输入类名.
6
运行某个Java程序时,如果出现类似是Exceptioninthreadmainjava.lang.NoClassDefFounfError:Welcome1,消息.有可能是CLA SSPA TH环境变量没有正确配置.
7
忘记為程序中使用的类加上相应的import声明,通常会导致编译错误,其消息中有“cannotre-solvsymbol字样。如果出现这样的错误,检查是否提供了正确的import声明,以及声明中的名字是否拼写正确,包括大小写是否正确。
========

Java编程中常见的错误有哪些?



sql错误
找不到列
Unknown column ‘fillMen’ in ‘field list’
2.列名在sql语句中写了2次
Column ‘fillMan’ specified twice
3.下面2条 数据库插入对应的列都是int类型的 所插入的数据类型不符合要求时报错
Data truncated for column ‘gatheringMoney’ at row 1
Data truncation: Out of range value adjusted for column ‘amount’ at row 1
4.
Mixing of GROUP columns (MIN(),MAX(),COUNT(),̷) with no GROUP columns is illegal if there is no GROUP BY clause
5.修改一个表时无法取得同一个表的数据
ERROR 193 (HY): You can’t specify target table ‘context’ for update in FROM
clause
6.主键 未自动增长
ERROR 162 :
7.int 类型字符过长
com.mysql.jdbc.exceptions.MySQLDataException: ’2.526744582E1′ in column ’1′ is outside valid range for the datatype INTEGER.
8.没找到错误 没影响数据读取
java.sql.SQLException: Operation not allowed after ResultSet closed
9.类型错误 最常见的是数字类型错误
Data truncated for column ‘gatheringMoney’ at row 1
1.换另一个项目时 数据库连接池没换 (未解)
Name java: is not bound in this Context
换了之后 似乎还是不行
====
java:/comp/env/jdbc/ConnSqlSer
前面多了一个’/’
应为java:comp/env/jdbc/ConnSqlSer
====
11.\(未解)
java.sql.SQLException: QueryRunner requires a DataSource to be invoked in this way, or a Connection should be passed in
12.executeQuery()方法改成execute()(未解)
Can not issue data manipulation statements with executeQuery().
Can not issue data manipulation statements with executeQuery().
struts错误
Failed to obtain specified collection 下拉框没值
警告: No FormBeanConfig found under ‘yuanLiaoRuKuForm’ 配置文件Form出错
Cannot find bean: ̶org.apache.struts.taglib.html.BEAN” in any scope
<html:text >标签外面没有嵌套<html:form >标签
No getter method for property: ̶outDate” of bean: ̶com.System.storage.form.YuanliaoPandianForm”
在form里面没有定义 此字段的get方法
Cannot get a connection, pool error Timeout waiting for idle object 数据库错误
Operation not allowed after ResultSet closed 可能是结果集关闭了
//在Action里面调用的DAO类 没有进行重新实例化
28-5-12 2:2:9 org.apache.struts.action.RequestProcessor processException
警告: Unhandled Exception thrown: class java.lang.NullPointerException
28-5-12 2:2:9 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet action threw exception
java.lang.NullPointerException
at com.System.storage.action.FinishedAction.finishedCheckAdd(FinishedAction.java:151)
at sun.reflect.NativeMethodAccessorImpl.invoke(Native Method)
//在C标签里从一个对象里取值时 , 所请求的是对象里没有的属性
An error occurred while evaluating custom action attribute ̶value” with value ̶${rows.storageAmount}”: Unable to find a value for ̶storageAmount” in object of class ̶com.System.storage.domain.Finished” using operator ̶.” (null)
forward 转向 连接到了一个还没开始写的action里面 而那个action所对应的jsp页面还没改
Resources cannot be null.
找不到下面这个action
Cannot retrieve mapping for action /finished/finishedChuKuSearch
Cannot retrieve mapping for action /purveyAdd 如果把jsp页面的action写错了 就这样报
/jspfinance/client/ShouKuanJiLu_Sel.jsp(172,) The end tag ̶</html:form” is unbalanced
类型转换错误 一般是参数类型写错 在公共方法或者配置文件里面找
com.System.finance.client.form.GatheringForm cannot be cast to com.System.finance.client.form.InvoiceForm
struts标签里面 写进了html标签内的属性
/jspclient/KeHuDangAn_Ins.jsp(44,88) equal symbol expected
/jspfinance/client/TuiHuanHuo_Ins.jsp(7,42) quote symbol expected
/jspstorage/material/YuanLiaoChuKu_Ins.jsp(84,31) equal symbol expected
org.apache.jasper.JasperException: /scDept/dingDanXinZeng.jsp(333,167) equal symbol expected
jsp页面上action写错了
Cannot retrieve mapping for action /finished/finishedCheckAdd
配置文件出错
The content of element type ̶action-mappings” must match ̶(action)*”.
java常见错误以及可能原因集锦
28-7-8 15:55、 需要标识符
a) 不在函数内
1、 非法表达式开始
b) 可能:丢失括号 .
2. no data found
a) 可能:setInt(1,1)中,没有1这个值
3. 找不到符号
a) 可能:没导入包
4. 指定了无效URL
a) 可能:数据库名或IP错误,即连接出错
5. 类路径没有找到
a) 可能: ClassNotFoundException: oracle.jdbc.driver.OracleDriver
b) 原因: 一般是指包名写错,或者没有import包,或者没有在类路径中找到jar文件
c) 解决: 没有加载Oracle驱动jar,在.bash_profile中把ojdbc14.jar加进来
6. 空指针异常
a) 可能: 数据源错误 比如数据库名或IP错误
7. 不能执行查询
a) 可能: 数据库中表的问题,比如列名不存在
8. invalid identity
a) 可能: 列名出错
9. 若在数据库中创建了 两个sequence ,运行时出现异常可能是先后执行了多次select语句,导致与原有的序列号产生冲突
1. 表名或列名不存在
a) 可能:表不存在或者没有插入数据到表中
11. 不支持的类,类的版本错误
a) 可能:没有导入jdk5.,或者编译器仍为1.4
12. MappingNotFoundException
a) Maybe: In the Eclipse Not refersh , or not exist in the dirctory
13. HibernateException: /hibernate.cfg.xml not found
a) Maybe1: hibernate.cfg.xml not in the root directory
b) Maybe2: Could not parse configuration .
c) resolve: database not connect or use another database
14. ConstraintViolationException
a) Maybe: used a not true database
15. 驱动没有找到 或者 JDBC Driver not found
可能:连接数据库的驱动jar包不存在或者版本不一致,比如将旧的版本换成新的会造成该类错误
16. 空指针异常 , java.lang.NullPointerException
a) 可能1:数据库连接出错,比如在hibernate.cfg.xml中的数据错误会导致异常。
17. 数据插入异常 ,GenericJDBCException: could not insert
a) 可能1:没有建立表或者表中没有任何数据
b) 可能2:插入数据后没有执行提交语句:commit
18. LazyInitializationException 或者延迟加载异常
a) 可能1:没有在查询语句中加fetch
19. IdentifierGenerationException
a) 可能1:高位表没有初始化(比如hi_value中没有记录)
2. could not initialize a collection: [hibernate.entity.Role.modules#32768] Syntax error: Encountered ̶-” at line 1, column 132.
a) 错误原因:><set name=”modules” table=”module-role” lazy=”false”>红色字部分中“-”为非法字符,替换为module_role
21. could not insert collection rows: [hibernate.entity.Module.roles#1]
在Module.hbm.xml文件的如下配置中加入inverse=”true”
<set name=”roles” table=”module_role” inverse=”true”>
22.在部署Struts时,出现如下错误信息:
HTTP Status 44 – Servlet action is not available
type Status report
message Servlet action is not available
description The requested resource (Servlet action is not available) is not available.
问题原因:
1.、web.xml文件中未配置ActionServlet。
2、struts-config.xml文件未配置你要访问的Action。
3、你的jsp文件form标记中action属性的路径名称错误。
4、非以上三种情况。
针对以上4种情况相应的解决方案如下:
1、在web.xml文件中加上ActionServlet的配置信息
?????? /WEB-INF/struts-config.xml?
?2、在struts-config.xml文件检查你要访问的Action配置文件。
3、检查jsp文件form标记中action属性的路径名称是否与struts-config.xml文件中action标记的path属性的路径名称一致。
4、非以上情况的解决办法就是检查web容器的log日志,如果时tomcat则检查下logs目录下的localhost_log文件,看里边是否记录有错误信息,然后根据错误信息提示将其纠正。
23.java.lang.NoClassDefFoundError: org/apache/commons/beanutils/Converter
缺少spring-framework-2..3\lib\jakarta-commons\commons-beanutils.jar
24.
ava 代码Caused by: java.lang.NoClassDefFoundError: org/objectweb/asm/Type
缺少spring-framework-2..3\lib\asm\asm-2.2.2.jar包,版本不同,该包的名字有相应的区别
java 代码Caused by: java.lang.NoClassDefFoundError: org/dom4j/DocumentException
缺少spring-framework-2..3\lib\dom4j\dom4j-1.6.1.jar包,版本不同,该包的名字有相应的区别
把这个包进去就可以了:\Spring26\lib\dom4j、dom4j-1.6.1.jar
java 代码Caused by: java.lang.NoClassDefFoundError: org/apache/commons/collections/SequencedHashMap
缺少spring-framework-2..3\lib\jakarta-commons\commons-collections.jar包,版本不同,该包的名字有相应的区别
java 代码Caused by: java.lang.NoClassDefFoundError: net/sf/cglib/proxy/CallbackFilter
缺少spring-framework-2..3\lib\cglib\cglib-nodep-2.1_3.jar包,版本不同,该包的名字有相应的区别
java 代码Caused by: java.lang.NoClassDefFoundError: org/objectweb/asm/CodeVisitor
缺少hibernate-3.2\lib\asm.jar包,版本不同,该包的名字有相应的区别
java 代码
org.hibernate.exception.SQLGrammarException: could not load an entity: [www.proudsoul.xml.User#1]
JAVA工程与WEB工程包的区别:一个是asm.jar,一个是jta.jar
java 代码Caused by: java.lang.NoClassDefFoundError: javax/transaction/TransactionManager
缺少spring-framework-2..3\lib\j2ee\jta.jar包,版本不同,该包的名字有相应的区别此种错误请检查相应的***.hbm.xml配置文件的配置
25,
Exception in thread ̶main” org.springframework.dao.InvalidDataAccessResourceUsageException: could not get next sequence value; nested exception is org.hibernate.exception.SQLGrammarException: could not get next sequence value
Caused by: org.hibernate.exception.SQLGrammarException: could not get next sequence value
原因:没有加Sequence或者数据库的方言写错了
26,
我的配置文件
<id name=”id” column=”id”>
<generator class=”native” />
</id>
然后我运行保存一条数据进去
The database returned no natively generated identity value
就会报这个错误
因为你native是根据看底层数据库的能力选择identity, sequence 或者hilo中的一个
而我建表的时候id没有指定
alter table `student` change `id` `id` int auto_increment
我们把我们的表的结构小小的改动一下就没问题了
27,
UpdateManager无法启动27-8-9 15:21如果在应用程序安装过程中,暴力中断安装程序,会出现如下状况:1.apt-get remove 和dpkg –remove 无法删除软件 2.UpdateManager无法启动3.新立得软件包管理程序无法启动
以上可能是deb损坏之类造成的
sudo dpkg -r sqldveloper
正在读取软件包列表̷ 完成
正在分析软件包的依赖关系树̷ 完成
E: 软件包 sqldeveloper 需要重新安装,但是我无法找到相应的安装文件。
解决方法:1.从 /var/lib/dpkg/status 中把对应的段删掉
重要:修改之前请先备份
在status中找到你对应的包删除就OK了̷
28,
Exception in thread ̶main” java.lang.NoClassDefFoundError: antlr/ANTLRException
少了antlr-2.7.5H3.jar包,从D:\eclipse\eclipse\plugins\com.genuitec.org.hibernate.eclipse_4.1.1\myeclipse-data\3.\lib
29,
Caused by:
java.lang.NoClassDefFoundError: javax/transaction/Synchronization
缺少Spring26\lib\j2ee\jta.jar
3,
Caused by:
java.lang.IllegalArgumentException: Cannot convert value of type [$Proxy3] to required type [business.impl.PriceBiz] for property ‘priceBiz’: no matching editors or conversion strategy found
在java类中设置priceBiz时应该设置的是接口PriceBizIf,而非实现类
31, 数组越界
可能1: 在命令行后面需要加入参数.
可能2:加入的参数错误
32, car is not mapped
可能1: 在cfg.xml中没有增加映射的hbm.xml文件在<mapping̷/>中
33, 属性没有找到
可能1: hbm.xml文件中的 <property name =””> 有问题, 也许是name的值与对应类中的成员名不一致
34,SQLException: 无当前连接 可能1:在比如创建帐户时没有对相应的方法添加到<list>中去,比如这个没有加入:<value>newAccount</value>
<bean id=”transactionAdvisor” class=”org.springframework.aop.support.NameMatchMethodPointcutAdvisor”>
<property name=”advice”>
<ref bean=”advice”/>
</property>
<property name=”mappedNames”>
<list>
<value>transfer</value>
</list>
</bean>
35,SQLException: Syntax error: Encountered ̶table” at line 1, column 8.
可能1;执行SQL时出现冲突,可能是SQL语句中使用了关键字作为变量来用,比如
update order set balance=1;
其中order被用作表名来使用,这是错误的,因为order是个关键字,用在order by中
36, Servlet /Spring-WebMvc threw load() exception
org.xml.sax.SAXParseException: Document root element ̶beans”, must match DOCTYPE root ̶null”.
jar包的冲突,spring-1.2.6.jar和spring.jar出现在一个war的目录中:J Boss/server/all/deploy/Spring-WebMvc.war/WEB-INF/lib
37, java.sql.SQLException: 当事务仍处于活动状态时,无法关闭连接。
38 ,
validateJarFile(/home/soft1/Tomcat/webapps/sms-token-struts/WEB-INF/lib/servlet-api.jar)
Servlet /sms-struts-tiles threw load() exception
java.lang.ClassNotFoundException: org.apache.struts.action.ActionServlet
原因:在WEB-INF/lib/ 目录下有servlet-api.jar与/Tomcat/common/lib目录下的servlet-api.jar冲突了,把WEB-INF/lib/下的servlet-api.jar删除就可以了
39,java.lang.ClassNotFoundException: org.springframework.web.struts.ContextLoaderPlugIn
Marking servlet action as unavailable
1:3:47,88 ERROR [/NetCT_OSS]:3953 – Servlet /NetCT_OSS threw load() exception
javax.servlet.UnavailableException
4,org.xml.sax.SAXParseException: Document root element ̶beans”, must match DOCTYPE root ̶null”.
缺少MyEclipse/eclipse/plugins/com.genuitec.eclipse.springframework_5.5./data/2./dist/modules/spring-struts.jar
41,Caused by: java.sql.BatchUpdateException: ORA-2291: integrity constraint (SD72.FK82D343CF49A4B831) violated – parent key not found
Caused by: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
有可能是ID生成策略的问题,我将它XML映射文件改成<generator class=”increment” />就好了
42,
27-8-19 16:21:43,29 ERROR [org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/OSS].[action]] – Servlet.service() for servlet action threw exception
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [entity.Module#2]
java.util.ConcurrentModificationException
原因:已经有对象拥有了所选择的module,用clear(),而不是remove();
43,
ERROR [org.apache.catalina.session.ManagerBase] – IOException while loading persisted sessions: java.io.InvalidClassException: entity.Module; local class incompatible: stream classdesc serialVersionUID = -59883531935445758, local class serialVersionUID = -297934247726484429
java.io.InvalidClassException: entity.Module; local class incompatible: stream classdesc serialVersionUID = -59883531935445758, local class serialVersionUID = -297934247726484429
在Module中加上一个关键字:transient
44,
Caused by: java.sql.BatchUpdateException: ORA-2292: integrity constraint (SD72.SYS_C132664) violated – child record found
原因:有其他的表引用了该表的外建,所以报这个异常,如果没有引用就不会出现
45,
出现数组越界的时候,看看是不是循环时没有加=,比如:
for(int i = 1; i<arr.length;i++)可以改成
for(int i = 1; i><=arr.length;i++)
46.如果使用Ant和Junit是被报找不到test,检查一下是否误用了private
出错信息:
<failure message=”No tests found in test.AllTest” type=”junit.framework.AssertionFailedError”>junit.framework.AssertionFailedError: No tests found in test.AllTest
该错误有个很特别的特点,当你不通过ant来运行测试,而是通过AllTest类来运行的话,可以正常运行。小心哦
47.如果遇到报:表名无效,且jvm报严重错误,看看是不是用了数据库的保留关键字来做表名了,如User。
48.使用ant时必须小心ant的classpath它用的不是IDE的classpath,小心!
49.小心下边的异常,
exception setting property value with CGLIB (set hibernate.cglib.use_reflection_optimizer=false for more info) setter of xp.bean.Users.?” type=”net.sf.hibernate.PropertyAccessException”>java.lang.ClassCastException at xp.bean.UsersMetaClass1.setPropertyValues(<generated>) at net.sf.hibernate.persister.AbstractEntityPersister.setPropertyValues
上次出现该错错误的原因是:
源文件里我的class类型为一个类Contact contact
而跑到mapping里却成了Set,呵呵厉害。
5.突然间冒出大量的NullPointException
重新build一下。
51.=”Flush during cascade is dangerous – this might occur if an object was deleted and then re-saved by cascade”
52.Tapestry的出错信息:
Class com.bookshop.Hello does not implement the IPage interface.
location: context:/WEB-INF/Home.page, line 6
原因:与显示有关的哪个java类没有从IPage家族继承。
53.使用ant时给出的路径好象不允许出现空格。
54.由于使用ant时用junit做测试的话,classpath中出现j2ee.jar的话,问题多多。例如xml格式的log文件生成不了,莫名其妙的NullPointException等。所以我设置了两个包个包含j2ee.jar,为编译用;一个没有,为junit用
55.进行单元测试时,在查询返回后应马上assertNotNull(),这样可以更快速的定位NullPointException
56.要使用ResourceBundle的话,要千万小心。必须用日志记录下它的状态。还有该属性文件应该放在classes下面。
57.当要显示任何页面时,都被提示无效,那么应该检查一下lib目录了,还有一些很奇怪的异常,例如你明明可以找到一个类但是服务器却提示 ClassNotDefException,那么估计是缺少了该类所必须的包了。或者多了不兼容的包,如, xdoclet系列包不被struts的lib目录所兼容
58.编写clone时从Java编程思想(2nd)上学到的(732):
.引数传递过程中会自动产生别名(alias)。
.没有局域对象(local objects),只有局域性的(local)references。
.reference受范围(scope)的限制,对象则否。
.对象的寿命从来不是Java的讨论议题(因为有垃圾回收机制)
59.try catch finally的域居然是分离的。
6.jsp乱码的其中一个原因:charset=”gb2312″ 等号”=”的两边不允许有空格。
61.我的基于Displaytag的简单报表解决方案。
http://displaytag.sourceforge.net/
下载displaytag.jar和displaytag.tld
displaytag.jar放在lib目录,而displaytag.tld放在WEB-INF目录,在web.xml中为displaytag.tld声明一下。
<taglib>
<taglib-uri>http://displaytag.org</taglib-uri>
<taglib-location>/WEB-INF/displaytag.tld</taglib-location>
</taglib>
在jsp里使用前,加上
<%@ taglib uri=”http://displaytag.org” prefix=”display” %>
注意该软件有个bug,他要用的一个包common-lang.jar版本必须在2.以上。
如果碰到下面异常,则应坚持一下是否该包的版本问题。
java.lang.NoSuchMethodError: org.apache.commons.lang.StringUtils.capitalize(Ljava/lang/String;)Ljava/lang/String;
然后就可放心使用了
<display:column property = ̶xxx”/>其中xxx为对象中的带有getter的变量>。
定义表格的样子,用css定义。如
TABLE.its THEAD TR {
BACKGROUND-COLOR: #69c
}
TABLE.its TR.even {
BACKGROUND-COLOR: #def
}
在使用分页时,可能会出现这种情况,点击其他页时,弹出下载窗口,让你下载当前jsp页面,这是因为你在当前页面读取了数据的缘故。解决办法为在action里读取数据而不是在jsp里。可以参考
http://www.displaytag.org/example-paging.jsp?d-26189-p=2
62.使用displaytag时,在一列中放入多个元素
必须在display:table中定义一个id
<display:table name = ̶allBooks” class = ̶its” pagesize = ̶5″ id = ̶item”>
<display:column title = ̶操作”>
查看
编辑
删除
</display:column>
要在displaytag中使用链接,必须具备paramId,否则不显示为链接
<display:column property = ̶product.name” href = ̶viewDetailV2..jsp” title = ̶书名” paramId=”item” paramProperty=”product.id”/>
可以这样使用display
<display:column property = ̶product.id” title = ̶ID”/>
其中product为对象
63.<bean:write name = ̶xxxx” property = ̶xxx”/> 可以直接取到session.getAttribute()取到的东西。
64.实验struts-upload例子时要注意的地方:
1.If you would rather write this file to another file, please check here:
这一行要打钩
2.If you checked the box to write to a file, please specify the file path here:
在这里要重命名如:c:\b.jpg
上传成功的话,会出现提示 The file has been written to ̶c:\b.jpg”
65.Hibernate的like可以这么用:
Query query = session.createQuery(̶from src.persistent.Book as book where upper(book.name) like :name ̶);
query.setString(̶name”, ̶%”);
result = query.list();
66.Hibernate出现 duplicate import : className
异常也可能是因为忘了为持久类在configuration中addClass了
67.<logic:iterate id = ̶author” name = ̶authors”>
name所引用的是session里的attribute。
68.如果发现要出现询问下载的情况,有可能是因为要跳转的页面出现了问题。试试在要跳转到的页删掉
<%@ page contentType=”text/html; charset=gb2312″%>
69.如果JSP页面跳转时出现下边的错误信息:
The request sent by the client was syntactically incorrect (Invalid path /web/shoppingCart was requested).
原因是struts-config的action = ̶x” 写成了 action = ̶x.do”
7.在struts中,strut-config.xml中,forward时使用redirect = ̶true”可以将.do重定向为.jsp
71.以后在判断相等性之前先用logger把两个值显示出来。
72.从session里getAttribute后,修改并不需要重新setAttribute一次。
73.在hibernate中使用subclass是一棵继承树共用一个表,仅生成个mapping。
每个类中必须有discrimator-value。在最上层的类中必须声明:
@hibernate.discriminator column = ̶class”。
不可以将子类添加到configuration里去。(即不可addClass(子类))
使用Xdoclet的建立subclass的例子(该类是父类)
/**
* @hibernate.class discriminator-value = ̶customer”
* @hibernate.discriminator column = ̶class”
*/
而使用joined-subclass则是一类一表,也不许将子类添加进configuration里去。
/**
* @hibernate.joined-subclass
* @hibernate.joined-subclass-key
* column=”customer_id”
*/
如果发现生成的mapping文件中joined-subclass的key column为空,那么可能是@hibernate.joined-subclass-key这句没有写对。
用joined-subclass生成的表,仔细看。Member extends Customer
create table Customer (
id VARCHAR2(255) not null,
name VARCHAR2(255),
description VARCHAR2(255),
primary key (id)
)
create table Member (
customer_id VARCHAR2(255) not null,
password VARCHAR2(255),
primary key (customer_id)
)
74.在junit中尽量使用assertEquals代替assertTrue;
75.Hibernate
如果Child extends Parent
那么from Parent as parent 也将会将Child选出来,而from Child as child 则不会选出Parent
76.Hibernate
使用hibernate的one-to-one时,应该两方向都set,否则会报save NullPointException
parent.setChild(child);
child.setParent(parent);
77.Hibernate
遇到下边的异常,估计是与因为外键出现了问题:
java.lang.NullPointerException
at net.sf.hibernate.persister.AbstractEntityPersister.getPropertyValue(AbstractEntityPersister.java:675)
at net.sf.hibernate.id.ForeignGenerator.generate(ForeignGenerator.java:33)
如:
* @hibernate.id generator-class = ̶foreign”
* @hibernate.generator-param name = ̶property” value = ̶customer”
* @hibernate.one-to-one name = ̶custmoer” class = ̶src.persistent.Customer”
value的值和one-to-one 中name的值不符,则会出现上边的异常。如果不显示指定name则默认取成员变量名

Customer a
则name = ̶a” 需要小心的是不是类名。所以,以后最好显示指定名字为好。
78.出现异常:
Exceptionobject references an unsaved transient instance – save the transient instance before flushing: src.persistent.Product
原因没有为某对象进行set设置, 如上边的这个就是某对象没有调用setProduct
79.
̶xxx action = ̶/a” 不用.do和根目录名
8.
Caused by: java.sql.SQLException: ORA-2291: 违反完整约束条件 (BOOKSHOP.FK4AAEE
47687CCA6B) – 未找到父项关键字
如果你觉得该做的e是出现这个问题,那么检查一下,是否将类的继承关系在hibernate的mapping中反映了出来。joined-subclass或subclass
例如a extends b
如果持久类c 需要Set的是持久类a,那么你把b传入,而b又没有在mapping中将父子关系反映出来的话,就会出现该异常
81.使用DynaActionForm需要注意的问题
在struts-config声明
><form-bean name=”memberloginForm” dynamic =”true” type=”org.apache.struts.action.DynaActionForm”>
<form-property name = ̶name” type = ̶java.lang.String”/>
<form-property name = ̶password” type = ̶java.lang.String”/>
</form-bean>
在Action里将form强制转化成DynaActionForm,然后get(̶属性名”)就可以了
82.struts的validate最简单实现
1.首先准备好错误提示信息。
xxx.properties 里
errors.required={} is required.(默认已有)
2.Form必须从ValidatorForm继承
3.不可以重载ValidatorForm的validate函数
4.在validate.xml中为你想验证的表单进行验证设计。例如
<form name=”logonForm”>
<field property=”userName” depends=”required”>
<arg key=”prompt.userName”/>
(该参数将在显示错误信息是从xxx.properties读取prompt.userName,填入{}方括号里,取代。如果是arg1将将填入{1}位置,以此类推。
</field>
</form>
不需要在action里做任何处理。只管forward就行了。作为forward的目标页,不需要任何有关用于处理出错信息的处理。
83.服务器报
The requested resource (/xxxx/xxx.htm) is not available.的很奇怪的一个的可能原因
在web.xml中定义的tld,没有找到
或者是lib目录下的包太多出现了问题。

<taglib>
<taglib-uri>/spring</taglib-uri>
<taglib-location>/WEB-INF/spring.tld</taglib-location>
</taglib>
如WEB-INF目录下不存在spring.tld的话,就会报上边的错误
84.
spring的xxx-servlet.xml的使用SimpleFormController系列的类问题:
<bean id = ̶priceIncreaseForm” class=”PriceIncreaseFormController”>
<property name=”sessionForm”><value>true</value></property>
<property name=”beanName”><value>priceIncrease</value></property>
<property name=”commandClass”><value>PriceIncrease</value></property>
<property name=”formView”><value>priceIncrease</value></property>
<property name=”successView”><value>hello</value></property>
<property name=”productManager”>
<ref bean=”prodMan”/>
</property>
</bean>
这里要注意几个问题:
(1)上边的PriceIncrease是了类名,必须在classes里存在该类,否则报:
PropertyVetoExceptionsException: 1 errors:– ErrorCodedPropertyVetoException: message=[Failed to convert property value of type [java.lang.String] to required type [java.lang.Class] for property named ‘commandClass’; nested exception is:
java.lang.IllegalArgumentException: Invalid class name [PriceIncrease]: PriceIncrease]; errorCode=[typeMismatch]
java.lang.IllegalArgumentException: Invalid class name [PriceIncrease]: PriceIncrease
(2)<property name=”formView”><value>priceIncrease</value></property>
这一行必不可少,priceIncrease是页面的名字,他将会是prefix + priceIncrease + suffix
如果缺少该行,则报:
javax.servlet.ServletException: Error in ModelAndView object or View resolution encountered by servlet with name ‘pocketSpring’: View to render cannot be null with ModelAndView [ModelAndView: materialized View is [null]; Model=[{priceIncrease=PriceIncrease@148e798, org.springframework.validation.BindException.priceIncrease=org.springframework.validation.BindException: BindException: errors}]]
使用Errors的rejectValue相关问题:
rejectValue(java.lang.String field, java.lang.String errorCode, java.lang.Object[] errorArgs, java.lang.String defaultMessage)
Reject the given field of the current object, using the given error description.
当前对象指的是xxx-servlet.xml中与validator类有联系的哪个对象。
第一个是:当前对象的属性名,必须存在
第二个是:将要从属性文件中读取的消息
第三个是:传递给所读取的消息的参数,如:
error.too-low=You have to specify a percentage higher than {}!
第四个是:当从属性文件中读取消息不成功时,所reject的值
以后从request中读取parameter可以借用spring的RequestUtils包里的
getStringParameter
public static java.lang.String getStringParameter(javax.servlet.http.HttpServletRequest request,
&bsp; java.lang.String name,
java.lang.String defaultVal)
Get a string parameter, with a fallback value. Never throws an exception. Can pass a distinguished value to default to enable checks of whether it was supplied.
不会抛异常
Hibernate的问题:2.1rc的问题
INFO: cache provider: net.sf.ehcache.hibernate.Provider
net.sf.hibernate.HibernateException: could not instantiate CacheProvider:
解决办法,2.1rc比以前的版本多需要一个包
ehcache.jar
85.static的问题
static函数只可以访问static成员变量。
而static变量可以被任何成员函数访问。
86.
java.lang.NoClassDefFoundError: javax/transaction/Synchronization];
需要添加JTA.jar
========

相关链接

http://jinnianshilongnian.iteye.com/category/283252?page=2
http://www.importnew.com/12074.html
0 0