万字总结,一文带你秒懂Java中的封装、继承和多态(有代码 有示例)


前言

我们都常听到软件设计思想有面向对象和面向过程。那么什么是面向对象、什么是面向过程呢?
接下来我举个例子说明一下他们两者的区别。
有一天你想吃蛋炒饭,怎么办,有两个选择。
第一:自己买鸡蛋,蒸米饭,开炒,装盘。

第二:去饭店,“老板,给我来份蛋炒饭”。

那么这两种有什么区别呢?很显然,第一种是面向过程思想,第二种是面向对象思想。
众所周知,java是一种典型的面向对象的开发语言,而面向对象开发有三大特性:封装、继承和多态。


一、面向对象有什么优势?

面向过程优点和缺点

优点:性能比面向对象好,因为类调用时需要实例化,开销比较大,比较浪费资源。

缺点:不易维护、不易复用、不易扩展

面向对象优点和缺点

优点:易维护、易复用、易扩展、面向对象有封装、继承、多态的特征,可以设计出低耦合的系统,使系统更加灵活、易于维护。

缺点:性能比面向过程差

二、面向对象的三大特性!

1.封装

1.1访问限定符

在解释封装这个特性之前,先来了解一下Java中的访问限定符:

Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认
知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:
private(当前类的内部可见)<default(不写访问修饰符就默认是包访问权限),当前包的内部可见,父文件夹和子文件夹都不可见<protected(继承权限,不同文件夹(和父类不同包)的子类可见)<public(公开访问权限)


private:关于访问限定符,比较重要的(或者说用的最多的)就是private,日后写代码基本上百分之九十的属性都是private修饰的。
default:包其实就是文件夹,不写访问修饰符就默认是包访问权限,正是因为有包访问权限的存在,才可以在不同的包下创建同名的类。
protected: 比包访问权限大就在于可以不同文件夹下的子类也可见
public:所有成员可见,随意使用,完全公开

在java中类的全名称是包名.类名,这样就可以唯一确定一个类,一个类使用package关键字声明该类属于哪个包。

1.2关于包的导入

在Java中提供了很多现成的类供开发者使用,例如Date类、日期类、数学类等等,但是在java.util包下有Date类,在java.sql包下也有Date类,因此我们在具体使用是要使用全名称。
在这里插入图片描述
也可以使用impor关键字告知编译器导入包:
在这里插入图片描述
假设还需要在这个类中导入java.util包中的其他类,可以使用通配符*来表示导入,此时用到哪个类javac编译器会自己进行导入。
在这里插入图片描述
拓展一下开发中常用的包:

java.lang :系统的基础类所在的包,String,Object (万物之母)此包JDK1.1开始默认导入
java.util:工具包,集合类都在这个包中Scanner
java.io : IO开发包,输入输出工具包
java.net :网络编程开发包
Socketjava.sql:数据库编程开发包
java.lang.reflect:反射开发包,反射是所有框架的基础

1.2封装

在来谈封装:体现了程序的保护性易用性,使用private关键字实现属性的封装,将某些具体的属性使用关键字private封装在一个类的内部,该属性对外部完全隐藏,想要在当前类的外部使用私有属性,需要对外提供两类方法getter(获取)/setter(修改)。例如银行卡中的密码与余额就属于敏感信息,类的外部必须通过提供的方法(setter)来操作这两个属性。
代码示例如下:

public class Card {
    // 卡号
    private int cardNum;
    // 密码
    private String password;
    // 余额
    private double banlance;
    // 构造方法
    public Card(int cardNum, String password) {
        this.cardNum = cardNum;
        this.password = password;
    }
    private void test() {
        System.out.println("Card中的私有成员方法");
    }

    private static void fun() {
        System.out.println("Card中的静态私有方法");
    }

    public static void hello() {
        fun();//私有方法在类内是可以直接调用的
    }
    //getter方法来访问private的成员
    public int getCardNum() {
        //test();
        return cardNum;
    }

    public double getBanlance() {
        return banlance;
    }
    public void setPassword(String password) {
        // 修改密码之前,需要用户输入现有密码,验证通过之后才能修改!尝试三次
        Scanner scanner = new Scanner(System.in);
        for (int i = 0; i < 3; i++) {
            System.out.println("请输入旧密码:");
            String oldPass = scanner.nextLine();
            if (oldPass.equals(this.password)) {
                System.out.println("验证成功,开始修改密码");
                this.password = password;
                System.out.println("修改密码成功!");
                return;
            }
        }
        System.out.println("当日尝试次数过多,明天再试试!");
    }
    // 修改余额,存钱或取钱!
    public void setBanlance(double banlance) {
        if (banlance > 0) {
            // 存钱流程
            this.banlance += banlance;
            System.out.println("存钱成功!存入之后的余额为 : " + this.banlance);
        }else {
            // 验证取钱的数字是否超过当前余额
            if (this.banlance + banlance < 0) {
                System.out.println("余额不足,无法取出!");
            }else {
                this.banlance += banlance;
                System.out.println("取钱成功!提款之后账户还剩 : " + this.banlance);
            }
        }
    }

在其他类中 具体操作如下:

public class CardTest {
    public static void main(String[] args) {
        Card card = new Card(10010,"123");
        card.setPassword("456");
        card.setBanlance(300.0);
        System.out.println("卡号为 : " + card.getCardNum());
        System.out.println("余额为 : " + card.getBanlance());
        Card.fun();  //私有静态方法不能直接调用
        card.test();//私有方法不能调用
        card.setBanlance(-200.5);
        card.setBanlance(-200.0);
    }
}

从上面的示例可以看出,private也可以修饰方法,但这些方法仅限于当前类的内部使用,对外完全隐藏

2.继承

继承机制:继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行拓展,增加新功能,这样产生新的类称之为派生类(子类)。继承呈现了程序设计的层次结构,继承主要解决的问题就是:共性的抽取,实现代码复用。
在这里插入图片描述
上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态。

2.1继承的语法

在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:

修饰符 class 子类 extends 父类 {
// …
}

在这里插入图片描述
在Java中使用extends表示继承一个父类,父类所有的属性和行为都会被子类继承下来

注意:在java中,类的继承是单继承!一个类只能继承一个父类。不允许多重继承,但可以多层继承,一般多层继承不会超过三层关系。

显示继承与隐式继承:

父类中private修饰的属性和方法,通过继承子类不能直接使用(需要通过getter和setter),称之为隐式继承。显示继承下来的东西可以随心所欲的使用。

2.2父类成员访问

1、访问父类的属性:子类要想访问父类的属性,父类的属性权限必须>=protected,若显示继承,使用父类的属性可以直接使用(前提是子类中没有和父类同名的属性)。若子类中定义了和父类同名的属性呢?
在这里插入图片描述
程序开发的就近匹配原则:编译器寻找变量a:先在方法中寻找,找不到再在当前类的内部寻找,再找不到才向上在父类中寻找同名变量。只要在子类中定义了和父类名称相同的属性,无关类型使用相同名称时,调用的都是子类中覆盖后的变量!

如果要想在子类中调用被覆盖的父类中的同名属性,使用super关键字! super修饰属性,明确表示直接从父类中寻找同名属性!super.属性名称表示从父类中寻找同名属性

2、在子类中调用父类的方法

在这里插入图片描述
testA在子类中不存在,继续去父类中寻找同名方法

若子类定义了和父类完全名称相同的方法?

方法重写(override) :发生在有继承关系的类之间,子类定义了和父类除了权限不同以外其他全部相同(名称,参数列表,返回值都相同)的方法,称之为方法的重写。
子类重写后的方法权限>=父类的权限。

在子类中要调用被覆写后的父类方法,使用super.方法名称()表示直接从父类中寻找同名方法

在这里插入图片描述

3、关于父子类对象的产生

当调用子类构造方法产生子类对象时,JVM会首先调用父类的构造方法先产生父类对象后再产生子类对象!(没有你爸哪儿来的你同理)但是,若父类只有有参构造,而子类中要调用构造方法时首先默认调用的是父类的无参构造。这时候就需要用到super关键字了!

super调用父类的构造方法:
1.若调用的是父类的无参构造,可以不写
2.若调用的是父类的有参构造,则必须使用super(参数列表),明确表示先调用父类的构造方法。
3. **在子类中调用父类的构造方法必须放在子类构造方法的首行!!**否则报错在这里插入图片描述
4.在学习this关键字时,通过在构造方法中使用this调用其他构造方法以简化代码,但this()也得放首行,因此this和super表示构造方法调用时,不能同时出现!
在这里插入图片描述

4、final关键字:终结器

①使用final修饰的属性值无法修改,一般使用final关键字来定义常量。
注意:fianl修饰引用数据类型时,值不能改是引用保存的地址不能更改,地址中包含的内容还是可修改的。
在这里插入图片描述
②被final修饰的方法不能被重写!(不允许子类覆盖)
③被final修饰的类不能被继承,final修饰的类没有子类:JDK中的String类就是典型的final类(至于为什么要使用final修饰,是为了保证JDK的使用者们用到的String类完全一样,没有别的版本。后面有时间的话单独写一下这个)。

3.多态

3.1何为多态

因为继承的特性,我们可以使用一个父类Animal定义各种属性,从而在子类中只需定义独有的方法(比如eat())即可,但是当我们使用这些方法时,他是如何得知我们调用的是哪个类中的eat方法呢?
同样是eat方法,当传入Dog对象时,eat表现出来的行为是吃狗粮,当传入Cat对象时, eat表现出来的就是吃猫粮。
一言以蔽之,多态性就是同样的一件事情(行为/方法),发生在不同的对象上,表现出不同的结果称之为多态性。

在java中要实现多态,必须满足以下几个条件:

1、必须在继承体系下;
2、子类必须要对父类中方法进行重写;
3、通过父类的引用调用重写的方法

下面通过一个实例看一下多态:

public class Animal {
    protected String name;
    protected int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(this.name + "正在吃饭");
    }
}
public class Cat extends Animal{
    // 当父类中没有无参构造时,子类构造方法首行必须显示使用super调用父类的有参构造
    public Cat(String name,int age) {
        // 显示调用父类的有参构造
        super(name,age);
    }
    // 方法重写 : 发生在有继承关系的类之间,子类定义了和父类除了权限不同以外,其他全部相同的方法称之为方法重写
    public void eat() {
        System.out.println(this.name + "正在吃小鱼干");
    }
}
public class Dog extends Animal{
    String food;
    public Dog(String name,int age) {
        super(name,age);
    }
    public void bark() {
        System.out.println("Dog类独有的方法bark");
    }
    public void eat() {
        System.out.println(this.name + "正在啃骨头");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal("动物",0);
        Dog dog = new Dog("66",5);
        Cat cat = new Cat("喵喵",3);
//        fun(dog);
//        fun(cat);
        animalEat(animal);
        animalEat(dog);
        animalEat(cat);
    }
    public static void animalEat(Animal animal) {
        animal.eat();
    }
    // 方法形参是Animal父类的引用,但是根据传入的具体子类不同,表现出来的eat方法就不同
    // 这种特性就称之为对象的多态性
    public static void fun(Animal animal) {
        animal.eat();
    }
}

在这里插入图片描述

3.2 向上转型&向下转型

3.2.1 向上转型

向上转型实际上,就是创建一个子类对象,将其当作父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型() 比如:Animal animal = new dog(“旺财”,2);
animal是父类类型,但是可以引用一个子类对象,因为是从小范围到大范围的转换,是安全的。

向上转型的使用场景一般有三种:直接赋值、方法传参、方法返回值

1、直接赋值:

父类类型 对象名 = new 子类类型() 比如:Animal animal = new dog(“旺财”,2);

2、方法传参:
在这里插入图片描述

当传入参数为形参的子类时,实参会默认向上转型为其父类。

3、方法的返回值
在这里插入图片描述

当方法的返回值需要返回一个Animal类时,方法内返回的子类会向上转型为其父类。

**向上转型的优点:**参数统一化,方便进行子类的拓展。

先说参数统一化,假设现在需要实现一个方法,可以接受所有Animal以及其子类的对象,调用eat方法,假设没有向上转型,只能当前类型的引用指向当前类型,代码如下:

在这里插入图片描述

如果animal有成千上万个子类,那么此方法就需要重载上万次,每当需要支持一个子类的eat方法调用就需要重载一次!但是!有了向上转型,参数统一化,只需要在方法形参规定父类的引用,那么所有该类的子类对象都能使用父类引用来接受子类参数。

在这里插入图片描述

再说子类拓展,假设现在有三种图形,圆形,正方形,三角形,需要实现一个一个方法进行这三种图形的打印。

在这里插入图片描述
有了多态以后的代码,我们可以创建一个形状的对象数组,拓展时只需要将新拓展的图形加入数组即可。无论拓展多少子类,都不需要改变,只要拓展的是Sharp的子类,都可以向上转型变为Sharp父类的引用。
在这里插入图片描述

向上转型优点:单数统一化,让代码实现更加简单灵活
向上转型缺点:不能调用到子类特有的方法
注意:只要是子类重写过的方法,哪怕是使用父类引用进行的 .操作 ,调用的也一定是重写后的方法!!!父类引用.方法名称到底调用的是父类方法还是子类方法,就看new再哪儿

3.2.2 向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。

注意:
1、要使用向下转型,必须先向上转型!
2、类型强转有风险,不能将毫不相关的两个类进行转换。

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了instanceof,如果该表达式为true,则可以安全转换。
在这里插入图片描述

3.3方法重写override

方法重写ovride:发生在有继承关系的类之间,子类定义了和父类除了权限不同(子类权限>=父类权),返回值可以为向上转型类的返回值之外(除此情况,返回值必须相同),参数列表方法名完全相同的方法、就称为方法重写。

注意:
1、私有方法无法被重写,子类压根不知道父类中有此方法,肯定是不能重写的
2、静态方法不能被重写,重写方法是父类和子类相关的,静态对象压根没有对象,无法覆写。
3、被final修饰的方法也无法重写
4、构造方法也无法被重写,构造方法是为了产生对象,无法在子类中修改父类对象如何产生,况且重写的条件首先是方法名相同,名字都不同当然无法重写。

public class Animal {
    protected String name;
    protected int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat() {
        System.out.println(this.name + "正在吃饭");
    }
}

public class Cat extends Animal{
    // 当父类中没有无参构造时,子类构造方法首行必须显示使用super调用父类的有参构造
    public Cat(String name,int age) {
        // 显示调用父类的有参构造
        super(name,age);
    }
    // 方法重写 : 发生在有继承关系的类之间,子类定义了和父类除了权限不同以外,其他全部相同的方法称之为方法重写
    public void eat() {
        System.out.println(this.name + "正在吃小鱼干");
    }
}

重写方法时,可以使用注解@Override检查是否重写成功!
在这里插入图片描述

3.4 多态优缺点

1、能够降低代码的“圈复杂度",避免使用大量的if - else。

圈复杂度是一种描述一段代码复杂程度的方式.一段代码如果平铺直叙,那么就比较简单容易理解.而如果有很多的条件分支或者循环语句,就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为"圈复杂度".如果一个方法的圈复杂度太高,就需要考虑重构.

就像上面提到的多态优点中提到的画图的示例。使用多态,我们可以更方便的拓展子类,降低代码的圈复杂度。

2、可拓展能力更强,不再赘述。


三、总结

个人看来面向对象这三大特性的核心其实是继承和多态,封装是为继承和多态服务的,关于多态和继承又设计很多别的知识点,比如向上转型、重写、super 、this关键字等等,需要掌握的知识点还是挺多的。
以上就是上一周总结的内容,学完就想着总结一下,结果愣是拖了半个多月才开始动手O_O。拖拖拉拉的写了一周总算写完了,下一篇准备写抽象类和接口,还在学se的小白一个,有写的不对的地方欢迎大佬指出,互相交流一起进步~
BTW:手打1w字真的好累QAQ!!!再也不拖这么久写这么长了~

  • 16
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彭彭彭摆鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值