1Z0-809复习 第1章 toString(), equals(),hashCode(),

Advanced Class Design

Reviewing OCA Concepts

Access Modifiers

Overloading and Overriding

Abstract Classes

Static and Final

Imports

Using instanceof

Understanding Virtual Method Invocation

Annotating Overridden Methods

Coding equals, hashCode, and toString

java中所有类都是直接或间接继承了Object,因此也继承了Object类的所有方法。

toString

java中的print方法,会自动调用打印对象的toString()方法。

对象如果实装了toString()方法,使得打印的对象内容更清楚明了。
譬如ArrayList,[1,2,3]
如果对象使用Object的toString()方法,例如array对象
[Ljava.lang.String;@65cc892e]

一般的做法是打印出对象的所有成员变量,但一个一个写的话非常麻烦。
Apache社区提供了上述opensource,利用其提供的方法,
可以非常方便的打印出对象中所有成员变量。

//定义build.gradle
compile("org.apache.commons:commons-lang3:3.8")
//方法重载
public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

这其中利用到了反射,在实行期取得类的信息。
所有source:

import org.apache.commons.lang3.builder.ToStringBuilder;

public class Hippo {
    private String name;
    private double weight;
    public Hippo(String name, double weight) {
        this.name = name;
        this.weight = weight;
    }
    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
    public static void main(String[] args) {
        Hippo h1 = new Hippo("Harry", 3100);
        System.out.println(h1); // Harry
    }
}

执行结果:com.apibot.Hippo@78308db1[name=Harry,weight=3100.0]

//简略版
public String toString() {
    return ToStringBuilder.reflectionToString(this,ToStringStyle.SHORT_PREFIX_STYLE);
}

执行结果:Hippo[name=Harry,weight=3100.0]

equals

如果类重载了equals方法,那么利用重载的方法进行比较。

String s1 = new String("lion");
String s2 = new String("lion");
System.out.println(s1.equals(s2)); // true
StringBuilder sb1 = new StringBuilder("lion");
StringBuilder sb2 = new StringBuilder("lion");
System.out.println(sb1.equals(sb2)); // false

这个应该是在StringBuilder类中equals没有重载,
因此用的是Object的equals方法来比较。
而String在重载的equals方法中,比较的是每个字符串的每个字符,所以是相等的。
- reflexive :反身性的,x.equals(x) 肯定是true
- symmetric: :对称的,如果x.equals(y)是true,那么y.equals(x)肯定也是true
- transitive: :传递性的,如果x.equals(y)是true,y.equals(z),那么 x.equals(z)也应该是相等的。
- consistent: : - x.equals(null) 永远都是false。而不是出NullPointerException

public boolean equals(Lion obj) {
    if (obj == null) return false;
    return this.idNumber == obj.idNumber;
}

传进来的参数如果不是Object类型,那么这个就不是Override,而是Overload。
虽然没有任何问题,但还是要注意

public boolean equals(Object obj) {
    return EqualsBuilder.reflectionEquals(this, obj);
}

重载之后比较的就是对象的所有成员变量。

public boolean equals(Object obj) {
    if ( !(obj instanceof LionEqualsBuilder)) return false;
    Lion other = (Lion) obj;
    return new EqualsBuilder().appendSuper(super.equals(obj))
                              .append(idNumber, other.idNumber)
                              .append(name, other.name)
                              .isEquals();
}

重载之后,比较的是对象中的idNumber和name。 虽然不是很简洁,还是得手动处理null和instanceof,但毕竟比所有都全手写要方便。

hashCode

当把object作为key存到map里去的话,会需要用到hashcode

public class Card {
    private String rank;
    private String suit;
    public Card(String r, String s) {
        if (r == null || s == null) {
            throw new IllegalArgumentException();
        }
        rank = r;
        suit = s;
    }
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Card)) {
            return false;
        }
        Card c = (Card) obj;
        return rank.equals(c.rank) && suit.equals(c.suit);
    }
    @Override
    public int hashCode() {
        return rank.hashCode();
    }
}

如果是primitive数字,可以直接用,或者取更小的int作为hashcode。
另外,一般在hashcode中不包含boolean和char类型的变量。

  • 同样的program,hashcode()的结果不会变。
    意味着不可以包含会变的变量。
    前面的Hippo类例子,只包括名字,因为体重会变。

  • 如果equals()的结果是true,那么hashCode()会返回相同的结果。
    这个意味着hashCode使用equals比较的成员变量的子集。
    例如上面Card的例子,有两个变量用于equals,但只有其中的一个用于hashCode。

  • 另一个角度讲,如果equals()的结果是false,hashCode()并不需要返回相同的结果。

//没问题,hashCode和equals用相同的变量
public int hashCode() { return idNumber; }
//没问题,但没有意义,永远返回相同的6
public int hashCode() { return 6; }
//方法名里的c不是C,因此是一个全新的方法。如果是C也有问题,override的时候返回值类型只能是子类。
public long hashcode() { return idNumber; }
//这个有问题,因为用到的参数比equals多,idNumber以外用了age
public int hashCode() { return idNumber * 7 + age; }

Working with Enums

public enum Season {
    WINTER, SPRING, SUMMER, FALL //没有分号
}

Enum值最后是没有分号的。
Enum和常数的区别在于,其是Type Safe,也就是在编译之前就知道这些成员变量的type。
enum包含的是static变量,以及helper method。

Season s = Season.SUMMER;

关于这个用法稍微有点违和感,貌似把类的成员变量赋予了类。

System.out.println(s == Season.SUMMER); // true

看上去像是两个字符串用等号来比较,有违和感。
可以想象成这是两个一开始就定义好的static final的变量。
这样用等号就没有违和感了。
enum的toString()起作用时,取得是enum的name

for(Season season: Season.values()) {
    System.out.println(season.name() + " " + season.ordinal());
}
System.out.println(Season.SPRING.name() + " " + Season.SPRING.ordinal());

这个看上去也有违和感。是因为Season的静态方法values()会取得其中的所有元素。
循环这个array,用name()方法取得每个元素的名字。用ordinal()方法,取得每个元素的值。

可以看出来每一个元素都有一个name一个顺序int值

Season s1 = Season.valueOf("SUMMER"); // SUMMER
Season s2 = Season.valueOf("summer"); // java.lang.IllegalArgumentException

可以用完全一模一样的字符串创建一个enum

Season.SPRING.values(); // Season的所有元素,少用
Season.values();        // Season的所有元素,相同

Using Enums in Switch Statements

Season summer = Season.SUMMER;
switch (summer) {
//这个用法是错的,因为:
//Season.WINTER是一个enum,并不是enum的int
//WINTER可以称为一个enum的value,是case可以接受的类型。
//Season可以称作enum的type。
case Season.WINTER:
    System.out.println("Get out the sled!");
    break;
case SUMMER:
    System.out.println("Time for the pool!");
    break;
default:
    System.out.println("Is it summer yet?");
}

最终针对Season.SUMMER了解几个概念:

Adding Constructors, Fields, and Methods

public enum Season {
    WINTER("Low"), SPRING("Medium"), SUMMER("High"), FALL("Medium");
    //成员变量
    private String expectedVisitors;
    //构造函数
    private Season(String expectedVisitors) {
        this.expectedVisitors = expectedVisitors;
    }
    //成员方法
    public void printExpectedVisitors() {
        System.out.println(expectedVisitors);
    }
}
  • 如果enum类里面只有value的一行,那么最后一个分号是可以省略的。
    像上面的情况,最后一个分号就不可以省略。
  • 构造方法的修饰词必须是private,因为只可以在enum之内被调用。public是无法通过编译的。
  • 构造函数可以看出来,是将方法的形参赋给成员变量。 再看第一行的定义就明白了,Low,Medium,High就是形参的具体值。
Season.SUMMER.printExpectedVisitors();

执行结果是High,因为SUMMER("High")调用构造函数把High赋给了SUMMER 另一enum的用法:

public enum Season {
    WINTER {
        @Override
        public void printHours() {
            System.out.println("9am-3pm");
        }
    },
    SPRING {
        @Override
        public void printHours() {
            System.out.println("9am-5pm");
        }
    },
    SUMMER {
        @Override
        public void printHours() {
            System.out.println("9am-7pm");
        }
    },
    FALL {
        @Override
        public void printHours() {
            System.out.println("9am-5pm");
        }
    };
    public abstract void printHours();
}
Season summer = Season.SUMMER;
summer.printHours();

Creating Nested Classes

Member Inner Classes

Local Inner Classes

Anonymous Inner Classes

Static Nested Classes

Summary

Exam Essentials

Review Questions