欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

JavaSE-06面向对象的三大特征:封装、继承、多态,类就是一种封装:类就

来源: javaer 分享于  点击 8975 次 点评:215

JavaSE-06面向对象的三大特征:封装、继承、多态,类就是一种封装:类就


JavaSE-06面向对象的三大特征:封装、继承、多态

  • [ 任务列表 ]
  • 1.封装
  • 2.继承
  • 2.1.权限修饰符
  • 2.2.继承的特点
  • 2.3.在子类中访问父类成员
  • 2.4.方法重写
  • 2.5.方法重载
  • 2.6.子类构造器
  • 2.7.兄弟构造器
  • 3.多态
  • 3.1.对象多态
  • 3.2.行为多态
  • 3.3.多态下的类型转换——调用子类的功能
  • 3.4.强制类型转换前的判断——instanceof关键字
  • 4.其他
  • 4.1.祖宗类Object
  • 4.2.开发规范
  • 4.3.解耦合
  • 4.4.lombok注解@Data
  • 4.5.方法重载和方法重写的区别
  • 5.参考资料

1.封装

  • 面向对象的三大特征:封装、继承、多态。

  • 类就是一种封装:类就是把要处理的对象的数据,以及对这些数据进行处理的方法封装成一个架子。
    例如:日常中,手机、洗衣机、汽车……等都是封装。

  • 封装的设计要求:合理隐藏,合理暴漏。

  • 哪些需要隐藏,哪些需要暴露,怎么隐藏,怎么暴露?
    如何隐藏:使用private关键字修饰成员变量,就只能在本类中访问,其他任何地方不能直接访问。
    如何暴露(合理暴露):使用public修饰(公开)的getter和setter方法合理暴露。

  • 面向对象的高级语法:继承、多态。

2.继承

  • 面向对象的高级语法之一:继承。

  • 什么是继承?为什么要有继承?
    定义的javabean实体类中会有大量的重复代码,怎么把重复的代码抽出来,让其他实体类共同使用呢?——即所有人都叫他爸爸。(减少一些重复代码的使用)

  • Java中提供了一个关键字extends,用这个关键字,可以让一个类和另一个类建立起父子关系。这就是继承。

public class B  extends A{
}
// A类称为父类(基类、超类)
// B类称为子类(派生类)
  • 子类能继承啥?
    子类能继承父类的非私有成员(成员变量、成员方法)

  • 继承后对象的创建:
    子类的对象是由子类、父类共同完成的
    子类对象其实是由子类和父类多张设计图共同创建出来的对象,所以子类对象是完整的。

2.1.权限修饰符

  • 什么是权限修饰符?
    就是用来限制类中的成员(成员变量、成员方法、构造器)能够被访问的范围。

  • 权限修饰符:

    • public:公开访问。
    • protected:同一包内或者不同包中的子类可访问。即本类,同一个包中的类,子孙类。
    • 默认(无修饰符):仅限于同一个包内的类访问。即本类,同一个包中的其他类。
    • private:仅限于本类内部访问。

    其中,public > protected > 默认(无修饰符)> private

  • 权限修饰符的常用情境:
    成员变量大概率会private修饰,
    成员方法大概率会public修饰,
    而缺省和protected一般不会使用。

2.2.继承的特点

  • 继承的特点:
    单继承,一个类只能继承一个父类,只能有一个爸爸;
    多层继承,Java不支持多继承,但支持多层继承,就是爸爸也会继承;
  • Java中的类为什么不支持多继承?
    用反证法,假设A、B中都有个method方法,A是复习语文的,B是复习数学的
    假如C可以同时继承 A和B,如果用C创建对象,C可以调用父类的方法,这是C就不知道去调用A还是B的方法。
  • 继承后子类访问成员的特点:就近原则
    ① 在子类中访问其他成员,是依照就近原则的,先在子类局部范围找,然后子类成员范围找,然后父类成员范围找,如果父类范围还没有找到则报错。
    ② 如果子父类中,出现了重名的成员,会优先使用子类的成员。

2.3.在子类中访问父类成员

  • 如果此时一定要在子类中使用父类的成员怎么办?
    可以通过super关键字,指定访问父类的成员。

    super.父类成员变量;
    super.父类成员方法;
    

2.4.方法重写

  • 方法重写(Overriding):
    当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时(即子类重新定义了父类中的一个已有的方法),子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
  • 方法重写规范:
    1. 方法名称,形参列表必须一样,这个方法就是方法重写。
    2. @Override,方法重写的校验注解(标志):要求方法名称和形参列表必须与被重写方法一致,否则报错!(更安全,可读性更好,更优雅)
    3. 子类重写父类的方法时,访问权限必须大于或者等于父类该方法的权限,即不能从public变为protected或private,并且抛出的异常范围也应保持一致或更小。(public > protected > 缺省)
    4. 重写的方法,返回值类型必须与被重写方法的返回值类型一样,或者范围更小;
    5. 私有方法(private),静态方法(static修饰)不能被重写,如果重写会报错;
    6. 通常用于实现多态性,允许使用父类类型的引用指向子类对象,并调用子类中重写的方法;
    7. 重写的规范:声明不变,重新实现。
public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        // 实现多态性 可以这么写:
        // Animal cat = new Cat();
        cat.cry();
    }
}

class Animal{
    public void cry(){
        System.out.println("动物会叫~~~ ");
    }
}

class Cat extends Animal{   // Cat类继承Animal类
    // 重写的规范:声明不变,重新实现。
    @Override // 方法重写的校验注解(标志)
    public void cry(){   // 方法重写:方法名称,形参列表必须一样
        System.out.println("猫 喵喵喵的叫~~~");
    }
}
  • 方法重写的常见应用场景:
    子类重写Object类的toString()方法,以便返回对象的内容。
    当返回的对象是一个地址时,说明没有重写toString方法;
    当返回的对象是一个对象内容时代表重写了toString方法。
public class Test2 {
    public static void main(String[] args) {
        Student s = new Student("赵敏",'女',25);
        System.out.println(s);   
        System.out.println(s.toString());  
    }
}

class Student{
    private String name;
    private char sex;
    private int age;

    // 重写的toString方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                ", age=" + age +
                '}';
    }

    // 无参构造器
    public Student() {
    }

    // 有参构造器
    public Student(String name, char sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

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

    public char getSex() {
        return sex;
    }
    public void setSex(char sex) {
        this.sex = sex;
    }

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

2.5.方法重载

  • 在Java中,方法重载(Overloading)和方法重写(Overriding)是面向对象编程中的两个重要概念,它们各自有不同的含义、用途和规则。

  • 方法重载(Overloading)

    方法重载指的是在一个类中定义多个同名但参数列表不同的方法。这些方法可以有不同的参数类型、不同数量的参数,或者两者兼有。返回值类型也可以不同,但是不能仅凭返回值类型来区分重载的方法。

  • 方法重载的特点

    1. 发生在同一类中。
    2. 方法名称相同,但参数列表不同(参数的数量或类型不同)。
    3. 可以有不同的返回类型,但这不是区分重载方法的标准。
    4. 与访问修饰符无关。
    public class Example {
        void show(int a) {
            System.out.println("Integer: " + a);
        }
        // 重载方法1
        void show(String b) {
            System.out.println("String: " + b);
        }
        // 重载方法2
        void show(int a, String b) {
            System.out.println("Both: " + a + ", " + b);
        }
    }
    
  • 方法重载(Overloading)和方法重写(Overriding)的区别:

    • 方法重载关注的是“同一个类中”方法的不同版本,主要通过不同的参数列表来区分。
    • 方法重写则涉及到“父子类之间”的方法替换,要求方法签名完全一致,主要用于实现多态性和特定行为的定制化。

2.6.子类构造器

  • 子类构造器特点:子类的全部构造器,都会先调用父类的构造器,再执行自己。

  • 子类构造器是如何实现调用父类构造器的?

    1. 默认情况下,子类全部构造器的第一行代码都是调用父类的无参构造器。super()(写不写都有),它会调用父类的无参构造器;
    2. 如果父类没有无参构造器,则我们必须在所有子类构造器的第一行手写super(xxx),指定去调用父类的有参构造器。
    3. 子类构造器可以通过调用父类构造器,把对象中包含父类这部分的数据先初始化赋值,
      再回来把对象里包含子类这部分的数据也进行初始化赋值。
    4. 在Java中,子类的构造器不能同时调用多个父类的构造器。每个子类构造器的第一行要么隐式或显式地调用一个父类构造器(通过super()super(参数列表)),要么在同一类中调用该类的另一个构造器(通过this()this(参数列表))即兄弟构造器。这意味着你只能选择其中一个父类构造器进行调用。
public class Test {
    public static void main(String[] args) {
        Zi zi = new Zi();   // 新建一个子类的对象
    }
}

class Zi extends Fu{   // Zi类继承Fu类
    public Zi(){  // Zi类无参构造器
//        super();  // (如果父类有无参构造器)默认存在的,写不写都有
//        super(666);  // 指定调用Fu类的有参构造器
// 如果父类没有无参构造器,则必须在子类构造器的第一行写这个super(666),指定去调用父类的有参构造器。
        System.out.println("子类无参构造器");
    }
}

class Fu{
    public Fu(){  // 无参构造器,私有的时,不能被子类继承
        System.out.println("父类无参构造器");
    }
    public Fu(int a){  // 有参构造器
        System.out.println("父类有参构造器");
    }
}
执行语句:
父类无参构造器
子类无参构造器

2.7.兄弟构造器

  • 理解this(xxx) 关键字,调用兄弟构造器:
    注意:super(...) this(...)必须写在构造器的第一行,但是两者不能同时出现;
    因为this调用兄弟构造器时,兄弟构造器肯定先找爸爸构造器,此时super再执行,就是找第二遍爸爸构造器了。
public class Student {
    private String name;
    private char sex;
    private int age;
    private String school;

    public Student() {
    }

    public Student(String name, char sex, int age) {
        // this调用兄弟构造器
        // 注意:super(...)  this(...)必须写在构造器的第一行,而且两者不能同时出现
        this(name,sex,age,"家里蹲大学"); // 悄悄使用兄弟构造器
//        super();   // this下面第二行不能使用super关键字调用父类的构造器
// 当前构造器是传入三个形参变量,但是要求该类在初始化赋值时,
// 如果没有传入school变量,该类能够自动对school变量进行赋值"XXX大学"
// 所以该构造器要手动调用它的兄弟构造器(传入四个形参变量),其中一个手动赋值。

//        this.name = name;
//        this.sex = sex;
//        this.age = age;
//        this.school = "黑马程序员";
    }

    public Student(String name, char sex, int age, String school) {
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.school = school;
    }

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

    public char getSex() {
        return sex;
    }
    public void setSex(char sex) {
        this.sex = sex;
    }

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

    public String getSchool() {
        return school;
    }
    public void setSchool(String school) {
        this.school = school;
    }

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

3.多态

  • 多态是在继承(类之间)、实现(接口和类)情况下的一种现象,表现为:对象多态、行为多态。成员变量不谈多态。

3.1.对象多态

  • 对象多态:父类引用指向子类对象;
Animal a1 = new Wolf();  // 对象多态:父类引用指向子类对象
Animal a2 = new Tortoise();

3.2.行为多态

  • 行为多态:定义一个通用的接口或基类,并让其子类完成该类的具体实现。这样,即使使用父类类型的引用,也能调用到子类中重写的方法,从而表现出不同的行为。
  • 多态的前提:
    有继承、实现关系;
    父类引用子类对象;
    存在方法重写。(只有方法重写才能出现行为多态)
public class Animal {
    String name =  "Animal";
    public void run(){
        System.out.println("Animal is running");
    }
}

public class Wolf extends Animal{
    String name = "狼";

    @Override
    public void run() {
        System.out.println("狼跑的贼溜~~~");
    }
}

public class Tortoise extends Animal{
    String name = "乌龟";

    @Override
    public void run() {
        System.out.println("乌龟跑的贼慢!!!");
    }
}
public class Test1 {
    public static void main(String[] args) {
        // 1、对象多态
        Animal a1 = new Wolf();  // 父类引用指向子类对象

        a1.run();  // 方法:编译看左边,运行看右边
        // 当Animal中没有run方法时,编译报错
        // 当运行run方法时,会去Wolf中找run方法运行

        Animal a2 = new Tortoise();
        a2.run();   // 方法:编译看左边,运行看右边


        // 成员变量:编译看左边,运行也看左边
        System.out.println(a1.name);   // Animal
        System.out.println(a2.name);   // Animal

        // 使用父类类型数组管理不同子类对象
        Animal[] animals = {a1, a2};
        for (Animal animal : animals) {
            animal.run(); // 动态绑定,运行时确定调用哪个子类的方法
        }
    }
}
执行结果:
狼跑的贼溜~~~
乌龟跑的贼慢!!!
Animal
Animal
狼跑的贼溜~~~
乌龟跑的贼慢!!!
  • 多态的好处:
    在多态的形式下,右边的对象是解耦合的,更便于扩展和维护;
    定义方法时,使用父类类型的形参,可以接受一切子类对象,扩展性更强,更便利。
public class Animal {
    String name =  "Animal";
    public void run(){
        System.out.println("Animal is running");
    }
}

public class Wolf extends Animal {
    String name = "狼";

    @Override
    public void run() {
        System.out.println("狼跑的贼溜~~~");
    }

    // 子类独有的功能:狼吃羊
    public void eatSheep() {   
        System.out.println("狼吃羊");
    }
}

public class Tortoise extends Animal {
    String name = "乌龟";

    @Override
    public void run() {
        System.out.println("乌龟跑的贼慢!!!");
    }

    // 子类独有的功能:乌龟缩头
    public void shrinkHead() {
        System.out.println("乌龟缩头了!!!");
    }
}
public class Test2 {
    public static void main(String[] args) {
        // 1、多态的好处1:右边的对象是解耦合的。
        Animal a1 = new Tortoise();
        a1.run();

//      a1.shrinkHead(); // 报错,多态下不能调用子类独有的功能

        // 2、多态的好处2:父类类型的变量作为参数,可以接受一个子类对象(go方法中,父类型Animal作为形参变量,可以接受Wolf和Tortoise类型的变量)
        Wolf w = new Wolf();
        go(w);

        Tortoise t = new Tortoise();
        go(t);
    }

    // 宠物游戏:所有动物都可以送给这个方法开始跑步
    public static void go(Animal w) {
        System.out.println("开始......");
        w.run();
//      a1.shrinkHead(); // 报错,多态下不能调用子类独有的功能
    }
}
执行结果:
乌龟跑的贼慢!!!
开始......
狼跑的贼溜~~~
开始......
乌龟跑的贼慢!!!

3.3.多态下的类型转换——调用子类的功能

  • 多态下会产生一个问题,怎么解决?
    多态下不能调子类独有的功能——解决方法:多态下的类型转换(也称为向下转型)

  • 多态下的类型转换:

自动类型转换:父类  变量名  =  new  子类();
强制类型转换:子类  变量名  =  (子类) 父类变量;
  • 强制类型转换的注意事项:

    • 存在继承、实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错。
    • 运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误出来。

    因为它涉及到将一个父类类型的引用转换为具体的子类类型。这种转换在编译时不会报错,只要存在继承或实现关系,但在运行时如果对象的实际类型与目标类型不匹配,则会抛出ClassCastException

public class Test3 {  // 会用到Test2里面的:Animal类,Wolf类,Tortoise类
    public static void main(String[] args) {
        // 正确的类型转换
        Animal a1 = new Tortoise();// 向上转型(隐式)
        a1.run();
        // 强制类型转换
        Tortoise t1 = (Tortoise) a1;  // 向下转型
        t1.shrinkHead(); // 调用子类特有的方法
        
        System.out.println("============================");
        // 有继承关系就可以强制转换,编译阶段不会报错!!
        // 运行时可能会出现类型转换异常:ClassCastException
        Animal a2 = new Wolf();  // ===> Animal a1 = new Tortoise();
        a2.run();
        // 强制类型转换 
        Tortoise t2 = (Tortoise) a2;// 编译阶段不会报错,但是运行时会报错
        t2.shrinkHead();   
    }
}
运行结果:
乌龟跑的贼慢!!!
乌龟缩头了!!!
============================
狼跑的贼溜~~~
Exception in thread "main" java.lang.ClassCastException: class sun.superstring.polymorphise3.Wolf cannot be cast to class sun.superstring.polymorphise3.Tortoise (sun.superstring.polymorphise3.Wolf and sun.superstring.polymorphise3.Tortoise are in unnamed module of loader 'app')
	at sun.superstring.polymorphise3.Test1.main(Test1.java:21)

3.4.强制类型转换前的判断——instanceof关键字

  • 类型转换异常解决:
    强转前:使用 instanceof 关键字,判断当前对象的真实类型,再进行强转。

  • instanceof 使用:object instanceof Type

    • object:要检查的对象。
    • Type:你想要检查的类型(可以是类、接口或者抽象类)。
    • 它返回一个布尔值:如果对象是指定类型的实例(包括该类型的子类),则返回 true;否则返回 false。
  • instanceof 使用注意事项:

    • null 值检查:对于 null 值,instanceof 总是返回 false,因为 null 不指向任何对象。

      String str = null;
      System.out.println(str instanceof String); // false
      
    • 编译期检查:如果 Type 不是 object 类型或其超类、接口之一,编译器会在编译阶段报错。例如,如果你尝试检查一个与对象无关的类型,如 Integer instanceof String,编译器会报错。

    • 泛型类型参数instanceof 不能直接用于泛型类型参数,因为泛型信息在运行时会被擦除(类型擦除)。你可以使用原始类型来进行检查,但要注意这样做可能会失去泛型带来的类型安全性。

public class Test3 {  // 会用到Test2里面的:Animal类,Wolf类,Tortoise类
    public static void main(String[] args) {
        // 正确的类型转换
        Animal a1 = new Tortoise();// 向上转型(隐式)
        a1.run();
        // 强制类型转换
        Tortoise t1 = (Tortoise) a1;  // 向下转型
        t1.shrinkHead(); // 调用子类特有的方法
        System.out.println("============================");

        // 调用独特功能
        Animal w = new Wolf();
        special(w);

        Animal t = new Tortoise();
        special(t);
    }

    // 宠物游戏:所有动物都可以送给这个方法开始跑步
    public static void special(Animal w) {
        System.out.println("开始......");
        // Java建议:强制类型转换前,应该判断对象的真实类型,再进行强制类型转换
        if(w instanceof Wolf){  
            Wolf w1 = (Wolf) w;
            w1.eatSheep();
        }else if(w instanceof Tortoise){
            Tortoise t11 = (Tortoise) w;
            t11.shrinkHead();
        }
    }
}
运行结果:
乌龟跑的贼慢!!!
乌龟缩头了!!!
============================
开始......
狼吃羊
开始......
乌龟缩头了!!!

4.其他

4.1.祖宗类Object3

  • 继承的特点:祖宗类Object,Java中所有的类都是Object的子类。
    Java中所有的类,要么直接继承了Object,要么默认继承了Object,要么间接继承了Object;因此Object是所有类的祖宗类,Object 功能任何类都可以用。
    例如:Object类中的toString方法,会自动返回类的地址。
    就近原则,优先访问自己类中,自己类中没有的才会访问父类。

4.2.开发规范

  • 可以把多个类写在一个文件中,但是在开发中不建议这样写;
    多个类写在一个文件中,但是只能有一个类用public修饰,就是公开类只能有一个暴露出去

4.3.解耦合

  • 解耦合:遥控器没电了,拔出来换个电池就可以继续使用了。遥控器和电池是解耦合了

4.4.lombook注解@Data

  • 注解:
    • lombook技术可以实现为类自动添加getter 和 setter 方法,无参构造,重写toString 方法;
    • 使用@Data注解可以调用lombook技术,自动生成getter 和 setter 方法,无参构造,toString 方法。
      其中,@AllArgsConstructor注解提供所有的有参构造器,这个时候无参构造器没了,所以还需要加注解@NoArgsConstructor,提供无参构造器
    • 如果不好用,这个类显示报错,那就自己写有参无参构造器还有getter,setter方法。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// lombook 可以实现为类自动添加getter 和 setter 方法,无参构造,toString 方法
// @Data注解可以调用lombook技术,可以自动生成getter 和 setter 方法,无参构造,toString 方法
@Data
@NoArgsConstructor     // 提供无参构造器
@AllArgsConstructor    // 提供所有的有参构造器
public class Card {
    private String carId;
    private String name;
    private String phoneNumber;
    private double money;  // 账户余额
}

4.5.方法重载和方法重写的区别

  • 重载和重写的区别:
    ① 重载指的是在一个类中定义多个方法名相同但参数列表不同的方法。编译器根据调用时传递的参数类型和数量来决定具体调用哪个方法。重载与返回类型无关,也就是说,仅改变返回类型而不改变参数列表的方法不会被视为重载。
    ② 重写是指子类重新定义从父类继承而来的方法。重写的方法必须具有相同的名称、参数列表以及返回类型(或者返回类型的协变)。此外,重写方法的访问级别不能比被覆盖的方法更严格,并且重写方法不能抛出新的检查异常或更广泛的异常。
  • 总结区别:
特性 重载(Overloading) 重写(Overriding)
发生地点 同一个类内 父类与子类之间
方法签名 必须不同(参数列表不同) 必须相同(包括参数列表和返回类型)
返回类型 不影响是否为重载 必须相同或返回类型的协变
访问修饰符 可以不同 子类方法的访问权限不能比父类方法更严格
异常处理 没有特殊限制 子类方法不能抛出新的检查异常或更广泛的异常
决定调用时机 编译时 运行时

5. 参考资料

  • 黑马程序员:黑马程序员Java+AI智能辅助编程全套视频教程,java零基础入门到大牛一套通关_哔哩哔哩_bilibili
  • 通义千问:通义 - 你的实用AI助手
相关栏目:

用户点评