Java三大特性:封装,继承与多态

(尊重劳动成果,转载请注明出处:https://blog.csdn.net/qq_25827845/article/details/84592274冷血之心的博客)

目录

(一)封装

封装的定义

封装的好处

(二)继承

继承的概念

子类的特点

构造函数

覆盖/重写的概念

(三)多态

多态的概念

向上转型

向下转型

总结

结束语


        面向对象的语言有三大特性,即封装继承与多态。三大特性是面向对象编程的核心,对于初学者务必加强对三大特性的理解与领会。在这篇博客中,我们通过具体的案例来依次阐述封装继承与多态的概念与使用。

 

(一)封装

封装的定义

何为封装?

        把事物抽象成一个类,将事物拥有的属性和动作隐藏起来,只保留特定的方法与外界联系。当内部的逻辑发生变化时,外部调用不用因此而修改,它们只调用开放的接口,而不用去关心内部的实现。

封装的好处

  • 实现了专业的分工,将处理逻辑封装成一个方法,做到见名知其义
  • 良好的封装能够减少耦合
  • 隐藏信息,实现细节

接下来我们举两个封装的具体案例来进行阐述。

案例一:

package com.pak1;

public class Test {
    public static void main(String[] args) {
        Student student = new Student();
        student.name = "小明";
        student.age = 16;
        student.printStudentAge();

        Student student2 = new Student();
        student2.name = "小白";
        student2.age = 120;
        student2.printStudentAge();

    }
}

class Student {
    String name;
    int age;

    public void printStudentAge() {
        System.out.println(name + "同学的年龄:" + age);
    }
}

输出如下:

这个时候我们可以看到输出的小白同学的年龄120明显不科学,所以我们需要做一些内部逻辑的处理。所以需要进行代码封装,将内部逻辑进行一个隐藏。

封装之后的代码如下:

package com.pak1;

public class Test {
    public static void main(String[] args) {
        Student student = new Student();
        student.setName("小明");
        student.setAge(16);
        student.printStudentAge();

        Student student2 = new Student();
        student.setName("小白");
        student.setAge(120);
        student2.printStudentAge();

    }
}

class Student {
    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) {
        if (age < 0 || age > 60)
            throw new RuntimeException("年龄设置不合法");
        this.age = age;
    }

    public void printStudentAge() {
        System.out.println(name + "同学的年龄:" + age);
    }
}

输出结果如下:

我们将Student这个类的name和age私有化,提供了公共的get/set方法才能进行访问,在get/set方法中我们可以对内部逻辑进行封装处理,外部的调用方不必关心我们的处理逻辑。

        我们再来看另一种封装,在一个方法中,太多的逻辑将会导致该方法违反了单一性,导致该方法的可读性变差,这个时候我们需要将可以拆成一块的方法进行拆分,建立相应的service或者utils来封装该方法。举例如下:

案例二:

package com.pak1;

public class TestFZ {
    public static void main(String[] args) {
        int score = 78;
        if (score > 100 || score < 0) {
            System.out.print("对不起,你的分数输入有错误");
        } else if (score > 89) {
            System.out.print("奖励你一台手机");
        } else if (score > 79) {
            System.out.print("奖励你一副耳机");
        } else if (score > 59) {
            System.out.print("奖励你一朵小红花");
        } else if (score > 59) {
            System.out.print("奖励你一朵小红花");
        }else {
            System.out.print("奖励你一个耳光");
        }


    }
}

好了,这部分的处理逻辑很复杂,看起来可读性也很差,这个时候我们需要封装。封装之后的代码如下:

package com.pak1;

public class TestFZ {
    public static void main(String[] args) {
        int score = 78;
        TestFZ testFZ = new TestFZ();
        testFZ.printReward(score);
    }

    public void printReward(int score) {
        if (score > 100 || score < 0) {
            System.out.print("对不起,你的分数输入有错误");
        } else if (score > 89) {
            System.out.print("奖励你一台手机");
        } else if (score > 79) {
            System.out.print("奖励你一副耳机");
        } else if (score > 59) {
            System.out.print("奖励你一朵小红花");
        } else if (score > 59) {
            System.out.print("奖励你一朵小红花");
        } else {
            System.out.print("奖励你一个耳光");
        }
    }
}

这样我们便把处理各个分数段奖励的逻辑进行了封装,增强了可读性,并且外部接口不需要了解内部如何实现。

(二)继承

继承的概念

       继承是面向对象的最显著的一个特征。继承是从已有的类(父类或者超类)中派生出新的类(子类),新的类能吸收已有类的数据属性和行为,并能扩展新的能力(方法的覆盖/重写)。JAVA不支持多继承,一个类只能有一个父类。父类是子类的一般化,子类是父类的特殊化(具体化)

子类的特点

  • 子类拥有父类非private的属性和方法
  • 子类可以添加自己的方法和属性,即对父类进行扩展
  • 子类可以重新定义父类的方法,即方法的覆盖/重写

构造函数

  • 构造函数不能被继承,子类可以通过super()显示调用父类的构造函数
  • 创建子类时,编译器会自动调用父类的无参构造函数
  • 如果父类没有定义无参构造函数,子类必须在构造函数的第一行代码使用super()显示调用

由于后边要阐述多态,这里我们先来介绍一个重要的概念,即方法的覆盖/重写

覆盖/重写的概念

        当子类需要修改父类的一些方法进行扩展,增大功能,程序设计者常常把这样的一种操作方法称为重写,也叫称为覆盖。

可以这么理解:重写就是指子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。这样,就可以实现对父类方法的覆盖。如果子类将父类中的方法重写了,而我们想调用父类中的同名方法怎么办?此时,通过使用super关键就可以实现这个功能,super关键字可以从子类访问父类中的内容,如果要访问被重写过的方法,使用“super.方法名(参数列表)”的形式调用。

下边我们给出一个案例,子类扩展了父类的行为,并且重写了父类中的方法。代码如下:

package com.pak1;

public class Animal {
    private String name;
    private int age;

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

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

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    @Override
    public String toString() {
        return "{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

class Dog extends Animal {
    // 对父类中方法的一种重写
    public String getName() {
        return super.getName() + "Dog";
    }

    // 狗叫和狗吃饭都是在扩展父类中的行为
    public void voice() {
        System.out.println(super.getName() + " 汪");
    }

    public void eat() {
        System.out.println(super.getName() + "吃东西");
    }
}

class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("大狗");
        dog.setAge(3);
        System.out.println(dog.toString());

        dog.eat();
        dog.voice();
        System.out.println(dog.getName()); //执行的是Dog中的getName方法
    }
}

(三)多态

多态的概念

多态的本质是:一个程序中同名的不同方法。在面向对象的程序设计中,多态主要有以下三种方式来实现。

  • 通过子类对父类方法的覆盖来实现多态
  • 通过一个类中方法的重载来实现多态
  • 通过将子类的对象作为父类的对象实现多态。

覆盖的概念我们在前面以及介绍了,接下来我们简单阐述下何为重载

  • 重载是指一个类里面(包括父类的方法)存在方法名相同,但是参数不一样的方法,参数不一样可以是不同的参数个数、类型或顺序
  • 如果仅仅是修饰符、返回值、throw的异常 不同,那么这是2个相同的方法

我们重点阐述第三种实现方法,即通过将子类的对象作为父类的对象实现多态

把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。也就是说,父亲的行为像儿子,而不是儿子的行为像父亲。(这句话是我理解第三种方法的关键,请仔细阅读理解)

         继承是面向对象语言中一个代码复用的机制,简单说就是子类继承了父类中的非私有属性和可以继承的方法,然后子类可以继续扩展自己的属性及方法。

         对象的引用型变量是具有多态性的,因为一个引用型变量可以指向不同形式的对象,即:子类对象作为父类对象来使用。在这里涉及到了向上转型和向下转型。

向上转型

        子类对象转为父类,父类可以是接口。公式:Father f = new Son(); Father是父类或接口,Son是子类。

向下转型

        父类对象转为子类。公式:Son s = (Son) f;

在向上转型的时候我们可以直接转,但是在向下转型的时候我们必须强制类型转换。并且,如案例中所述,该父类必须实际指向了一个子类对象才可强制类型向下转型。若Father f = new Father()那么不可以转换,运行会报错。

对于多态,我们先举一个例子。在一个单位中,有职工employee,职工中又有少数是管理者manager,管理者中又有一部分是领导。若小明是管理者manager类的对象,他也可以被看做是employee的对象,即他也可以被看做是一个职工,他同时具备着职工的所有属性。

public class testDuoTai {
 
	public static void main(String[] args) {
		Employee emp1=new Employee("小明",23, 1000);   //emp1是Employee的对象
        System.out.println(emp1.getInfo());
        
        Employee emp2=new Manager("小明",23, 1000,5000); //注意此处emp2是Manager类的对象
        System.out.println(emp2.getInfo());
	}
 
}
//定义一个父类
class Employee
{
	String name;
	int age;
	float salary;
	
	Employee(){};
	
	Employee(String name,int age,float sal)
	{
		this.name=name;
		this.age=age;
		this.salary=sal;				
	}
	String getInfo()
	{
		return "职工姓名:"+name+"年龄:"+age+"工资:"+salary;
	}
}
//定义一个子类
class Manager extends Employee
{
	float allowance;
	Manager(String name,int age,float sal,float aa)
	{
		this.name=name;
		this.age=age;
		this.salary=sal;
		allowance=aa;
	}
}

输出结果如下:

接下来我们再来看一个经典的多态讲解案例,代码如下:

package com.pak2;

class A {
    public String show(D d) {
        return ("A and D");
    }
    public String show(A a) {
        return ("A and A");
    }
}

class B extends A {
    public String show(B b) {
        return ("B and B");
    }
    public String show(A a) {
        return ("B and A");
    }
}
class C extends B {}
class D extends B {}

public class Test {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new B();

        B b = new B();
        C c = new C();
        D d = new D();

        System.out.println("1--" + a1.show(b));
        System.out.println("2--" + a1.show(c));
        System.out.println("3--" + a1.show(d));
        System.out.println("--------------");
        System.out.println("4--" + a2.show(b));
        System.out.println("5--" + a2.show(c));
        System.out.println("6--" + a2.show(d));
        System.out.println("--------------");
        System.out.println("7--" + b.show(b));
        System.out.println("8--" + b.show(c));
        System.out.println("9--" + b.show(d));
    }
}

这段代码的输出是啥呢?大家可以先分析分析,然后再看正确答案。

好了,正确答案如下:

在分析结果之前,我们先来看相关的知识点概念:

  • 父类对象引用变量引用子类对象时,被引用对象的类型不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法

产生多态时候,各个方法调用的优先级顺序由高到低依次为:

this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)

我们来分析以下结果:

(1)System.out.println("1--" + a1.show(b));

       首先a1是一个标准的A对象的引用变量,传入的参数为标准的 new B(),我们在A类的方法中找不到show(B b)的方法,接着去看A的超类是否有show(B b)的方法,发现A没有超类。那么去执行this.show((super)O),即A类中的show(A a)方法,所以输出为A and A

(2)System.out.println("2--" + a1.show(c));

      同理,a1.show(c) C的父类是B,所以C也是A的子类,那么最后还是调用A类中的show(A a)方法,所以输出为A and A

(3)System.out.println("3--" + a1.show(d));

      这个没什么好疑惑的,直接就是调用A类中的show(D obj),所以输出为A and D

(4)System.out.println("4--" + a2.show(b));

      这个的结果输出就有点迷惑人了,小伙伴们会惊奇,为什么不是调用B类中的show(B b)方法输出B and B ?我们来解释下,a2是一个父类A的引用变量指向了一个子类B的对象,也就是说表面类型是A,实际类型是B。当我们调用方法的时候,首先从其表面类型里边寻找方法 show(B b)结果没有找到,那么按照调用优先级,我们最终会调用到this.show((super)O) 也就是说我们调用了A类中的show(A a)方法。按照上边所述的概念:当父类对象引用变量引用子类对象时,被引用对象的类型不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。在当前的表面类型中找到了该方法show(A a),并且在子类(B)中也覆盖了该方法,那么最终会调用实际类型(B类)中的该方法show(A a),所以输出B and A

(5)System.out.println("5--" + a2.show(c));

      这个分析和4中的保持一致

(6)System.out.println("6--" + a2.show(d));

     这个没什么疑问,在A类中找到了该方法show(D d),并且子类没有覆盖该方法,所以直接调用A类中的方法,输出A and D

(7)System.out.println("7--" + b.show(b));

       b.show(b)应该没什么疑问,会直接调用B类中的方法show(B b) 输出 B and B

(8)System.out.println("8--" + b.show(c));

     根据方法调用优先级,最终会调用到B类中的方法show(B b) 输出 B and B

(9)System.out.println("9--" + b.show(d));

     根据方法调用优先级,最终会调用到A类中的方法show(D d) 输出 A and D

      这是多态学习中的一个经典案例,我们要牢记方法之间的调用优先级,并且要分清楚表面类型和实际类型,以及区分子类中是否进行了方法的覆盖等知识点,这样才能做到条理清晰。

总结

        在这篇博客中,我们依次介绍了面向对象的三大特性,即封装继承与多态。针对多态,我们又详细分析了经典的案例,帮助大家学习理解,接下来我会将这篇文章录制成CSDN学院的免费课程(视频教学),CSDN学院搜索 “杨文强” 关注我的课程。

结束语

        如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,可以进群824733818一起交流学习。本群给大家提供一个学习交流的平台,欢迎各位真正喜欢提问喜欢分享知识的小伙伴进来交流技术。
 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

温柔狠角色

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

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

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

打赏作者

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

抵扣说明:

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

余额充值