目录
一、继承存在的意义
我们在Java中使用类来描述现实生活中的实体,对类实例化后就产生了表示现实生活的实体的对象
但是现实生活中的实体都是有各自的类别的,比如说猫、狗、鱼、鸟、老虎、狮子等等都属于动物这一类别
我们可以先来创建两个类:猫、狗
package TestDome1;
public class Dog {
public String name;
public int age;
public String color;
public void eat(){
System.out.println(name + "正在吃饭");
}
public void sleep(){
System.out.println(name + "正在睡觉");
}
public void bark(){
System.out.println(name + "正在汪汪叫");
}
}
package TestDome1;
public class Cat {
public String name;
public int age;
public String color;
public void eat(){
System.out.println(name + "正在吃饭");
}
public void sleep(){
System.out.println(name + "正在睡觉");
}
public void miao(){
System.out.println(name + "正在喵喵叫");
}
}
我们可以看到上面两个类中存在着许多重复的内容
不停的写重复的内容太繁杂了,我们可以想办法将他们的共性提取出来,使这些共性只用写一次却能被多个有这些共性的类来使用
此时就要提到继承啦
面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用
二、 继承浅认识
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
继承主要解决的问题是:共性的抽取,实现代码复用
那么现在我们就采用继承的思想来抽取上述猫和狗两个类的共性,实现复用
在Java中如果要表示类之间的继承关系,需要借助extends关键字
修饰符 class 子类 extends 父类 {
// ...
}
现在我们将继承放到实例中
package TestDome2;
//父类
public class Animal {
public String name;
public int age;
public String color;
public void eat(){
System.out.println(name + "正在吃饭!");
}
public void sleep(){
System.out.println(name + "正在睡觉!");
}
}
package TestDome2;
public class Dog extends Animal{
public void bark(){
System.out.println(name + "正在汪汪叫!");
}
}
package TestDome2;
public class Cat extends Animal{
public void miao(){
System.out.println(name + "正在喵喵叫!");
}
}
package TestDome2;
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
//dog访问的name、color、eat()、sleep()都是从父类中继承过来的
dog.name = "花花";
dog.color = "白色";
dog.bark();
dog.eat();
dog.sleep();
System.out.println("----------------------------");
Cat cat = new Cat();
//cat同理
cat.name = "咪咪";
cat.miao();
cat.eat();
cat.sleep();
}
}
注:
- 子类会将父类中的成员变量或者成员方法继承到子类中了
- 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
继承方式
注:
- Java中不支持多继承
- 一般我们不希望出现超过三层的继承关系
三、子类访问父类成员
(一)子类访问父类的成员变量
1.子类和父类成员变量不同名
package TestDome3;
public class Base {
int a;
int b;
}
package TestDome3;
public class Derived extends Base{
int c;
public void func(){
a = 15;//访问从父类继承下来的成员变量a
b = 10;//访问从父类继承下来的成员变量b
c = 16;//访问子类自己的成员变量c
}
}
2.子类和父类成员变量同名
package TestDome4;
public class Base {
int a = 10;
int b = 20;
int c = 15;
}
package TestDome4;
public class Derived extends Base {
int a = 5;
char b = 'a';
public void func(){
System.out.println(a);//访问子类自己的成员变量a
System.out.println(b);//访问子类自己的成员变量a
System.out.println(c);//访问从父类继承下来的成员变量c
}
}
package TestDome4;
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();
derived.func();
}
}
注:
- 如果访问的成员变量子类中有,优先访问自己的成员变量
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的
总结:
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找
(二)子类访问父类的成员方法
1.子类和父类成员方法不同名
package TestDome5;
public class Base {
public void func1(){
System.out.println("Base中的方法");
}
}
package TestDome5;
public class Derived extends Base{
public void func2(){
System.out.println("Derived中的方法");
}
public void func3(){
func1();//访问父类的方法func1()
func2();//访问子类自己的方法func2()
}
}
package TestDome5;
public class Test {
public static void main(String[] args) {
Derived derived = new Derived();
derived.func3();
}
}
2.子类和父类成员方法同名
package TestDome6;
public class Base {
public void func1(){
System.out.println("Base中的func1()");
}
public void func2(){
System.out.println("Base中的func2()");
}
}
package TestDome6;
public class Derived extends Base{
public void func1(int a) {
System.out.println("Derived中的func1()");
}
public void func2(){
System.out.println("Derived中的func2()");
}
public void func3(){
func1();//未传参,访问Base中的func1()
func1(15);//传参,访问Derived中的func1()
func2();//访问Derived中的func2()
}
}
package TestDome6;
public class Test {
public static void main(String[] args) {
Derived derived = new Derived();
derived.func3();
}
}
注:
- 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错
- 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法传递的参数选择合适的方法访问,如果没有则报错
从上面我们可以知道,如果子类中存在与父类中相同的成员时,要优先访问子类中的成员,那么如果我要在子类中访问父类相同名称的成员时应该怎么办呢?
此时我们就需要认识一下super关键字啦!
四、super关键字
super关键字的主要作用:在子类方法中访问父类的成员
package TestDome7;
public class Base {
int a = 15;
int b = 16;
public void A(){
System.out.println("Base中的A()");
}
public void B(){
System.out.println("Base中的B()");
}
}
package TestDome7;
public class Derived extends Base{
int a = 100;
int b = 150;
public void A(int a){
System.out.println("Derived中的A()");
}
public void B(){
System.out.println("Derived中的B()");
}
public void C(){
System.out.println(a);
System.out.println(b);
System.out.println(super.a);
System.out.println(super.b);
A(a);
A();
B();
super.B();
}
}
package TestDome7;
public class Test {
public static void main(String[] args) {
Derived derived = new Derived();
derived.C();
}
}
注:
- 只能在非静态方法中使用
- 在子类方法中,访问父类的成员变量和方法
子类的构造方法
父类和子类可以看作是父子,先有父后有子,那么对应的:子类对象构造时,需要先调用父类构造方法,然后执行子类的构造方法
package TestDome8;
public class Base {
public Base() {
System.out.println("Base()");
}
}
package TestDome8;
public class Derived extends Base{
public Derived() {
System.out.println("Derived()");
}
}
package TestDome8;
public class Test {
public static void main(String[] args) {
Derived derived = new Derived();
}
}
注:
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有super()
- 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败
- 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句
- super(...)在子类构造方法中只出现一次,并且不能和this同时出现
迅速区分super与this
相同点:
- 都是关键字
- 只能在类的非静态方法中使用,用来访问非静态成员
- 必须是构造方法的第一条语句,且不能同时存在
不同点:
- this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
- 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
五、再遇初始化
首先,我们先来回顾一下没有遇到继承情况下的实例代码块、静态代码块、构造方法的执行顺序
package TestDome9;
public class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("执行构造方法");
}
{
System.out.println("执行实例代码块");
}
static {
System.out.println("执行静态代码块");
}
}
package TestDome9;
public class Test {
public static void main(String[] args) {
Person person1 = new Person("张三",19);
System.out.println("------------------------------------");
Person person2 = new Person("李四",25);
}
}
总结:
- 静态代码块先执行,并且只执行一次,在类加载阶段执行
- 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后执行构造方法
好啦,现在让我们看一下在继承关系上的执行顺序吧
package TestDome10;
public class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:执行构造方法");
}
{
System.out.println("Person:执行实例代码块");
}
static {
System.out.println("Person:执行静态代码块");
}
}
package TestDome10;
public class Student extends Person{
public Student(String name, int age) {
super(name, age);
System.out.println("Student:执行构造方法");
}
{
System.out.println("Student:执行实例代码块");
}
static {
System.out.println("Student:执行静态代码块");
}
}
package TestDome10;
public class Test {
public static void main(String[] args) {
Student student1 = new Student("小王",20);
System.out.println("----------------------------------------");
Student student2 = new Student("小李",18);
}
}
总结:
- 最先执行父类静态代码块,其次执行子类静态代码块
- 然后执行父类实例代码块和父类构造方法
- 接着执行子类的实例代码块和子类构造方法
- 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
六、protected和final关键字
(一)protected关键字
此前,我在封装那篇文章的开头已经介绍了访问控制修饰符,感兴趣的可以去了解一下
同一包中
package TestDome11;
public class A {
private int a;
protected int b;
public int c;
int d;
}
package TestDome11;
public class B extends A{
public void method(){
super.a = 10;
super.b = 15;
super.c = 12;
super.d = 20;
}
}
不同包中的子类
package TestDome11;
public class A {
private int a;
protected int b;
public int c;
int d;
}
package TestDome12;
import TestDome11.A;
public class C extends A {
public void method(){
super.a = 10;
super.b = 20;
super.c = 30;
super.d = 40;
}
}
注:
尽可能的使用比较严格的访问权限,例如如果一个方法能用 private, 就尽量不要
用 public
(二)final关键字
final关键可以用来修饰变量、成员方法以及类
1.修饰变量或字段
表示常量(不能修改)
public class Test {
public static void main(String[] args) {
final int a = 15;
a = 25;//编译错误
System.out.println(a);
}
}
2.修饰类
表示此类不能被继承
package TestDome13;
final public class Animal {
}
package TestDome13;
public class Dog extends Animal{
public static void main(String[] args) {
System.out.println();
}
}
3.修饰方法
表示该方法不能被重写
七、继承与组合
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车有轮胎、方向盘、发动机等
class Tire{
//轮胎类
}
class Engine{
//发动机
}
class SteeringWheel{
//方向盘类
}
class Car{
private Tire tire;//可以复用轮胎中的属性和方法
private Engine engine;//可以复用发动机中的属性和方法
private SteeringWheel steeringWheel;//可以复用方向盘中的属性和方法
}
//消防车是汽车
public class FireTruck extends Car{
//将汽车中包含的:轮胎、发动机、方向盘全部继承下来
}
建议:
能用组合尽量用组合