1Z0-809复习 第1章 Creating Nested Classes

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

嵌套类是定义在另一个类里面的类。
如果是非static,则叫做内部类inner class。

  • 成员内部类和成员变量同等级定义,非static。
    通常是指不显示定义类型的内部类。
  • 本地内部类,定义在方法体内。
  • 匿名内部类是一个没有名字的本地内部类的。
  • 静态嵌套类,是跟static变量同级别的静态类。

用嵌套类的好处之一:就是可以封装helper class到容器类中

Member Inner Classes

member level(和成员方法,成员变量和构造方法一级)定义的类。
- 可以定义,public,private,protected,default access
- 可以继承任何其他类或接口 - 可以是抽象类和final - 不可以定义静态成员和方法。 - 可以访问外部类的成员变量,包括private成员变量。 最后两条非常重要。

public class Outer {
    private String greeting = "Hi";
    protected class Inner {
        public int repeat = 3;
        public void go() {
            for (int i = 0; i < repeat; i++) {
                System.out.println(greeting);
            }
        }
    }
    public void callInner() {
        Inner inner = new Inner();
        inner.go();
    }
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.callInner();
        Outer outer2 = new Outer();
        // 一开始以为应该是 new outer2.Inner();
        // 但我错了,要记住这个格式
        Inner inner2 = outer2.new Inner();
    }
}

编译后的.class文件。一个是Outer.class,一个是Outer$Inner.class InnerClass可以和OuterClass有相同名字的成员变量。

public class A {
    private int x = 10;
    class B {
        private int x = 20;
        class C {
            private int x = 30;
            public void allTheX() {
                System.out.println(x);        // 30
                System.out.println(this.x);   // 30
                System.out.println(B.this.x); // 20
                System.out.println(A.this.x); // 10
            }
        }
    }
    public static void main(String[] args) {
        A a = new A();
        A.B b = a.new B();
        A.B.C c = b.new C();
        c.allTheX();
    }
}

可以从A.B省略成B,因为类B和main方法在同一个member level
但不可以把A.B.C省略成C,因为C在下一层。
另外可以增加一句import A.B.C,这样就可以直接用C了。

private interface
public class CaseOfThePrivateInterface {
    private interface Secret {
        public void shh();
    }
    class DontTell implements Secret {
        @Override
        public void shh() {
        }
    }
}

关于interface的规则没有变: - 成员方法缺省都是public,实装的方法必须是public的。
- interface本身不必须是public,跟内部类相同,inner interface可以是private的。
同时也意味着这个interface只能被outerclass使用。

Local Inner Classes

local inner class 定义直到方法被调用的时候才产生。
然后到方法返回时就失效了。
所有的动作跟成员变量相同。
- 不可以有修饰符。 - 不能声明为static,也不能有static的成员变量和方法。 - 可以访问包围类的所有成员和方法。 - 他们没有权限访问本地变量,除非这些变量是final或者等效final。

public class Outer {
    private int length = 5;
    public void calculate() {
        //这里是final所有没有问题
        //如果是 int width = 20; width = 30; 就会出错。
        final int width = 20;
        class Inner {

            public void multiply() {
                System.out.println(length * width);
            }
        }
        Inner inner = new Inner();
        inner.multiply();
    }
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.calculate();
    }
}

一个是instance变量,一个是final变量,因此在innerclass内可以访问。

effecitvely final

不需要显示定义,实际上只发生一次赋值的变量都是 effecitvely final

Anonymous Inner Classes

就是没有名字的Local Inner Class。

public class AnonInner {
    abstract class SaleTodayOnly {
        abstract int dollarsOff();
    }
    public int admission(int basePrice) {
        SaleTodayOnly sale = new SaleTodayOnly() {
            @Override
            int dollarsOff() {
                return 3;
            }
        };
        return basePrice - sale.dollarsOff();
    }
}

看上去像是new了一个抽象类,抽象类是不能被new的。
但只要有方法体{},就可以(多态?)
不是抽象类,接口也可以。 interface 的方法缺省是public的。
但抽象类的方法,是default的。
无法继承一个class的同时实现一个接口。
除非要继承的是java.lang.Object <-这句话什么意思。

如果真有这个需求,那么最好是定义成local inner class
然后然后就可以继承类和实现接口了。
如果真的需要这么复杂,内部类本身就可能并不是一个合适的选择。

public class AnonInner {
    interface SaleTodayOnly {
        int dollarsOff();
    }
    public int pay() {
        return admission(5, new SaleTodayOnly() {
            @Override
            public int dollarsOff() {
                return 3;
            }
        });
    }
    public int admission(int basePrice, SaleTodayOnly sale) {
        return basePrice - sale.dollarsOff();
    }
}

Static Nested Classes

静态类的用法跟静态方法一样,例如不能直接访问外部类的成员变量。

public class Enclosing {
    static class Nested {
        //静态类里面也可以定义静态方法,但是需要有静态类对象才能访问。
        private int price = 6;
    }
    public static void main(String[] args) {
        //初始化的时候不需要Enclosing的对象就可以初始化,这跟内部成员类有区别。
        Nested nested = new Nested();
        //生成了静态类的对象,然后访问。
        System.out.println(nested.price);
    }
}

import静态类时的两个方法。
普通方法: import Enclosing.Nested
静态导入: import static Enclosing.Nested

不是静态内部类,只能用普通方法import。
嵌套类import的时候,outerclass看上更像是一个namespace。

Summary

Exam Essentials

Review Questions