抽象类
由来:父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。
定义
- 抽象方法 : 用abstract来修饰一个方法,该方法叫做抽象方法。 该方法没有方法体,抽象方法:只有方法的声明,没有方法的实现。以分号结束:比如:public abstract void talk();
- 抽象类:用abstract关键字来修饰一个类,这个类叫做抽象类。
abstract使用
- 不能用abstract修饰变量、代码块、构造器;
- 不能用abstract修饰私有方法、静态方法、final的方法、final的类。
抽象方法
- 使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式:
抽象类
- 如果一个类包含抽象方法,那么该类必须是抽象类。
定义格式:
下面我们自定义抽象类Animal
public abstract class Animal {
// 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
public abstract void eat();
}
如何使用抽象类和抽象方法:
- 不能直接创建new抽象类对象。
- 必须用一个子类来继承抽象父类。
- 子类必须覆盖重写抽象父类当中所有的抽象方法。覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。
- 创建子类对象进行使用。
使用我们刚才定义的抽象类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();
}
}
接口和抽象类之间的对比
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类, 要么实现接口。