day16_抽象类丶接口

抽象类

由来:父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。

定义

  • 抽象方法 : abstract来修饰一个方法,该方法叫做抽象方法。 该方法没有方法体,抽象方法:只有方法的声明,没有方法的实现。以分号结束:比如:public abstract void talk();
  • 抽象类:abstract关键字来修饰一个类,这个类叫做抽象类

abstract使用

  • 不能用abstract修饰变量、代码块、构造器;
  • 不能用abstract修饰私有方法、静态方法、final的方法、final的类。

抽象方法

  • 使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

定义格式:

抽象类

  • 如果一个类包含抽象方法,那么该类必须是抽象类。

定义格式: 

 下面我们自定义抽象类Animal

public abstract class Animal {
 
    // 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
    public abstract void eat();
    
}

如何使用抽象类和抽象方法:

  1. 不能直接创建new抽象类对象。
  2. 必须用一个子类来继承抽象父类。
  3. 子类必须覆盖重写抽象父类当中所有的抽象方法。覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。
  4. 创建子类对象进行使用。

使用我们刚才定义的抽象类Animal

package demo11;
//必须用一个子类来继承抽象父类。
public class Cat extends Animal {
    //类必须覆盖重写抽象父类当中所有的抽象方法。
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
 
}

定义测试类 

package demo11;
 
public class DemoMain {
 
    public static void main(String[] args) {
        // Animal animal = new Animal();  错误写法!不能直接创建抽象类对象
 
        //创建子类对象进行使用。
        Cat cat = new Cat();
        cat.eat();//猫吃鱼
    }
 
}

此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。 

注意事项

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

  • 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

  • 理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。 

  • 理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

  • 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

多态的应用:模板方法设计模式(TemplateMethod)  

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

解决的问题

  • 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以 把不确定的部分暴露出去,让子类去实现。
  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用, 这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽 象出来,供不同子类实现。这就是一种模板模式。

举例

设计模板

//定义父类,设计模板
public abstract class Template {
    public final void getTime() {
        long start = System.currentTimeMillis();
        //将来调用子类的方法
        code();
        long end = System.currentTimeMillis();
        System.out.println("执行时间是:" + (end - start));
    }
 
    public abstract void code();
}

具体要做的事情

class SubTemplate extends Template {
    public void code() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(i);
        }
    }
}

模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的 影子,比如常见的有:

  • 数据库访问的封装
  • Junit单元测试
  • JavaWeb的Servlet中关于doGet/doPost方法调用
  • Hibernate中模板程序
  • Spring中JDBCTemlate、HibernateTemplate等

接口

概述:一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能" 的关系。

  • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/...则 必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能" 的关系。
  • 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都 要遵守。
  • 接口(interface)是抽象方法常量值定义的集合

接口的特点:

  • interface来定义。
  • 接口中的所有成员变量都默认是由public static final修饰的。
  • 接口中的所有抽象方法都默认是由public abstract修饰的。
  • 接口中没有构造器。
  • 接口采用多继承机制。

定义格式

public interface 接口名称 {
    // 接口内容
}

在任何版本的Java中,接口都能定义抽象方法

格式:

  • public abstract 返回值类型 方法名称(参数列表);

注意事项:

  • 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
  • 这两个关键字修饰符,可以选择性地省略。
  • 方法的三要素,可以随意定义。

举例:

package demo01;
 
 
public interface MyInterfaceAbstract {
 
    // 这是一个抽象方法
    public abstract void methodAbs1();
 
    // 这也是抽象方法
    abstract void methodAbs2();
 
    // 这也是抽象方法
    public void methodAbs3();
 
    // 这也是抽象方法
    void methodAbs4();
 
}

在任何版本的Java中,接口都能定义常量

  •  接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。从效果上看,这其实就是接口的【常量】。

格式:

  • public static final 数据类型 常量名称 = 数据值;

备注:

  • 一旦使用final关键字进行修饰,说明不可改变。

注意事项:

  • 接口当中的常量,可以省略public static final,注意:不写也照样是这样。
  • 接口当中的常量,必须进行赋值;不能不赋值。
  • 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)

 举例:

public interface MyInterfaceConst {
 
    // 这其实就是一个常量,一旦赋值,不可以修改
    public static final int NUM_OF_MY_CLASS = 12;
 
}

接口使用步骤:

1:接口不能直接使用,必须有一个“实现类”来“实现”该接口。

格式:

public class 实现类名称 implements 接口名称 {

  // ...

}

2:接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。实现:去掉abstract关键字,加上方法体大括号。

3:创建实现类的对象,进行使用。

注意事项:

  • 如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。

代码举例

定义接口

 
package demo01;
 
public interface LiveAble {
    //定义常量
    public static final int NUM_OF_MY_CLASS = 12;
    
    // 定义抽象方法
    public abstract void eat();
 
    public abstract void sleep();
}

定义实现类

package demo01;
//定义实现类,实现接口
public class Animal implements LiveAble {
    @Override
    public void eat() {
        System.out.println("吃东西");
    }
 
    @Override
    public void sleep() {
        System.out.println("晚上睡");
    }
}

定义测试类

package demo01;
 
public class InterfaceDemo {
    public static void main(String[] args) {
        // 创建实现类对象
        Animal a = new Animal();
        // 访问接口当中的常量
        System.out.println(LiveAble.NUM_OF_MY_CLASS);//12
 
        // 调用实现后的方法
        a.eat();// 吃东西
        a.sleep();// 晚上睡
    }
}

从Java 8开始,接口里允许定义默认方法

格式:

public default 返回值类型 方法名称(参数列表) {
  方法体
}

备注:接口当中的默认方法,可以解决接口升级的问题。

默认方法的使用

  • 可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。

举例:

定义接口

package demo01;
 
public interface MyInterfaceDefault {
    // 添加默认方法
    public default void methodDefault() {
        System.out.println("这是新添加的默认方法");
    }
 
}

定义实现类,继承默认方法,代码如下:

package demo01;
 
public class MyInterfaceDefaultA implements MyInterfaceDefault {
  
}

定义实现类,重写默认方法,代码如下:

package demo01;
public class MyInterfaceDefaultB implements MyInterfaceDefault {
 
    @Override
    public void methodDefault() {
        System.out.println("实现类B覆盖重写了接口的默认方法");
    }
}

定义测试类

package demo01;
 
 
/*
1. 接口的默认方法,可以通过接口实现类对象,直接调用。
2. 接口的默认方法,也可以被接口实现类进行覆盖重写。
 */
public class Demo02Interface {
 
    public static void main(String[] args) {
        // 创建了实现类对象
        MyInterfaceDefaultA a = new MyInterfaceDefaultA();
 
        // 调用默认方法,如果实现类当中没有,会向上找接口
        a.methodDefault(); // 这是新添加的默认方法
        
        // 创建了实现类对象
        MyInterfaceDefaultB b = new MyInterfaceDefaultB();
        b.methodDefault(); // 实现类B覆盖重写了接口的默认方法
    }
 
}

从Java 8开始,接口当中允许定义静态方法

格式:
public static 返回值类型 方法名称(参数列表) {
    方法体
}

静态方法的使用

  • 静态与.class 文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用

举例

定义接口

public interface MyInterfaceStatic {
 
    public static void methodStatic() {
        System.out.println("这是接口的静态方法!");
    }
 
}

定义测试类

package demo01;
 
/*
注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。
正确用法:通过接口名称,直接调用其中的静态方法。
格式:
接口名称.静态方法名(参数);
 */
public class Demo03Interface {
 
    public static void main(String[] args) {
    ;
        // 直接通过接口名称调用静态方法
        MyInterfaceStatic.methodStatic();//这是接口的静态方法!
    }
 
}

从Java 9开始,接口当中允许定义私有方法

1. 普通私有方法,解决多个默认方法之间重复代码问题
格式:
private 返回值类型 方法名称(参数列表) {
  方法体
}

2. 静态私有方法,解决多个静态方法之间重复代码问题
格式:
private static 返回值类型 方法名称(参数列表) {
  方法体
}

如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。

私有方法的使用

  • 私有方法:只有默认方法可以调用。
  • 私有静态方法:默认方法和静态方法可以调用。

举例

public interface Demo {
    default void func() {
 
        func2();
        func3();
    }
 
    static void func1() {
        func3();
    }
 
    //只有默认方法可以调用。
    private void func2() {
        System.out.println("跑起来~~~");
    }
 
    //默认方法和静态方法可以调用。
    private static void func3() {
        System.out.println("跑起来~~~");
    }
 
 
}

使用接口的时候,需要注意:

1:接口是没有静态代码块或者构造方法的。

2:一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。

格式:

public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {

  // 覆盖重写所有抽象方法

}

3:如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。

4:如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。

5:如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。

6:一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法。

接口的多实现

  • 类与类之间是单继承的。直接父类只有一个。
  • 类与接口之间是多实现的。一个类可以实现多个接口。
  • 接口与接口之间是多继承的。

注意事项:

  • 多个父接口当中的抽象方法如果重复,没关系。
  • 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】。

接口的应用:代理模式(Proxy)

代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其 他对象提供一种代理以控制对这个对象的访问。

应用场景:

  • 安全代理:屏蔽对真实角色的直接访问。
  • 远程代理:通过代理类处理远程方法调用(RMI)
  • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象

比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有 100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理 模式,当需要查看图片时,用proxy来进行大图片的打开。

分类

  • 静态代理(静态定义代理类)
  • 动态代理(动态生成代理类)
  • JDK自带的动态代理,需要反射等知识

举例

定义接口

interface Network {
    public void browse();
}

定义代理类

class ProxyServer implements Network {
    private Network network;
 
    public ProxyServer(Network network) {
        this.network = network;
    }
 
    public void check() {
        System.out.println("检查网络连接等操作");
    }
 
    public void browse() {
        check();
        network.browse();
    }
}

 定义测试类

public class Test {
    public static void main(String[] args) {
        Network net = new ProxyServer(new RealServer());
                net.browse();
    }
}

接口和抽象类之间的对比

                     

在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类, 要么实现接口。

发布了20 篇原创文章 · 获赞 7 · 访问量 6614

猜你喜欢

转载自blog.csdn.net/weixin_44462792/article/details/105052022
今日推荐