Java——代码复用(组合和继承)
前言
“复用代码是Java众多引人注目的功能之一。但要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它必须还能够做更多的事情。” Java解决问题都围绕类展开的,对于复用代码,可以创建新的类来复用,也可以使用别人已经开发并调试好的类。方法的关键在于使用类而不破坏现有程序代码。有两种方式达成此方法的目的:组合和继承。下面将介绍这两种代码重用机制。
组合和继承的实现
在新的类中产生现有类的对象,即做组合。该方法只是复用了现有程序代码的功能,而不是它的形式。按照现有类的类型创建新类,无需改变现有类的形式,采用现有类的形式并在其中添加新的代码,此方式就是继承。
组合的实现
只需要将对象的引用置于新类中即可。
class A{
...
}
class B{
private A;
...
}
在Java中,类中域为基本类型时能够被自动初始化为零,对象引用会被初始化为null。编译器并不是简单地为每一个引用都创建默认对象。若是想初始化对象引用,可以在代码中的下列位置进行:
- 在定义对象的地方。这也意味着它们会在构造器被调用之前被初始化。
- 在类的构造器中。
- 在临近使用这些对象之前,再初始化,这种方式就叫做
惰性初始化
。 - 使用实例初始化。
使用《Java编程思想》上面的实例说明:
class Soap{
private String s;
public Soap() {
System.out.println("Soap()无参构造器");
s = "Constructed"; //在类构造器中初始化
}
@Override
public String toString() { return s;}
}
public class Bath {
private String s1 = "Happy";//在定义对象的地方初始化
private Soap castille = new Soap();
private String s2;
private int i;
public Bath() { System.out.println("Bath() 无参构造器");}
//实例初始化
{
i = 31;
System.out.println("初始化i为31");
}
@Override
public String toString() {
if(s2 == null) { //惰性初始化
s2 ="Java";
}
return s1 + "\t"+ s2 + "\t" + i + "\t" + castille;
}
public static void main(String[] args) {
System.out.println(new Bath());
}
}
/*
output:
Soap()无参构造器
初始化i为31
Bath() 无参构造器
Happy Java 31 Constructed
*/
继承的实现
Java中的继承是使用关键字extends
实现的,使用继承子类将会得到父类中所有的域和方法。父类中的私有域和方法将被隐式继承,非私有方法和域将被显式继承。隐式继承也就说可以被间接访问到。
下面将以一个例子说明继承中出现的一些问题。
class Person {
private String name; //私有域
public Person() {
System.out.println("Person() 无参构造器");
name = "noName";
}
public Person(String name) {
System.out.println("Person(String name) 带参构造器");
this.name = name;
}
public String getName() { return name;} //让子类可以间接访问到私有域name
public String toString() { return "Person: "+name; }
}
class Student extends Person{
public Student() {
//默认调用 super(); 表示调用父类的无参构造器
System.out.println("Student() 无参构造器");
}
public Student(String name) {
super(name); //调用父类的带参构造器
System.out.println("Student(String name) 带参构造器");
}
public String toString() { return "Student: " + getName();}
}
public class ExtendsExample {
public static void main(String[] args) {
System.out.println(new Student());
System.out.println(new Student("sakura"));
}
}
/*
output:
Person() 无参构造器
Student() 无参构造器
Student: noName
Person(String name) 带参构造器
Student(String name) 带参构造器
Student: sakura
*/
子类和父类的构造器调用问题
关于构造器的调用顺序在前面构造方法和匿名对象提过,这里再着重介绍下子类和父类的构造器调用问题。
有一个常识问题就是肯定是先有父再有子,那么在程序中也是,应该是现有父类然后才有子类。Java中使用super关键字表示超类也就是父类。若是子类中没有显式调用父类的构造器,那么编译器就会默认调用super(),即父类的默认构造器;若是父类没有默认构造器,则子类中就必须显式调用父类的带参构造器,并且调用必须放在构造器中的第一行!
super()和this()可以同时存在吗?
通过代码可以得知,super()和this()是不能同时出现在构造器中。需要知道,不管怎么样,在子类构造器调用之前一定会先执行父类的构造器。
组合和继承之间的区别
组合和继承都允许在新的类中防止子对象,组合是显式这样做,而继承则是隐式这样做。组合技术通常是用于想在新类中使用现有类的功能而非其接口的情况。即,在新类中嵌入某个对象,让其实现所需要的功能;新类的用户看到的只是为新类所定义的接口,而非所嵌入类的接口。继承是使用某个现有类并开发其特殊版本,会拥有现有类的接口并且可以为新类开发新的接口。所以,继承关系为“is-a”(是一个),组合关系为“has-a”(有一个)。
protected关键字
在实际的项目中,经常会想要将某些事物尽可能对这个世界隐藏起来,但是仍然允许导出类的成员访问它们。protected
关键字就是实现这个作用。它表明:对于任何继承于此类的导出类或者任何与此类在同一个包中的类来说,被protected修饰的方法和域是可以访问的,但是对于除这两种类的其他类则是不可访问的。
继承的局限
Java中继承的局限便是不允许类似C++那样的多继承,至于为什么没有多继承最直接的原因就为简单编程,不用去考虑父类的父类是谁、父类的父类的父类是谁、以及自己包含的方法继承自谁的。比如B和C继承自A,D又继承B、C,当D调用A中的一个方法时,是继承自B的还是继承自C的呢?
为了实现多继承的效果的话可以使用多层继承(A继承B,B继承C),但是最好不要超过三层,否则代码会太乱。后面会介绍Java使用接口声明的多继承。一个类虽然不能从继承多个基类但是可以声明继承多个接口(interface),从而达到多继承效果。
小结
继承和组合都可以从现有类型生成新类型。组合一般是将现有类型作为新类型的底层实现的一部分来加以复用,而继承复用的是接口。在使用继承时,由于导出的类具有基类的接口,因此它可以向上转型至基类,这对后面要介绍的多态是至关重要的。
参考:
《Java编程思想》第四版
Java——代码复用(组合和继承)的更多相关文章
- java代码复用(继承,组合以及代理)
作为一门面向对象开发的语言,代码复用是java引人注意的功能之一.java代码的复用有继承,组合以及代理三种具体的表现形式,下面一一道来. 第一种方式是通过按照现有的类的类型创建新类的方式实现代码的复 ...
- 深入理解Java中的组合和继承
Java是一个面向对象的语言.每一个学习过Java的人都知道,封装.继承.多态是面向对象的三个特征.每个人在刚刚学习继承的时候都会或多或少的有这样一个印象:继承可以帮助我实现类的复用.所以,很多开发人 ...
- Java代码复用的三种常用方式:继承、组合和代理
复用代码是Java众多引人注目的功能之一.这句话很通顺,没什么问题,但问题在于很多人并不清楚“复用”是什么.就好像我说“沉默王二是一个不止会写代码的程序员”,唉,沉默王二是谁? 我们需要来给“复用”下 ...
- Java 代码复用 —— 泛型
public interface Comparable<T> { public int compareTo(T o); } 1. 接口(Comparable:可比较接口) public s ...
- Rust 中的继承与代码复用
在学习Rust过程中突然想到怎么实现继承,特别是用于代码复用的继承,于是在网上查了查,发现不是那么简单的. C++的继承 首先看看c++中是如何做的. 例如要做一个场景结点的Node类和一个Sprit ...
- 初涉JavaScript模式 (13) : 代码复用 【上】
引子 博客断了一段时间,不是不写,一是没时间,二是觉得自己沉淀不够,经过一段时间的学习和实战,今天来总结下一个老生常谈的东西: 代码复用. 为何复用 JS门槛低,故很多人以为写几个特效就会JS,其实真 ...
- javascript 模式(1)——代码复用
程序的开发离不开代码的复用,通过代码复用可以减少开发和维护成本,在谈及代码复用的时候,会首先想到继承性,但继承并不是解决代码复用的唯一方式,还有其他的复用模式比如对象组合.本节将会讲解多种继承模式以实 ...
- Java面向对象理解_代码块_继承_多态_抽象_接口
面线对象: /* 成员变量和局部变量的区别? A:在类中的位置不同 成员变量:在类中方法外 局部变量:在方法定义中或者方法声明上 B:在内存中的位置不同 成员变量:在堆内存 局部变量:在栈内存 C:生 ...
- 编写高质量代码改善C#程序的157个建议——建议103:区分组合和继承的应用场合
建议103:区分组合和继承的应用场合 继承所带来的多态性虽然是面向对象的一个重要特性,但这种特性不能在所有的场合中滥用.继承应该被当做设计架构的有用补充,而不是全部. 组合不能用于多态,但组合使用的频 ...
随机推荐
- golang ntp协议客户端
NTP(Network Time Protocol,网络时间协议)是由RFC 1305定义的时间同步协议,用来在分布式时间服务器和客户端之间进行时间同步.NTP基于UDP报文进行传输,使用的UDP端口 ...
- JUnit介绍(转)
测试的重要性毋庸再说,但如何使测试更加准确和全面,并且独立于项目之外并且避免硬编码,JUnit给了我们一个很好的解决方案.一.引子 首先假设有一个项目类SimpleObject如下: pu ...
- Go语言基础之接口
Go语言基础之接口 接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节. 接口 接口介绍 在Go语言中接口(interface)是一种类型,一种抽象的类 ...
- Linux系统如何添加IP别名
IP别名可以在一块物理网卡上绑定多个IP地址,这样就能够在使用单一网卡的同一个服务器上运行多个基于IP的虚拟主机,简单来说,IP别名就是一张物理网卡上配置多个IP,实现类似子接口之类的功能. 那么IP ...
- ES6-个人学习大纲
1,let const学习补充 1.1,let的知识点: 01-作用域只限制在当前代码块内,代码块形式如下: { var str = '张三'; console.log(str); let str ...
- springboot整合mybatis和mybatis-plus
问题 1 分页查询问题 2 mybatis的配置由mybatis变成mybatis-plus 3 Mybatis-plus中的Wrapper
- 【页面置换算法】LRC算法和FIFS算法
算法介绍 FIFO:该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰.该算法实现简单,只需把一个进程已调入内存的页面,按先后次序链接成一个队列,并设置一个指针,称为替换指针, ...
- Red and Black---POJ - 1979
There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A ...
- HTML5 部分新增语义化标签元素
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- SEO需要掌握的基础知识
什么是SEO? 官方解释: SEO是指通过对网站内部调整优化及站外优化,使网站满足搜索引擎收录排名需求,在搜索引擎中提高关键词排名, 从而把精准用户带到网站,获得免费流量,产生直接销售或品牌推广 ...