1、继承概述:
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
通过extends关键字可以实现类与类的继承 class 子类名 extends 父类名 {}
单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。
有了继承以后,我们定义一个类的时候,可以在一个已经存在的类的基础上,还可以定义自己的新成员。
2、继承的好处
* 提高了代码的复用性;
* 多个类相同的成员可以放到同一个类中;
* 提高了代码的维护性;
* 如果功能的代码需要修改,修改一处即可;
* 让类与类之间产生了关系,是多态的前提;
* 其实这也是继承的一个弊端:类的耦合性很强。
3、继承的特点
* Java只支持单继承,不支持多继承。
一个类只能有一个父类,不可以有多个父类。
class SubDemo extends Demo{} //ok
class SubDemo extends Demo1,Demo2...//error
* Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
4、继承的注意事项
* 子类只能继承父类所有非私有的成员(成员方法和成员变量),其实这也体现了继承的另一个弊端:打破了封装性
* Object类 是所有的类的顶层父类,所有类都是直接或间接继承自他
* 子类不能继承父类的构造方法,但是可以通过super(后面讲)关键字去访问父类构造方法。
即,构造子类对象时,必须先访问父类构造方法。
*不要为了部分功能而去继承。
案例:
//父类
public class Person {
private String name;
private String sex;
private int age;
// 父类无参构造方法
public Person() {
super();//访问顶层父类object的构造方法
System.out.println("父类的无参构造方法");
}
//父类的有参构造方法
public Person(String name, String sex, int age) {
super();//访问顶层父类object的构造方法
this.name = name;
this.sex = sex;
this.age = age;
System.out.println("父类的有参构造方法");
}
}
public class Student extends Person {
public Student() {
super();//访问父类Person的无参构造
System.out.println("子类的无参构造方法");
}
public Student(String name, String sex, int age) {
super(name, sex, age);//访问父类Person的有参构造
System.out.println("子类的有参构造方法");
}
}
public class Text {
public static void main(String[] args) {
Student stu=new Student();
Student stu1=new Student("Bob","男",18);
}
}
运行结果:
5、继承中成员变量之间的关系
在父类中定义一个成员变量;
1)在子类中定义一个成员变量和父类中成员变量名称不同,然后再在子类中定义一个方法去访问变量,发现变量名不同,访问非常简单;
2)在子类中再定义一个成员变量,和父类中的成员变量名称一致,然后继续访问。发现访问的是子类的成员变量;子类中的成员变量和父类中的成员变量名称一样,在子类中访问一个变量的查找顺序("就近原则")。
3)如果我要访问父类的成员变量该怎么办呢?通过回想this来引入super关键字
** super关键字:和this关键字很像
this代表本类对应的引用。
super代表父类存储空间的标识(可以理解为父类引用)
# 用法(this和super均可如下使用)
* 访问成员变量:
this.成员变量
super.成员变量
* 访问构造方法
this(…);
super(…);
* 访问成员方法
this.成员方法();
super.成员方法();
6、 继承中构造方法的关系
* 子类中所有的构造方法默认都会访问父类中空参数的构造方法,为什么呢?
因为子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化。每一个构造方法的第一条语句默认都是:super()
* 如果父类中没有构造方法呢?
1、手动给父类加上一个无参构造方法;
2、调用本类的有参构造;(案例如下)
//父类Animal
public class Animal {
public String name;
//方法1:给父类加上无参构造
// public Animal() {
// super();
// }
public Animal(String name) {
super();
this.name = name;
System.out.println("父类的有参数构造执行了");
}
}
public class Dog extends Animal {
String sname;
public Dog() {
this("aaa");// 调用本类有参数构造
System.out.println("本类无参数构造执行了");
}
public Dog(String sname) {
super(sname);
System.out.println(sname);
System.out.println("本类的有参数构造执行了");
}
}
//测试类
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
//运行结果
// //程序分析:
Dog dog = new Dog();创建子类对象,且是调用无参构造方法。
首先执行this("aaa");调用本类有参构造,调用本类有参构造首先要执行父类的有参构造方法。
案例2:
class Fu {
static {
System.out.println("静态代码块Fu");// 1
}
{
System.out.println("构造代码块Fu");// 2
}
public Fu() {
System.out.println("构造方法Fu");// 3
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");// 2
}
{
System.out.println("构造代码块Zi");// 5
}
public Zi() {
System.out.println("构造方法Zi");// 6
}
}
//测试类
class Test {
public static void main(String[] args) {
Zi zi = new Zi();
}
}
//运行结果
//分析小知识:
1、静态随着类的加载而加载。
2、静态代码块、构造代码块、构造方法的执行流程
静态代码块-->构造代码块-->构造方法
3、只要有子父关系,肯定先初始化父亲的数据,然后初始化子类的数据。
案例3:
class X {
Y b = new Y();
X() {
System.out.print("X");
}
}
class Y {
Y() {
System.out.print("Y");
}
}
public class Z extends X {
Y y = new Y(); //y就是Z类的一个成员变量,创建Z的对象时要执行该语句。
Z() {
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
//运行结果: YXYZ
//分析:
1) 成员变量有基本类型和引用类型的。
class Demo {
//基本类型
int x = 10;
//引用类型
Student s = new Student();
}
2) 类的初始化过程:
加载class文件;
堆中开辟空间,只要有对象的创建,都先要在堆内存中给成员变量开辟空间赋初始值;
变量的默认初始化,一般引用类型成员变量默认初始值为null,基本数据类型默认初始值不为null(int:0; float:0.0;);
变量的显示初始化;
构造代码块初始化;
构造方法初始化;
3) 遇到extends,就要知道,先初始化父类数据,然后初始化子类数据。
要注意是分层初始化。super在这里仅仅表示要先初始化父类数据。
7、继承中成员方法的关系
当父类和子类出现一模一样的方法(方法名相同、参数列表相同、返回值类型相同)时,就会发生方法的覆盖,也就是方法重写。
为什么要进行方法重写呢?那是因为子类对父类提供的方法不满意,需要在其基础上进行改进和丰富,那么此时就需要方法重写,重写后子类对象调用该方法时访问重写后的方法。
* 方法重写的注意事项:
1) 父类中私有方法不能被重写,因为父类私有的方法子类不能继承,因此也就谈不上方法重写;
2) 子类重写父类方法时,访问权限不能更低,最好一致,权限修饰符号的级别大小 public>protected>缺省>private;
3) 父类静态方法,子类也必须通过静态方法进行重写。
* 方法重写与方法重载的区别:
方法重载要求几个方法的方法名相同,参数列表不同,返回值类型可以不同;
方法重载则要求方法的声明完全相同(方法名相同、参数列表相同、返回值类型相同),
且方法重载的前提是要有继承关系。
案例:学生类和教师类根据各自的特点,对sleep()方法、eat()方法进行了重写
//父类Person
public class Person {
String name;
int age;
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
//学生类Student
public class Student extends Person {
@Override
public void eat() {
System.out.println("学生喜欢吃,海底捞");
}
@Override
public void sleep() {
System.out.println("学生喜欢白天睡觉,晚上哈皮");
}
}
//教师类
public class Teacher extends Person {
@Override //表示重写
public void eat() {
System.out.println("老师喜欢吃面条");
}
@Override
public void sleep() {
System.out.println("我白天讲课,晚上睡觉");
}
}
//测试类:
public class Test {
public static void main(String[] args) {
Teacher teacher = new Teacher();
Student student = new Student();
teacher.eat();
student.eat();
teacher.sleep();
student.sleep();
}
}
//运行结果
8、final关键字
final关键字是最终的意思,可以修饰类,成员变量,成员方法;
* 修饰类,类不能被继承;
* 修饰变量,变量就成了常量,只能被赋值一次,其值不能被改变;
* 修饰方法,方法可以被继承,但是不能被重写。