JavaSE-封装、继承、多态


封装

封装:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的的方法来实现对内部信息的操作和访问

为了实现良好的封装,需要从两方面考虑:

  • 讲对象的成员变量和实现细节隐藏起来,不允许外部直接访问
  • 把方法暴露出去,让方法来控制对这些成员变量进行安全的访问和操作

因此,封装实际上有两方面的含义:把该隐藏的隐藏起来,把该暴露的暴露出来。这两个方面都需要通过使用Java提供的控制访问符来实现

访问控制符

Java提供了三个访问控制符:public protected private 分别代表了3个访问控制级别,另外还有一个不加任何访问控制符的访问控制级别,提供了4个访问控制级别

访问控制级别由小到大:private → default → protected → public

  • private(当前类访问权限):除了包含该成员的类之外,其他任何类都无法访问这个成员
  • default(包访问权限):默认访问权限没有任何关键字,通常指的就是报访问权限(有时也表示为friendly),这就意味着当前包中的所有其他类对那个成员都有访问权限,但对于这个包之外的所有类,这个成员是私有的
  • protected(子类访问权限):如果一个成员使用 protected 访问控制符修饰,那么这个成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问。通常情况下,如果使用 protected 来修饰一个方法,通常是希望其子类来重写这个方法
  • public(公共访问权限):使用public修饰的成员或外部类可以被所有类访问
private default protected public
同一个类中
同一个包中
不同包下的子类
不同包下的无关类

继承

Java的继承通过 extends 关键字来实现,实现继承的类被称为子类,被继承的类称为父类,有的也称为其为基类、超类
子类继承父类的语法格式:修饰符 class SubClass extends SuperClass{}

继承的特点:

  • 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器
  • Java是单继承模式:即一个类只能继承一个直接父类;Java不支持多继承、但是支持多层继承,允许一个类有无限多个间接父类
  • 如果定义一个类时并未显示指定这个类的直接父类,则这个类默认扩展 java.lang.Object
  • 子类可以继承父类的私有成员,只是不能直接访问

设计父类需要遵循的规则

继承是实现类复用的重要手段,但继承也带来了一个最大的坏处:破坏封装,子类可以直接访问父类的成员变量和方法,从而造成子类和父类的严重耦合。为了保证父类有良好的封装性,不会被子类随意改变,设计父类通常应该遵循如何规则

  • 尽量隐藏父类的内部数据。尽量把父类的所有成员变量都设置为 private 访问类型,不要让子类直接访问父类的成员变量
  • 不要让子类可以随意访问、修改父类的方法
    • 父类中那些仅为辅助其他的工具方法,应该使用 private 访问修饰符,让子类无法访问该方法
    • 如果父类中的方法需要被外部类访问,则必须以 public 修饰
    • 如果父类中的方法需要被外部类访问的同时,又不希望子类重写该方法,可以使用 final 修饰符来修饰该方法
    • 如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,则可以使用 protected 来修饰该方法
  • 尽量不要在父类构造器中调用将要被子类重写的方法

到底何时需要从父类派生新的子类呢?不仅需要保证子类是一种特殊的父类,而且需要具备一下两个条件之一:

  • 子类需要额外增加属性,而不仅仅是属性值的改变
  • 子类需要增加自己独有的行为方式(包括增加新的方式或重写父类的方法)

多态

Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态

  • 多态的前提:有继承/实现关系;有父类引用指向子类对象;有方法重写
  • 多态的优势:在多态形式下,右边对象可以实现解耦合,便于扩展和维护
    • 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利

多态的常见形式

  • 父类类型 对象名称 = new 子类构造器;
  • 接口 对象名称 = new 实现类构造器;

Java允许把一个子类对象直接赋给一个父类引用变量,无需任何类型转换;向上转型是由系统自动完成

多态中成员访问特点

  • 方法调用:编译看左边,运行看右边
  • 变量调用:编译看左边,运行也看左边(多态侧重行为多态)

?? 多态下会产生的一个问题:多态下不能使用子类的独有功能?
引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用的对象确实包含该方法。如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换为运行时类型

强制类型转换

类型强制转换语法格式:(type)variable

  • 基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行类型转换
  • 引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则将在运行时引发 ClassCastException 异常

考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前应先通过 instanceof 运算符来判断是否可以成功转换,从而避免ClassCastException异常,保证了程序更加健壮

if(objPri instanceof String){
    String str = (String)objPri;
}

instanceof 运算符

instanceof运算符的前一个操作数通常是一个引用类型的变量,后一个操作数通常是一个类(也可以是一个接口),它用于判断前面的对象是否是后面的类,或者是其子类、实例类的实例。如果是,则返回true,否则返回false

注意:instanceof运算符前面的操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误