面向对象:继承、组合和多态

面向对象三大特性之继承

继承:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类(子类)。
继承主要解决的问题是:共性的抽取,实现代码复用。

继承的基本语法

在Java中如果要表示类之间的继承关系,需要借助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 + "喵喵喵~~~");
    }
}

// TestExtend.java
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. 子类和父类不存在同名成员变量
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
    }
}

  1. 子类和父类成员变量同名
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
        b = 101; // 访问子类自己新增的b
        c = 102; // 子类没有c,访问从父类继承下来的c
        // d = 103; // 编译失败,因为父类和子类都没有定义成员变量d
    }
}

在子类方法中 或者 通过子类对象访问成员时:

  • 如果访问的成员变量子类中有,优先访问自己的成员变量。
  • 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
  • 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
    成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找

子类中访问父类的成员方法

  1. 父类和子类的成员方法名字不同
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()
    }
}

  1. 父类和子类的成员方法名字相同
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{
    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(),基类的无法访问到
    }
}

  • 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问;否则在父类中找,找到则访问,如果父类中也没有则编译报错。
  • 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问;如果没有则报错。
  • 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表、返回值都相同,则遵循就近原则,直接访问子类中的方法,不会访问父类中的方法。
    在java的继承关系中,新的类称为子类,也叫派生类。

继承方式

在这里插入图片描述
注意:Java中不支持多继承,且一般我们不希望出现超过三层的继承关系。

final 关键字

final关键可以用来修饰变量、成员方法以及类。

  1. 修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20; // 编译出错,这里a是常量,不可以被修改

  1. 修饰类:表示此类不能被继承
final public class Animal {
...
}

public class Bird extends Animal {
...
} 

// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继

3.修饰方法:final修饰的方法叫做密封方法,不能被重写。

面向对象三大特征之一多态

1.多态的概念

多态:当传递不同类对象时,同一函数,产生不同不同效果。因为调用了不同类中的方法。

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态;同一件事情,发生在不同对象身上,就会产生不同的结果。

2.重写

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
(private:被private修饰的属性和方法,只能在当前类的内部可见,出了当前类的内部,也就是出了 {} ,对外部就完全隐藏了。)
【方法重写的规则】

  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
  • 被重写的方法返回值类型可以不同,但是必须是具有父子关系的
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected
  • 父类被static、private、final修饰的方法,构造方法都不能被重写。
  • 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写

【重写和重载的区别】

方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现
【重写的设计原则】

  • 对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。

  • 静态绑定: 也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。

  • 动态绑定: 也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。 这也是多态的特征。

3.向上转型和向下转型

向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用
语法格式:父类类型 对象名 = new 子类类型( )

//animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。
Animal animal = new Cat("元宝",2);

【使用场景】

  • 直接赋值
  • 方法传参
  • 方法返回
public class TestAnimal {
// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
    public static void eatFood(Animal a){
        a.eat();
    }

    // 3. 作返回值:返回任意子类对象
    public static Animal buyAnimal(String var){
        if("狗" == var){
            return new Dog("狗狗",1);
        }else if("猫" == var){
            return new Cat("猫猫", 1);
        }else{
            return null;
        }
    }
    public static void main(String[] args) {
        Animal cat = new Cat("元宝",2);
    // 1. 直接赋值:子类对象赋值给父类对象
        Dog dog = new Dog("小七", 1);
        eatFood(cat);
        eatFood(dog);
        Animal animal = buyAnimal("狗");
        animal.eat();
        animal = buyAnimal("猫");
        animal.eat();
    }
}

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 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实际指向的是狗
        // 现在要强制还原为猫,无法正常还原,运行时抛出:ClstException
        cat = (Cat)animal;
        cat.mew();
        
        // animal本来指向的就是狗,因此将animal还原为狗也是安全的
        dog = (Dog)animal;
        dog.bark();
    }
}

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。

4.多态实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:

继承(废话)
重写:必须的,不同对象的同一动作(方法、函数)要产生不同效果
利用父类引用(用父类做参数)
参数:提前告诉函数,要接收的变量类型
多态和继承的关系(联系):

  • 多态以继承为继承。
    实现多态的实例:
public class Animal {
    String name;
    int age;
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(name + "吃饭");
    }
}
public class Cat extends Animal{
    public Cat(String name, int age){
        super(name, age);
    } 
    @Override
    public void eat(){
        System.out.println(name+"吃鱼~~~");
    }
}
public class Dog extends Animal {
    public Dog(String name, int age){
        super(name, age);
    } 
    @Override
    public void eat(){
        System.out.println(name+"吃骨头~~~");
    }
}

///分割线//

public class TestAnimal {
// 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
// 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
// 注意:此处的形参类型必须时父类类型才可以
    public static void eat(Animal a){
        a.eat();
    }
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
        eat(cat)//元宝吃鱼~~~
        eat(dog)//小七吃骨头~~~
    }
} 

5.多态的优缺点

优点:

  • 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
    圈复杂度是一种描述一段代码复杂程度的方式,计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”;
  • 可扩展能力更强
    如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.
    缺点:
  • 代码的运行效率降低
  • 属性没有多态性,当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性 。
  • 构造方法没有多态性 。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值