畅销书对Java中Iterator的理解误区

引言

最近放假,闲来无事,便翻看以前看过的一些书,竟然发现有些书本(甚至是一些畅销书)对Java中Iterator有很大的误解,比如某畅销书在Collection那一章有这么一句话:“当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。”

我们先看它举的例子,代码如下(注:原文中字符串为中文,此处按个人偏好将其翻译成了英文):

import java.util.*;

public class IteratorTest
{
  public static void main(String[]args)
  {
     Collection books=new HashSet();
     books.add("Light Java EE Practice");
     books.add("Crazy Java Textbook");
     books.add("Crazy Android Textbook");

     Iterator it=books.iterator();
     while(it.hasNext())
     {
        String book=(String)it.next();
        System.out.println(book);
        if(book.equals("Crazy Java Textbook"))
        {
           it.remove();
        }
        book="Test String";
     }
     System.out.println(books);
   }
}

其输出结果如下:

java_iterator01

从这个输出结果来看:好像作者的结论挺对的。但是,其实这个结论很容易被证伪。如下面的一个例子:

import java.util.*;

class Apple
{
  int weight;
  public Apple(int weight)
  {
    this.weight=weight;
   }
   public String toString()
   {
      return "weight:"+weight;
    }
}

public class IteratorSample
{
  public static void main(String[]args)
  {
     Collection collection=new HashSet();
     Apple[]appleArray=new Apple[10];
     for(int i=0;i<10;++i)
     {
        appleArray[i]=new Apple(i+200);
        collection.add(appleArray[i]);
     }
     System.out.println(collection);

     Iterator iterator=collection.iterator();
     Apple apple=(Apple)iterator.next();
     apple.weight=300;
     System.out.println(collection);
   }
}

输出结果如下:

java_iterator02

显然,collection中的第一个元素被改变了。

那么到底为什么会出现这种看起来截然相反的两种结果呢?

问题就出在String上!在我的博客深刻理解Java中final的作用(一):从final的作用剖析String被设计成不可变类的深层原因一文中就讲过String是Java中一个很特殊的类,它的特殊之外就在于它是一个不可变类,即String对象一旦生成便不可改变,经常看到的对String变量的赋值其实是让引用指向新的String对象!

也就是说,在本文中第一个例子中,语句String book=(String)it.next();的作用只是让book也指向it.next()所指向的对象,后面book=”Test String”;只是让book这个引用指向一个新的字符串对象,显然这种改变不会也不可能影响原对象,it.next()所指向的另然是原来的那个对象,所以从结果来看好像作者的结论挺有道理。但是实际上Iterator.next()确实是提供了原对象的引用,对于除不可变类之外的其他对象,都可以通过这个引用来改变它(当然,也要有相应的接口)。本文中第二个例子就充分地说明了这一点,由于Apple并不是一个不可变类,故可通过iterator.next()来改变它指向的对象。

可能有人会质疑,为什么第一个例子中就不是让it.next()这个引用指向新的对象呢?原因很简单:book和it.next()是两个独立的引用,而程序中只对book进行了赋值,并没有对it.next()进行赋值。看下面一个简单的例子即可知:

import java.util.*;

public class StringTest
{
  public static void main(String[]args)
  {
     String str1="I miss you";
     String str2=str1;
     System.out.println("str1:"+str1);
     System.out.println("str2:"+str2);
     str2="Will you miss me?";
     System.out.println("str1:"+str1);
     System.out.println("str2:"+str2);
  }
}

输出结果如下:

java_iterator03

显然,对str2的赋值根本就不影响str1,本文中第一个例子与其类似。

以前看书总是全盘接收,很少持怀疑态度,现在才发现“尽信书不如无书”,即使是畅销书,特别是国内的一些书籍,结论未必严谨。带着批判的角度去看书,多一些独立思考,方能有更大的收获。