重学面向对象-基础篇03封装、继承和多态

封装、继承和多态

基础概念

封装:把对象的属性和方法结合城一个独立的整体,隐藏实现细节,并提供对外访问的接口

继承:从已知的一个类中派生出一个新的类,叫子类。子类实现了父类所有非私有化的属性和方法,并根据实际需求扩展出新的行为

多态:多个不同的对象对同一消息作出响应,同一消息根据不同的对象而采用各种不同的方法

代码实现

类的封装

package com.test.entity;

public class Person {
    private String name; //现在类的属性只能被自己直接访问
    private int age;
    private String sex;

    public Person(String name, int age, String sex) { //构造方法也要声明为公共,否则对象都构造不了
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

    public String getSex() {
        return sex;
    }

    public int getAge() {
        return age;
    }
}
package com.test;

import com.test.entity.Person;

public class Main {
    public static void main(String[] args) {
        Person person = new Person("小明",18,"男");
        System.out.println(person.getName()); //只能通过调用getName()方法来获取名字
    }
}

类的继承

//定义一父类Person
public class Person{
    String name;
    int age;
    String sex;
}

//工人类
public class Worker extends Person{}

//学生类
public class Student extends Person{}

//class前面添加final关键字表示这个类已经是最终形态,不能继承
public final class Person {}
//父类
public class Person {
    String name;
    int age;
    String sex;

    public void hello(){
        System.out.println("我叫 "+name+",今年 "+age+" 岁了!");
    }
}

//子类
public class Student extends Person{
    public void study(){
        System.out.println("我的名字是 "+name+",我在学习!");   //可以直接访问父类中定义的name属性
    }
}

//实际应用
public static void main(String[] args) {
    Student student = new Student();
    student.study();    //子类不仅有自己的独特技能
    student.hello();    //还继承了父类的全部技能
}
//父类存在有参构造方法,子类必须在构造方法中调用
public class Person {
    protected String name;   //因为子类需要用这些属性,所以说我们就将这些变成protected,外部不允许访问
    protected int age;
    protected String sex;
    protected String profession;

    //构造方法也改成protected,只能子类用
    protected Person(String name, int age, String sex, String profession) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.profession = profession;
    }

    public void hello(){
        System.out.println("["+profession+"] 我叫 "+name+",今年 "+age+" 岁了!");
    }
}

//子类Student
public class Student extends Person{
    public Student(String name, int age, String sex) {    //因为学生职业已经确定,所以说学生直接填写就可以了
        super(name, age, sex, "学生");   //使用super代表父类,父类的构造方法就是super()
    }

    public void study(){
        System.out.println("我的名字是 "+name+",我在学习!");
    }
}

//子类Worker
public class Worker extends Person{
    public Worker(String name, int age, String sex) {
        super(name, age, sex, "工人");    //父类构造调用必须在最前面
        System.out.println("工人构造成功!");    //注意,在调用父类构造方法之前,不允许执行任何代码,只能在之后执行
    }
}

//实际使用
public static void main(String[] args) {
    Person person = new Student("小明", 18, "男"); //使用父类型的变量,去引用一个子类对象(向上转型)
    Student student = (Student) person; //使用强制类型转换(向下转型)
    person.Hello(); //父类对象的引用相当于作为父亲来使用,只能访问父类对象的内容
    student.study();
}

//[学生]我叫小明,今年 18岁了!
//我叫小明,我在学习
//判断变量引用对象是什么类
public static void main(String[] args) {
    Person person = new Student("小明", 18, "男");
    if(person instanceof Student) {   //我们可以使用instanceof关键字来对类型进行判断
        System.out.println("对象是 Student 类型的");
    }
    if(person instanceof Person) {
        System.out.println("对象是 Person 类型的");
    }
}

//子类可以定义和父类同名属性
public class Worker extends Person{
    protected String name;   //子类中同样可以定义name属性

    public Worker(String name, int age, String sex) {
        super(name, age, sex, "工人");
    }

    //在子类中直接使用时
    public void work(){
        System.out.println("我是 "+name+",我在工作!");
        //这里的name,依然是作用域最近的哪一个,也就是在当前子类中定义的name属性,而不是父类的name属性
    }

    //子类存在同名变量,访问父类同名变量
    public void work(){
        System.out.println("我是 "+super.name+",我在工作!");
        //这里使用super.name来表示需要的是父类的name变量
    }
}

注:没有super.super这种用法,也就是说如果存在多级继承的话,那么最多只能通过这种方法访问到父类的属性

顶层Object类

所有类都默认继承自Object类,除非手动指定继承的类型,但是依然改变不了最顶层的父类是Object类。

所有类都包含Object类中的方法所有类都默认继承自Object类,除非手动指定继承的类型,但是依然改变不了最顶层的父类是Object类。所有类都包含Object类中的方法。

public class Person extends Oject{
//除非我们手动指定要继承的类是什么,实际上默认情况下所有的类都是继承自Object的,只是可以省略
}
public void println(Object x) {
    String s = String.valueOf(x); //调用对象的toString方法
    synchronized(this){
        print(s);
        newLine();
    }
}

//默认比较两个对象是否为同一个对象,这里得到的肯定是false
public static void main(String[] args) {
    Person p1 = new Student("小明", 18, "男");
    Person p2 = new Student("小明", 18, "男");
    System.out.println(p1.equals(p2));
}
//实现“如果名字、年龄、性别都完全相同,则肯定是同一个人”,需要修改equals的默认实现

方法的重写

方法的重载是为某个方法提供更多种类,而方法的重写是覆盖原有的方法实现

public class Person{
    @Override //重写方法一般使用@Override注解
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (obj instanceof Person) {
            Person person = (Person) obj;
            return this.name.equals(person.name) && //字符串内容的比较,不能使用==,必须使用equals方法
                this.age == person.age && //基本类型的比较跟之前一样,直接==
                this.sex.equals(person.sex);
        }
        return false;
    }
    
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", sex=" + sex + ", profession=" + profession + "]";
    }
}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Student("Ming", 18, "male");
        Person p2 = new Student("Ming", 18, "male");
        System.out.println(p1.equals(p2));
        System.out.println(p1);
    }
}

true
Person [name=Ming, age=18, sex=male, profession=学生]

基于这种方法可以重写的特性,对于一个类定义的行为,不同的子类可以出现不同的行为;不同的子类,对于同一个方法会产生不同的结果

面向对象编程中多态特性的一种体现

public class Person {
    ...
    public void exam() {
        System.out.println("我是考试方法");
    }
}

public class Student {
    ...
    @Override
    public void exam() {
        System.out.println("我是做题蛆,我要拿A");
    }
}

public class Worker {
    ...
    @Override
    public void exam() {
        System.out.println("我是工人,我要拿S");
    }
}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Student("Ming", 18, "male");
        Person p2 = new Worker("Wang", 20, "male");
        p1.exam();
        p2.exam();
    }
}

如果不希望子类重写某个方法,我们可以在方法前添加final关键字,表示这个方法已经是最终形态

如果父类中方法的可见性为private,那么子类同样无法访问,也就不能重写,但是可以定义同名方法

在重写父类方法时,如果希望调用父类原本的方法实现,那么同样可以使用super关键字

子类在重写父类方法时,不能降低父类方法中的可见性

抽象类由于不是具体的类定义(它是类的抽象)可能会存在某些方法没有实现,因此无法直接通过new关键字来直接创建对象

要使用抽象类,我们只能去创建它的子类对象

具体的实现,需要由子类来完成,而且如果是子类,必须要实现抽象类中所有抽象方法

抽象类由于不是具体的类定义(它是类的抽象)可能会存在某些方法没有实现,因此无法直接通过new关键字来直接创建对象

抽象类一般只用作继承使用,抽象类的子类也可以是一个抽象类

public abstract class Person { //添加abstract关键字,表明此为抽象类
    protected String name; //大体和普通类差不多
    protected int age;
    protected String sex;
    protected String profession;

    protected Person(String name, int age, String sex, String profession) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.profession = profession;
    }

    public abstract void exam();  //抽象类中可以有抽象方法,也就是说这个方法只有定义,没有方法体
}

public class Worker extends Person {

    public Worker(String name, int age, String sex) {
        super(name, age, sex, "工人");
    }

    @Override
    public void exam() { //子类必须要实现抽象类所有的抽象方法,强制要求,否则无法通过编译
        System.out.println("我是工人,做题我不擅长,只能得到D");
    }
}

public class Student extends Person { //如果抽象类的子类也是抽象类,那么可以不用实现父类中的抽象方法
    public Student(String name, int age, String sex) {
        super(name, age, sex, "学生");
    }

    @Override //抽象类中并非只有抽象方法,1抽象类中也能有正常方法的实现
    public void exam() {
        System.out.println("我是学生,我要A");
    }
}

接口只代表某个确切的功能,接口一般只代表某些功能的抽象,接口包含了一些列方法的定义,类可以实现这个接口,表示类支持接口代表的功能

接口的目标就是将类所具有某些的行为抽象出来

//Study.java
public interface Study { //使用interface表示这是一个接口
    void study(); //接口中只能定义访问权限为public抽象方法,其中public和abstract关键字可以省略
}
//Student.java
public class Student extends Person implements Study {
    public Student(String name, int age, String sex) {
        super(name, age, sex, "学生");
    }

    @Override
    public void study() {
        System.out.println("我会学习!");
    }
}

//Teacher.java
public class Teacher extends Person implements Study {
    protected Teacher(String name, int age, String sex) {
        super(name, age, sex, "教师");
    }

    @Override
    public void study() {
        System.out.println("我会加倍学习!");
        ;
    }
}

接口不同于继承,接口可以同时实现多个

public class Student extends Person implements Study, A, B, C {} //多个接口的实现使用逗号隔开

接口跟抽象类一样,不能直接创建对象

我们也可以将接口实现类的对象以接口的形式去使用

当做接口使用时,只有接口中定义的方法和Object类的方法,无法使用类本身的方法和父类的方法

接口同样支持向下转型

从Java8开始,接口中可以存在方法的默认实现(如果方法在接口中存在默认实现,那么实现类中不强制要求进行实现)

接口中不允许存在成员变量和成员方法,但是可以存在静态变量和静态方法

public interface Study {
    public static final int a = 10;   //接口中定义的静态变量只能是public static final的
  
  	public static void test(){    //接口中定义的静态方法也只能是public的
        System.out.println("我是静态方法");
    }
    
    void study();
}

可以直接通过接口名.的方式使用静态内容

接口可以继承自其他接口

接口没有继承数量限制,接口支持多继承

接口的继承相当于是对接口功能的融合

Object类中提供的克隆方法

package java.lang;

public interface Cloneable {    //这个接口中什么都没定义
}

实现接口后,需要将克隆方法可见性提升,不然无法使用

public class Student extends Person implements Study, Cloneable {   //首先实现Cloneable接口,表示这个类具有克隆的功能
    public Student(String name, int age, String sex) {
        super(name, age, sex, "学生");
    }

    @Override
    public Object clone() throws CloneNotSupportedException {   //提升clone方法的访问权限
        return super.clone();   //因为底层是C++实现,我们直接调用父类的实现就可以了
    }

    @Override
    public void study() {
        System.out.println("我会学习!");
    }
}
public static void main(String[] args) throws CloneNotSupportedException {  //这里向上抛出一下异常,还没学异常,所以说照着写就行了
    Student student = new Student("小明", 18, "男");
    Student clone = (Student) student.clone();   //调用clone方法,得到一个克隆的对象
    System.out.println(student);
    System.out.println(clone);
    System.out.println(student == clone);
}

原对象和克隆对象,是两个不同的对象,但是他们的各种属性都是完全一样的,此处的clone方法是浅拷贝

  • 浅拷贝: 对于类中基本数据类型,会直接复制值给拷贝对象;对于引用类型,只会复制对象的地址,而实际上指向的还是原来的那个对象,拷贝个基莫。
  • 深拷贝: 无论是基本类型还是引用类型,深拷贝会将引用类型的所有内容,全部拷贝为一个新的对象,包括对象内部的所有成员变量,也会进行拷贝。

枚举类

假设现在我们想给小明添加一个状态(跑步、学习、睡觉),外部可以实时获取小明的状态,可以用枚举类实现状态标记,开发者拿到使用的就是我们预先定义好的状态

public enum Status {   //enum表示这是一个枚举类,枚举类的语法稍微有一些不一样
   RUNNING, STUDY, SLEEP;    //直接写每个状态的名字即可,最后面分号可以不打,但是推荐打上
}
public class Student extends Person implements Study {

    private String status;   //状态,可以是跑步、学习、睡觉这三个之中的其中一种

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}
private Status status;   //类型变成刚刚定义的枚举类

public Status getStatus() {
    return status;
}

public void setStatus(Status status) {
    this.status = status;
}

枚举类型是普通的类,我们可以给枚举类型添加独有的成员方法

public enum Status {
    RUNNING("睡觉"), STUDY("学习"), SLEEP("睡觉");   //无参构造方法被覆盖,创建枚举需要添加参数(本质就是调用的构造方法)

    private final String name;    //枚举的成员变量
    Status(String name){    //覆盖原有构造方法(默认private,只能内部使用!)
        this.name = name;
    }

    public String getName() {   //获取封装的成员变量
        return name;
    }
}

枚举类还自带一些继承下来的实用方法,比如获取枚举类中的所有枚举

posted on 2024-08-08 11:33  Baby091  阅读(4)  评论(0编辑  收藏  举报

导航