Erlo

day06-面向对象编程:三大特征

2025-01-28 15:29:23 发布   20 浏览  
页面报错/反馈
收藏 点赞

Java面向对象的三大特征

—————————————————————————————————————————————————————————————————————

1.封装

  • 面向对象的三大特征:封装、继承、多态
    封装是面向对象的三大特征之一;

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

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

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

2.继承

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

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

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

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

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

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

  • 权限修饰符:
    private:只能本类;
    缺省:本类,同一个包中的其他类;
    protected:本类,同一个包中的类,子孙类;
    public:任意位置。
    private

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

  • 继承的特点:
    单继承,一个类只能继承一个父类,只能有一个爸爸;
    多层继承,Java不支持多继承,但支持多层继承,就是爸爸也会继承;

  • Java中的类为什么不支持多继承?
    用反证法,假设A、B中都有个method方法,A是复习语文的,B是复习数学的
    假如C可以同时继承 A和B,如果用C创建对象,C可以调用父类的方法,这是C就不知道去调用A还是B的方法。

  • 继承后子类访问成员的特点:就近原则
    ① 在之类中访问其他成员,是依照就近原则的,先在子类局部范围找,然后子类成员范围找,然后父类成员范围找,如果父类范围还没有找到则报错。
    ② 如果子父类中,出现了重名的成员,会优先使用子类的成员。

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

super.父类成员变量;
super.父类成员方法;
  • 方法重写:
    当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。

  • 方法重写规范:
    ① 方法名称,形参列表必须一样,这个方法就是方法重写。
    ② @Override,方法重写的校验注解(标志):要求方法名称和形参列表必须与被重写方法一致,否则报错!(更安全,可读性更好,更优雅)
    ③ 子类重写父类的方法时,访问权限必须大于或者等于父类该方法的权限 (public > protected > 缺省)
    ④ 重写的方法,返回值类型必须与被重写方法的返回值类型一样,或者范围更小。
    ⑤ 私有方法,静态方法不能被重写,如果重写会报错;
    ⑥ 重写的规范:声明不变,重新实现。

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.cry();
    }
}

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

    public static void cry2(){
        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;
    }
}
  • 子类构造器特点:子类的全部构造器,都会先调用父类的构造器,再执行自己。

  • 子类构造器是如何实现调用父类构造器的?
    默认情况下,子类全部构造器的第一行代码都是super()(写不写都有),它会调用父类的无参构造器;
    如果父类没有无参构造器,则我们必须在子类构造器的第一行手写super(……),指定去调用父类的有参构造器。
    子类构造器可以通过调用父类构造器,把对象中包含父类这部分的数据先初始化赋值,
    再回来把对象里包含子类这部分的数据也进行初始化赋值。

public class Test {
    public static void main(String[] args) {
        Zi zi = new Zi();   // 新建一个子类的对象
    }
}

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

class Fu{
    public Fu(){  // 无参构造器,私有的时,不能被子类继承
        System.out.println("父类无参构造器");
    }
    public Fu(int a){  // 有参构造器
        System.out.println("父类有参构造器");
    }
}
  • 理解this(...) 关键字,调用兄弟构造器:
    注意: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.多态

  • 多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态。
    成员变量不谈多态。
    对象多态:父类引用指向子类对象;
    行为多态:定义一个通用的接口或基类,并让其子类完成该类的具体实现。这样,即使使用父类类型的引用,也能调用到子类中重写的方法,从而表现出不同的行为。
Animal a1 = new Wolf();  // 对象多态:父类引用指向子类对象
Animal a2 = new Tortoise();
  • 多态的前提:
    有继承/实现关系;
    父类引用子类对象;
    存在方法重写。(只有方法重写才能出现行为多态)
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(); // 动态绑定,运行时确定调用哪个子类的方法
        }
    }
}

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 Test2 {
    public static void main(String[] args) {
        // 1、多态的好处1:右边的对象是解耦合的。
        Animal a1 = new Tortoise();
        a1.run();

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

        // 2、多态的好处2:父类类型的变量作为参数,可以接受一个子类对象
        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(); // 报错,多态下不能调用子类独有的功能

    }
}


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("乌龟缩头了!!!");
    }
}
  • 多态下会产生一个问题,怎么解决?
    多态下不能调子类独有的功能——多态下的类型转换

  • 多态下的类型转换:

自动类型转换:父类  变量名  =  new  子类();
强制类型转换:子类  变量名  =  (子类) 父类变量;
  • 强制类型转换的注意事项:
    存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错。
    运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误出来;

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

public class Test3 {  // 会用到Test2里面的:Animal类,Wolf类,Tortoise类
    public static void main(String[] args) {
        Animal a1 = new Wolf();  // ===> Animal a1 = new Tortoise();
        a1.run();

        // 强制类型转换
        Tortoise t1 = (Tortoise) a1;  // 编译阶段不会报错,但是运行时会报错
        // 解决方法:将第一行的new Wolf(); 换成 new Tortoise();
        t1.shrinkHead();
        System.out.println("============================");
        // 有继承关系就可以强制转换,编译阶段不会报错!!
        // 运行时可能会出现类型转换异常:ClassCastException

        Wolf w = new Wolf();
        go(w);

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

    // 宠物游戏:所有动物都可以送给这个方法开始跑步
    public static void go(Animal w) {
        System.out.println("开始......");
        w.run();

        // Java建议:强制类型转换前,应该判断对象的真实类型,再进行强制类型转换
        if(w instanceof Wolf){
            Wolf w1 = (Wolf) w;
            w1.eatSheep();
        }else if(w instanceof Tortoise){
            Tortoise t11 = (Tortoise) w;
            t11.shrinkHead();
        }
    }
}

4.其他

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

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

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

  • 注解:
    lombook技术可以实现为类自动添加getter 和 setter 方法,无参构造,重写toString 方法;使用@Data注解可以调用lombook技术,自动生成getter 和 setter 方法,无参构造,toString 方法。
    @AllArgsConstructor注解提供所有的有参构造器,这个时候无参构造器没了,所以还需要加注解@NoArgsConstructor,提供无参构造器

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// lombook 可以实现为类自动添加getter 和 setter 方法,无参构造,toString 方法
// @Data注解可以自动生成getter 和 setter 方法,无参构造,toString 方法
@Data
@NoArgsConstructor     // 提供无参构造器
@AllArgsConstructor    // 提供所有的有参构造器
public class Card {
    private String carId;
    private String name;
    private String phoneNumber;
    private double money;  // 账户余额
}
  • 重载和重写的区别:
    ① 重载指的是在一个类中定义多个方法名相同但参数列表不同的方法。编译器根据调用时传递的参数类型和数量来决定具体调用哪个方法。重载与返回类型无关,也就是说,仅改变返回类型而不改变参数列表的方法不会被视为重载。
    ② 重写是指子类重新定义从父类继承而来的方法。重写的方法必须具有相同的名称、参数列表以及返回类型(或者返回类型的协变)。此外,重写方法的访问级别不能比被覆盖的方法更严格,并且重写方法不能抛出新的检查异常或更广泛的异常。

  • 总结区别:
    | 特性 | 重载(Overloading) | 重写(Overriding) |
    | ---- | ---- | ---- |
    | 发生地点 |同一个类内 | 父类与子类之间 |
    | 方法签名 |必须不同(参数列表不同) |必须相同(包括参数列表和返回类型) |
    | 返回类型 |不影响是否为重载 |必须相同或返回类型的协变 |
    | 访问修饰符 |可以不同 |子类方法的访问权限不能比父类方法更严格|
    | 异常处理 |没有特殊限制 |子类方法不能抛出新的检查异常或更广泛的异常|
    | 决定调用时机 | 编译时 |运行时 |

登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认