Java学习总结——抽象类与接口

拾叁——抽象类与接口

一、抽象类

在 Java 中可以创建一种类专门用来做父类,这种类称为 “ 抽象类 ”。抽象类实际上也是一个类,只是与之前的普通类相比,内部新增了抽象方法。

1.抽象类的基本概念

抽象方法是只声明而未实现的方法,所有的抽象方法必须使用 abstract 关键字声明,包含抽象方法的类也必须使用 abstract class 声明。

抽象类定义规则如下:

(1)抽象类和抽象方法都必须用 abstract 关键字来修饰;

(2)抽象类不能直接实例化,也就是不能直接用 new 关键字去产生对象;

(3)抽象类定义时抽象方法只需声明,而不需实现;

(4)含有抽象方法的类必须被声明为抽象类,抽象类的子类必须实现所有的抽象方法后才能被实例化,否则这个子类还是个抽象类。

抽象类定义格式如下:

        abstract class 类名称    //定义抽象类
        {
            声明数据成员;
            访问权限 返回值的数据类型 方法名称(参数……)
            {
                //定义一般方法
            }
            //定义抽象方法,在抽象方法里没有定义方法体
            abstract 返回值的数据类型 方法名称(参数……);    
        }

例如:

        abstract class Book    //定义一个抽象类
        {
            private String title = "Java";    //属性
            public void print()
            {
                //普通方法,有“{ }”表示有方法体
                System.out.println(title);
            }
            //没有定义方法体,是一个抽象方法
            public abstract void fun();    
        }

由此可知,抽象类的定义就是比普通类多了一些抽象方法的定义而已。虽然定义了抽象类,但是抽象类却不能直接使用。

        Book book = new Book();    //错误:Book是抽象的,无法实例化

如果一个类可以实例化对象,那么这个对象可以调用类中的属性或者是方法,但是抽象类中存在抽象方法,而抽象方法没有方法体,没有方法体的方法无法使用。

所以,对于抽象类的使用原则如下:

(1)抽象类必须有子类,子类使用 extends 继承抽象类,一个子类只能够继承一个抽象类;

(2)子类( 如果不是抽象类 )则必须实现抽象类之中的全部抽象方法;

(3)如果要想实例化抽象类的对象,则可以使用子类进行对象的向上转型来完成。

提示:在 Java 中,当子类继承父类时,子类可由此得到父类的方法。我们可在子类中,重新改写继承父类的同名方法,我们称这个过程为覆盖重写,简称覆写( override )。

从抽象类的设计理念可知,抽象类生来就是被继承的。在其内的抽象方法,通常是没有方法体的,抽象方法的本意就是期望子类重新定义这个方法,赋予新的内涵。因此,在其子类中的非抽象( 具体 )定义,我们用 “ 实现 ”,而非 “ 覆盖 ”,来描述这个过程。用 “ 实现 ”,相当于在子类中,将来自父类的抽象方法 “ 给予 ” 生命。

举例抽象类的用法:

//抽象类的用法
abstract class Person	//定义一个抽象类 Person
{
	String name;
	int age;
	String occupation;
	public abstract String talk();	//声明一个抽象方法talk()
}

class Student extends Person	//Student类继承自Person类
{
	public Student(String name,int age,String occupation)
	{
		this.name = name;
		this.age = age;
		this.occupation = occupation;
	}
	
	public String talk()	//实现抽象方法talk()
	{
		return "name:" + this.name + "age:" + this.age + "occupation" + this.occupation;
	}
}

class Worker extends Person	//Worker类继承自Person类
{
	public Worker(String name,int age,String occupation)
	{
		this.name = name;
		this.age = age;
		this.occupation = occupation;
	}
	
	public String talk()	//实现抽象方法talk()
	{
		return "name:" + this.name + "age:" + this.age + "occupation" + this.occupation;
	}
}

class AbstractClassDemo
{
	public static void main(String[] args)
	{
		Student s = new Student("张三",20,"学生");	//创建Student类对象s
		Worker w = new Worker("李四",25,"工人");	//创建Worker类对象w
		System.out.println(s.talk());	//调用被实现过的方法
		System.out.println(w.talk());
	}
}

举例抽象类中构造方法的定义使用:

//抽象类中构造方法的定义使用
abstract class Person	//定义一个抽象类 Person
{
	String name;
	int age;
	String occupation;

	public Person(String name,int age,String occupation)	//定义构造函数
	{
		this.name = name;
		this.age = age;
		this.occupation = occupation;
	}	
	public abstract String talk();	//声明一个抽象方法
}

class Worker extends Person	//声明抽象类的子类
{
	public Worker(String name,int age,String occupation)
	{
		//在这里必须明确调用抽象类中的构造方法
		super(name,age,occupation);
	}
	
	public String talk()	//实现抽象方法talk()
	{
		return "name:" + this.name + "age:" + this.age + "occupation" + this.occupation;
	}
}

class AbstractConstructor
{
	public static void main(String[] args)
	{
		Worker w = new Worker("李四",25,"工人");	//创建对象w
		System.out.println(w.talk());	//调用被实现过的方法
	}
}

举例验证 static 定义的内部抽象类:

//验证static定义的内部抽象类
abstract class Book
{
	public abstract void print();	//抽象方法
	static abstract class CD
	{
		//静态内部抽象类
		public abstract void get();	//抽象方法
	}
}

class JavaCD extends Book.CD //继承抽象类
{
	public void get()
	{
		System.out.println("java");
	}
}

public class StaticInnerAbstractClass
{
	public static void main(String[] args)
	{
		Book.CD cd = new JavaCD();	//实例化对象
		cd.get();
	}
}

抽象类的特征:

(1)抽象类中可以有构造方法

与一般类相同,抽象类也可以拥有构造方法,但是这些构造方法必须在子类中被调用,并且子类实例化对象的时候依然满足类继承的关系,先默认调用父类的构造方法,而后再调用子类的构造方法,毕竟抽象类之中还是存在属性的,但抽象类的构造方法无法被外部类实例化对象调用。

抽象类也可以像普通类一样,有构造方法、一般方法和属性,更重要的是还可以有一些抽象方法,需要子类去实现,而且在抽象类中声明构造方法后,在子类中必须明确调用。

(2)抽象类不能够使用 final 定义

因为使用 final 定义的类不能有子类,而抽象类使用的时候必须有子类,这是一个矛盾的问题,所以抽象类上不能出现 final 定义。

(3)Static 声明,抽象类内外不同

在外部抽象类上无法使用 static 声明,但是内部抽象类却可以使用 static 定义,使用 static 定义的内部抽象类就表示一个外部类。

(4)抽象类之中可以没有抽象方法,但即便没有抽象方法的抽象类也不能够直接在外部通过关键字 new 实例化

2.抽象类应用——模板设计模式

在使用抽象类时,可以将部分逻辑以具体方法和具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式。

例如:

现在有三类事物——机器人、美女、帅哥,这三类事物可以完成的功能如下:

机器人:吃饭、工作;

美女:吃饭、跑步、睡觉;

帅哥:吃饭、工作、跑步、睡觉。

这三类事物共同的特征就是拥有四个固定的操作行为。代码实现如下:

abstract class Action	//表示操作行为
{
	public static final int EAT = 1;
	public static final int WORK = 2;
	public static final int SLEEP = 5;
	public static final int RUN = 10;
	
	//声明抽象方法
	public abstract void eat();
	public abstract void sleep();
	public abstract void run();
	public abstract void work();
	
	public void command(int ch)	//具体的命令
	{
		switch (ch) {
		case EAT:
			this.eat();
			break;
		case SLEEP:
			this.sleep();
			break;
		case RUN:
			this.run();
			break;
		case WORK:
			this.work();
			break;
		case EAT + WORK:
			this.eat();
			this.work();
			break;
		case EAT + SLEEP + RUN:
			this.eat();
			this.sleep();
			this.run();
			break;
		case EAT + SLEEP + RUN + WORK:
			this.eat();
			this.sleep();
			this.run();
			this.work();
			break;
		}
	}
}

class Robot extends Action	//定义子类Robot行为
{
	public void eat()	//实现抽象方法
	{
		System.out.println("加燃料");
	}
	public void sleep(){}
	public void run(){}
	public void work()
	{
		System.out.println("开始工作");
	}
}

class Women extends Action	//定义子类Women行为
{
	public void eat()	//实现抽象方法
	{
		System.out.println("吃东西");
	}
	public void sleep()
	{
		System.out.println("睡觉");
	}
	public void run()
	{
		System.out.println("跑步");
	}
	public void work(){}
}

class Man extends Action	//定义子类Man行为
{
	public void eat()	//实现抽象方法
	{
		System.out.println("吃东西");
	}
	public void sleep()
	{
		System.out.println("睡觉");
	}
	public void run()
	{
		System.out.println("跑步");
	}
	public void work()
	{
		System.out.println("工作");
	}
}

public class TemplateMethod
{
	public static void main(String[] args)
	{
		Action actA = new Robot();	//机器人行为
		actA.command(Action.EAT);
		actA.command(Action.WORK);
		Action actB = new Women();	//美女的行为
		actB.command(Action.EAT + Action.SLEEP + Action.RUN);
		Action actC = new Man();	//帅哥的行为
		actC.command(Action.EAT + Action.SLEEP + Action.RUN + Action.WORK);
	}
}

此时,如果想要实现指定的操作,只需要将方法按照要求实现即可,相当于父类定义出了一个操作模板。实际用的时候也可以在 Servlet 程序设计上使用。

二、接口

接口( interface )是 Java 所提供的另一种重要技术,它是一种特殊的类,它的结构和抽象类非常相似,可视为是抽象类的一种变体。

1.接口的基本概念

在 Java 8 之前,接口的一个关键特征是,它既不包含方法的实现,也不包含数据。换句话说,接口里的方法默认为 abstract 类,即接口中的方法是 “ 抽象方法 ”。另外,接口的宗旨在于,定义由多个继承类共同遵守的 “ 契约 ”,所以接口中所有成员访问类型都必须为 public,否则不能继承,就失去了 “ 契约 ” 内涵。

在 Java 8 中,接口也可以包括数据成员,但这些数据必须是常量,其值一旦被初始化后,是不允许更改的,这些数据成员通常为全局变量。

Java 8 为避免在接口中添加新方法后要修改所有实现类,允许定义默认方法( default 方法 ),也可称为 Defender 方法,或者虚拟扩展方法( Virtual extension methods )。

Default 方法是指,允许在接口内部,实现一些默认方法( 也就是说,在接口中可以包含方法体,这打破了 Java 8 版本之前,对接口的语法限制 ),从而使得接口在进行扩展的时候,不会破坏与接口相关的实现类代码。

在 Java 中使用 interface 关键字来定义一个接口。

接口定义的语法如下:

        interface 接口名称    //定义抽象类
        {
            //数据成员必须赋初值
            final 数据类型 成员名称 = 常量;

            //抽象方法,注意在抽象方法里没有定义方法主体
            abstract 返回值的数据类型 方法名称(参数……);

            //默认方法,包含方法体
            default 返回值的数据类型 方法名称(参数……);
            {……方法体……}
        }

定义接口例:

        interface A    //定义一个接口
        {
            public static final String INFO = "Hello";    //全局常量
            public abstract void print();    //抽象方法
        }

带默认方法的接口定义例:

        interface B    //定义一个接口
        {
            public static final String INFO = "Hello";    //全局常量
            public abstract void print();    //抽象方法
            default public void otherprint()    //带方法体的默认方法
            {
                System.out.println("default method");    //带默认方法的方法体
            }
        }

虽然有了接口,可是在所定义的接口 A 和接口 B 中,因接口内存在抽象方法,都不能被用户直接使用,必须在其子类中,需要 “ 实现 ” 这些抽象方法,把 “ 抽象的 ” “ 务实 ”,变为实实在在的可用方法,才可以用来实例化对象。

2.接口的使用原则

使用接口必须遵守如下原则:

(1)接口必须有子类,子类依靠 implements 关键字可以同时实现多个接口;

(2)接口的子类( 如果不是抽象类 )则必须实现接口中的全部抽象方法;

(3)接口可以利用对象多态性,利用子类实现对象的实例化。

接口与一般类一样,本身也具有数据成员与方法,但数据成员一定要赋初值,且此值不能再更改,方法也必须是 “ 抽象方法 ” 或 default 方法。也正因为方法除 default 方法外必须是抽象方法,而没有一般的方法,所以接口定义格式中,抽象方法声明的关键字 abstract 是可以省略的。

同理,因接口的数据成员必须赋初值,且此值不能再被更改,所以声明数据成员的关键字 final 也可省略。

简写的接口定义举例:

        interface A    //定义一个接口
        {
            public static final String INFO = "Hello";    //全局常量
            public abstract void print();    //抽象方法
            default public void otherprint()    //带方法体的默认方法
            {
                System.out.println("default method");    //带默认方法的方法体
            }
        }

在 Java 中接口是用于实现多继承的一种机制,也是 Java 设计中最重要的一个环节,每一个由接口实现的类必须在类内部实现接口中的抽象方法,且可自由地使用接口中的常量。

既然接口里只有抽象方法,它只需声明而不用定义处理方法,于是自然可以联想到接口没有办法像一般类一样,再用它来创建对象。利用接口创建新类的过程,称之为接口的实现( implementation )。

以下为接口实现的语法:

        class 类名称 implements 接口A,接口B……    //接口的实现
        {
            ……
        }

举例:

//Java 8 版本以上带default方法接口的实现
interface InterfaceA	//定义一个接口
{
	public static String INFO = "static final";	//全局常量
	public void print();	//抽象方法
	default public void otherprint()	//带方法体的默认方法
	{
		System.out.println("default InterfaceA");
	}
}

class InterfaceAB implements InterfaceA	//子类InterfaceAB实现接口InterfaceA
{
	public void print()	//实现接口中的抽象方法
	{
		System.out.println("abstract InterfaceA");
		System.out.println(INFO);
	}
}

public class Interfacedefault
{
	public static void main(String[] args)
	{
		InterfaceAB ab = new InterfaceAB();	//实例化子类对象
		ab.print();	//调用实现过的抽象方法
		ab.otherprint();	//调用接口中的默认方法
		System.out.println(InterfaceA.INFO);	//输出接口中的常量
	}
}

Java 8 中允许在接口中只定义默认方法,无抽象方法。

举例:

//仅有default方法接口的使用
interface InterfaceA	//定义一个接口
{
	default public void otherprint()	//带方法体的默认方法
	{
		System.out.println("default InterfaceA");
	}
}

class InterfaceAB implements InterfaceA	//子类InterfaceAB实现接口InterfaceA
{
}

public class Interfacedefaultonly
{
	public static void main(String[] args)
	{
		InterfaceAB ab = new InterfaceAB();	//实例化子类对象
		ab.otherprint();	//调用接口中的默认方法
	}
}

上例定义了仅有一个默认方法的接口,无抽象方法,继承接口的子类因不需实现抽象方法,内容为空。

接口与抽象类相比,最大的区别就在于子类上,子类可以同时实现多个接口。

举例:

//子类继承多个接口的应用
interface InterfaceA	//定义一个接口
{
	public static String INFO = "static final";	//全局常量
	public void print();	//抽象方法
}

interface InterfaceB
{
	public abstract void get();
}

class X implements A,B	//一个子类同时实现了两个接口A,B
{
	public void print()	//实现接口中的抽象方法
	{
		System.out.println(INFO);
	}
	public void get()
	{
		System.out.println("Hello");
	}
}

public class InterfaceDemo
{
	public static void main(String[] args)
	{
		X x = new X();	//实例化子类对象
		A a = x;	//为父接口实例化
		B b = x;	//为父接口实例化
		a.print();
		b.get();
	}
}

接口与抽象类相比,最大的区别就在于子类上,子类可以同时实现多个接口。

但在 Java 8 中,如果一个类实现两个或多个接口,即多继承,但是若其中两个接口中都包含一个名字相同的 default 方法,编译器就会报错,因为编译器不知道应该在两个同名的 default 方法中选择哪一个,因此产生了二义性。

因此,一个类实现多个接口时,若接口中有默认方法,不能出现同名默认方法。

多继承中,如果说在一个子类即要实现接口又要继承抽象类,则应该采用先继承后实现的顺序完成。

举例:

interface InterfaceA	//定义一个接口
{
	String INFO = "Hello";	//全局常量
	void print();	//抽象方法
}

interface InterfaceB
{
	public abstract void get();
}

abstract class C
{
	public abstract void fun();	//抽象方法
}

class X extends C implements A,B
{
	//先继承后实现
	public void print()	//实现接口中的抽象方法
	{
		System.out.println(INFO);
	}
	public void get()
	{
		System.out.println("Hello");
	}
	public void fun()
	{
		System.out.println("Hello world");
	}
}

public class ExtendsInterface
{
	public static void main(String[] args)
	{
		X x = new X();	//实例化子类对象
		A a = x;	//为父接口实例化
		B b = x;	//为父接口实例化
		C c = x;	//为抽象类实例化
		a.print();
		b.get();
		c.fun();
	}
}

接口使用过程中,一个抽象类可以继承多个接口,但是反过来讲,一个接口却不能够继承抽象类,但是一个接口却可以使用 extends 关键字继承多个接口。

        interface 子接口名称 extends 父接口1,父接口2,……
        {
            …………
        }

举例:

interface InterfaceA	//定义一个接口
{
	String INFO = "Hello";	//全局常量
	void print();	//抽象方法
}

interface InterfaceB
{
	public abstract void get();
}

abstract class C implements A,B    //抽象类实现了A和B接口
{
	public abstract void fun();	//抽象方法一共有三个
}

interface D extends A,B
{
    //同时继承两个接口
    public void printD();
}

class X extends C implements D
{
	//先继承后实现
	public void print()	//实现接口中的抽象方法
	{
		System.out.println(INFO);
	}
	public void get()
	{
		System.out.println("Hello");
	}
	public void fun()
	{
		System.out.println("抽象类C实现接口A,B");
	}
    public void printD()
    {
        System.out.println("接口D继承两个接口A,B");
    }
}

public class AbstractInterfaces
{
	public static void main(String[] args)
	{
		X x = new X();	//实例化子类对象
		A a = x;	//为父接口实例化
		B b = x;	//为父接口实例化
		C c = x;	//为抽象类实例化
        D d = x;    //父接口实现化
		a.print();
		b.get();
		c.fun();
        d.printD();
	}
}

一个接口可以同时继承多个接口,也可以同时继承多个接口的抽象方法与常量。

3.接口的作用——制定标准

接口是标准,所谓的标准,指的是各方共同遵守的一个守则。只有操作标准统一了,所有的参与者才可以按照统一的规则操作。

例如在电脑以及各个设备的连接上,USB 就是一个操作标准,那么下面通过代码来验证以上的操作。

interface USB	//定义USB接口的标准
{
	public void work();	//拿到USB设备就表示要进行工作
}

class Computer
{
	public void plugin(USB usb)
	{
		usb.work();	//按照固定的方式进行工作
	}
}

class Print implements USB	//打印机实现了USB接口标准
{
	public void work()
	{
		System.out.println("打印机使用USB接口,开始工作");
	}
}

class Flash implements USB
{
	//U盘实现了USB接口标准
	public void work()
	{
		System.out.println("U盘使用USB接口,开始工作");
	}
}

public class Interfacestandards
{
	public static void main(String[] args)
	{
		Computer com = new Computer();
		com.plugin(new Print());	//在电脑上使用打印机
		com.plugin(new Flash());	//在电脑上使用U盘
	}
}

按照固定的 USB 接口标准,可以定义无数多个子类,并且这无数多个子类,都可以在电脑上插入使用。

按照以上的方式,不管有多少个设备,电脑上支持度都是一样,所以现在的 USB 提供的就是一个操作标准。

4.接口的作用——工厂设计模式( Factory )

在面向对象编程中,最通常的办法是一个 new 操作符产生一个对象实例,new 操作符就是用来构造对象实例的。但是在某些情况下, new 操作符直接生成对象会带来一些问题。例如:许多类型对象的创造需要一系列的步骤,你可能需要计算或取得对象的初始设置;选择生成哪个子对象实例;或在生成你需要的对象之前必须先生成一些辅助功能的对象。在这些情况下,新对象的建立就是一个 “ 过程 ”,不再是一个单步操作,就更像一部大机器中的批量齿轮传动。这时,我们构建一个工厂模式,来创建新的对象就能解决此问题。

举例:

//工厂模式
interface Fruit	//定义一个水果标准
{
	public void eat();	//吃
}

class Apple implements Fruit
{
	public void eat()
	{
		System.out.println("吃苹果");
	}
}

class Orange implements Fruit
{
	public void eat()
	{
		System.out.println("吃橘子");
	}
}

class Factory
{
	//此类不需要维护属性的状态
	public static Fruit getInstance(String className)
	{
		if("apple".equals(className))
		{
			return new Apple();
		}
		if("orange".equals(className))
		{
			return new Orange();
		}
		return null;
	}
}

public class factory
{
	public static void main(String[] args)
	{
		Fruit f = Factory.getInstance(args[0]);	//初始化参数
		f.eat();
	}
}

根据参数 args[0] 的内容实例化不同的子类,参数内容为 “ apple ”,实例化的是 Apple 类,参数内容 “ orange ”,则实例化的是 Orange 类,即输出内容也不同。

也可以在主类中直接设置参数内容 “ orange ”:Fruit f = Factory.getInstance("orange");

此时的程序,客户端没有和具体的子类耦合在一起,这样一来,如果再有更多的 Fruit 接口子类出现,只需要修改 Factory 类即可。即:所有的接口对象都通过 Factory 类取得。在程序员自己开发的代码之中,只要是遇见了接口对象实例的操作,都应该采用工厂设计模式。

5.接口的作用——代理设计模式( Proxy )

代理模式:给某一对象提供代理对象,并由代理对象控制具体对象的引用。

代理,指的就是一个角色代表另一个角色采取行动。就像生活中,一个红酒厂商是不会直接把红酒零售给客户的,都是通过代理来完成他的销售业务的。而客户也不用为了喝红酒而到处找工厂,他只要找到厂商在当地的代理就行了,具体红酒工厂在哪里,客户不用关心,代理会帮他处理。

这里产生了四个对象:客户、代理商、红酒厂商、代理商—红酒厂商( 关系 )。

代理模式作用:为其他对象( 红酒厂商 )提供一种代理( 代理商 )以控制对这个对象( 红酒厂商 )的访问。

代理对象可以在客户端( 客户 )和目标对象( 红酒厂商 )之间起到中介作用。

举例:

//代理设计模式
abstract class Subject	//代理请求
{
	abstract public void request();
}
class RealSubject extends Subject	//真实角色(红酒厂商)
{
	public void request()	//实现抽象方法
	{
		System.out.println("红酒厂商");
	}
}

class ProxySubject extends Subject	//代理角色(代理商)
{
	private RealSubject realSubject;	//以真实角色作为代理角色的属性
	public void request()	//该方法封装了真实对象的request方法
	{
		preRequest();
		if (realSubject == null) 
		{
			realSubject = new RealSubject();
		}
		realSubject.requset();	//此处执行真实对象的request方法
		postRequest();
	}
	private void preRequest()
	{
		System.out.println("宣传");
	}
	private void postRequest()
	{
		System.out.println("购买");
	}
}

public class Proxytest
{
	public static void main(String[] args)	//客户直接找代理商
	{
		Subject sub = new ProxySubject();	//子类为接口实例化
		sub.request();
	}
}

代理设计模式的核心组成:一个接口有两个子类,一个子类负责真实的业务操作功能,另外一个子类负责完成与真实业务有关的操作。

三、抽象类和接口对比

abstract class 和 interface 是 Java 语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了 Java 强大的面向对象能力。虽然两者在对于抽象类定义的支持方面具有很大的相似性,但也有各自不同的特点。

序号 区别 抽象类 接口
1 定义语法 abstract class 类名称 { } interface 接口名称 { }
2 组成 常量、全局常量、变量、构造方法、普通方法、抽象方法 全局常量、抽象方法、默认方法
3 权限 可以使用各种权限 都是 public 权限
4 使用 子类通过 extends 关键字继承一个抽象类 自类通过 implements 关键字实现多个接口
5 关系 一个抽象类可以实现多个接口 一个接口不能继承抽象类,但可以继承多个接口
6 设计模式 模板设计模式 工厂设计模式、代理设计模式
7 局限 单继承局限 没有单继承局限

通过分析,可以发现,

抽象类和接口的共同点如下:

(1)都是抽象类型;

(2)都可以有实现方法( 以前接口不行 );

(3)都可以不需要实现类或者继承者去实现所有方法。( 以前不行,现在接口中默认方法不需要实现者实现 )

抽象类和接口的不同点如下:

(1)抽象类不可以多重继承,接口可以( 无论是多重类型继承还是多重行为继承 );

(2)抽象类和接口所反映出的设计理念不同。其实抽象类表示的是 “ is-a ” 关系,接口表示的是 “ like-a ” 关系。

(3)接口中定义的变量默认是 public static finial 型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值;抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。

总体来说,抽象类和接口在很大程度上都是可以互相替换使用的,但就是由于抽象类本身具备单继承局限,所以当抽象类和接口全部都可以使用的时候优先考虑接口,因为接口没有单继承局限,并且在 Java 8 中接口可以设定默认方法,在一定程度上避免代码重复,利于后期的维护。

在 Java 8 中,允许给接口添加一个非抽象方法的实现,在语法上,只需要在方法前面添加 default 关键字即可,这个特征又叫做扩展方法,例如:

        interface Formula
        {
            double cal(int a);    //这是一个抽象方法,没有方法体
            default double my_sqrt(int a)
            {
                //添加default关键字,可以有方法体,称为扩展方法
                return Math.sqrt(a);
            }
        }

Formula 接口在拥有 cal 方法之外,同时还定义了 my_sqrt 方法,实现了 Formula 接口的子类只需要实现一个 cal 方法,默认方法 my_sqrt 可在子类上直接使用。如下代码所示:

        Formula my_form = new Formula()
        {
            @Override
            public double cal(int a)
            {
                //实现接口Formula的cal方法
                return sqrt(a*100);
            }
        };
        formula.cal(100);    //100.0
        formula.cal(81);    //9.0

上面代码中的 my_form 是一个匿名类的实例。由于在 Java 中只支持单继承,如果要让一个类赋予新的特性,通常使用接口,来实现类似于 C++ 中的多继承功能。Java 8 的这个新特性,在编译器实现的角度上来说,非常接近一种 JVM 语言 Scala 的 Trait( 特征 )。

四、本文注意事项

1.继承抽象类和继承普通类最大的区别

(1)在普通类之中所有的方法都是有方法体的,那么如果说有一些方法希望由子类来实现的时候,子类即使不实现也不会出现错误;

(2)如果使用抽象类的话,那么抽象类之中的抽象方法在语法上就必须要求子类进行实现,这样就可以强制子类做一些固定操作。

2.接口、抽象类、类、对象的关系

(1)基本类:也就是一般的类( 一般所说的类就是基本类 ),是对象的模板,是属性和方法的集合。可以继承其他基本类、抽象类、实现接口。

(2)抽象类:有抽象方法的类( 抽象方法就是该方法必须由继承来实现,本身只定义,不实现 )。抽象类可以有一个或多个抽象方法,它是基本类和接口类的过渡。

(3)接口:接口中的所有方法除默认方法( 带方法体 )外都是抽象方法,抽象方法本身只定义不实现,用来制定标准。

实际上所谓的接口就是指在类的基础上的进一步抽象。而很多的时候在开发之中,也会避免掉抽象类的出现,因为抽象类毕竟存在单继承局限。类与类之间的共性就成为了接口的定义标准。

类、抽象类、接口之间的联系,也可以举例如下,一个公司,有老板、经理、员工。每一个员工就是一个对象,类就是员工,抽象类就是经理,接口就是老板。接口就是给个方法,但是他自己不做,比如老板说我要那个文件,给我订机票,我要策划方案等,都是下面的人来做。老板只说不做。抽象类给的方法,有的他自己做,有的其他人做。比如经理说我要那个文档,员工就要发给他,但是他自己也要做点事,比如拿方案给老板看。经理又说有做。一般类给的方法,就是什么都要做,都要实现。

3.接口和抽象类的应用

abstract class 在 Java 语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在 “ is a ” 关系,即父类和派生类在概念本质上应该是相同的。对于 interface 来说则不然,并不要求 interface 的实现者和 interface 定义在概念本质上是一致的,仅仅是实现了 interface 定义的契约而已。

考虑这样一个例子,预研究建立一个关于 Door 的抽象概念,一般认为 Door 可执行两个动作 open 和 close,若通过 abstract class 或者 interface 来定义一个表示该抽象概念的类型,定义方式分别如下所示:

使用 abstract class 方式定义 Door:

        abstract class Door
        {
            abstract void open();
            abstract void close();
        }

使用 interface 方式定义 Door:

        interface Door
        {
            void open();
            void close();
        }

其他具体的 Door 类型可以 extends 使用 abstract class 方式定义的 Door 或者 implements 使用 interface 方式定义的 Door。看起来好像使用 abstract class 和 interface 没有大的区别。

如果现在要求 Door 还要具有报警功能,如何设计类结果呢?

解决方案一:

简单的在 Door 的定义中增加一个 alarm 方法,用抽象类定义如下:

        abstract class Door
        {
            abstract void open();
            abstract void close();
            abstract void alarm();
        }

则具有报警功能的 AlarmDoor 的定义方法如下:

        class AlarmDoor extends Door
        {
            void open(){……}
            void close(){……}
            void alarm(){……}
        }

或者用 interface:

        interface Door
        {
            void open();
            void close();
            void alarm();
        }

则具有报警功能的 AlarmDoor 通过接口的定义方法如下:

        class AlarmDoor implements Door
        {
            void open(){……}
            void close(){……}
            void alarm(){……}
        }

直接增加 alarm 方法违反了面向对象设计中的一个核心原则 ISP( Interface Segregation Principle ),在 Door 的定义中把 Door 概念本身固有的行为方法和另外一个概念 “ 报警器 ” 的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于 Door 这个概念的模块会因为 “ 报警器 ” 这个概念的改变( 比如:修改 alarm 方法的参数 )而改变,反之亦然。

解决方案二:

显然,open、close 和 alarm 属于两个不同的概念,根据 ISP 原则应该把它们分别定义在代表这两个概念的抽象类中。

定义的可能方式有三种。

(1)这两个概念都适用 abstract class 方式定义

由于 Java 语言不支持多重继承,所以两个概念都使用 abstract class 方式定义是不可行的。

(2)两个额概念都使用 interface 方式定义

无法明确体现 AlarmDoor 在概念本质上到底是 Door 还是报警器,无法反映 AlarmDoor 在概念本质上和 Door 是一致的。

(3)一个概念使用 abstract class 方式定义,另一个概念使用 interface 方式定义

        abstract class Door
        {
            abstract void open();
            abstract void close();
        }

        interface Alarm
        {
            void alarm();
        }

        class AlarmDoor extends Door implements Alarm
        {
            void open(){……}
            void close(){……}
            void alarm(){……}
        }

abstract class 在 Java 语言中表示一种继承关系,而继承关系在本质上是 “ is a ” 关系,对于 Door 这个概念,我们应该使用 abstract class 方式定义。interface 表示的是 “ like a ” 关系,AlarmDoor 又具有警报功能,说明它又能够完成报警概念中定义的行为。

你可能感兴趣的:(Java)