首页 > 编程语言 >【Java编程思想】读书笔记(二)第六章---第十章

【Java编程思想】读书笔记(二)第六章---第十章

时间:2022-12-02 17:34:15浏览次数:48  
标签:Java final 读书笔记 void public --- 基类 println class

Java编程思想(第四版)学习笔记 第六章---第十章

第六章:访问权限控制

6.2Java访问权限修饰词

  1. 包访问权限:默认的访问权限(有时也表示成为friendly)
    意味着当前包中的所有的其他类对那个成员都有访问权限,但是对于这个包之外的所有类,这个成员却是private。
  2. public:接口访问权限
    使用关键字public,意味着public之后紧跟着的成员声明自己对每个人都是可用的。
  3. private:你无法访问
    除了包含该成员的类之外,其他任何类都无法访问这个成员。
  4. protected:继承访问权限(extends)
    基类的创建者希望有一个特定的成员,把
    对它的访问权限赋予派生类而不是所有的类,这就需要protected来完成这一工作,并且protected也提供了宝访问权限,也就是说,相同包内的其他类可以访问protected元素。
第七章:复用类

7.1 组合语法

初始化:类中域为基本类型时能够自动被初始化为零,但是对象的引用会被初始化为null。
惰性初始化:创建时,不初始化,在使用之前判空,若为null则初始化。

 public class Bath{
    	private String s;
    	public String toString(){
    		if(s == null){
    			s = "Joy";
    		}
    		return s;
    	}
    }

7.2 继承语法(extends)

所有的类都是隐式地从Java的标准根类Object进行继承。导出的类会自动获取基类中所有的域和方法。

为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法都指定为public。(protected成员也可以借助导出类来访问)

无参构造器基类的构造器总是会被调用,并且在导出类的构造器之前被调用
有参构造器导出类的有参构造器必须在构造器中的第一句调用基类构造器,否则编译器报错

    class Game{
    	Game(int i){
    		print("Game constructor");
    	}
    }
    class BoardGame extends Game{
    	BoardGame(int i){
    		super(i);
    		print("BoardGame constructor");
    	}
    }
    public class Chess extends BoardGame{
    	Chess(int i){
    		super(i);
    		print("Chess constructor");
    	}
    	public static void main(){
    		Chess x = new Chess();
    	}
    }
    /*
    output:
    Game constructor
    BoardGame constructor
    Chess constructor
    */

7.4.2名称屏蔽(重载)

在导出类中重新定义基类中的方法名称,并不会屏蔽基类中的方法。

class Homer{
    char doh(char c){
        System.out.println("doh(char)");
        return 'd';
    }

    float doh(float f){
        System.out.println("doh(float)");
        return 1.0f;
    }
}

class Milhouse{ }

class Bart extends Homer{
    //@Override  编译器提示基类中不包含此类方法  不能覆盖
    void doh(Milhouse m){
        System.out.println("doh(Milhouse)");
    }
}

public class Hide {
    public static void main(String[] args) {
        Bart b = new Bart();
        b.doh(1);
        b.doh('x');
        b.doh(1.0f);
        b.doh(new Milhouse());
    }
}

/*Output:
doh(float)
doh(char)
doh(float)
doh(Milhouse)
 */

Java SE5新增加了@Override注解它并不是关键字,但是可以把它当做关键字使用。当你想要覆写某个方法时,可以选择添加这个注解,在你不留心重载而并非覆写了该方法时,编译器就会生成一条错误消息。

7.6 protected关键字

可以将某些事物尽可能对这个世界隐藏起来,但仍然允许导出类的成员访问他们。
对于任何继承于此类的导出类或者位于同一个包内的类来说,它确实可以访问的。(Protected提佛那个了包内访问权限)

7.7向上转型

由于继承可以确保基类中所有的方法在导出类中也同样有效,所以能够向基类发送的所有信息同样也可以向导出类发送。

如果Instrument类具有一个paly() 方法,那么Wind乐器也将同样具备。这意味着我们可以准确的说Wind对象也是一种类型的INstrument。

class Instrument{
    public void play(){};
    
    static void tune(Instrument i){
        i.play();
    }
}

public class Wind extends Instrument{
    public static void main(String[] args) {
        Wind flute = new Wind();
        Instrument.tune(flute); //Upcasting
    }
}

一个方法是在基类中定义的,参数类型是基类,但是可以在子类中使用基类对象调用这个方法,参数类型可以是子类。这时便把子类参数向上转型为基类。(小往大转,总是安全的)

练习16:创建一个名为Amphibian的类。由此继承产生一个称为Frog的类。在基类中设置适当的方法。在main()中创建一个Frog并向上转型至Amphibian,然后说明所有的方法都可工作。

class Amphibian {
    public void moveInWater() {
        System.out.println("Moving in Water");
    }

    public void moveOnLand() {
        System.out.println("Moving on Land");
    }
}

class Frog extends Amphibian {
}
public class E16_Frog {
    public static void main(String args[]) {
        Amphibian a = new Frog();
        a.moveInWater();
        a.moveOnLand();
    }
} /* Output: Moving in Water Moving on Land */

到底该用组合还是继承,一个最清晰的判断方法就是是否需要从新类向基类进行向上转型,如果必须向上转型,则继承是必要的,如果不需要,则应当考虑是否需要继承。

7.8 final关键字

7.8.1 final数据

  1. 一个永不改变的编译时常量
  2. 一个在运行时被初始化的值,而你不希望它被改变

对于编译器常量这种情况,编译器可以将常量值带入到任何可能用到的计算式中,也就是说可以在编译时执行计算式。这类常量必须是基本数据类型,并且以关键字final表示。在对这个变量进行定义的时候,必须对其进行赋值

典型的对常量进行定义的方式:

public static final int VALUE_THREE = 39;

定义为public,则可以用于包之外;定义为static,则强调只有一份;定义为final,则说明它是一个常量。
带有恒定初始值(即编译期常量)的final static 基本类型全用大写字母命名,并且字与字之间用下划线隔开。

当final作用于一个对象时,代表这个变量用于对象的引用,并且只能引用这一个对象,而不能再次进行引用设置。
比如当定义一个final变量a数组,就不能再后面将a指向别的数组。

private final int[] a = {1, 2, 3, 4, 5, 6};
//a = new int[3];   //编译器会报错

练习18:创建一个含有static final域和final域的类,并说明二者之间的区别。

class SelfCounter {
    private static int count;

    private int id = count++;

    public String toString() {
        return "SelfCounter " + id;
    }
}

class WithFinalFields {
    final SelfCounter scf = new SelfCounter();
    static final SelfCounter scsf = new SelfCounter();

    public String toString() {
        return "scf = " + scf + "\nscsf = " + scsf;
    }
}

public class E18_FinalFields {
    public static void main(String args[]) {
        System.out.println("First object:");
        System.out.println(new WithFinalFields());
        System.out.println("Second object:");
        System.out.println(new WithFinalFields());
    }
}
/*
Output:
First object:
scf = SelfCounter 1
scsf = SelfCounter 0
Second object:
scf = SelfCounter 2
scsf = SelfCounter 0
*///:~

Because class loading initializes the static final, it has the same value in both instances of WithFinalFields, whereas the regular final’s values are different for each instance.
因为类装入初始化了静态final,所以它在WithFinalFields的两个实例中具有相同的值,而常规final的值对于每个实例是不同的。

空白final
Java允许生成空白的final,所谓空白final是指被声明为final但又为给定初始值的域。无论什么情况下,编译器都确保空白final在使用前必须被初始化。

必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是final域在使用前总是被初始化的原因所在。

final参数
Java允许在参数列表中以声明的方式将参数指明为final,这意味着你无法在方法中更改参数引用所指向的对象:

class Gizmo{
    public void spin(){}
}

public class FinalArguments {
    void with(final Gizmo g){
        //g = new Gizmo();  //不能给final类型的引用对象指定新的引用
    }
    void without(Gizmo g){
        g = new Gizmo();
        g.spin();
    }

    void f(final int i){
        //i++;  //final的基本类型变量,值不能被更改
    }
    int g(final int i){return i + 1;}  // 可以被读取到

    public static void main(String[] args) {
        FinalArguments bf = new FinalArguments();
        bf.without(null);
        bf.with(null);
    }
}

final和private关键字
类中所有的private方法都隐式地指定为final的。由于无法取用private方法,所以也就无法覆盖它。可以对private方法添加final修饰词,但是并不能给该方法增加任何额外的意义。

如果你视图覆盖一个private方法(隐含是final的),似乎是凑效的,而且编译器也不会给出错误信息,但是调用的时候会发现,private方法无法调用。

class WithFinals{
    private final void f(){
        System.out.println("WithFinals.f()");
    }
    private void g(){
        System.out.println("WithFinals.g()");
    }
}

class OverridingPrivate extends WithFinals{
    //@Override
    private final void f(){
        System.out.println("Overriding.f()");
    }
    private void g(){
        System.out.println("Overriding.g()");
    }
}

class OverridingPrivate2 extends OverridingPrivate{
    public final void f(){
        System.out.println("OverridingPrivate2.f()");
    }
    public void g(){
        System.out.println("OverridingPrivate2.g()");
    }
}

public class FinalOverridingIllusion {
    public static void main(String[] args) {
        OverridingPrivate2 op2 = new OverridingPrivate2();
        op2.f();
        op2.g();

        //OverridingPrivate op = op2;
        OverridingPrivate op = new OverridingPrivate();
        //op.f();  // 提示该方法在类中定义为private
        //op.g();
        WithFinals wf = op2;
        //wf.f();
        //wf.g();
    }
}

“覆盖”只有在某方法是基类的接口的一部分时才会出现。即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法是private,它就不是基类的接口的一部分。它仅是一些隐藏于类中的程序代码,只不过是具有相同的名称而已。但如果再导出类中以相同的名称生成一个public、protected或包访问权限方法的话,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时你并没有覆盖该方法,仅是生成了一个新的方法。由于private方法无法触及而且能有效隐藏,所以除了把它看成是因为它所归属的类的组织结构的原因而存在外,其他任何事物都不需要考虑到它。

**问题???**:在练习20中,说使用@Overriding注解可以解决上面的问题。 但是我加了注解之后仍然不能调用,不知道哪里的问题。

7.8.3 final类
当将某个类的整体定义为final时(通过将关键字final置于它的定义之前),就表明了你不打算继承该类,而且也不允许别人这样做。

final类的域可以根据个人的意愿选择是或不是final类型,如果不是final类型,则仍然可以修改其值。

由于final类禁止继承,所以final类中的所有方法都隐式指定为是final的,因为无法覆盖它们。

class SmallBrain{
}

final class Dinosaur{
    final int i = 7;
    int j = 1;
    SmallBrain x = new SmallBrain();
    void f(){}
}

//class Further extends Dinosaur{}  // 提示不可继承

public class Jurassic {
    public static void main(String[] args) {
        Dinosaur n = new Dinosaur();
        n.f();
        //n.i = 40;  //final类型的域不可更改
        System.out.println(n.i);
        n.j++;
        System.out.println(n.j);
    }
}

7.9 初始化及类的加载

一般来说,类的代码在初次使用时才会加载,这通常是指加载发生于创建类的第一个对象之时,但是当访问static域或者static方法时,也会发生加载。

初次使用之处也是static初始化发生之处。所有的static对象和static代码段都会在加载时依程序中的顺序(即,定义类时的书写顺序)而一次初始化。当然定义为static的东西只会被初始化一次。

7.9.1 类的继承与初始化

了解包括继承在内的初始化全过程,以对所有发生的一切有个全局性的把握,是很有益的。

class Insert {
    private int i = 9;
    protected  int j;
    Insert(){
        System.out.println("i = " + i + ", j = " + j);
        j = 39;
    }
    private static int x1 = printInit("static Insert.x1 initialized");

    static int printInit(String s){
        System.out.println(s);
        return 47;
    }
}

public class Beetle extends Insert{
    private int k = printInit("Beetle.k initialized");
    public Beetle(){
        System.out.println("k = " + k);
        System.out.println("j = " + j);
    }
    private static int x2 = printInit("static Beetle.x2 initialized");

    public static void main(String[] args) {
        System.out.println("Beetle constructor");
        Beetle b = new Beetle();
    }
}
/*
static Insert.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
 */

在Beetle上运行java时,所发生的第一件事情就是试图访问Beetle.main()(一个static方法),于是加载器开始启动并找出Beetle类的编译代码(在名为Beetle.class的文件中)。对它进行加载的过程中,编译器注意到它有一个基类(这是由关键字extends得知的),于是它继续进行加载。不管你是否打算产生一个该基类的对象,这都要发生(请尝试将对象创建代码注释掉,以证明这一点)。

如果该基类还有其自身的基类,那么第二个基类就会被加载,以此类推。接下来基类中的static初始化(此例中为Insect)即会被执行,然后是一个导出类,以此类推。这种方式很重要,因为导出类的static初始化可能会依赖基类成员能否正确初始化。

至此为止,必要的类已加载完毕,对象的创建就可以进行了。首先,对象中所有的基本类型都会被设为默认值对象引用被设为null—这是通过将对象对象内存设为二进制零值而一举生成的。然后基类的构造器会被调用。在本例中,它是被自动调用的。但也可以用super来指定对基类构造器的调用(正如在Beetle()构造器中的第一步操作)。基类构造器和导出类构造器一样,以相同的顺序来经历相同的过程。在基类构造器完成后,实例变量按其次序被初始化。自后构造器的其余部分被执行(???构造器和实例变量谁先执行)。

个人总结:首先是执行main方法,加载器开始加载编译代码class文件,由extends发现有基类,先初始化基类的static域,static域初始化之后,开始对象创建,先将对象中所有的基本类型设为默认值(这里的基本类型理解为int i = 0这种的比较好理解,上面例子中的k初始化在构造器之前了),然后是构造器的执行(基类构造器先执行),构造器执行结束,实例变量再按顺序初始化。

第八章:多态

多态的作用则是消除类型之间的耦合关系

8.1 再论向上转型

演奏乐符:Note(枚举类型)

public enum Note {
    MIDDLE_C, C_SHARP, B_FLAT;
}

Wind是一种Instrument,因此可以从Instrument类继承。

class Instrument{
    public void play(Note n){
        System.out.println("Instrument.play()");
    }
}

public class Wind extends Instrument {
    public void play(Note n){
        System.out.println("Wind.play() " + n);
    }
}
public class Music {
    public static void tune(Instrument i){
        i.play(Note.MIDDLE_C);
    }

    public static void main(String[] args) {
        Wind flute = new Wind();
        tune(flute);
    }
}
/*OutPut:
Wind.play() MIDDLE_C
*/

Music.tunr()方法接受一个Instrument引用,同时也接受任何导出自Instrument的类。在main() 方法中,当一个Wind引用传递到tune()方法时,就会出现这种情况,而不需要任何类型转换。这样做是允许的----因为Wind从Instrument继承而来,所以Instrument的接口必定存在于wind中。从Wind向上转型到Instrument可能会缩小接口,但不会比Instrument的全部接口更窄。

练习10: 创建一个包含两个方法的基类,在第一个方法中可以调用第二个方法。然后产生一个继承自该基类的导出类,且覆盖基类中的第二个方法。为该导出类创建一个对象,将它向上转型到基类型并调用第一个方法,解释发生的情况。

class TwoMethods {
    public void m1() {
        System.out.println("Inside m1, calling m2");
        m2();
    }

    public void m2() {
        System.out.println("Inside m2");
    }
}

class Inherited extends TwoMethods {
    public void m2() {
        System.out.println("Inside Inherited.m2");
    }
}

public class E10_MethodCalls {
    public static void main(String args[]) {
        TwoMethods x = new Inherited();
        x.m1();
    }
}
/* Output:
Inside m1, calling m2
Inside Inherited.m2
*///:~

可以看出导出类只覆盖了基类的第二种方法,在向上转型之后,基类对象x执行方法1时,由于导出类为覆盖方法一,则直接执行基类中的方法一,而方法一又调用了方法二,这时导出类中又覆盖了方法二,因此会执行导出类的方法二,而不会执行基类的方法二。对于导出类而言,基类中的方法二已经被覆盖掉了,而方法一作为直接继承过来的方法,可以直接使用。

8.2.4 缺陷:“覆盖”私有方法(private)

public class PrivateOverriding {
    private void f(){
        System.out.println("private f()");
    }

    public static void main(String[] args) {
        PrivateOverriding po = new Derived();
        po.f();
    }
}

class Derived extends PrivateOverriding{
    public void f(){
        System.out.println("public f()");
    }
}
/* Output:
private f()
*///:~

这里会发现一个很神奇的东西,输出的结果竟然是基类的方法,而不是导出类的方法。
这是由于private方法被自动认为是final方法,而且对导出类是屏蔽的。因此,在这种情况下,Derived类中的f()方法就是一个全新的方法;既然基类中的方法f() 方法在子类Derived中不可见,因此也不能被重载。

结论就是:只有非private方法才可以被覆盖;但是还需要密切注意覆盖private方法的现象,这时编译器不会报错,但是也不会按照我们所期望的来执行。确切的说,在导出类中,对于基类中的private方法,最好采用不同的名字。

8.2.5 缺陷:域与静态方法

class Super{
    public int field = 0;
    public int getField(){
        return field;
    }
}

class Sub extends Super{
    public int field = 1;
    public int getField(){
        return field;
    }
    public int getSuperField(){
        return super.field;
    }
}

public class FieldAccess {
    public static void main(String[] args) {
        Super sup = new Sub();
        System.out.println("sup.field = " + sup.field + ", sup.getField = " + sup.getField());
        Sub sub = new Sub();
        System.out.println("sub.field = " + sub.field + ", sub.getField = " + sub.getField() + ", sub.getSuperField = " + sub.getSuperField());
    }
}
/* Output:
sup.field = 0, sup.getField = 1
sub.field = 1, sub.getField = 1, sub.getSuperField = 0
*///:~

当Sub对象转型为Super引用时,任何域访问操作都将由编译器解析,因此不是多态的。

在本例中,为Super.field和Sub.field分配了不同的存储空间。这样,Sub实际上包含两个称为field的域:它自己的域和它从Super处得到的。然而,在引用Sub中的field时所产生的默认域并非Super版本的field域。因此,为了得到Super.field,必须显式地指明super.field.

如果某个方法是静态的,它的行为就不具有多态性

class StaticSuper{
    public static String staticGet(){
        return "Base staticGet()";
    }

    public String dynamicGet(){
        return "Base dynamicGet()";
    }
}

class StaticSub extends StaticSuper{
    public static String staticGet(){
        return "Derived staticGet()";
    }

    public String dynamicGet(){
        return "Derived dynamicGet()";
    }
}

public class StaticPolymorphism {
    public static void main(String[] args) {
        StaticSuper sup = new StaticSub();
        System.out.println(sup.staticGet());
        System.out.println(sup.dynamicGet());
    }
}
/* Output:
Base staticGet()
Derived dynamicGet()
*///:~

8.3 构造器和多态

8.3.1构造器的调用顺序

class Meal{
    Meal(){
        System.out.println("Meal()");
    }
}

class Bread{
    Bread(){
        System.out.println("Bread()");
    }
}

class Chess{
    Chess(){
        System.out.println("Chess()");
    }
}

class Lettuce{
    Lettuce(){
        System.out.println("Lettuce()");
    }
}

class Lunch extends Meal{
    Lunch(){
        System.out.println("Lunch()");
    }
}

class PortableLunch extends Lunch{
    PortableLunch(){
        System.out.println("PortableLunch()");
    }
}

public class Sandwich extends PortableLunch {
    private Bread b = new Bread();
    private Chess c = new Chess();
    private Lettuce l = new Lettuce();
    public Sandwich(){
        System.out.println("Sandwitch()");
    }
    public static void main(String[] args) {
        new Sandwich();
    }
}
/* Output:
Meal()
Lunch()
PortableLunch()
Bread()
Chess()
Lettuce()
Sandwitch()
*///:~

复杂对象调用构造器要遵照下面的顺序:

  1. 调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次的跟,然后是下一层导出类,等等,直到最底层的导出类。
  2. 按照声明顺序调用成员的初始化方法。
  3. 调用导出类构造器的主体。

8.5.2 向下转型和运行时类型识别

向上转型会丢失具体的类型信息,是安全的。
向下转型失败时,会跑出一个类转型异常(ClassCastException)

class Useful{
    public void f(){
        System.out.println("Useful.f()");
    }
    public void g(){
        System.out.println("Useful.g()");
    }
}

class MoreUseful extends Useful{
    public void f(){
        System.out.println("MoreUseful.f()");
    }
    public void g(){
        System.out.println("MoreUseful.g()");
    }
    public void u(){
        System.out.println("MoreUseful.u()");
    }
}

public class RTTI {
    public static void main(String[] args) {
        Useful[] x = {new Useful(), new MoreUseful()};
        x[0].f();
        x[1].g();
        ((MoreUseful)x[1]).u();  // Downcast /RTTI
        //((MoreUseful)x[0]).u();  //ClassCastException: char8.Useful cannot be cast to char8.MoreUseful
    }
}
/*
Output:
Useful.f()
MoreUseful.g()
MoreUseful.u()*/
第九章:接口

9.1抽象类和抽象方法

抽象类的目的是为它的所有的导出类创建一个通用接口。不同的子类可以用不同的方法会表示此接口。

抽象方法:仅有声明而没有方法体。 abstract void f();

包含抽象方法的类叫做抽象类,如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的(abstract),否则编译器会报错。

如果一个抽象类继承,并不想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义。如果不那么做(可以选择不做),那么该导出类便也是抽象类,并且编译器会强制我们使用abstract关键字来限定该类。

抽象类中的方法并不需要所有的方法都是抽象的,仅需将我们需要的方法声明为抽象即可。其他的非抽象方法需要有方法体。

练习2:创建一个抽象类,并验证不能被实例化。

abstract class NoAbstractMethod{
    void f(){
        System.out.println("f()");
    }
}

public class E02_Abstract {
    public static void main(String[] args) {
        //new NoAbstractMethod();  //编译器会报错,抽象类,不能被实例化
    }
}

练习3:创建一个基类,让他包含抽象方法print(),并在导出类中覆盖改方法。覆盖后的方法版本可以打印出导出类中定义的某个整形变量的值。在定义该变量之处,赋予它非零值。在基类的构造方法中调用这个方法。现在,在main() 方法中创建一个导出类对象,然后调用它的print方法。解释这种情况。

abstract class BaseWithPrint {
    public BaseWithPrint() {
        print();
    }

    public abstract void print();
}

class DerivedWithPrint extends BaseWithPrint {
    int i = 47;

    public void print() {
        System.out.println("i = " + i);
    }
}

public class E03_Initialization {
    public static void main(String args[]) {
        DerivedWithPrint dp = new DerivedWithPrint();
        dp.print();
    }
} /* Output: 
i = 0 
i = 47 
*///

The java virtual machine zeroes the bits of the object after it allocates storage, producing a default value for i before any other initialization occurs. The code calls the base-class constructor before running the derived-class initialization, so we see the zeroed value of i as the initial output.
The danger of calling a method inside a constructor is when that method depends on a derived initialization. Before the derived-class constructor is called, the object may be in an unexpected state (in Java, at least that state is defined; this is not true with all languages – C++, for example). The safest approach is to set the object into a known good state as simply as possible, and then perform any other operations outside the constructor.
翻译:java虚拟机在分配存储之后对对象的位进行归零,在任何其他初始化发生之前为i生成一个默认值。代码在运行派生类初始化之前调用基类构造函数,因此我们将i的零值作为初始输出。

在构造函数中调用方法的危险在于该方法依赖于派生的初始化。在调用派生类构造函数之前,对象可能处于意外状态(在Java中,至少定义了该状态;并不是所有的语言都是这样,例如c++)。最安全的方法是尽可能简单地将对象设置为已知的良好状态,然后在构造函数之外执行任何其他操作。

这是第八章中的关于初始化的知识:
初始化的实际过程:

  1. 在其他任何事物发生之前,将分配给对象的存储空间初始化为二进制的零。
  2. 如前述那样调用基类构造器。此时,调用被覆盖后的print()方法,由于步骤一的缘故,我们此时会发现i的值为0.
  3. 按照声明的顺序调用成员的初始化方法。
  4. 调用导出类的构造器主体。

编写构造器时有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话避免调用其他方法”。在构造器内唯一安全调用的那些方法是基类中的final方法(也适用于private方法,他们自动属于finale方法)。这些方法不能被覆盖,因此也就不会出现上述的问题。

9.4 Java中的多重继承(区分extends和implements的使用)

关键字解释
extends:继承;指的是同一类型的,接口与接口之间或者基类与子类之间使用,为扩展性。
implements:实现;类实现接口时使用。
使用接口的核心原因:为了能够向上转型为多个基类型(以及由此带来的灵活性)。第二个原因却是与抽象基类相同:防止客户端程序员创建该类的对象,并确保这仅仅建立一个接口。

这就带来了一个问题:我们应该使用接口还是抽象类?

如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。事实上,如果知道某事物应该成为一个基类,那么第一选择应该是使它成为一个接口。

练习13:创建一个接口,并从该接口继承两个接口,然后从后面两个接口多重继承第三个接口。

interface BaseInterface {
    void f();
}
 
interface IntermediateInterface1 extends BaseInterface {
    void f();
}
 
interface IntermediateInterface2 extends BaseInterface {
    void f();
}
 
interface CombinedInterface extends IntermediateInterface1, IntermediateInterface2 {
    void f();
}
 
class CombinedImpl implements CombinedInterface {
    public void f() {
        System.out.println("CombinedImpl.f()");
    }
}
 
public class E13_Diamond {
    public static void main(String[] args) {
        new CombinedImpl().f();
    }
}
    
/* Output: CombinedImpl.f() *///:~

一般情况下,只可以将extends用于单一类,但是可以引用多个基类接口。就像上面一样,只需用逗号将接口名一一分开即可。

第十章 内部类

看的有点麻木了,暂时先跳过了。。。。。。。。。

标签:Java,final,读书笔记,void,public,---,基类,println,class
From: https://blog.51cto.com/linmengmeng/5907343

相关文章