封装、继承、多态

目录

访问限定符 

封装 

继承

super关键字 

super和this的比较

继承在内存中的情况 

重写override 

重写和重载的比较

多态 

JAVA中的动态绑定机制


JAVA面向对象程序三大特性:封装、继承、多态。在类和对象阶段,主要研究的就是封装特性。

在了解封装之前,先来了解一下访问限定符。

访问限定符 

java中主要通过访问权限来实现封装 ,类可以将数据以及封装数据的方法结合在一起,而访问权限用来控制方法或者字段能否直接在类外边使用。

java提供了四种访问修饰符

封装 

封装就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。

封装的实现

  1. 将属性进行私有化private
  2. 提供一个公开的set方法,用于对属性判断并赋值
  3. 提供一个公开的get方法,用于获取属性的值

案例:

写一个设定工资的小程序,要求用户输入自己的姓名、年龄、工资信息。其中输入的名字要求为2~4个字,年龄为0~100之间,若想设置工资就必须输入身份验证。

public class Test{
    public static void main(String[] args) {
        SetSalary salary = new SetSalary();
        salary.setName("鸡,鸡");
        salary.setAge(20);
        salary.setSalary(3000.00);
        salary.print();
    }
}
class SetSalary{
    public String name;
    private int age;
    private Double salary;

    public SetSalary(){

    }

    public SetSalary(String name,int age,Double salary){
        this.name = name;
        this.age = age;
        this.salary = salary;
        //在构造器中加一道防护,因为用户可能通过构造器绕开检测机制
        this.setSalary(salary);
        this.setAge(age);
        this.setName(name);
    }

    public void setName(String name){
        if(name.length()>=2&&name.length()<=4){
            this.name = name;
        }else{
            System.out.println("你的名字不符合要求");
            System.exit(0);
        }
    }

    public void setAge(int age){
        if(age>0&&age<100){
            this.age = age;
        }else{
            System.out.println("你的年龄不符合要求");
            System.exit(0);
        }

    }

    public void setSalary(Double salary){
        System.out.println("请输入你的银行卡后四位");
        Scanner scan = new Scanner(System.in);
        String code = scan.nextLine();
        if(code.equals("1234")){
            this.salary = salary;
            System.out.println("输入成功");
        }else{
            System.out.println("输入不正确,不能设置");
            System.exit(0);
        }
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Double getSalary() {
        return salary;
    }

    public void print(){
        System.out.println("姓名:"+this.name+"\n"+"年龄:"+this.age+"\n"+"工资:"+this.salary);
    }
}

 封装的好处

  1. 可以隐藏实现细节
  2. 可以对数据进行验证,保证安全合理

继承

 现在有三个人,张三、李四、王五,若我们要描述他们的特点就需要将他们的一些属性抽取出来,用一个类来描述一个人。比如张三的身高175cm、体重60kg、喜欢看电影、他还会游泳。李四身高180cm、体重58kg、喜欢弹吉他、会打篮球。王五身高170cm、体重70kg、喜欢动漫、会制作鬼畜视频。

从上面我们发现若要描述他们,我们就需要创建三个类分别对应,但是这样做很麻烦,而且代码效率执行起来比较低。我们发现他们都有一些共同的属性:身高、体重、爱好,唯一不同的就是特长。那么我们只需要将他们的共同属性抽取出来,单独创建一个类,让张三、李四、王五的类去继承这个类就可以了,这样写起来代码简洁,逻辑性强,而且复用性高。

继承的概念

将若干类中相同的属性、方法抽取出来创建一个父类,其他的类不用在单独写共同的属性、方法。而只需要继承父类即可,继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。

 继承示意图

继承的基本语法 

class 子类 extends 父类{
    
}

继承演示

import java.util.Scanner;
public class Test{
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("哈巴狗");
        dog.setAge(2);
        dog.eat();
        dog.print();
        System.out.println("==========================");

        Cat cat = new Cat();
        cat.setAge(3);
        cat.setName("加菲猫");
        cat.sleep();
        cat.print();
    }
}
class Animal{
    String name;
    int age;

    public Animal(){

    }

    public Animal(String name,int age){
        this.age = age;
        this.name = name;
        this.setAge(age);
        this.setName(name);
    }

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

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

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

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

    public void eat(){
        System.out.println(name+"正在吃饭");
    }

    public void sleep(){
        System.out.println(name+"正在睡觉");
    }

    public void print(){
        System.out.println("名字:"+this.name+"\t"+"年龄:"+this.age);
    }
}

class Dog extends Animal{
    public void bark(){
        System.out.println(name+"汪汪汪叫");
    }
}
class Cat extends Animal{
    public void mew(){
        System.out.println(name+"喵喵叫");
    }
}

从上面可以看到,我们并没有在Dog、Cat类里面写吃饭、睡觉和打印信息的方法,而却可以调用这些方法,这就是继承,子类调用父类的方法。 

为了更好的了解继承,这里不得不提出一个关键字super

super关键字 

super代表父类的引用,用于访问父类的属性、方法、构造器 

基本语法:

1.访问父类的属性——super.属性名,但不能访问父类的private属性

2.访问父类的方法——super.方法名(参数列表),不能访问父类的private方法

3.访问父类的构造器——super(参数列表);注意只能放在构造器的第一句并且只能出现一句

下面分别演示三个基本语法

public class Test {
    public static void main(String[] args) {
        B b = new B();
    }
}
class A{
    public int n1 = 100;
    protected int n2 = 121;
    int n3 = 0;
    private int n4 = 99;

    public A(){

    }
    public A(String name){}
    public A(String naem,int age){}

    public void test1(){}
    protected void test2(){}
    void test3(){}
    private void test4(){}
}
class B extends A{
    //演示访问父类的属性,但不能访问父类的private属性
    public void test(){
        System.out.println(super.n1+" "+super.n2+" "+super.n3);//这里不能访问父类的n4属性,会报错
    }

    //演示访问父类的方法,不能访问父类的private方法
    public void hi(){
        super.test1();
        super.test2();
        super.test3();
        //这里不能访问父类的test4方法,会报错
    }

    //演示访问父类的构造器,注意只能放在构造器的第一句并且只能出现一句
    public B(){
//        super();
//        super("simth");
        super("jack",20);
    }
}

super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员 ,访问的时候遵循就近原则。

 演示调用子类中的某个成员(属性、方法)

public class Test {
    public static void main(String[] args) {
        B b = new B();
        b.bark();
    }
}
class Base{
    public void cal(){
        System.out.println("Base类的cal()方法被调用");
    }
}
class A extends Base{
    public void eat(){
        System.out.println("正在吃东西");
    }
}
class B extends A{
    public void bark(){
        cal();
    }
}

调用的规则就是:

下面的调用规则适合没有关键字和关键字为this的情况,若是super关键字则没有在本类找这个动作

1.先在本类找,如果有,则调用

2.本类如果没有,则在父类找(如果有,并且可以调用,则调用)

3.如果父类没有,则继续找父类的父类

若查找属性的过程中,找到了,但是不能访问,则报错。如果查找属性的过程中,没有找到,则提示属性不存在。

super和this的比较

区别点thissuper
1访问属性访问本类中的属性,如果本类没有此属性则从父类中继续查找从父类开始查找属性
2调用方法访问本类中的方法,如果本类没有此属性则从父类中继续查找从父类开始查找方法
3调用构造器调用本类构造器,必须放在构造器的首行调用父类的构造器,必须放在子类构造器的首行
4特殊表示当前对象子类中访问父类对象

继续回到继承

继承的细节问题 

1.子类必须调用父类的构造器,完成父类的初始化(在调用子类构造器中,隐藏了super关键字)

2.当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类中的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否者,编译不会通过

3.如果希望指定去调用父类的某个构造器,则显示的调用一下:super(参数列表)

继承在内存中的情况 

 下面以一个案例来展开继承在内存中的情况

public class Test {
    public static void main(String[] args) {
        Son son = new Son();
    }
}
class GrandPa{
    String name = "大头爷爷";
    String hobit = "旅游";
}
class Father extends GrandPa{
    String name = "大头爸爸";
    private int age = 20;
}
class Son extends Father{
    String name = "大头儿子";
}

分析:

第一步:在方法区中依次加载Object类、Grandpa类、Father类、Son类的信息

第二步:分别对Grandpa类、Father类、Son类中信息初始化并分配内存,将字符串在常量池中的地址返回给堆空间

第三步:将堆中的地址返回给son变量

我们可以看出子类继承父类,当创建子类对象好后,在内存中建立的是一种查找关系。

查找关系:

1.首先看子类是否有该属性或方法,有并且可以访问,则返回信息。

2.若子类没有,就看父类有没有这个属性,如果父类有该属性,并且可以访问,就返回信息

3.若父类没有,则按照2的查找关系依次向上找

注意若其中找到了,但不能访问就会报错。 

重写override 

重写:简单来说就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么就说子类的这个方法重写了或者覆盖了父类的方法。

案列演示重写

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
    }
}
class Animal{
    public void eat(){
        System.out.println("动物吃东西");
    }
}
class Cat extends Animal{
    public void eat(){//这个方法和父类的eat方法名称、返回类型、参数都一样
        System.out.println("小猫吃东西");
    }
}

重写的注意事项 

1.子类方法的形参列表、方法名称要和父类的形参列表、方法名称完全一样

2.子类方法的返回类型和父类方法的返回类型一样,或者是父类返回类型的子类,比如父类返回类型是Object,子类方法的返回类型是String

3.子类方法不能缩小父类方法的访问权限(public>procted>默认>private)

4.父类被static、private修饰的方法不能被重写

重写和重载的比较

名称发生范围方法名形参列表返回类型修饰符
重载本类必须一样类型,个数或者顺序至少有一个不同无需求无需求
重写父子类必须一样相同子类重写的方法,返回的类型和父类返回的类型一致,或者是其子类子类方法不能缩小父类方法的访问范围

多态 

通过一个案列来引出多态

编写一个程序,Master类中有一个feed(喂食)方法,可以完成主人给动物喂食的信息

package Poly;

class Master{
    private String name;
    public Master(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public void feed(Cat cat,Fish fish){
        System.out.println("主人"+name+"给"+cat.getName()+"吃"+fish.getName());        
    }
    public void feed(Dog dog,Bond bond){
        System.out.println("主人"+name+"给"+dog.getName()+"吃"+bond.getName());
    }
}

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
class Cat extends Animal{
    public Cat(String name){
        super(name);
    }
}
class Dog extends Animal{
    public Dog(String name){
        super(name);
    }
}

class Food{
    private String name;
    public Food(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
class Bone extends Food{
    public Bone(String name){
        super(name);
    }
}
class Fish extends Food{
    public Fish(String name){
        super(name);
    }
}
public class Test {
    public static void main(String[] args) {
        Master simth = new Master("simth");
        Dog dog = new Dog("哈巴狗");
        Bone bone = new Bone("排骨");
        simth.feed(dog,bone);
        System.out.println("=================");
        Cat cat = new Cat("大橘");
        Fish fish = new Fish("鲫鱼");
        simth.feed(cat,fish);
    }
}

 我们发现上面这样写也能解决问题,但是如果动物一多,而且吃得食物还不一样,比如再添加一个动物Pig,它要吃白菜。我们就又得单独为他创建feed方法,这样做就很麻烦。这里我们就不得不引入另外一种机制,多态。

多态:就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会出现不同的状态。

多态的实现条件:

1.必须在继承体系下

2.子类必须要对父类中方法进行重写

3.通过父类的引用调用重写的方法

多态的具体体现

 1).方法的多态

重写和重载体现多态

public class Override {
    public static void main(String[] args) {
        A a = new A();
        //方法的重载体现多态
        System.out.println(a.sum(1, 2));
        System.out.println(a.sum(1, 2, 3));
        B b = new B();
        //方法的重写体现多态
        a.say();
        b.say();
    }
}
class B{
    public void say(){
        System.out.println("B say()方法被调用");
    }
}
class A extends B{
    public int sum(int n1,int n2){
        return n1+n2;
    }
    public int sum(int n1,int n2,int n3){
        return n1+n2+n3;
    }
    public void say(){
        System.out.println("A say() 方法被调用");
    }
}

2).对象的多态 

重要知识:

1.一个对象的编译类型和运行类型可以不一样

2.编译类型在定义对象时,就确定了,不能改变

3.运行类型是可以改变的

编译类型看定义时=号的左边,运行类型看=的右边

public class Override {
    public static void main(String[] args) {
        Animal animal = new Cat();
        animal.cry();
        System.out.println("====================");
        animal = new Dog();
        animal.cry();
    }
}
class Animal{
    public void cry(){
        System.out.println("Animal cry() 动物在叫");
    }
}
class Cat extends Animal{
    public void cry(){
        System.out.println("Cat cry() 猫在叫");
    }
}
class Dog extends Animal{
    public void cry(){
        System.out.println("Dog cry() 狗在叫");
    }
}

上面animal的编译类型是Animal,运行类型分别是Cat、Dog,当调用方法的时候,也是调用运行类型相关的方法,这里就体现出来多态,编译类型必须一样,但运行类型可以有很多。 

通过多态现在就能解决主人喂食的问题了

只需要将喂食这个动作变成多态就行了

package Poly;

class Master{
    private String name;
    public Master(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public void feed(Animal animal,Food food){
        System.out.println("主人"+name+"给"+animal.getName()+"吃"+food.getName());         
    }
}

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
class Cat extends Animal{
    public Cat(String name){
        super(name);
    }
}
class Dog extends Animal{
    public Dog(String name){
        super(name);
    }
}

class Food{
    private String name;
    public Food(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
class Bone extends Food{
    public Bone(String name){
        super(name);
    }
}
class Fish extends Food{
    public Fish(String name){
        super(name);
    }
}

这样做我们即使现在再添加其他动物也能保证代码的复用性。

多态的注意事项 

  • 多态的前提是:两个对象(类)存在继承关系

多态的向上转型,本质:父类的引用指向了子类的对象

语法: 父类类型   引用名   =   new   子类类型();

特点:可以调用父类中的所有成员,不能调用子类中的特有成员(因为在编译阶段能调用那些成员,是由编译器类型决定的)

最终运行效果看子类的具体实现(调用方法和前面讲的继承的调用规则是一样的)

public class Override {
    public static void main(String[] args) {
        Animal animal = new Cat();//向上转型
        animal.eat();//这里输出猫吃鱼,说明查找还是先从子类开始
        animal.run();
        animal.show();
        //animal.sleep();报错:'sleep()' 在 'Animal' 中具有 private 访问权限
    }
}
class Animal{
    String name = "动物";
    int age = 10;
    private void sleep(){
        System.out.println("睡觉");
    }
    public void run(){
        System.out.println("跑");
    }
    public void eat(){
        System.out.println("吃");
    }
    public void show(){
        System.out.println("你好");
    }
}
class Cat extends Animal{
    public void eat(){
        System.out.println("猫吃鱼");
    }
    public void catchMouth(){
        System.out.println("猫抓老鼠");
    }
}
class Dog extends Animal{

}

  

多态的向下转型

语法:子类类型   引用名    =     (子类类型)   父类引用

要求父类的引用必须指向的是当前目标类型的对象,当向下转型后,可以调用子类类型中所有的成员

public static void main(String[] args){
    Object a = new Integer(5);//可以,这里发生了向上转型
    String str = (String)a;//不可以,将指向Integer的父类引用,转成String发生类型转化异常
    Integer b = (Integer)a;//可以,向下转型
}

JAVA中的动态绑定机制

  • 当调用对象方法的时候,该方法会和该对象的运行类型绑定
  • 当调用对象属性时,没有动态绑定机制,哪里声明哪里使用
public class Test {
    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.sum());//结果为40
        System.out.println(a.sum1());//结果为30
    }
}
class A{
    public int i = 10;
    public int sum(){
        return getI()+10;
    }
    public int sum1(){
        return i+10;
    }
    public int getI(){
        return i;
    }
}
class B extends A{
    public int i = 20;
    public int sum(){
        return i+20;
    }
    public int getI(){
        return i;
    }
    public int sum1(){
        return i+10;
    }
}

解析a.sum:

调用方法时有动态绑定机制,a的运行类型是B,则调用a.sum方法先在子类中查找,找到了而且这个方法不是子类特有的(父类中也有)。子类方法中的i就是本类中的20,因为属性没有动态绑定机制,所以返回的结果就是40。

解析a.sum1:

分析过程和上面一样,返回的结果是30

public class Test {
    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.sum());//结果为30
        System.out.println(a.sum1());//结果为20
    }
}
class A{
    public int i = 10;
    public int sum(){
        return getI()+10;
    }
    public int sum1(){
        return i+10;
    }
    public int getI(){
        return i;
    }
}
class B extends A{
    public int i = 20;

    public int getI(){
        return i;
    }
}

解析a.sum:

调用方法时有动态绑定机制,a的运行类型是B,则调用a.sum方法先在子类中查找,子类中没有,在去父类中查找,找到了。父类中a.sum方法返回值有getI方法,根据动态绑定机制,他就会去a的运行类型中查找,找到了,再根据属性没有动态绑定机制,所以i就是20。所以a.sum方法的返回值为30。

解析a.sum1:

调用方法时有动态绑定机制,a的运行类型是B,则调用a.sum方法先在子类中查找,子类中没有,在去父类中查找,找到了。再根据属性没有动态绑定机制,所以i就是10。所以a.sum1方法的返回值为20。

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

咸鱼吐泡泡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值