Java面向对象程序设计OOP的三大特性详解

1. OOP的三大特性有什么

  1. 封装
  2. 继承
  3. 多态

2. 封装

2.1 什么是封装

在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。

2.2 为什么要封装

  • 封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
  • 封装是为了保护数据,这里的数据是指对象的属性(不是方法的局部变量)。

保护数据中的保护是什么意思?保护是指对数据的读写权限。

保护是如何实现的?或者说封装是如何实现的?是通过访问修饰符实现的。

2.3 访问修饰符

访问修饰符共有四个

  • public:公共的
  • protected:受保护的
  • 默认|default(什么都不写)
  • private:私有的
2.3.1 封装之Public

public修饰的属性没有访问限制,是最宽松的封装。

public class Animal{
    public String dog;
}

dog被public修饰,可以在任何地方被访问以及被读写。

public修饰的类、属性和方法在任何地方都能被访问。

2.3.2 封装之protected

protected是为继承而设计的访问修饰符

protected修饰的属性和方法在子类中一定可以访问,同一个包可以访问,子类在不同包也可以访问。

package com.sunmer.tmp;

public class Father {
    protected int age;
    int weight;
}
package com.sunmer.tmp;//同包

public class Sub1 extends Father{
    public static void main(String[] args) {
        Sub1 sub1 = new Sub1();
        sub1.age= 1;//可以访问
        sub1.weight = 1;//可以访问
    }
}
package com.sunmer.oop;//不同包
import com.sunmer.tmp.Father;
public class Sub2 extends Father {
    public static void main(String[] args) {
        Sub2 sub2 = new Sub2();
        sub2.age = 0;//可以访问
    }
}
package com.sunmer.oop;//不同包
import com.sunmer.tmp.Father;
public class Sub3 extends Sub2 {
    public static void main(String[] args) {
        Sub2 sub2 =new Sub2();
        sub2.age = 0;//不可访问
        Sub3 sub3 =new Sub3();
        sub3.age = 0;//可以访问
    }
}
2.3.3 封装之默认(default)

默认访问修饰符是指什么都不写

默认访问修饰符修饰的类、属性和方法只能在同一个包里被访问

default可以修饰类、属性和方法

package packageone;

public class Father {
    int num1=1;
    void print(){
        System.out.println("哒哒哒!");
    }
}
package packageone;//同一个包

public class Test {
    public static void main(String[] args) {
        Father father = new Father();
        father.num1=1;//可以访问
        father.print();//可以访问
    }
}
package packagetwo;//不同包

import packageone.Father;

public class Test {
    public static void main(String[] args) {
        Father father = new Father();
        father.num1 = 1;//不可访问
        father.print();//不可访问
    }
}

在不同包里,会提示:

‘print()’ is not public in ‘packageone.Father’. Cannot be accessed from outside package

2.3.4封装之private

private修饰的属性只能在同一个类里被访问,是最严格的封装

public class Father {
    private int num1=1;
    private void print(){
        System.out.println("哒哒哒!");
    }
}
public class Test {
    public static void main(String[] args) {
        Father father = new Father();
        father.num1=1;//不可访问
        //'num1' has private access in 'packageone.Father'
        father.print();//不可访问
        //'print()' has private access in 'packageone.Father'
    }
}

private不能修饰类

private class Father{//报错,private不能修饰类

}

如果我们非要访问private封装的属性怎么办呢?

可以使用getter和setter方法访问

最典型封装是:共有方法封装私有属性

getter和setter方法

public class Father {
    private int num1=1;

    public int getNum1() {//给私有成员可读权限
        return num1;
    }

    public void setNum1(int num1) {//给私有成员可写权限
        this.num1 = num1;
    }
}
public class Test {
    public static void main(String[] args) {
        Father father = new Father();
        father.getNum1();//可读取变量,但不能改
        father.setNum1(1);//可以改写变量,给变量赋值
    }
}

2.4 表格展示

位置 public protected 默认修饰符 private
同类访问
同包其他类访问 ×
同包子类访问 ×
不同包子类访问 × ×
不同包非子类访问 × × ×

3. 继承

3.1 生活中的继承

在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物,同理,狸花猫和波斯猫继承自猫,而牧羊犬和秋田犬继承自狗。这些动物之间会形成一个继承体系,具体如下图所示。

image-20220728190010378.png

3.2 Java中的继承

类和类之间有三种关系

is a :继承关系,例如:公共汽车 is a 汽车

use a:使用关系,例如:人 use a 钳子

has a:包含关系,例如:人has a 胳膊

3.2.1 继承的基本概念

继承可以解决编程中代码的冗余问题,是实现代码重用的重要手段之一。

继承是软件可重用性的一种表现,新类可以在不增加自身代码的情况下,通过从现有的类中继承其属性和方法,来实现充实自身内容,这种现象或行为就称之为继承。此时新类称之为子类,现有类称为父类。

继承最基本的作用是使得代码可以重用,即一个类只能有一个直接父类。

3.2.2 为什么要继承
  • 继承的出现提高了代码的复用性,提高软件开发效率。
  • 继承的出现让类与类之间产生了关系,提供了多态的前提。
3.2.3 继承的语法规则

在程序中,如果想要声明一个类继承另一个类,需要使用extends关键字。

class 子类 extends 父类{
    
}
3.2.4 继承的使用
image-20220728193224717.png

定义鸟类bird

public class Bird {
    private String name;

    public Bird(String name) {
        this.name = name;
    }

    public Bird() {
    }
    public void sound(){
        System.out.println("吱吱吱。。。");
    }
    public void fly(){
        System.out.println("时速300km/h中。。。");
    }
    public String getName() {
        return name;
    }

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

定义猫类cat

public class Cat {
    private String name;

    public Cat(String name) {
        this.name = name;
    }

    public Cat() {
    }
    public void sound(){
        System.out.println("喵喵喵");
    }
    public void climbTrees(){
        System.out.println("上树中......");
    }
    public String getName() {
        return name;
    }

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

在这两个类中我们看到有很多相同的属性和方法,既然有重复的地方我们可以使用继承了

定义动物类Animal

将鸟类和猫类相同的属性和方法写在动物类中

public class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public Animal() {
    }
    public void sound(){
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

然后将两个类作为子类继承动物类,并且将之前的相同代码删除,留下不同属性方法。

public class Bird extends Animal{
    public Bird(String name) {
        super(name);
    }
    public Bird() {
    }
    public void fly(){
        System.out.println("时速300km/h中。。。");
    }
}
public class Cat extends Animal{
    public Cat() {
    }
    public Cat(String name) {
        super(name);
    }
    public void climbTrees(){
        System.out.println("上树中......");
    }
}

注意:

  • 继承中的super(调用父类成员)
  • super调用父类构造函数,必须是子类构造函数的第一行
  • super只能出现在子类(子类的普通方法或构造方法)中,而不是其他位置
  • 具有访问权限的限制,如无法通过super访问父类private成员
  • 无法继承父类的构造方法

验证子类是否继承到了父类的数据

public class Test {
    public static void main(String[] args) {
        Bird bird = new Bird();//实例化子类对象
        Cat cat = new Cat();//实例化子类对象
        bird.setName("鸟爷");//通过父类的属性方法为子类赋值
        cat.setName("猫哥");//通过父类的属性方法为子类赋值
        System.out.println(bird.getName());
        System.out.println(cat.getName());
    }
}

结果为:
鸟爷
猫哥

结果说明子类是继承了父类的数据的。

对象的属性可以通过set方法赋值

3.2.5 方法重写

如果子类从父类继承的方法不能满足子类的需要,或者不适合子类的需要。

此时子类可以将从父类继承的方法重写定义成满足自己需要的方法。

重新定义称为重写。

在上边的例子中,鸟和猫都是有一个相同发出叫声的sound方法的,但是鸟叫和猫叫显然是不一样的,不能满足子类的需要,这时可以使用方法重写

public class Bird extends Animal{
    public Bird(String name) {
        super(name);
    }
    public Bird() {
    }

    @Override//方法重写
    public void sound() {
        System.out.println("吱吱吱!");
    }

    public void fly(){
        System.out.println("时速300km/h中。。。");
    }
}
public class Cat extends Animal{
    public Cat() {
    }
    public Cat(String name) {
        super(name);
    }

    @Override//方法重写
    public void sound() {
        System.out.println("喵喵喵");
    }

    public void climbTrees(){
        System.out.println("上树中......");
    }
}

调用一下两个子类sound方法

public class Test {
    public static void main(String[] args) {
        Bird bird = new Bird();
        Cat cat = new Cat();
        bird.sound();
        cat.sound();
    }
}

结果为:
吱吱吱!
喵喵喵

实现了具体叫声的不同

方法重写的注意事项

  • 方法重写时,方法的返回值类型 方法名 参数列表都要与父类一样。(同名,同参,同返回)
  • 子类方法覆盖父类方法,必须要保证权限大于等于父类权限
class Fu{   
    void show(){}
    public void method(){}
}
class Zi extends Fu{
    public void show(){}  //扩大show的访问权限,编译运行没问题
    void method(){}       //缩小method的访问权限,编译错误
}
  • 方法重写时,子类不能缩小父类抛出的异常
 Bird bird = new Bird("鸟爷");//构造方法直接赋值
Cat cat = new Cat("猫哥");//构造方法直接赋值
3.2.6 创建子类对象时的运行规则
  1. 第一个方面:构造函数的调用顺序问题
    1. new子类时,首先调用子类构造函数,但是不执行子类构造函数
    2. 子类构造函数被调用后立即调用父类的构造函数。
  2. 第二个方面:属性初始化顺序的问题
    1. 从Object类开始初始化
    2. 然后依次从父到子的顺序初始化(哪个类定义的属性,就由哪个类负责初始化)
image-20220728203550857.png

代码编写步骤:

第一步:重构父类构造

  • 首先为父类添加构造,父类的构造为父类中定义的属性初始化
  • 子类定义的特有属性不是父类负责初始化的。

第二步:重构子类构造

  • 子类构造应该获得所有属性的初始值,本例中共6个属性,其中5个是父类的,1个是自己的
  • 子类构造的第一行必须是调用父类构造,
  • 如果不写super(),那么默认再子类第一行添加调用父类无参的构造super()
  • 子类再调用父类构造代码super()的下面负责给自己的属性赋值

第三步:重构实例化子类对象

  • 再测试类中new子类时,要为子类传入属性的参数

第四步:测试

3.2.7 继承的注意事项
  • 类只支持单继承,不允许多继承
  • 多个类子可以继承一个父类
  • 允许多层继承
class A{}
class B extends A{}   // 类B继承类A,类B是类A的子类
class C extends B{}   // 类C继承类B,类C是类B的子类,同时也是类A的子类
  • 子类和父类是一种相对的概念
  • 所有类都有一个父类object,如果一个类没有显示定义父类,那么默认继承Object类

4. 多态

4.1 认识多态

多态一词的通常含义是指能够呈现出多种不同的形式或形态。而在程序设计术语中,它意味着一个特定类型的变量,可以引用不同类型的对象,并且能自动的调用引用的对象的方法。也就是根据用到的不同对象类型,响应不同的操作。

方法重写是实现多态的基础。

多态的实现方法有两种

  • 方法重写(公认的多态)
  • 方法重载(非公认的多态)

4.2 多态的语法格式

多态的格式:父类引用指向子类实例(即为里氏替换原则)

父类类型  变量名 = new 子类类型();
变量名.方法名();
Pet pet = new Cat();
pet.toString();

4.3 多态的实现

public class Bird extends Animal{
    public Bird(String name) {
        super(name);
    }
    public Bird() {
    }

    @Override//方法重写
    public void sound() {
        System.out.println("吱吱吱!");
    }

    public void fly(){
        System.out.println("时速300km/h中。。。");
    }
}
public class Cat extends Animal{
    public Cat() {
    }
    public Cat(String name) {
        super(name);
    }

    @Override//方法重写
    public void sound() {
        System.out.println("喵喵喵");
    }

    public void climbTrees(){
        System.out.println("上树中......");
    }
}

多态表现为:

public class Test {
    public static void main(String[] args) {
        Animal animal = new Bird("鸟爷");//父类引用指向子类实例
        animal.sound();
        animal = new Cat("猫哥");//父类引用指向子类实例
        animal.sound();
    }
}

结果为:
吱吱吱!
喵喵喵

4.4 多态的转型

多态的转型分为向上转型与向下转型两种:

  • 向上转型就是父类引用指向子类对象
  • 向下转型就是子类引用指向父类对象
向上转型

向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。

使用格式:

父类类型  变量名 = new 子类类型();
向下转型

向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。

如果是直接创建父类对象,是无法向下转型的!

使用格式:

子类类型 变量名 = (子类类型) 父类类型的变量;

4.5 instanceOf运算符解决子类扩展方法的调用

多态有优势,但是也有劣势,向上转型后,只能使用父类共性的内容,而无法使用子类特有的功能。

可以通过instanceOf运算符来解决这个问题

public class Bird extends Animal{
    public Bird(String name) {
        super(name);
    }
    public Bird() {
    }

    @Override//方法重写
    public void sound() {
        System.out.println("吱吱吱!");
    }

    public void fly(){
        System.out.println("时速300km/h中。。。");
    }
}
public class Cat extends Animal{
    public Cat() {
    }
    public Cat(String name) {
        super(name);
    }

    @Override//方法重写
    public void sound() {
        System.out.println("喵喵喵");
    }

    public void climbTrees(){
        System.out.println("上树中......");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal = new Bird("鸟爷");//构造方法直接赋值
        animal.sound();
        if ( !(animal instanceof Bird) ){
            System.out.println("类型不匹配,不能转换");
            return;
        }else {
            Bird bird = (Bird) animal;//向下转型,实现子类特有的功能
            bird.fly();
        }
    }
}

结果为:
吱吱吱!
时速300km/h中。。。

【信息由网络或者个人提供,如有涉及版权请联系COOY资源网邮箱处理】

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容