1、 引言
面向对象编程允许你从已经存在的类中定义新的类,这成为继承。
2、 父类和子类
继承使得你可以定义一个通用的类(即父类),之后扩充该类为一个更加特定的类(即子类)。
关于继承应该注意的几个关键点
- 和传统的理解不同,子类并不是父类的一个子集。实际上,一个子类通常比它的父类包含更多的信息和方法。
- 父类中的私有数据域在该类之外是不可访问的。因此,不能在子类中直接使用。但是,如果父类中定义了公共的访问器/修改器,那么可以通过这些公共的访问器/修改器来访问和修改它们。
- 不是所有的“是一种”(is-a)关系都应该用继承来建模。例如:正方形是一种矩形,但是不应该定义一个 Square 类来扩展 Rectangle 类,因为 width 和 height 属性并不适合于正方形。应该定义一个继承自 GemetricObject 类的 Square 类,并为正方形的边定义一个 side 属性。
- 继承是用来为“是一种”(is-a)建模的。不要仅仅为了重用方法这个原因而盲目地扩展一个类。例如:尽管 Person 类和 Tree 类可以共享类似高度和重量这样的通用特性,但是从 Person 类扩展出 Tree 类是毫无意义的。一个父类和它的子类之间必须存在是“是一种”(is-a)关系。
- 某些程序设计语言允许从及各类派生出一个子类的。这种能力成为多重继承(multiple inheritance)。但是限制称为单一继承(single inheritance) 。如果使用 extends 关键字来定义一个子类,它只允许有一个父类。然而,多重继承是可以通过接口来实现的。
3、 使用 super 关键字
关键字 super 指代父类,可以用于调用父类中的普通方法和构造方法。
子类继承它的父类中所有可访问的数据域和方法。
3.1、 调用父类的构造方法
super()或者super(parameters);
语句super()和super(arguments)必须出现在子类构造方法的第一行,这是显示调用父类构造方法的唯一方式。
要调用父类构造方法就必须使用关键字 super,而且这个调用必须是构造方法的第一条语句。在子类汇总调用父类构造方法的名字会引起一个语法错误。
3.2、 构造方法链
在任何情况下,构造一个类的实例时,将会调用沿着继承链的所有父类的构造方法。当构造一个方法的对象时,子类构造方法会在完成自己的任务之前,首先调用它的父类的构造方法。如果父类继承自其它类,那么父类构造方法又会在完成自己的任务之前,调用它自己的父类的构造方法。这个过程持续到沿着这个继承体系结构的最后一个构造方法被调用为止。这就是构造方法链(constructor chaining)
如果要设计一个可以被继承的类,最好提供一个无参构造方法以避免程序设计错误。
一般情况下,最好能为每个类提供一个无参构造方法,以便于对该类进行扩展,同时避免错误。
3.3、 调用父类的方法
super.方法名(参数)
4、 方法重写
要重写一个方法,需要在子类汇总使用和父类一样的签名以及一样的返回值类型来对该方法进行定义。
子类从父类中继承方法。有时,子类需要修改父类中定义的方法的实现,这称为方法重写(method overriding)。
以下几点值得注意:
- 仅当实例方法时可访问时,它才能被覆盖。因为私有方法在它的类本身以为是不能访问的,所以它不能被覆盖。如果子类汇中定义的方法在父类汇总是私有的,那么这两个方法完全没有关系。
- 与实例方法一样,静态方法也能也继承。但是,静态方法不能被覆盖。如果父类中定义的静态方法在子类中被重新定义,那么在父类中定义的静态方法将被隐藏。可以使用语法:父类名.静态方法名(superClassName.staticMethodName)调用隐藏的静态方法。
5、 方法重写与重载
重载意味着使用同样的名字但是不同的签名来定义多个方法。重写以为着在子类汇总提供一个对方法的新的实现。
注意以下问题:
6、 Object 类及其 toString() 方法
Java中的所有类都继承自 java.lang.Object 类。
toString()方法
7、 多态
- 方法重写发生在通过继承而相关的不同类中;方法重载可以发生在同一个类中,也可以发生在由于继承而相关的不同类中。
- 方法重写具有同样的签名和返回值类型;方法重载具有同样的名字,但是不同的参数列表。
多态意味着父类的变量可以指向子类对象。
子类型和父类型(一个类实际定义了一种类型。子类定义的类型成为子类型,而父类的类型成为父类型)
继承关系使一个子类继承父类的特征,并且附加一些新特征。子类是它的父类的特殊化,每个子类的实例都是其父类的实例,但是反过来就不成立。例如:每个圆都是一个几何对象,但并非每个几何对象都是圆。因此,总可以将子类的实例传给需要父类型的参数。
8、 动态绑定
方法可以在沿着继承链的多个类中实现。JVM决定运行时调用那个方法。
声明类型(一个变量必须被声明为某种类型,变量的这个类型成为它的声明类型)和实际类型(是变量引用的对象的实际类)
变量调用方法由变量的实际类型决定,这称为动态绑定(dynamic binding)。
匹配方法的签名和绑定方法的实现吗是两个不同的问题。引用变量的声明类型决定了编译时匹配哪个方法。在编译时,编译器会根据参数类型、参数个数和参数顺序找到匹配的方法。一个方法可能在沿着继承链的多个类中实现。Java虚拟机在运行时动态绑定方法的实现,这是有变量的实际类型决定的。
9、 对象转换和 instanceof 关键字
对象的引用可以类型转化为对另一个对象的引用,这称为对象转换。
instanceof 关键字 (转换之前确保该对象是另一个对对象的实例)
为了更好地理解类型转换,可以认为他们类似于水果、苹果、橘子之间的关系,其中水果类 Fruit 是苹果类 Apple 和橘子类 Orange 的父类。苹果是水果,所以,总是可以将Apple 的实例安全地赋值给 Fruit 变量。但是,水果不一定是苹果,所以,必须进行显示转换才能将 Fruit 的实例赋值给 Apple 的变量。
对象成员访问运行符(.)优先于类型转换运算符。使用元括号保证在点运算符(.)之前进行转换
10、 Object类的equals方法
object.euqals(object o);
比较运算符 == 用来比较两个基本数据类型的值是否相等,或者判断两个对象是否具有相同的引用。如果想让equals方法能够判断两个对象是否具有相同的内容,可以在定义这些对象的类时,重写类中的euqals方法。运算符 == 要比 equals 方法的功能强大些,因为 == 运算符可以检测两个引用变量是否指向同一个对象
在子类中,使用签名 equals(SomeClassName obj) 重写euqals方法是一个常见错误,应该使用equals(Object obj)。
11、 ArrayList类
ArrayList类对象可以用于存储一个对象列表。
数组和 ArrayList 之间的差异
操作 数组 ArrayList 创建数组 / 数组列表 String[] a = new String[10] ArrayList list<String> = new ArrayList() 引用元素 a [index] list.get(index) 更新元素 a [index] = "London" list.set(index, "London") 返回大小 a.length list.size() 添加一个新元素list.add("London")
插入一个新元素 list.add(index, "London") 删除一个元素 list.remove(index) 删除一个元素 list.remove(Object) 删除所有元素list.clear()
12、 protected 数据和方法
一个类中的受保护成员可以从子类中访问。
类中成员的修饰符 | 在同一类内可访问 | 在同一包内可访问 | 在子类内可访问 | 在不同包可访问 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | - |
(default) | √ | √ | - | - |
private | √ | - | - | - |
子类可以重写它的父类的 protected 方法,并把它的可见性改为public 。 但是,子类不能削弱父类汇总定义的方法的可访问性。
13、 防止扩展和重写
一个被 final 修饰的类和方法都不能被扩展。被 final 修饰的数据域是一个常数。
修饰符 public、protected、private、static、abstract 以及final可以用在类和类的成员(数据和方法)上,只有 final 修饰符可以用在方法中的局部变量上。方法内的最终局部变量就是常量。