继承
继承
概述
继承:子类继承了父类的属性及行为,使得子类的对象具有父类相同的属性及行为
子类的对象是可以直接访问父类中的非私有的属性及行为
extends 表示继承
可以申明一个类是从另一个类继承而来
继承的格式:
class 父类 { }
class 子类 extends 父类 { }
继承的关系式:is-a的关系,例 class 食肉动物 extends 动物 { }
解析:食肉动物 是 动物
补充:父类也称之为基类、超类
优缺点
优点
代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性
提高代码的可重用性及可维护性
提高代码的可扩展性,实现父类的方法就可以“为所欲为” (方法的重写)
缺点
继承是侵入性的。只要实现继承,就必须拥有父类的所有属性和方法
降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束
增强代码的耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构
代码演示:extends关键字基本使用
public class Test extends Person{
// 子类 继承 父类
public static void main(String[] args) {
Test test = new Test();
test.name = "ha";
test.age = 30;
// 虽然子类没有定义属性及行为,但可以直接给对象进行赋值,说明子类继承父类
test.eat(test.name);
System.out.println("个人信息是" + test.name + ",今年:" + test.age + "岁!");
}
}
class Person {
// 父类
String name;
int age;
public void eat (String name) {
System.out.println(name + "吃饭");
}
}
继承关系中成员变量的访问特点
代码演示:继承关系中成员变量的访问特点
public class Test {
public static void main(String[] args) {
Fu fu = new Fu();
System.out.println("父类的访问成员变量");
System.out.println(fu.aFu); // 10
System.out.println(fu.b); // 20
Zi zi = new Zi();
System.out.println("子类的访问成员变量");
System.out.println(zi.aFu); // 10
System.out.println(zi.aZi); // 30
System.out.println(zi.b); // 40
System.out.println(zi.getB()); //20
}
}
class Fu {
int aFu = 10;
int b = 20;
public int getB() {
return b;
}
}
class Zi extends Fu {
int aZi = 30;
int b = 40;
}
总结
父类的对象只能访问自己的成员变量
继承关系中:
子类可访问本类的成员变量及成员方法,父类的非私有化的成员变量及成员方法
若父子类中存在相同的成员变量,访问父类的成员变量,可通过间接的方式调用(getXxx方法)
若父子类中存在相同的成员方法,访问父类的成员方法,可通过间接的方式调用(将相同名的成员方法放置在另一个方法中)
方法重写
概述
方法重写:子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法
创建对象格式:父类 对象名 = new 子类(); // 父类的引用指向子类的对象
方法重写的规则
1. 方法名、参数的类型和个数相同
2. 不能发生在同类中,只能发生在子类中
3. 子类的成员方法上面存在 @Override,表示方法重写
4. 返回值类型
void/8种基本数据类型,父类的返回值类型保持一致
当父类的成员方法的类型是父类引用类型时,返回值类型可以是父类,也可以是子类
当父类的1成员方法的类型是子类引用类型时,返回值类型只能是子类
5. 修饰词
方法上不写修饰词,说明是默认的权限,只是将default修饰词隐藏罢了
子类的成员方法权限一定要大于等于父类的成员方法权限,但是“特殊情况除外”
特殊情况:当父类的成员方法权限是public时,子类的成员方法权限也一定是public
当父类的成员方法权限是private时,子类无法进行重写
权限从大到小排序:
public(公开的)> protected(受保护的)> 默认的(default)> private(私有的)
代码演示:方法重写的注意事项
public class Test {
public static void main(String[] args) {
// Fu fu = new Fu();
// Zi zi = new Zi();
// 以上两种方式只能在本类中进行调用,无法通过父类调用子类的方法
Fu t = new Zi();
// 为了解决,父类调用子类的方法。通过继承关系,父类的对象调用子类的成员方法
System.out.println(t.b); // 20
System.out.println(t.getB()); // 30
// t.getB() 此处父类优先调用自己类的方法,但是又发现子类中有同名的方法,这时子类对父类的方法发生了覆盖(重写)
}
}
class Fu {
int b = 20;
public int getB() {
return b;
}
}
class Zi extends Fu {
int b = 30;
public int getB() {
return b;
}
}
总结
特殊情况下,如果子类中没有重写方法,那么只能获取到父类的成员方法
代码演示:不同对象访问成员方法的运行结果
public class Demo {
public static void main(String[] args) {
Phone phone = new Phone();
phone.call();
phone.show();
System.out.println("=========");
NewPhone newPhone = new NewPhone();
newPhone.call();
newPhone.show();
System.out.println("=========");
Phone p = new NewPhone(); // 父类的引用指向子类的对象
p.call(); // 当子类中没有重写方法,访问的父类中的成员方法
p.show(); // 当子类中发生重写方法,访问的子类中的成员方法
}
}
class NewPhone extends Phone {
@Override // 表示方法重写
public void show() {
super.show(); // super 表示父类
// 若出现上行代码,不仅调用了父类的show方法,说明在原先的方法中添加了其他方法体
System.out.println("显示姓名");
System.out.println("显示照片");
}
}
class Phone {
public void call() {
System.out.println("打电话");
}
public void show() {
System.out.println("显示电话号码");
}
}
代码演示:成员方法返回值
public class Demo {
public static void main(String[] args) {
Phone p = new NewPhone(); // 父类的引用指向子类的对象
p.show();
}
}
class NewPhone extends Phone {
@Override
public Phone show() {
super.show();
System.out.println("显示姓名");
return new Phone();
}
}
class Phone {
public Phone show() {
System.out.println("显示电话号码");
return new NewPhone();
}
}
总结
当父类的成员方法类型是父类时,那么子类重写方法的返回值类型可以是父类,也可以是子类
当父类的成员方法类型是子类时,那么子类重写方法的返回值类型只能是子类
小结:重写与重载的区别
程序先编译再运行
重写:遵循“运行期绑定”。重写考虑签名、修饰词,只有在程序运行,才能发现程序是否异常。
重载:遵循“编译期绑定”。在编译时,编译器就可以判断方法名是否相同,若方法名相同,参数列表是否不同,若参数列表不同,则重载成立。
父子类构造方法的访问
代码演示:子类构造方法访问无参父类构造方法
public class Demo {
public static void main(String[] args) {
Student student = new Student();
// 编译结果:先执行父类的构造方法的内容,再执行子类的构造方法的内容
}
}
class Person {
Person() {
System.out.println("父类构造方法开始运行!");
}
}
class Student extends Person {
Student() {
super(); // 子类的构造方法中默认有一个父类的无参构造方法【super()】。可以可不写,但都会被执行
// (上行解析通俗的说:在构造子类的时候,优先构造父类)
// super调用构造方法,必须位于子类构造方法第一行
// 一个子类构造方法中,只能出现一个super
System.out.println("子类构造方法开始运行!");
}
}
代码演示:子类构造方法访问有参父类构造方法
public class Demo {
public static void main(String[] args) {
Student student = new Student();
}
}
class Person {
Person(int a) {
System.out.println("父类构造方法开始运行!");
}
}
class Student extends Person {
Student() {
super(20); // 父类是有参构造方法,子类构造方法不能隐藏【super()】。必须要写
System.out.println("子类构造方法开始运行!");
}
}
Super 与 this 的三种用法
Super的三种用法
1. 在子类的构造方法中,访问父类的构造方法
2. 在子类的成员方法中,访问父类的成员变量
3. 在子类的成员方法中,访问父类的成员方法
代码演示:Super的三种用法
public class Demo {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method1();
zi.run();
}
}
class Fu {
int i = 10;
public Fu() {
System.out.println("父类构造方法优先运行!");
}
void run() {
System.out.println("爸爸做运动!!");
}
}
class Zi extends Fu {
int i = 20;
// 1. 访问父类的构造方法
public Zi() {
super();
}
// 2. 访问父类的成员变量
void method1() {
System.out.println("访问父类成员变量i是" + super.i);
}
// 3. 访问父类的成员方法
@Override
void run() {
System.out.println("这是子类的方法!");
super.run();
}
}
this的三种用法
1. 在本类的构造方法中,访问本类的构造方法【注意:多个构造方法间调用避免死循环现象】
2. 在本类的成员方法中,访问本类的成员变量
3. 在本类的成员方法中,访问本类其他的成员方法
代码演示:this的三种用法
public class Demo {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method2();
}
}
class Fu {
int i = 10;
public Fu() {
System.out.println("无参构造");
}
public Fu(int a) {
this(); // 在本类的构造方法中,访问本类的构造方法
// 错误的调用构造方法,Fu();
System.out.println("有参构造");
}
}
class Zi extends Fu {
int i = 20;
public void method1() {
System.out.println("这是子类的成员方法!!");
}
public void method2() {
System.out.println("本类的成员变量是" + this.i); // 在本类的成员方法中,访问本类的成员变量
this.method1(); // 在本类的成员方法中,访问本类其他的成员方法
}
}
继承的三种特性
一个类只能有一个父类
【正确方式】
class A {}
class B extends A {}
【错误方式】
class A{}
class B extends A,C {}
一个父类有多个子类
【正确方式】
class A {}
class B extends A {}
class C extends A {}
多级继承
【正确方式】
class A {}
class B extends A {}
class C extends B {}