2022.2.17

问题

  1. 正如你在第8章会看到的,当引入继承时,通过继承而来的类(子类)可以访问父类的protected成员以及public成员(但不能访问private成员)。只有当两个类在同一个包中时,它才可以访问父类的包访问权限成员。但现在不必担心继承和protected。7.2.1

    没看懂这句话想表达什么

    答:意思很简单,就是强调默认包的访问权限

  2. 然而,仅仅因为一个对象的引用在类中是private的,并不意味着其他对象不能拥有对同一个对象的public引用。(请参阅进阶卷第2章了解别名问题。)7.2.3

    这句话我也没看懂

第七章 实现隐藏

  1. 对于仅用于实现类但不提供给客户程序员直接使用的方法也是如此。

    这里的方法对应的应该是我们的私有方法

  2. 当编译一个.java文件时,文件中的每个类都会有一个输出文件。输出文件的名字就是其在.java文件中对应的类的名字,但扩展名为.class。因此,你可以从少量的.java文件中得到相当多的.class文件。如果使用编译型语言写过程序,你可能习惯于编译器输出一个中间形式(通常是obj文件),然后使用链接器(linker)或库生成器(librarian,用来创建库)将它与其他同类文件打包在一起,以创建一个可执行文件。Java不是这样的。在Java中一个可运行程序就是一堆.class文件,可以使用jar归档器将它们打包并压缩成一个Java档案文件(JAR)。Java解释器负责查找、加载和解释这些文件。

  3. import static onjava.Range.*;
    

    这种写法可以让你直接调用静态方法

    fun()
    

    而不用写成这样

    类名.方法名(),
    
  4. 挺奇妙的,protected的使用居然是为了在包外可以使用继承的基类的方法,这确实让我挺意外的

  5. 不是public的类的引用,如果在包外使用了,编译器是找不到的

  6. 还有protected修饰符不能用于类

    protected class one {protected void f(){System.out.println("hello");}
    }
    

    这么写编译不过

  7. 有时候基类的创建者想要把特定成员的访问权限赋给子类,而不是所有的类,这时候protected就可以发挥作用了。protected还提供了包访问权,也就是说,同一包中的其他类也可以访问protected元素。

    如果你回顾文件Cookie.java,就会知道下面的类不能调用包访问权限成员bite()

    // hiding/ChocolateChip.java
    // 无法在另一个包里调用包访问权限的成员
    import hiding.dessert.*;public class ChocolateChip extends Cookie {public ChocolateChip() {System.out.println("ChocolateChip constructor");}public void chomp() {//- bite(); // 无法访问bite}public static void main(String[] args) {ChocolateChip x = new ChocolateChip();x.chomp();}
    }
    /* 输出:
    Cookie constructor
    ChocolateChip constructor
    */
    

    如果类Cookie中存在一个方法bite(),那么这个方法也存在于任何继承Cookie的类中。但是bite()只具有包访问权限并且位于另一个包中,因此无法在当前包中使用它。你可以将其修改为public,但这样的话每个人就都可以访问它了,这也许不是你想要的。如果按如下方式更改类Cookie

    // hiding/cookie2/Cookie.java
    package hiding.cookie2;public class Cookie {public Cookie() {System.out.println("Cookie constructor");}protected void bite() {System.out.println("bite");}
    }
    

    这样任何继承Cookie的类都可以访问bite()

    // hiding/ChocolateChip2.java
    import hiding.cookie2.*;public class ChocolateChip2 extends Cookie {public ChocolateChip2() {System.out.println("ChocolateChip2 constructor");}public void chomp() { bite(); } // protected方法public static void main(String[] args) {ChocolateChip2 x = new ChocolateChip2();x.chomp();}
    }
    /* 输出:
    Cookie constructor
    ChocolateChip2 constructor
    bite
    */
    

    这时尽管bite()也有包访问权限,但它不是public的。

  8. 总结一下,如果将不同的包看作使用者,那么public就是公车.如果将类看作使用者的话,那么默认类就是公车,默认类至少保证了同一个包中的引用还是可用的,不至于无法创建引用.(注意我这里没提构造器的事)

  9. 然后,变量的访问权限大于类的访问权限没有意义

  10. 请注意,类不能是private(这将使除该类之外的任何类都无法访问它)或protected5。因此,对于类访问权限,只有两种选择:包访问权限和public。如果想要防止对该类的访问,可以将其所有的构造器都设为private,从而禁止其他人创建该类的对象,而你则可以在这个类的静态方法中创建对象:

    5实际上,内部类可以是private的或protected的,但这是特殊情况。这些主题在第11章中会介绍。

  11. 默认变量只在同一个包中可以随便用,也就是它是同一个包下类的公车

    package example;public class one {int i=9;public static void main(String[] args){System.out.println(new two().i);}
    }
    class two {int i=0;
    }
    
    package Test;import example.one;public class test {public static void main(String[] args){one a = new one();System.out.println(a.i);}}
    

    你会发现下面的代码编译通不过

  12. 注意,访问权限控制侧重于库开发者和该库的外部客户之间的关系,这也是一种通信方式。不过有很多情况并非如此。例如,你自己编写所有的代码,或者你与一个小团队密切合作,而且所有的内容都放在同一个包中。这些情况是另一种不同的通信方式,严格遵守访问权限规则可能不是最佳选择。默认的(包)访问权限可能就够用了。

第八章 复用

  1. 初始化引用有下列4种方式。

    1. 在定义对象时。这意味着它们将始终在调用构造器之前被初始化。
    2. 在该类的构造器中。
    3. 在对象实际使用之前。这通常称为延迟初始化(lazy initialization)。在对象创建成本高昂且不需要每次都创建的情况下,它可以减少开销。
    4. 使用实例初始化

    以下是这4种方式的示例:

    // reuse/Bath.java
    // 使用组合进行构造器初始化class Soap {private String s;Soap() {System.out.println("Soap()");s = "Constructed";}@Override public String toString() { return s; }
    }public class Bath {private String // 在定义时初始化s1 = "Happy",s2 = "Happy",s3, s4;private Soap castile;private int i;private float toy;public Bath() {System.out.println("Inside Bath()");s3 = "Joy";toy = 3.14f;castile = new Soap();}// 实例初始化{ i = 47; }@Override public String toString() {if(s4 == null) // 延迟初始化s4 = "Joy";return"s1 = " + s1 + "\n" +"s2 = " + s2 + "\n" +"s3 = " + s3 + "\n" +"s4 = " + s4 + "\n" +"i = " + i + "\n" +"toy = " + toy + "\n" +"castile = " + castile;}public static void main(String[] args) {Bath b = new Bath();System.out.println(b);}
    }
    /* 输出:
    Inside Bath()
    Soap()
    s1 = Happy
    s2 = Happy
    s3 = Joy
    s4 = Joy
    i = 47
    toy = 3.14
    castile = Constructed
    */
    

  1. 如果省略访问权限修饰符,则该成员的权限默认是包访问权限,仅允许包内的成员进行访问。因此,在这个包内,如果没有访问权限修饰符,任何人都可以使用这些方法。例如Detergent就没有问题。但是,如果来自其他包的类要继承Cleanser,那它就只能访问public成员。因此,考虑到继承,作为一般规则,应该将所有字段设为private,将所有方法设为public(稍后你将学到,protected成员也允许子类访问)。在特定情况下,你必须进行调整,但一般来说这是一个有用的指导方针。

    注意下,protected修饰符是专门用来针对public修饰符的,默认修饰符对他影响不大

    public class test extends two {public static void main(String[] args){one a = new one();System.out.println(a.i);two b = new two();}
    }
    
    package example;public class one {int i=9;public static void main(String[] args){System.out.println(new two().i);}
    }
    class two {int i=0;protected void f(){System.out.println("hello");}
    }
    

    会显示example.two在example中不是公共的; 无法从外部程序包中对其进行访问,还有默认变量无法在包外访问,还有这里光是导入two这个类就会报错

    package Test;import example.one;public class test extends one {public static void main(String[] args){one a = new one();new test().f();}}
    
    package example;public class one {int i=9;public static void main(String[] args){System.out.println(new two().i);}protected void f(){System.out.println("hello");}
    }
    class two {int i=0;protected void f(){System.out.println("hello");}
    }
    

    正如在scrub()中看到的那样,可以使用基类中定义的方法并对其进行修改。在这个示例中,你可能想从新版本的方法里调用继承来的基类方法。但是在scrub()中不能简单地调用scrub(),因为这会产生递归调用。为了解决这个问题,Java提供了super关键字,来指代当前类继承的“超类”(基类)。因此,表达式super.scrub()调用了基类版本的scrub()方法。

    这里会递归调用主要是因为会写成这样

    f(){f()
    }
    

    如果不是的话,其实没必要,不过把这个堵死了也好,毕竟可能产生歧义

  2. 现在涉及两个类:基类和子类。想象一下子类产生的对象,这可能会令人困惑。从外部看,新类与基类具有相同的接口,或许还有一些额外的方法和字段。但是继承不只是复制基类的接口这么简单。当创建子类对象时,它里面包含了一个基类的子对象(subobject)。这个子对象与直接通过基类创建的对象是一样的。只是从外面看,基类的子对象被包裹在了子类的对象中。

  3. 对基类构造器的调用必须是子类构造器的第一个操作

  4. // reuse/SpaceShipDelegation.javapublic class SpaceShipDelegation {private String name;private SpaceShipControls controls =new SpaceShipControls();public SpaceShipDelegation(String name) {this.name = name;}// 委托方法:public void back(int velocity) {controls.back(velocity);}public void down(int velocity) {controls.down(velocity);}public void forward(int velocity) {controls.forward(velocity);}public void left(int velocity) {controls.left(velocity);}public void right(int velocity) {controls.right(velocity);}public void turboBoost() {controls.turboBoost();}public void up(int velocity) {controls.up(velocity);}public static void main(String[] args) {SpaceShipDelegation protector =new SpaceShipDelegation("NSEA Protector");protector.forward(100);}
    }
    
    public class SpaceShipControls {void up(int velocity) {}void down(int velocity) {}void left(int velocity) {}void right(int velocity) {}void forward(int velocity) {}void back(int velocity) {}void turboBoost() {}
    }
    
    // reuse/DerivedSpaceShip.javapublic class
    DerivedSpaceShip extends SpaceShipControls {private String name;public DerivedSpaceShip(String name) {this.name = name;}@Override public String toString() {return name;}public static void main(String[] args) {DerivedSpaceShip protector =new DerivedSpaceShip("NSEA Protector");protector.forward(100);}
    }
    

    看了半天他这个代码,终于看懂了他的意思,就是要隐藏一个类,定义的public实际上就类似于一个空壳子,里面private的对象才是实际上掌控方法的,就怎么说呢,这个写的是逻辑吧,境界太高,只能理解到这了

    其实有那么一点感觉,委托模式好像是代理模式的前身,虽然两者差别很大.

  5. 没看懂8.4.1,我寻思着,你这是要按照栈的顺序清理是吗?看着太像入栈和出栈了

其他

  1. 现在想想,其实构造器作为一种静态方法确实很神奇,它居然可以访问到非静态的变量,现在对构造器的理解应该是一种特殊的方法,因为它能够修改类的private变量,会导致一定程度的封装破坏,所以对它的使用就比较严格

    而转过来想一想,也正是因为构造器实际上是一种静态方法,所以才可以在构造器方法里调用另一个构造器方法,虽然不能同时调用两个构造器,且构造器必须出现在构造器方法的最上面

    public class test {int i ,j,m,n;public test(int i){this.i=i;}public test(int i,int j,int m){this(i);this.m=m;this.j=j;}public test(int i,int j){this(i);this.j=j;}void f(){System.out.println("i="+i+"j="+j+"m="+m);}public static void main(String[] args){new test(5,3,2).f();}
    }
    

    这里的this我侧重于理解this方法,this静态方法.其实感觉如果理解了静态方法的概念,也就理解this()方法的精髓了,这个构造器方法确实巧妙

  2. 虽然静态方法可以产生内部类,不过这个内部类外部是不用想着访问了

    public class test {public static void f(){class  a {public a(){System.out.println("hello");}}new a();}public static void main(String[] args){a one = new a();test.f();}
    }
    

    上面的代码会显示找不到对象a,所以即便我将a的构造器作为了public对象,外界也别想访问这个对象,不是因为初始化做不到,而是找不到这个类

2022.2.18

第八章 复用

  1. 组合和继承都会将子对象放置在新类中(组合是显式执行此操作,而继承是隐式执行)。你可能想知道两者之间的区别,以及如何在两者之间做出选择。

    当希望在新类中使用现有类的功能而不是其接口时,应该使用组合。也就是说,在新类中嵌入一个对象(通常是private)来实现自己的特性。新类的用户看到的是新类定义的接口,而不是嵌入对象的接口。

    对于新类里通过组合得到的成员,有时候允许类的使用者直接访问它们是合理的。为此,可以将成员对象设为public(你可以将其视为一种“半委托”)。成员对象隐藏自己的实现,所以这种做法是安全的。当用户了解到你正在组装一堆组件时,会更容易理解你的接口。car对象就是一个很好的例子:

    // reuse/Car.java
    // 使用公共对象来实现组合class Engine {public void start() {}
    public void rev() {}
    public void stop() {}
    }class Wheel {public void inflate(int psi) {}
    }class Window {public void rollup() {}
    public void rolldown() {}
    }class Door {public Window window = new Window();
    public void open() {}
    public void close() {}
    }public class Car {public Engine engine = new Engine();
    public Wheel[] wheel = new Wheel[4];
    public Doorleft = new Door(),right = new Door(); // 双门车
    public Car() {for(int i = 0; i < 4; i++)wheel[i] = new Wheel();
    }
    public static void main(String[] args) {Car car = new Car();car.left.window.rollup();car.wheel[0].inflate(72);
    }
    }
    

    很有意思的一种组合方式,用public确实可以让人更加清晰的了解

  2. // reuse/Orc.java
    // protected关键字class Villain {private String name;
    protected void set(String nm) { name = nm; }
    Villain(String name) { this.name = name; }
    @Override public String toString() {return "I'm a Villain and my name is " + name;
    }
    }public class Orc extends Villain {private int orcNumber;
    public Orc(String name, int orcNumber) {super(name);this.orcNumber = orcNumber;
    }
    public void change(String name, int orcNumber) {set(name); // 方法可用,因为是protected的this.orcNumber = orcNumber;
    }
    @Override public String toString() {return "Orc " + orcNumber + ": " + super.toString();
    }
    public static void main(String[] args) {Orc orc = new Orc("Limburger", 12);System.out.println(orc);orc.change("Bob", 19);System.out.println(orc);
    }
    }
    /* 输出:
    Orc 12: I'm a Villain and my name is Limburger
    Orc 19: I'm a Villain and my name is Bob
    */
    

    还有这里学习一下调用基类的toString();

  3. 常量之所以有用,有两个原因:

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

    对编译时常量来说,编译器可以将常量值“折叠”到计算中;也就是说,计算可以在编译时进行,这节省了一些运行时开销。在Java里,这些常量必须是基本类型,并用final关键字表示。在定义常量时必须提供一个值。

  4. final关键字与对象引用而非基本类型一起使用时,其含义可能会令人困惑。对于基本类型,final使其恒定不变,但对于对象引用,final使引用恒定不变。一旦引用被初始化为一个对象,它就永远不能被更改为指向另一个对象了。但是,对象本身是可以修改的。Java没有提供使对象恒定不变的方法。(但是,你可以编写类,使对象具有恒定不变的效果。)这个限制同样适用于数组,它们也是对象。

  5. public class one {String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){System.out.println(new two("second"));}
    }
    
    public class two  extends one{public two (String s1){super(s1);this.s1="other";}@Overridepublic String toString() {return s1;}
    }
    

    最后输出的是other不过要在是s1前加this,不然就是second

    不加s1,就不是two这个对象的字段被赋值,而是s1这个变量被赋值

  6. // reuse/FinalArguments.java
    // 在方法参数中使用finalclass Gizmo {public void spin() {}
    }public class FinalArguments {void with(final Gizmo g) {//- g = new Gizmo(); // Illegal -- g is final}void without(Gizmo g) {g = new Gizmo(); // OK -- g not finalg.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);}
    }
    
  7. “重写”只有在方法是基类接口的一部分时才会发生。也就是说,必须能将一个对象向上转型为其基类类型并能调用与其相同的方法(下一章中你会更理解这一点)。如果一个方法是private的,它就不是基类接口的一部分。它只是隐藏在类中的代码,只不过恰好具有相同的名称而已。即使在子类中创建了具有相同名称的publicprotected或包访问权限的方法,它与基类中这个相同名称的方法也没有任何联系。你并没有重写该方法,只不过是创建了一个新的方法。private方法是不可访问的,并且可以有效地隐藏自己,因此除了定义它的类的代码组织之外,它不会影响任何事情。

  8. final类的字段可以是final,也可以不是,根据个人选择而定。无论类是否定义为final,相同的规则都适用于字段的final定义。然而,由于final类禁止继承,它的所有方法都是隐式final的,因为无法重写它们。你可以在final类的方法中包含final修饰符,但它不会添加任何意义。

    首先final类本身不能继承,所以即便字段是public的,也不能用子类进行访问

    但是一个public的final类并不会禁止包外的类除了自己的子类外访问自己public字段

    package Test;import example.a;
    import example.one;
    import example.*;
    public class test  extends two {public static void main(String[] args){new one().s1 = "heool";}
    }
    
    package example;
    public final class one {public String s1 = "hello";
    }
    
  9. // reuse/Beetle.java
    // 初始化的全过程class Insect {private int i = 9;protected int j;Insect() {System.out.println("i = " + i + ", j = " + j);j = 39;}private static int x1 =printInit("static Insect.x1 initialized");static int printInit(String s) {System.out.println(s);return 47;}
    }public class Beetle extends Insect {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 Insect.x1 initialized
    static Beetle.x2 initialized
    Beetle constructor
    i = 9, j = 0
    Beetle.k initialized
    k = 47
    j = 39
    */
    

    当你运行java Beetle时,首先会尝试访问静态方法Beetle.main(),所以加载器会去Beetle.class文件中找到Beetle类的编译代码。在加载它的代码时,加载器注意到有一个基类,然后它就会去加载基类。无论是否创建该基类的对象,都会发生这种情况。(可以尝试注释掉对象创建来验证一下。)

    如果基类又有自己的基类,那么第二个基类也将被加载,以此类推。接下来,会执行根基类(本例中为Insect)中的静态初始化,然后是下一个子类,以此类推。这很重要,因为子类的静态初始化可能依赖于基类成员的正确初始化。

    现在所有必要的类都已加载,因此可以创建对象了。首先,该对象中的所有基本类型都被设为其默认值,并且对象引用被设为null——这通过将对象中的内存设置为二进制零来一步实现。然后调用基类构造器。这里的调用是自动的,但也可以通过super关键字来指定基类构造器的调用(需要作为Beetle构造器中的第一个操作)。基类构造器以与子类构造器相同的顺序经历相同的过程。基类构造器完成后,子类的实例变量按文本顺序初始化。最后,执行子类构造器的其余部分。

第九章 多态

  1. 将一个方法调用和一个方法体关联起来的动作称为绑定。在程序运行之前执行绑定(如果存在编译器和链接器的话,由它们来实现),称为前期绑定。你之前可能没有听说过这个术语,因为在面向过程语言中默认就是前期绑定的。例如,在C语言中只有一种方法调用,那就是前期绑定。

    上述程序之所以令人困惑,主要是由于前期绑定。这是因为当编译器只有一个Instrument引用时,它无法知道哪个才是要调用的正确方法。

    解决这个问题的方案称为后期绑定,这意味着绑定发生在运行时,并基于对象的类型。后期绑定也称为动态绑定运行时绑定。当一种语言实现后期绑定时,必须有某种机制在运行时来确定对象的类型,并调用恰当的方法。也就是说,编译器仍然不知道对象的类型,但方法调用机制能找到并调用正确的方法体。后期绑定机制因语言而异,但可以想象,必须要将某种类型信息放在对象里。

    这里前期调用需要理解的最重要的一句话就是方法调用和方法体是紧密一对一的链接的,下面举一个例子

    f()->方法diaoyonf(){//方法体
    int i =0;
    }
    

    然后前期绑定所无法做道的就是,无法通过一个方法名去定位有多个方法体的目标,学过c语言的就会感觉一个方法体可以有多个目标本身就很离谱

    下面我串一下c语言的函数,学过c语言的应该都知道函数指针这种东西,它的解引用其实就是方法体的位置,那么其实可以很容易的推断出来,一个指针变量所能指向的位置是唯一确定的,但问题来了,如果指针指向的方法存在多种形式(也就是多种同名方法),那么每个形式的方法体占的地址肯定也是不同的,而这就是蛋疼的地方了,这种特性导致了它能实现重载的可能性直接变0.

  2. Java中的所有方法绑定都是后期绑定,除非方法是staticfinal的(private方法隐式为final)。

    static的方法其实还是有c语言的遗风的,然后final方法我不是很能理解为什么可以不是后期绑定机制

    final方法可能原因是不能进行重写吧,重载是编译时的多态,重写才是运行时多态的表现

  3. // polymorphism/shape/RandomShapes.java
    // 一个随机产生形状的工厂
    package polymorphism.shape;
    import java.util.*;public class RandomShapes {private Random rand = new Random(47);public Shape get() {switch(rand.nextInt(3)) {default:case 0: return new Circle();case 1: return new Square();case 2: return new Triangle();}}public Shape[] array(int sz) {Shape[] shapes = new Shape[sz];// 用各种形状填满数组for(int i = 0; i < shapes.length; i++)shapes[i] = get();return shapes;}
    }
    
    // polymorphism/Shapes.java
    // Polymorphism in Java
    import polymorphism.shape.*;public class Shapes {public static void main(String[] args) {RandomShapes gen = new RandomShapes();// 执行多态方法调用for(Shape shape : gen.array(9))shape.draw();}
    }
    /* 输出:
    Triangle.draw()
    Triangle.draw()
    Square.draw()
    Triangle.draw()
    Square.draw()
    Triangle.draw()
    Square.draw()
    Triangle.draw()
    Circle.draw()
    */
    
  4. // polymorphism/FieldAccess.java
    // 字段的直接访问是在编译时确定的class Super {public int field = 0;public int getField() { return field; }
    }class Sub extends Super {public int field = 1;@Override 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());}
    }
    /* 输出:
    sup.field = 0, sup.getField() = 1
    sub.field = 1, sub.getField() = 1, sub.getSuperField()
    = 0
    */
    

    Sub对象向上转型为Super引用时,任何字段访问都会被编译器解析,因此不是多态的。在此示例中,Super.fieldSub.field被分配了不同的存储空间。因此,Sub实际上包含两个被称为field的字段:它自己的字段和它从Super继承的字段。然而,当你在Sub中引用field时,Super版本并不是默认的那个。要获得Superfield,必须明确地说super.field

    如果要调用父类的字段,要显示用super调用,当然,如果时private字段,你用super也没用

  5. 这里层次结构中的每个类都包含类型为CharacteristicDescription的成员对象,它们也必须被销毁。处置顺序应该与初始化顺序相反,以防子对象依赖于其他对象。对于字段,这意味着与声明顺序相反(因为字段是按声明顺序初始化的)。对于基类(遵循C++中析构函数的形式),首先执行子类清理,然后是基类清理。这是因为子类在清理时可能会调用基类中的一些方法,这些方法可能需要基类组件处于存活状态,因此不能过早地销毁它们。输出显示了Frog对象的所有部分都是按照与创建顺序相反的顺序进行销毁的。

    有点能够理解为什么要以相反的顺序清理了,就是为了防止某些对象是和其他对象有关联的,比如基类子类,删了基类子类就残疾了

  6. // polymorphism/PolyConstructors.java
    // 构造器和多态
    // 不会生成你所期望的结果class Glyph {void draw() { System.out.println("Glyph.draw()"); }Glyph() {System.out.println("Glyph() before draw()");draw();System.out.println("Glyph() after draw()");}
    }class RoundGlyph extends Glyph {private int radius = 1;RoundGlyph(int r) {radius = r;System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);}@Override void draw() {System.out.println("RoundGlyph.draw(), radius = " + radius);}
    }public class PolyConstructors {public static void main(String[] args) {new RoundGlyph(5);}
    }
    /* 输出:
    Glyph() before draw()
    RoundGlyph.draw(), radius = 0
    Glyph() after draw()
    RoundGlyph.RoundGlyph(), radius = 5
    */
    

    这个其实是一件特别奇妙的事情,我用c的角度来审视这个问题就是,在创建一个对象时,该对象的全部信息应该存储在堆中的一块区域当中,那么,本例中的两个对象本身其实他们的draw()方法其实就是存储在他们对应的区域中,但是,java的后期绑定导致了一个问题,在java中,构造器和方法其实是由个隐形的参数this的,用来确定对象使用的,那么这里构造子对象时,其实是在子对象的空间中进行调用,也就是说,基类构造器构造的过程中是会被传入一个this的隐参来进行定位,也就是这个父对象其实是在这个子对象的空间当中的,这也就导致了在调用方法时,其实是隐式调用了this.fun(),而不是super.fun().

  7. 实际的初始化过程如下所示。

    1. 在发生任何其他事情之前,为对象分配的存储空间会先被初始化为二进制零。
    2. 如前面所述的那样调用基类的构造器。此时被重写的draw()方法会被调用(是的,这发生在RoundGlyph构造器被调用之前),由于第1步的缘故,此时会发现radius值为零。
    3. 按声明的顺序来初始化成员。
    4. 子类构造器的主体代码被执行。

    这里我着重理解的是初始化为二进制0,因为我学过一点汇编,也学过一点逆向,在我的理解中,c语言是直接划定了一边区域作为了某个变量的值,里面不一定是什么,而java这是划定区域以后,把里面的值先设置为0.

  8. 编写构造器时有一个很好的准则:“用尽可能少的操作使对象进入正常状态,如果可以避免的话,请不要调用此类中的任何其他方法。”只有基类中的final方法可以在构造器中安全调用(这也适用于private方法,它们默认就是final的)。这些方法不能被重写,因此不会产生这种令人惊讶的问题。

  9. Java 5添加了协变返回类型(covariant return type),这表示子类中重写方法的返回值可以是基类方法返回值的子类型

    // polymorphism/CovariantReturn.javaclass Grain {@Override public String toString() {return "Grain";
    }
    }class Wheat extends Grain {@Override public String toString() {return "Wheat";
    }
    }class Mill {Grain process() { return new Grain(); }
    }class WheatMill extends Mill {@Override Wheat process() {return new Wheat();
    }
    }public class CovariantReturn {public static void main(String[] args) {Mill m = new Mill();Grain g = m.process();System.out.println(g);m = new WheatMill();g = m.process();System.out.println(g);
    }
    }
    /* 输出:
    Grain
    Wheat
    */
    

    Java 5与其之前版本的主要区别在于,其之前版本强制要求process()的重写版本返回Grain,而不能是Wheat,即使Wheat继承自Grain,因而也是一个合法的返回类型。协变返回类型允许更具体的Wheat返回类型。

    这个其实很有意思

  10. // polymorphism/Transmogrify.java
    // 通过组合动态的改变对象的行为(状态设计模式)class Actor {public void act() {}
    }class HappyActor extends Actor {@Override public void act() {System.out.println("HappyActor");
    }
    }class SadActor extends Actor {@Override public void act() {System.out.println("SadActor");
    }
    }class Stage {private Actor actor = new HappyActor();
    public void change() { actor = new SadActor(); }
    public void performPlay() { actor.act(); }
    }public class Transmogrify {public static void main(String[] args) {Stage stage = new Stage();stage.performPlay();stage.change();stage.performPlay();
    }
    }
    /* 输出:
    HappyActor
    SadActor
    */
    

    Stage对象包含了一个Actor的引用,它被初始化为一个HappyActor对象。这意味着performPlay()方法会产生特定的行为。因为引用可以在运行时重新绑定到不同的对象,所以可以将actor中的引用替换为对SadActor对象的引用,这样performPlay()产生的行为也随之改变。因此你就在运行时获得了动态灵活性(这也称为状态模式)。相反,你不能在运行时决定以不同的方式来继承,这必须在编译期间就完全确定下来。

    “使用继承来表达行为上的差异,使用字段来表达状态上的变化”在前面的例子中,两者都用到了:通过继承得到了两个不同的类来表达act()方法的差异,而Stage使用组合来允许其状态发生改变。在这里,状态的改变恰好导致了行为的变化。

    不得不说,真是精妙的思想,继承只是为了接口的多态性,如果不能使接口产生多态性,应该使用组合的方式来实现状态的多样性.

on java 8 学习笔记 2022.2.17-2022.2.18相关推荐

  1. 可能是最全面的 Java G1学习笔记

    转载自 可能是最全面的 Java G1学习笔记 引子 最近遇到很多朋友过来咨询G1调优的问题,我自己去年有专门学过一次G1,但是当时只是看了个皮毛,因此自己也有不少问题.总体来讲,对于G1我有几个疑惑 ...

  2. 【Java基础学习笔记】- Day11 - 第四章 引用类型用法总结

    Java基础学习笔记 - Day11 - 第四章 引用类型用法总结 Java基础学习笔记 - Day11 - 第四章 引用类型用法总结 4.1 class作为成员变量 4.2 interface作为成 ...

  3. 尚学堂JAVA基础学习笔记_2/2

    尚学堂JAVA基础学习笔记_2/2 文章目录 尚学堂JAVA基础学习笔记_2/2 写在前面 第10章 IO技术 1. IO入门 2. IO的API 3. 装饰流 4. IO实战 5. CommonsI ...

  4. 尚学堂JAVA高级学习笔记_1/2

    尚学堂JAVA高级学习笔记 文章目录 尚学堂JAVA高级学习笔记 写在前面 第1章 手写webserver 1. 灵魂反射 2. 高效解析xml 3. 解析webxml 4. 反射webxml 5. ...

  5. Android(java)学习笔记176: 远程服务的应用场景(移动支付案例)

    一. 移动支付:       用户需要在移动终端提交账号.密码以及金额等数据 到 远端服务器.然后远端服务器匹配这些信息,进行逻辑判断,进而完成交易,返回交易成功或失败的信息给移动终端.用户提交账号. ...

  6. Java NIO 学习笔记(三)----Selector

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  7. 深入浅出 Java CMS 学习笔记

    转载自  深入浅出 Java CMS 学习笔记 引子 带着问题去学习一个东西,才会有目标感,我先把一直以来自己对CMS的一些疑惑罗列了下,希望这篇学习笔记能解决掉这些疑惑,希望也能对你有所帮助. 1. ...

  8. Java NIO学习笔记之图解ByteBuffer

    转载自 Java NIO学习笔记之图解ByteBuffer ByteBuffer前前后后看过好几次了,实际使用也用了一些,总觉得条理不够清晰. <程序员的思维修炼>一本书讲过,主动学习,要 ...

  9. 转载:mongoDB java驱动学习笔记

    http://www.blogjava.net/watchzerg/archive/2012/09/22/388346.html mongoDB java驱动学习笔记 指定新mongo实例: Mong ...

  10. 2019年Java Web学习笔记目录

    Java Web学习笔记目录 1.Java Web学习笔记01:动态网站初体验 2.Java Web学习笔记02:在Intellij里创建Web项目 3.Java Web学习笔记03:JSP元素 4. ...

最新文章

  1. “双一流”高校毕业生就业图鉴:企业都偏爱哪些高校?
  2. oschina多媒体工具
  3. mysql 批量插入数据过多的解决方法
  4. 【深度学习】我用 PyTorch 复现了 LeNet-5 神经网络(CIFAR10 数据集篇)!
  5. java 多态 降低耦合_java多态
  6. Spring Boot EasyUI datagrid
  7. sharepoint小 tip
  8. 权限系统(第一次测试)
  9. Linux Unix shell 编程指南学习笔记(第二部分)
  10. python vim 自动换行_Vim学习笔记整理
  11. Linux 内核的网络协议栈
  12. 大厂员工涌入外包:中年失业,外包已是我最好的选择
  13. GNURadio(一)
  14. win7登录密码破解
  15. 在线成语接龙答题有奖微信小程序源码V1.5.1
  16. 第7章 面向对象技术
  17. dnf命令 (常用总结)
  18. 薇娅,李佳琦都点赞的淘宝双11直播系统,是如何打造的?
  19. React模拟后台项目(八)user页面文件配置
  20. 专利申请需要注意什么

热门文章

  1. oracle的监听器是什么,Oracle监听器,让你监听想要的东东
  2. 关于服务器的入门知识整理
  3. Android 关于“NetworkOnMainThreadException”
  4. 8张图告诉你如何运营微信公众号
  5. Linq to SQL学习
  6. 设计高效sql一般经验谈
  7. 解决vc 6在vista下的一些兼容问题
  8. switch 根据键盘录入成绩 显示分数及判断等级(第二次)
  9. sql注入 1-1_基于报错的注入
  10. ftp服务器如何复制文件路径,ftp服务器上复制文件路径