Java编程思想第四版阅读笔记(1章-14章)

构造器初始化

初始化顺序

先根据成员变量定义顺序初始化成员变量,再调用构造函数。
package constructorInitialization;

class Window{
    Window(int marker){
        System.out.println("Window(" + marker +")");
    }
}
class House{
    Window w1 = new Window(1);
    House(){
        System.out.println("House()");
        w2 = new Window(22);    // reinitialize
    }
    Window w2 = new Window(2);
}

public class OrderOfInitialization {
    public static void main(String[] args) {
        new House();
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第1张图片

静态数据的初始化

先初始化静态成员变量(只在第一次使用类的时候搞一次),再初始化非静态成员变量,最后执行构造函数。
package constructorInitialization;

class Bowl{
    Bowl(int marker){
        System.out.println("Bowl(" + marker + ")");
    }
}
class Table{
    static Bowl bowl1 = new Bowl(1);
    Table(){
        System.out.println("Table()");
    }
    static Bowl bowl2 = new Bowl(2);
}
class Cupboard{
    Bowl bowl3 = new Bowl(3);
    static Bowl bowl4 = new Bowl(4);
    Cupboard(){
        System.out.println("Cupboard()");
    }
    static Bowl bowl5 = new Bowl(5);
}

public class StaticInitialization {
	// 要调用 main 方法,就得先加载 StaticInitialization 类,并先完成初始化。
    public static void main(String[] args) {
        System.out.println("Creating new Cupboard() in main...");
        new Cupboard();
        System.out.println("Creating new Cupboard() in main...");
        new Cupboard();
    }
    static Table table = new Table();
    static Cupboard cupboard = new Cupboard();
}

Java编程思想第四版阅读笔记(1章-14章)_第2张图片

静态初始化块

package constructorInitialization;

class Cup{
    Cup(int marker){
        System.out.println("Cup(" + marker + ")");
    }
}
class Cups{

    static {
        cup = new Cup(1);   // 2.调用构造函数初始化
    }
    static Cup cup;    // 1. 初始化为 null
    Cups(){
        System.out.println("Cups()");
    }
}

public class ExplicitInitialization {
    public static void main(String[] args) {
        new Cups();
        new Cups();     // 静态代码块只会搞一次,静态成员也只在第一次搞的时候初始化一次
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第3张图片

普通初始化块

package constructorInitialization;

class Mug{
    Mug(int marker){
        System.out.println("Mug(" + marker + ")");
    }
}

public class Mugs{
    {
        mug = new Mug(1);
    }
    Mug mug;

    Mugs(){
        System.out.println("Mugs()");
    }

    public static void main(String[] args) {
        new Mugs();
        new Mugs();
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第4张图片

可变形参列表

下面似乎方法重载的很好!如果调用 f() 编译器就傻眼了!
package constructorInitialization;

public class OverloadVariableArguments {
    static void f(Character... args){
        System.out.println("f(Character... args)");
        for(Character character : args){
            System.out.print(character + " ");
        }
        System.out.println();
    }
    static void f(Integer... args){
        System.out.println("f(Integer... args)");
        for(Integer integer : args){
            System.out.print(integer + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        f(1, 2, 3);
        f('a', 'b', 'c');
    }
}

下面给其中一个方法添加非可变参数似乎可以重载好!然报错如下图!

在这里插入图片描述

package constructorInitialization;

public class OverloadVariableArguments {
    static void f(Character... args){
        System.out.println("f(Character... args)");
        for(Character character : args){
            System.out.print(character + " ");
        }
        System.out.println();
    }
    static void f(int i, Character... args){
        System.out.println("f(int i, Character... args)");
        System.out.println("i="+i);
        for(Character integer : args){
            System.out.print(integer + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        f(1, 'a');
        f('a', 'b', 'c');
    }
}

给每个方法都添加非可变参数,可完美重载!!
package constructorInitialization;

public class OverloadVariableArguments {
    static void f(float f, Character... args){
        System.out.println("f(float f, Character... args)");
        System.out.println("f="+f);
        for(Character character : args){
            System.out.print(character + " ");
        }
        System.out.println();
    }
    static void f(int i, Character... args){
        System.out.println("f(int i, Character... args)");
        System.out.println("i="+i);
        for(Character integer : args){
            System.out.print(integer + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        f(1, 'a', 'b', 'c');
        f(1.0f, 'a', 'b', 'c');
        f('a', 'b', 'c');

    }
}

Java编程思想第四版阅读笔记(1章-14章)_第5张图片

代理

组合和继承之间的中庸之道!可以只暴露被组合/继承对象的部分方法。

package constructorInitialization;

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(int velocity){}
}

public class SpaceshipDelegation {
    private String name;
    private SpaceshipControls spaceshipControls = new SpaceshipControls();
    private SpaceshipDelegation(String name){
        this.name = name;
    }
    // 可选择性地暴露 SpaceshipControls 的部分方法
    public void up(int velocity){
        spaceshipControls.up(velocity);
    }
    public void forward(int velocity){
        spaceshipControls.forward(velocity);
    }

    public static void main(String[] args) {
        SpaceshipDelegation spaceshipDelegation = new SpaceshipDelegation("xxx");
        spaceshipDelegation.forward(10);
    }
}

继承与初始化

每个类的编译代码都存在于它自己的独立文件中,该文件只在需要使用程序代码时才会被加载。一般来说,类的代码在初次使用时才加载。这通常是指加载发生于创建类的第一个对象之时,但是当访问 static 域或方法时也会发生加载。

package constructorInitialization;

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"); // step1, x1=47
    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+", j="+j);
    }
    private static int x2 = printInit("static Beetle.x2 initialized"); // step2, x2=47
    public static void main(String[] args){
        System.out.println("Beetle constructor");
        Beetle b = new Beetle();
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第6张图片

多态

方法调用绑定和向上转型

将一个方法调用同一个方法主体关联起来成为绑定。若在程序执行前进行绑定(编译器和连接程序实现),叫做前期绑定(C 语言就是前期绑定)。运行时根据对象的类型进行绑定就是后期绑定(动态绑定)。

Java 中除了 static 方法和 final 方法(private 方法属于 final 方法)之外,其他所有方法都属于后期绑定。

Java编程思想第四版阅读笔记(1章-14章)_第7张图片

package constructorInitialization;

import java.util.Random;

class Shape{
    public void draw(){
        System.out.println("Shape.draw()");
    }
}
class Triangle extends Shape{
    public void draw(){
        System.out.println("Triangle.draw()");
    }
}
class Circle extends Shape{
    public void draw(){
        System.out.println("Circle.draw()");
    }
}

class RandomShapeGenerator{
    private Random random = new Random(47);
    public Shape next(){
        switch (random.nextInt(2)){
            default:
            case 0: return new Triangle();
            case 1: return new Circle();
        }
    }
}

public class Shapes {
    private static RandomShapeGenerator gen = new RandomShapeGenerator();
    public static void main(String[] args) {
        Shape[] s = new Shape[6];
        for(int i = 0; i < s.length; i++){
            s[i] = gen.next();
            s[i].draw();
        }
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第8张图片
注意1:试图覆盖私有方法

package constructorInitialization;

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

    public static void main(String[] args) {
        PrivateOverride po = new Derived();
        po.f();	// d
    }
}
class Derived extends PrivateOverride{
	// 该方法不会覆盖 PrivateOverride 的 f() (private)
    public void f(){
        System.out.println("public void f()");
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第9张图片
注意:域(成员变量)不具有多态性

package constructorInitialization;

class Super{
    public int filed = 0;
    public int getFiled(){
        return filed;
    }
}
class Sub extends Super{
    public int field = 1;
    public int getField(){
        return field;
    }
    public int getSuperField(){
        return super.filed;
    }
}
public class FieldAccess {
    public static void main(String[] args) {
        Super sup = new Sub();
        System.out.println("sup.field="+sup.filed+", sup.getField()="+sup.getFiled());

        Sub sub = new Sub();
        System.out.println("sub.field="+sub.field+", sub.getField()="+sub.getField()+", sub.getSuperFiled()="+sub.getSuperField());
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第10张图片
注意:静态方法不具有多态性

package constructorInitialization;

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());
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第11张图片

构造器和多态性

构造器的调用顺序

package constructorInitialization;

import sun.util.resources.cldr.lu.CalendarData_lu_CD;

class Meal{
    Meal(){
        System.out.println("Meal()");
    }
}
class Bread{
    Bread(){
        System.out.println("Bread()");
    }
}
class Cheese{
    Cheese(){
        System.out.println("Cheese()");
    }
}
class Lettuce{
    Lettuce(){
        System.out.println("Lettuce()");
    }
}
class Lunch extends Meal{
    Lunch(){
        System.out.println("Lunch()");
    }
}
class PortableLunch extends Lunch{
    PortableLunch(){
        System.out.println("PortableLuhch()");
    }
}

public class Sandwich extends PortableLunch{
    private Bread bread = new Bread();
    private Cheese cheese = new Cheese();
    private  Lettuce lettuce = new Lettuce();
    public Sandwich(){
        System.out.println("Sandwich()");
    }
    public static void main(String[] args) {
        new Sandwich();
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第12张图片
继承与清理
dispose 的顺序与初始化顺序相反,类似于 C++ 的析构函数。子类可能需要调用基类的方法来销毁自己,所以先销毁子类,再销毁父类。

package constructorInitialization;

class Characteristic{
    private String s;
    Characteristic(String s){
        this.s = s;
        System.out.println("Creating Characteristic " + s);
    }
    protected void dispose(){
        System.out.println("disposing Characteristic " + s);
    }
}
class Description{
    private String s;
    Description(String s){
        this.s = s;
        System.out.println("Creating Description " + s);
    }
    protected void dispose(){
        System.out.println("disposing Description " + s);
    }
}

class LivingCreature{
    private Characteristic p = new Characteristic("is live");
    private Description t = new Description("Basic Living Creature");
    LivingCreature(){
        System.out.println("LivingCreature()");
    }
    protected void dispose(){
        System.out.println("LivingCreature dispose");
        p.dispose();
        t.dispose();
    }
}
class Animal extends LivingCreature{
    private Characteristic p = new Characteristic("has heart");
    private Description t = new Description("Animal not Plant");
    Animal(){
        System.out.println("Animal()");
    }
    protected void dispose(){
        System.out.println("Animal dispose");
        p.dispose();
        t.dispose();
        super.dispose();
    }
}
class Amphibian extends Animal{
    private Characteristic p = new Characteristic("can live in water");
    private Description t = new Description("Both water and land");
    Amphibian(){
        System.out.println("Amphibian()");
    }
    protected void dispose(){
        System.out.println("Amphibian dispose");
        p.dispose();
        t.dispose();
        super.dispose();
    }
}

public class Frog extends Amphibian{
    private Characteristic p = new Characteristic("croak");
    private Description t = new Description("Eats Bugs");
    public Frog(){
        System.out.println("Frog()");
    }
    protected void dispose(){
        System.out.println("Frog dispose");
        p.dispose();
        t.dispose();
        super.dispose();
    }

    public static void main(String[] args) {
        Frog frog = new Frog();
        System.out.println("Bye");
        frog.dispose();
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第13张图片
如果多对象共享某个对象,就不能用上述方式销毁了!应该引用计数。

package constructorInitialization;

class Shared{
    private int refcount = 0;
    private static long counter = 0;
    private final  long id = counter++;
    public Shared(){
        System.out.println("Creating " + this);
    }

    public void addRef(){
        refcount++;
    }

    protected void dispose(){
        if(--refcount == 0){
            System.out.println("disposing " + this);
        }
    }

    @Override
    public String toString() {
        return "Shared " + id;
    }
}
class Composing{
    private Shared shared;
    private static long counter = 0;
    private final long id = counter++;
    public Composing(Shared shared){
        System.out.println("Creating " + this);
        this.shared = shared;
        this.shared.addRef();
    }
    protected void dispose(){
        System.out.println("disposing " + this);
        shared.dispose();
    }

    @Override
    public String toString() {
        return "Composing " + id;
    }
}

public class ReferenceCounting {
    public static void main(String[] args) {
        Shared shared = new Shared();
        Composing[] composings = {new Composing(shared), new Composing(shared), new Composing(shared)};
        for(Composing composing : composings){
            composing.dispose();
        }
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第14张图片
构造器内部调用动态绑定方法
如果调用构造器内部的一个动态绑定方法,就要用到那个方法的被覆盖后的定义。然而,这个调用的效果可能相当难预料,因为被覆盖的方法在对象被完全构造之前就会被调用,可能造成一些难于发现的隐藏错误。
编写构造器有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法”。在构造器内唯一能够安全调用的那些方法是基类中的final/private 方法,这些方法不能被覆盖,因此也就不会出现下面代码令人惊讶的结果!

package constructorInitialization;

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);
    }
    void draw(){
        System.out.println("RoundGlyph.draw(), radius = " + radius);
    }
}

public class PolyConstructors {
    public static void main(String[] args) {
        new RoundGlyph(6);
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第15张图片
向下转型

package constructorInitialization;

class Useful{
    public void f(){}
}
class MoreUseful extends Useful{
    public void f(){}
    public void g(){}
}

public class RTTI{
    public static void main(String[] args) {
        Useful[] x = {new Useful(), new MoreUseful()};
        x[0].f();
        x[1].f();

        // x[1].g();   // 编译错误!这样只能调用子类覆盖的方法

        ((MoreUseful)x[1]).g(); // 向下转型!
        // ((MoreUseful)x[0]).g(); // RTTI
    }
}

类型信息

Java 在运行时识别类的信息的方式主要有 RTTI(Run-Time Type Identification)和反射,RTTI 假定在编译时已经知道了所有的类型,反射允许我们在运行时发现和使用类的信息。

RTTI

Java编程思想第四版阅读笔记(1章-14章)_第16张图片

package type;

import java.util.Arrays;
import java.util.List;

abstract class Shape {
    void draw() {
        System.out.println(this + ".draw()");
    }
    public abstract String toString();
}
class Circle extends Shape {
    public String toString(){
        return "Circle";
    }
}
class Square extends Shape{
    public String toString(){
        return "Square";
    }
}
class Triangle extends Shape{
    public String toString(){
        return "Triangle";
    }
}
public class Shapes {
    public static void main(String[] args) {
        List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle());
        for(Shape shape : shapeList){
            shape.draw();
        }
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第17张图片

Class 对象

要理解 RTTI 在 Java 中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由 Class 对象完成的,它包含了与类有关的信息。事实上,Class 对象就是用来创建类的所有“常规”对象的。Java 使用 Class 对象来执行其 RTTI,即使你正在执行的是类似转型这样的操作。

每个类都有一个 Class 对象,每编译一个新类就会产生一个 Class 对象(更恰当地说,是被保存在一个同名的 .class 文件中)。所有的类都是在第一次使用时,类加载器负责动态加载(非加载整个类,类各部分分别用到时才加载)到 JVM 中。当程序创建第一个对类的静态成员的引用时,就会加载这个类(这表明构造器是隐式的 static 方法)。

一旦某个类的 Class 对象载入内存,它就被用来创建这个类的所有对象。

package type;

class Candy {
    static {
        System.out.println("Loading Candy");
    }
}

class Gum {
    static {
        System.out.println("Loading Gum");  // Gum.class 不会执行这句
    }
}

public class SweetShop {
    public static void main(String[] args) {
        new Candy();
        try{
            Class clazz = Class.forName("type.Gum");
            System.out.println(clazz.getSimpleName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第18张图片

为了使用类而做的准备工作包含三个步骤:

  1. 加载。类加载器查找字节码,并利用字节码创建一个 Class 对象。
  2. 链接。验证字节码。为静态域分配内存,并且如果必需的话,将解析这个类创建的对其它类的所有引用。
  3. 初始化。如果该类有父类,则对父类初始化,执行静态初始化块。
package type;

import java.util.Random;

class Initable {
    static final int staticFinal = 47; // 编译期常量
    static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
    static {
        System.out.println("Initializing Initable");
    }
}
class Initable2 {
    static int staticNonFinal = 147;
    static {
        System.out.println("Initializing Initable2");
    }
}
class Initable3 {
    static int staticNonFinal = 74;
    static {
        System.out.println("Initializing Initable3");
    }
}
public class ClassInitialization {
    public static Random rand = new Random(47);

    public static void main(String[] args) throws Exception{
        Class initable = Initable.class;
        System.out.println(Initable.staticFinal);
        System.out.println(Initable.staticFinal2);
        System.out.println(Initable2.staticNonFinal);
        Class initable3 = Class.forName("type.Initable3");
        System.out.println(Initable3.staticNonFinal);
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第19张图片
instanceof 与 Class 的不同
instanceof 和 isInstance() 的结果一致。equals() 和 == 的结果一致。instanceof 保持了类型的概念,它指的是“你是这个类吗?或者你是这个类的派生类吗?”,而如果用== 比较实际的 Class 对象,就没有考虑继承——它或者是这个确切的类型,或者不是。

package type;

class Base {}
class Derived extends Base {}

public class FamilyVSExactType {
    static void test (Object x) {
        System.out.println("Testing x of type " + x.getClass());
        System.out.println("x instanceof Base " + (x instanceof Base));
        System.out.println("x instanceof Derived " + (x instanceof Derived));
        System.out.println("Base.isInstance(x) " + Base.class.isInstance(x));
        System.out.println("Derived.isInstance(x) " + Derived.class.isInstance(x));
        System.out.println("x.getClass() == Base.class " + (x.getClass() == Base.class));
        System.out.println("x.getClass() == Derived.class " + (x.getClass() == Derived.class));
        System.out.println("x.getClass().equals(Base.class) " + (x.getClass().equals(Base.class)));
        System.out.println("x.getClass().equals(Derived.class) " + (x.getClass().equals(Derived.class)));
    }

    public static void main(String[] args) {
        test(new Base());
        System.out.println();
        test(new Derived());
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第20张图片

反射:运行时的类信息

RTTI 和反射真正的区别在于,对RTTI来说,编译器在编译时打开和检查 .class 文件(换句话说,我们可以用”普通“方式调用对象的所有方法);而对于反射来说, .class 文件在编译时是不可获取的,所以是在运行时打开和检查 .class 文件。

某对象的确切类型在编译时已知,才可通过 RTTI 识别对应的类型。但假设获取了一个指向某个并不在你的程序空间中的对象的引用,事实上,在编译时你的程序根本没法获知这个对象所属的类。例如,假设你从磁盘文件或网络连接中获取一串字节,并且你被告知这些字节代表了一个类,既然这个类在编译器为你的程序生成代码之后很久才会出现,那么怎样才能使用这样的类呢?反射。

书中举了个代码例子,大概就是在运行时为 main 方法传入 args 参数,显然,在编译的时候 args 参数是不知的,编译器无法知道 args 的类型,只有在运行时传入了 args 参数后,才可查看 args 参数对应的是什么类,这里就要反射!先获取 Class 对象,从而获取这个类的构造器、方法、属性等信息。

动态代理

下面展示一个代理的简单例子,SimpleProxy 就是 RealObject 的代理,其实只是对 RealObject 的简单封装!

package type;

interface Interface {
    void doSomething();
    void somethingElse(String arg);
}
class RealObject implements Interface {
    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println("somethingElse" + " arg");
    }
}
class SimpleProxy implements Interface {

    private Interface proxied;

    public SimpleProxy(Interface proxied) {
        this.proxied = proxied;
    }

    @Override
    public void doSomething() {
        System.out.println("SimpleProxy doSomething");
        proxied.doSomething();
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println("SimpleProxy somethingElse " + arg);
        proxied.somethingElse(arg);
    }
}

public class SimpleProxyDemo {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }
    public static void main(String[] args) {
        consumer(new RealObject());
        System.out.println();
        consumer(new SimpleProxy(new RealObject()));
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第21张图片
下面展示 Java 动态代理

package type;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Interface {
    void doSomething();
    void somethingElse(String arg);
}
class RealObject implements Interface {
    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println("somethingElse" + " arg");
    }
}
class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;
    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("*** proxy: " + proxy.getClass() + ", method: " + method.getName() + ", args " + args);
        if(args != null) {
            for(Object arg : args) {
                System.out.println(arg);
            }
        }
        return method.invoke(proxied, args);
    }
}

public class SimpleDynamicProxy {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }

    public static void main(String[] args) {
        RealObject real = new RealObject();
        consumer(real);
        System.out.println();

        Interface proxy = (Interface) Proxy.newProxyInstance(
                Interface.class.getClassLoader(),
                new Class[]{Interface.class},
                new DynamicProxyHandler(real)
        );
        consumer(proxy);
    }
}

Java编程思想第四版阅读笔记(1章-14章)_第22张图片

你可能感兴趣的:(Java基础知识记)