接口这个东西呢,我也是学了java才知道的,以前学Python和C(我是半吊子)的时候都没注意这个。所以这里很有必要认真学一下。
目录
使用接口实现算法重用
发现接口类型
声明接口类型
实现接口类型
比较接口和继承
处理接口类型
从类转换为接口
在接口变量上调用方法
从接口强制转换为类
Comparable接口
使用接口实现回调
内部类
使用接口实现算法重用
打个比方,一个餐厅,除了可以为人类这种类型的顾客提供服务,如果修猫来了,也可以将它们视作顾客提供服务:
public void serve(Person client)
// 为人类服务
public void serve(Cat client)
// 为修猫服务
注意,要做到可以泛化的功能就需要定义一个新类型叫做接口类型
发现接口类型
这里有一个例子(求解平均值),大家可以观察一下有什么相同之处和相异之处
// 计算一个银行账户数组的平均值
public static double average(BankAccount[] objects) {
double sum = 0;
for (BankAccount obj : objects) {
sum = sum + obj.getBalance();
}
if (objects.length > 0) {
return sum / objects.length;
} else {
return 0;
}
}
// 计算一个国家土地数组的平均值
public static double average(Country[] objects) {
double sum = 0;
for (Country obj : objects) {
sum = sum + obj.getArea();
}
if (objects.length > 0) {
return sum / objects.length;
} else {
return 0;
}
}
很显然,算法是一样的,只不过一些细节需要改动。
很容易想到的一个办法就是让他们统一把返回值定义一个getMeasure方法!
但是这样的想法只解决了一半!
因为在Java里面必须声明变量obj的类型!当然了,不可以写作:
BankAccount or Country or ... obj;
声明接口类型
接口类型用来指定所需的操作。
接口的声明和类的声明很类似:要列出接口所需的方法但不需要提供方法的实现。
public interface Measurable
{
double getMeasure();
}
一般的接口类型会要多个方法,然而上面这个例子只需要一个方法。
并且因为接口自身属性就应该是公共的,所以方法自动为公共
接下来我们重构average方法
public static double average(Measurable[] objects) {
double sum = 0;
for (Measurable obj : objects) {
sum = sum + obj.getMeasure();
}
if (objects.length > 0) {
return sum / objects.length;
} else {
return 0;
}
}
实现接口类型
既然average实现Measurable接口的任何类的对象,那么只要一个类在implements子句中声明了这个接口,这个类就会实现这个接口类型:
public class Quiz implements Measurable {
private double scores_num;
...
public double getMeasure() {
return scores_num;
}
}
一旦Quiz类实现了Measurable接口类型,那么Quiz对象就是Measurable类型实例:
Measurable obj = new BankAccount();
值得注意的是,average方法是放在另外一个Data的类里面的。像这样的操作是接口类型的常用操作!
代码放送如下:
public class Data
{
/**
Computes the average of the measures of the given objects.
@param objects an array of Measurable objects
@return the average of the measures
*/
public static double average(Measurable[] objects)
{
double sum = 0;
for (Measurable obj : objects)
{
sum = sum + obj.getMeasure();
}
if (objects.length > 0) { return sum / objects.length; }
else { return 0; }
}
}
/**
This program demonstrates the measurable BankAccount and Country classes.
*/
public class MeasurableTester
{
public static void main(String[] args)
{
// Calling the average method with an array of BankAccount objects
Measurable[] accounts = new Measurable[3];
accounts[0] = new BankAccount(0);
accounts[1] = new BankAccount(10000);
accounts[2] = new BankAccount(2000);
double averageBalance = Data.average(accounts);
System.out.println("Average balance: " + averageBalance);
System.out.println("Expected: 4000");
// Calling the average method with an array of Country objects
Measurable[] countries = new Measurable[3];
countries[0] = new Country("Uruguay", 176220);
countries[1] = new Country("Thailand", 513120);
countries[2] = new Country("Belgium", 30510);
double averageArea = Data.average(countries);
System.out.println("Average area: " + averageArea);
System.out.println("Expected: 239950");
}
}
比较接口和继承
一个类可以有多个接口,这是必然的!
public class Country implements Measurable,Name
这里的Name就是另一个不同的接口。与此不同的是,一个类只能扩展一个超类。一般来说,当你的代码要以一种通用的方式处理不同类的对象,就要建立接口。
处理接口类型
从类转换为接口
看一下这个代码:
double averageBalance = Data.average(accounts);
accounts是一个数组,不过它是属于BankAccount的对象。上面的代码则是想要将BankAccount类型转换为Measurable类型,这是合法的!
因为一般的情况下,可以从一个类的类型转换成这个类实现的任意一个接口的类型,例如:
BankAccount account = new BankAccount(1000);
Measurable meas = account;
那么也举一个反例:
// 标准库里面的Rectangle类则是未实现Measurable接口,
// 所以下面的赋值是错误的
Measurable meas = new Rectangle(5,10,20,30);
在接口变量上调用方法
教材这段的内容只是说理,没什么对于写代码者有实际要求的:
比如,实现了一个对象meas,但是不知道它属于哪一个类。虽然我们可以确定它是实现了Measurable类型的(不然也不可以调用getMeasure方法),但是我们不清楚,不过计算机清楚它到底是BankAccount类还是Country类。
从接口强制转换为类
有的时候你的接口存储了一个对象,并且接下来的操作你需要将它转换为原来的类型。
举一个生动形象的例子,一个超人,他平时都是普通人的行为,当他需要拯救世界的时候,他自然不能以一个普通人的能力去拯救世界(普通人当然也伟大),而是应该转换为超人的状态。
public static Measurable larger(Measurable obj1, Measurable obj2)
{
if (obj1.getMeasure() > obj2.getMeasure())
{
return obj1;
}
else
{
return obj2;
}
}
好了,定义了应该取更大值的方法,那么结合这部分内容详解一下:
Country uruguay = new Country("Uruguay",176220);
Country thailand = new Country("Thailand",513120);
Measurable max = larger(uruguay,thailand);
// 那么现在问题来了,怎么处理max引用呢?
// 简单,直接强制类型转换
Country maxCountry = (Country) max;
String name = maxCountry.getName();
Comparable接口
Comparable是一个标准库的内容。Comparable接口是比较复杂的,但是我们只需要理解和记住的只有compareTo方法(实际上该库内也只有这一个方法):
a.comapreTo(b)
这个东西到我们手里,往往是要重新扩展定义的:
比如BankAccount类实现 Comparable:
public class BankAccount implements Comparable
{
...
public int compareTo(Object otherObject)
{
BankAccount other = (BankAccount) otherObject;
if (balance < other.balance) {return -1;}
if (balance > other.balance) {return 1;}
return 0;
}
...
}
值得注意的是,这里compareTo方法有一个Object类型的参数变量,为了把它转换为BankAccount可引用,需要强制转换。
BankAccount[] accounts = new BankAccount[3];
accounts[0] = new BankAccount(10000);
accounts[1] = new BankAccount(0);
accounts[2] = new BankAccount(2000);
// 一旦实现了Comparable接口,就可以用Arrays.sort进行排序
Arrays.sort(accounts);
使用接口实现回调
之所以会出现这种机制的原因是,我们自定义的类想怎么改动就怎么改动,getMeasure()方法可以直接写进去,但是,一些官方已经写好并且不允许改动的库也要做我们自定义同样的方法的效果该怎么实现呢?就好比如Rectangele类,我们想要测度它,但是它肯定是没有getMeasure()方法的。
所以,这个回调就出现了。
回调是建立一个代码块,使它可以在之后某个时间被调用的机制。
Java作为面向对象的编程语言,是需要把回调转换为对象的。这个过程中,首先要为回调声明一个接口:
/**
Describes any class whose objects can measure other objects.
*/
public interface Measurer
{
/**
Computes the measure of an object.
@param anObject the object to be measured
@return the measure
*/
double measure(Object anObject);
}
import java.awt.Rectangle;
/**
Objects of this class measure rectangles by area.
*/
public class AreaMeasurer implements Measurer
{
public double measure(Object anObject)
{
Rectangle aRectangle = (Rectangle) anObject;
double area = aRectangle.getWidth() * aRectangle.getHeight();
return area;
}
}
public class Data
{
/**
Computes the average of the measures of the given objects.
@param objects an array of objects
@param meas the measurer for the objects
@return the average of the measures
*/
public static double average(Object[] objects, Measurer meas)
{
double sum = 0;
for (Object obj : objects)
{
sum = sum + meas.measure(obj);
}
if (objects.length > 0) { return sum / objects.length; }
else { return 0; }
}
}
import java.awt.Rectangle;
/**
This program demonstrates the use of a Measurer.
*/
public class MeasurerTester
{
public static void main(String[] args)
{
Measurer areaMeas = new AreaMeasurer();
Rectangle[] rects = new Rectangle[]
{
new Rectangle(5, 10, 20, 30),
new Rectangle(10, 20, 30, 40),
new Rectangle(20, 30, 5, 15)
};
double averageArea = Data.average(rects, areaMeas);
System.out.println("Average area: " + averageArea);
System.out.println("Expected: 625");
}
}
内部类
内容很简单,就是一个类里面再建一个类,那就是内部类了。但是你会发现报错,那么解决办法就是用静态来static处理!
既然都说到static了,那么我们把这个原本在第八章里面的内容说一下:本质含义就是共享!比如说我们之前的多篇文章里面都是提到了一个我们自建类BankAccount,里面有一些私有变量,比如说balance,但是当年建立各个BankAccount对象的时候,各个对象的balance是独立的,就是说你和我的银行账户的余额是不互通的。但是如果你在定义BankAcccount类的时候不小心在balance前面加了一个static,那不好意思,所有BankAccount对象的账户余额balance是互通的!就是一个balance!