星空网 > 软件开发 > Java

[Thinking in Java]第7章

【复用代码是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章

关键词:JAVA

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。
相关文章
我的浏览记录
最新相关资讯
海外公司注册 | 跨境电商服务平台 | 深圳旅行社 | 东南亚物流