复用类

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

复用类的两种方法:

  1. 组合:新类中产生现有类的对象
  2. 继承:基于现有的类,创建一个新的类,并添加新的代码

1. 组合语法

 1 //组合语法:在新类中创建现有类的对象
 2 class WaterSource {
 3     private String s;
 4 
 5     public WaterSource() {
 6         System.out.println("WaterSource()");
 7         s = "Constructed";
 8     }
 9 
10     public String toString() {
11         return s;
12     }
13 }
14 public class SprinklerSystem {
15     private String value1, value2, value3, value4;
16     private WaterSource source = new WaterSource(); // 使用已存在的类
17     private int i;
18     private float f;
19     public String toString() {
20         return "value1 = " + value1 + " " +
21                "value2 = " + value2 + " " +
22                "value3 = " + value3 + " " +
23                "value1 = " + value1 + "\n" +
24                "i = " + i + " " + "f = " + f + " " +
25                "source = " + source;
26 
27     }
28 
29     public static void main(String[] args) {
30         SprinklerSystem sprinklers = new SprinklerSystem();
31         System.out.println(sprinklers);
32     }
33 }

上面两个类中定义了一个特殊的方法toString()。每一个非基本类型的对象都一个toString()方法,当编译器需要一个String,但是你却是个对象的时候会调用此方法。

编译器并不是简单地为每一个引用都创建默认对象,因为如果那样做的话,会在很多情况下增加不必要的负担。如果想用初始化这些引用,可以在代码中的下列位置进行(1、2、4在“初始化与清理”章节已经详细说明过):

  1. 在定义对象的地方。这意味着它们总是能后在构造器被调用前初始化。
  2. 在类的构造器中。
  3. 就在要使用对象之前,这种方式称为“惰性初始化”。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担。
  4. 使用实例初始化。
 1 class Soap {
 2     private String s;
 3     Soap() {
 4         System.out.println("Soap()");
 5         s = "Constructed";
 6     }
 7     public String toString() {
 8         return s;
 9     }
10 }
11 public class Bath {
12     private String s1 = "Happy", s2 = "Happy"; // 定义的时候初始化
13     private String s3, s4;
14     private Soap castille;
15     private int i;
16     private float toy;
17     public Bath() {
18         System.out.println("Inside Bath()");
19         s3 = "joy";  // 构造器中初始化
20         toy = 3.14f;
21         castille = new Soap();
22     }
23     { i = 47;} // 实例初始化
24     public String toString() {
25         if (s4 == null) { //惰性初始化
26             s4 = "Joy";
27         }
28         return "s1 = " + s1 + " \n" +
29                 "s2 = " + s2 + " \n" +
30                 "s3 = " + s3 + " \n" +
31                 "s4 = " + s4 + " \n" +
32                 "i = " + i + " \n" +
33                 "toy = " + toy + " \n" +
34                 "castille = " + castille + " \n";
35 
36     }
37 
38     public static void main(String[] args) {
39         Bath b = new Bath();
40         System.out.println(b);
41     }
42 }

2. 继承语法

当创建一个类时,总是在继承,除非明确的指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承。继承语法使用extends关键字: class SubclassName extends SuperclassName

 1 // 继承语法
 2 class Cleanser {
 3     private String s = "Cleanser";
 4     public void append(String a) { s += a; }
 5     public void dilute() { append(" dilute()"); }
 6     public void apply() { append(" apply()"); }
 7     public void scrub() { append(" scrub()"); }
 8     public String toString() { return s; }
 9 
10     public static void main(String[] args) {
11         Cleanser x = new Cleanser();
12         System.out.println(x);
13     }
14 }
15 
16 public class Detergent extends Cleanser{ // 继承,使用extends关键字
17     public void scrub() { // 重写父类方法
18         append(" Detergent scrub()");
19         super.scrub();
20     }
21     public void foam() { append(" foam()"); } // 添加新方法
22 
23     public static void main(String[] args) {
24         Detergent x = new Detergent();
25         x.dilute(); // 继承父类的方法,可直接使用
26         x.apply();
27         x.scrub();
28         x.foam();
29         System.out.println(x);
30         System.out.println("Testing base class");
31         Cleanser.main(args);
32     }
33 }

为了继承,一般规则是将所有的数据成员指定为private,将所有的方法指定为public(后面会学到protected成员也可以借助子类访问)。

Cleanser的接口中append()、dilute()、apply()、scrub()和toString() 。由于Detergent是由关键字extendsCleanser导出的,所以他可以在其接口中自动获得这些方法,所以可以将继承视为是对类的复用。

scrub()函数可以看到,子类可以对基类中定义的方法进行修改,本例中调用基类方法,需要使用了super关键字。

在继承中也可以添加新的方法,如foam()。

2.1 初始化基类

创建子类对象时,先创建父类的对象,再创建子类的对象,并将父类的对象放置在子类的内部

 1 class Art {
 2     Art() { System.out.println("Art constructor"); }
 3 }
 4 
 5 class Drawing extends Art {
 6     Drawing() {
 7         System.out.println("Drawing constructor");
 8     }
 9 }
10 public class Cartoon extends Drawing{
11     public Cartoon() {
12         System.out.println("Cartoon constructor");
13     }
14 
15     public static void main(String[] args) {
16         Cartoon x = new Cartoon();
17     }
18 }
19 输出:
20 Art constructor
21 Drawing constructor
22 Cartoon constructor

创建子类时,必须满足以下规则:

  1. 子类必须调用父类的构造器。
  2. 如果父类没有无参的构造器,则必须必须使用super显示调用带参的构造器
  3. 子类调用父类的构造器必须是第一条执行语句
 1 class Game {
 2     Game(int i) {
 3         System.out.println("Game constructor");
 4     }
 5 }
 6 class BoardGame extends Game {
 7     // 无参的构造函数会报错,默认调用父类无参的构造函数,但是父类没有无参的构造函数
 8     // 添加了super(9),显示调用后不报错,将super(9)放在第二条语句,编译报错
 9 //    BoardGame() {
10 //        System.out.println("BoardGame constructor");
11 //        super(9);
12 //
13 //    }
14     BoardGame(int i) {
15         super(i);
16         System.out.println("BoardGame constructor");
17     }
18 }
19 public class Chess extends BoardGame {
20     Chess(){
21         super(11);
22         System.out.println("Chess constructor");
23     }
24 
25     public static void main(String[] args) {
26         Chess x = new Chess();
27     }
28 }

3. 结合使用组合和继承

同时使用组合和继承,并配以必要的构造器初始化,可以创造更加复杂的类。

3.1 确保正确的清理

一般情况下,清理不是问题,垃圾回收器就能完成此功能。但有时需要自己清理,清理的时候要有先后顺序,和创建类的顺序刚好相反,清理时先清理子类,再清理基类。例如:

 1 /**
 2  * 确保正确的清理
 3  */
 4 class Shape {
 5     Shape(int i) {
 6         System.out.println("Shape constructor");
 7     }
 8     public void dispose() {
 9         System.out.println("Shape dispose");
10     }
11 }
12 class Circle extends Shape {
13     Circle(int i) {
14         super(i);
15         System.out.println("Circle constructor");
16     }
17     public void dispose() {
18         System.out.println("Circle dispose");
19         super.dispose();
20     }
21 }
22 class Triangel extends Shape {
23     Triangel(int i) {
24         super(i);
25         System.out.println("Triangle constructor");
26     }
27     public void dispose() {
28         System.out.println("Triangle dispose");
29         super.dispose();
30     }
31 }
32 public class CADSystem extends Shape{
33     private Circle c;
34     private Triangel t;
35     CADSystem(int i) {
36         super(i + 1);
37         c = new Circle(1);
38         t = new Triangel(1);
39         System.out.println("CADSystem constructor");
40     }
41 
42     public void dispose() {
43         System.out.println("CADSystem dispose");
44         // 1.先清理自身
45         t.dispose();
46         c.dispose();
47         // 2.在清理基类
48         super.dispose();
49     }
50 
51     public static void main(String[] args) {
52         CADSystem x = new CADSystem(0);
53         try {
54             // Code
55         } finally {
56             x.dispose();
57         }
58     }
59 }
60 输出:
61 Shape constructor
62 Shape constructor
63 Circle constructor
64 Shape constructor
65 Triangle constructor
66 CADSystem constructor
67 CADSystem dispose
68 Triangle dispose
69 Shape dispose
70 Circle dispose
71 Shape dispose
72 Shape dispose

3.2 名称屏蔽

子类可以对父类的方法进行重载或者覆盖。如果只是想覆写某个方法,可以使用@Override注解,如果不小心重载而并非覆写改方法,编译器会报错。

 1 /**
 2  * 子类重载和覆盖父类的方法
 3  */
 4 class Base {
 5     public void f1(int a) {
 6         System.out.println("Base::int(): " + a);
 7     }
 8     public void f1(double a) {
 9         System.out.println("Base::double(): " + a);
10     }
11 }
12 public class OverLoadAndOverride extends Base{
13     // 重载
14     public void f1(float a) {
15         System.out.println("OverLoadAndOverride::float(): " + a);
16     }
17 
18     // 覆盖报错,因为父类中没有此方法
19 //    @Override
20 //    public void f1(short a) {
21 //        System.out.println(a);
22 //    }
23     // 覆盖正确
24     @Override
25     public void f1(int a){
26         System.out.println("OverLoadAndOverride::int(): " + a);
27     }
28 
29     public static void main(String[] args) {
30         OverLoadAndOverride ov = new OverLoadAndOverride();
31         ov.f1(1);
32         ov.f1(1.0d);
33         ov.f1(1.0f);
34     }
35 }
36 
37 输出:
38 OverLoadAndOverride::int(): 1
39 Base::double(): 1.0
40 OverLoadAndOverride::float(): 1.0

4. 在组合与继承之间选择

组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。“has-a”(有一个)的关系用组合来表达。例如: 汽车由发动机、车窗,轮子等组合而成。

继承则是使用现有类,并开发他的一个特殊版本。“is-a”(是一个)的关系用继承来表达。例如:交通工具,汽车、自行车都是一种交通工具。

5. protected关键字

protected修饰的域或函数,只有子类或者同一个包的其他类有访问权限。但是最好的方式还是将域保持private,用protected方法来控制类的继承者的访问权限。

6. 向上转型

继承技术最重要的方面是用来表现新类和基类之间的关系。即“新类是基类的一种类型”。所有能够向基类发送的信息同样也能够向子类发送,例如:

 1 /**
 2  * 向上转型
 3  */
 4 class Instrument {
 5     public void play() {
 6         System.out.println("Instrument.play()");
 7     }
 8     static void tune(Instrument i) {
 9  i.play(); 10  } 11 } 12 public class Piano extends Instrument { 13 public static void main(String[] args) { 14 Piano p = new Piano(); 15 Instrument.tune(p); // 钢琴也是一种乐器 16  } 17 } 18 输出: 19 Instrument.play()

为什么称之为向上转型? 子类转型成基类,继承图上是向上移动的。

7. final关键字

final:无法改变的,可能使用的三种情况:数据,方法,类。

7.1 final数据

常见的final数据:

  1. 一个永不改变的编译时常量(既是static又是final的域)。
  2. 一个在运行时被初始化的值,不希望被改变。
  3. 方法的参数,不希望被改变

容易混淆的点:

  1. 对于基本类型,final使数值无法修改。
  2. 对于对象类型,final使引用恒定不变,即不能改变使其指向其他引用,但是对象自身是可以变化的

空白final:即被声明为final但又未给定初值的域。使用前必须保证被初始化。空白final可以做到根据对象而有所不同,却又保持其恒定不变的特性,提供了更大的灵活性。

 1 /**
 2  * final数据测试
 3  */
 4 class Value {
 5     int i;
 6     public Value(int i) {
 7         this.i = i;
 8     }
 9 } 10 public class FinalData { 11 private final int a = 0; 12 private int b = 0; 13 private final Value v = new Value(1); 14 15 public int f(final int i) { 16 // return i++; // 编译错误,参数是final,不允许修改 17 return i + 1; 18  } 19 public void g(final Value v) { 20 // v = new Value(1); // 编译错误,参数是final,不允许修改 21  } 22 23 public static void main(String[] args) { 24 FinalData f = new FinalData(); 25 // f.a = 1; //编译错误 26 f.b = 1; // 非final数据,可正常赋值 27 // f.v = new Value(10); // 编译错误,final修饰的对象引用不能改变 28 f.v.i = 10; // 可以正常赋值,final修饰的对象本身可以改变 29  System.out.println(f.v.i); 30  } 31 }

7.2 final方法

使用final方法的主要原因:把方法锁定,防止任何继承类修改它的定义

final和private:

类中的private方法都隐式的指定为final的,由于无法取用private方法,所以就无法覆盖它。可以对private方法加final修饰,但是这没有增加任何额外的意义。

覆盖只有在方法是基类接口的一部分才会出现,private是隐藏的,所以子类重新创建相同的方法不是覆盖,只是在子类中创建了一个和父类一摸一样的方法而已。使用@Override可以显示的说明是覆盖,这样则会报错,无法覆盖。

 1 class Base {
 2     private void f(){
 3         System.out.println("Base::f()");
 4     }
 5 }
 6 public class FinalOverride extends Base{ 7 // 这个其实是没有覆盖,在新的类中重新创建了f函数 8 private void f() { 9 System.out.println("FinalOverride::f()"); 10  } 11 // 显示的指出是覆盖,编译器会报错 12 // @Override 13 // private void f(){ 14 // System.out.println("FinalOverride::f()"); 15 // } 16 17 public static void main(String[] args) { 18 Base fo = new FinalOverride(); 19 // fo.f(); // 编译失败,无法调用基类的private方法 20  } 21 }

7.3 final类

当类定义成final时,这个类不能再被继承。

8. 初始化及类的加载

类是在其任何static成员被访问时加载的,构造方法也是static,只是没有显示的写出来。

 1 /**
 2  * 类的加载
 3  */
 4 
 5 class Inspect {
 6     private static int a1 = printInit("static Inspect.a1 initialized");
 7     private int b1 = printInit("Inspect.b1 initialized");
 8     protected int c1 = 100;
 9     Inspect() {
10         System.out.println("Inspect constructor"); 11  } 12 13 static { 14 System.out.println("Inspect static code block"); 15  } 16  { 17 System.out.println("Inspect init code block"); 18 19  } 20 private int d1 = printInit("Inspect.d1 initialized"); 21 22 private static int e1 = printInit("static Inspect.e1 initialized"); 23 static int printInit(String s) { 24  System.out.println(s); 25 return 47; 26  } 27 28 } 29 public class Beetle extends Inspect { 30 private static int a2 = printInit("Beetle.a2 initialized"); 31 private int b2 = printInit("Beetle.b2 initialized");; 32 public Beetle() { 33 System.out.println("Beetle constructor c1 = " + c1); 34  } 35  { 36 System.out.println("Beetle init code block"); 37  } 38 private int d2 = printInit("Beetle.d2 initialized"); 39 static { 40 System.out.println("Beetle static code block"); 41  } 42 private static int e2 = printInit("static Beetle.e2 initialized"); 43 44 public static void main(String[] args) { 45 System.out.println("Beetle will be created"); 46 Beetle b = new Beetle(); 47 48  } 49 50 }
 输出:
1 static Inspect.a1 initialized 2 Inspect static code block 3 static Inspect.e1 initialized 4 Beetle.a2 initialized 5 Beetle static code block 6 static Beetle.e2 initialized 7 Beetle will be created 8 Inspect.b1 initialized 9 Inspect init code block 10 Inspect.d1 initialized 11 Inspect constructor 12 Beetle.b2 initialized 13 Beetle init code block 14 Beetle.d2 initialized 15 Beetle constructor c1 = 100

将Beetle b = new Beetle();注释掉,也会输出前4行,证明了只要访问了类的static函数(main)也会加载类的部分属性

从输出看,我们创建类时,程序执行顺序如下:

  1. 基类的静态代码块,静态变量,初始化的顺序按照出现顺序。 // 1-3行,如果该基类还有其自身的基类,那么它的基类就会被加载,也就是说根基类中的static先初始化,然后是下一个子类,以此类推。
  2. 子类的静态代码块,静态变量,初始化的顺序按照出现顺序。// 4-6行
  3. 基类非静态初始化
    1. 基类的成员变量,初始代码块,初始化的顺序按照出现顺序。// 8-10行。第7行是创建对象前的提示,也就是说即使不创建对象,前面1-6行也会执行(只要调用类的static成员变量或者成员函数,当前调用的是static main函数)
    2. 基类的构造函数 // 11行 。
  4. 子类非静态初始化
    1. 子类的成员变量,初始代码块,初始化的顺序按照出现顺序 // 12-14行
    2. 子类的构造函数 // 15行。

转载于:https://www.cnblogs.com/songchj-bear/p/10399146.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值