继承
1. 什么是继承?
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
2. 继承的语法
//父类,基类,超类
class Animal{
public String name;
public int age;
public void eat(String name){
System.out.println(name+"正在吃饭!");
}
public void sleep(String name){
System.out.println(name+"正在睡觉!");
}
}
//子类,派生类
class Cat extends Animal{
public void mew(String name){
System.out.println(name+"喵喵喵~~~~");
}
}
//子类,派生类
class Dog extends Animal{
public void bark(String name){
System.out.println(name+"汪汪汪~~~");
}
}
public class TestDemo {
public static void main(String[] args) {
Cat cat =new Cat();
cat.eat("cat1");
cat.sleep("cat2");
cat.mew("cat3");
}
}
3.当实例化一个含有继承对象时
4. 当父类和子类成员(成员变量和成员方法)重名时
class A{
int a=10;
int b=20;
}
class B extends A{
int a=40;
int d=50;
public void method(){
System.out.println(a);
System.out.println(b);
System.out.println(d);
System.out.println("=============");
System.out.println(super.a);
System.out.println("=============");
System.out.println(this.a);
}
}
public class TestDemo2 {
public static void main(String[] args) {
B flg = new B();
flg.method();
}
}
总结:
- 当子类和父类中得成员重名时,优先访问子类自己的成员
- 要访问父类的 a ,就得用 super.a
5. 对重载的进一步理解
- 在同一个类中时,可以构成重载
- 在不同类中时,要构成重载,类之间就得有继承关系
6. super 关键字总结
super.date ——————访问父类成员变量
super.fun() ——————访问父类成员方法
super() ——————访问父类构造方法
注意事项:
- 不能在静态方法中使用( this 也是 )
- 在子类方法中,访问父类的成员变量和方法
7. 子类构造方法
父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法
当子类继承父类之后,一定要先创建父类,在创建子类
class Animal{
private String name;
public void eat(){
System.out.println(this.name+"正在吃");
}
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Cat extends Animal{
public String hair;
public Cat(String name,String hair){
super(name);//显示调用父类的构造方法,来初始化子类从父类继承过来的东西
this.hair="black";
}
public void mew(){
System.out.println(this.getName()+"正在叫");
}
}
public class Test {
public static void main(String[] args) {
Cat cat1=new Cat("咪咪","black");
cat1.mew();
cat1.eat();
}
}
之前没有报错时因为,编译器自动添加了一对不带参数的构造方法
注意:
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法
- 如果父类构造方法是带有参数的,此时编译器不会再给子类生成默认的构造方法,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
- 在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
- super(…)只能在子类构造方法中出现一次,并且不能和this同时出现
8.super 和 this
相同之处:
- 不能再静态方法中使用
- 在构造方法调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同之处
- this是当前对象的引用,当前对象即调用实例方法的对象,super 相当于是子类对象从父类继承下来部分成员的引用
- 在非静态方法中,this 用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- this 是非静态成员方法的一个隐藏参数,super 不是隐藏参数
- 成员方法中直接访问本类成员时,编译之后会将this还原,即本类非静态成员都是通过this来访问的;在子类中如果通过super访问父类成员,编译之后在字节码层面super实际是不存在的(通过字节码文件可以验证)
- 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
- 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有
9. 有继承关系时,各个代码块的执行顺序
class Animal{
private String name;
static{
System.out.println("Animal的静态代码块");
}
{
System.out.println("Animal的实例代码块");
}
public Animal(String name) {
this.name = name;
System.out.println("Animal的构造方法");
}
}
class Cat extends Animal{
public String hair;
static{
System.out.println("Cat的静态代码块");
}
{
System.out.println("Cat的实例代码块");
}
public Cat(String name,String hair){
super(name);//显示调用父类的构造方法,来初始化子类从父类继承过来的东西
this.hair="black";
System.out.println("Cat的构造方法");
}
}
public class Test {
public static void main(String[] args) {
Cat cat1=new Cat("咪咪","black");
System.out.println();
Cat cat2=new Cat("咪咪","black");
}
}
总结:
通过分析执行结果,得出以下结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
10. protected 关键字
即 :在不同包中,有继承关系时,子类能够访问父类中用 proteced 修饰的成员,故又叫做继承权限
注意:父类中private成员变量随时在子类中不能直接访问,但是也继承到子类中了
11. 继承方式
注意:Java中不支持多继承。
时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.
但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
如果想从语法上进行限制继承, 就可以使用 final 关键字
12. final 关键字
fianl 可以用来修饰变量,成员方法以及类
1.修饰变量或字段,表示常量
final int a=10;
a=20;//编译出错
2.修饰类,表示此类不能被继承
final public calss Animal{
}
public calss Cat extends Animal{
}//编译出错
13.继承和组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车
// 轮胎类
class Tire{
// ...
}
// 发动机类
class Engine{
// ...
}
// 车载系统类
class VehicleSystem{
// ...
}
class Car{
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法
// ...
}
// 奔驰是汽车
class Benz extend Car{
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}
多态
1. 多态的概念
多态:通俗点说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生不同的状态
2. 多态的实现条件
在java中要实现多态,必须要满足如下几个条件,缺一不可:
- 必须在继承体系下
- 子类必须要对父类中方法进行重写
- 通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
3. 向上转型和向下转型
3.1 向上转型——父类引用 引用子类对象
Animal animal=new Cat();
animal.eat();
注意:此时的 animal 只能访问父类自己特有的成员
三种常见的向上转型:
- 直接赋值
Animal animal=new Cat();
animal.eat();
- 作为方法的参数
public static void func(Animal animal){
}
public static void main(String[] args) {
func(new Cat());
}
- 作为方法的返回值
public static Animal func1(){
return new Cat();
}
public static void main(String[] args) {
Animal animal=func1();
}
3.2 向下转型——子类引用 引用父类的对象
public class TestAnimal {
public static void main(String[] args) {
Cat cat = new Cat("元宝",2);
Dog dog = new Dog("小七", 1);
// 向上转型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
// 编译失败,编译时编译器将animal当成Animal对象处理
// 而Animal类中没有bark方法,因此编译失败
// animal.bark();
// 向上转型
// 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
cat = (Cat)animal;
cat.mew();
// animal本来指向的就是狗,因此将animal还原为狗也是安全的
dog = (Dog)animal;
dog.bark();
}
}
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常,Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换
public class TestAnimal {
public static void main(String[] args) {
Cat cat = new Cat("元宝",2);
Dog dog = new Dog("小七", 1);
// 向上转型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
if(animal instanceof Cat){
cat = (Cat)animal;
cat.mew();
} if(animal instanceof Dog){
dog = (Dog)animal;
dog.bark();
}
}
}
4.方法重写
4.1 方法重写的特点
- 方法名相同
- 方法的返回值相同
- 方法的参数列表相同
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(this.name+"吃饭");
}
}
class Cat extends Animal{
public void eat(){ //方法重写
System.out.println(this.name+"吃猫粮");
}
public void mew(){
System.out.println(this.name+"正在叫");
}
}
public class TestDemo {
public static void main(String[] args) {
Animal animal=new Cat();
animal.eat();
}
}
在上述代码中,方法重写的部分:
运行结果:
即运行的是重写之后的方法
4.2 为什么会运行重写之后的方法呢?
编译的之后还是父类的方法,但是运行时会执行子类的方法——— 即运行时绑定,也叫动态绑定
4.3 重写中的注意点
- 静态方法不能进行重写
- private 修饰的方法不能进行重写
- 子类的访问修饰符的权限一定得大于等于父类的权限,如:父类为protected,则子类为 protected 或者 public
- 被 final 修饰的方法不能进行重写
4.4 重写和重载的区别
5. 多态思想
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(this.name+"吃饭");
}
}
class Cat extends Animal{
public void eat(){
System.out.println(this.name+"吃猫粮");
}
public void mew(){
System.out.println(this.name+"正在叫");
}
}
class Dog extends Animal{
public void eat(){
System.out.println(this.name+"吃狗粮");
}
public void bark(){
System.out.println(this.name+"正在叫");
}
}
public class TestDemo {
public static void func(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
func(dog);
func(cat);
}
}
运行结果:
思想:同一个方法,因为调用这个方法的对象不同,就会又不同的执行结果
6.多态的优点
6.1 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
什么叫 “圈复杂度” ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”.如果一个方法的圈复杂度太高, 就需要考虑重构.
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10 .
例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:
多态的优点:
public static void drawShapes() {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Flower flower = new Flower();
String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
for (String shape : shapes) {
if (shape.equals("cycle")) {
cycle.draw();
} else if (shape.equals("rect")) {
rect.draw();
} else if (shape.equals("flower")) {
flower.draw();
}
}
}
如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单.
public static void drawShapes() {
// 我们创建了一个 Shape 对象的数组.
Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
new Rect(), new Flower()};
for (Shape shape : shapes) {
shape.draw();
}
}
6.2 可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}
对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了, 改动成本很低.
而对于不用多态的情况, 就要把 drawShapes 中的 if - else 进行一定的修改, 改动成本更高
多态缺陷:代码的运行效率降低
6.3 避免在构造方法中调用重写的方法
class B {
public B() {
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class TestDemo {
public static void main(String[] args) {
D d = new D();
}
}
运行结果:
- 构造 D 对象的同时, 会调用 B 的构造方法.
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0
结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.