1 继承概述
1.1 概述
为什么需要用到继承了?我们举一个例子,现在有大壮、二憨、三壮三个同学,如果我对其创建描述类,需要分别创建姓名,年龄,还有工作。按照正常的做法就是分别创建三个类。但是这样太麻烦了,而且很重复。所以有什么方法省事呢。这就需要用到继承了,首先提取一个共同的类,具有姓名,年龄和爱好。在分别创建三各类继承它。然后根据自己不同的情况重写就好了。简单来说就是对一批相似度较高的类进行抽象,讲共同的变量和方法抽出来创建一个类,然后让其他对象继承它。
1.2 继承格式
一般如下所示:
class Fu{
}
class Zi extends Fu{
}
根据上面例子可得:
class Student{
String name;
int age;
public void work(String str){
System.out.println(str);
}
}
class Dazhuang extends Student{
}
这样大壮就拥有了姓名、年龄和work
1.3优缺点和说明
继承的优点
就简化了代码,提高了代码的复用性,可维护性、拓展性。
继承的缺点
也极其明显,增加了耦合性,因此在使用继承的时候,不要轻易更改原码。(哪怕原码是屎山,也不能轻易改)
说明
1.单一继承;java是不支持多继承的,也就是说一个子类只允许有一个父类。
2.多层继承;虽然不可以多继承,但是可以多层继承来实现,如下:
class D1{} class D2 extends D1{} class D3 extends D2{}
这样就实现了多层继承。
3.所有的继承都不能继承private的类,或者方法。这个我们下面展开讲。
2 继承详解
2.1 变量
子类继承父类之后,会自动继承父类所有的变量和方法,也可以对其进行覆盖,也可以自行扩展其他的变量和方法。
对变量继承要说三点:
第一,但是并不是在子类继承了父类之后,并不是继承父类所有的变量,它遵循上一节说明提到的不可以继承private修饰的成员变量。
第二,父类和子类一定要在同一个包之下,才能继承。
第三,对于可以继承的父类和子类来说,如果子类中出现了非私有的同名变量时,子类访问本类变量可以使用this,子类访问父类变量可以使用super。
2.2 方法
方法的继承和变量继承的三点相似。
第一,不能继承private成员方法。
第二,父类和子类必须在同一个包下,才可以继承。
第三,对于可以继承的父类和子类来说,如果子类中出现了和父类一模一样的函数时,当子类独享调用改函数时,会运行子类改函数的内容,例如:
class Person{
String name;
int age=18;
public void show(){
System.out.println("这是父类的show");
}
}
class Dazhuang extends Person{
public void show(){
System.out.println("这是子类的show");
}
}
运行之后的结果是
这是子类的show
注意:这里是子类的show覆盖(重写)了父类的show方法,但是其实也保留了父类的show方法,可以使用super调用,比如
class Person{
String name;
int age=18;
public void show(){
System.out.println("这是父类的show");
}
}
class Dazhuang extends Person{
public void show(){
super.show();
System.out.println("这是子类的show");
}
}
这里就通过super调用父类的show方法。
运行结果是:
这是父类的show
这是子类的show
这里再插一句题外话,在java中覆盖就是重写,但是重写和重载是有区别的:
重写(覆盖):
1.子类覆盖父类,必须要保证子类权限大于等于父类的权限才可以覆盖,否则编译失败,但是还是要遵循private不能被继承。
2.静态只能覆盖静态。
重载:方法重载是一个类定于了多个方法名相同,但是他们的参数数量不同捉着类型不同胡总和次序不同。
可以告诉大家辨别重写(覆盖)的一个小技巧,重写要求子类父类的这个方法一模一样。
2.3 构造函数
首先需要声明的是构造函数时不可以被继承的。但是可以被调用。
1.当父类存在无参构造函数时,在对子类对象初始化的时候,父类的构造函数也会运行,因为子类的构造函数默认第一行有一条隐式语句super()。super()会访问父类中空参数的构造函数,而且子类所有的构造函数默认第一行都是super()。
2.当父类中没有空参数的构造函数时,子类必须手动显式写super指定要访问的构造函数。
这里在加一个题外话,大家可以想一下为什么子类一定要访问父类的构造函数?
因为父类中的非私有数据都可以被子类直接获取,所以子列对象在建立的时候,需要提前了解父类的构造函数是否对其进行了修改。
3 小练习
前面两题借用了作者:Matrix海子
出处:http://www.cnblogs.com/dolphin0520/
1.
public class Test {
public static void main(String[] args) {
new Circle();
}
}
class Draw {
public Draw(String type) {
System.out.println(type+" draw constructor");
}
}
class Shape {
private Draw draw = new Draw("shape");
public Shape(){
System.out.println("shape constructor");
}
}
class Circle extends Shape {
private Draw draw = new Draw("circle");
public Circle() {
System.out.println("circle constructor");
}
}
结果是
shape draw constructor shape constructor circle draw constructor circle constructor
这道题目主要考察的是类继承时构造器的调用顺序和初始化顺序。要记住一点:父类的构造器调用以及初始化过程一定在子类的前面。由于Circle类的父类是Shape类,所以Shape类先进行初始化,然后再执行Shape类的构造器。接着才是对子类Circle进行初始化,最后执行Circle的构造器。
2.这个题目其实更多的和多态有关
public class Test {
public static void main(String[] args) {
Shape shape = new Circle();
System.out.println(shape.name);
shape.printType();
shape.printName();
}
}
class Shape {
public String name = "shape";
public Shape(){
System.out.println("shape constructor");
}
public void printType() {
System.out.println("this is shape");
}
public static void printName() {
System.out.println("shape");
}
}
class Circle extends Shape {
public String name = "circle";
public Circle() {
System.out.println("circle constructor");
}
public void printType() {
System.out.println("this is circle");
}
public static void printName() {
System.out.println("circle");
}
}
结果是
shape constructor circle constructor shape this is circle shape
解释:首先前两行不用解释了,是两个构造函数;
在继续解释之前,我们还要引入隐藏这个概念;
隐藏式之父类和子类拥有相同名字的属性或者方法(方法移仓只有一种,就是父类和子类存在相同的静态方法)时,父类的同名属性或者方法在形式上不见了,实际时存在的。也就是说隐藏式针对静态方法和成员变量而言的。
对多态而言,子类对象的多态性不适用于属性,因此这里调用的name和静态方法printName()都还是父类的。
参考博客: