更新自2020.04.22
代码复⽤是⾯向对象编程(OOP)最具魅⼒的原因之⼀。
面向过程和面向对象复用的区别:对于像 C 语⾔等⾯向过程语⾔来说,“复⽤”通常指的就是“”
Java 围绕“类”(Class)来解决问题。我们可以直接使⽤别⼈构建或
调试过的代码,⽽⾮创建新类、重新开始。
前提:
在不污染源代码的前提下使⽤现存代码
两种方法:组合和继承
他们都是基于现有类型构建新的类型
组合的语法概念:把对象的引⽤(object references)放置在⼀个新的类⾥,这就使⽤了组合。
举例如下:假设你需要⼀个对象,其中内置了⼏个 String 对象,两个基本类
型(primitives)的属性字段,⼀个其他类的对象。
注:
对于⾮基本类型对象,
将引⽤直接放置在新类中,对于基本类型属性字段则仅进⾏声明。
代码如下:
class WaterSource {
private String s;
WaterSource() {
System.out.println("WaterSource()");
s = "Constructed";
}
@Override
public String toString() { return s; }
}
public class SprinklerSystem {
private String valve1, valve2, valve3, valve4;
private WaterSource source = new WaterSource();
private int i;
private float f;
@Override
public String toString() {
return
"valve1 = " + valve1 + " " +
"valve2 = " + valve2 + " " +
"valve3 = " + valve3 + " " +
"valve4 = " + valve4 + "\n" +
"i = " + i + " " + "f = " + f + " " +
"source = " + source; // [1]
}
public static void main(String[] args) {
SprinklerSystem sprinklers = new SprinklerSystem();
System.out.println(sprinklers);
}
}
/*输出结果:
WaterSource()
valve1 = null valve2 = null valve3 = null valve4 = null
i = 0 f = 0.0 source = Constructed
*/
关于上面的程序的解析
仅限于个人理解
如果没有设置初始值,那么,引用的初始化结果为null,基础类型为int 的初始化结果是0,float类型的初始化结果是0.0
其中最重要的是source = Constructed的运行逻辑是new SprinklerSystem()
,又因为在这个初始化块中声明了s的值是"Constructed",所以最后输出source = Constructed
编译器不会为每个引用创建一个默认对象,这是有意义的,因为在许多情况下,这会导致不必要的开销。
初始化引用有四种方法: 当对象被定义时。这意味着它们总是在调用构造函数之前初始化。 在该类的构造函数中。 在实际使用对象之前。这通常称为延迟初始化。在对象创建开销大且不需要每次都创建对象的情况下,它可以减少开销。 使用实例初始化。class Soap {
private String s;
Soap() {
System.out.println("Soap()");
s = "Constructed";
}
@Override
public String toString() { return s; }
}
public class Bath {
private String // Initializing at point of definition:
s1 = "Happy",
s2 = "Happy",
s3, s4;
private Soap castille;
private int i;
private float toy;
public Bath() {
System.out.println("Inside Bath()");//1
s3 = "Joy";
toy = 3.14f;
castille = new Soap();
}
// Instance initialization:
{ i = 47; }
@Override
public String toString() {
if(s4 == null) // Delayed initialization:
s4 = "Joy";
return
"s1 = " + s1 + "\n" +
"s2 = " + s2 + "\n" +
"s3 = " + s3 + "\n" +
"s4 = " + s4 + "\n" +
"i = " + i + "\n" +
"toy = " + toy + "\n" +
"castille = " + castille;
}
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
castille = Constructed
*/
在 Bath 构造函数中,有一个代码块在所有初始化发生前就已经执行了。当你不在定义处初始化时,仍然不能保证在向对象引用发送消息之前执行任何初始化——如果你试图对未初始化的引用调用方法,则未初始化的引用将产生运行时异常。
当调用 toString()
时,它将赋值 s4,以便在使用字段的时候所有的属性都已被初始化。
继承是面向对象的三大特性之一,每个类都会继承,如果它们不显式继承其他类,那么肯定它们隐式继承(Object)类,也是所有java类的祖类。
组合和继承的区别就是继承使用了一种特殊的语法——使用关键字 extends 后跟基类的名称。
class 子类 extends 父类
之后子类就会拥有父类的所有字段和方法,无需再次声明。
注:java不允许菱形继承
代码示例如下
class Cleanser {
private String s = "Cleanser";
public void append(String a) {
s += a;
}
public void dilute() {
append(" dilute()");
}
public void apply() {
append(" apply()");
}
public void scrub() {
append(" scrub()");
}
@Override
public String toString() {
return s;
}
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute();
x.apply();
x.scrub();
System.out.println(x);
}
}
public class Detergent extends Cleanser {
// Change a method:
@Override
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Add methods to the interface:
public void foam() {
append(" foam()");
}
// Test the new class:
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
System.out.println(x);
System.out.println("Testing base class:");
Cleanser.main(args);
}
}
/* Output:
Cleanser dilute() apply() Detergent.scrub() scrub()
foam()
Testing base class:
Cleanser dilute() apply() scrub()
*/
这演示了一些特性。首先,在 Cleanser 的 append()
方法中,使用 +=
操作符将字符串连接到 s,这是 Java 设计人员“重载”来处理字符串的操作符之一 ,专门为了处理字符串而进行重载的。
在这里,Detergent.main()
显式地调用 Cleanser.main()
,从命令行传递相同的参数(当然,你可以传递任何字符串数组)。
Cleanser 的接口中有一组方法: append()
、dilute()
、apply()
、scrub()
和 toString()
。因为 Detergent 是从 Cleanser 派生的(通过 extends 关键字),即使你没有在 Detergent 中看到所有这些方法的显式定义,它也会在其接口中自动获取所有这些方法。
但是如果从父类里面的继承过来的方法不满意的话,可以覆写这个方法,只需要定义一个同名方法即可,不过你最好加上 @Override注解,如 scrub() 方法就进行了重写。
那么,可以把继承看作是复用类。如在 scrub()
中所见,可以使用基类中定义的方法并修改它。在这里,你可以在新类中调用基类的该方法。但是在 scrub()
内部,不能简单地调用 scrub()
,因为这会产生递归调用。为了解决这个问题,Java的 super 关键字引用了当前类继承的“超类”(基类)。因此表达式 super.scrub()
调用方法 scrub()
的基类版本。
继承时,你不受限于使用基类的方法。你还可以像向类添加任何方法一样向派生类添加新方法:只需定义它。方法 foam()
就是一个例子。Detergent.main()
中可以看到,对于 Detergent 对象,你可以调用 Cleanser 和 Detergent 中可用的所有方法 (如 foam()
)。
本笔记大部分基于《on java 8》整理, 另外初学者一枚,大家多多关照,有错误可以在下面说出来 谢谢大家