抽象类与接口详解

1.抽象类

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

举个例子:现在有Person,Student,Employee三个类,在类的关系层次中,Student和Employee都继承自Person这个超类。由于Student和Employee都有姓名属性,所以可以将getName()方法放置于位于继承关系较高层次的通用超类Person中。若再增加一个getDescription()方法,它可以返回一个人的简短描述。例如:
an employee with a salary of $50,000.00
a student majoring in computer science
在Employee类和Student类中实现这个方法很容易,但是在Person类中应该提供什么内容呢?除了姓名之外,Person类一无所知。当然可以让Person.getDescripton()返回一个空字符串。但还有一个更好的方法:就是使用abstract关键字,这样在Person类中就完全不需要实现这个方法了。

抽象类的特性:

1.为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。类即使不含有抽象方法 ,也可以将类声明为抽象类。代码如下:

public abstract class Person {
	...
	public abstract String getDescription();
}

2.除了抽象方法外,抽象类还可以包含具体数据和具体方法。例如,Person类还保存着姓名和一个返回姓名的具体方法。

public abstract class Person {
	private String name;
	public Person(String name) {
		this.name = name;
	}
	public abstract String getDescription();
	public String getName() {
		return name;
	}
}

3.如何扩展抽象类
抽象方法充当着占位的角色,它们的具体实现在子类中。扩展抽象类有两种方法。
(1)在继承抽象类的子类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类。
(2)第二种是定义全部的抽象方法,这样一来,子类就不是抽象的了。

4.抽象类不能被实例化
例如不能创建抽象类Person的对象,表达式:

new Person("Mr True");//ERROR

错误的,但是可以创建一个具体子类的对象。
需要注意,可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。例如:

Person p = new Student("Mr True", "Software Engineering");//OK

为了更好的理解抽象类,Person,Student,Employee类的代码如下:

package abstractClasses;

public abstract class Person {
	public abstract String getDescription();
	private String name;
	public Person(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
}
package abstractClasses;
//由于在Student类中不再含有抽象方法,所以不用为这个类声明为抽象的
public class Student extends Person {
	private String major;
	/**
	 * @param name the student's name
	 * @param major the student's major
	 */
	public Student(String name, String major) {
		super(name);//调用父类Person的构造器
		this.major = major;
	}
	
	public String getDescription() {
		return "a student majoring in " + major;
	}

}

package abstractClasses;

import java.time.LocalDate;

public class Employee extends Person {
	private double salary;
	private LocalDate hireDay;
	
	public Employee(String name, double salary, int year, int month, int day) {
		super(name);
		this.salary = salary;
		hireDay = LocalDate.of(year, month, day);
	}
	public double getSalary() {
		return salary;
	}
	
	public LocalDate getHireDay() {
		return hireDay;
	}
	public String getDescription() {
		return String.format("an employee with a salary of $%.2f", salary);
	}
	public void raiseSalary(double byPercent) {
		double raise = salary * byPercent / 100;
		salary += raise;
	}
}

注意:不可以省略Person超类中的抽象方法而仅仅在Employee和Student子类中定义getDescription()方法。原因是Java是强类型语言,在调用方法的时候,编译器会检查这个方法是否存在,它只允许调用在类中声明的方法。如果省略,就不能通过变量p调用getDescription()方法了。

2.接口

接口技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。

在Java程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。“如果类遵从某个特定接口,那么就履行这项服务”。下面给出一个具体的示例:
Arrays类中的sort方法承诺可以对对象数组进行排序,但要求满足前提——对象所属的类必须实现了Comparable接口。Comparable接口的代码如下:

public interface Comparable {
	int compareTo(Object other);
}

这就是说,任何实现了Comparable接口的类都需要包含一个compareTo方法,并且这个方法的参数必须是一个Object对象,返回一个整型数值。
概述接口特性:接口绝不能含有实例域,(在Java SE 8之前也不能再接口中实现方法),可以定义常量,在Java SE 8中也可以包含多个方法(当然,这个方法不能引用实例域),包括静态方法(虽然有违于将接口作为抽象规范的初衷)因此,可以将接口看成是没有实例域的抽象类。

接口的特性:

1.接口不是类,尤其不能使用new运算符实例化一个接口:

x = new Comparable(...); //ERROR

2.然而,尽管不能构造接口的对象,却能声明接口的变量:

Comparable x; //OK

接口变量必须引用了实现了接口的类对象。
3.可以用instanceof检查一个对象是否实现了某个特定的接口:

if (anObject instanceof Comparable) {
	...
}

4.接口可以被扩展,允许存在多条从具有较高通用性的接口到较高专用性接口的链。例如,假设有一个称为Moveable的接口:

public interface Moveable {
	void move(double x, double y);
}	 

然后,可以以它为基础扩展一个叫做Powered的接口:

public interface Powered extends Moveable {
	double milesPerGallon();
}

5.接口中的方法自动的被设置为public,接口中的域自动的被设置为public static final,也就是常量。

public interface Powered extends Moveable {
	double milesPerGallon();// a public method
	double SPEED_LIMIT = 95;// a public static final constant
}

6.类如何实现接口
(1)将类声明为实现给定的接口:

class Employee implements Comparable {
	...
}

(2)对接口中的所有方法进行定义
在接口声明中,没有将compareTo方法声明为public,这是因为在接口中的所有方法都自动地是public。不过,在类实现接口时,必须把方法声明为public;否则,编译器将认为这个方法的访问属性时包可见性,即类的默认访问属性,之后编译器就会给出试图提供更严格的访问权限的警告信息。

class Employee implements Comparable {
	public int comparableTo(Object other) {
		...
	}
}

补充:在Java SE 5.0中,Comparable接口已经改进为泛型类型:

public interface Comparable<T>{
	int compareTo(T other);// parameter has type T
}

此时,在实现Comparable<T>的接口的类中必须提供以下代码:

class Employee implements Comparable<T> {
	public int comparableTo(T other) {
		...
	}
}//现在看起来顺眼多了

7.在接口中增加静态方法
目前为止,通常的做法都是将静态方法放在伴随类中。在标准库中,接口和实用工具类都是成对出现的,如Collection/Collections或Path/Paths。Paths类中只包含两个工厂方法。可以由一个字符串序列构造一个文件或目录的路径,如

Paths.get("jdk1.8.0", "jre", "bin");

在Java SE 8中,可以为Path接口增加以下方法:

public interface Path {
	public static Path get(String first, String... more) {
		return FileSystems.getDefault().getPath(first, more);
	}
	...
}

这样一来,Paths类就不是必要的了。不过整个Java库都以这个方式重构也是不太可能的,但是在实现自己的接口时,不再需要为实用工具方法另外提供一个伴随类。

8.为接口提供默认实现
在为接口方法提供一个默认实现时,必须用default修饰符标记。

public interface Comparable<T> {
	default int compareTo(T other) {
		return 0;
	}
	//by default, all elements are the same
}

当然,着并没有太大用处,因为Comparable的每一个实际实现都要覆盖这个方法。不过有些情况下,默认方法可能很有用。例如,如果希望在发生鼠标点击事件时得到通知,就要实现包含5个方法的接口:

public interface MouseListener {
	void mouseClicked(MouseEvent event);
	void mousePressed(MouseEvent event);
	void mouseReleased(MouseEvent event);
	void mouseEntered(MouseEvent event);
	void mouseExited(MouseEvent event);
}

大多数情况下,只需要关心其中1、2个事件类型。在Java SE 8中,可以将所有方法声明为默认方法,这个默认方法什么也不做。

public interface MouseListener {
	default void mouseClicked(MouseEvent event);
	default void mousePressed(MouseEvent event);
	default void mouseReleased(MouseEvent event);
	default void mouseEntered(MouseEvent event);
	default void mouseExited(MouseEvent event);
}

这样一来,实现这个接口的程序员只需要为它们真正关心的事件覆盖相应的监听器。
**默认方法可以调用其他任何方法。**例如,Collection接口可以定义一个便利方法:

public interface Collection {
	int size();//an abstract method
	default boolean isEmpty() {
		return size() == 0;
	}
	...
}

这样,实现Collection的程序员就不用操心实现isEmpty()方法了。

9.解决默认方法冲突
问题描述:
如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,会发生什么情况呢?
规则如下:
(1)超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
(2)接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型(不论是否是默认参数)相同的方法,必须覆盖这个方法来解决冲突。
现有另一个包含getName()方法的接口:

interface Named {
	default String getName() {
		return getClass().getName() + "_" + hashCode();
	}
}

如果一个类同时实现了这两个接口:

class Student implements Person, Named{
	...
}

Student类会继承Person和Named接口提供的两个不一致的getName()方法,并不是在其中选择一个。此时Java编译器会报告一个错误,让程序员来解决这个二义性。只需要在Student类中提供一个getName()方法。在这个方法中,可以选择两个冲突方法中的一个,代码如下:

class Student implements Person, Named{
	public String getName() {
		return Person.super.getName();
	}
	...
}

现在假设Named接口没有为getName()提供默认实现:

interface Named {
	String getName();
}

Student类也不会只从Person接口中继承默认方法。Java强调一致性,两个接口如何冲突并不重要,如果至少有一个接口提供了一个实现,编译器就会报告错误,而程序员就必须解决这个二义性。
注释:如果两个接口都没有为共享方法提供默认实现,那么就不存在冲突。

3.抽象类与接口的比较

1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。

2、抽象类要被子类继承,接口要被类实现。

3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现

4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。

6、抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果

7、抽象类里可以没有抽象方法

8、如果一个类里有抽象方法,那么这个类只能是抽象类

9、抽象方法要被实现,所以不能是静态的,也不能是私有的。

10、接口可继承接口,并可多继承接口,但类只能单根继承。

链接: 百度百科:抽象类

补充:多重继承
使用抽象类表示通用属性存在这样一个问题:每个类只能扩展于一个超类。如果一个类已经扩展于一个超类了,那么它就不能再扩展于第二个超类了。允许一个类有多个超类的这种特性称为多重继承。例如C++就支持多重继承,但Java不支持多重继承,其主要原因是多重继承会使语言本身变得非常复杂,效率也会降低。取而代之的是,每个类可以实现多个接口,接口可以提供多重继承的大多数好处,同时还可以避免多重继承的复杂性和低效性。
例如:Java有一个非常重要的内置接口,称为Cloneable。如果某个类实现了这个Cloneable接口,Object类中的clone方法就可以创建类对象的一个拷贝。如果希望自己设计的类同时拥有克隆和比较的能力,只要实现这两个接口就可以了,使用逗号将实现的各个接口分割开:

class Employee implements Cloneable, Comparable {
	...//对实现接口中的所有方法进行定义
}

以上内容出自《java核心技术卷Ⅰ基础知识》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值