首页 > 编程语言 >JavaSE4️⃣OOP - 三大特性

JavaSE4️⃣OOP - 三大特性

时间:2023-02-06 23:11:06浏览次数:42  
标签:JavaSE4 子类 Person OOP Student 父类 方法 public 三大

1、封装

1.1、简介

封装(Encapsulation)

  1. 含义
    1. 将数据和基于数据的操作封装在一起,构成一个不可分割的独立实体。
    2. 将对象的状态信息隐藏在内部,提供公共接口对外提供该对象的功能。
  2. 好处
    1. 隐藏类的实现细节。
    2. 限制使用者对成员变量的不合理访问。
    3. 实现 “高内聚低耦合”,提高程序可维护性。
    4. 可进行数据检查,保证对象信息的完整性。

1.2、访问修饰符

1.2.1、访问控制级别

含义 可访问范围
public 公开 所有类
protected 受保护的 同个包下的类、所在类的子类
default 缺省(默认级别,无修饰符) 同个包下的类
private 私有 所在类内部

1.2.2、可修饰结构

成员变量、方法、构造方法 局部变量 外部类 内部类
public
protected
default
private
  1. 局部变量
    1. 说明:无法使用任何访问修饰符。
    2. 解释:作用域为当前方法,无法被其它类访问,因此。
  2. 外部类
    1. 说明:无法使用 privateprotected,只能用 publicdefault 修饰符 。
    2. 解释:没有位于任何类的内部,不存在所在类的概念。

1.2.3、使用原则

  1. 成员变量
    1. 实例变量:即普通属性,通常使用 private 修饰。
    2. 类变量:即 static 修饰的属性,考虑使用 public 修饰。
  2. 工具方法:用于辅助其它方法实现预期目标,本身不对外暴露,应当使用 private 修饰方法。
  3. 子类重写:若某个类设计为父类,部分方法希望子类重写方法且不对外暴露,应当使用 protected 修饰。

1.3、JavaBean 规范

良好实践:JavaBean 规范。

  1. 每个成员变量都使用 private 修饰。

  2. 每个成员变量提供 public 修饰的 getter/setter 方法。

    class User {
        private int id;
        private String name;
        
        public int getId() {}
        public void setId(int id) {}
        public String getName() {}
        public void setName(String name) {}
    }
    

2、继承

2.1、案例引入

假设已有 Person 类,现需要定义一个 Student 类。

(字段如下)

class Person {
    private String name;
    private int age;

    // 两对 getter, setter
}

class Student {
    private String name;
    private int age;
    private int score;

    // 三对 getter, setter
}

分析

  1. Student 类包含了 Person 类的所有结构(属性、方法),在此基础上多了其它结构。

  2. 使用继承,子类可以获取父类的结构,实现代码复用

    class Student extends Person {
        private int score;
        
        // score的 getter, setter
    }
    

术语:假设 A 继承 B

  • A:超类(super),父类(parent),基类(base)。
  • B:子类(sub),扩展类(extended)。

2.2、说明

2.2.1、继承

extends(扩展)

  1. OOP 中实现复用的重要手段,实现 IS-A 关系。
  2. 从现有的类派生出子类,在此基础上可扩展新的功能。
  3. 无法获取的结构
    1. 父类的 private 结构。
    2. 父类的构造方法(可以从构造方法与类同名的角度理解)。

2.2.2、继承树

  1. ObjectObject 类是所有类的超类

    1. 即任意类都会直接或间接继承 Object(除了 Object 本身)。
    2. 若定义类时没有指定 extends,编译器会自动加上 extends Object
  2. 单继承:Java 类有且仅有一个直接父类(Object 没有父类)。

  3. 类和类之间的继承关系,形成树状结构。

           ┌───────────┐
           │  Object   │
           └───────────┘
                 ▲
                 │
           ┌───────────┐
           │  Person   │
           └───────────┘
              ▲     ▲
              │     │
              │     │
    ┌───────────┐ ┌───────────┐
    │  Student  │ │  Teacher  │
    └───────────┘ └───────────┘
    

2.2.3、阻止继承

  1. final:使用 final 修饰某个类,则无法被继承。

    public final class String {   
    }
    
  2. sealed ... permits ...(Java 15+):使用 sealed 修饰某个类,通过 permits 指定允许继承的子类名称。

    // 允许 A,B,C 三个类继承
    public sealed class Demo permits A, B, C {
        ...
    }
    
    // 编译通过
    class A extends Demo {
    }
    
    // 编译错误:class is not allowed to extend sealed class
    class D extends Demo {
    }
    

2.2.4、继承 vs 组合

根据类的关系,选择使用继承(IS-A)或组合(HAS-A)。

  1. 继承:表达 IS-A 关系。
    1. 适用场景:允许对另一个类公开所有接口。
    2. 示例:模板方法、工厂方法等设计模式。
  2. 组合:表达 HAS-A关系。

示例

class Person {
}
class Book {
}

// 学生 is 人,可使用继承
class Student extends Person {
    // 组合
    private Book book;
}

2.3、super 关键字

2.3.1、说明

super:表示父类,用于调用父类结构。

  1. 作用:访问父类的非 private 结构(属性、方法、构造方法)。
    1. 可以访问直接父类或间接父类的结构。
    2. 若继承链中有多个同名结构,就近原则。
  2. 构造方法调用:Java 规定,构造方法首行必须调用父类构造方法
    1. 若无显式调用,编译器会自动在首行生成 super();
    2. 同一个构造方法中,只能调用一次父类构造方法。

2.3.2、示例

Person 类有一个有参构造方法,Student 类继承 Person 类。

class Student extends Person {
    private int score;

    public Student(String name, int age, int score) {
        this.score = score;
    }
}

class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

以上代码编译不通过,分析如下

  1. Student 类的构造方法没有显式调用 Person 的构造方法。

  2. 编译器自动生成 super();,但 Person 类没有无参构造方法。

    public Student(String name, int age, int score) {
        super();	// 自动生成
        this.score = score;
    }
    

解决方案子类调用父类中存在的构造方法

public Student(String name, int age, int score) {
    super(name, age);
    this.score = score;
}

2.4、转型

2.4.1、向上转型

向上转型(upcasting)

将一个子类类型安全转换为父类类型的赋值(父类引用指向子类对象)。

Person p = new Student();	// upcasting
Object o = new Student();	// upcasting

2.4.2、向下转型

向下转型(downcasting)

将一个父类类型强制转型为子类类型。

前提:父类引用指向的是子类对象。

  • p1:指向 Person 类型的对象实例,不满足前提,向下转型失败。

  • p2:指向 Student 类型的对象实例,满足前提,向下转型成功。

    Person p1 = new Person();
    Person p2 = new Student(); // upcasting
    
    Student s1 = (Student) p1; // 错误:ClassCastException
    Student s2 = (Student) p2; // ok
    

2.4.3、instanceof

instanceof:判断一个实例是否为指定类型

Person p1 = new Person();
Person p2 = new Student(); // upcasting

System.out.println(p1 instanceof Person);	// true
System.out.println(p1 instanceof Student);	// false

System.out.println(p2 instanceof Person);	// false
System.out.println(p2 instanceof Student);	// true

优雅的向下转型

  1. 先判断后转型

    Person p = new Student();
    
    // 判断
    if (p instanceof Student) {
        // 向下转型
        Student s = (Student) p;
        System.out.println(s.getName());
    }
    
  2. 判断并转型(Java 14+):判断 instanceof 后可直接转型为指定变量。

    Person p = new Student();
    
    // 判断成功后,自动转型为s
    if (p instanceof Student s) {
        // 可直接使用变量s
        System.out.println(s.getName());
    }
    

2.5、同名属性

子类可以定义与父类中同名的属性或方法。

同名属性:子类定义的与父类同名的属性二者完全独立

  1. 父类对象只调用自己的属性,不会调用子类的属性。
  2. 子类对象默认调用自己的属性(即 this),调用父类属性需要使用 super

子类可定义与父类方法签名完全相同的方法。

称为重写(override),见下文。

3、多态

3.1、方法重写

3.1.1、含义

重写(override)

  1. 含义:子类中定义了与父类方法签名完全相同的方法
  2. 要求
    1. 方法名、参数列表相同。
    2. 权限修饰符大于父类,返回值类型抛出异常类型小于父类。
  3. 说明
    1. 无法重写 private 方法和构造方法。
    2. 在子类的重写方法上添加 @Override 注解(非必要),编译器会检查是否正确重写了方法。

3.1.2、重写 Object 方法

Object 是所有类的超类。

常用的三个方法如下,必要时可以进行重写。

作用 默认实现
equals() 判断两个实例是否逻辑相等 ==:基本类型比较值,引用类型比较地址
hashCode() 计算实例的哈希值 基于对象的内存地址转换的值
toString() 将实例输出为字符串 类名 + "@" + 哈希值对应的 16 进制数

3.1.3、重写 vs 重载

对比

重写(override) 重载(overload)
含义 继承关系中,子类定义的与父类方法签名完全相同的方法 同一个类中方法名相同、参数列表不同的方法构成重载
方法名 相同 相同
参数列表 相同 不同
修饰符、返回值、异常 权限修饰符大于父类,返回值、抛出异常类型小于父类 与权限修饰符、返回值、抛出异常类型无关
注意 无法重写 private 方法和构造方法 如果定义了两个方法名和参数列表相同的方法,编译不通过。

3.2、多态

3.2.1、案例引入

Student 类继承 Person 类,并且重写了父类的方法。

class Person {
    public void work() {
        System.out.println("Person is working");
    }
}

class Student extends Person {
    @Override
    public void work() {
        System.out.println("Student is working");
    }
}

创建两个 Person 变量并调用 work() 方法,查看结果。

  • p1 指向 Person 类型实例,实际调用的是 Person 的 work() 方法。

  • p2 指向 Student 类型实例,实际调用的是 Student 的 work() 方法。

    public static void main(String[] args) {
        Person p1 = new Person();
        p1.work();	// Person is working
        
        Person p2 = new Student();
        p2.work();	// Stuednt is working
    }
    

分析

  1. 两个变量均声明为 Person 类型,但指向的实例类型不同。
  2. 实例方法的调用是基于运行时实际类型动态调用而非变量的声明类型

3.2.2、多态

多态(Polymorphic)

父类引用指向子类对象的一种体现。

  1. 含义:同一个行为在运行时具有不同表现形式,无法提前确定。
    1. 变量声明时,指向的具体类型在运行时才确定。
    2. 方法调用时,真正执行的方法取决于运行时的实际类型。
  2. 好处面向抽象编程,提高代码复用性、程序可扩展性。
  3. 实现方式:继承父类、实现接口。

3.2.3、应用示例

需求示例:实现一个税额计算方法,计算每种收入的税额。

假定有普通类型、工资类型、免税类型。

  1. 收入类 Income:作为默认收入类型。

    class Income {
        protected double income;
        
        public double getTax() {
            return income * 0.1;	// 税率10%
        }
    }
    
  2. 其它类型收入

    class SalaryIncome extends Income {
        @Override
        public double getTax() {
            return income <= 5000 ? 0 : (income - 5000) * 0.2;	// 5000元起征
        }
    }
    
    class TaxExemptIncome extends Income {
        @Override
        public double getTax() {
            return 0;	// 免税
        }
    }
    
  3. 税额计算

    public double totalTax(Income... imcomes) {
        double total = 0.0;
        for (Income income : imcomes) {
            total += income.getTax();
        }
        
        return total;
    }
    
  4. 测试

    1. 无需提前知道有多少种具体类型,只要子类正确地重写父类方法。

    2. 利用多态调用父类定义的方法,即可实现预期目标。

    3. 即使有新的收入类型,只需定义一个新的子类并重写方法即可。

      public static void main(String[] args) {
          Income[] incomes = new Income[] {
              new Income(3000),
              new Salary(10000),
              new TaxExemptIncome(2000)
          };
          System.out.println(totalTax(incomes));
      }
      

小结 & 思考

关键字

① this & super

  • this:表示当前对象实例,用于调用当前对象的结构。
  • super:表示父类,用于调用父类非 private 结构。

② final

  1. 修饰类:无法被继承。
  2. 修饰方法:无法被重写。
  3. 修饰属性:初始化后无法被重新赋值。
    • 声明时初始化。
    • 构造方法初始化。

思考

① 继承存在的问题?

  1. 问题耦合性
    1. 破坏封装:向子类暴露了实现细节。
    2. 耦合:父类改变时,所有子类都可能受到影响。
  2. OOP 设计原则
    1. 合成复用原则:优先使用组合、聚合,其次考虑继承。
    2. 里氏替换原则:继承必须保证父类的性质在子类中成立。
      • 子类重写方法会降低多态的复用性。
      • 若子类方法逻辑与父类方法不同,以多态调用父类方法时无法实现预期功能。

② 重载是多态吗?

标签:JavaSE4,子类,Person,OOP,Student,父类,方法,public,三大
From: https://www.cnblogs.com/secretmrj/p/17097008.html

相关文章