在编程过程中,常常需要对一部分类进行重复利用,以减轻开发的工作量,也方便日后代码的维护和拓展。
实现代码复用主要有两种方式:组合和继承。
一、组合
组合:将需要使用的类引用置于新类中即可。
public class Room {
String name;
public Room(String name) {
this.name = name;
}
public void cleanRoom() {
System.out.println(name + " room cleaned");
}
}
public class House {
private String color;
private Room room;
public House(String color, Room room) {
this.color = color;
this.room = room;
}
public static void main(String[] args) {
Room room = new Room("Lisa");
House house = new House("white", room);
house.room.cleanRoom();
}
}
Lisa room cleaned
通过组合引用对象时,一定要确保对象在调用前被成功初始化,否则会抛出空指针异常。
可以通过三种方式初始化引用对象:
(1)定义对象时,直接初始化
public class House {
private String color;
private Room room = new Room("Lisa");
}
(2)在构造器中初始化
public class House {
private String color;
private Room room;
public House(String color, Room room) {
this.color = color;
this.room = room;
}
}
(3)在使用对象之前,初始化对象。这被称为惰性初始化,在不必每次都生成对象的场景下,可以减轻程序的负担。
public class House {
private String color;
private Room room;
public House(String color) {
this.color = color;
}
public static void main(String[] args) {
House house = new House("white");
if (house.room == null){
house.room = new Room("Lisa");
}
house.room.cleanRoom();
}
}
二、继承
继承:通过 extend 关键字声明继承其他类,获得基类中所有的成员和方法(基类中的 private 成员和方法无法获得)。倘若不声明,则默认隐式继承标准根类 Object (这也解释了为何任何一个类都默认带有 toString() 、equals() 方法 )。
下面依次来了解使用继承的几个注意点:
(1) super 关键字
通过继承创建的类被称为导出类,被继承的类称为基类。每个导出类内部都隐式包含了一个基类的子对象,在初始化导出类时,会优先初始化基类,初始化过程是根据继承的结构从上至下的。
public class Art {
public Art() {
System.out.println("Art init");
}
}
public class Drawing extends Art {
public Drawing() {
System.out.println("Drawing init");
}
}
public class Cartoon extends Drawing {
public Cartoon() {
System.out.println("Cartoon init");
}
public static void main(String[] args) {
Cartoon cartoon = new Cartoon();
}
}
Art init
Drawing init
Cartoon init
假设基类只提供带参数的构造方法,在初始化导出类时,必须将参数传入基类的构造方法进行初始化。
public class Game {
private String name;
public Game(String name) {
this.name = name;
System.out.println("Game init");
}
}
public class BoardGame extends Game {
public BoardGame(String name) {
super(name);
System.out.println("BoardGame init");
}
}
这里出现了一个新的关键字 super ,它用来表示继承的基类,super() 代表基类的构造方法。
除此之外,我们还可以通过 super 调用基类的其他方法。
public class Game {
private String name;
public Game(String name) {
this.name = name;
System.out.println("Game init");
}
@Override
public String toString() {
return "Game{" +
"name='" + name + '\'' +
'}';
}
}`
public class BoardGame extends Game {
public BoardGame(String name) {
super(name);
System.out.println("BoardGame init");
System.out.println(super.toString());
}
public static void main(String[] args) {
BoardGame boardGame = new BoardGame("chess");
}
}
Game init
BoardGame init
Game{name='chess'}
(2) protected 关键字
在使用继承的过程中,我们想定义一些成员变量或者方法供导出类使用,又不想暴露给其他外部类,此时 protected 关键字便派上了用场。
protected 定义在成员变量或者方法前,此域便只能由同包类或者导出类可以访问。
public class Game {
private String name;
public Game(String name) {
this.name = name;
System.out.println("Game init");
}
protected void startGame() {
System.out.println("game start");
}
}
public class BoardGame extends Game {
public BoardGame(String name) {
super(name);
System.out.println("BoardGame init");
startGame();
}
public static void main(String[] args) {
BoardGame boardGame = new BoardGame("chess");
}
}
Game init
BoardGame init
game start
(3)向上转型
这里需要注意一个概念:导出类可以是基类,但基类不一定是导出类。比如:创建一个 Instrument 基类代表乐器,再创建一个 piano 导出类代表钢琴。我们可以说钢琴是乐器,但不能说乐器一定就是钢琴。
下面来看看在继承中,如何表述这种关系:
public class Instrument {
private String color;
public Instrument(String color) {
this.color = color;
}
public void play() {
System.out.println(color + " instrument play");
}
public static void tune(Instrument instrument) {
instrument.play();
}
}
public class Piano extends Instrument {
public Piano(String color) {
super(color);
}
}
public static void main(String[] args) {
Instrument instrument = new Instrument("blue");
Instrument.tune(instrument);
System.out.println("----second tune-----");
Piano piano = new Piano("black");
Instrument.tune(piano);
}
blue instrument play
----second tune-----
black instrument play
tune() 方法不仅可以接收 Instrument 类型的引用,还可以接收其导出类 Piano 的引用。这是因为程序对 piano 类执行了向上转型,将 Piano 转换为 Instrument 进行调用。因为导出类是包含基类所有方法的,所以往往是安全的。故在调用tune() 方法时,即使没有指定参数的转换类型,编译器也会自动执行向上转型。
小结
复用类的方式分为两种:
1.组合
通过直接引用复用类为自身成员变量。在调用复用类时,一定得确保复用类已被正确初始化。
2.继承
通过 extend 关键字继承复用类的成员变量和方法。
继承中可通过 super 关键字访问基类的成员变量和方法;
可通过 protected 关键字让成员或者方法供包外部的导出类访问;
当方法参数为基类时,可将导出类传入方法,触发自动向上转型进行调用。
关于如何选择组合和继承?
倘若不需要对复用类进行拓展,推荐直接使用组合;反正,则推荐使用继承。不过,我们还是应当慎重考虑使用继承的场景,因为继承会隐藏代码的具体实现,不利于代码的可读性和可维护性。
本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。
若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!