封装

接下来我们进一步来学习一些面向对象编程的技术。先来了解封装的概念。

封装是一种隐藏信息的技术,是将一个系统中的结构和行为通过类来划分的过程。即通过定义一组类,将特定的数据组合到某一个类中,形成一个整体,将该隐藏的数据进行保护,只对外暴露这些数据的访问的方法。

封装代码有两个好处:

  • 代码使用者无需考虑实现细节就能直接使用它,同时不用担心不可预料的副作用,别人不能随便修改内部结构
  • 在外部接口保持不变的情况下,自己可以修改内部实现

封装甚至被一些面向对象的开发人员视为第一原则。

如何隐藏类的具体实现和数据?其实之前的代码中我们已经用到了,现在我们来系统地了解一下。

Java是通过访问控制关键字来实现的信息隐藏的,一共有三个关键字:publicprotectedprivate

关键字可用于修饰类,或者修饰类中的成员变量和成员方法。

用于修饰类

  • 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;
    }

}

这样的方法我们称之为settergetter,在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就覆盖了Graphshow方法,同时爱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类。

登录发表评论 注册

.JPG

1024

反馈意见