目录
1. 什么是继承
子类:又叫派生类
父类:又叫基类/超类
继承:就是对共性的抽取,从而达到对代码的复用【重复使用】
在Java中,只能继承一个类,不能多继承
当子类继承父类之后,就会把父类中的成员变量和成员方法全部继承到子类中
私有的成员也可以被继承,但是不能被访问。要想被访问,父类的成员中要提供公开的方法。
子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
2. 父类成员的访问
先去子类中找,找到则访问,找不到再去父类中找,找到则访问,否则编译报错。
若子类和父类都有此成员,就近原则,优先访问子类。
2.1 子类中访问父类的成员变量
子类继承父类中的所有成员变量和成员方法,当父类和子类中拥有同名的成员变量时,遵循就近原则,会优先访问子类自己的
非要访问父类的,前面要加个【super点号】
2.2 子类中访问父类的成员方法
和访问父类中的成员变量一样
当父类和子类中拥有同名的成员方法时,遵循就近原则,会优先访问子类自己的
非要访问父类的,前面要加个【super点号】
2.3 重载和重写的区别
重载:可以发生在一个类中,也可以发生在继承当中
- 1. 方法名称必须一样
- 2. 方法的参数必须不一样(个数,数据类型,不同数据类型的顺序 这三个中,满足一个不同就算参数不一样)
- 3. 返回值不做要求,可以相同也可以不同
重写:在子类中把父类本身有的方法重新写一遍
- 1. 方法名称必须一样
- 2. 方法的参数必须一样(个数,数据类型,不同数据类型的顺序 都要一样)
- 3. 返回值也必须一样
- 4. 修饰符可以不一样,但是有要求:子类的访问权限要大于等于父类的访问权限且修饰符不可以是private,private修饰的方法是不可以重写的。
访问权限由大到小:【public > protected > default > private 】
具体见多态。
3. super关键字
由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,
如果想要在子类方法中访问父类同名成员,需要用到super关键字。
3.1 super的用法
1. super.data;【访问父类的普通(非静态)成员变量】
2. super.func();【调用父类的普通(非静态)成员方法】
3. super();【调用父类的构造方法】
静态的通过类名去调用,不建议使用this或super
super只能在非静态方法中使用,静态方法中不能有super,不能有this,静态方法中只能是静态的。
3.2 super和this的区别
同:
- 1. 都是Java中的关键字
- 2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
- 3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
异:
- 1. this是当前对象的引用,谁调用了这个方法,谁就是this。super指代了子类对象中从父类继承下来这部分成员,就是一个关键字,不是父类的引用,父类根本就没new对象
- 2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- 3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
- 4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
4. 子类构造方法:给子类的成员变量赋值
先给从父类中继承的成员变量赋值,再给子类自己新增的成员变量赋值
实例化子类对象时,JVM会将对象中的成员初始化成初始值,接着会调用子类构造方法。super();的作用是:调用父类的构造方法。所以,会先给从父类中继承的成员变量赋值,再给子类自己新增的成员变量赋值。
注意:
1. 虽然执行了父类的构造方法,但是并没有生成父类的对象,这里只是帮助子类给从父类继承过来的属性赋值。
2. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造方法中第一条语句。
3. super(...)不能和this同时出现在一个构造方法中,因为它们都要放在构造方法的第一条语句,会形成冲突,不知道执行哪条语句。
4.1 父类中是显式定义无参或者默认的无参构造方法
那么在子类构造方法第一行默认会有隐含的super()调用【调用父类构造方法】
4.1.1 子类中也是显式定义无参或者默认的无参构造方法
只要是无参构造方法,无论子类和父类有没有提供,或是一方提供一方没提供,子类构造方法第一行有没有super();
只要没有,编译器都会帮我们生成一份默认的无参构造方法,没有的编译器都会帮我们补全
最终会补全成下面这样:【子类父类都有无参构造方法,子类的无参构造方法第一行有super();】
4.1.2 子类中是有参构造方法
子类中提供有参构造方法,父类中是显式定义无参或者默认的无参构造方法,
那么编译器将不会帮我们在子类中生成无参构造方法,若父类中不是显式定义无参,编译器会帮我们在父类中生成一份默认的无参构造方法,且在子类有参构造方法第一行默认有隐含的super();
最终会补全成下面这样:【子类有参父类无参,子类的有参构造方法第一行有super();】
4.2 父类中是有参构造方法
那么在子类构造方法第一行不会有隐含的super()调用【调用父类构造方法】需要自己写
无论子类中的构造方法是有参还是无参,也都得自己写,都得是显性的。
4.2.1 子类中是显式定义无参构造方法
4.2.2 子类中是有参构造方法
5.如何给子类的成员变量赋值(从父类继承的和子类自己新增的)
成员变量包括:从父类继承的和子类自己新增的
5.1 子类和父类不存在同名成员变量
1. 定义时就直接赋值
2. 在main函数中通过【引用.成员变量】进行赋值
3. 在子类中使用成员方法
4. 在子类中使用构造方法
(有参数的构造方法、构造方法中调用本类当中的其他构造方法)
【使用有参数的构造方法进行赋值最好用】
class Animals{
public String name;
public int age;
public String sex = "男";// 1. 定义时就直接赋值
}
public class Dog extends Animals {
public boolean silly;
/**
* 3. 使用成员方法
*/
public void setDog(int age,boolean silly){
this.age = age;
this.silly = silly;
}
@Override
public String toString() {
return name+" "+age+" "+sex+" "+silly;
}
public static void main(String[] args) {
Dog dog = new Dog();
/**
* 2. 在main函数中通过【引用.成员变量】进行赋值
*/
dog.name = "大黄";
dog.setDog(10,true);
System.out.println(dog);
}
}
4.1 使用有参数的构造方法
4.2 使用无参数的构造方法,并调用本类当中的其他构造方法
4.3 使用有参数的构造方法,并调用本类当中的其他构造方法
5.2 子类和父类存在同名成员变量
相同成员变量中,如何分别给继承的父类成员变量和子类自己的成员变量赋值?
使用构造方法进行赋值时,父类是有参构造方法且参数是同名的成员变量时,是给继承的父类成员变量赋值;父类是默认的无参构造方法时,说明根本没有访问到父类的成员变量,所以是给子类自己的成员变量赋值。
(!!!)就近原则,this在父类的构造方法中,就是给父类的成员变量赋值;this在子类的构造方法中,就是给子类的成员变量赋值。
使用构造方法给继承的父类成员变量和子类自己的成员变量赋值对比:
(1)有参数的构造方法:
(2) 构造方法中调用本类当中的其他构造方法:
给子类自己的成员变量赋值:
1. 定义时就直接赋值
2. 在子类中使用成员方法(因为就近原则,方法中的成员变量都是子类自己的)
3. 使用构造方法
(有参数的构造方法、构造方法中调用本类当中的其他构造方法)
给继承的父类成员变量赋值:
1. 定义时就直接赋值
2. 在子类中使用成员方法【成员变量前要加super点号】
3. 在父类中使用成员方法(因为就近原则,方法中的成员变量都是父类自己的)
4. 使用构造方法
(有参数的构造方法、构造方法中调用本类当中的其他构造方法)
class Animals{
public String name = "小黑";// 1. 定义时就直接赋值
public int age;
public String sex;
}
public class Dog extends Animals {
public String name;
public int age;
/**
* 2. 在子类中使用成员方法【成员变量前面要加super点号】
*/
public void setDog(String name,int age,String sex) {
super.name = name;
super.age = age;
super.sex = sex;
}
@Override
public String toString() {
return sex+" "+name+" "+age+" "+" "+super.name+" "+super.age;
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.setDog("hello",18,"男");
System.out.println(dog);
}
}
class Animals{
public String name;
public int age;
public String sex;
/**
* 3. 在父类中使用成员方法
*/
public void setDog(String name,int age){
this.name = name;
this.age = age;
}
}
public class Dog extends Animals {
public String name;
public int age;
@Override
public String toString() {
return sex+" 子类中的:"+name+" "+age+" "+"父类中的:"+super.name+" "+super.age;
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.setDog("小灰",2);
System.out.println(dog);
}
}
6. 代码块
静态代码块在类加载的时候就被执行了,它不依赖于对象,且只会执行一次
实例代码块和无参数的构造方法只有在创建对象时才会执行,且创建几个对象执行几次
1. 父类静态代码块优先于子类静态代码块执行,且是最早执行
2. 父类实例代码块和父类构造方法紧接着执行
3. 子类的实例代码块和子类构造方法紧接着再执行
4. 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
顺序:父类中的静态代码块,子类中的静态代码块,父类中的实例代码块,父类中的构造代码块,子类中的实例代码块,子类中的构造代码块
注:静态的成员变量、成员方法,也是静态;实例的成员变量、成员方法也是实例
也遵循上述代码块的顺序:先父类静态,子类静态。再分别父类和子类的实例和构造。
class Animals{
public String name;
public int age;
public String sex;
static{
System.out.println("Animals::static{}");
}
{
System.out.println("Animals::{}");
}
public Animals(){
System.out.println("Animals()");
}
}
class Cat extends Animals{
static{
System.out.println("Cat::static{}");
}
public Cat(){
System.out.println("Cat()");
}
{
System.out.println("Cat::{}");
}
}
public class Dog extends Animals {
public String name;
public int age;
static{
System.out.println("Dog::static{}");
}
public Dog(){
System.out.println("Dog()");
}
{
System.out.println("Dog::{}");
}
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
}
}
7. protected 关键字
可访问范围:同一个包中,不同包中的它的子类
8. final 关键字
final关键可以用来修饰变量、成员方法以及类
1. 修饰变量或字段,表示常量(即不能修改)【public final int a = 10;】
2. 修饰方法:就变成了密封方法,表示该方法不能被重写
3. 修饰类:就变成了密封类,表示此类不能被继承【public final class A{},A将不能被继承】
9. 继承与组合
组合也能达到代码复用的效果。组合并没有涉及到特殊的语法, 仅仅是将一个类的实例作为另外一个类的字段。
继承表示对象之间是is-a的关系:
class Dog extends Animal -》 Dog is a Animal
组合表示对象之间是a part of/has-a的关系:
学生和老师是学校的一部分
学校有学生和老师
class Teacher{
}
class Student{
}
class School{
//一个学校有很多老师和很多学生组成
public Teacher[] teacher;
public Student[] student;
}