Java面向对象高级:封装、继承和多态

一、封装

1. 封装介绍

封装指的是属性私有化,即隐藏具体属性和实现细节,仅对外开放接口,控制程序中属性的访问级别;使用类设计对象时,将需要处理的数据以及处理这些数据的方法,设计到对象中。

封装目的:
a. 更好的维护数据。
b. 使用者无需关心内部实现,只要知道如何使用即可。

封装的设计规范:合理隐藏,合理暴露。

2. 权限修饰符

  • private:同一个类中
public class Student {
    private int age;
    
    private void show(){
        System.out.println(age);//在同一个类中才可以访问属性age
    }
}

测试:

public class StudentTest {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.age; //报错,访问不到成员变量age
        stu.show(); //报错,访问不到成员方法show()
    }
}
  • (defalut):同一个类中,同一个包中
  • protected:同一个类中,同一个包中,不同包的子类(与继承相关)
  • public:任意位置访问

权限修饰符可以修饰成员变量和成员方法,不同的权限修饰符修饰的权限访问大小是不一样的。

3. 私有成员变量的设置和获取

私有成员变量是为了保证数据的安全性,但是在主方法中却不能通过 对象名. 的方式来访问私有成员变量,所以这边针对私有成员变量,提供对应的 setXxx 和 getXxx 方法。

例如:

public class Student {
    //私有成员变量
    private int age;
    //设置年龄的方法
    public void setAge(int age){
        //可以判别获取的年龄是否是合理数据
        if (age > 0 & age < 150){
            this.age = age;//给成员变量age赋值
        }
        else {
            System.out.println("年龄有误!");
        }
    }
    //返回年龄的方法
    public int getAge(){
        return age;
    }
}

测试:

public class StudentTest {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.setAge(18);
        int age = stu.getAge();
        System.out.println(age);
    }
}

运行结果为:

18

4. 标准 JavaBean

JavaBean 就是实体类,即封装数据的类。

这个类中的成员变量都要私有,并且要对外提供相应的 getXxx 和 setXxx 方法,此外该类中也需要提供无参、带参构造方法。

实体类的应用场景:实体类只负责数据存取,而对数据的处理交给其它类来完成,以实现数据和数据业务处理相分离。

例如:

public class Student {
    //私有成员变量
    private String name;
    private int age;

    //空参构造方法
    public Student() {
    }

    //带参构造方法
    public Student(String name, int age) {
        this.name = name;
        this.age = 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;
    }
}

二、继承

1. 继承介绍

让类与类之间产生关系(子父类关系),子类可以直接使用父类中非私有的成员。

继承的格式:

  • 格式:public class 子类名 extends 父类名 { }
  • 范例:public class Zi extends Fu { }
  • Fu:是父类,也被称为基类、超类
  • Zi:是子类,也被称为派生类

什么时候使用继承:

  • 当类与类之间,存在相同(共性)的内容,并且产生了 is a 的关系,就可以考虑使用继承,来优化代码。
  • 例如:
    在这里插入图片描述

2. 继承中成员的访问特点

成员变量

子父类中,如果出现了重名的成员变量,使用的时候根据就近原则,优先使用子类的成员变量。

父类:

public class Fu {
    int num = 10;
}

子类:

public class Zi extends Fu {
    int num = 20;

    public void method(){
        System.out.println(num);
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.method();//运行结果为20
    }
}

但是如果一定要使用父类中的重名成员变量,可以使用 super 关键字:

public class Zi extends Fu {
    int num = 20;

    public void method(){
        System.out.println(super.num);//此时测试类中再次调用该方法时运行的结果为10
    }
}

成员方法

子类继承了父类的父类之后,子类仍然可以继续定义自己的方法。

子父类中,如果出现了方法声明一模一样的方法(方法名,参数,返回值),在创建子类对象,调用方法的时候,会优先使用子类的方法逻辑,这虽然是就近原则的现象,但其实是子类的方法对父类的方法进行了重写操作:

  • 方法重写的前提:必须要存在继承的关系
  • 方法重写:子类中,出现了同父类方法声明一模一样的方法(方法名,参数,返回值)
public class Test {
    public static void main(String[] args) {
        Son s = new Son();
        s.method();//运行结果为 Son...method
    }
}

class Father {
    public void method(){
        System.out.println("Father...method");
    }
}

class Son extends Father {
    public void method(){
        System.out.println("Son...method");//方法重写
    }
}
  • 方法重写的使用场景:子类需要父类的方法,且想对父类的方法逻辑进行修改或者增强,就可以对父类的方法进行重写
public class Test {
    public static void main(String[] args) {
        Son s = new Son();
        s.method();
        /*
        	运行结果为:Father...method
        	           Son...method
        */
    }
}

class Father {
    public void method(){
        System.out.println("Father...method");
    }
}

class Son extends Father {
    public void method(){
        super.method();//仍需要父类的方法
        System.out.println("Son...method");//对父类的方法逻辑进行增强
    }
}
  • 方法重写的注意事项:
    父类中私有方法不能被重写
    子类重写父类方法时,访问权限必须大于等于父类

权限修饰符访问权限:

在这里插入图片描述

注意与方法重载区分:

方法重载的要求:

  • 方法名称必须相同
  • 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)
  • 方法的返回类型可以相同也可以不相同
  • 仅仅返回类型不同不足以成为方法的重载

构造方法

  • 子类不能继承父类的构造方法,子类需要手动编写构造方法
  • 子类在初始化之前,需要先完成父类的初始化,其实在除了 Object 类以外的所有的构造方法中,都默认隐藏了一句代码: super(); 通过这句代码可以访问父类的空参构造方法。
  • Java当中所有的类,都直接或者间接的继承了 Object 类。

下面通过内存图来理解:
在这里插入图片描述
首先主方法进入栈内存,接着创建对象 stu,通过 new 在堆内存中开辟了一块空间,该空间内部存放了 Student 类的成员变量 score,以及从 Person 类继承的成员变量 name 和 age,虽然 name 和 age 是 Person 类的私有成员变量,但是 Student 类可以继承,只是无法直接访问

在这里插入图片描述
然后调用 Student 类的构造方法进行传值,Student 类的构造方法进入栈内存,其中 super(name,age)访问父类的带参构造方法,通过父类进行初始化,所以 Person 类的构造方法进入栈内存,以此来给成员变量 name 和 age 赋值。

在这里插入图片描述
之后 Person 类的构造方法弹栈消失,继续给 Student 类的成员变量 score 赋值,随后 Student 类的构造方法弹栈消失,至此堆内存中的所有变量都已经赋值完毕。

在这里插入图片描述
最后把堆内存中 Student 类开辟空间的地址交给栈内存中的对象 stu,这样通过 stu 就可以找到 Student 类的各个成员变量。

3. super 关键字

代表父类存储空间的标识

应用范围

只能用于子类的构造函数和实例方法中,不能用于子类的静态方法中。
why:因为 super 指代的是一个父类的对象,它需要在运行时被创建,而静态方法是类方法,它是类的一部分。当类被加载时,方法已经存在,但是这时候父类对象还没有被初始化。

应用场景

a. 在子类中调用父类的成员变量或成员方法: 如果子父类有相同的成员变量或者成员方法,那么想调用父类的成员变量或者成员方法,就必须使用 super 关键字。

b. 在子类中指代父类的构造方法: 在 Java 中,子类是父类的派生类,子类的实例化依赖于父类的实例化。所以它的任何一个构造函数都必须要初始化父类,Java 就是使用 super 关键字来调用父类的构造方法来完成这个操作。

例如:

public class Test {
    public static void main(String[] args) {
        Zi z = new Zi();
    }
}
class Fu {
    public Fu() {
        System.out.println("Fu...构造方法");
    }
}
class Zi extends Fu {
    public Zi() {
        /*
        	隐藏代码 调用了父类的无参构造方法
        	super(); 必须要在子类构造方法的第一行
        */
        System.out.println("Zi...构造方法");
    }
}

运行结果为:

Fu...构造方法
Zi...构造方法

在子类的构造方法中,如果没有显式调用 super 来初始化父类的话,那么 Java 会隐式的调用 super();注意 Java 只会隐式的调用无参构造函数,如果父类没有无参构造函数,那么子类中就必须显示的调用 super 关键字来调用已有的有参构造函数来初始化父类。

public class Test {
    public static void main(String[] args) {
        Zi z = new Zi();
    }
}
class Fu {
    public Fu(String name) {
        System.out.println("Fu...有参构造方法");
    }
}
class Zi extends Fu {
    public Zi() {
        super("name");//显式调用已有的有参构造函数来初始化父类
        System.out.println("Zi...无参构造方法");
    }
}

运行结果为:

Fu...有参构造方法
Zi...无参构造方法

super 调用父类成员的省略规则

super . 父类成员变量 | super . 父类成员方法 ( ) - - - > 被调用的变量和方法在子类中不存在,super.可以直接省略

this 和 super

super 注意点:

  • super 调用父类的构造方法,必须在构造方法的第一个
  • super 必须只能出现在子类的方法或者构造方法中
  • super 和 this 不能同时调用构造方法 - - - > 两者不能共存

Vs this:

  • 代表的对象不同:

    • this:代表本类对象的引用
    • super:代表父类对象的引用
  • 前提不同:

    • this:没有继承关系也可以使用
    • super:只有在继承关系下才可以使用
  • 构造方法不同:

    • this( ):本类的构造方法
    • super( ):父类的构造方法

三、抽象类和抽象方法

抽象类是一种特殊的父类,内部可以编写抽象方法。

1. 抽象类和抽象方法的介绍

  • 抽象方法:将共性的行为(方法)抽取到父类之后,发现该方法的实现逻辑无法在父类中给出具体明确,该方法就可以定义为抽象方法。
  • 抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类。

2. 抽象类和抽象方法的定义格式

  • 抽象方法的定义格式:public abstract 返回值类型 方法名(参数列表);
  • 抽象类的定义格式:public abstract class 类名{ }

例如:定义一个员工类,员工类中存在一个 work 方法,由于不同的员工有着不同的工作,所以 work 方法可以定义为抽象方法,员工类也可以定义为抽象类。

public abstract class Employee {
    public abstract void work();
}

程序员类:

public class Coder extends Employee {
    //重写抽象方法
    public void work() {
        System.out.println("打代码");
    }
}

项目经理类:

class Manger extends Employee {
    //重写抽象方法
    public void work() {
        System.out.println("项目管理");
    }
}

3. 注意事项

  • 抽象类不能实例化
  • 抽象类存在构造方法,这样子类通过 super 可以访问
  • 抽象类中可以存在普通方法,可以让子类继承到从而继续使用
  • 抽象类的子类:要么重写抽象类中的所有抽象方法;要么是抽象类

4. abstract 关键字的冲突

  • final:被 abstract 修饰的方法,强制要求子类重写,被 final 修饰的方法子类不能重写
  • private:被 abstract 修饰的方法,强制要求子类重写,被 private 修饰的方法子类不能重写
  • static:被 static 修饰的方法可以类名调用,类名调用抽象方法没有意义

四、接口

1. 接口的介绍

体现的思想是对规则的声明,Java 中的接口更多体现的是对行为的抽象。
如果一个类没有成员变量,没有普通方法,只有抽象方法,一般将该类设计为接口。

  • 接口用关键字 interface 来定义
    具体的定义格式为:interface 接口名 { }
  • 接口不能实例化,接口和类之间是实现关系,通过 implements 关键字来表示
    具体的定义格式为:public class 类名 implements 接口名 { }
  • 实现类(接口的子类)需要重写接口中的所有抽象方法

例如定义一个接口:

interface Inter {
    public abstract void show();
    public abstract void method();
}

实现类:

public class InterImpl implements Inter {//重写所有的抽象方法

    @Override
    public void show() {
        System.out.println("show...");
    }

    @Override
    public void method() {
        System.out.println("method...");
    }
}

测试类:

public class InterfaceDemo01 {
    public static void main(String[] args) {
        InterImpl i = new InterImpl();
        i.show();
        i.method();
    }
}

运行结果为:

show...
method...

2. 接口中的成员特点

  • 成员变量:只能是常量,默认修饰符为 public static final
  • 构造方法:没有
  • 成员方法:只能是抽象方法,默认修饰符为 public abstract

3. 类和接口之间的关系

  • 类和类的关系:继承关系,只能单继承,但是可以多层继承
  • 类和接口的关系:实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
  • 接口和接口的关系:继承关系,可以单继承,也可以多继承

4. 抽象类和接口的对比

  • 成员变量:
    抽象类:可以定义变量,也可以定义常量
    接口:只能定义常量
  • 成员方法:
    抽象类:可以是定义具体方法,也可以定义抽象方法
    接口:只能定义抽象方法
  • 构造方法:
    抽象类:有
    接口:没有

五、多态

1. 多态介绍

同一个行为具有多个不同表现形式或形态的能力

多态前提:

  • 有继承或者实现关系
  • 有方法重写
  • 有父类引用指向子类对象

2. 对象多态

方法的形参可以定义为父类类型,那么该方法就可以接收到该父类的任意子类对象

通过例子来理解,首先创建一个动物 Animal 类,内部包含一个抽象方法 eat 方法:

public abstract class Animal {
    public abstract void eat();
}

创建 Dog 子类:

public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃肉");
    }
}

创建 Cat 子类:

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

测试类:

public class PolymorphismDemo01 {
    public static void main(String[] args) {
        useAnimal(new Dog());
        useAnimal(new Cat());//对象多态
    }

    public static void useAnimal(Animal a) {
        /*
            相当于Animal a = new Dog()
            等号左边是父类引用
            等号右边是子类对象 --->满足多态的前提
         */
    }
}

3. 行为多态

同一个行为具有多个不同表现形式或形态的能力

仍以对象多态的例子来说明,修改测试类:

public class PolymorphismDemo01 {
    public static void main(String[] args) {
        useAnimal(new Dog());
        useAnimal(new Cat());
    }

    public static void useAnimal(Animal a) {
       a.eat();//同一个行为具有多个不同表现形式或形态的能力
    }
}

运行结果为:

狗吃肉
猫吃鱼

4. 多态的成员访问特点

  • 成员变量:编译看左边(父类),运行看左边(父类)
  • 成员方法:编译看左边(父类),运行看右边(子类)

例如:

public class PolymorphismDemo02 {
    public static void main(String[] args) {
        Fu f = new Zi();//父类的引用指向子类的对象
        /*
        	多态的成员访问成员变量:
        	编译时看父类是否存在变量num,如果不存在则报错
        	运行时输出的是父类中num的值
        */
        System.out.println(f.num);
        /*
        	多态的成员访问成员方法:
        	编译时看父类是否存在方法show,如果不存在则报错
        	运行时执行子类的show的方法逻辑
        */
        f.show();
    }
}

class Fu {
    int num = 10;

    public void show() {
        System.out.println("Fu...show");
    }
}

class Zi extends Fu {
    int num = 20;

    public void show() {
        System.out.println("Zi...show");
    }
}

运行结果为:

10
Zi...show

需要注意,多态创建对象,调用静态成员的情况:
因为静态的成员推荐类名进行调用,所以在生成字节码文件后,会自动将对象名调用,改成类名调用,所以谁访问的静态成员,就执行谁的代码逻辑。

6. 多态的好处和弊端

  • 多态的好处:提高了程序的扩展性
  • 多态的弊端:不能使用子类的特有成员(因为编译时需要看父类是否具有该成员)

7. 多态中的转型

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

  • 向上转型:多态本身就是向上转型的过程

    • 使用格式:父类类型 变量名 = new 子类类型(); - - - > Fu f = new Zi();
  • 向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用类型转为子类引用各类型

    • 使用格式:子类类型 变量名 =(子类类型) 父类类型的变量; - - - > Zi z = (Zi) f;
    • 适用场景:当要使用子类特有功能时

多态中的转型问题:如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现 ClassCastException
解决方法就是使用 instanceof 关键字

8. instanceof 关键字

  • 使用格式:对象名 instanceof 类型
  • 判断一个对象是否是一个类的实例(即判断关键字左边的对象,是否是右边的类型)
  • 注意: 返回类型为布尔类型

例如:

public class Application {
    public static void main(String[] args) {

        // Object > String
        // Object > Person > Teacher
        // Object > Person > Student
        Object object = new Student();

        System.out.println(object instanceof Student);//true
        System.out.println(object instanceof Person);//true
        System.out.println(object instanceof Object);//true
        System.out.println(object instanceof Teacher);//false
        System.out.println(object instanceof String);//false

        System.out.println("==================================");
        Person person = new Student();

        System.out.println(person instanceof Student);//true
        System.out.println(person instanceof Person);//true
        System.out.println(person instanceof Object);//true
        System.out.println(person instanceof Teacher);//false
        // System.out.println(person instanceof String); 编译报错!

        System.out.println("==================================");
        Student student = new Student();

        System.out.println(student instanceof Student);//true
        System.out.println(student instanceof Person);//true
        System.out.println(student instanceof Object);//true
        // System.out.println(student instanceof Teacher); 编译报错!
        // System.out.println(student instanceof String); 编译报错!

    }
}
class Person{}
class Student extends Person{}
class Teacher extends Person{}

运行结果为:

true
true
true
false
false
==================================
true
true
true
false
==================================
true
true
true
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java作为一种面向对象的编程语言,具有封装继承多态这三个重要概念。 首先,封装是指将数据和对数据的操作封装在一个类中,隐藏了实现的细节。通过封装,我们可以将数据的访问权限进行控制,从而保证数据的安全性和完整性。通过提供公共方法,我们可以控制对象对数据的访问方式,使得使用者只能通过指定的方法来访问和修改数据,避免了不恰当的操作。 其次,继承是指子类可以继承父类的属性和方法,可以实现代码的复用。通过继承,我们可以建立类之间的层次关系,并且在子类中可以重写父类的方法,实现对方法的扩展和改进。继承还可以提高代码的可维护性和可扩展性,当需要修改或新增功能时,只需要在相应的子类中进行修改或扩展,而不需要修改所有的类。 最后,多态是指同一个方法可以根据不同的对象产生不同的行为。多态性可以提高代码的灵活性和可扩展性,使代码更易于理解和维护。通过多态,我们可以对不同对象使用相同的方法和接口,使得代码的逻辑更清晰,减少条件判断和重复的代码。 综上所述,封装继承多态Java面向对象编程中的重要概念。封装可以保证数据的安全性和完整性,继承可以实现代码的复用和功能的扩展,多态可以提高代码的灵活性和可扩展性。掌握这三个概念,可以帮助我们更好地设计和实现面向对象的程序,提高代码的质量和效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值