Java面向对象--抽象类(abstract)和接口(interface)
使用abstract关键字声明的类为抽象类,只声明而未实现的方法称为抽象方法;Java接口是一系列方法的声明,是一些方法特征的集合。本篇博客介绍Java面向对象中的抽象类、抽象方法的使用以及接口的具体应用。
目录:
☍ 抽象类和抽象方法(abstract关键字)
抽象类和抽象方法的概述
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
✯ 用abstract关键字修饰的类叫做抽象类
abstract class AbstractTest{
//...
}
✯ 用abstract修饰的方法叫做抽象方法
☄ 抽象方法:只有方法的声明,没有方法的实现,以分号结束。
public abstract void shout();
抽象类和抽象方法使用注意项
➛ 含有抽象方法的类必须被声明为抽象类,但抽象类中不一定要有抽象方法。
➛ 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须实现父类的抽象方法,并提供方法体。若没有实现全部的抽象方法,则该子类仍应该定为抽象类。
➛ 不能用abstract修饰变量、代码块、构造器。
➛ 不能用abstract修饰私有方法、静态方法、final的方法、final的类。
抽象类举例:
/*
* 1、abstract:抽象的
* 2、abstract可以用来修饰的结构:类,方法
*
* 3、abstract修饰类:抽象类
* 3-1、抽象类不能实例化
* 3-2、抽象类中一般定义构造器来初始化属性,便于类实例化时调用(涉及子类对象的实例化过程)
* 3-3、开发中,都会提供抽象的子类,让子类对象实例化,完成相关操作
* 4、abstract修饰方法:抽象方法
* 4-1、只有声明,没有方法体 abstract void methond();
* 4-2、包含抽象方法的类一定是抽象类,但是抽象类中不一定有抽象方法
* 4-3、继承抽象类就要实现抽象方法,如果没有实现所有的抽象方法,该类也应该定义为抽象类
*/
public class AbstractTest {
public static void main(String[] args) {
// Animal animal = new Animal(); 一旦定义为抽象类,就无法创建实例化对象了
Dog dog = new Dog();
dog.info();
dog.act();
dog.eat();
System.out.println("*************");
Cat cat = new Cat();
cat.info();
cat.act();
cat.eat();
System.out.println("*************");
Fish fish = new Fish();
fish.info();
fish.act();
fish.eat();
System.out.println("*************");
//向上转型,执行的是重写后的方法
Animal animal = new Cat();
animal.info();
animal.act();
animal.eat();
System.out.println("*************");
//向下转型,执行的是重写后的方法
Animal animal2 = new Fish();
Fish fish2 = (Fish)animal2;
fish2.info();
fish2.act();
fish2.eat();
}
}
abstract class Animal {
String name;
int legs;
public Animal() {
}
public Animal(String name, int legs) {
this.name = name;
this.legs = legs;
}
//抽象方法
public abstract void eat();
public abstract void act();
public void info() {
System.out.println("Animal:" + name + ", legs:" + legs);
}
}
//宠物类继承动物类,重写了act方法,仍为抽象类
abstract class NormalPet extends Animal{
public NormalPet() {
}
public NormalPet(String name, int legs) {
super(name,legs);
}
//继承抽象类就要重写抽象方法,如果没有重写所有的抽象方法,改类也应该定义为抽象类
public void act() {
System.out.println("常见宠物,四肢走路");
}
}
//“🐕”继承了宠物类,重写了eat方法
class Dog extends NormalPet{
public Dog() {
super("狗" , 4);
}
@Override
public void eat() {
System.out.println("喜欢吃骨头");
}
}
//“🐱”继承了宠物了,重写了eat方法
class Cat extends NormalPet{
public Cat() {
super("猫", 4);
}
@Override
public void eat() {
System.out.println("喜欢吃小鱼干");
}
}
//“🐟”继承了动物了,重写了atc和eat方法
class Fish extends Animal{
public Fish() {
super("鱼",0);
}
@Override
public void eat() {
System.out.println("大鱼吃小鱼,小于吃虾米");
}
@Override
public void act() {
System.out.println("🐟在水里游");
}
}
✦ 抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类
抽象类的匿名子类
❣ 抽象类的匿名子类:在创建对象的同时实现抽象类的方法(默认向上转型)
//抽象类的匿名子类(自动向上转型),创建时重写抽象方法
public static void main(String[] args){
//抽象类的匿名子类(自动向上转型),创建时重写抽象方法
//并不是创建了Animal对象,是一个匿名对象,通过Animal引用变量指向该匿名对象(向上转型了)
Animal rabbit = new Animal("小白",4) {
@Override
public void eat() {
System.out.println("吃胡萝卜");
}
@Override
public void act() {
System.out.println("能跑能跳");
}
};
}
模板方法(TemplateMethod)设计模式
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
☃ 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
☃ 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
例子:
//抽象类的应用:模板方法的设计模式
public class TemplateMethodTest {
public static void main(String[] args) {
BankTemplateMethod btm = new DrawMoney();
btm.process();
BankTemplateMethod btm2 = new ManageMoney();
btm2.process();
}
}
abstract class BankTemplateMethod {
// 具体方法
public void takeNumber() {
System.out.println("取号排队");
}
public abstract void transact(); // 办理具体的业务 //钩子方法
public void evaluate() {
System.out.println("反馈评分");
}
// 模板方法,把基本操作组合到一起,子类一般不能重写
public final void process() {
this.takeNumber();
// 像个钩子,具体执行时,挂哪个子类,就执行哪个子类的实现代码
this.transact();
this.evaluate();
}
}
class DrawMoney extends BankTemplateMethod {
public void transact() {
System.out.println("我要取款!!!");
}
}
class ManageMoney extends BankTemplateMethod {
public void transact() {
System.out.println("我要理财!我这里有100万元!!");
}
}
应用:
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
☃ 据库访问的封装。
☃ Junit单元测试。
☃ JavaWeb的Servlet中关于doGet/doPost方法调用。
☃ Hibernate中模板程序。
☃ Spring中JDBCTemlate、HibernateTemplate。
☍ 接口(interface)
接口的概述
↪ 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
↪ 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有继承的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、充电器、手机、移动硬盘等都支持USB连接,但他们之间没有子父类关系。
↪ 接口就是规范定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"
的关系。
↪ 接口的本质是契约,标准,规范,制定好后使用时就要遵守。
✦ 接口的本质是契约,标准,规范,制定好后使用时就要遵守。
接口的特点
↪ 用interface来定义,接口采用多继承机制。
↪ JDK7及以前,只能定义全局常量和抽象方法;JDK8之后,除了定义全局常量和抽象方法之外,还可以定义静态方法,默认方法(略)
↪ 接口中的所有成员变量都默认是由public static final
修饰的。书写时可以省略public static final部分或全部关键字,不可添加其他关键字
◌ 不管省不省略关键字,实际的常量仍为public static final
修饰,权限也只能时public,类型依然为final常量
↪ 接口中的所有抽象方法都默认是由public abstract
修饰的。书写时可以省略public abstract
部分或 全部关键字,不可添加其他关键字
◌ 不管省不省略关键字,实际的常量仍为public abstract修饰,权限也只能时public,类型依然为abstract抽象方法
↪ 接口中没有构造器,意味着接口不可以实例化。
↪ 接口采用多继承机制,一个类可以实现多个接口,用‘,’隔开,弥补了Java单继承的局限性。
↪ 、Java开发中,接口通过让类去实现(implement)的方式使用,如果类覆盖了接口中的所有抽象方法,则此实体类就可以实例化;如果实现类没有覆盖接口中所有的抽象方法,则此实现类要定义成抽象方法 。
public class InterfaceTest {
public static void main(String[] args) {
Plane plane = new Plane();
plane.fly();
plane.stop();
}
}
//飞接口
interface Flyable{
//全局常量
public static final int MAX_SPEED = 7900; //第一宇宙速度
int MIN_SPEED = 1; //最低速度
public int NUMBER_1 = 12;
public static double NUMBER_2 = 23.5;
public final String FLY = "Fly";
//protected char C = 'Y'; //错误,只能为public
//抽象方法
public abstract void fly(); //起飞
void stop(); //停止
public void addSpeed(); //加速
abstract void reduceSpeed(); //减速
//protected abstract void other(); 报错,只能是public修饰
//public Flyable() {} 编译报错,接口不能定义构造器
}
//接口通过implement
class Plane implements Flyable{
//要么实现所有的抽象方法,要么将类定义为抽象类,等待子类对未实现的抽象方法进行重写
@Override
public void fly() {
System.out.println("加速起飞");
}
@Override
public void stop() {
System.out.println("停止降落");
}
@Override
public void addSpeed() {
System.out.println("加速中...");
}
@Override
public void reduceSpeed() {
System.out.println("减速中...");
}
public void getInfo() {
System.out.println("这是一架飞机");
}
}
//抽象类
abstract class Kite implements Flyable{
@Override
public void fly() {
System.out.println("放飞风筝");
}
@Override
public void stop() {
System.out.println("收回风筝");
}
}
interface Attackable{
void attack();
}
//实现类可以实现多个接口,用、‘,’隔开,也可以与继承同时使用
abstract class Bullet extends Object implements Flyable,Attackable{
@Override
public void attack() {
}
@Override
public void fly() {
}
}
接口使用的详细说明
↪ 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,
接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义
(JDK7.0及之前),而没有变量和方法的实现。
↪ 实现类可以使用implements关键字实现接口,实现接口的同时也可以继承,语法格式为先写extends,后写implements
class SubClass extends SuperClass implemeents InterfaceA,InterfaceB{}
↪ 一个类可以实现多个接口,接口也可以继承其它接口,且可以多继承。
interface A{
void method1();
}
interface B{
void method2();
}
interface C extends A,B{
//接口C包含接口A和接口B的所有抽象方法和常量,如果要实例化接口C,要把A和B中的抽象方法都实现了
}
↪ 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
↪ 接口的主要用途就是被实现类实现。(面向接口编程)
↪ 若实现的多个接口中有同名的抽象方法,实现类实现抽象方法时会自动将所有同名的方法都覆盖掉
↪ 与继承关系类似,接口与实现类之间存在多态性。
public class InterfaceTest {
public static void main(String[] args) {
Plane plane = new Plane();
//接口的多态性体现(接口与实现类的多态使用类似继承中父类与子了的使用)
//向上转型:自动,接口实现类对象赋给实现的接口的引用(与继承中的向上转型类似)
//只能调用接口引用中定义了的常量和方法
Flyable airPlane =new Plane();
airPlane.fly();
airPlane.addSpeed();
System.out.println("**************");
//airPlane.getInfo(); Flyable接口中没有getInfo抽象方法
//向下转型,实现类对象的接口引用赋给实现类引用,使用强制转换(与继承中的向下转型类似)
//可以使用接口中的常量和方法,也可以使用自己特有的常量和方法
Plane plane2 = (Plane) airPlane;
plane2.fly();
plane2.reduceSpeed();
plane2.getInfo();
System.out.println("***************");
//典型多态的应用,方法中形参声明为接口类变量,实际传入实现类对象,调用实现(重写)后的接口方法
InterfaceTest test = new InterfaceTest();
test.m1(plane);
test.m2(plane);
test.m3(plane);
}
public void m1(Flyable f) {
f.fly();
f.addSpeed();
}
public void m2(Locat l) {
l.locat();
}
public void m3(Destination d) {
d.destination();
}
}
//飞接口
interface Flyable{
//抽象方法
public abstract void fly(); //起飞
void stop(); //停止
public void addSpeed(); //加速
abstract void reduceSpeed(); //减速
}
interface Locat{
void locat();
}
interface Destination{
void destination();
}
//接口通过implement
class Plane implements Flyable,Locat,Destination{
@Override
public void fly() {
System.out.println("加速起飞");
}
@Override
public void stop() {
System.out.println("停止降落");
}
@Override
public void addSpeed() {
System.out.println("加速中...");
}
@Override
public void reduceSpeed() {
System.out.println("减速中...");
}
@Override
public void locat() {
System.out.println("中国制造");
}
@Override
public void destination() {
System.out.println("目的地是北京");
}
public void getInfo() {
System.out.println("这是一架飞机");
}
}
↪ 接口的匿名实现类使用。
//创建接口的匿名实现类的非匿名对象
public static void main(String[] args)
//创建接口的匿名实现类的非匿名对象:声明接口引用时创建实现类的对象并覆盖接口中的抽象方法
//注意,new Attackable(){}不是创建了接口对象,而是实例化了匿名的实现类对象赋给接口引用,涉及到了向上转型
Attackable attackable = new Attackable() {
@Override
public void attack() {
System.out.println("具有攻击性");
}
};
}
接口的应用:代理模式(Proxy)
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其
他对象提供一种代理以控制对这个对象的访问。
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就类似我们生活中常见的中介,明星的经纪人,代购等。
代理模式的优点:
☃ 功能提供类可以更加专注于主要功能的实现,不用关心其他非本职责的事务,通过后期的代理完成一件事务,代码清晰。在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
☃ 可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能,并且只要实现了接口,接口不变,代理类就可以不做任何修改继续使用,符合“开闭原则”,对拓展是开发的,对修改是封闭的。
✦这里只简单介绍代理模式的概念,通过一个静态代理模式的例子体现
//静态代理方式
public class ProxyDemo {
public static void main(String[] args) {
Network net = new ProxyServer(new
RealServer());
net.browse();
}
}
interface Network {
public void browse();
}
// 被代理类
class RealServer implements Network {
@Override
public void browse() {
System.out.println("真实服务器上网浏览信息");
}
}
//代理类
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();
}
}
代理模式分类(此篇博客不详细介绍):
☃ 静态代理(静态定义代理类)
◌ 在使用静态代理时,被代理对象与代理对象需要一起实现相同的接口或者是继承相同父类,因此要定义一个接口或抽象类.
☃ 动态代理(动态生成代理类)
◌ JDK自带的动态代理,需要反射等知识
◌ 动态代理的主要特点就是能够在程序运行时JVM才为被代理对象生成代理对象。
◌ 在 Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler
接口和 java.lang.reflect.Proxy
类的支持。
应用场景:
☃ 安全代理:屏蔽对真实角色的直接访问。
☃ 远程代理:通过代理类处理远程方法调用(RMI)
☃ 先加载轻量级的代理对象,真正需要再加载真实对象
Java8中关于接口的改进
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
静态方法:使用 static 关键字修饰。只能通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中
找到像Collection/Collections
或者Path/Paths
这样成对的接口和类。
默认方法:默认方法使用 default关键字修饰。只能通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java8 API中对Collection
、List
、Comparator
等接口提供了丰富的默认
方法。
☃ 如果实现类重写了接口中的默认方法,调用的是重写后的方法。
interface AA{
void info();
//默认方法,通过实现类对象来调用
public default void methond1() {
System.out.println("method1");
}
default void method2() {
System.out.println("method2");
}
//静态方法:通过接口直接调用静态方法
public static void method3() {
System.out.println("method3");
}
static void method4() {
System.out.println("method4");
}
}
//必须是public权限,默认也为public权限,不能是其他权限
public class Test{
public static void main(String[] args){
AA test = new AA(){ //匿名实现类的非匿名对象
@Override
public void info(){
System.out.println("信息");
}
}
AA.methond3(); //静态方法:通过接口直接调用静态方法
//AA.method1(); 无法通过接口直接调用默认方法
test.methond1(); //通过实现类对象调用接口默认方法
//test.method3(); 无法通过实现类对象调用静态方法
}
}
☃ 若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突。
☄ 解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。
☃ 若一个接口中定义了一个默认方法,而实现类的父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。
//方法的调用方式
class A extends B implements C,D{
public void getMethod(){
method1(); //调用自己重写的方法
super.method2(); //调用父类中声明的方法
C.super.method3(); //调用接口中的默认方法
D.method4(); //调用接口中的静态方法
}
//实现接口的抽象类方法
}
☍ 接口和抽象类之间的对比
No. | 区别 | 抽象类 | 接口 |
---|---|---|---|
1 | 定义 | 包含抽象方法的类 | 主要是抽象方法和全局常量的集合 |
2 | 组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法、(jdk8:默认方法、静态方法) |
3 | 使用 | 子类继承抽象类(extends) | 实现类实现接口(implements) |
4 | 关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但可以继承多个接口 |
5 | 常见设计模式 | 模板方法设计模式 | 简单工厂、工厂方法、代理模式 |
6 | 对象 | 都通过对象的多态性产生实例化对象 | |
7 | 局限 | 抽象类有单继承的局限 | 接口没有单继承的局限 |
8 | 实际 | 作为一个模板 | 作为一个标准或者表示一种共性的能力 |
9 | 选择 | 如果抽象类和接口都可以使用的话,优先使用接口,可以避免单继承的局限 |
本博客与CSDN博客༺ཌ༈君☠纤༈ད༻同步发布