Java中的继承
Java 继承
什么是继承
继承是用来描述“is-a”的关系;如果说两个对象A和B,若可以描述成“A是B”,则可以表示A继承B;其中,B是父类或者超类,A是子类或者派生类
继承者不仅拥有了父类的特性,还拥有自己独有的特性,所以子类会具有父类的一般特性也会具有自身的特性
为什么需要继承
如果没有继承,将会是如下情况,每个类之间存在着大量重复的代码,java中是不允许存在大量重复的代码的,这段代码不仅从代码上重复,而且从概念上讲学生一定是人,只是学生类描述的范围小,具备更多的属性和方法,这个时候想要消除结构定义上的重复,就要用到继承。
class Person {
private String name;
private int age;
//...省略get和set方法
}
class Student {
private String name;
private int age;
private String schoolName;
//...省略get和set方法
}
所以对于若干个相同或者相识的类,我们可以抽象出他们共有的行为或者属相并将其定义成一个父类或者超类,然后用这些类继承该父类,他们不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法。
继承的格式
关键字:extends
格式:class SubClass extends SuperClass {}
继承的类型
- 单继承:
B extends A {}
- 多重继承:
B extends A {}; C extends B {}
- 不同类继承同一个类:
B extends A{}; C extends A {}
继承的特性
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法(重写,体现多态)。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
继承想关的关键字
super 与 this 关键字
- super关键字:可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
- this关键字:指向自己的引用。
extends和implements
- extends关键字:在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
- implements关键字:使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
final关键字
- final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:
构造器
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。
如果父类的构造器带有参数,则必须在子类的构造器中显式地通过super()
调用父类的构造器并配以适当的参数列表。
class SuperClass {
private int n;
SuperClass(int n) {
System.out.println("SuperClass(int n):" + n);
this.n = n;
}
}
class SubClass extends SuperClass{
private int n;
public SubClass(int n){
super(300); // 调用父类中带有参数的构造器;必须通过关键字调用父类的构造函数
System.out.println("SubClass(int n):" + n);
this.n = n;
}
}
public static void main(String[] args) {
SubClass sc2 = new SubClass(100);
}
Output:
SuperClass(int n):300
SubClass(int n):100
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
public class Person {
protected String name;
protected int age;
protected String sex;
Person(){
System.out.println("Person Constrctor...");
}
}
public class Husband extends Person{
private Wife wife;
Husband(){
System.out.println("Husband Constructor...");
}
public static void main(String[] args) {
Husband husband = new Husband();
}
}
Output:
Person Constrctor...
Husband Constructor...
通过这个示例可以看出,构建过程是从父类“向外”扩散的,也就是从父类开始向子类一级一级地完成构建。而且我们并没有显示的引用父类的构造器,这就是java的聪明之处:编译器会默认给子类调用父类的构造器。
类继承时构造器的调用顺序和初始化顺序。要记住一点:父类的构造器调用以及初始化过程一定在子类的前面
继承的缺点
继承存在如下缺陷:
- 父类变,子类就必须变。
- 继承破坏了封装,对于父类而言,它的实现细节对与子类来说都是透明的。
- 继承是一种强耦合关系。
经典题目
下面代码输出啥?
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是一个指向Shape类型的对象,只是重名的方法被子类覆写了。根据这个思想我们看一下这个程序的运行流程
解释步骤:
1.先初始化 Shape shape = new Circle();
先会调用父类Shape的无参构造函数,打印shape constructor,然后在调用Circle的无参构造函数,打印circle constructor
2.System.out.println(shape.name);
因为shape是指向Shape类的,所以显示“shape”
非空对象调用静态方法、静态变量、非静态成员变量时,以声明的类的类型为准(静态绑定)
非空对象调用非静态方法时,以生成的类的类型为准(动态绑定)
3.shape.printType();
这一步printType不是静态方法,所以方法可以被Circle子类覆写了,调用的是Circle里面的方法,显示this is circle
4.shape.printName();
这一步因为printName方法是静态方法所以不会进行覆写,调用的是Shape类里面的方法,显示shape
故输出如下:
shape constructor
circle constructor
shape
this is circle
shape
网友评论