飞's profile程序人生PhotosBlogListsMore Tools Help

Blog


    December 01

    关于Java里面重载equals方法的注意事项

    看《Core Java》的时候注意到的,跟C++里面重载"=="操作符很类似:

     

    How should the equals method behave if the implicit and explicit parameters don't belong to the same class? Unfortunately, different programmers take different actions in this case. We recommend that equals should return false if the classes don't match exactly. But many programmers use a test:

    if (!(otherObject instanceof Employee)) return false;

    This leaves open the possibility that otherObject can belong to a subclass. Other programmers use no test at all. Then the equals method throws an exception if otherObject cannot be cast to an Employee object. Technically speaking, both of these approaches are wrong. Here is why. The Java Language Specification requires that the equals method has the following properties:

    1. It is reflexive: for any non-null reference x, x.equals(x) should return true.
    2. It is symmetric: for any references x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
    3. It is transitive: for any references x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
    4. It is consistent: If the objects to which x and y refer haven't changed, then repeated calls to x.equals(y) return the same value.
    5. For any non-null reference x, x.equals(null) should return false.

    Rule 5 mandates that you include the test

    if (otherObject == null) return false;

    in your equals method. What is less obvious is that Rule 2 requires you to test for class equality. Consider a call

    e.equals(m)

    where e is an Employee object and m is a Manager object, both of which happen to have the same name, salary, and hire date. If you don't check that the class of m is the same as the class of e, this call returns true. But that means that the reverse call

    m.equals(e)

    also needs to return true—Rule 2 does not allow it to return false, or to throw an exception.

    Unfortunately, the Java Language Specification does a poor job of explaining this consequence, and the majority of programmers seem to be unaware of it. The standard Java library contains over 150 implementations of equals methods, with a mishmash of using instanceof, calling getClass, catching a ClassCastException, or doing nothing at all. Only a tiny minority of implementations fulfills Rule 2. You can do better, by following our recipe for the perfect equals method.

    Here is a recipe for writing the perfect equals method:

    1. Call the explicit parameter otherObject—later, you need to cast it to another variable that you should call other.

    2. Test whether this happens to be identical to otherObject:

    if (this == otherObject) return true;

    This is just an optimization. In practice, this is a common case. It is much cheaper to check for identity than to compare the fields.

    3. Test whether otherObject is null and return false if it is. This test is required.

    if (otherObject == null) return false;

    4. Test whether this and otherObject belong to the same class. This test is required by the "symmetry rule".

    if (getClass() != otherObject.getClass()) return false;

    5. Cast otherObject to a variable of your class type:

    ClassName other = (ClassName)otherObject

    6. Now compare all fields. Use == for primitive type fields, equals for object fields. Return true if all fields match, false otherwise.

    return field1 == other.field1
       && field2.equals(other.field2)
       && . . .;

    If you define the equals method for a subclass of a class that follows these rules, first call equals on the superclass. If that test doesn't pass, then the objects can't be equal. If the superclass fields are equal, then you are ready to compare the instance fields of the subclass.


     

    November 19

    关于java的文档

        前几天蜜蜂发了个文档给我,是java的中文文档,只有java.lang.*和java.util.*,唉,看起来还是很不习惯,为啥java的文档就做得那么土呢?
        今天早上,某个本家兄弟说了一句经典的话:“老用了msdn现在查javaAPI感觉去乡下玩似的。”哈哈,这比喻说得太好了。
        别的不说吧,6、7年前第一次看见MSDN,那时候老师说,MSDN是很好的文档,有关微软产品的开发中遇到的问题几乎都能在上面查到,各种函数的参考非常详细。简直是不可想象,在那个年代,光文档就做成两张光盘,微软对于用户的考虑实在不是一般的详尽周到,因为有MSDN,我才能够在没有网络、问不到人的情况下,自己去解决一些问题。不光是这些,在那之前的VB5.0,不但有完全的中文版,而且连几十M的帮助文件都全部翻译成中文了,正因为它,学VB的中国人才有这么多。
        后来Java一路走红,微软暂时落后了一阵,然后.net 1.1的文档出来之后,简直是太震撼了,全中文的版本,简直不是一般的精美,但是Java的文档还是那么老土,连个style都不舍得用一下,不知道是怎么想的,哎……
        也许美观不是那么重要?但是用起来总要方便一些吧。不说了,抓了两个图做对比:
     
    October 15

    关于Java函数调用的参数

        唔,看来确实有很多人像我一样误解了Java里面的参数传递方式,这里是《Core Java™ 2: Volume I - Fundamentals》总结:

     

        Here is a summary of what you can and cannot do with method parameters in the Java programming language:

    • A method cannot modify a parameter of primitive type (that is, numbers or Boolean values).

    • A method can change the state of an object parameter.

    • A method cannot make an object parameter refer to a new object.

        哈,土狗也误解了,蜜蜂你贴的那个代码正是针对这三点总结的吧。

    October 14

    Java里面的函数调用参数传递方式

        前几天加班比较狠,所以晚上回去没时间看书,这两天看了一些,有些感想,发上来。
     
        关于Java的函数参数传递方式,《Core Java™ 2: Volume I - Fundamentals》中有这样一段话:

        The Java programming language always uses call by value. That means, the method gets a copy of all parameter values. In particular, the method cannot modify the contents of any parameter variables that are passed to it.

        Many programming languages (in particular, C++ and Pascal) have two methods for parameter passing: call by value and call by reference. Some programmers (and unfortunately even some book authors) claim that the Java programming language uses call by reference for objects. However, that is false. Because this is such a common misunderstanding, it is worth examining a counterexample in detail.

        Let's try to write a method that swaps two employee objects:

        public static void swap(Employee x, Employee y) // doesn't work
        {
           Employee temp = x;
           x = y;
           y = temp;
        }
    

        If the Java programming language used call by reference for objects, this method would work:

        Employee a = new Employee("Alice", . . .);
        Employee b = new Employee("Bob", . . .);
        swap(a, b);
        // does a now refer to Bob, b to Alice?
    

        However, the method does not actually change the object references that are stored in the variables a and b. The x and y parameters of the swap method are initialized with copies of these references. The method then proceeds to swap these copies.

        // x refers to Alice, y to Bob
        Employee temp = x;
        x = y;
        y = temp;
        // now x refers to Bob, y to Alice
    

        But ultimately, this is a wasted effort. When the method ends, the parameter variables x and y are abandoned. The original variables a and b still refer to the same objects as they did before the method call.

        原来Java里面的函数参数传递方式全部是值传递,以前理解错了吧……

    September 20

    Java里面的二维数组

    下面这段摘自《Core Java™ 2: Volume I - Fundamentals》,昨天晚上看到的,由于忙,还没有来得及验证一下,等有空了看看,以前没见过这样的说法。

     

    The expression balance[i] refers to the ith subarray, that is, the ith row of the table. It is, itself, an array, and balance[i][j] refers to the jth entry of that array.

    Because rows of arrays are individually accessible, you can actually swap them!

    double[] temp = balance[i];
    balance[i] = balance[i + 1];
    balance[i + 1] = temp;
    
    最近是怎么了,这个标题都不许我发:《关于Java里面的多维数组》,郁闷……
    说:此项包含禁止的语言。请从此项删除禁止的语言。
    August 07

    System.out.println的感想

        今天上午看《Core Java, volume I》,看到第三章,提到System.out.println这个最常用的输出方法,发现了一个奇怪的事情,跟我以前的想象不一样,这里的System居然是一个类,out是类的静态成员,println是静态方法,我原来还以为System是个包,out才是类呢。
        不过仔细想想,以前确实犯傻了,哪里会有小写开头的类名字哦。不过……,难道C#里面的跟这个不一样?我再去查了.Net SDK 1.1的文档,System.Console.WriteLine,狂汗,这个命名,全是PASCAL格式的,这里的System是个命名空间,Console是个类,而WriteLine才是静态方法,原来,看起来这么类似的两句话,差别却这么大。
        附带说明一下,从J2SDK 1.4.2文档中查到的:

    java.lang
    Class System

    java.lang.Object
      |--java.lang.System
    public final class System
    extends Object

    The System class contains several useful class fields and methods. It cannot be instantiated.

    Among the facilities provided by the System class are standard input, standard output, and error output streams; access to externally defined "properties"; a means of loading files and libraries; and a utility method for quickly copying a portion of an array.

    Since:
    JDK1.0
    static PrintStreamout           The "standard" output stream.


        这就是说,out的类型是PrintStream,而PrintStream中提供了参数重载的println静态方法。

    July 28

    关于Java中的args

        上次香蕉问我,为什么Java中的main函数一定要写args,args到底是什么作用,我简单回答了一下,不过自己觉得还是总结一下的好。
        Java中args的作用是从命令行取得输入参数,这样在程序中可以调用。例如有这样一个程序:
        public class Test
        {
            public static void main(String args[])
            {
                for (int i=0; i<args.length; i++)
                {
                    System.out.println(args[i]);
                }
            }
        }
     
        那么,编译成class文件以后,从命令行进行调用,输入:java Test hello world,这里,java是Java的运行命令,Test是类文件名,后面的hello跟world就将被带到args数组中,这里跟C++有一个区别,C++把所执行程序的全路径作为args的第一项,而java只从后面跟的参数算起。就是说,如果有一个C++编译出来的程序Test.exe,输入Test hello world的话,hello将作为args[1],而world将作为args[2],但是在Java里面,hello保存在args[0],world保存在args[1]。
        看起来很奇怪,不过我感觉Java程序要考虑跨平台啊,路径无关的事情,保存个程序路径应当是一点必要都没有的,是这样的吧?
        那么,args不写,可以吗?来试试这个:
        public class Test
        {
            public static void main()
            {
                System.out.println("Hello,world!");
            }
        }
     
        嗯,编译可以通过,但是执行……,啊?提示说:
        Exception in thread "main" java.lang.NoSuchMethodError: main
        这么说,main的访问属性、参数列表都是固定的,只有这样才能作为主程序入口啊。上面的程序被编译成一个普通类了,main退化成为一个普通的公有静态无参数无返回值的方法,原来如此。