Java面向对象三大特性(封装继承多态)

Posted 爱敲代码的三毛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面向对象三大特性(封装继承多态)相关的知识,希望对你有一定的参考价值。


前言

OOP 语言:也就是面向对象编程。
面向对象的语言有三大特性:封装、继承、多态。三大特性是面向对象编程的核心。下面就来介绍一下面向对象的三大特性。
如果想了解面向对象可以看一下这一篇博客类和对象


一、封装

1. 封装的概念

在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者

封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的,
把属性和动作隐藏,只提供相应的方法来调用即可,只要知道如何使用类就行了.
当类的实现者把内部的逻辑发生变化时,类的调用者根本不用因此而修改方法。
这样就降低了类使用者的学习和使用成本,从而降低了复杂程度,也保证了代码的安全性

2. private实现封装

private 访问限制修饰符,被它修饰的字段或者方法就只能在当前类中使用。

如果我们直接使用public修饰字段

class People{
    public String name;
    public int age;
}
public class Test {
    public static void main(String[] args) {
        People people = new People();
        people.name = "小明";
        people.age = 18;
        System.out.println("姓名:"+people.name+" 年龄:"+people.age);
    }
}

运行结果

在这里插入图片描述
这样的代码必须要了解 People 这个类的才能类内部的实现, 才能够使用这个类. 学习成本较高。
而且一旦类的实现者把name这两个字段修改成myName,外部就无法调用了,那么类的调用者就需要大量的修改代码,维护成本就非常高了。

使用 private 封装属性, 并提供 public 方法供类的调用者使用.

class People{
    private String name;
    private int age;
    
    public void show() {
        System.out.println("姓名:"+name+" 年龄:"+age);
    }
}
public class Test {
    public static void main(String[] args) {
        People people = new People();
        people.show();
    }
}

此时字段已经使用 private 来修饰. 类的调用者(main方法中)不能直接使用. 而需要借助 show 方法.
此时类的使用者就不必了解 Person 类的实现细节. 同时如果类的实现者修改了字段的名字,
类的调用者不需要做出任何修改(类的调用者根本访问不到 name, age这样的字段).

那么问题来了,我们前面说过 private 修饰的字段只能在当前类中使用。也就是说现在我们访问不到了name和age了。这就得用到 ger 和 set 方法了

3. getter和setter方法

当我们用private修饰字段后,这个字段就无法被直接使用了。
这个时候就用到了 get 和 set 方法了

代码示例:

class People{
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void show() {
        System.out.println("姓名:"+name+" 年龄:"+age);
    }
}
public class Test {
    public static void main(String[] args) {
        People people = new People();
        people.setName("小明");
        people.setAge(18);
        people.show();
    }
}

运行结果

在这里插入图片描述

getName 即为 getter 方法, 表示获取这个成员的值.
setName 即为 setter 方法, 表示设置这个成员的值
不是所有的字段都一定要提供 setter / getter 方法, 而是要根据实际情况决定提供哪种方法.

在 IDEA中快速生成 get 和 set 方法
Alt+Insert 键或者点鼠标右建找到Generate
在这里插入图片描述

4.封装的好处

1.提高了数据的安全性
别人不能够通过 变量名来修改某个私有的成员属性
2.操作简单
封装后,类的调用者在使用的时候,只需调用方法即可。
3.隐藏了实现
实现过程对类的调用者是不可见的,类的调用者只需调用方法即可,不知道具体实现。

二、继承

1. 继承的概念

继承的意义:代码的重复使用

代码中创建的类, 主要是为了抽象现实中的一些事物(包含属性和方法).
有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联。

来看一段代码:

class Animal {
    public String name;

    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
    }
}
class Dog {
    public String name;
        
    public void eat() {
        System.out.println(this.name+"吃东西");
    }
}
class Bird {
    public String name;
    
    public void eat() {
        System.out.println(this.name+"吃东西");
    }
    
    public void fly() {
        System.out.println(this.name+"起飞");
    }
}

这个代码我们发现其中存在了大量的冗余代码.
仔细分析, 我们发现 Animal 和 Cat 以及 Bird 这几个类中存在一定的关联关系。

  • 这三个类都有相同的eat方法
  • 这三个类都有一个name属性
  • 从逻辑上讲, Cat 和 Bird 都是一种 Animal (is - a语义).

此时我们就可以让 Cat 和 Bird 分别继承 Animal 类, 来达到代码重用的效果

2. extends实现继承

基本语法

class 子类 extends 父类 {

} 
  • 使用 extends 指定父类.
  • Java不同于C++/Python,JAVA中一个子类只能继承一个父类(单继承)
  • 子类会继承父类的所有public 的字段和方法.
  • 对于父类的 private 的字段和方法, 子类中是无法访问的.
  • 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用

我们再把上面的代码修改一下,用extends关键字实现继承,此时我们让 Cat 和 Bird 继承自 Animal 类, 那么 Cat 在定义的时候就不必再写 name 字段和 eat 方法。

class Animal {
    public String name;


    public void eat() {
        System.out.println(this.name + " 正在吃");
    }
}
class Dog extends Animal {

}
class Bird extends Animal{

    public void fly() {
        System.out.println(this.name+"起飞");
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "金毛";
        dog.eat();
    }
}

运行结果

在这里插入图片描述

此时, Animal 这样被继承的类, 我们称为 父类 , 基类超类, 对于像 Cat 和 Bird 这样的类, 我们称为 子类,或者派生类
和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到代码重用的效果

此时我们来简单看一下内存中的存储
在这里插入图片描述

3. super 关键字

我们在类和对象讲过当一个类没有写构造方法的时候,系统默认会有一个没有参数且没有任何内容的构造方法。

来看一个列子:

在这里插入图片描述
当我们自己给父类写了一个构造方法后,两个子类都报错了,是什么原因呢?

因为当子类继承父类后,在构造子类之前,就必须先帮父类进行构造。(重点)

就用到了关键字super
super 表示获取到父类实例的引用.,和this类似共有三种用法

1.super.父类的成员变量
2.super.父类的成员方法
3.super():调用父类的构造方法

注意:super 和 this一样不能在静态方法里使用 !

class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat() {
        System.out.println(this.name + " 正在吃");
    }
}
class Bird extends Animal{
    public String name = "乌鸦";
    public Bird(String name) {
        super(name);// 使用 super 调用父类的构造方法
    }
    public void fly() {
        System.out.println(super.name);//调用父类的成员变量
        super.eat();//调用父类的构造方法
        System.out.println(this.name+"起飞");//调用自己的成员变量
    }
}
public class Test {
    public static void main(String[] args) {
        Bird bird = new Bird("麻雀");
        bird.fly();
    }
}

运行结果

在这里插入图片描述
当子类和父类有了同名的成员变量的内存结够图

在这里插入图片描述
注意:在用super关键字在子类的构造方法里帮父类构造的时候一定要在第一行
在这里插入图片描述

Object

如果一个类没有指定父类的时候,默认继承的就是Object类。

class Animal {//默认继承Object类
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat() {
        System.out.println(this.name + " 正在吃");
    }
}

4.访问权限

(1) private

当我们把父类的访问权限改成 private 的时候,子类就无法访问了。但并不是没有继承,而是无法直接访问了,因为被 private 修饰的只能在当前类里使用!

在这里插入图片描述
private是可以修饰构造方法的,在类外不能实例化对象,要提供一个静态方法来帮助构造一个对象。这样的操作在以后的单例设计模式会用到。

在这里插入图片描述

(2) protected

刚才我们发现, 如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.两全其美的办法就是 protected 关键字

  • 对于类的调用者来说, protected 修饰的字段和方法是不能访问的
  • 对于类的 子类 和 同一个包的其他类 来说, protected修饰的字段和方法是可以访问的

在这里插入图片描述

在这里插入图片描述

(3) default

当一个类什么修饰符都不加的时候就是默认的访问权限,也就是包访问权限default .相当于这个类只能在当前包中使用。

class Cat extends Animal{//没有任何访问权限修饰符
    Cat(String name) {
        super(name);
        this.name = name;
    }
}

(4) 小结

总结: Java 中对于字段和方法共有四种访问权限

1.private: 类内部能访问, 类外部不能访问
2.默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
3.protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
4.public : 类内部和类的调用者都能访问

在这里插入图片描述

5.更复杂的继承

这样的继承方式称为多层继承, 即子类还可以进一步的再派生出新的子类.
虽然语法上可以继承很多层,但不建议超过三层,超过三层的话就用final修饰最后一层,如果再往下继承的话编译器就会报错。

class Animal {
    public String name;
    public void eat() {
        System.out.println(this.name + " 正在吃");
    }
}
class B extends Animal {

}
class C extends B {

}
final class D extends C {

}

6.final 关键字

1.final修饰变量(常量,这个常量不能再被修改)
2.final修饰类,密封类:当前类不能再继承
3.final修饰方法,密封方法:该方法不能进行重写

三、组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.
例如表示一个学校:

public class Student {

}
public class Teacher {

}
public class School {
 public Student[] students;
 public Teacher[] teachers;
} 

组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.
这是我们设计类的一种常用方式之一.

组合表示 has - a 语义 在刚才的例子中, 我们可以理解成一个学校中 “包含” 若干学生和教师.

继承表示 is - a 语义 在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物

一定要理解组合和继承的区别

四、多态

1. 向上转型

(1) 概念

向上转型就是把一个子类引用给一个父类引用,也就是父类引用 引用了子类的对象

class Animal {
    public String name;
    public void eat() {
        System.out.println(this.name + " 正在吃");
    }
}
class Cat extends Animal {
    
}
public class Test extends TestDemo {

    public static void main(String[] args) {
        //父类引用 引用了 子类引用所引用的对象
        Cat cat = new Cat();
        Animal animal = cat;//向上转型
    }
}

我们把一个 Animal类型引用了它的子类Cat这就是向上转型

(2) 向上转型发生的几种时机

1.直接赋值

public static void main(String[] args) {
        //父类引用 引用了 子类引用所引用的对象
        Animal animal = new Cat();;//向上转型
}

2.方法传参

我们这里把一个 Cat的子类 传给 一个Animal类型的父类,这里也是能发生向上转型的

public class Test extends TestDemo {

    public static void func(Animal animal) {
        
    }
    public static void main(String[] args) {
        //父类引用 引用了 子类引用所引用的对象
        Cat cat = new Cat();
        func(cat);
    }
}

3.方法返回

这里func方法的返回类型是 Animal 但返回的确是一个Cat类型,这里也是发生了向上转型

public class Test extends TestDemo {
    public static Animal func() {
        Cat cat = new Cat();
        return cat;
    }
    public static void main(String[] args) {
        Animal animal = func();
    }
}

(3) 注意事项

注意:当发生向上转型的时候,通过父类引用只能调用父类的自己的方法和成员变量

在这里插入图片描述

2.向下转型

(1) 概念

知道了向上转型,那么向下转型就好理解了。向下转型就是父类对象转成子类对象。

我们把一个父类引用 Animal类型的引用 给了一个 Bird类型 的引用,这就是向下转型

注意:向下转型的时候一定要进行强制类型转换

class Animal {
    public String name;
    public void eat() {
        System.out.println(this.name + " 正在吃");
    }
}
class Cat extends Animal {

}
class Bird extends Animal {
    public int age;
    public void fly() {
        System.out.println(this.name+"起飞");
    }
}
public class Test extends TestDemo {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Bird bird = (Bird) animal;//必须进行强制类型转换
    }
}

(2) instanceof关键字

向下转型我们一般不建议使用,因为它非常不安全。

来看一段代码:

在这里插入图片描述
运行结果:

在这里插入图片描述
运行之前并没有报出,但运行之后这里报出了一个类型转换异常。

因为这里Animal本身引用的就是一个Cat的对象,然后把它强转为Bird,因为Cat里根本没有fly()方法,就相当于你让一只猫去飞,它能非起来吗?

所以向下转型非常的不安全,如果要让它安全就要加上一个关键字instanceof 来判断一下。

public class Test extends TestDemo {
    public static void main(String[] args) {
        Animal animal = new Bird();
        if (animal instanceof Bird) {
            Bird bird = (Bird) animal;
            bird.fly();
        }
    }
}

instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了

所以向下转型我们一般不建议使用,如果非要使用就一定要用instanceof 关键字判断一下。

3.动态绑定(运行时绑定)

(1) 动态绑定概念

动态绑定发生的前提

1.先向上转型
2.通过父类引用来调用父类和子类同名的覆盖方法

来看一段代码:

在这里插入图片描述
运行结果

在这里插入图片描述

我们发现这里我们通过父类引用调用了 Animal 和 Cat
同名的覆盖方法(重写),运行的是子类Cat的eat方法。此时这里就发生了动态绑定

动态绑定也就叫运行时绑定,因为程序在编译的时候调用的其实是父类的 eat 方法,但是程序在运行时运行的则是子类的 eat 方法,运行期间发生了绑定。

(2) 重写(Override)

前面的博客中我们提到了重载,那么重写又是什么时候发生的呢?

重写发生的条件

1.方法名相同
2.方法的参数列表相同(返回类型和数据类型)
3.方法的返回值相同

返回值构成父子类关系也是可以发生重写的,此时叫做:协变类型

在这里插入图片描述

注意:

1.子类的重写的这个方法,他的访问修饰符,一定要大于等于父类方法的访问修饰符
2.被final和static修饰的方法是不能发生重写的

在这里插入图片描述

(3) @Override注解

被@Override注解修饰的方法代表是要重写的方法,一但方法被这个注解修饰,只要方法的方法名、放回值、参数列表有一个地方不满足重写的要求,编译器就会报错。

这个注解可以帮助开发人员进行检查

在这里插入图片描述

(4) 动态绑定的一个坑

来看一段代码,我们实例化一个Cat类,因为Cat是子类,所以要帮父类先构造,那么在父类的构造方法里有一个 eag 方法,那么会执行哪个类里的 eag 方法呢?

在这里插入图片描述
运行结果

在这里插入图片描述
我们发现这里调用的不是 是Animal 的 eat 方法,而是 Cat 的,因为这里也发生了动态绑定。

所以构造方法当中也是可以发生动态绑定的
注意:这样的代码以后不要轻易写出来!

4.多态

(1) 理解多态

多态其实就是一种思想,一个事物表现出不同的形态,就是多态。

通过代码来理解,这里我要打印一些形状

class Shape {
    public void draw() {

    }
}

class Rect extends Shape{

    public void draw() {
        System.out.println("♦");
    }
}

class Cycle extends Shape{
    public void draw() {
        System.out.printlnJAVA基础——面向对象三大特性:封装继承多态

java面向对象的三大特征?

面向对象的三大特性:继承封装多态

细说Java面向对象三大特性:继承封装多态

Java面向对象三大特性 封装 继承 多态

Python面向对象之:三大特性:继承,封装,多态。