封装
接下来我们进一步来学习一些面向对象编程的技术。先来了解封装的概念。
封装是一种隐藏信息的技术,是将一个系统中的结构和行为通过类来划分的过程。即通过定义一组类,将特定的数据组合到某一个类中,形成一个整体,将该隐藏的数据进行保护,只对外暴露这些数据的访问的方法。
封装代码有两个好处:代码使用者无需考虑实现细节就能直接使用它,同时不用担心不可预料的副作用,别人不能随便修改内部结构
在外部接口保持不变的情况下,自己可以修改内部实现
封装甚至被一些面向对象的开发人员视为第一原则。
如何隐藏类的具体实现和数据?其实之前的代码中我们已经用到了,现在我们来系统地了解一下。
Java是通过访问控制关键字来实现的信息隐藏的,一共有三个关键字:public、protected和private。
关键字可用于修饰类,或者修饰类中的成员变量和成员方法。
用于修饰类public:其他任何类都可以访问该类
private:只能用于修饰内部静态类,一般不提倡
默认情况,如果没有任何访问控制修饰符,则表示相同包内的类可以访问该类
用于修饰成员变量和成员方法:public:其他任何类都可以访问该成员
protected:只有继承自己的子类才能访问该成员
private:除自己外其他任何类都不能访问该成员
默认情况,如果没有任何访问控制修饰符,则表示相同包内的类可以访问该成员
可见,如果要隐藏一个类的成员变量,只要在该成员变量的前面加上private,这样外部就无法直接通过类的实例来访问这些成员变量了。
回头再看Post类的代码,一开始我们就用public修饰类本身,而用private修饰了所有成员变量,现在你应该知道其中的含义了。package com.tianmaying.domain;
public class Post {
private long id;
private String title;
private String content;
private static int count = 0;
...
}
想要让外部访问该成员变量的话,可以给这些私有成员变量添加public的访问方法:package com.tianmaying.domain;
public class Post {
...
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
这样的方法我们称之为setter和getter,在Eclipses中我们可以直接通过菜单栏的【Source】->【Generate Getters and Setters】来生成。
继承
继承的语法
继承是一种类和类之间的关系,是面向对象系统的基石。继承表明为一个"是一种"(is-a)的关系,为在现实中有很多这样的例子:学生是一种人;树是一种植物,矩形是一种图案。
我们可以把共性的结构和行为放到父类中,子类可以通过继承复用父类中的代码,并且根据自己的需要进行扩展。
在Java中,使用extends关键字表示继承关系,来看一个具体的例子:class Graph {
String name;
public Graph(){}
public Graph(String name) {
this.name = name;
}
public void show() {
System.out.println("I'm a graph");
}
}
class Rectangle extends Graph {
int width;
int height;
public Rectangle(){
super();
}
public Rectangle(String name) {
super(name);
}
public Rectangle(int width, int height,String name) {
this(name);
System.out.println("My width is:" + width + "my height is :"+ height);
}
public void show() {
super.show();
System.out.println("at the same time I'm a Rectangle");
}
}
Java中的继承是单继承的,也就是说一个子类只能继承一个父类。子类会继承父类中的除构造函数以外的所有非private成员方法,以及所有非private成员变量。
具体到这个例子中来,由于Rectangle继承了Graph,它默认就具有了name属性和show方法,而无需自己声明。
构造方法的调用
生成子类对象或者实例时,Java默认地首先调用父类的不带参数的构造方法,接下来再调用子类的构造方法,生成子类对象。
this表示对当前对象的引用,而super表示对父类对象的引用。在子类的构造函数中,一般第一条语句是supre();,表示调用父类构造函数。也可以调用父类有参数的构造函数,比如super(name);。
如果一个类的构造函数的第一语句既不是this()也不是super()时,就会隐含的调用super()。
方法覆盖
如果子类中有和父类中非private的同名方法,且返回类型和参数表也完全相同,就会覆盖从父类继承来的方法。
当两个方法形成重写关系时,可以在子类中通过super关键字调用父类被重写的方法。
比如Retangle就覆盖了Graph的show方法,同时爱show方法中通过super.show();调用了父类的show方法。
回顾
我们已经实现了PostRepository,但是所有博客内容只保存在内存中,程序结束所有内容就会消失。假如我们希望将博客内容保存到TXT文件中,此时我们就可以定义一个FlatFilePostRepository,通过继承PostRepository就能自动拥有博客管理的功能,即add()、remove()等成员方法。然后我们只需在其中添加从文件加载博客loadData()以及将博客写入文件saveData两个方法即可。package com.tianmaying.repository;
public class FlatFilePostRepository extends PostRepository {
public static void saveData() {
// 从TXT文件加载所有博客信息
}
public static void loadData() {
// 将所有博客信息写入TXT文件
}
}
如果没有继承,我们就要把PostRepository中那些代码拷贝过来,这样的重复代码显然是难以维护的。我们从中可以看到继承对于代码复用的作用。
可能有人说可以直接在PostRepository中添加saveData()和loadData()方法。那如果将来我们希望将博客信息保存到XML文件或者数据中时,怎么办呢?这里我们就能看到继承的第二个作用,建立这样的抽象层次,将来系统会更容易扩展。如果我们希望实现XML保存博客的功能,那么我们再实现一个XMLPostRepository,让它也继承PostRepository就行了。
至于这两个方法如何实现,我们后面讲解。
final关键字
final 修饰变量
一个变量可以声明为final,这样做的目的是阻止它的内容被修改。这意味着在声明final变量的时候,必须初始化它(在这种用法上,final类似于C/C++中的const)。
通常情况下,我们会使用final来定义一些常量,例如:public class Post {
private String title;
private String content;
private static int count = 0;
public final static String DEAFUTL_Content = "这个家伙很懒,什么也没写";
public Post(){
count++;
}
public static int getCount(){
return count;
}
...
}
final变量的所有字符选择大写是一个普遍的编码约定,用final修饰的变量在实例中不占用内存,它实质上是一个常数。
这里我们定义了表示一篇博客的默认标题的静态变量DEAFUTL_Content,我们用final修饰该变量表明该对象在最初的赋值后是不可修改的,如果我们试图调用DEAFUTL_Content = "another title"的话Java编译器会直接抛出错误。final修饰符可以保证我们的变量值的安全性。
final 修饰方法
被final修饰的方法可以被子类继承,不能被子类的方法覆盖,因此,如果一个类不想让它的子类覆盖它的某个成员方法,就可以在该成员方法前面加上final关键字
final不能修饰构造方法。由于父类中的private成员方法是不能被子类覆盖的,所有有private限制的成员方法默认也是final的。
在之前的例子中,我们不希望getCount()方法被其子类覆盖,这样可以保证getCount()方法必然可以得到当前的Post数量而不被篡改,此时我们将Post修改为:public class Post {
private String title;
private String content;
private static int count = 0;
public final static String DEAFUTL_TITLE = "这是一篇天码营的博客";
public Post(){
count++;
}
public static final int getCount(){
return count;
}
...
}
使用final修饰方法除了不想让子类覆盖之外,还有一个原因就是高效,Java编译器在遇到final关键字修饰的方法时会使用内联机制,省去函数调用的开销,大大提高执行效率。
final 修饰类
由final修饰的类是不能类是不能继承的,因此,如果设计类的时候不想让该类被继承,就在该类的前面加上final关键字。public final class Post {
private int id;
private String title;
private String content;
private static int count = 0;
public final static String DEAFUTL_TITLE = "这是一篇天码营的博客";
public Post(){
count++;
}
public static final int getCount(){
return count;
}
...
}
例如如果我们将Post声明为final,其他子类将不能再继承Post类。