本文已参与「新人创造礼」活动,一起敞开创造之路。
回顾类和目标
回想类和目标的完结,似乎还在昨天…
Java中有三个重要的概念:封装、承继和多态
那么在之前的面临目标中,咱们解说了封装的概念在完成上选用private润饰成员变量/办法,对外躲藏完成的细节,只供给揭露的办法。是揭露的办法和私有的特点之间结合完成的。
1. 承继
1.1 为什么要承继
在Java中运用类对实践世界中的实体来进行描述,类经过实例化之后的产品目标,则能够用来表明实践中的实体,但是实践世界扑朔迷离,事物之间可能会存在一些相关,那么咱们在设计程序的时分就要考虑到承继
比方:狗和猫,它们都是哺乳动物
然后咱们用Java语言来完成这种联系,咱们单独写两个类,一个是Cat,一个是Dog
public class Cat {
String name;
int age;
float weight;
public void eat(){
System.out.println(name+"正在吃猫粮");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
void mew(){
System.out.println(name+"在喵喵喵~");
}
}
public class Dog {
String name;
int age;
float weight;
public void eat(){
System.out.println(name+"正在吃狗粮");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
void Bark(){
System.out.println(name+"在汪汪汪!");
}
}
咱们观察上面两个类就会发现猫和狗的代码中存在大量重复
那么咱们能否将这些共性抽取出来呢?
面向目标思维中提出了承继的概念,专门用来进行共性的抽取,完成代码的复用。
1.2 承继的概念
承继(inheritance)机制:是面向目标程序设计使代码能够复用的最重要的手段,它答应程序员在坚持原有类的特性的基础上进行扩展,增加新功能,以此发生新的类,咱们称之为派生类。承继呈现了面向目标程序设计的层次结构,体现了又简略到杂乱的认知过程。
承继首要解决的问题便是:共性的抽取,完成代码复用
咱们接着上面的例子,猫和狗都是动物,那么咱们就能够将共性的内容进行抽取,然后选用承继的思维来达到代码的复用
上述图中,Dog和Cat都承继了Animal类,其间:Animal类称为父类/基类或许超类,Dog和Cat能够称为Animal的子类/派生类,承继之后,子类能够复用父类中的成员,子类在完成的时分只需求关怀自己新增加的成员就能够了
1.3 承继的语法
父类
public class Animal {
String name;
int age;
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
子类猫
public class Cat extends Animal {
void mew(){
System.out.println(name+"喵喵喵");
}
}
子类狗
public class Dog extends Animal {
void bark(){
System.out.println(name+"汪汪汪");
}
}
用来测试的一个类
public class Test1 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog类中并没有界说任何成员变量,name和age特点必定使从父类Animal中承继下来的
System.out.println(dog.name);
System.out.println(dog.age);
//dog拜访的eat()和sleep()办法也是从Animal中承继下来的
dog.eat();
dog.sleep();
dog.bark();
}
}
运转成果
[留意]:
- 子类会将父类中的成员变量或许成员办法承继到子类中了
- 子类承继父类之后,必需求新增加自己特有的成员,特显出与积累的不同,不然就没有必要再去承继了
1.4 父类成员拜访
在承继体系中,子类将父类中的办法和字段承继下来了,那在子类中能否直接拜访父类中承继下来的成员呢?
1.4.1 子类中拜访父类的成员变量
子类和父类不存在同名成员变量
public class Base {
int a;
int b;
}
public class Derived extends Base {
int c;
public void method(){
a = 10;//拜访从父类中承继下来的a
b = 20;//拜访从父类中承继下来的b
c = 30;//拜访子类自己的c
}
}
子类和父类成员变量同名
public class Base {
int a=10;
int b=101;
int c=100;
}
class Derived extends Base{
int a; //这儿与父类中成员a同名,且类型是相同的
char b; //与父类中成员b同名,但类型不同
public void method(){
a = 100;//拜访父类承继的a,仍是子类自己新增的a?
b = 101;//拜访父类承继的b,仍是子类自己新增的b?
c = 102;//子类中没有c,拜访的必定是从父类中承继下来的c
//d=103;//这儿编译会失利,由于父类和子类都没有界说成员变量d
}
public static void main(String[] args) {
Derived derived = new Derived();
derived.method();
System.out.println(derived.a);
System.out.println(derived.b);
System.out.println(derived.c);
}
}
运转成果如下
咱们能够看到这儿的a是100,b是一个char字符‘e’,c为102
这儿的值都被覆盖了,而咱们再去IDEA中观察
父类中的a,b都失去了光彩,变成了灰色,只有c是还有高亮的,阐明这儿a和b没用到
在子类办法中 或许 经过子类目标拜访成员时:
- 假如拜访的成员变量子类中有,优先拜访自己的成员变量
- 假如拜访的成员变量子类中没有,则拜访父类承继下来的,假如父类中也没有界说,那么就编译报错
- 假如拜访的成员变量与父类中成员变量同名,则优先拜访自己的,即:子类将父类同名成员躲藏了。
成员变量拜访遵循就近原则,自己假如有的话就拜访自己的,假如没有则向父类中找。
比方:你和你爸各自都有一款相同的手机,平常用的话你必定优先用自己的,没电了才回去用爸爸的。
1.4.2 子类中拜访父类的成员办法
成员办法姓名不同
public class Base{
public void methodA(){
System.out.println("Base中methodA()");
}
}
class Derived extends Base {
public void methodB() {
System.out.println("Derived中的methodB办法");
}
public void methodC(){
methodB();//拜访子类自己的methodB()
methodA();//拜访父类承继的methodA()
//methodD();这儿会编译失利,在整个承继体系中没有发现办法methodD()
}
}
总结:成员办法没有同名时,在子类办法中或许经过子类目标拜访办法时,则优先拜访自己的,自己没有的时分再去父类当中找,
成员办法姓名相同
public class Base {
public void methodA(){
System.out.println("Base中的methodA");
}
public void methodB(){
System.out.println("Base中的methodB");
}
}
class Derived extends Base{
public void methodA(int a){
System.out.println("Derived中的method(int)办法");
}
public void methodB(){
System.out.println("Derived中的methodB办法");
}
public void methodC(){
methodA();//这儿没有传参,拜访的是父类中的methodA
methodA(20);//传递了int参数,拜访子类中的methodA(int)
methodB();//直接拜访,则永久拜访到的都是子类中的methodB(),基类的无法拜访到
}
public static void main(String[] args) {
Derived derived = new Derived();
derived.methodC();
}
}
运转成果如下
[阐明]
- 经过子类目标拜访父类与子类中不同名办法时,优先在子类中找,找到则拜访,不然在父类中找,找到则拜访,不然编译会报错
- 经过派生类目标拜访父类与子类同名办法时,假如父类和子类同名办法的参数列表不同,构成重载,依据调用办法中传递的参数来挑选适宜的办法来拜访,假如没有则报错;假如父类和子类同名办法的原型一致,构成重写,那么就只能拜访到子类的,父类的无法经过派生类目标直接拜访到。
问题:假如子类中存在与父类中相同的成员时,那如何在子类中拜访父类相同称号的成员呢?
1.5 super关键字
咱们在进行程序设计的时分,子类和父类可能会存在相同称号的成员,假如要在子类办法中拜访父类同名成员时,咱们应该如何操作呢?直接拜访时无法做到的,但Java中供给了super关键字,该关键字首要作用便是:在子类办法中拜访父类的成员。
public class Base{
int a;
int b;
public void methodA(){
System.out.println("Base中的methodA");
}
public void methodB(){
System.out.println("Base中的methodB");
}
}
class Derived extends Base{
int a;//与父类中成员变量同名且类型相同
char b;//与父类中成员变量同名但类型不同
//与父类中的methodA构成重载
public void methodA(int a){
System.out.println("Derived中的method办法");
}
//与基类中methodB构成重写
public void methodB(){
System.out.println("Derived中的methodB办法");
}
public void methodC(){
//关于同名的成员变量,直接拜访时,拜访的都是子类的
a = 100;//等价于:this.a = 100
b = 101;//等价于:this.b = 101
//留意:this是当时目标的引证
//拜访父类的成员变量的时分,需求凭借super关键字
//super是获取到子类目标中从基类承继下来的父类部分
super.a = 200;
super.b = 201;
//父类和子类中构成了重载的办法,直接能够经过参数列表区分清楚拜访父类仍是子类
methodA();//没有传参,拜访父类中的methodA
methodA(20);//传递int参数,拜访子类中的methodA(int)
//假如在子类中要拜访重写的父类办法,则需求凭借super关键字
methodB();//直接拜访,则永久拜访的是子类中的methodA,基类的无法拜访到
super.methodB();//拜访父类的methodB办法
}
public static void main(String[] args) {
Derived derived = new Derived();
derived.methodC();
}
}
运转成果如下
在子类办法中,假如想要明确拜访父类中成员时,凭借super关键字即可
[留意事项]
- 只能在非静态办法中运用
- 在子类的办法中,拜访父类的成员变量和办法
1.6 子类结构办法
父子,先有父再有子,即:子类目标结构时,需求先调用基类结构办法,然后履行子类的结构办法
public class Base {
public Base(){
System.out.println("父类Base的结构函数");
}
}
class Derived extends Base{
public Derived(){
//super();//留意子类结构办法中默许会调用基类的无参结构办法:super();
//用户没有写super();给父类初始化的时分,编译器会主动增加,
// 而且super();有必要是子类结构办法中的第一条句子
//而且只能呈现一次
System.out.println("子类Derived的结构函数被调用了");
}
public static void main(String[] args) {
Derived d =new Derived();
}
}
运转成果如下
在子类结构办法中,并没有写任何关于父类结构函数的代码,但是在结构子类目标的时分,要先履行基类的结构办法,然后履行子类的结构办法,由于:
子类目标中成员是由两部分组成的,基类承继下来的以及子类新增加的那一部分。
父子必然是先有父再有子,所以结构子类目标的时分,先要调用基类的结构函数,将从基类承继下来的成员结构完整,然后再调用自己的结构函数及那个子类自己的新增加的成员初始化完整
[留意]:
- 假如父类中有午饭或许默许的结构办法,在子类结构器办法第一行默许有隐含的super();调用,即调用父类结构办法
- 假如父类结构办法是带有参数的,此时编译器不会再给子类生成默许的结构办法,此时需求用户在子类中界说一个结构办法,并在其间挑选适宜的父亲结构办法,不然就会编译失利
- 在子类结构办法中,super(…)调用父类结构时,有必要时子类结构函数中的第一句
- super(…)只能在子类结构办法中呈现一次,而且不能和this一起广场
1.7 super和this
super和this都能够在成员办法中用来拜访:成员变量和调用其他的成员办法,都能够作为结构办法的第一条句子,那他们之间有什么区别呢?
[相同点]
- 都是Java中的关键字
- 只能在类的非静态办法中运用,用来拜访非静态成员办法和字段
- 在结构办法中调用时,有必要时结构办法中的额第一条句子,而且不能一起存在
[不同点]
- this是对当时目标的引证,当时目标即调用办法的目标,super相当所以子类目标中从父类承继下来部分成员的引证
- 在非静态成员办法中,this用来拜访本类的办法和特点,super用来拜访父类承继下来的办法和特点
- this是非静态成员办法的一个躲藏参数,而super不是躲藏的参数
- 成员办法中直接拜访本类成员时,编译之后会将this添上去复原,即本类非静态成员都是经过this来拜访的;在子类中假如经过super拜访父类成员,编译之后在字节码层面super实践上是不存在在字节码文件中的
- 在结构办法中:this(…);用于调用本类的结构办法,super(…)用于调用父类的结构办法,两种调用不能一起在结构办法中呈现
- 结构办法中一定会存在super(…)的调用,用户没有写编译器也会主动增加,但是this(…)用户不写的话编译器不会主动增加
这儿对this不太熟悉的小伙伴能够看看我之前写的博客哦this的用法
1.8 再谈初始化
在类和目标的博客中,咱们提到了代码块的概念和用法,比较重要的有实例代码块和静态代码块。传送门代码块
在没有承继联系时分的履行次序咱们用代码再来梳理一遍
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("静态代码块被调用了!");
}
public static void main(String[] args) {
Person p1 = new Person("葛玉礼",19);
System.out.println("======分割线=======");
Person p2 = new Person("小红",19);
}
}
运转成果如下
1.静态代码块先履行,而且只履行一次,在类加载阶段开端履行 2.当有目标创立时,才会履行实例代码块,实例代码块履行完成后再轮到结构办法履行
[承继联系上的履行次序]
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类的静态代码块履行了!");
}
}
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的静态代码块被调用了!");
}
public static void main(String[] args) {
Student student1 = new Student("葛玉礼",19);
System.out.println("=======================");
Student student2 = new Student("Gremmie",18);
}
public static void main1(String[] args) {
Person person1 = new Person("葛玉礼",19);
System.out.println("======分割线=======");
Person person2 = new Person("小红",19);
}
}
main履行成果
main1的履行成果
咱们来剖析一下履行成果,得出如下结论
- 父类静态代码块优先于子类静态代码块履行,而且时最早履行得
- 父类实例代码块和父类结构办法紧接着履行
- 子类得实例代码块和子类得结构办法紧接着顺次进行
- 第二次实例化子类目标时,父类和子类的静态代码块都将不会再履行
1.9 protected关键字
在类和目标章节中,为了完成封装特性,Java中引入了拜访约束符,首要约束:
类或许类中成员能否在类外或许其他包中被拜访
那么父类中不同拜访权限的成员,在子类中的可见性又是什么姿态的呢?
这是我创立的两个包和四个类,B是父类,C和D都是承继B的子类
TestC是用来测试类C一些特点的类
//为演示父类中不同拜访权限在子类中的可见性,为了简略起见,类B中就不设置成员办法了
public class B {
private int a;
protected int b;
public int c;
int d;
}
//ProtectedTest包中
//同一个包中的子类
public class D extends B{
public void method(){
//super.a = 10;这儿编译会报错,父类private成员在相同包的子类中不行见
super.b = 20;//父类中protected成员在相同包子类中能够直接拜访
super.c = 30;//父类中public成员在相同包子类中能够直接拜访
super.d = 40;
}
}
//ProtectedTest2的包中
//不同包中的子类
public class C extends B {
public void method(){
//super.a = 10;这儿编译报错,父类中private成员在不同包子类中不行见
super.b = 20;//父类中protected润饰的成员在不同包承继的子类中能够直接拜访
super.c = 30;//父类中public润饰的成员在不同包子类中能够直接拜访
//super.d = 40//父类中默许拜访权限润饰的成员在不同包子类中不能直接拜访
}
}
//ProtectedTest2的包中
//不同包中的子类
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);
//父类中默许拜访权限润饰的成员在不同包其他类中不能直接拜访
}
//输出成果为:30
}
留意:父类中private成员变量虽然在子类中不能直接拜访,但是它仍旧承继到子类中了
那么咱们什么情况下用到哪一种拜访约束润饰符呢? 咱们期望类要尽量做到“封装”,即躲藏内部完成细节,只暴露出必要的信息给类的调试者 因而咱们在运用的时分应该尽可能的运用比较严厉的拜访权限,例如一个办法假如能运用private,就尽量不要运用public 别的,还有一种简略粗犷的做法:将一切字段设置为private,将一切办法设置为public,不过这种办法属所以对拜访权限的乱用,仍是更期望咱们写代码的时分认真思考,该类供给的字段办法到底该给谁运用(即,是类内部自己用,仍是类的调用者运用,仍是子类运用,都要考虑清楚)
1.10 承继办法
但在Java中,只支持以下几种承继办法
留意:Java中不支持多承继
时间牢记,咱们写的类是实践事物的笼统,而咱们真正在工作中所遇到的项目往往更加杂乱,可能会涉及到一系列杂乱的概念,都需求咱们运用代码来表明,所以咱们在实践项目中写的类也会有很多,类之间的联系也会更加杂乱 但是即使如此,咱们并不期望类之间的承继层次太杂乱, 一般咱们不期望呈现超越三层的承继联系。假如承继层次太多,就需求考虑对代码的整体结构进行修改了 假如向从语法上进行约束承继,不想承继某些办法或是变量,咱们就能够运用final关键字
1.11 final关键字
final关键字能够用来润饰变量、成员办法以及类
1、润饰变量或许字段
表明常量(即不能修改)
final int a = 10;
a = 20;//编译犯错
2、 润饰类
表明此类不能被承继
final public class Animal{
...
}
public class Bird extends Animal {
...
}
//编译犯错
Error:(3,27)java:无法从最终的Animal中进行承继
咱们平常运用的String字符串类,便是用final润饰的,不能被承继
3、润饰办法
表明该办法不能被重写
1.12 承继与组合
这儿我在之前的类联系中提到过相关的概念Java类联系详解
和承继相似,组合也是一种表达类之间联系的办法,也是能够达到代码重用的效果。
组合并没有涉及到特殊的语法(诸如extends这样的关键字),仅仅是将一个类的实例作为别的一个类的字段
承继表明目标之间是is-a的联系,比方狗是动物,猫是动物
**组合表明目标之间是has-a的联系,**比方:轿车有轮胎、发动机、方向盘
//轮胎类
class Tire{
//...
}
//发动机类
class Engine{
//...
}
//车载体系类
class VehicleSystem{
//...
}
public class Car {
private Tire tire;//能够复用轮胎中的特点和办法
private Engine engine;//能够复用发动机中的特点和办法
private VehicleSystem vs;////能够复用车载体系中的特点和办法
//...
}
//奔跑是轿车
class Benz extends Car{
//将轿车中包含的:轮胎,发动机、车载体系悉数承继下来
}
组合和承继都能够完成代码的复用,应该运用承继仍是组合,需求依据实践应用场景来挑选,一般建议:能用组合尽量用组合
承继到这儿就结束啦,下一篇为咱们解说多态
期望能帮到你
感谢阅读!