Java继承:不只是extends,你还需要知道这些 1继承1.1 为什么需要继承java中使用类对现实世界中的事物进行描述类经过实例化之后产生对象但是如果两个对象之间存在相同的属性我们再去写一遍就会很麻烦这时候就需要继承起作用了。以猫和狗为例// Dog.java 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 汪汪汪~~~); } } // Cat.Java 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 喵喵喵~~~); } }从上我们可以看出成员变量和成员方法都有重复的继承专门用于共性抽取实现代码的复用1.2 继承的概念继承inheritance机制是面向对象程序设计使代码可以复用的重要的手段它允许程序员在保持原有类特性的基础上进行拓展增加新功能这样产生新的类称为派生类。继承的主要功能是共性提取实现代码的复用继承之后子类可以复用父类中的成员子类在实现时只需要关心自己新增的成员即可。除了发射的豌豆个数不同其他的属性都继承父类这样就不用在子类对象中写成员变量了直接使用父类的成员变量实现了代码的复用。1.3 继承的语法使用extends关键字修饰符 class ⼦类 extends ⽗类 { // ... }改进猫狗的例子// Animal.java public class Animal{ String name; int age; public void eat(){ System.out.println(name 正在吃饭); } public void sleep(){ System.out.println(name 正在睡觉); } } // Dog.java public class Dog extends Animal{ void bark(){ System.out.println(name 汪汪汪~~~); } } // Cat.Java public class Cat extends Animal{ void mew(){ System.out.println(name 喵喵喵~~~); } }使用的话直接用就行了public class TestExtend { 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; int b; int c; } public 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 } }在子类方法中或者通过子类对象访问成员时如果访问的成员变量子类有优先访问自己的成员变量如果访问的成员变量子类没有则访问父类继承下来的如果父类也没有定义则编译器报错如果访问的成员变量与父类中成员变量同名则优先访问自己的成员变量访问遵循就近原则自己有优先自己的如果没有则在父类中找1.4.2 子类中访问父类的成员方法成员方法名字不同public class Base { public void methodA(){ System.out.println(Base中的methodA()); } } public 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()); } } public class Derived extends Base{ public void methodA() { System.out.println(Derived中的method()⽅法); } public void methodB(){ System.out.println(Derived中的methodB()⽅法); } public void methodC(){ methodA(); // 优先访问⼦类中的methodA() methodB(); // 直接访问则永远访问到的都是⼦类中的methodB()基类的⽆法访问到 } }通过子类对象访问父类与子类中不同名方法时优先在子类中找找到则访问否则在父类中找找到就访问否则编译报错通过子类对象访问父类与子类同名方法时如果父类和子类同名方法的参数列表不同重载根据调用方法传递的参数选择合适的方法访问如果没有就报错。如果子类中存在与父类中相同的成员时如何在子类中访问父类相同名称的成员呢1.5 super 关键字super关键字的主要作用是在子类方法中访问父类的成员public class Base { int a; int b; public void methodA(){ System.out.println(Base中的methodA()); } public void methodB(){ System.out.println(Base中的methodB()); } } public class Derived extends Base{ int a; // 与⽗类中成员变量同名且类型相同 // 与⽗类中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() } }super可以访问父类的成员变量super可以访问父类的成员方法super还可以调用父类当中指定的构造方法注意只能在非静态方法中使用在子类方法中访问父类的成员变量和方法1.6 子类构造方法public class Base { public Base(int a){ System.out.println(Base() a); } } public class Derived extends Base{ public Derived(){ System.out.println(Derived()); } } public class Test { public static void main(String[] args) { Derived d new Derived(); } }这段代码会编译报错。当我们在构造子类对象的时候需要先调用基类构造方法然后执行子类的构造方法。所以我们需要在子类对象构造完成之前先帮父类对其中的成员进行初始化public class Base { public Base(int a){ System.out.println(Base() a); } } public class Derived extends Base{ public Derived(){ super(10);//此时通过super(参数)的形式 System.out.println(Derived()); } } public class Test { public static void main(String[] args) { Derived d new Derived(); } } //打印结果 Base()10 Derived()注意通过super()可以调用父类指定的构造方法super()的形式只能出现在子类的构造方法当中且必须在第一行思考下面代码为什么没有报错呢public class Base { public Base(){ System.out.println(Base()); } } public class Derived extends Base{ public Derived(){ // super(); // 注意⼦类构造⽅法中默认会调⽤基类的⽆参构造⽅法super(), // ⽤⼾没有写时,默认会添加⼀个super()⽽且super()必须是⼦类构造⽅法中第⼀条语句 // 并且只能出现⼀次 System.out.println(Derived()); } } public class Test { public static void main(String[] args) { Derived d new Derived(); } } //打印结果 //Base() //Derived()当父类的构造方法是不带参数的构造方法且只有一个的情况下默认会添加一个super().总结在子类构造方法中并没有写任何关于基类构造的代码但是在构造子类对象时先执行基类的构造方法然后执行子类的构造方法因为子类对象中的成员是由两部分组成的基类继承下来以及子类新增的部分。构造比作出生的话肯定先有父类再有子类所以在构造子类对象的时候先要调用基类的构造方法将从基类继承下来的成员构造完整然后再调用子类自己的构造方法将子类自己增加的成员初始化完整。若父类显式定义无参或者默认的构造方法在子类构造方法第一行默认有隐含的super()调用子类构造方法中super()调用父类构造是必须是子类构造函数的第一条super()只能在子类构造方法中出现一次并且不能和this同时出现1.7 super和this相同都是java的关键字只能在类的非静态方法中使用用来访问非静态成员方法和字段在构造方法中调用时必须是构造方法中的第一条并且不能同时存在不同this是当前对象的引用当前对象即调用实例方法的对象super相当于是子类对象中从父类继承下来部分成员的引用在非静态成员方法中this用来访问本类的方法和属性super用来访问父类继承下来的方法和属性在构造方法中this(...)用来调用本类构造方法super(...)用于调用父类构造方法两种不能同时出现构造方法中一定存在super(...)的调用用户没有写编译器也会自己加但是this(...)用户不写则没有1.8 继承关系上代码块的初始化顺序先看一下实例代码块和静态代码块的执行顺序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 class TestDemo { public static void main(String[] args) { Person person1 new Person(cyy,10); System.out.println(); Person person2 new Person(cy,20); } }静态代码块先执行并且只执行一次在类加载阶段执行当有对象创建时才会执行实例代码块实例代码块执行完成后最后构造方法执行在继承关系上的执行顺序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 class TestDemo4 { public static void main(String[] args) { Student student1 new Student(张三,19); System.out.println(); Student student2 new Student(cyy,20); } }父类静态代码块优先于子类静态代码块执行且最早执行父类实例代码块和父类构造方法紧接着执行子类的实例代码块和子类构造方法紧接着再执行第二次实例化子类对象时父类和子类的静态代码块不会再执行1.9 protected 关键字编号范围私有(private)默认(default)保护(protected)公有(public)1同一包中的同一类√√√√2同一包中的不同类√√√3不同包中的子类√√4不同包中的非子类√如果被protected 关键字修饰该类当中成员变量成员方法等表示要么只能在同一个包中的类中进行访问要么在不同包中只能通过继承关系上的子类对象来访问。package net.csdn.mp1; public class Base { protected int b 100; }package net.csdn.mp2; import net.csdn.mp1.Base; public class Derived extends Base { public static void main(String[] args) { Derived derived new Derived (); System.out.println(derived.b); } }上面是不同包通过继承关系的子类对象访问。package net.csdn.mp2; import net.csdn.mp1.Base; class C extends Base { public static void main(String[] args) { C c new C(); System.out.println(c.b); } } public class Derived extends Base { public static void main(String[] args) { Derived derived new Derived (); System.out.println(derived.b); C c new C(); System.out.println(c.b);//该处出现错误 } }虽然 Derived 类继承自 Bese 类但它不能访问其他 Base 类子类C类实例的 protected 成员public 关键字我们希望类要尽量做到“封装”即隐藏内部实现细节只暴露出必要的信息给类的调用者。因此我们在使用的时候应该尽可能的使用比较严格的访问权限例如如果一个方法能用 private就不用public另外,还有⼀种简单粗暴的做法:将所有的字段设为private,将所有的⽅法设为public.不过这种⽅式属于是对访问权限的滥⽤,还是更希望同学们能写代码的时候认真思考,该类提供的字段⽅法到底给谁使⽤(是类内部⾃⼰⽤,还是类的调⽤者使⽤,还是⼦类使⽤)1.10 继承方式java支持以下几种继承方式一般我们不希望出现超过三层的继承关系如果继承层数太多就要考虑对代码进行重构了如果想从语法上进行限制继承就可以使用final关键字1.11 final关键字修饰变量或字段表示常量不能修改final int a 10; a 20; // 编译出错修饰类表示不能继承final public class Animal { ... } public class Bird extends Animal { ... }String就是被final修饰的不能被继承修饰方法表示方法不能被重写1.12 组合和继承以汽车为例// 轮胎类 class Tire{ // ... } // 发动机类 class Engine{ // ... } // ⻋载系统类 class VehicleSystem{ // ... } class Car{ private Tire tire; // 可以复⽤轮胎中的属性和⽅法 private Engine engine; // 可以复⽤发动机中的属性和⽅法 private VehicleSystem vs; // 可以复⽤⻋载系统中的属性和⽅法 // ... } // 奔驰汽车 class Benz extend Car{ // 将汽⻋中包含的轮胎、发送机、⻋载系统全部继承下来 }组合和继承都可以实现代码复⽤应该使⽤继承还是组合需要根据应⽤场景来选择⼀般建议能⽤组合尽量⽤组合