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

(一)封装

(1)什么是封装

  当我们需要安全地访问对象时,例如限制给对象赋值的范围(避免数据类型的不同或者数据范围超出预计等),就需要使用封装技术。

  封装就是将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法(getter和setter)来实现对隐藏信息的操作和访问。

(2)封装的步骤
  • 使用关键字private对类的属性进行隐藏(方法一般不用隐藏);
  • 利用setter / getter方法对属性值进行操作;
  • 可以在方法中加入条件控制语句,进行限制。
(3)封装示例
/**
 * 用户实体
 * 包括用户编号,姓名,用户名,密码,用户权限等级
 */
public class User {
    private int id;             //用户id
    private String name;        //姓名
    private String unm;         //用户名
    private String pwd;         //密码
    private int level;          //用户权限等级

    public User() {
    }

    public User(int id, String name, String unm, String pwd, int level) {
        this.id = id;
        this.name = name;
        this.unm = unm;
        this.pwd = pwd;
        this.level = level;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getUnm() {
        return unm;
    }

    public void setUnm(String unm) {
        this.unm = unm;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }
}
(4)this关键字

  在使用setter方法的时候,会用到this关键字。这是因为方法的形参和类的属性重名,为了区分而使用的。

  属性与局部变量(形参就是一种局部变量)若同名,则属性会被局部变量所覆盖、无法在该方法内起作用。此时,若想在该方法内使用被覆盖的成员变量,必须使用this或类名作为调用者来访问。

(二)继承

(1)概述

  继承是面向对象的三大特征之一,可以使得子类具有父类的属性和方法。另外,还可以在子类中重新定义、追加属性和方法。

  继承是在原有类的基础上,进行功能扩展,创建新的类型。

(2)注意事项
  • Java中的类只有单继承(即一个类只能有一个父类),没有多继承(即一个类不能同时有多个父类);
  • Java中的类支持多层继承(即允许A继承B、B继承C……);
  • 继承是类和类之间的一种关系。此外,类和类之间的关系还有依赖、组合、聚合等;
  • 继承关系的两个类,一个为子类(派生类),一个为父类(基类 / 超类)。子类继承父类,使用关键字extends来表示。
    • 子类和父类之间,从意义上讲应该具有子类 is a 父类的关系;
    • extends的意思是“扩展”,子类是父类的扩展。
  • 当调用子类中的一个成员变量 / 成员方法时,最先在子类中寻找,如果没找到就再去父类中寻找,如果都没有找到就报错。(即,不会再去父类的父类中寻找——这点也很好理解,如果父类的父类中有的话,父类会继承过来、不可能找不到)
(3)语法

  继承的语法格式:

public class 子类名 extends 父类名 {
    //...
}

  例子:

//父类
public class Person {
    public int money = 1000;
    public void say() {
        System.out.println("说...");
    }
}

//子类
public class Student extends Person {
    
}

//执行
public class Test {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.say();
        System.out.println(stu.money);
    }
}

  可见,虽然在子类Student中并没有定义属性、方法,但由于Student类继承了父类Person,因此Student类中自然就含有了父类当中的属性、方法,可以当成自己的属性、方法一样去使用。

(4)访问修饰符

  Java访问修饰符是用来控制类、方法和变量的访问权限。

  Java中一共有4种访问修饰符,分别是:

  • public:公共访问修饰符;
  • protected:保护访问修饰符;
  • default:默认访问修饰符(也就是啥也不写);
  • private:私有访问修饰符。
1.public

  范围:不同类、不同包下都可以任意访问。

2.protected

  范围:①本类内部;②子类内部;③与继承关系之中的父类处于同一个包中的其他类。

  例如,A是父类(A中定义了一个protected的属性),B是子类,B类继承A类,那么,

  • 在A类自己的类内部,可以随意访问该protected属性;
  • 在子类B自己的内部,可以随机访问该protected属性;
  • 有另一个C类,想通过new实例化对象的方式对该protected进行访问,那么它需要new一个A类,或者new一个B类。但此时,C类如果与A类在同一个包下,则能够通过对象.属性的方式访问到;反之,若C类与A类不在同一个包下,则访问不到该protected属性。
3.default

  当定义变量、方法及类时,若不写访问修饰符,我们称之为默认的访问修饰符,用一个词来形容就叫default。

  范围:①本类内部;②同一个包下的其他类。——即,专门针对本包访问而设计。

  注意:在接口中,默认的访问修饰符是public(即,你不写访问修饰符时,它会自动认为是public)。

4.private

  范围:只能在当前类内部访问,其他地方一律访问不到(无论是同包、跨包,无论是不是子类)。

(5)super关键字

  super关键字的用法和this关键字的用法类似。

  • this:代表对本类对象的引用;
  • super:代表对父类对象的引用。
关键字访问成员变量访问构造方法访问成员方法
thisthis.成员变量this([参数列表])this.成员方法([参数列表])
supersuper.成员变量super([参数列表])super.成员方法([参数列表])
(6)方法重写(Override)
1.概念

  子类中出现了和父类中一模一样的方法声明。

2.应用场景

  当子类需要父类的功能,而在此基础上子类又有自己特有的内容时,可以重写父类中的方法。这样,既沿袭了父类的功能,又定义了子类特有的内容。

3.注解

  @Override是一个注解,可以帮助我们检查重写方法的方法声明的正确性。

4.注意事项
  • 子类重写父类的方法,重写后的方法的访问权限不能比父类的更低。(即子类重写的方法不能比父类具有更严格的访问权限)
  • 子类重写父类的方法,必须是一模一样的方法声明。即,
    • 方法名不能修改;
    • 参数列表不能修改;
    • 返回值,如果是基本数据类型,则不能修改;如果是引用数据类型,则可以是原本返回值的子类(这一点涉及到多态的知识点)。
5.重写与重载的区别

  重写(Override)一定是发生在继承关系中,至少伴随着2个类,是子类把父类中具有相同声明(方法名相同、参数列表相同、返回值相同或为子类)的方法重新写一遍。

  重载(Overload)一定是发生在同一个类下的,与返回值无关,方法名不能修改,且参数列表必须不一样(形参个数不一样、形参类型不一样、形参顺序不一样)。

注意

  子类继承父类后,在子类中可以对从父类继承过来的方法进行重载。这样一来,两个方法看似是处于两个类中,似乎与“重载一定发生在同一个类下”相矛盾。实际上并不矛盾,子类继承了父类,实际上父类的那个方法已经隐式地存在于子类当中了,因此仍然是在同一个类下进行方法重载。

(7)继承中的构造方法

  父类的构造函数不能被子类继承。

  子类中所有的构造方法,默认都会先访问父类的无参构造方法。——即,在子类的每一个构造方法中,都会隐式地默认在开头处执行一行super();

  很好理解,由于子类要继承父类中的数据,所以,父类必须先初始化、开辟堆空间,否则子类去哪访问、操作父类的属性或方法呢?

  至此,再结合“构造函数”本身的相关知识点,我们可以分析出以下几个特殊情况的原理:

  1.如果在父类中手动进行了含参构造函数的定义,则此时系统默认提供的无参构造函数就不存在了,从而会导致子类的构造方法报错(因为子类调用不到父类的无参构造函数了,从而无法使父类初始化)。分析其原理,可知两种解决办法:①手动再给父类定义一个无参构造函数(推荐);②使用父类中定义的含参构造函数对父类进行初始化。即,在子类构造方法的第一行处,手动调用父类的含参构造函数super(形参列表);

  2.会出现一个现象,我们在实例化一个子类对象时,子类的构造函数会自动执行。但与此同时,父类的构造函数也执行了,且在子类的之前。例子如下:

class Animal {
    public Animal() {
        System.out.println("I am Animal.");
    }
}

class Dog {
    public Dog() {
        System.out.println("I am Dog.")
    }
}

  此时,如果实例化对象,执行Dog d = new Dog();,则运行结果如下:

I am Animal.
I am Dog.

(三)多态

(1)概念

  简单地说就是一个抽象的对象有多种具体的形态。

  具体来说就是父类中的方法被子类继承后,可以通过子类重写,使之在各个子类中具备不同的含义和作用。

  举例:

class Animal {
    public void eat() {
        System.out.println("吃东西");
    }
}

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

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

  上述代码中,父类中同一个eat()方法在不同子类中表现出不同的行为,就是多态。

(2)条件

  三大条件:

  • 继承:必须存在继承;
  • 重写:子类必须对父类中的方法进行重写;
  • 向上转型:父类引用指向子类对象。
(3)向上转型与向下转型
1.向上转型

  向上转型(upcasting)是指将一个子类的对象引用赋值给其父类类型的引用变量。这是在面向对象编程中的一种常见操作,用于实现多态性的灵活的对象处理。

  在向上转型中,子类对象可以被视为父类对象(就像byte b = 1; int i = b;一样,b就被视为int、按照int来继续进行后续操作了),可以使用父类类型的引用变量来引用子类对象。这样做的好处是可以以统一的方式处理不同类型的对象,实现代码的灵活性和可扩展性。

  特点:

  • 子类对象可以隐式地转型为父类对象,不需要任何显式的类型转换操作;
  • 父类引用变量可以引用子类对象,但通过父类引用变量只能访问到子类对象中定义的父类成员,无法访问子类独有的成员。如果确实要访问,有两种方法:
    • 向下转型转回去。如:Animal ani = new Dog();,再向下转型Dog dog = (Dog) ani;
    • 不向上转型,直接自己new自己。如:Dog dog = new Dog();
  • 子类对象中重写的方法,在通过父类引用变量调用时,会调用子类中的实现。
  • 向上转型是安全的操作,因为子类对象本身就是一个父类对象。

  举例:

class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}

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

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

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();		//父类的引用指向子类的对象
        Animal cat = new Cat();		//父类的引用指向子类的对象
        
        dog.eat();
        cat.eat();
    }
}
2.向下转型

  向下转型(downcasting)是指将一个父类类型的引用变量转换为其子类类型的引用变量。它与向上转型相反,需要进行显式的类型转换操作。

  在某些情况下,当一个对象被向上转型后,它的具体类型信息会丢失,只保留了父类类型的信息。如果我们需要访问子类中特有的成员,就需要使用向下转型。

  需要注意的是,向下转型是有风险的,因为转换的对象必须是实际上的子类对象才能成功,否则会在运行时抛出ClassCastException异常。

  举例:

class Animal {
    public void eat() {
        System.out.println("Animal is eating.");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating.")
    }
    
    //子类独有的方法
    public void bark() {
        System.out.println("Dog is barking.")
    }
}

public class Main {
    public static void main(String[] args) {
        Animal ani = new Dog();		//向上转型
        
        //使用向下转型之前,需要先检查对象是否实际上是子类的实例
        //错误示例:
        //从语法上是对的,符合强制类型转换。但一运行就会报错(ClassCastException),因为类型不匹配。
        //Cat cat = (Cat) ani;
        if(ani instanceof Dog) {
            Dog dog = (Dog) ani;		//向下转型
            dog.bark();
        } else {
            System.out.println("ani is not an instance of Dog.")
        }
    }
}

  说明:

  instance运算符用来检查ani是否是Dog类的实例,如果是,返回true,否则返回false,以此确保向下转型时的类型安全。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

秋秋秋叶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值