面向对象(上)

构造器

构造器用于创建实例时执行初始化,构造器是创建对象的重要途径。
如果 构造器B包含了构造器A,则可在方法B中调用方法A。可以使用this关键字来调用相应的构造器:

public class Apple
{
  public String name;
  public String color;
  public double weight;
  public Apple(){}
  //两个参数构造器
  public Apple(String name, String color)
  {
    this.name = name;
    this.color = color;
  }
  //三个参数的构造器
  public Apple(String name, String color, double weight)
  {
    //通过this调用另一个构造器的初始化代码
    this(name, color);
    //下面this引用该构造器正在初始化的Java对象
    this.weight = weight;
  }
}

使用 this 调用另一个重载构造器这种方法只能在构造器中使用,而且必需作为构造器执行体的第一条语句。使用 this 调用重载的构造器时, 系统会根据 this 后面括号里的实参来调用形参列表与之对应的构造器。

类的继承

Java继承具有单继承的特点, 每个子类只有一个直接父类。Java类虽然只能有一个直接父类, 但它可以有无限多个间接父类
子类继承了父类, 也将获得父类的全部成员变量和方法。但是, Java的子类不能继承父类的构造器。

重写父类的方法

子类包含与父类同名方法的现象称为方法重写(Override),也被称为方法覆盖。可以说子类重写了父类的方法, 也可以说子类覆盖了父类的方法。
方法的重写要遵循两同两小一大规则.

  1. 两同: 方法名相同 / 形参列表相同
  2. 两小: 子类方法返回值类型应比父类方法返回值类型小或相等. / 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等.
  3. 一大: 子类方法的访问权限应比父类方法的访问权限大或相等.

尤其需要指出, 覆盖方法和被覆盖方法要么都是类方法, 要么都是实例方法。不能一个是类方法, 一个是实例方法。

当子类覆盖了父类方法后, 子类的对象将无法访问父类中被覆盖的方法。但可以在子类方法中使用super(被覆盖的是实例方法) 或者 父类类名(被覆盖的是类方法) 来作为调用者, 调用父类中被覆盖的方法。

如果父类方法具有 private 访问权限, 则该方法对其子类是隐藏的。因此子类无法访问该方法, 也就无法重写该方法。如果子类中定义了一个与父类 private 方法具有相同的方法名 / 相同的形参列表 / 相同的返回值类型的方法, 依然不是重写:这只是在子类中重新定义了一个新的方法。

方法重载和方法重写在英文中分别是 overload 和 override,重载主要发生在同一个类的多个同名方法之间,而重写发生在子类和父类的同名方法之间。
当然, 父类方法和子类方法之间也有可能发生重载, 因为子类会获得父类方法:如果子类定义了一个与父类方法有相同方法名, 但参数列表不同的方法, 就会形成父类方法和子类方法的重载。

super 限定

如果在某个方法中访问名为 a 的成员变量, 但没有显式指定调用者, 则系统查找 a 的顺序为:

  1. 查找该方法中是否有名为 a 的局部变量.
  2. 查找当前类中是否包含名为 a 的成员变量.
  3. 查找 a 的直接父类中是否包含名为 a 的成员变量, 依次上溯 a 的所有父类. 直到 java.lang.Object 类.
  4. 如果最终不能找到名为 a 的成员变量, 则系统出现编译错误.

当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量。也就是说, 当系统创建一个 java 对象时,如果该 java 类有两个父类(一个直接父类 A / 一个间接父类 B),假设 A 类中定义了 2 个实例变量, B 类中定义了 3 个实例变量,当前类中定义了 2 个实例变量, 那么这个 java 对象会保存 2 + 3 + 2 个实例变量因为子类中定义与父类中同名的实例变量并不会完全覆盖父类中定义的实例变量, 它只是简单的隐藏了父类中实例变量,

调用父类构造器

子类不会获得父类的构造器,但子类构造器里可以调用父类构造器的初始化代码,类似于前面所介绍的一个构造器调用另一个重载的构造器。
不管是否使用 super 调用来执行父类构造器的初始化代码,子类构造器总会调用父类构造器一次。子类构造器调用父类构造器分如下几种情况:

  1. 子类构造器执行体的第一行使用 super 显式调用父类构造器.系统将根据 super 调用里传入的实参列表调用父类对应的构造器.
  2. 子类构造器执行体的第一行代码使用 this 显式调用本类中重载的构造器,系统将根据 this调用里传入的实参列表调用本类中的另一个构造器.执行本类中另一个构造器时即会调用父类构造器.
  3. 子类构造器执行体中既没有 super 调用, 也没有 this 调用, 系统将会在执行子类构造器之前, 隐式调用父类无参数的构造器.

不管上面哪种情况, 当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行:不仅如此, 执行父类构造器时, 系统会再次上溯执行其父类构造器……以此类推。创建任何 Java对象, 最先执行的总是 java.lang.Object 类的构造器.

多态

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

多态性

Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换, 或者被称为向上转型(upcasting)。当运行时调用该引用变量的方法时, 其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征, 这就可能出现:相同类型的变量 / 调用同一个方法时呈现出多种不同的行为特征, 这就是多态。

与方法不同的是, 对象的实例变量则不具备多态性。
引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此, 编写 Java代码时, 引用变量只能调用声明该变量时所用类里包含的方法。
例如 通过 Object p = new Person() 代码定义一个变量 p,则这个 p 只能调用 Object 类的方法, 而不能调用 Person 类里定义的方法.

通过引用变量来访问其包含的实例变量时, 系统总是试图访问它编译时类型所定义的成员变量, 而不是它运行时类型所定义的成员变量。

引用变量的强制类型转换

类型转换运算符的用法是: (type)variable
类型转换运算符可以将一个基本类型变量转换成另一个类型。

  • 基本类型之间的转换只能在数值类型之间进行. 这里所说的数值类型包括 整数型 / 字符型 / 浮点型 .
    但数值类型和布尔类型之间不能进行类型转换.

除此之外,这个类型转换运算符还可以将一个引用类型变量转换成其子类类型。

  • 引用类型之间的转换只能在具有继承关系的两个类型之间进行. 如果时两个没有任何继承关系的类型, 则无法进行类型转换,否则编译时就会出现错误. 如果试图把一个父类实例转换成子类类型, 则这个对象必需实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型), 否则将在运行时引发 ClassCastException 异常.

向上转型总会成功证明了子类是一种特殊的父类。这种转型只是表明了这个引用变量的编译时类型是父类,但实际执行他的方法时,依然表现出子类对象的行为方式。

instanceof运算符

instanceof 运算符的格式为

Object(引用变量类型) instanceof class(类或接口)//前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子关系,否则会引起编译错误。

用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。

Object a = String "Hello";
String astring = String "Hello";
if(a instanceof Math)//return false
if(astring instanceof Math)//编译错误

继承与组合

使用继承的注意点

继承带来了高度复用的同时,也严重的破坏了父类的封装性:如果权限允许,子类可以直接复用父类的成员变量(内部信息)和方法。造成子类和父类的严重耦合。
封装性:每个类都应该封装他的内部信息和实现细节,而只暴露必要的方法给其他类使用。

为了报政父类有良好的封装性,不会被子类任意改变,设计父类应该遵循如下规则:

  1. 尽量隐藏父类的内部数据,把成员变量都设置成private访问类型,不要让子类直接访问父类的成员变量。
  2. 不要让子类可以随意访问、修改父类的方法。父类中仅为辅助其他的工具方法,应该是用private访问控制符修饰;需要被外界调用但又不想被子类重写的方法,可以使用final修饰符;想被子类重写但不想被外界访问的方法,应该采用protect修饰符。
  3. 尽量不要在父类构造器中调用被子类重写的方法。

不想当做父类的:
用final修饰这个类;或者将这个类的构造器全部设置为private,从而保证子类无法调用该类的构造器,也就无法继承该类,这是提供一个静态方法,用于创建该类的实例。

何时需要创建新的子类:

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

利用组合实现复用

把旧类对象当做新类的成员变量组合进来(用private修饰),用以实现新类的功能。用户看到的是新类的方法,而不能看到被组合对象的方法。
继承:is-a
组合:has-a
使用组合和使用继承时,系统开销几乎一样大。

初始化块

使用初始化块

{static∪∅} {
    //初始化块的可执行代码
    ...
}//使用static是静态初始化块

初始化块和构造器

初始化块总是在构造器之前执行,不接受任何参数,因此初始化块对同一个类的所有对象所进行的初始化处理完全相同。

如果两个构造器中有相同的初始化段代码,且这些代码无需接受参数,就可以把它们放在初始化块中定义,提高代码的复用和可维护性。(实际在编译时,初始化块中的代码会被还原到每个构造器中,且位于构造器所有代码的前面(super?this?)。)

创建一个java**对象**时的执行顺序:
Object类的初始化块和构造器→…→父类的初始化块和构造器→该类的初始化块和构造器,返回该类得对象。

静态初始化块

static修饰,在类的初始化(第一次使用某个累的时候)阶段执行静态初始化块,而不是在创建对象时才执行。
类的生命周期:类加载,类验证,类准备,类解析,类初始化

当JVM第一次主动使用某个类的时候,系统会在类准备阶段为该类的所有静态成员变量分配内存;
在初始化阶段则负责初始化这些静态成员变量,初始化静态成员变量就是执行类初始化代码或者声明类成员变量时指定的内存值,他们的执行顺序与源代码中的排列顺序相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值