Java全面分析面向对象之继承
作者:厚积薄发ض
继承
什么是继承呢?
继承(Inheritance)是一种联结类与类的层次模型。指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;继承是一种is-a关系。
看了这些概念你可能还是百思不得其解,我来用大白话给你讲解一下,我们在现实生活中也听过继承,不过是继承家产,继承传承文明.......等等,那这些继承是不就把上一代人传下来的东西全部交给继承人了,这些继承下来的东西,继承人随时都可以用,但是继承人也有属于自己的东西。没错这就是继承,而在我们写代码的时候将一些类所具有的共性全部集中在一起放到一个类中(这个类就是父类),然后子类想要调用从父类继承下来的成员就随时可以调用了。这也就将共性全部抽取出来,实现了代码的复用。
说了这么多不写代码理解可能还是理解的不太通透,好接下来我就举个例子来给大家理解。
实现一个狗类:
//实现一个狗类 class Dog{ public String name;//名字 public int age;//年龄 public String furColor;//毛颜色 public void eat() { System.out.println(this.name+"吃饭!!!"); } public void sleep() { System.out.println(this.name+"睡觉!!!"); } public void bark() { System.out.println(this.name+"汪汪汪"); } }
实现一个猫类:
//实现一个猫类 class Cat{ public String name;//名字 public int age;//年龄 public String furColor;//毛颜色 public void eat() { System.out.println(this.name+"吃饭!!!"); } public void sleep() { System.out.println(this.name+"睡觉!!!"); } public void mew() { System.out.println(this.name+"喵喵喵"); } }
我们都知道猫和狗都是动物,他们都有共性的属性比如名字,年龄,吃饭,睡觉,并且它们还有自己的属性比如狗会汪汪汪,猫会喵喵喵。既然他们有共同的属性我们就可以创建一个动物类。
//实现一个动物类 class Animal{ public String name;//名字 public int age;//年龄 public String furColor;//毛颜色 public void eat() { System.out.println(this.name+"吃饭!!!"); } public void sleep() { System.out.println(this.name+"睡觉!!!"); } } //实现一个狗类 class Dog{ public void bark() { System.out.println("汪汪汪"); } } //实现一个猫类 class Cat{ public void mew() { System.out.println("喵喵喵"); } }
既然把他们共性都抽取出来怎么用呢?
显然直接用是用不了的。那需要怎么用呢。我们想的是这个狗和猫都要有这些属性,当我们向用的时候随时用,号接下来我们讲的继承就会很好地解决这个问题。
继承的语法:继承是利用extends关键字将子类与父类建立联系。
//实现一个狗类 class Dog extends Animal{ public void bark() { System.out.println(this.name+"汪汪汪"); } } //实现一个猫类 class Cat extends Animal{ public void mew() { System.out.println("喵喵喵"); } }
这回就不报错了。
我们来分析一下这段代码:
在这里子类又叫做派生类,父类可以叫做父类,基类还有超类。
这里有一个问题:
子类继承了父类的什么呢?
答案是:除了构造方法所有。
子类访问父类的成员变量
子类访问父类非同名成员变量
//实现一个猫类 class Cat extends Animal { public void mew() { System.out.println(this.name + "喵喵喵"); } public void Init() {//访问父类 this.name = "咪咪"; this.age = 2; this.furColor = "橘黄色"; } public void show() { System.out.println(name); System.out.println(age); System.out.println(furColor); } }
这里就体现了继承的关系,子类继承了父类的属性(成员变量和成员方法);
这里我们还可以调用构造方法初始化成员,调用toString方法来打印信息
//实现一个猫类 class Cat extends Animal { public void mew() { System.out.println(this.name + "喵喵喵"); } public Cat(String name,int age,String furColor) { this.name =name; this.age=age; this.furColor =furColor; } @Override public String toString() { return "Cat{" + "name='" + name + '\'' + ", age=" + age + ", furColor='" + furColor + '\'' + '}'; } } public class TestDemo1 { public static void main(String[] args) { //Cat cat = new Cat(); Cat cat = new Cat("咪咪",2,"橘黄色"); cat.eat(); cat.sleep(); cat.mew(); System.out.println(cat.toString()); } }
子类访问父类同名成员变量
当子类成员变量名与父类成员变量同名了会怎么办呢???
//实现一个动物类 class Animal{ public String name = "花花";//名字 public int age;//年龄 public String furColor;//毛颜色 public void eat() { System.out.println(this.name+"吃饭!!!"); } public void sleep() { System.out.println(this.name+"睡觉!!!"); } } //实现一个猫类 class Cat extends Animal { public String name = "咪咪"; public void mew() { System.out.println(this.name + "喵喵喵"); } public void Init() {//访问父类 name = "咪咪"; this.age = 2; this.furColor = "橘黄色"; } } public class TestDemo1 { public static void main(String[] args) { Cat cat = new Cat(); System.out.println(cat.name); } }
总结:当父类与子类同名时遵循就近原则,如果实例化子类对象就去子类找,找不到就去父类,父类没有就报错,如果实例化父类对象就直接去父类找,父类没有就报错。
子类访问父类的成员方法
子类访问父类的非同名方法
class Cat extends Animal { public String name = "咪咪"; public void mew() { System.out.println(this.name + "喵喵喵"); } public void methodSon() { System.out.println("我是子类的方法!!"); } public void method() { methodSon();//访问子类 methodFather();//访问父类 } }
子类访问父类的同名方法
//实现一个动物类 class Animal{ public String name = "花花";//名字 public int age;//年龄 public String furColor;//毛颜色 public void method(int a) { System.out.println("我是父类的方法!!"+a); } } //实现一个猫类 class Cat extends Animal { public String name = "咪咪"; public void mew() { System.out.println(this.name + "喵喵喵"); } public void method() { System.out.println("我是子类的方法!!"); } } public class TestDemo1 { public static void main(String[] args) { Cat cat = new Cat(); cat.method();//没有带参数的只有子类有,如果两者都有method同样的方法,那就涉及到重写(前提是引用子类对象)优先访问子类 cat.method(2);//带参数的只有父类有 //cat.method(); } }
子类访问父类同名的方法也是一样的也是采取就近原则,当引用子类对象的时候优先子类,然后去父类寻找,找不到报错。
但是方法这里有两个特殊情况,一是方法重载,说明的是同一个类可以支持方法重载,不同类但是有继承关系的也是支持方法重载的。当出现方法重载,就会根据参数列表的不同来访问。
二是方法重写,方法重写指的是方法名相同返回值相同,参数列表相同,当出现方法重写的时候(前提是引用子类对象)就优先子类,会出现动态绑定,这个咱后面讲解。
上面我们都说同名的时候都会遵循就近原则。就比如我们引用子类对象的时候,我就想优先调用父类,那怎么办呢????
那就该super关键字出场了;
super关键字
super就是一个普通的关键字,来引用当前对象的父类,当我们看见super的时候我们就要知道它是访问父类的就可以了。
好我们来实践一下。
super访问父类成员变量
//实现一个动物类 class Animal{ public String name = "花花";//名字 public int age;//年龄 public String furColor;//毛颜色 } //实现一个猫类 class Cat extends Animal { public String name = "咪咪"; public void mew() { System.out.println(this.name + "喵喵喵"); } public void method() { System.out.println(super.name); } } public class TestDemo1 { public static void main(String[] args) { Cat cat =new Cat(); cat.method(); } }
super访问父类成员方法
//实现一个动物类 class Animal{ public String name = "花花";//名字 public int age;//年龄 public String furColor;//毛颜色 public void method() { System.out.println("我是父类的方法!!!"); } } //实现一个猫类 class Cat extends Animal { public void method() { System.out.println("我是子类的方法"); } public void methodA() { super.method(); } } public class TestDemo1 { public static void main(String[] args) { Cat cat =new Cat(); cat.methodA(); } }
创建构造方法
当我们为父类创建了一个构造方法并且为子类创建构造方法的时候就会报错,原因是什么呢???
那就是当我们创建一个构造方法的时候一定要先为父类创建构造方法,原因是每个类都要至少要有一个构造方法,以前没有报错是因为编译器自动为我们生成了无参的构造方法。子类对象一般都有继承过来的属性还有自己独有的属性,在创建子类对象的时候,一般先执行父类的构造方法,将子类对象中继承父类的属性初始化完整,然后在调用自己的构造方法,为自己独有的属性初始化完整。
所以我们一定要先为父类创建构造方法。
怎么创建呢??? 与this一样只不过这是父类的构造方法,所以利用super关键字,利用super().
同时super不能在静态方法中使用,并且当调用构造方法的时候this()和super()只能出现一个,并且出现在第一行。
super要点:
- super关键字不能在静态方法中使用。
- super关键字只能放在方法中的第一行,并且与this()只能出现一个。
- super关键字访问父类属性 super.父类方法 super.父类成员变量 super()为父类提供构造方法。
- super关键字是引用父类的对象,当我们看见super的时候,他一定是引用父类的东西。
- 当没有为父类写带有参数的构造方法的时候,编译器会为子类自动提供构造方法,当父类写了带有参数的构造方法,编译器不会为子类提供无参的构造方法,需要用户自己写,并且在写子类的构造方法的时候一定要先为父类构造。
super与this的区别
相同点:
- this与super关键均不能在静态方法中使用,静态方法不依赖于对象。
- 并且只能放在方法的第一行。
- 都是java中的关键字
不同点:
- this是引用当前对象,super是用来引用当前对象的父类
- this()调用本类中的构造方法,super用来调用父类的构造方法。
- this调用非静态方法中本类当中的属性,super调用非静态方法中的父类的属性
- this是非静态方法中的隐藏参数,而super不是
- 构造方法一定会有super的调用,当没有时候编译器会自动增加,而this不会
同时增加一个this和super的内部图示:
顺序
静态代码块与实例代码块及构造方法的初始化顺序
我们之前在上一篇文章讲解了初始化顺序,那时候还没有讲解继承思想,我们再来回忆一下,应该是静态代码块>实例代码块>构造方法,并且代码块只执行一次,也就是只保存一份,当我们构造了两个对象的时候,在实例化第一个对象会执行静态代码块,当实例化第二个代码块的时候,静态代码块不会执行。
接下来我们与继承结合也就是有了父类和子类的静态代码块,构造方法,实例代码块。
那他们的顺序又会是怎样的呢???
我们来用代码实践一下,看结果到底是什么呢???
class Animal { public String name = "花花";//名字 public int age;//年龄 public String furColor;//毛颜色 static { System.out.println("我是父类的静态代码块!!!"); } { System.out.println("我是父类的实例化代码块!!!"); } public void eat() { System.out.println(this.name + "吃饭!!!"); } public void sleep() { System.out.println(this.name + "睡觉!!!"); } public void method() { System.out.println("我是父类的方法!!!"); } public Animal(String name, int age, String furColor) { this.name = name; this.age = age; this.furColor = furColor; System.out.println("我是父类的构造方法!!!"); } } //实现一个猫类 class Cat extends Animal { public String name = "咪咪"; static { System.out.println("我是子类的静态代码块!!!"); } { System.out.println("我是子类的实例化代码块!!!"); } public Cat(String name, int age, String furColor) { super(name, age, furColor); System.out.println("我是子类的构造方法!!!"); } public void mew() { System.out.println(this.name + "喵喵喵"); } public void method() { System.out.println("我是子类的方法"); } public void methodA() { super.method(); } } public class TestDemo { public static void main(String[] args) { Cat cat = new Cat("咪咪",18,"橘黄色"); } }
这里还是用了上面的代码,定义了两个类,Animal类和子类Cat,我们同时定义了静态代码块实例代码块,构造方法。接下来运行一下看看顺序到底是什么呢???
我们这里就可以总结一下了,还是静态代码块优先执行,只不过这里因为在构造子类时候优先构造父类所以是父类的静态的代码块优先于子类的静态代码块,然后是父类的实例和构造方法,最后是子类的实例和构造。
还是一样的我们在创建一个对象看还会跟上次的结果一样么??
总结:
初始化顺序:父类静态代码块>子类静态代码块>父类实例代码块>父类构造方法>子类实例代码块>子类构造方法。如果是第二次实例化对象,那就没有静态代码块。
这个应该很好记忆就是静态代码块优先然后实例代码块然后构造方法,因为构造子类之前要先构造父类所以父类的实例代码块和构造方法要大于子类的实例代码块和构造方法。
详解访问修饰限定符
访问修饰限定符 | public(公共的) | private(私有的) | protected(受保护的) | default(默认权限) |
同一包的同一类 | yes√ | yes√ | yes√ | yes√ |
同一包的不同类 | yes√ | No× | yes√ | yes√ |
不同包的子类 | yes√ | No× | yes√ | No× |
不同报的非子类 | yes√ | No× | No× | No× |
看这个你可能会有些懵,接下来我会详细讲解。前提我们先要知道这些都是访问权限超过它自己的权限就不可以访问了。
public:它是公共的意思是无论在哪里同一个包还是不同的包,同一类还是不同的类都可以进行访问。
pivate :只能访问本类中的成员。
default:默认权限就是成员前面啥也不加只能在同一个包中访问。
这里注意:
protected:可以在同一包中访问,可以再不同包中的子类访问,但是不可以在非子类访问,所以它与继承有很大的关系。
总结访问修饰限定符:
public 可以再同一包中的同一类不同类都能够访问,不同包的子类和非子类也能够访问。
private 只能在本类中使用。
default 也就是默认访问权限,什么也不加,只能在同一个包中访问。
protected 可以在同一包中访问,也可以在不同包的子类访问。
继承方式与组合
继承方式
继承方式多种多样,如果能的话就可以一直继承下去,但在java中继承方式都有哪些呢???
java中的继承方式可以进行单继承,多层继承。
这里还有一个重要的点:那就是虽然可以继承很多,但是最少不要超过3成继承关系。那如果不小心多继承了呢?没关系我们可以利用final修饰,这样就可以停止继承了。
但是java中不支持多继承也就是一个子类同时继承两个父类。
这种是不可以的,如果java中要进行多继承的话那就出现了接口。
组合
如果要说组合的话,我们举个例子,组合嘛那就是一个东西由什么组成呗,比如汽车由发动机引擎,轮胎等等组成,学校由老师,学生,工作人员等等组成,而这些老师,学生,工作人员又有自己的属性。我们把这个学校就称为组合。
那用代码如何表示呢??
class Teacher{//老师类 private String name; private int age; } class Student{//学生类 private String name; private int id; } class School{ //学校由学生和老师组成 private Student student[]; private Teacher teacher[]; }
从这里我们也可以知道继承与组成本质到底是什么样的关系;
我们把继承看做是一种 is a 关系:比如狗是一个动物,猫是一个动物
我们把组合看做是一种 has a的关系:比如学校有 学生,老师,工作人员组成。
那我们到底什么时候用组合,什么时候用继承呢???
两种方式不一定就要用哪个。我们他们两的区别:1.继承是一种is....a的关系,我们都知道程序是先编译后运行,而继承关系就是在编译器编译截断下确定好的。组合关系是在运行时确定的。
所以组合比继承更加简单灵活高效。我们如果非必要情况下优先选择组合。
到此这篇关于Java全面分析面向对象之继承的文章就介绍到这了,更多相关Java继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!