这是我参与8月更文挑战的第 18 天,活动详情查看:8月更文挑战
前言
当我们整理好需求后需要编写一个新的 class B 时,发现手里已经有一个写好了的 class A,而且,A 中已经基本涵盖了我们的需求,我们要做的就是使用 ctrl + c 复制一个 A,然后 ctrl + v 得到了 A(1).java,对新文件重命名得到 B.java,最后,在 B.java 中补上我们还缺失的功能。我们大部分的开发生涯,应该都是这么过来的吧
好消息是, Java 提供了一种 class 复用的方案,就是 继承,向上面一个例子,如果 class B 的声明是 class B extends A,那 B 默认就能访问 A 的全部字段、全部方法,就好像自己也写了一份似的 (当然,A中的那些方法必须声明为 public 或 protected,亦或者是包访问权限,且 A,B 位于同一个包目录下)
class A{
public void f(){
// do something
};
}
class B extends A{
public void g();
}
new B().f(); // use f() from A
new B().g(); // use g() by self
限制
在 java 中,每个类都只能继承一个类;如果没有显示的指明自己继承的类,则会默认继承 java.lang.Object
使用继承,一个语义化的描述就是 xx is a yy,例如 Trangle is a Shape, Dog is a Animal, Fish is Food,如果不存在这种 is a 的关系,建议使用组合来替代继承
另一个考量的地方,我们需要编写一些实体类,而且这些实体类有着共同的行为,或者是共同的成员,通过继承,我们可以省略这部分公共的代码,却能实际拥有这些功能。这样还有另一个好处,如果是编写一些公共的处理方法,我们甚至能引入泛型,例如 public <T extends POJO> void func(T t){},这种带限定的泛型,允许我们使用 POJO 这个父类中的方法、参数等
重写
当我们使用继承后,发现父类中的某些功能,满足不了新的需求,我们要对这个方法进行升级,或者是更加彻底的重新编写
class A{
public void f(){
// do something
System.out.print("A.f()")
};
}
class B extends A{
@Overrid
public void f(){
// do otherthings
System.out.print("B.f()")
super.f(); // 如果需要调用父类的 f(),可以用类似 super.xx() 的形式
}
public void g();
}
在这个示例代码块中,我们使用了注解 @Override 表示当前的方法是重写了父类中对应的方法,若父类中不存在该方法,就会有报错提示 避免我们重写父类中并不存在的方法
如果我们的重写方法的目的是对父类中对应的方法做功能增强,那么我们可以用类似方法调用的方式,通过 super.xxx() 来执行父类中的 xxx()
和重载的区别
重写
重点是子类对父类中对应方法的覆盖编写
重载
在同一个类中,使用相同的方法名,但是,他们的参数列表不同,JVM 借此可以定位到你实际调用的哪一个方法
或者说,在同一个 class 中,方法名+参数列表 是独一无二的
// 方法重载的示意
class C{
void f(){};
void f(int a, String b){}
void f(String b, int a){}
}
重载”陷阱“
重载的概念,不仅用于方法,也能适用与构造器,仔细阅读下一段代码
class D{
D(int a, String b){}
D(Strgin b, int a){}
static createInstanceForA(int a, String b){}
static createInstanceForB(Strgin b, int a){}
}
在初始化 class D 的时候,我们只需要传入两个参数,但是,在初始化的过程中,我们需要做不同的操作,这时候,能做的就是调整参数的顺序,通过重载的方式获得一个全新用途的构造器,但这种操作存在一个非常致命的问题, 语义非常不明显,他人不知道要实际需要调用哪一个构造器,因为他们的入参类型完全一致,仅仅是顺序不同,当然,我们可以贴心的提供注释说明(程序员最讨厌的事情之一:为他人写注释文档)
为了解决这个问题,推荐使用静态工厂的思路来创建实例,为这个方法提供详细的描述名,使用者见名知意,具体的操作方式就是后面的两个静态方法,更加友好了,对吧
子类初始化细节
class A{
A(int a){}
A(){};
}
class B extends A{
B(int a){
// A(); // 如果不显示指定,就默认调用父类中的无参构造器
// do
}
B(){
A(1); // 我们也可以手动调用需要的 A 构造器
// do
}
}
对于需要手动调用父类构造器的情况:
- 父类中不存在无参构造器
- 我们不希望默认使用父类的无参构造器
这个调用操作必须写在子类构造器的第一行