继承
- 1.继承和组合
- 1.1 继承的意义
- 1.2 继承的结构
- 1.3 继承的规则
- 1.4 super关键字
- 1.4.1 功能1——在子类中调用父类的成员变量或者方法
- 1.4.2 功能2——在子类中调用父类的构造方法
- 1.5 构造方法
- 1.6 super和this的比较
- 1.7 再谈构造代码块和构造方法的执行顺序
- 1.8 再谈访问修饰限定符
- 1.9 继承方式
- 2.final关键字
- 2.1 final修饰变量
- 2.2 final修饰类
- 2.3 final修饰方法
- 3.组合
1.继承和组合
1.1 继承的意义
因为现实中的对象之间都有一定的联系,比如对于猫和狗,他们有些特征是相同的,但是如果我们每次定义他们的类别的时候都要将他们的共同特点重新写一遍就会显得十分繁琐,因此我们可以抽象出他们共有的特点放在另一个动物类里面,通过 继承 ,就可以拥有这些共有的特点。
继承:共性的抽取,实现代码的复用。
1.2 继承的结构
继承的关键字: extends
修饰符 class 子类 extends 父类 {
// ...
}
见下面的例子:
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(this.name+"正在吃饭");
}
}
class dog extends Animal{
public void bark(){
System.out.println(this.name+"汪汪汪~~");
}
}
class cat extends Animal{
public void miaomiao(){
System.out.println(this.name+"喵喵喵~~");
}
}
1.3 继承的规则
- 继承的类叫做“子类、派生类”,被继承的类叫做 “ 父类、基类、超类”
- 父类的 成员变量 和 成员方法 都会被继承到子类中。
- 子类中允许存在和父类 名字相同的变量
- 访问的优先性:优先判断子类是否存在,再判断父类是否存在,若两者都不存在某一变量或者方法则报错—— 就近原则(通过派生类对象访问父类与子类同名方法是,如果父类和子类同名方法的参数列表不同(重载),则根据调用方法的实参选择合适的方法访问,若没有则报错)
- 访问修饰限定符只能决定访问的权限,不能决定能否被继承
1.4 super关键字
1.4.1 功能1——在子类中调用父类的成员变量或者方法
class Base{
public int a=99;
public void methodA(){
System.out.println("Base中的methodA");
}
}
class Derived extends Base{
public int a=199;
public void methodA(){
System.out.println("Derived中的methodA");
}
public void print(){ //用于测试
System.out.println(this.a);
System.out.println(super.a);
this.methodA();
super.methodA();
}
}
public class test {
public static void main(String[] args) {
Derived s1=new Derived();
s1.print(); //调用print方法看看结果对不对
}
}
注意super也只能够在非静态成员方法中使用
1.4.2 功能2——在子类中调用父类的构造方法
若是父类和子类都没有定义构造方法还好,但是若是父类定义了构造方法,那么子类一定也要定义构造方法,并且在子类构造方法的第一条必须要先调用父类的构造方法,而在子类的构造方法中调用父类的构造方法就要用到super关键字进行调用,与前面类和对象的this简化构造方法的使用方法一样 >>>点这里回顾
1.5 构造方法
- 若是在父类和子类中都没有定义构造方法,那么编译器会自动构建一个为空的构造方法
在父类中构建:
public Base(){
}
在子类中构建:
public Derived()
{
super();
}
- 若是父类中没有构造方法,子类中有构造方法,那么同样的会在父类中由编译器默认生成一个为空的构造方法,并且在子类的构造方法中也会在第一句默认加上
super()
- 若是父类中定义了构造方法,那么子类也必须定义一个构造方法,否则会报错,并且在子类构造方法的第一条语句必须是 “super()” 调用父类的构造方法,优先完成对父类的初始化。若是子类中有多个构造方法,那么每个构造方法的第一条语句也必须是“super()”
在前面我们学习了构造方法的重写,并且可以通过this()在一个构造方法中完成对另外一个构造方法的调用,但有个前提是这个this()也必须放在第一条语句。通过今天的学习我们发现在子类的构造方法中必须要将super()放在第一句,所以在派生类里面不要在一个构造方法中调用另外一个构造方法。
1.6 super和this的比较
- 都是java中的关键字
- 只能在非静态方法中使用,用来访问非静态的成员方法和字段
- 在构造方法中调用时,必须构造方法中的第一条语句,并且不能同时存在
不同点:
- this时当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- 在构造方法中:this(……)用于调用本类构造方法,super(……)用于调用父类构造方法,两种构造方法不能同时在构造方法中出现
- 构造方法中一定会存在super(……)的调用,用户没有写编译器也会增加,但是this(……)用户不写则没有
1.7 再谈构造代码块和构造方法的执行顺序
首先复习一下前面学习的知识:
- 实例代码块和构造方法只在创建对象的时候会被调用,并且这个对象的整个生命周期只会调用一次。
- 静态代码块是当这个类在main方法中加载的时候就会被调用,并且只调用一次
看这里复习笔记:链接: 点击
现在在学习了继承的概念之后,这些顺序又是如何呢?
首先见下面的代码:
class Base{
public int a;
{
System.out.println("父类::实例代码块"); //①
}
static {
System.out.println("父类::静态代码块"); //②
}
public Base(){
System.out.println("父类::构造方法"); //③
}
}
class Derived extends Base{
public static int b;
{
System.out.println("子类::实例代码块"); //④
}
static {
System.out.println("子类::静态代码块"); //⑤
}
public Derived(){
super();
System.out.println("子类:构造方法"); //⑥
}
}
public class test {
public static void main(String[] args) {
Derived s1=new Derived();
}
}
通过运行查看:
发现执行结果是:②⑤①③④⑥
- 执行顺序是:静态代码块 > 实例代码块 > 构造方法
- 父类 > 子类
- 静态代码块是在类加载阶段执行的,因此只会执行一次,这个和前面是一样的
1.8 再谈访问修饰限定符
在前面的学习中,我们了解到了这张表:
当时不了解子类的概念,现在学习了继承再次回顾一下这个知识点
首先见下面的代码:
// extend01包中
public class B{
private int a;
protected int b;
public int c;
int d;
}
// extend01包中
// 同一个包中的类
public class D extends B{
public void method{
super.a=10; // 编译报错,父类private成员在相同包子类中不可见
super.b=20; // 父类中protected成员在相同包子类中可以直接访问
super.c=30; // 父类中public成员在相同包子类中可以直接访问
super.d=40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
}
}
// extend02包中
// 不同包中的子类
public class C extends B {
public void method(){
super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见
super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问
super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问
super.d = 40; // 编译报错,父类中默认访问权限修饰的成员在不同包子类中不能直接访问
}
}
// extend02包中
// 不同包中的类
public class TestC {
public static void main(String[] args) {
C c = new C();
c.method();
System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见
System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问
System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问
System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
}
}
一些原则:
我们希望类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出必要的信息给类的调用者,因此我们在使用的时候应该尽可能的使用比较严格的访问权限. 例如如果一个方法能用 private, 就尽量不要用 public.
1.9 继承方式
多继承可能要到后面的多态中去了解。
一般我们不希望出现超过三层的继承关系
2.final关键字
2.1 final修饰变量
用final修饰了变量后,这个变量就变成了 常量 ,并且这个变量一定只能 被初始化一次
public class test {
public static void main(String[] args) {
final int a;
a=10;
a=20; // 这个时候会报错,因为a这个时候已经变成了一个常量,只能够被初始化一次
}
}
2.2 final修饰类
用final修饰了类之后,这个类就不能够被继承了,这也是为了限制继承的深度(最好不超过三层继承关系)
final class Base{ //使用了final修饰,这个类就不允许再被继承了
public int a;
{
System.out.println("父类::实例代码块");
}
static {
System.out.println("父类::静态代码块");
}
public Base(){
System.out.println("父类::构造方法");
}
}
class Derived extends Base{ // 这个时候会把报错,因为Base已经被final修饰了
public static int b;
{
System.out.println("子类::实例代码块");
}
static {
System.out.println("子类::静态代码块");
}
public Derived(){
super();
System.out.println("子类:构造方法");
}
}
2.3 final修饰方法
final修饰了方法后,这个方法就不允许被重写了,至于什么是方法的重写,我们后面再学习。
3.组合
组合用C语言来理解就是:在结构体里面定义结构体。因此用java理解就是,在类中创建另一个类的对象。
继承和组合都可以实现代码的复用,但是一般情况下 能用组合就用组合