﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-dwys0343-文章分类-J2SE</title><link>http://www2.blogjava.net/dwys0343/category/18827.html</link><description>技术整理</description><language>zh-cn</language><lastBuildDate>Mon, 26 Mar 2007 20:05:40 GMT</lastBuildDate><pubDate>Mon, 26 Mar 2007 20:05:40 GMT</pubDate><ttl>60</ttl><item><title>Java(JDK 1.5)的巨大变化</title><link>http://www.blogjava.net/dwys0343/articles/106471.html</link><dc:creator>特兰克斯</dc:creator><author>特兰克斯</author><pubDate>Mon, 26 Mar 2007 09:44:00 GMT</pubDate><guid>http://www.blogjava.net/dwys0343/articles/106471.html</guid><wfw:comment>http://www.blogjava.net/dwys0343/comments/106471.html</wfw:comment><comments>http://www.blogjava.net/dwys0343/articles/106471.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dwys0343/comments/commentRss/106471.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dwys0343/services/trackbacks/106471.html</trackback:ping><description><![CDATA[
		<p>
				<span class="boldbodycopy">
						<i>JDK 1.5版本包含了Java语法方面的主要改进。</i>
				</span>
		</p>
		<p>
				<span class="bodycopy">自从Java 1.0版本首次受到开发人员欢迎以来，Java语言的语法就没有发生过太大的变化。虽然1.1版本增加了内部类和匿名内部类，1.4版本增加了带有新的assert关键字的assertion（断定）功能，但Java语法和关键字仍然保持不变--像编译时常量一样处于静态。它将通过J2SE 1.5（代号Tiger）发生改变。</span>
		</p>
		<p>
				<span class="bodycopy">过去的J2SE版本主要关注新类和性能，而Tiger的目标则是通过使Java编程更易于理解、对开发人员更为友好、更安全来增强Java语言本身，同时最大限度地降低与现有程序的不兼容性。该语言中的变化包括generics（泛化）、autoboxing、一个增强的“for”循环、 typesafe enums（类型安全的枚举类型）、一个静态导入工具（static import facility）和varargs。</span>
		</p>
		<p>
				<span class="parahead1">通过generics来改进类型检查</span>
		</p>
		<p>
				<span class="bodycopy">generics使你能够指定一个集合中使用的对象的实际类型，而不是像过去那样只是使用Object。generics也被称为“参数化类型”，因为在generics中，一个类的类型接受影响其行为的类型变量。</span>
		</p>
		<p>
				<span class="bodycopy">generics并不是一个新概念。C++中有模板，但是模板非常复杂并且会导致代码膨胀。C++编码人员能够仅使用C++模板，通过一些小的技巧来执行阶乘函数，然后看着编译器生成C++源代码来处理模板调用。Java开发人员已经从C++语言中学到了很多关于generics的知识，并经过了足够长时间的实践，知道如何正确使用它们。Tiger的当前计划是从健壮的Generic Java (GJ)方案演变而来的。GJ方案的口号是“使Java的类型简化、再简化。”</span>
		</p>
		<p>
				<span class="bodycopy">为了了解generics，让我们从一个不使用generics的例子开始。下面这段代码以小写字母打印了一个字符串集合：</span>
		</p>
		<p>
		</p>
		<pre>//获得一个字符串集合
public void lowerCase(Collection c) {
  Iterator itr = c.iterator();
  while (itr.hasNext()) {
    String s = (String) itr.next();
    System.out.println(s.toLowerCase());
  }
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">这个方法不保证只接收字符串。编程人员负责记住传给这个方法什么类型的变量。Generics通过显式声明类型来解决这个问题。Generics证明并执行了关于集合包含什么东西的规则。如果类型不正确，编译器就会产生一个错误。在下面的改写代码中，注意Collection和Iterator是如何声明它们只接收字符串对象的：</span>
		</p>
		<p>
		</p>
		<pre>public void lowerCase(
     Collection&lt;String&gt; c)  {
  Iterator&lt;String&gt; itr = c.iterator();
  while (itr.hasNext()) {
    System.out.println(
      itr.next().toLowerCase());
  }
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">现在，该代码包含了更强大的类型，但它仍然包含许多键盘类型。我们将在后面加以介绍。注意，你可以存储类型参数的任何子类型。接下来，我们将使用这个特性draw()一个形状集合。</span>
		</p>
		<p>
		</p>
		<pre>// 获得孩子集合...
public void drawAll(Collection&lt;Shape&gt; c) {
  Iterator&lt;Shape&gt; itr = c.iterator();
  while (itr.hasNext()) {
    itr.next().draw();
  }
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">尖括号中的值被称为类型变量。参数化类型能够支持任何数量的类型变量。例如，java.util.Map就支持两个类型变量--一个用于键类型，一个用于值类型。下面的例子使用了一个带有指向一列元素对象的字符串查找键的map：</span>
		</p>
		<p>
		</p>
		<pre>public static void main(String[] args) {
  HashMap&lt;String, List&lt;Element&gt;&gt; map =
    new HashMap&lt;String, 
          List&lt;Element&gt;&gt;();
  map.put("root", 
    new ArrayList&lt;Element&gt;());
  map.put("servlet", 
    new LinkedList&lt;Element&gt;());
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">这个类定义声明了它支持多少个类型变量。类型参数的数量必须精确地与所期望的相匹配。而且，类型变量一定不能是原始类型（primitive types）。</span>
		</p>
		<p>
		</p>
		<pre>List&lt;String, String&gt; // takes one
List&lt;int&gt;            // 无效的，原始类型</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">即使在期望使用一个普通类型（raw type）的时候，你也可以使用一个参数化类型。当然，你也可以反过来做，但这么做会收到一条编译时警告：</span>
		</p>
		<p>
		</p>
		<pre>public static void oldMethod(List list) {
  System.out.println(list.size());
}

public static void main(String[] args) {
  List&lt;String&gt; words = 
    new ArrayList&lt;String&gt;();
  oldMethod(words);   // 没问题
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">这就实现了轻松的向后兼容：接受一个原始列表的老方法能够直接接受一个参数化List&lt;String&gt;。接受参数化List&lt;String&gt;的新方法也能够接受一个原始列表，但是因为原始列表不声明或执行相同的类型约束，所以这个动作会触发一个警告。可以保证的是：如果在编译时你没有得到名为unchecked（未检查）的警告，那么在运行时编译器生成的强制类型转换（cast）将不会失败。</span>
		</p>
		<p>
				<span class="bodycopy">有趣的是，参数化类型和普通类型被编译为相同的类型。没有专门的类来指定这一点，使用编译器技巧就可以完成这一切。instanceof检查可以证明这一点。</span>
		</p>
		<p>
		</p>
		<pre>words instanceof List             // true
words instanceof ArrayList        //true
words instanceof ArrayList&lt;String&gt; // true
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">这个检查产生了一个问题：“如果它们是相同的类型，这种检查能起多大作用？”这是一条用墨水而不是用血写的约束。这段代码将产生一个编译错误，因为你不能向List&lt;String&gt;中添加新的Point：</span>
		</p>
		<p>
		</p>
		<pre>List&lt;String&gt; list = 
  new ArrayList&lt;String&gt;();
list.add(new Point());  // 编译错误</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">但是这段代码被编译了！</span>
		</p>
		<p>
		</p>
		<pre>List&lt;String&gt; list = 
  new ArrayList&lt;String&gt;();
((List)list).add(new Point());
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">它将参数化类型强制转换为一个普通类型，这个普通类型是合法的，避免了类型检查，但正如前面所解释的那样，却产生了一个调用未检查的警告：</span>
		</p>
		<p>
		</p>
		<pre>warning: unchecked call to add(E) as a member of the raw type
 java.util.List
    ((List)list).add(new Point());
    ^
</pre>
		<p>
		</p>
		<p>
				<span class="parahead1">写一个参数化类型</span>
		</p>
		<p>
				<span class="bodycopy">Tiger提供了一个写参数化类型的新语法。下面显示的Holder类可以存放任意引用类型。这样的类很便于使用，例如，通过引用语义支持CORBA传递，而不需要生成单独的Holder类：</span>
		</p>
		<p>
		</p>
		<pre>public class Holder&lt;A&gt; {
  private A value;
  Holder(A v) { value = v; }
  A get() { return value; }
  void set(A v) { value = v; }
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">使用一个参数化的Holder类型，你能够安全地得到和设置数据，而不需进行强制类型转换：</span>
		</p>
		<p>
		</p>
		<pre>public static void main(String[] args) {
  Holder&lt;String&gt; holder = 
    new Holder&lt;String&gt;("abc");
  String val = holder.get();  // "abc"
  holder.set("def");
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">“A”类型参数名可以是任何标准的变量名。它通常是一个单一的大写字母。你也可以声明类型参数必须能够扩展另一个类，如下所示：</span>
		</p>
		<p>
		</p>
		<pre>// 也可以
public class Holder&lt;C extends Child&gt;
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">关于是否能够声明任何其他的类型参数仍然存在争议。你对generics了解的越深，你需要的特殊规则就越多，但是特殊规则越多，generics就会越复杂。</span>
		</p>
		<p>
				<span class="bodycopy">Tiger中设计用来保存线程局部变量（thread local variable）的核心类java.lang.ThreadLocal，将可能变得与下面这个 Holder类的作用类似： </span>
		</p>
		<p>
		</p>
		<pre>public class ThreadLocal&lt;T&gt; {
  public T get();
  public void set(T value);
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">我们也将看见java.lang.Comparable的变化，允许类声明与它们相比较的类型： </span>
		</p>
		<p>
		</p>
		<pre>public interface Comparable&lt;T&gt; {
  int compareTo(T o);
}
public final class String implements Comparable&lt;String&gt; {
  int compareTo(String anotherString);
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">Generics不仅仅用于集合，它们有更为广泛的用途。例如，虽然你不能基于参数化类型（因为它们与普通类型没有什么不同）进行捕捉（catch），但是你可以抛出(throw)一个参数化类型。换句话说，你可以动态地决定throws语句中抛出什么。</span>
		</p>
		<p>
				<span class="bodycopy">下面这段令人思维混乱的代码来自generics规范。该代码通过扩展Exception的类型参数E定义了一个 Action接口。Action类有一个抛出作为E 出现的任何类型的run()方法。然后，AccessController类定义一个接受Action&lt;E&gt;的静态exec()方法，并声明exec()抛出E。声明该方法自身是参数化的需要该方法标记（method signature）中的特殊&lt;E extends Exception&gt;。</span>
		</p>
		<p>
				<span class="bodycopy">现在，事情变得有点棘手了。main()方法调用在Action 实例（作为一个匿名内部类实现）中传递的AccessController.exec()方法。该内部类被参数化，以抛出一个 FileNotFoundException。main()方法有一个捕捉这一异常类型的catch语句。如果没有参数化类型，你将不能确切地知道run()会抛出什么。有了参数化类型，你能够实现一个泛化的Action类，其中run()方法可以任意实现，并可以抛出任意异常（Exception）：</span>
		</p>
		<p>
		</p>
		<pre>interface Action&lt;E extends Exception&gt; {
  void run() throws E;
}

class AccessController {
  public static &lt;E extends Exception&gt;
  void exec(Action&lt;E&gt; action) throws E {
    action.run();
  }
}

public class Main {
  public static void main(String[] args) {
    try {
      AccessController.exec(
      new Action&lt;FileNotFoundException&gt;() {
        public void run()
        throws FileNotFoundException {
          // someFile.delete();
        }
      });
    }
    catch (FileNotFoundException f) { }
  }
}
</pre>
		<p>
		</p>
		<p>
				<span class="parahead1">协变返回类型</span>
		</p>
		<p>
				<span class="bodycopy">下面进行一个随堂测验：下面的代码是否能够成功编译？</span>
		</p>
		<p>
		</p>
		<pre>class Fruit implements Cloneable {
  Fruit copy() throws
    CloneNotSupportedException {
   return (Fruit)clone();
  }
}
class Apple extends Fruit 
           implements Cloneable {
  Apple copy() 
    throws CloneNotSupportedException {
   return (Apple)clone();
  }
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">答案：该代码在J2SE 1.4中不能编译，因为改写一个方法必须有相同的方法标记（包括返回类型）作为它改写的方法。然而，generics有一个叫做协变返回类型的特性，使上面的代码能够在Tiger中进行编译。该特性是极为有用的。 </span>
		</p>
		<p>
				<span class="bodycopy">例如，在最新的JDOM代码中，有一个新的Child接口。Child有一个detach()方法，返回从其父对象分离的Child对象。在Child接口中，该方法当然返回Child： </span>
		</p>
		<p>
		</p>
		<pre>public interface Child {
  Child detach();
  // etc
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">当Comment类实现detach()时，它总是返回一个Comment，但如果没有协变返回类型，该方法声明必须返回Child：</span>
		</p>
		<p>
		</p>
		<pre>public class Comment {
  Child detach() {
    if (parent != null)
      parent.removeContent(this); 
    return this;
  }
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">这意味着调用者一定不要将返回的类型再向下返回到一个Comment。协变返回类型允许Comment 中的detach()返回 Comment。只要Comment是Child的子类就行。除了能够返回Document的DocType和能够返回Element的EntityRef，该特性对立刻返回Parent 的Child.getParent()方法也能派上用场。协变返回类型将确定返回类型的责任从类的用户（通过强制类型转换确认）转交给类的创建者，只有创建者知道哪些类型彼此之间是真正多态的。 这使应用编程接口(API)的用户使用起来更容易，但却稍微增加了API设计者的负担。</span>
		</p>
		<p>
				<span class="parahead1">Autoboxing</span>
		</p>
		<p>
				<span class="bodycopy">Java有一个带有原始类型和对象（引用）类型的分割类型系统。原始类型被认为是更轻便的，因为它们没有对象开销。例如，int[1024]只需要4K存储空间，以及用于数组自身的一个对象。然而，引用类型能够在不允许有原始类型的地方被传递，例如，传递到一个List。这一限制的标准工作场景是在诸如list.add(new Integer(1))的插入操作之前，将原始类型与其相应的引用类型封装（box或wrap）在一起，然后用诸如((Integer)list.get(0)).intValue()的方法取出（unbox）返回值。</span>
		</p>
		<p>
				<span class="bodycopy">新的 autoboxing特性使编译器能够根据需要隐式地从int转换为Integer，从char 转换为Character等等。auto-unboxing进行相反的操作。在下面的例子中，我不使用autoboxing计算一个字符串中的字符频率。我构造了一个应该将字符型映射为整型的Map，但是由于Java的分割类型系统，我不得不手动管理Character和Integer封箱转换（boxing conversions）。</span>
		</p>
		<p>
		</p>
		<pre>public static void countOld(String s) {
  TreeMap m = new TreeMap();
  char[] chars = s.toCharArray();
  for (int i=0; i &lt; chars.length; i++) {
    Character c = new Character(chars[i]);
    Integer val = (Integer) m.get(c);
    if (val == null)
      val = new Integer(1);
    else
      val = new Integer(val.intValue()+1);
    m.put(c, val);
  }
  System.out.println(m);
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">Autoboxing使我们能够编写如下代码：</span>
		</p>
		<p>
		</p>
		<pre>public static void countNew(String s) {
  TreeMap&lt;Character, Integer&gt; m =
    new TreeMap&lt;Character, Integer&gt;();
  char[] chars = s.toCharArray();
  for (int i=0; i &lt; chars.length; i++) {
    char c = chars[i];
    m.put(c, m.get(c) + 1);  // unbox
  }
  System.out.println(m);
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">这里，我重写了map，以使用generics，而且我让autoboxing给出了map能够直接存储和检索char 和int值。不幸的是，上面的代码有一个问题。如果m.get(c)返回空值（null），会发生什么情况呢？怎样取出null值？在抢鲜版（early access release）(参见下一步)中，取出一个空的Integer 会返回0。自抢鲜版起，专家组决定取出null值应该抛出一个NullPointerException。因此，put()方法需要被重写，如下所示： </span>
		</p>
		<p>
		</p>
		<pre>m.put(c, Collections.getWithDefault(
  m, c) + 1);
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">新的Collections.getWithDefault()方法执行get()函数，在该方法中，如果值为空值，它将返回期望类型的默认值。对于一个int类型来说，则返回0。</span>
		</p>
		<p>
				<span class="bodycopy">虽然autoboxing有助于编写更好的代码，但我的建议是谨慎地使用它。封箱转换仍然会进行并仍然会创建许多包装对象（wrapper-object）实例。当进行计数时，采用将一个int与一个长度为1的的 int数组封装在一起的旧方法更好。然后，你可以将该数组存储在任何需要引用类型的地方，获取intarr[0]的值并使用intarr[0]++递增。你甚至不必再次调用put()，因为会在适当的位置产生增量。使用这一方法和其他一些方法，你能够更有效地进行计数。使用下面的算法，执行100万个字符的对时间会从650毫秒缩短为30毫秒：</span>
		</p>
		<p>
		</p>
		<pre> 
public static void countFast(String s) {
  int[] counts = new int[256];
  char[] chars = s.toCharArray();
  for (int i=0; i &lt; chars.length; i++) {
    int c = (int) chars[i];
    counts[c]++;  // no object creation
  }
  for (int i = 0; i &lt; 256; i++) {
    if (counts[i] &gt; 0) {
      System.out.println((char)i + ":" 
           + counts[i]);
    }
  }
}
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">在C#中，我们可以看到一个类似但稍微不同的方法。C#有一个统一的类型系统，在这个系统中值类型和引用类型都扩展System.Object。但是，你不能直接看到这一点，因为C#为简单的值类型提供了别名和优化。int是System.Int32的一个别名，short是System.Int16的一个别名，double是System.Double的一个别名。在C#中，你能够调用“int i = 5； i.ToString()；”，它是完全合法的。这是因为每个值类型都有一个在它被转换为引用类型时创建的相应隐藏引用类型（在值类型被转换为一个引用类型时创建的）。 </span>
		</p>
		<p>
		</p>
		<pre>int x = 9;
object o = x;  //创建了引用类型
int y = (int) o;
</pre>
		<p>
		</p>
		<p>
				<span class="bodycopy">当基于一个不同的类型系统时，最终结果与我们在J2SE 1.5中看到的非常接近。</span>
		</p>
		<p>
				<span class="bodycopy">
				</span> </p>
		<span class="bodycopy">
				<p>
						<span class="parahead1">对于循环的增强</span>
				</p>
				<p>
						<span class="bodycopy">还记得前面的这个例子么？ </span>
				</p>
				<p>
				</p>
				<pre>public void drawAll(Collection&lt;Shape&gt; c) {
  Iterator&lt;Shape&gt; itr = c.iterator();
  while (itr.hasNext()) {
    itr.next().draw();
  }
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">你再也不用输入这么多的文字了！这里是Tiger版本中的新格式。</span>
				</p>
				<p>
				</p>
				<pre>public void drawAll(Collection&lt;Shape&gt; c) {
  for (Shape s : c) {
    s.draw();
  }
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">你可以阅读这样一段代码“foreach Shape s in c”。我们注意到设计者非常聪明地避免添加任何新的关键字。考虑到很多人都用“in”来输入数据流，我们对此应该感到非常高兴。编译器将该新的语法自动扩展到其迭代表中。 </span>
				</p>
				<p>
				</p>
				<pre>for (Iterator&lt;Shape&gt; $i = c.iterator(); 
     $i.hasNext(); ) {
  Shape s = $i.next();
  s.draw();
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">你可以使用该语法来对普通（raw，非参数化的）类型进行迭代，但是编译器会输出一个警告，告诉你必须的类型转换可能会失败。你可以在任何数组和对象上使用“foreach”来实现新的接口java.lang.Iterable。</span>
				</p>
				<p>
				</p>
				<pre>public interface Iterable&lt;T&gt; {
  SimpleIterator&lt;T&gt; iterator();
}
public interface SimpleIterator&lt;T&gt; {
  boolean hasNext();
  T next();
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">java.lang中的新的接口避免对java.util的任何语言依赖性。Java语言在java.lang之外必须没有依赖性。要注意通过next()方法来更巧妙地使用协变返回类型。需要说明的一点是，利用该“foreach”语法和SimpleIterator接口，就会丧失调用iterator.remove()的能力。如果你还需要该项能力，则必须你自己迭代该集合。</span>
				</p>
				<p>
						<span class="bodycopy">与C#对比一下，我们会看到相似的语法，但是C#使用“foreach”和“in”关键字，从最初版本开始它们就被作为保留字。</span>
				</p>
				<p>
				</p>
				<pre>// C#
foreach (Color c in colors) {
  Console.WriteLine(c);
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">C#的“foreach”对任何集合（collection）或者数组以及任何可列举的实现都有效。我们再一次看到了在Java和C#之间的非常相似之处。</span>
				</p>
				<p>
						<span class="parahead1">（类型安全的枚举类型）typesafe enum </span>
				</p>
				<p>
						<span class="bodycopy">enums 是定义具有某些命名的常量值的类型的一种方式。你在C，C++中已经见过它们，但是显然，它们曾经在Java中不用。现在，经过了八年之后，Java重又采用它们，并且大概比先前的任何语言都使用得更好。让我们首先来看看先前我们是如何解决enum问题的？不知道你有没有编写过如下代码？ </span>
				</p>
				<p>
				</p>
				<pre>class PlayingCard {
  public static final int SUIT_CLUBS 
    = 0;
  public static final int SUIT_DIAMONDS 
    = 1;
  public static final int SUIT_HEARTS 
    = 2;
  public static final int SUIT_SPADES 
    = 3;
  // ...
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">这段代码很简单也很常见，但是它有问题。首先，它不是类型安全的（typesafe）。可以给一个方法传递文字“5”来获取一个suit，并且将被编译。同时，这些值用这些常量直接被编译成每个类。Java通过这些常量进行这种"内联"（inlining）来达到优化的目的，但其风险在于，如果对这些值重新排序并且只重新编译该类，则其他类将会错误地处理这些suits。 而且，该类型是非常原始的，它不能被扩展或者增强，同时如果你输出这些值中的一个，你只会得到一个含意模糊的整型量，而不是一个好记的有用名字。这种方法非常简单，这也正是我们为什么要这样做的原因，但是它并不是最好的方法。所以，也许需要尝试一下下面的这个方法：</span>
				</p>
				<p>
				</p>
				<pre>class PlayingCard {
  class Suit {
     public static final Suit CLUBS 
       = new Suit();
     public static final Suit DIAMONDS 
       = new Suit();
     public static final Suit HEARTS 
       = new Suit();
     public static final Suit SPADES 
       = new Suit();
     protected Suit() { }
  }
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">它是类型安全的（typesafe）且更加具有可扩展性，并且，属于面向对象设计的类。然而，这样简单的一种方法并不支持序列化，没有合法值的列表，无法将这些值排序，并且，不能作为一个有意义的字符串来打印一个值。你当然可以添加这些特性，Josh Bloch在他的Effective Java一书中（第五章，第21条）为我们准确展示了如何解决这些问题。然而，你最终得到的是几页蹩脚的代码。</span>
				</p>
				<p>
						<span class="bodycopy">Java新的enum特性具有一个简单的单行语法： </span>
				</p>
				<p>
				</p>
				<pre>class PlayingCard {
  public enum Suit { clubs, 
        diamonds, hearts, spades }
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">被称之为enum（枚举）类型的该suit，对应于每个enum常量都有一个成员项。每个enum类型都是一个实际类，它可以自动扩展新类java.lang.Enum。编译器赋予enum类以有意义的String()、 hashCode(), 和equals() 方法, 并且自动提供Serializable（可序列化的）和Comparable（可比较的）能力。令人高兴地是enum类的声明是递归型的：</span>
				</p>
				<p>
				</p>
				<pre>public class Enum&lt;E extends Enum&lt;E&gt;&gt;
     implements Comparable&lt;E&gt;, 
               Serializable {
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">使用最新的enum类型可以提供很多好处：包括：比整型操作（int operations）更好的性能，编译时更好的类型安全性，不会被编译到客户端并且可以被重新命名和排序的常量，打印的值具有含意清晰的信息，能够在集合（collections）甚至switch中被使用，具有添加域（fields）和方法的能力，以及实现任意接口的能力。 </span>
				</p>
				<p>
						<span class="bodycopy">每个enum具有一个字符串名字和一个整型顺序号值：</span>
				</p>
				<p>
				</p>
				<pre>out.println(Suit.clubs);       // "clubs"
out.println(Suit.clubs.name);  // "clubs"
out.println(Suit.clubs.ordinal);    // 0
out.println(Suit.diamonds.ordinal); // 1
Suit.clubs == Suit.clubs     // true
Suit.clubs == Suit.diamonds  // false
Suit.clubs.compareTo(Suit.diamonds) // -1
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">enum可以拥有构造器和方法。甚至一个main()方法都是合法的。下面的例子将值赋给罗马数字：</span>
				</p>
				<p>
				</p>
				<pre>public enum Roman {
  I(1), V(5), X(10), L(50), C(100),
  D(500), M(1000);

  private final int value;

  Roman(int value) { this.value = value; }

  public int value() { return value; }

  public static void main(String[] args) {
    System.out.println(Roman.I);
  }
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">非常奇怪的是，不能将序列数值赋给一个enum，比如说“enum Month{jan=1,feb=2,….}”。 然而，却可以给enum常量添加行为。比如说，在JDOM中，XMLOutputter支持数种空白处理方法。如果JDOM是参照J2SE1.5构建的，那么这些方法就可以用一个enum来定义，并且enum类型本身可以具有这种处理行为。不管这种编码模式是不是会被证明是有用的，我们都会逐渐了解它。肯定这是一个异常有趣的概念。 </span>
				</p>
				<p>
				</p>
				<pre>public abstract enum Whitespace {
  raw {
    String handle(String s) { return s; }
  },
  trim {
    String handle(String s) { 
      return s.trim(); }
  },
  trimFullWhite {
    String handle(String s) { 
      return s.trim().equals("") ? "":s; }
  };
  abstract String handle(String s);

  public static void main(String[] args) {
    String sample = " Test string ";
    for (Whitespace w : Whitespace.VALUES) 
      System.out.println(w + ": '" 
          + w.handle(sample) + "'");
}}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">很少有公开的出版物谈及Java新的enum。enum常量名是不是都应该都大写？对于常量来说，这是一个标准，但是规范指出小写名称“于更好的字符串格式，一般应该避免使用定制的toString方法。”另外，名字和顺序号该是域还是方法？这是封装方法一再引起争论的问题。在向J2SE1.5添加关键字方面，该特性也落了一个不太好的名声。令人伤心的是，它还是一个通常被用作存储Enumerator（计数器）实例的词。如果你已经在你的代码中使用了“enum”，那么在你为J2SE 1.5的应用编译之前，必须修改它。现在，你已得到了充分的警示。 </span>
				</p>
				<p>
						<span class="bodycopy">让我们看一下C#，所有的enum都扩展成System.Enum。每个enum都具有可以被赋值的整型（或者字节型或者其他类型）值。enum还拥有静态方法，以便从字符串常量来初始化enum，获取有效值列表，从而，可以看到某个值是不是被支持。通过使用[flags]属性来标记一个enum，你可以确保值支持位屏蔽，并且系统负责打印被屏蔽的值的有用输出: </span>
				</p>
				<p>
				</p>
				<pre>// C#
[Flags]
public enum Credit : byte {
  Visa = 1, MC = 2, Discover = 4 }

Credit accepted = Credit.Visa | Credit.MC;

c.WriteLine(accepted);         // 3
c.WriteLine(accepted.Format());//"Visa|MC"
</pre>
				<p>
				</p>
				<p>
						<span class="parahead1">静态导入</span>
				</p>
				<p>
						<span class="bodycopy">静态导入使得我们可以将一套静态方法和域放入作用域（scope）。它是关于调用的一种缩写，可以忽略有效的类名。比如说，对Math.abs(x)的调用可以被简单地写成 abs(x)。为了静态地导入所有的静态域和方法，我们可以使用“import static java.lang.Math”，或者指定要导入的具体内容，而使用“import static java.lang.System.out”--在这里没有什么令人激动的新特性，只是缩写而已。它让你可以不用Math而来完成math（数字计算）。 </span>
				</p>
				<p>
				</p>
				<pre>import static java.lang.Math.*;
import static java.lang.System.out;

public class Test {
  public static void main(String[] args) {
    out.println(abs(-1) * PI);
  }
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">注意，“static”关键字的重用是为了避免任何新的关键词。语言越成熟，对于“static”关键词的使用就越多。如果在静态成员之间发生冲突的话，就会出现含混的编译错误，这一点跟类的导入一样。是否将java.lang.Math.* 作为固有的导入引发了一定的争论，不过在获知其将会触发含混的错误之后，这种争论不会再发生了。</span>
				</p>
				<p>
						<span class="parahead1">Varargs</span>
				</p>
				<p>
						<span class="bodycopy">“varargs”表示“参数的变量”，存在于C语言中，并且支持通用的printf()和scanf()函数。 在Java中，我们通过编写一些接受Object[]、List、Properties（属性）的方法以及可以描述多个值的其它简单数据结构--比如说，Method.invoke（Object obj，Object[] args--来模拟这一特性。）。这要求调用程序将数据封装到这种单一的容器结构中。varargs允许调用程序传递值的任意列表，而编译器会为接收程序将其转化为数组。其语法就是在参数声明中的参数名之后添加“...”，以便使其成为vararg。它必须是最后一个参数--比如说，编写一个sum()函数以便将任意数量的整数相加： </span>
				</p>
				<p>
				</p>
				<pre>out.println(sum(1, 2, 3));

public static int sum(int args...) {
  int sum = 0;
  for (int x : args) { sum += x; }
  return sum;
}
</pre>
				<p>
				</p>
				<p>
						<span class="bodycopy">在抢鲜版本中，vararg符号使用方括号，就像sum(int[] args...)。然而，在之后的讨论中，根据James Gosling的提议，方括号被去掉了。在这里的例子中，我们不使用方括号，但是如果你需要在抢鲜版本中使用这些代码的话，就需要将方括号添加上去。借助autoboxing，可以通过接受一个Object args…，可以接受任何类型的参数，包括原始类型。这与printf()类型的一些方法一样，它们接受任何数量的所有类型的参数。实际上，该Tiger版本可以使用这一特性通过format方法（其行为与printf()一样）来提供一个Formattable（可格式化）的接口。这是我们在以后的文章中将要讨论的话题。目前，我们只编写简单的printf()：</span>
				</p>
				<p>
				</p>
				<pre>public static void printf(String fmt, 
                        int args...) {
  int i = 0;
  for (char c : fmt.toCharArray()) {
    out.print(c == '%' ? args[i++] : c);
  }
}

public static void main(String[] args) {
  printf("My values are % and %
", 
         1, 2);
}
<span class="bodycopy">在Tiger版本中，你会发现采用一个新格式的invoke（）函数： invoke(Object obj, Object args...). 这看上去更加自然。</span><p></p><p><span class="parahead1">结论</span></p><p><span class="bodycopy">J2SE1.5版努力使Java的编程更加简便、安全和更加富有表现力。这些特性和谐完美的被结合在一起。如果你跟我一样，总是喜欢用老的“for”循环，你肯定希<br />望你拥有Tiger。</span><span class="bodycopy">然而，需要记住的是，该规范并没有最终完成，很多地方还需要修改。管理这些变化的专家小组（JSR-14,JSR-175以及JSR-201）会在2003年<br />年末的beta版本发布之前，以及预期在2004年发布最终版发布之前，会做出很多修改。然而，Sun表达了对JavaOne的信心，认为总体上主要原则不会改变太多。<br />如果你想体验一下，那么你可以从下面的站点获取抢鲜版本。 从中你会找到可能从任何一个预览版软件都会遇到的错误，但是也会看到很多激动人心的新特性。<br />我强烈建议你尝试一下。</span></p></pre>
		</span>
<img src ="http://www.blogjava.net/dwys0343/aggbug/106471.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dwys0343/" target="_blank">特兰克斯</a> 2007-03-26 17:44 <a href="http://www.blogjava.net/dwys0343/articles/106471.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在JAVA中使用文档对象模型DOM经验小结</title><link>http://www.blogjava.net/dwys0343/articles/102363.html</link><dc:creator>特兰克斯</dc:creator><author>特兰克斯</author><pubDate>Wed, 07 Mar 2007 04:46:00 GMT</pubDate><guid>http://www.blogjava.net/dwys0343/articles/102363.html</guid><wfw:comment>http://www.blogjava.net/dwys0343/comments/102363.html</wfw:comment><comments>http://www.blogjava.net/dwys0343/articles/102363.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dwys0343/comments/commentRss/102363.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dwys0343/services/trackbacks/102363.html</trackback:ping><description><![CDATA[
		<div class="content" id="content">文档对象模型 (dom) 是一个文档标准，对于完备的文档和复杂的应用程序，dom 提供了大量灵活性。dom标准是标准的。它很强壮且完整，并且有许多实现。这是许多大型安装的决定因素--特别是对产品应用程序，以避免在api发生改变时进行大量的改写。 <br />以上是我在选择处理xml数据时之所以没有选择jdom或者dom4j等其它面向对象的标准的原因，不过也由于dom从一开始就是一种与语言无关的模型，而且它更趋向用于像c或perl这类语言，没有利用java的面向对象的性能，所以在使用的过程中也遇到了不少的麻烦，今天这里做一个小结。另外，我目前使用xml主要是作为数据传输的统一格式，并统一用户界面展示的接口，应用的面并不是很广，所以使用到的dom的内容其实不多。 <br />在准备使用它的时候，是做了充足的准备的，也有遇到困难的准备，所以一开始就有了一个简单的工具类来封装dom对象使用时必要的公共方法，实际证明这样做是很明智的，一个简单的创建document对象的操作，要是每次都需要写上5行以上代码，并且还要处理那些烦人的exception，实在是会打击大家的积极性，所以在最初，做了一个xmltool类，专门封装了如下的公共方法： <br />1、 document对象创建（包括空的document对象创建，以一个给定node节点作为根节点创建。 <br />2、 将一个规范的xml字符串转换成一个document对象。 <br />3、 从物理硬盘读取一个xml文件并返回一个document对象。 <br />4、 将一个node对象转换成字符串。 <br /><br />其中每个方法都截获相关的dom操作所抛出的异常，转换成一个runtimeexception抛出，这些异常在实际使用过程中，一般状况下其实都不会抛出，特别是象生成一个document对象时的parserconfigurationexception、转换node节点成字符串时要生成一个transformer对象时的transformerconfigurationexception等等，没有必要在它们身上花时间精力。而且真就出了相关的异常的话，其实根本没有办法处理，这样的状况通常是系统环境配置有问题（比如必要的dom实现解析器等包没有加入环境），所以包装该异常时只是很简要的获取其message抛出。 <br />代码如下： <br />/** <br />* 初始化一个空document对象返回。 <br />* @return a document <br />*/ <br />public static document newxmldocument() { <br />try { <br />return newdocumentbuilder().newdocument(); <br />} catch (parserconfigurationexception e) { <br />throw new runtimeexception(e.getmessage()); <br />} <br />} <br /><br />/** <br />* 初始化一个documentbuilder <br />* @return a documentbuilder <br />* @throws parserconfigurationexception <br />*/ <br />public static documentbuilder newdocumentbuilder() <br />throws parserconfigurationexception { <br />return newdocumentbuilderfactory().newdocumentbuilder(); <br />} <br /><br />/** <br />* 初始化一个documentbuilderfactory <br />* @return a documentbuilderfactory <br />*/ <br />public static documentbuilderfactory newdocumentbuilderfactory() { <br />documentbuilderfactory dbf = documentbuilderfactory.newinstance(); <br />dbf.setnamespaceaware(true); <br />return dbf; <br />} <br />/** <br />* 将传入的一个xml string转换成一个org.w3c.dom.document对象返回。 <br />* @param xmlstring 一个符合xml规范的字符串表达。 <br />* @return a document <br />*/ <br />public static document parsexmldocument(string xmlstring) { <br />if (xmlstring == null) { <br />throw new illegalargumentexception(); <br />} <br />try { <br />return newdocumentbuilder().parse( <br />new inputsource(new stringreader(xmlstring))); <br />} catch (exception e) { <br />throw new runtimeexception(e.getmessage()); <br />} <br />} <br /><br />/** <br />* 给定一个输入流，解析为一个org.w3c.dom.document对象返回。 <br />* @param input <br />* @return a org.w3c.dom.document <br />*/ <br />public static document parsexmldocument(inputstream input) { <br />if (input == null) { <br />throw new illegalargumentexception("参数为null！"); <br />} <br />try { <br />return newdocumentbuilder().parse(input); <br />} catch (exception e) { <br />throw new runtimeexception(e.getmessage()); <br />} <br />} <br />/** <br />* 给定一个文件名，获取该文件并解析为一个org.w3c.dom.document对象返回。 <br />* @param filename 待解析文件的文件名 <br />* @return a org.w3c.dom.document <br />*/ <br />public static document loadxmldocumentfromfile(string filename) { <br />if (filename == null) { <br />throw new illegalargumentexception("未指定文件名及其物理路径！"); <br />} <br />try { <br />return newdocumentbuilder().parse(new file(filename)); <br />} catch (saxexception e) { <br />throw new illegalargumentexception( <br />"目标文件（" + filename + "）不能被正确解析为xml！\n" + e.getmessage()); <br />} catch (ioexception e) { <br />throw new illegalargumentexception( <br />"不能获取目标文件（" + filename + "）！\n" + e.getmessage()); <br />} catch (parserconfigurationexception e) { <br />throw new runtimeexception(e.getmessage()); <br />} <br />} <br />/** <br />* 给定一个节点，将该节点加入新构造的document中。 <br />* @param node a document node <br />* @return a new document <br />*/ <br />public static document newxmldocument(node node) { <br />document doc = newxmldocument(); <br />doc.appendchild(doc.importnode(node, true)); <br />return doc; <br />} <br /><br />/** <br />* 将传入的一个dom node对象输出成字符串。如果失败则返回一个空字符串""。 <br />* @param node dom node 对象。 <br />* @return a xml string from node <br />*/ <br />public static string tostring(node node) { <br />if (node == null) { <br />throw new illegalargumentexception(); <br />} <br />transformer transformer = newtransformer(); <br />if (transformer != null) { <br />try { <br />stringwriter sw = new stringwriter(); <br />transformer.transform( <br />new domsource(node), <br />new streamresult(sw)); <br />return sw.tostring(); <br />} catch (transformerexception te) { <br />throw new runtimeexception(te.getmessage()); <br /><br />} <br />} <br />return errxmlstring("不能生成xml信息！"); <br />} <br />/** <br />* 将传入的一个dom node对象输出成字符串。如果失败则返回一个空字符串""。 <br />* @param node dom node 对象。 <br />* @return a xml string from node <br />*/ <br />public static string tostring(node node) { <br />if (node == null) { <br />throw new illegalargumentexception(); <br />} <br />transformer transformer = newtransformer(); <br />if (transformer != null) { <br />try { <br />stringwriter sw = new stringwriter(); <br />transformer.transform( <br />new domsource(node), <br />new streamresult(sw)); <br />return sw.tostring(); <br />} catch (transformerexception te) { <br />throw new runtimeexception(te.getmessage()); <br /><br />} <br />} <br />return errxmlstring("不能生成xml信息！"); <br />} <br />/** <br />* 获取一个transformer对象，由于使用时都做相同的初始化，所以提取出来作为公共方法。 <br />* @return a transformer encoding gb2312 <br />*/ <br />public static transformer newtransformer() { <br />try { <br />transformer transformer = <br />transformerfactory.newinstance().newtransformer(); <br />properties properties = transformer.getoutputproperties(); <br />properties.setproperty(outputkeys.encoding, "gb2312"); <br />properties.setproperty(outputkeys.method, "xml"); <br />properties.setproperty(outputkeys.version, "1.0"); <br />properties.setproperty(outputkeys.indent, "no"); <br />transformer.setoutputproperties(properties); <br />return transformer; <br />} catch (transformerconfigurationexception tce) { <br />throw new runtimeexception(tce.getmessage()); <br />} <br />} <br />/** <br />* 返回一段xml表述的错误信息。提示信息的title为：系统错误。之所以使用字符串拼装，主要是这样做一般 <br />* 不会有异常出现。 <br />* @param errmsg 提示错误信息 <br />* @return a xml string show err msg <br />*/ <br />public static string errxmlstring(string errmsg) { <br />stringbuffer msg = new stringbuffer(100); <br />msg.append("&lt;?xml version=\"1.0\" encoding=\"gb2312\" ?&gt;"); <br />msg.append("&lt;errnode title=\"系统错误\" errmsg=\"" + errmsg + "\"/&gt;"); <br />return msg.tostring(); <br />} <br />/** <br />* 返回一段xml表述的错误信息。提示信息的title为：系统错误 <br />* @param errmsg 提示错误信息 <br />* @param errclass 抛出该错误的类，用于提取错误来源信息。 <br />* @return a xml string show err msg <br />*/ <br />public static string errxmlstring(string errmsg, class errclass) { <br />stringbuffer msg = new stringbuffer(100); <br />msg.append("&lt;?xml version=\"1.0\" encoding=\"gb2312\" ?&gt;"); <br />msg.append( <br />"&lt;errnode title=\"系统错误\" errmsg=\"" <br />+ errmsg <br />+ "\" errsource=\"" <br />+ errclass.getname() <br />+ "\"/&gt;"); <br />return msg.tostring(); <br />} <br />/** <br />* 返回一段xml表述的错误信息。 <br />* @param title 提示的title <br />* @param errmsg 提示错误信息 <br />* @param errclass 抛出该错误的类，用于提取错误来源信息。 <br />* @return a xml string show err msg <br />*/ <br />public static string errxmlstring( <br />string title, <br />string errmsg, <br />class errclass) { <br />stringbuffer msg = new stringbuffer(100); <br />msg.append("&lt;?xml version=\"1.0\" encoding=\"gb2312\" ?&gt;"); <br />msg.append( <br />"&lt;errnode title=\"" <br />+ title <br />+ "\" errmsg=\"" <br />+ errmsg <br />+ "\" errsource=\"" <br />+ errclass.getname() <br />+ "\"/&gt;"); <br />return msg.tostring(); <br />} <br /><br />以上都是dom的基本应用，所以就不一一详细说明了。 <br />在实际使用过程中，有几种状况使用很频繁，但是dom的接口的设计却使该操作很麻烦，所以分别添加了相应的处理方法。 <br />其中最麻烦的要数获取一个节点的text子节点文本信息了，如下的xml节点： <br />&lt;element&gt; <br />text <br />&lt;/element&gt; <br />在拥有element节点对象时，要获取其中的文本信息"text"，首先要获取element节点的子节点列表，要判断其是否存在子节点，如果存在，那么遍历其子节点找到一个textnode节点，通过getnodevalue()方法来获取该文本信息，由于这里element节点没有信息时没有子节点，所以必须判断element节点是否存在子节点才能去访问真正包含了文本信息的textnode节点，那么如果要处理的数据都是以这种形式给出的，就会增加大量的开发代码同时让开发工作枯燥无味，因此这里使用了一个默认的约定实现，就是，给出了一个公共方法，该方法取给定node下的直接子节点的text节点文本信息，如果不存在text节点则返回null，这个约定虽然使该方法的使用有所限制，也可能导致错误使用该方法，但是，按实际使用的状况来看，这样的约定和使用方式是没有问题的，因为实际用到的都是上面举的例子的状况，代码： <br />/** <br />* 这个方法获取给定node下的text节点文本信息，如果不存在text节点则返回null。 <br />* 注意：是直接子节点，相差2层或2层以上不会被考虑。 <br />* @param node a node 一个node。 <br />* @return a string 如果给定节点存在text子节点，则返回第一个访问到的text子节点文本信息，如果不存在则返回null。 <br />*/ <br />public static string getnodevalue(node node) { <br />if (node == null) { <br />return null; <br />} <br /><br />text text = gettextnode(node); <br /><br />if (text != null) { <br />return text.getnodevalue(); <br />} <br /><br />return null; <br />} <br /><br />/** <br />* 这个方法获取给定node下的text节点，如果不存在text节点则返回null。 <br />* 注意：是直接子节点，相差2层或2层以上不会被考虑。 <br />* @param node a node 一个node。 <br />* @return a text 如果给定节点存在text子节点，则返回第一个访问到的text子节点，如果不存在则返回null。 <br />*/ <br />public static text gettextnode(node node) { <br />if (node == null) { <br />return null; <br />} <br />if (node.haschildnodes()) { <br />nodelist list = node.getchildnodes(); <br />for (int i = 0; i &lt; list.getlength(); i++) { <br />if (list.item(i).getnodetype() == node.text_node) { <br />return (text) list.item(i); <br />} <br />} <br />} <br />return null; <br />} <br /><br />上面代码将获取给定node节点的直接text子节点分开包装。 <br /><br />另一个很经常碰到的状况是，我希望直接定位到目标节点，获取该节点对象，而不需要通过一层一层的节点遍历来找到目标节点，dom2接口中至少提供了如下的方式来定位节点： <br />1、 对于document对象： <br />1） getdocumentelement()――获取根节点对象，实际很少使用的，因为根节点基本也就只是根节点而已，实际的数据节点都是根节点下的直接子节点开始的。 <br />2） getelementbyid(string elementid)――这个方法本来应该是一个最佳的定位方法，但是在实际使用过程中没有被我使用，其主要原因就是，这里的"id"不同于一个节点的属性"id"，这在org.w3c.dom.document的api说明中是明确指出，而我找了不少的资料也没有看到有关的使用方式，所以只好放弃了。 <br />3） getelementsbytagname(string tagname)――这个方法其实是没有办法的选择，只好用它了，不过实际倒也很合用，虽然该方法返回的是一个nodelist，但是实际使用时，将节点的tagname设计成特殊字符串，那么就可以直接获取了，而实际使用时，其实也差不多，很多时候会直接拿数据库中的字段名来作为tagname，以方便得获取该字段得值，在一个简单得约定下，使用了如下方法： <br />/** <br />* 这个方法检索参数element下所有tagname为：tagname的节点，并返回节点列表的第一个节点。 <br />* 如果不存在该tagname的节点，则返回null。 <br />* @param element 待搜索节点 <br />* @param tagname 待搜索标签名 <br />* @return a element 获得以tagname为标签名的节点列表的第一个节点。 <br />*/ <br />public static element getfirstelementbyname( <br />element element, <br />string tagname) { <br />return (element) getfirstelement(element.getelementsbytagname(tagname)); <br />} <br />/** <br />* 从给定节点列表中获取第一个节点返回，如果节点集合为null/空，则返回null。 <br />* @param nodelist a nodelist <br />* @return a node <br />*/ <br />private static node getfirstelement(nodelist nodelist) { <br />if (nodelist == null || nodelist.getlength() == 0) { <br />return null; <br />} <br />return nodelist.item(0); <br />} <br />这个约定看似限制很大，其实实际使用时基本都是这样的，只要获取第一个给定tagname的element节点就可以了的。 <br />4）getelementsbytagnamens(string namespaceuri, string localname)――这个方法基本没有使用，因为还没有碰到需要使用命名空间的状况。 <br />2、 对于element对象――――element对象和document对象雷同，少了getdocumentelement()方法，不过和document一样也都是主要使用getelementsbytagname()方法。 <br />3、 其它的节点对象基本没有直接定位的访问方法 <br /><br />还有一种，是由于dom2的限制导致的，dom2规范中，不能将一个document doca的节点直接加入到另一个document docb对象的节点的子节点列表中，要这么做必须首先将doca的节点通过docb的importnode方法转换后在添加到目标节点的子节点列表中，所以也有一个方法来统一处理： <br />/** <br />* 这个方法将参数appendeddoc的根节点及其以下节点附加到doc的跟节点下面。 <br />* 作为doc的跟节点的作后一个子节点。 <br />* 相当于：doc.appenddoc(appendeddoc); <br />* @param doc a document <br />* @param appendeddoc a document <br />*/ <br />public static void appendxmldocument(document doc, document appendeddoc) { <br />if (appendeddoc != null) { <br />doc.getfirstchild().appendchild( <br />doc.importnode(appendeddoc.getfirstchild(), true)); <br />} <br />} <br />/** <br />* 这个方法将参数appendeddoc的根节点及其以下节点附加到node节点下面。 <br />* 作为node节点的作后一个子节点。 <br />* 相当于：node.appenddoc(appendednode); <br />* @param node 待添加的节点将被添加到该节点的最后。 <br />* @param appendednode a node 这个节点将被添加作为node节点的最后一个子节点。 <br />*/ <br />public static void appendxmldocument(node node, node appendednode) { <br />if (appendednode == null) { <br />return; <br />} <br />if (appendednode instanceof document) { <br />appendednode = ((document) appendednode).getdocumentelement(); <br />} <br />node.appendchild( <br />node.getownerdocument().importnode(appendednode, true)); <br />} <br /><br />基本上就这些常用的了，其它还有一些零碎的方法，不过都不常用到，就不作介绍了。另外要说的是，如果哪位知道上面说到的getelementbyid()方法的具体可行的方便用法，也请指教下。 </div>
<img src ="http://www.blogjava.net/dwys0343/aggbug/102363.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dwys0343/" target="_blank">特兰克斯</a> 2007-03-07 12:46 <a href="http://www.blogjava.net/dwys0343/articles/102363.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java的简单数据类型</title><link>http://www.blogjava.net/dwys0343/articles/95718.html</link><dc:creator>特兰克斯</dc:creator><author>特兰克斯</author><pubDate>Wed, 24 Jan 2007 05:36:00 GMT</pubDate><guid>http://www.blogjava.net/dwys0343/articles/95718.html</guid><wfw:comment>http://www.blogjava.net/dwys0343/comments/95718.html</wfw:comment><comments>http://www.blogjava.net/dwys0343/articles/95718.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dwys0343/comments/commentRss/95718.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dwys0343/services/trackbacks/95718.html</trackback:ping><description><![CDATA[　　数据类型就是对内存位置的抽象表达。程序员可以利用多种数据类型：某些由编程语言定义，某些由外部库定义，还有些则由程序员来定义。很多编程语言都依赖于特定的计算机类型和对数据类型属性的具体编译实现，比如word和integer数据类型的大小等。另一方面，Java的虚拟机负责定义其内置数据类型的各方面内容。这就意味着不管Java虚拟机（JVM）运行在何等低级的操作系统之上，数据类型的属性都是完全一样的。 <br /><br />　　<b><font color="#0000ff">简单数据类型</font></b><br /><br />　　简单数据类型是不能再简化的、内置的数据类型，由编程语言定义，表示真实的数字、字符和整数。更大、更复杂的数据类型可以采用简单数据类型的组合来定义。在大多数情况下，简单数据类型都具有其硬件等价物。比方说，int简单类型有时存放在32位硬件寄存器内。Java提供了几类简单数据类型表示数字和字符。 <br /><br />　　简单数据类型通常划分为以下几种类别：实数、整数、字符和布尔值。这些类别中又包含了多种简单类型。比如说，Java定义了两种简单类型：float和double，它们都属于实数类别，另外4种简单类型：byte、short、int和long则都属于整数类别。此外还有一种简单类型char则归于字符类型。布尔值类别只有一种简单类型：boolean。表A详细列出了Java的简单数据类型. <br /><br />　　<b><font color="#0000ff">表A Java简单数据类型</font></b><br /><br />　　简单类型 大小 范围/精度 <br /><br />　　float 4 字节 32位IEEE 754单精度 <br /><br />　　double 8 字节 64位IEEE 754双精度 <br /><br />　　byte 1字节 -128到127 <br /><br />　　short 2 字节 -32,768到32,767 <br /><br />　　int 4 字节 -2,147,483,648到2,147,483,647 <br /><br />　　long 8 字节 -9,223,372,036,854,775,808到9,223,372,036, 854,775,807 <br /><br />　　char 2 字节 整个Unicode字符集 <br /><br />　　boolean 1 位 True或者false <br /><br />　　<b><font color="#0000ff">Java的简单数据类型</font></b><br /><br />　　Java中的所有数字变量都是有符号的，Java不允许数据类型之间随意的转换。只有数字变量之间可以进行类型转换。比如，boolean就不能转换为其他数据类型，而且其他数据类型也不能转换为boolean。 <br /><br />　　因为Java的简单数据类型都经过准确定义，而且直接内存访问也是不允许的，所以在Java语言中取消了sizeof运算符。 <br /><br />　　Java的简单数据类型并不是对象。为了采用面向对象方式对待Java简单数据类型，你需要首先用类封装它们。 <br /><br />　　<b><font color="#0000ff">封装类</font></b><br /><br />　　Java还提供了Byte、Short、Boolean、Character、Integer、Double、Float和Long等内置的封装类。这些封装（wrapper）类提供了很直观的实用方法。比如，Byte、Float, Integer、Long和Double类都具有doubleValue()方法，通过它可以把存储在类的实例中的值转换为Double类型。还有，所有的封装类都提供了静态的valueOf(String s)方法把给定的String转换为对应的简单类型。清单A的代码演示了这些封装类的一些用法。 <br /><br />　　<b><font color="#0000ff">简单数据类型初始化</font></b><br /><br />　　在Java语言中，简单数据类型作为类的成员变量声明时自动初始化为默认值，除非显式地声明。简单数据类型为某一方法声明局部变量时不会自动地初始化而且会导致编译器扔出类似以下的错误消息“Variable x may not have been initialized.（x变量没有初始化）”表B定义了Java简单数据类型的默认值。 <br /><br />　　表B Java简单数据类型的默认值 <br /><br />　　类型 <br /><br />　　默认值 <br /><br />　　boolean <br /><br />　　false <br /><br />　　Byte <br /><br />　　0 <br /><br />　　short <br /><br />　　0 <br /><br />　　int <br /><br />　　0 <br /><br />　　Long <br /><br />　　0 <br /><br />　　Char <br /><br />　　´\u0000´ <br /><br />　　Float <br /><br />　　0.0 <br /><br />　　double <br /><br />　　0.0 <br /><br />　　<b><font color="#0000ff">Java简单数据类型的默认初始值</font></b><br /><br />　　清单B 中的代码显示所有的Java简单数据类型都用做了Initialization类的成员变量。该例还显示Initialization类的构造器中局部声明了一个int变量。在不修改以上代码的情况下，编译器会在对以上代码进行编译的时候扔出错误。 <br />　　一旦引发问题的代码行（那些引用未初始化变量导致错误发生的代码）被删除或者注释掉。程序成功编译和执行之后就会显示以下的结果： <br /><br />　　byte: 0 <br /><br />　　short: 0 <br /><br />　　int: 0 <br /><br />　　long: 0 <br /><br />　　float: 0.0 <br /><br />　　double: 0.0 <br /><br />　　char: 0 <br /><br />　　boolean: false <br /><br />　　我们还可以显式地初始化成员变量为其他值，如以下代码所示： <br /><br />　　byte b = 5; <br /><br />　　short s = 123; <br /><br />　　int i = 1234; <br /><br />　　long l = 12345; <br /><br />　　float f = 123.45f; <br /><br />　　double d = 12345.678; <br /><br />　　char c = ´A´; <br /><br />　　boolean z = true; <br /><br />　　<b><font color="#0000ff">小结</font></b><br /><br />　　Java定义了全套简单数据类型。此外，Java取消了其他变成语言中的硬件和编译器依附，允许程序员把注意力转移到其他问题上来。在下一篇文章里，我们将继续讨论某些经常用到的、Java定义的复杂数据类型，同时了解下如何利用它们来处理典型的编程问题。 <img src ="http://www.blogjava.net/dwys0343/aggbug/95718.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dwys0343/" target="_blank">特兰克斯</a> 2007-01-24 13:36 <a href="http://www.blogjava.net/dwys0343/articles/95718.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java系统中内存泄漏测试方法的研究</title><link>http://www.blogjava.net/dwys0343/articles/90981.html</link><dc:creator>特兰克斯</dc:creator><author>特兰克斯</author><pubDate>Sat, 30 Dec 2006 04:16:00 GMT</pubDate><guid>http://www.blogjava.net/dwys0343/articles/90981.html</guid><description><![CDATA[
		<strong>
				<font color="#a52a2a">摘 要</font>
		</strong>
		<br />稳定性是衡量软件系统质量的重要指标，内存泄漏是破坏系统稳定性的重要因素。由于采用垃圾回收机制，Java语言的内存泄漏的模式与C++等语言相比有很大的不同。全文通过与C++中的内存泄漏问题进行对比，讲述了Java内存泄漏的基本原理，以及如何借助Optimizeit profiler工具来测试内存泄漏和分析内存泄漏的原因，在实践中证明这是一套行之有效的方法。 
<p><strong><font color="#0000ff">关键词</font></strong> Java; 内存泄漏; GC(垃圾收集器) 引用; Optimizeit<br /><br /></p><p><strong><font color="#0000ff">问题的提出</font></strong></p><p>　　笔者曾经参与开发的网管系统，系统规模庞大，涉及上百万行代码。系统主要采用Java语言开发，大体上分为客户端、服务器和数据库三个层次。在版本进入测试和试用的过程中，现场人员和测试部人员纷纷反映:系统的稳定性比较差，经常会出现服务器端运行一昼夜就死机的现象，客户端跑死的现象也比较频繁地发生。对于网管系统来讲，经常性的服务器死机是个比较严重的问题，因为频繁的死机不仅可能导致前后台数据不一致，发生错误，更会引起用户的不满，降低客户的信任度。因此，服务器端的稳定性问题必须尽快解决。</p><p><strong><font color="#0000ff">解决思路</font></strong></p><p>　　通过察看服务器端日志，发现死机前服务器端频繁抛出OutOfMemoryException内存溢出错误，因此初步把死机的原因定位为内存泄漏引起内存不足，进而引起内存溢出错误。如何查找引起内存泄漏的原因呢?有两种思路:第一种，安排有经验的编程人员对代码进行走查和分析，找出内存泄漏发生的位置;第二种，使用专门的内存泄漏测试工具Optimizeit进行测试。这两种方法都是解决系统稳定性问题的有效手段，使用内存测试工具对于已经暴露出来的内存泄漏问题的定位和解决非常有效;但是软件测试的理论也告诉我们，系统中永远存在一些没有暴露出来的问题，而且，系统的稳定性问题也不仅仅只是内存泄漏的问题，代码走查是提高系统的整体代码质量乃至解决潜在问题的有效手段。基于这样的考虑，我们的内存稳定性工作决定采用代码走查结合测试工具的使用，双管齐下，争取比较彻底地解决系统的稳定性问题。</p><p>　　在代码走查的工作中，安排了对系统业务和开发语言工具比较熟悉的开发人员对应用的代码进行了交叉走查，找出代码中存在的数据库连接声明和结果集未关闭、代码冗余和低效等故障若干，取得了良好的效果，文中主要讲述结合工具的使用对已经出现的内存泄漏问题的定位方法。</p><p><strong><font color="#0000ff">内存泄漏的基本原理<br /></font></strong></p>在C++语言程序中，使用new操作符创建的对象，在使用完毕后应该通过delete操作符显示地释放，否则，这些对象将占用堆空间，永远没有办法得到回收，从而引起内存空间的泄漏。如下的简单代码就可以引起内存的泄漏:<code><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e6e6e3" border="1"><tbody><tr><td>void function(){<br />　Int[] vec = new int[5];<br />}</td></tr></tbody></table></code><p>　　在function()方法执行完毕后，vec数组已经是不可达对象，在C++语言中，这样的对象永远也得不到释放，称这种现象为内存泄漏。</p><p>　　而Java是通过垃圾收集器(Garbage Collection，GC)自动管理内存的回收，程序员不需要通过调用函数来释放内存，但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。在下面的代码中，循环申请Object对象，并将所申请的对象放入一个Vector中，如果仅仅释放对象本身，但是因为Vector仍然引用该对象，所以这个对象对GC来说是不可回收的。因此，如果对象加入到Vector后，还必须从Vector中删除，最简单的方法就是将Vector对象设置为null。</p><p></p><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e6e6e3" border="1"><tbody><tr><td>Vector v = new Vector(10);<br />for (int i = 1; i &lt; 100; i++)<br />{<br />　Object o = new Object();<br />　v.add(o);<br />　o = null;<br />}//此时，所有的Object对象都没有被释放，因为变量v引用这些对象。</td></tr></tbody></table><p>　　实际上无用，而还被引用的对象，GC就无能为力了(事实上GC认为它还有用)，这一点是导致内存泄漏最重要的原因。</p><p>　　Java的内存回收机制可以形象地理解为在堆空间中引入了重力场，已经加载的类的静态变量和处于活动线程的堆栈空间的变量是这个空间的牵引对象。这里牵引对象是指按照Java语言规范，即便没有其它对象保持对它的引用也不能够被回收的对象，即Java内存空间中的本原对象。当然类可能被去加载，活动线程的堆栈也是不断变化的，牵引对象的集合也是不断变化的。对于堆空间中的任何一个对象，如果存在一条或者多条从某个或者某几个牵引对象到该对象的引用链，则就是可达对象，可以形象地理解为从牵引对象伸出的引用链将其拉住，避免掉到回收池中;而其它的不可达对象由于不存在牵引对象的拉力，在重力的作用下将掉入回收池。在图1中，A、B、C、D、E、F六个对象都被牵引对象所直接或者间接地“牵引”，使得它们避免在重力的作用下掉入回收池。如果TR1-A链和TR2-D链断开，则A、B、C三个对象由于失去牵引，在重力的作用下掉入回收池(被回收)，D对象也是同样的原因掉入回收池，而F对象仍然存在一个牵引链(TR3-E-F)，所以不会被回收，如图2、3所示。<br /><span lang="EN-US" style="FONT-SIZE: 8pt; COLOR: black; FONT-FAMILY: Ђˎ̥; mso-bidi-font-family: 宋体; mso-fareast-font-family: 宋体; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA"><?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /?><v:shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><font face="宋体"> <font size="3">　　<img height="153" alt="Java系统中内存泄漏测试方法的研究（图一）" src="http://www.qqread.com/ArtImage/20060926/nl118_1.jpg" width="287" border="0" /><br />　　图1 初始状态<br /><br />　　<img height="145" alt="Java系统中内存泄漏测试方法的研究（图二）" src="http://www.qqread.com/ArtImage/20060926/nl118_2.jpg" width="294" border="0" /><br />　　图2 TR1-A链和TR2-D链断开，A、B、C、D掉入回收池<br /><br />　　<img height="154" alt="Java系统中内存泄漏测试方法的研究（图三）" src="http://www.qqread.com/ArtImage/20060926/nl118_3.jpg" width="290" border="0" /><br />　　图3 A、B、C、D四个对象被回收</font></font></v:shapetype></span></p><p>　　通过前面的介绍可以看到，由于采用了垃圾回收机制，任何不可达对象都可以由垃圾收集线程回收。因此通常说的Java内存泄漏其实是指无意识的、非故意的对象引用，或者无意识的对象保持。无意识的对象引用是指代码的开发人员本来已经对对象使用完毕，却因为编码的错误而意外地保存了对该对象的引用(这个引用的存在并不是编码人员的主观意愿)，从而使得该对象一直无法被垃圾回收器回收掉，这种本来以为可以释放掉的却最终未能被释放的空间可以认为是被“泄漏了”。<br />这里通过一个例子来演示Java的内存泄漏。假设有一个日志类Logger，其提供一个静态的log(String msg)方法，任何其它类都可以调用Logger.Log(message)来将message的内容记录到系统的日志文件中。Logger类有一个类型为HashMap的静态变量temp，每次在执行log(message)方法的时候，都首先将message的值丢入temp中(以当前线程+当前时间为键)，在方法退出之前再从temp中将以当前线程和当前时间为键的条目删除。注意，这里当前时间是不断变化的，所以log方法在退出之前执行删除条目的操作并不能删除方法执行之初丢入的条目。这样，任何一个作为参数传给log方法的字符串最终由于被Logger的静态变量temp引用，而无法得到回收，这种违背实现者主观意图的无意识的对象保持就是我们所说的Java内存泄漏。</p><p><font color="#0000ff">　　<strong>鉴别泄漏对象的方法</strong></font></p><p>　　一般说来，一个正常的系统在其运行稳定后其内存的占用量是基本稳定的，不应该是无限制的增长的，同样，对任何一个类的对象的使用个数也有一个相对稳定的上限，不应该是持续增长的。根据这样的基本假设，我们可以持续地观察系统运行时使用的内存的大小和各实例的个数，如果内存的大小持续地增长，则说明系统存在内存泄漏，如果某个类的实例的个数持续地增长，则说明这个类的实例可能存在泄漏情况。</p><p>　　Optimizeit是Borland公司的产品，主要用于协助对软件系统进行代码优化和故障诊断，其功能众多，使用方便，其中的OptimizeIt Profiler主要用于内存泄漏的分析。Profiler的堆视图(如图4)就是用来观察系统运行使用的内存大小和各个类的实例分配的个数的，其界面如图四所示，各列自左至右分别为类名称、当前实例个数、自上个标记点开始增长的实例个数、占用的内存空间的大小、自上次标记点开始增长的内存的大小、被释放的实例的个数信息、自上次标记点开始增长的内存的大小被释放的实例的个数信息，表的最后一行是汇总数据，分别表示目前JVM中的对象实例总数、实例增长总数、内存使用总数、内存使用增长总数等。</p><p>　　在实践中，可以分别在系统运行四个小时、八个小时、十二个小时和二十四个小时时间点记录当时的内存状态(即抓取当时的内存快照，是工具提供的功能，这个快照也是供下一步分析使用)，找出实例个数增长的前十位的类，记录下这十个类的名称和当前实例的个数。在记录完数据后，点击Profiler中右上角的Mark按钮，将该点的状态作为下一次记录数据时的比较点。</p><p>　　　<img height="240" alt="Java系统中内存泄漏测试方法的研究（图四）" src="http://www.qqread.com/ArtImage/20060926/nl118_4.jpg" width="328" border="0" /><br />　　图4 Profiler 堆视图<br /><br />系统运行二十四小时以后可以得到四个内存快照。对这四个内存快照进行综合分析，如果每一次快照的内存使用都比上一次有增长，可以认定系统存在内存泄漏，找出在四个快照中实例个数都保持增长的类，这些类可以初步被认定为存在泄漏。</p><p><font color="#0000ff">　　<strong>分析与定位</strong></font></p><p>　　通过上面的数据收集和初步分析，可以得出初步结论:系统是否存在内存泄漏和哪些对象存在泄漏(被泄漏)，如果结论是存在泄漏，就可以进入分析和定位阶段了。</p><p>　　前面已经谈到Java中的内存泄漏就是无意识的对象保持，简单地讲就是因为编码的错误导致了一条本来不应该存在的引用链的存在(从而导致了被引用的对象无法释放)，因此内存泄漏分析的任务就是找出这条多余的引用链，并找到其形成的原因。前面还讲到过牵引对象，包括已经加载的类的静态变量和处于活动线程的堆栈空间的变量。由于活动线程的堆栈空间是迅速变化的，处于堆栈空间内的牵引对象集合是迅速变化的，而作为类的静态变量的牵引对象的集合在系统运行期间是相对稳定的。</p><p>　　对每个被泄漏的实例对象，必然存在一条从某个牵引对象出发到达该对象的引用链。处于堆栈空间的牵引对象在被从栈中弹出后就失去其牵引的能力，变为非牵引对象，因此，在长时间的运行后，被泄露的对象基本上都是被作为类的静态变量的牵引对象牵引。</p><p>　　Profiler的内存视图除了堆视图以外，还包括实例分配视图(图5)和实例引用图(图6)。</p><p>　　Profiler的实例引用图为找出从牵引对象到泄漏对象的引用链提供了非常直接的方法，其界面的第二个栏目中显示的就是从泄漏对象出发的逆向引用链。需要注意的是，当一个类的实例存在泄漏时，并非其所有的实例都是被泄漏的，往往只有一部分是被泄漏对象，其它则是正常使用的对象，要判断哪些是正常的引用链，哪些是不正常的引用链(引起泄漏的引用链)。通过抽取多个实例进行引用图的分析统计以后，可以找出一条或者多条从牵引对象出发的引用链，下面的任务就是找出这条引用链形成的原因。</p><p>　　实例分配图提供的功能是对每个类的实例的分配位置进行统计，查看实例分配的统计结果对于分析引用链的形成具有一定的作用，因为找到分配链与引用链的交点往往就可以找到了引用链形成的原因，下面将具体介绍。</p><p>　　<img height="241" alt="Java系统中内存泄漏测试方法的研究（图五）" src="http://www.qqread.com/ArtImage/20060926/nl118_5.jpg" width="326" border="0" /><br />　　图5 实例分配图<br /><br />　　<img height="240" alt="Java系统中内存泄漏测试方法的研究（图六）" src="http://www.qqread.com/ArtImage/20060926/nl118_6.jpg" width="325" border="0" /><br />　　图6 实例引用图<br /></p><p>　　设想一个实例对象a在方法f中被分配，最终被实例对象b所引用，下面来分析从b到a的引用链可能的形成原因。方法f在创建对象a后，对它的使用分为四种情况:1、将a作为返回值返回;2、将a作为参数调用其它方法;3、在方法内部将a的引用传递给其它对象;4、其它情况。其中情况4不会造成由b到a的引用链的生成，不用考虑。下面考虑其它三种情况:对于1、2两种情况，其造成的结果都是在另一个方法内部获得了对象a的引用，它的分析与方法f的分析完全一样(递归分析);考虑第3种情况:1、假设方法f直接将对象a的引用加入到对象b，则对象b到a的引用链就找到了，分析结束;2、假设方法f将对象a的引用加入到对象c，则接下来就需要跟踪对象c的使用，对象c的分析比对象a的分析步骤更多一些，但大体原理都是一样的，就是跟踪对象从创建后被使用的历程，最终找到其被牵引对象引用的原因。</p><p>　　现在将泄漏对象的引用链以及引用链形成的原因找到了，内存泄漏测试与分析的工作就到此结束，接下来的工作就是修改相应的设计或者实现中的错误了。</p><p><font color="#0000ff">　　<strong>总结</strong></font></p><p>　　使用上述的测试和分析方法，在实践中先后进行了三次测试，找出了好几处内存泄漏错误。系统的稳定性得到很大程度的提高，最初运行1~2天就抛出内存溢出异常，修改完成后，系统从未出现过内存溢出异常。此方法适用于任何使用Java语言开发的、对稳定性有比较高要求的软件系统。</p><img src ="http://www.blogjava.net/dwys0343/aggbug/90981.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dwys0343/" target="_blank">特兰克斯</a> 2006-12-30 12:16 <a href="http://www.blogjava.net/dwys0343/articles/90981.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>编写安全的Java代码</title><link>http://www.blogjava.net/dwys0343/articles/90818.html</link><dc:creator>特兰克斯</dc:creator><author>特兰克斯</author><pubDate>Fri, 29 Dec 2006 14:37:00 GMT</pubDate><guid>http://www.blogjava.net/dwys0343/articles/90818.html</guid><wfw:comment>http://www.blogjava.net/dwys0343/comments/90818.html</wfw:comment><comments>http://www.blogjava.net/dwys0343/articles/90818.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dwys0343/comments/commentRss/90818.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dwys0343/services/trackbacks/90818.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 本文是来自				Sun				官方站点的一篇关于如何编写安全的				Java				代码的指南				,				开发者在编写一般代码时，可以参照本文的指南：																								•        				静态字段										•        				缩小作用域										•      ...&nbsp;&nbsp;<a href='http://www.blogjava.net/dwys0343/articles/90818.html'>阅读全文</a><img src ="http://www.blogjava.net/dwys0343/aggbug/90818.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dwys0343/" target="_blank">特兰克斯</a> 2006-12-29 22:37 <a href="http://www.blogjava.net/dwys0343/articles/90818.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java入门需掌握的30个基本概念</title><link>http://www.blogjava.net/dwys0343/articles/90816.html</link><dc:creator>特兰克斯</dc:creator><author>特兰克斯</author><pubDate>Fri, 29 Dec 2006 14:32:00 GMT</pubDate><guid>http://www.blogjava.net/dwys0343/articles/90816.html</guid><wfw:comment>http://www.blogjava.net/dwys0343/comments/90816.html</wfw:comment><comments>http://www.blogjava.net/dwys0343/articles/90816.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dwys0343/comments/commentRss/90816.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dwys0343/services/trackbacks/90816.html</trackback:ping><description><![CDATA[
		<p>
				<font color="#a52a2a" size="5">
						<strong>Java的白皮书为我们提出了Java语言的11个关键特性</strong>
				</font>
		</p>
		<p>　　(1)Easy:Java的语法比C++的相对简单，另一个方面就是Java能使软件在很小的机器上运行，基础解释其和类库的支持的大小约为40kb，增加基本的标准库和线程支持的内存需要增加125kb。</p>
		<p>　　(2)分布式:Java带有很强大的TCP/IP协议族的例程库，Java应用程序能够通过URL来穿过网络来访问远程对象，由于servlet机制的出现，使Java编程非常的高效，现在许多的大的web server都支持servlet。</p>
		<p>　　(3)OO:面向对象设计是把重点放在对象及对象的接口上的一个编程技术.其面向对象和C++有很多不同，在与多重继承的处理及Java的原类模型。</p>
		<p>　　(4)健壮特性:Java采取了一个安全指针模型，能减小重写内存和数据崩溃的可能型。</p>
		<p>　　(5)安全:Java用来设计网路和分布系统，这带来了新的安全问题，Java可以用来构建防病毒和防攻击的System.事实证明Java在防毒这一方面做的比较好。</p>
		<p>　　(6)中立体系结构:Java编译其生成体系结构中立的目标文件格式可以在很多处理器上执行，编译器产生的指令字节码(Javabytecode)实现此特性，此字节码可以在任何机器上解释执行。</p>
		<p>　　(7)可移植性:Java中对基本数据结构类型的大小和算法都有严格的规定所以可移植性很好。</p>
		<p>　　(8)多线程:Java处理多线程的过程很简单，Java把多线程实现交给底下操作系统或线程程序完成.所以多线程是Java作为服务器端开发语言的流行原因之一。</p>
		<p>　　(9)Applet和servlet:能够在网页上执行的程序叫Applet，需要支持Java的浏览器很多，而applet支持动态的网页，这是很多其他语言所不能做到的。</p>
		<p>
				<font color="#0000ff" size="4">　　基本概念</font>
		</p>
		<p>　　<font color="#ff1493">1</font>.OOP中唯一关系的是对象的接口是什么，就像计算机的销售商她不管电源内部结构是怎样的，他只关系能否给你提供电就行了，也就是只要知道can or not而不是how and why.所有的程序是由一定的属性和行为对象组成的，不同的对象的访问通过函数调用来完成，对象间所有的交流都是通过方法调用，通过对封装对象数据，很大限度上提高复用率。</p>
		<p>　　<font color="#ff1493">2</font>.OOP中最重要的思想是类，类是模板是蓝图，从类中构造一个对象，即创建了这个类的一个实例(instance)。</p>
		<p>　　<font color="#ff1493">3</font>.封装:就是把数据和行为结合起在一个包中)并对对象使用者隐藏数据的实现过程，一个对象中的数据叫他的实例字段(instance field)。</p>
		<p>　　<font color="#ff1493">4</font>.通过扩展一个类来获得一个新类叫继承(inheritance)，而所有的类都是由Object根超类扩展而得，根超类下文会做介绍。</p>
		<p>　　<font color="#ff1493">5</font>.对象的3个主要特性 </p>
		<p>　　behavior---说明这个对象能做什么.<br />　　state---当对象施加方法时对象的反映.<br />　　identity---与其他相似行为对象的区分标志.<br />　　每个对象有唯一的indentity 而这3者之间相互影响.</p>
		<p>　　<font color="#ff1493">6</font>.类之间的关系:</p>
		<p>　　use-a :依赖关系<br />　　has-a :聚合关系<br />　　is-a :继承关系--例:A类继承了B类，此时A类不仅有了B类的方法，还有其自己的方法.(个性存在于共性中)</p>
		<p>　　<font color="#ff1493">7</font>.构造对象使用构造器:构造器的提出，构造器是一种特殊的方法，构造对象并对其初始化。</p>
		<p>　　例:Data类的构造器叫Data</p>
		<p>　　new Data()---构造一个新对象，且初始化当前时间.<br />　　Data happyday=new Data()---把一个对象赋值给一个变量happyday，从而使该对象能够多次使用，此处要声明的使变量与对象变量二者是不同的.new返回的值是一个引用。</p>
		<p>　　构造器特点:构造器可以有0个，一个或多个参数<br />　　构造器和类有相同的名字<br />　　一个类可以有多个构造器<br />　　构造器没有返回值<br />　　构造器总是和new运算符一起使用.</p>
		<p>
				<font color="#ff1493">        8</font>.重载:当多个方法具有相同的名字而含有不同的参数时，便发生重载.编译器必须挑选出调用哪个方法。</p>
		<p>　　<font color="#ff1493">9</font>.包(package)Java允许把一个或多个类收集在一起成为一组，称作包，以便于组织任务，标准Java库分为许多包.java.lang java.util java，net等，包是分层次的所有的java包都在java和javax包层次内。</p>
		<p>　　<font color="#ff1493">10</font>.继承思想:允许在已经存在的类的基础上构建新的类，当你继承一个已经存在的类时，那么你就复用了这个类的方法和字段，同时你可以在新类中添加新的方法和字段。</p>
		<p>　　<font color="#ff1493">11</font>.扩展类:扩展类充分体现了is-a的继承关系. 形式为:class (子类) extends (基类)。</p>
		<p>　　<font color="#ff1493">12</font>.多态:在java中，对象变量是多态的.而java中不支持多重继承。</p>
		<p>　　<font color="#ff1493">13</font>.动态绑定:调用对象方法的机制。</p>
		<p>　　(1)编译器检查对象声明的类型和方法名。</p>
		<p>　　(2)编译器检查方法调用的参数类型。</p>
		<p>　　(3)静态绑定:若方法类型为priavte static final 编译器会准确知道该调用哪个方法。</p>
		<p>　　(4)当程序运行并且使用动态绑定来调用一个方法时，那么虚拟机必须调用x所指向的对象的实际类型相匹配的方法版本。</p>
		<p>　　(5)动态绑定:是很重要的特性，它能使程序变得可扩展而不需要重编译已存代码。</p>
		<p>　　<font color="#ff1493">14</font>.final类:为防止他人从你的类上派生新类，此类是不可扩展的。</p>
		<p>　　<font color="#ff1493">15</font>.动态调用比静态调用花费的时间要长。</p>
		<p>　　<font color="#ff1493">16</font>.抽象类:规定一个或多个抽象方法的类本身必须定义为abstract。</p>
		<p>　　例: public abstract string getDescripition</p>
		<p>　　<font color="#ff1493">17</font>.Java中的每一个类都是从Object类扩展而来的。</p>
		<p>　　<font color="#ff1493">18</font>.object类中的equal和toString方法。</p>
		<p>　　equal用于测试一个对象是否同另一个对象相等。</p>
		<p>　　toString返回一个代表该对象的字符串，几乎每一个类都会重载该方法，以便返回当前状态的正确表示.<br />　　(toString 方法是一个很重要的方法)</p>
		<p>　　<font color="#ff1493">19</font>.通用编程:任何类类型的所有值都可以同object类性的变量来代替。</p>
		<p>　　<font color="#ff1493">20</font>.数组列表:ArrayList动态数组列表，是一个类库，定义在java.uitl包中，可自动调节数组的大小。</p>
		<p>　　<font color="#ff1493">21</font>.class类 object类中的getclass方法返回ckass类型的一个实例，程序启动时包含在main方法的类会被加载，虚拟机要加载他需要的所有类，每一个加载的类都要加载它需要的类。</p>
		<p>　　<font color="#ff1493">22</font>.class类为编写可动态操纵java代码的程序提供了强大的功能反射，这项功能为JavaBeans特别有用，使用反射Java能支持VB程序员习惯使用的工具。</p>
		<p>　　能够分析类能力的程序叫反射器，Java中提供此功能的包叫Java.lang.reflect反射机制十分强大.</p>
		<p>　　1.在运行时分析类的能力。<br />　　2.在运行时探察类的对象。<br />　　3.实现通用数组操纵代码。<br />　　4.提供方法对象。</p>
		<p>而此机制主要针对是工具者而不是应用及程序。</p>
		<p>　　反射机制中的最重要的部分是允许你检查类的结构.用到的API有:</p>
		<p>　　java.lang.reflect.Field 返回字段.<br />　　java.reflect.Method 返回方法.<br />　　java.lang.reflect.Constructor 返回参数.</p>
		<p>　　方法指针:java没有方法指针，把一个方法的地址传给另一个方法，可以在后面调用它，而接口是更好的解决方案。</p>
		<p>　　<font color="#ff1493">23</font>.接口(Interface)说明类该做什么而不指定如何去做，一个类可以实现一个或多个interface。</p>
		<p>　　<font color="#ff1493">24</font>.接口不是一个类，而是对符合接口要求的类的一套规范。</p>
		<p>　　若实现一个接口需要2个步骤:　</p>
		<p>　　1.声明类需要实现的指定接口。<br />　　2.提供接口中的所有方法的定义。</p>
		<p>　　声明一个类实现一个接口需要使用implements 关键字</p>
		<p>　　class actionB implements Comparable 其actionb需要提供CompareTo方法，接口不是类，不能用new实例化一个接口.</p>
		<p>　　<font color="#ff1493">25</font>.一个类只有一个超类，但一个类能实现多个接口。Java中的一个重要接口：Cloneable</p>
		<p>　　<font color="#ff1493">26</font>.接口和回调.编程一个常用的模式是回调模式，在这种模式中你可以指定当一个特定时间发生时回调对象上的方法。</p>
		<p>　　例:ActionListener 接口监听.<br />　　类似的API有:java.swing.JOptionPane</p>
		<p>　　java.swing.Timer<br />　　java.awt.Tookit</p>
		<p>　　<font color="#ff1493">27</font>.对象clone:clone方法是object一个保护方法，这意味着你的代码不能简单的调用它。</p>
		<p>　　<font color="#ff1493">28</font>.内部类:一个内部类的定义是定义在另一个内部的类。</p>
		<p>　　原因是:</p>
		<p>　　1.一个内部类的对象能够访问创建它的对象的实现，包括私有数据。</p>
		<p>　　2.对于同一个包中的其他类来说，内部类能够隐藏起来。</p>
		<p>　　3.匿名内部类可以很方便的定义回调。</p>
		<p>　　4.使用内部类可以非常方便的编写事件驱动程序。</p>
		<p>　　<font color="#ff1493">29</font>.代理类(proxy):</p>
		<p>　　1.指定接口要求所有代码</p>
		<p>　　2.object类定义的所有的方法(toString equals)</p>
		<p>　　<font color="#ff1493">30</font>.数据类型:Java是强调类型的语言，每个变量都必须先申明它都类型，java中总共有8个基本类型.4种是整型，2种是浮点型，一种是字符型，被用于Unicode编码中的字符，布尔型。<br /></p>
<img src ="http://www.blogjava.net/dwys0343/aggbug/90816.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dwys0343/" target="_blank">特兰克斯</a> 2006-12-29 22:32 <a href="http://www.blogjava.net/dwys0343/articles/90816.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>