第5章:Java高级类特性5:抽象类_接口_工厂方法_内部类


时间:2018-07-26 作者:魏文应


一、抽象类

什么是抽象类?

什么叫抽象类?我们先看下面例子:

  • 抽象类

如果你要创建一个 教师 这个 类的实例,我们肯定不会用 生物 这个类去创建 教师 这个类的实例。同样的,你要是创建学生,工人这些类的实体,也不会用 生物 这个类去创建。最终导致的一个现象是,生物这个类没有实例化的必要,我们压根没有实例化它的需求。这种类就是 抽象类。抽象类,就是抽象出某类对象集合基本特性。

抽象类的形式

抽象类使用 abstract 关键字修饰:

abstract class Person{

}

这个类 不能直接实例化,比如下面是 错误的

public class TestAbstract {
    public static void main(String[] args){ 
        Person p = new Person();
        p.eat();
    }   
}

abstract class Person{
}

上面代码中,new Person 实例化一个抽象类,会报错 。抽象类有以下特点:

  • 不可以被实例化。
  • 抽象类有构造器(凡是类都有构造器)。

抽象方法

可以使用 abstract 修饰方法,表示抽象方法:

abstract class Person{
    public abstract void eat();
    public abstract void walk();
}

上面的 eat() 方法就是 抽象方法。方法内没有执行方法,需要子类去重写:

class Student extends Person {
    public void eat(){
        System.out.println("学生吃饭");
    }
    public void walk(){
        System.out.println("学生吃饭");
    }
}

上面代码中,抽象方法在子类中,必须重写,而是 所有抽象方法都要得到重写。比如,下面是 错误的,walk() 方法得不到重写:

class Student extends Person {
    public void eat(){
        System.out.println("学生吃饭");
    }
}

抽象方法 必须在抽象类内,比如下面是 错误的

class Person{
    public abstract void eat();
}

上面的Person类 不是抽象类,那么在其内部使用抽象方法,会 导致报错 。总之,在 可以实例化 的类中,必须确保每一个方法 都有方法体。比如下面是 没有方法执行体的

public abstract void eat();

而下面这样是 有方法执行体的,只是执行方法的内容为空:

public void eat(){

}

下面是抽象类和抽象方法的 使用

public class TestAbstract {
    public static void main(String[] args){ 
        Person p = new Student();
        p.eat();
    }
}

不能使用 abstract 修饰的情况

abstract 不能用来修饰属性、构造器、private、final、static。下面的用法 都是错误的

abstract class Test{
    // 不能用abstract 修饰属性
    abstract int name;
    // 不能用abstract 修饰构造器
    public abstract Test(){
    }
    // 不能用abstract 修饰private私有方法
    private abstract void method1();
    // 不能用abstract 修饰final方法
    public abstract final void method2();
    // 不能用abstract 修饰static静态方法
    public abstract static void method3();
}

不能修饰的原因很简单,abstract 修饰的,就意味着子类可以去重写,如果不能重写,就不能用abstract。而使用absract 修饰 有static 修饰的方法时,当通过 类名.方法 的形式调用方法,会导致没有方法执行体,比如上面的 Test.method3(),使用static修饰的方法也不能使用abstract修饰。

二、抽象类设计理念

抽象类体现的的就是一种 模板模式的设计。抽象类作为多个类的 通用模板。子类在抽象类的基础上进行扩展、改造,但子类从总体上 保留抽象类的行为方式

其解决了以下问题:

  • 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把 不确定的部分 暴露出去,让子类去实现。
  • 编写一个抽象父类,父类提供了多个子类的 通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。

三、接口

接口定义

为什么需要接口?有时必须从 几个类 中派生出 一个子类,继承它们 所有的 属性和方法。但是,Java不支持多重继承。有了接口,就可以 得到多重继承的效果

接口,是一种 特殊的抽象类。这种抽象类中,只包含常量和方法的定义,而没有变量和方法的实现。下面是 定义接口的形式:

interface Test{

}

上面定义了 一个名为Test的接口,我们在这个接口内部,定义 常量抽象方法

interface Test{
    public static final int I =12;
    public static final boolean FLAG = false;

    public abstract void method1(); 
}

事实上,在接口内部,public static final 默认 被加在常量前面,public abstract 默认 被加在方法前面。下面的写法,和上面的写法是一样的:

interface Test{
    int I =12;               // 默认在其前面加上 public static final
    boolean FLAG = false;    // 默认在其前面加上 public static final

    void method1();          // 默认在其前面加上 public abstract
}

接口内部,只有 常量抽象方法,没有 构造器、方法、属性。接口与接口之间,可以有 继承关系,和类只能单继承不一样,接口可以 多继承

interface Test3 extends Test2,Test1{

}

上面代码中,Test3 继承了 Test2 和 Test1

接口的使用

那么接口怎么使用呢?接口定义的只是功能,要使用这些功能,需要通过类去重写方法,然后去实例化。比如下面代码:

interface Test{
    int AGES = 12;               
    void method();  
}
class Person implements Test{
    public void method(){
        System.out.println(AGES);
    }
}
public class TestInterface {
    public static void main(String[] args) {
        System.out.println(Person.AGES);
    }
}

上面 Person 类,是实现 Test 接口的类。Person中,重写了 method() 方法。里面的常量 AGES,在Person 中可以直接使用,在外部通过 Person.AGES 形式使用,或者通过 Test.AGES 形式使用。接口的方法必须在类中 全部得到重写

interface Test1{             
    void method1(); 
}
interface Test2{             
    void method2(); 
}
interface Test3 extends Test2{             
    void method3(); 
}
class Person implements Test1,Test3{
    public void method1(){
        System.out.println(AGES);
    }
    public void method2(){
        System.out.println(AGES);
    }
    public void method3(){
        System.out.println(AGES);
    }
}

上面代码中,接口的实现类 Person ,需要重写Test1、Test3的所有方法。因为Test3继承了Test2,所以 Person 类还必须要 重写Test2内的所有方法。也就是说,Person 类要重写 Test1、Test2、Test3 接口的所有方法。

从上面可以看出,接口的实现依赖类,让类去实现接口中的抽象定义。

接口的多态

接口的多态,和类的多态有些类似:

interface Runner{
    public abstract void run();
}

interface Swimmer{
    void swim();
}
class Duck implements Runner,Swimmer{
    public void swim() {
        System.out.println("鸭子游泳。");        
    }
    public void run() {
        System.out.println("鸭子走路。");        
    }
}
public class TestInterface {
    public static void main(String[] args) {
        Duck d = new Duck();
        
        TestInterface.test1(d);
        TestInterface.test2(d);
    }

    public static void test1(Runner r){
        r.run(); // 虚拟方法调用
    }
    public static void test2(Swimmer s){
        s.swim();
    }
}

上面代码中,Duck 类是 Runner 和 Swimmer 两个接口的实现,那么可以通过 虚拟方法调用,实现类似于上面的多态传参。

四、接口设计模式:工厂方法

什么是工厂方法?定义一个 用于创建对象的接口,让 子类决定实例化 哪一个类。比如,下面代码:

interface Work{
    void doWork();
}
interface IWorkFactory{
    Work getWork();
}
class StudentWork implements Work{
    public void doWork() {
        System.out.println("学生写作业");
    }
}

class TeacherWork implements Work{
    public void doWork() {
        System.out.println("老是批改作业");
    }   
}
class StudentWorkFactory implements IWorkFactory{
    public Work getWork() {     
        return new StudentWork();
    }
}

class TeacherWorkFactory implements IWorkFactory{
    public Work getWork() {
        return new TeacherWork();
    }
}
public class TestFactoryMethod {
    public static void main(String[] args){
        IWorkFactory i = new StudentWorkFactory();
        i.getWork().doWork();
        
        IWorkFactory i1 = new TeacherWorkFactory();
        i1.getWork().doWork();
    }
}

上面代码中,子类 StudentWorkFactory 决定实例化 StudentWork 类,子类 TeacherWorkFactory 决定实例化* TeacherWork 类。这样,就达到了接口只是定义了创建一个什么样的类,具体的执行内容根据需求在子类中去实现,并且由子类决定何时去实例化它。

五、内部类

类的成员之五:内部类。内部类就是在类的内部,去定义新的类。

class Person{
    class Bird{
        // 成员内部类
    }

    public void method1(){
        class A{
            // 局部内部类
        }
    }
}

在外部类Person的内部,直接定义的类Bird,Bird 叫做 成员内部类;在类内方法method1内部,定义类A,类A叫做 局部内部类。那么,任何创建成员内部类的对象?

创建成员内部类对象

看下面例子:

public class TestInnerClass {
    public static void main(String[] args){
        // 创建静态的成员内部类
        Person.Dog d = new Person.Dog();
        // 创建非静态的成员内部类
        Person p = new Person();
        Person.Bird b = p.new Bird();
    }
}

class Person{
    class Bird{
    }
    
    static class Dog{   
    }
}

区分调用内部类、外部类的变量

看下面例子:

public class TestInnerClass {
    public static void main(String[] args){
        Person p = new Person();
        Person.Bird b = p.new Bird();
        b.setName("setName");
    }
}

class Person{
    String name = "Person";
    
    class Bird{
        String name = "Bird";
        
        void setName(String name){
            System.out.println(name);               // 这个 name 是 setName 的参数
            System.out.println(this.name);          // 这个 name 是 Bird 的属性
            System.out.println(Person.this.name);   // 这个 name 是 Person 的属性
        }
    }
}

局部内部类的使用

常常使用一个方法,使其 返回值 为某个类或接口的 对象。而这个类或接口在方法内部创建:

interface Test{
    void show();
}
class OuterClass{
    public Test getTest(){
        class MyClass implements Test{
            public void show(){
                System.out.println("show print!");
            }
        }
        
        return new MyClass();
    }
}
public class TestInnerClass {
    public static void main(String[] args) {
        OuterClass c = new OuterClass();    
        Test t = c.getTest();   
        t.show();
    }
}

首先,MyClass 类在方法 getTest() 内部,创建这个类以后,使用这个类创建类的对象,我们可以获得这个对象的引用。达到的效果就是,这个类只在方法的内部使用。 当然,我们还可以使用 匿名的方式 创建内部类:

class OuterClass{
    public Test getTest(){
        return new Test(){
            public void show(){
                System.out.println("这是匿名类");
            }
        };
    }
}

匿名的方式,和上面有名的方式的效果是一样的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容