在《Java编程思想》中,继承这一特性是放在 第七章 复用类 中去讲的。 这样做是有道理的,继承仅仅是复用代码的一种方式。 其实所谓的复用就是“利用现成的东西”的意思,其实实现的两种方法就是Java中经常听到的——组合和继承。
本文内容: - 前言 - 组合 - 继承 - 初始化与构造器 - 组合&继承 - protected关键字 - 向上转型的简述 - final关键字 - static关键字 - 后记
在新的类中产生现有类的对象。由于新的类是由现有类的对象所组成,所以这种方法成为组合。
一开始我被这么官方的解释蒙住了,但是看一下简单的代码就会明白:
public class Car { Tire tire; public String toString() { return "drive"; } } class Tire{ }其实这就是我们经常会用到的在一个类中使用另一个类的对象,只是我们之前在学习的时候可能并没有将其称为“组合”。以面向对象来看这种功能的作用是强大的,我们可以为Car中每一个零件都创建一个类然后在再把这些部件组合起来。
与组合的平实相比较,继承则是一种特殊的语法。
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类
继承是一种构建新类的方式,他是基于已有的类的定义为基础,构建新的类,已有的类称为父类,新构建的类称为子类,子类能调用父类的非private修饰的成员,同时还可以自己添加一些新的成员,扩充父类,甚至重写父类已有的方法,更其表现符合子类的特征。让子类的表现更独特,更专业。
继承的过程其实就是声明“新类与旧类相似”,而用Java语言来表示,就是一个类后面紧跟 extends关键字,再加一个类的名字,则表示新建的类继承自extends 后面的那个类。 关于继承的语法实现,可以看看下面的代码:
//Cartoon.java class Art { //这是构造器 Art() { print("Art constructor"); } } class Drawing extends Art { //这是构造器 Drawing() { print("Drawing constructor"); } } public class Cartoon extends Drawing { public Cartoon() { print("Cartoon constructor"); } public static void main(String[] args) { Cartoon x = new Cartoon(); } }输出是:
//OutPut Art constructor Drawing constructor Cartoon constructor上面的Cartoon.java代码,不仅说明了继承的语法,还展示了一种重要的机制:
我们知道初始化早Java中占有至关重要的地位,而构造器则是精巧的初始化机制。 当我们在创建一个子类对象的时候,该子类对象还包含了一个父类的子对象(子对象与父类直接创建的对象是一样的,但是被包装在子类的内部),所以我们在创建子类对象的时候,必然要对其包含的父类子对象进行初始化。因此Java会自动在子类的构造器中插入对父类构造器的调用。
并且通过上面的示例可以看出,构建过程是从父类“向外”扩散的,也就是从父类开始向子类一级一级地完成构建。所以在父类的构造器可以访问它之前,就已经完成了初始化。即使没有为Catroon()创建构造器,编译器也会为你合成一个默认的构造器来调用基类的构造器。
在上面的例子中,编译器调用构造器显得格外的轻松,因为我们发现这些构造器都是默认构造器不含参的,因此不必要考虑传什么参数的问题。但是如果要调用父类的带参数的构造器,我们就必须使用关键字super来显式的调用父类的构造器,并配以相应的参数列表。
//Chess.java class Game { //带参的构造函数 Game(int i) { print("Game constructor"); } } class BoardGame extends Game { //这个也是带参数的构造函数 BoardGame(int i) { super(i); //通过super关键字调用父类的构造器,是子类的构造器中要做的第一件事 print("BoardGame constructor"); } } public class Chess extends BoardGame { Chess() { super(11); //配以相应的参数 print("Chess constructor"); } public static void main(String[] args) { Chess x = new Chess(); } }输出是:
//OutPut Game constructor BoardGame constructor Chess constructor要注意上述代码的注释:通过super关键字调用父类的构造器,是子类的构造器中要做的第一件事
组合和继承同属于复用技术,都允许在新的类中放置子对象,组合是显式的这样做,而继承则是隐式地做。因此我们在使用的时候要明确两者之间的区别。打一个生动的比方:
继承是说“我父亲在家里给我帮了很大的忙”。 组合是说“我请了个老头在我家里干活”。
组合是在一类类中引用另一个类。生成另一个类的实例。而继承只是继承了父类的变量和方法。 使用组合可以用到另一个类中私有的变量和方法,而继承就不可以用到父类的私有的变量和方法了。
尽管面向对象的编程对继承极力强调,但在开始一个设计的时候我们一般优先选择组合,或是代理模式(我想在模式设计中提到它)。只有在确实需要的是hi才回去使用继承,因为组合更具有灵活性。 而实际上我们要慎用继承。 首先我们需要明确,继承存在如下缺陷: * 父类变,子类就必须变。 * 继承破坏了封装,对于父类而言,它的实现细节对与子类来说都是透明的。 * 继承是一种强耦合关系。
所以说当我们使用继承的时候,我们需要确信使用继承确实是有效可行的办法。那么到底要不要使用继承呢?《Think in java》中提供了解决办法:
问一问自己是否需要从子类向父类进行向上转型。如果必须向上转型,则继承是必要的,但是如果不需要,则应当好好考虑自己是否需要继承。
private访问修饰符,对于封装而言,是最好的选择,但这个只是基于理想的世界。有时候我们需要这样的需求: 我们需要将某些事物尽可能地对这个世界隐藏,但是仍然允许子类的成员来访问它们。 这个时候就需要使用到protected。 对于protected而言,它指明就类用户而言,他是private,但是对于任何继承与此类的子类而言或者其他任何位于同一个包的类而言,他却是可以访问的。
public class Person { private String name; private int age; private String sex; protected String getName() { return name; } protected void setName(String name) { this.name = name; } public String toString(){ return "this name is " + name; } /** 省略其他setter、getter方法 **/ } public class Student extends Person{ private Teach而 teacher; public String toString(){ setName("Japson"); //调用父类的setName(); return super.toString(); //调用父类的toString()方法 } public static void main(String[] args) { Student student = new Student(); System.out.println(student.toString()); } } Output: this name is Japson从上面示例可以看出子类Student可以明显地调用父类Person的setName()。 尽管可以使用protected访问修饰符来限制父类属性和方法的访问权限,但是最好的方式还是将属性保持为private(我们应当一致保留更改底层实现),通过protected方法来控制类的继承者的访问权限。
第一次在一本书中接触向上转型这一概念时,我觉得这个概念理解起来很简单,我甚至可以有一百种方法来解释它。简单的说在上面的继承中我们谈到继承是is-a的相互关系,在上述的例子中,学生继承与人,所以我们可以说学生是人,或者说学生是属于人的。这样将学生看做人就是向上转型。或者说: 向上转型是从一个较专用类型向较通用的类型转换。
但是让我搞不清楚的是,我既然知道了这是一个子类的对象,我为什么要使用向上转型把它定义为一个父类类型,而不是直接使用其自己的类型呢?
关于这个问题和向上转型的详解,涉及到多态。所以我想在另一篇笔记中介绍。
当我看到这个标题的时候,我脑子里跳出的第一个东西就是那道再熟悉不过的面试题
final、finally和finalize的区别是什么?
这是一道再经典不过的面试题了,我们在各个公司的面试题中几乎都能看到它的身影。尤其是我在还没有系统的复习Java知识时,在牛客网上看到这道题,心里想的是:这是什么鬼? 虽然final、finally和finalize它们三个看上去就一副有奸情的样子,但是它们的含义和用法却是大相径庭。在这一段中我想把它们三个彻底搞清楚!
首先我们要说这一部分的主角 关键字final
通常它指的是“这是无法改变的。”不想改变可能处于两种理由:设计或效率。
关键字final它可以用于三种情况:数据、方法和类。
我们希望数据是恒定不变时,就要用final来修饰。 如果final修饰的是一个基本类型,就表示这个变量被赋予的值是不可变的,即它是个常量; 如果final修饰的是一个对象,就表示这个变量被赋予的引用是不可变的。这里需要明确的是:一旦引用被初始化指向一个对象,是无法把它改指向另一个对象,而对象其自身是可以被修改的。
并且final域总是在使用前被初始化。
使用final方法的原因有两个。第一个原因是把方法锁定,以防止任何继承类修改它的含义。这样做的目的是为了确保在继承中方法行为保持不变,并且不会被覆盖。 第二个原因是因为效率。
并且类中的所有private方法都隐式地指定为是final的。
将某各类的整体定义为final时(也就是在class前加final)意味着不想让任何人继承这个类,也就是说,不希望它有子类。
final和finally、finalize确实没什么联系,但是它和另外一个关键字static纠缠不清,下面说一下static: static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块。
被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。
用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象时,不生成static变量的副本,而是类的所有实例共享同一个static变量。
static变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用(当然也可以在非静态成员方法中使用–废话),但是不能在其他类中通过类名来直接引用,这一点很重要。实际上你需要搞明白,private是访问权限限定,static表示不要实例化就可以使用,这样就容易理解多了。static前面加上其它访问权限关键字的效果也以此类推。 static修饰的成员变量和成员方法习惯上称为静态变量和静态方法,可以直接通过类名来访问,访问语法为: 类名.静态方法名(参数列表…)
用static修饰的代码块表示静态代码块,当Java虚拟机(JVM)加载类时,就会执行该代码块。
下面给一个表格:
修饰static :静态的fianl:只能赋值一次static final:联合修饰属性只属于类,不属于类的对象基本类型不能被修改;引用类型的引用指向不可以被修改属于类且只能赋值一次修饰方法只属于类,不属于类的对象不可以被重写属于类且不可以被重写修饰类可以修饰内部类,方法和成员变量;不可以修饰外部类和局部变量不可以被继承不可以被继承在final说完以后,我们再说说finally 这个比较简单,它只能用在try/catch语句中,并且附带着一个语句块,表示这段语句最终总是被执行。
public final class FinallyTest { public static void main(String[] args) { try { throw new NullPointerException(); } catch (NullPointerException e) { System.out.println("程序抛出了异常"); } finally { System.out.println("执行了finally语句块"); } } }最后再来说说finalize 它是一个方法,属于java.lang.Object类,它的定义如下:
protected void finalize() throws Throwable { }在此我们说说finalize()方法的作用是什么? 我们可以先把它看成是析构函数的替代者,垃圾回收器准备释放内存的时候,会先调用finalize()。 finalize()方法是在GC清理它所从属的对象时被调用的,如果执行它的过程中抛出了无法捕获的异常(uncaught exception),GC将终止对改对象的清理,并且该异常会被忽略;直到下一次GC开始清理这个对象时,它的finalize()会被再次调用。
在最后《Java编程思想》中有一话,讲述了程序设计中复用的思想:
当你开始设计一个系统时,应该认识到程序开发是一种增量过程,犹如人类的学习一样,这一点很重要。程序开发依赖于实验,你可以尽己所能去分析,但当你开始执行一个项目时,你仍然无法知道所有的答案。如果将项目视作是一种有机的、进化着的生命体而去培养,而不是打算像盖摩天大楼一样快速见效,就会获得更多的成功和更迅速的回馈。继承与组合正是在面向对象程序设计中使得你可以执行这种实验的最基本的两个工具。