码迷,mamicode.com
首页 > 编程语言 > 详细

[Thinking in Java]第7章-复用类

时间:2015-04-26 13:47:50      阅读:153      评论:0      收藏:0      [点我收藏+]

标签:

【复用代码是Java众多引人注目的功能之一。但要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事。】

 

7.1 组合语法

组合就是把对象放在新类中,做新类的成员,比如

class A {
}

class B {
    private String value1, value2, value3;
    private A a = new A();
    private int integer;
    private double d;
}

如上所示,类A对象作为类B的一个成为,此外,类B还具有类String3个对象,又有基本数据类型integer和d。

如果想要初始化类成为,可以在代码中的4种位置进行

1.在定义对象的地方;

2.在类的构造器中;

3.使用实例初始化,也就是初始化块

4.正要使用这个对象之前,这种方式也称作惰性初始化,可以减少额外的负担。

class A{
}

class B {
    // 1.在定义对象的地方
    private String name = new String("小明");
    private String sex;//未初始化
    private int id;// 未初始化
    private A a;// 未初始化
     
    {//4.使用实例初始化
        id = 13;
    }
 
    public B() {//2.在构造器中初始化
        a = new A();
    }

    public void f() {
        //4.正要使用这个对象时初始化
        if (sex == null)
            sex = "boy";
        System.out.println("I am a " + sex);
    }
}

 

7.2 继承语法

当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则总是在隐式地从Java的标准根类Object进行继承。

class A {
    private int a;
    private double b;
    private String name;
    
    public void f1() {}
    public void f2() {}
}

class B extends A {
    private int c;
    
    public void f3() {}
}

如上代码所示,使用关键字extends,类B继承了类A,获得了父类所有的属性和方法,包括父类的private属性

class Person {    
    public String toString() {
        return "I am a person";
    }
}

class Father extends Person {
    private String name = "小明";
    
    public Father(String name) {
        this.name = name;
    }
    
    public String toString() {
        return "I am a father";
    }
    
    public String getName() {
        return name;
    }
}

class Son extends Father {
    public Son(String name) {
        super(name);
    }
    
    public String toString() {
        return "I am a son";
    }
}

public class PersonTest {
    public static void main(String[] args) {
        Father father = new Father("小天");
        Son son = new Son("小强");
        
        father.say("My name is" + father.getName());
        son.say("I am " + son.getName());
    }
}

如上代码所示,类Father定义了private修饰的属性name,被类Son继承并在构造器中初始化了;同时类Father定义了访问器getName(),因为是用public修饰的,所以类Son的对象可以直接使用;我们还可以注意到,类Person定义了公共方法toString(),实际上根类Object也定义了toString(),但类Person的toString()是重载了,因为方法签名不一样,类Father也定义了相同方法签名的toString(),这是覆盖类Person的toString(),同理类Son的toString()覆盖了类Father的toString()。

注意到类Son的构造器这一句 super(name); 如果注释掉这一句,或者改为super();编译器只能报错,这是因为继承机制要求要初始化父类:

实际上,继承并不只是复制父类的接口,当创建了一个子类的对象时,该对象包含了一个父类的子对象,这个子对象与你用父类直接创建对象是一样,只不过后者来自于外部,而父类的子对象被包装在子类对象的内部。对父类子对象的正确初始化也是至关重要的,而且也仅仅有一种方法,那就是早构造器中调用父类的构造器来执行初始化

class Art {
    Art() {System.out.print("Art 构造器");}
}

class Drawing extends Art {
    Drawing() {System.out.print("Drawing 构造器");}
}

class Cartoon extends Drawing {
    Cartoon() {System.out.print("Cartoon 构造器");}
}

如上代码所示,当初始化Cartoon时,发现有父类Drawing,于是编译器会默认在构造器的第一句添加一句super();再先初始化父类Drawing,又发现有父类Art,于是会默认在构造器的第一句添加一句super();再初始化父类Art,对于根类Object就不说了,就这样输出结果为

Art 构造器Drawing 构造器Cartoon 构造器

那构造器的第一行的super(初始化父类的super本来就必须是第一行)能否带有参数呢?当然可以,像上面的例子,类Son的构造器中super(name)就是带有参数的,但若去掉参数name,或者干脆不写,编译器就抱怨无法初始化了,因为父类Father不存在无参构造器。如果遇到下面这样的例子呢

class A {
    A() {
        System.out.println("A无参");
    }
}

class B extends A {
    B() {
        System.out.println("B无参");
    }
    
    B(int a) {
        this();
        System.out.println("B有参");
    }
}

class C extends B {
    C() {
        super(1);
        System.out.println("C无参");
    }
}

public class Inheritance {
    public static void main(String[] args) {
        new C();
    }
}

因为这个构造方法链是从初始化对象开始,一直到根类Object结束。所以,当执行this();程序跳转到B()这个构造器,但是此时整个构造方法链条早已经完成了,自然不会super到父类A了,因此程序输出为

A无参
B无参
B有参
C无参

 

7.3 代理

类之间的关系有组合,继承,还有第三种关系成为代理,Java没有提供对它的直接支持,它是组合与继承的中庸之道。

因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们会在新类中暴露了此成员对象的所有方法(就像继承),代理解决了此难题

class A {
    f1(int i) {}
    f2(int i) {}
}

class B {
    A a = new A();
    
    f1(int i) {
        a.f1(i);
    }
    
    f2(int i) {
        a.f2(i);
    }
}

如上代码所示,在设计时,我们希望使用B的f1和f2方法,而不是直接使用A的f1和f2方法,打个比方,我们希望让一辆汽车向前开,而不是让汽车的控制引擎向前开

 

7.4 重载和覆盖

重载就是方法名相同,而参数列表不同,允许返回值类型不同,其中参数列表不同在于参数的个数和类型不同;

覆盖就是子类重写了父类的方法,子类的某个方法的方法名,返回值类型和参数列表必须和父类的一个方法一模一样

 1 class A {
 2     int method(int i) {
 3         return i;
 4     }
 5 }
 6 
 7 public class B extends A {
 8     public int method(int i) {
 9         return ++i;
10     }
11     
12     double method(double d) {
13         return d;
14     }
15     
16     double method(int a, double b) {
17         return a + b;
18     }
19     
20     public static void main(String[] args) {
21         B c = new B();
22         System.out.println(c.method(1));
23         System.out.println(c.method(5.6));
24     System.out.println(c.method(5, 5.5));
25 } 26 }

 

需要注意的是,如果第8行代码的修饰符改成private,编译器就抱怨修饰符的可见性比父类的method低,因此覆盖时必须保证修饰符的可见性不低于父类的方法;第12到14行是重载了父类的method,其中返回值类型可以不同,第16到18行也是重载了method,其中参数列表的个数不同

 

7.5 初始化以及类的加载

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

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

下面是Java编程思想的一个例子

 1 import static java.lang.System.*;
 2 
 3 class Insect {
 4     private int i = 9;
 5     protected int j;
 6     Insect() {
 7         out.println("i = " + i + ", j = " + j);
 8         j = 39;
 9     }
10     
11     private static int x1 = 
12         printInit("static Insect.x1 initialized");
13     
14     static int printInit(String s) {
15         out.println(s);
16         return 47;
17     }
18 }
19 
20 public class Beetle extends Insect {
21     private int k = printInit("Beetle.k initialized");
22     public Beetle() {
23         out.println("k = " + k);
24         out.println("j = " + j);
25     }
26     
27     private static int x2 = 
28         printInit("static Beetle.x2 initialized");
29     
30     public static void main(String[] args) {
31         out.println("Beetle constructor");
32         Beetle b = new Beetle();
33     }
34 }

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

如果该父类还有其自身的父类,那么第二个父类就会被加载,如此类推。接下来,根父类中的static初始化(在此例中为Insect)即会被执行,然后是下一个子类,以此类推。

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

正如这个例子,在试图访问Beetle.main()时,会先尝试加载Beetle类,但又发现Beetle的父类Insect,于是先加载Insect,Insect的第一个static语句是第11行,而第11行又调用第14行的printInit(String s),打印s并返回47,因此x1=47;然后再加载Beetle,第一个static语句是第27行,而第27行又调用父类的printInit(String s),打印s并返回47,因此x2=47,至此,所有的类已加载完毕;回到Beetle.main(),执行第31行的语句,然后执行第32行,创建Beetle对象,于是尝试执行第22行的构造器,但发现有父类Insect,因而先初始化父类,程序跳转到第6行,调用父类的构造器,当父类初始化完毕,再初始化子类,以此类推,才成功创建Beetle对象。

[Thinking in Java]第7章-复用类

标签:

原文地址:http://www.cnblogs.com/cyninma/p/4456945.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!