文章目录

  • 什么是抽象类?
  • 定义抽象类
  • 示例代码
  • 什么是接口?
  • 接口声明
  • 接口特性
  • 实现接口
  • 在实现接口时要注意的规则
  • 接口与抽象类
  • 接口的高级应用
  • 接口的多态
  • 适配接口
  • 嵌套接口
  • 接口回调


什么是抽象类?

假设要编写一个计算圆、三角形和矩形的面积与周长的程序。若按前面所学的方式编程,就必须定义4 个类:圆类、三角形类、矩形类和使用前3 个类的公共类,它们之间没有继承关系。程序写好后虽然能执行,但从程序的整体结构上看,前3 个类之间的许多共同属性和操作在程序中没有很好地被利用,需要重复编写代码,降低了程序的开发效率,且使出现错误的机会增加。

  • 仔细分析上面例子中的前3 个类,可以看到这3 个类都要计算面积与周长,虽然公式不同,但目标相同。因此,可以为这3 个类抽象出一个父类,在父类里定义圆、三角形和矩形3 个类共同的成员属性及成员方法。把计算面积与周长的成员方法名放在父类中说明,再将具体的计算公式在子类中实现。
  • 这样,通过父类就大概知道子类所要完成的任务,而且,这些方法还可以应用于求解梯形、平行四边形等其他图形的面积与周长。这种结构就是抽象类的概念。
  • Java 程序用抽象类(abstract class)来实现自然界的抽象概念。抽象类的作用在于将许多有关的类组织在一起,提供一个公共的类,即抽象类。而那些被它组织在一起的具体的类将作为它的子类由它派生出来。
  • 抽象类刻画了公有行为的特征,并通过继承机制传递给它的派生类。
  • 抽象类是它的所有子类的公共属性的集合,是包含一个或多个抽象方法的类。使用抽象类的一大优点就是可以充分利用这些公共属性来提高开发和维护程序的效率。

定义抽象类

与普通类相比,抽象类要使用abstract 关键字声明。普通类是一个完善的功能类,可以直接产生实例化对象,并且在普通类中可以包含构造方法、普通方法、static 方法、常量和变量等内容。而抽象类是在普通类的结构里面增加抽象方法的内容。

抽象类的使用原则:

1.抽象方法必须为public 或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),默认为public。
2.抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理
3.抽象类必须有子类,使用extends 继承,一个子类只能继承一个抽象类
4.子类如果不是抽象类,则必须重写抽象类中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为抽象类)。
5.抽象类不能使用final 关键字声明,因为抽象类必须有子类,而final 定义的类不能有子类。

示例代码

public abstract class Shapes {
	/**
	 * 定义抽象类Shapes图形类, 包含抽象方法getArea();getPerimeter();
	 **/
	public abstract double getArea();

	// 获取面积
	public abstract double getPerimeter();
	// 获取周长
}

public class Circle extends Shapes {
	double r;

	public Circle(double r) {
		this.r = r;
	}

	public double getArea() {
		return r * r * Math.PI;
	}

	public double getPerimeter() {
		return 2 * Math.PI * r;
	}
}
public class Square extends Shapes {
	int width;
	int height;

	public Square(int width, int height) {
		this.width = width;
		this.height = height;
	}

	public double getArea() {
		return width * height;
	}

	public double getPerimeter() {
		return 2 * (width + height);
	}
}

public class Test {
	public static void main(String args[]) {
		Circle c1=new Circle(1.0);
        System.out.println("圆形面积为"+c1.getArea());
        System.out.println("圆形周长为"+c1.getPerimeter());
        Square s1=new Square(1,1);
        System.out.println("正方形面积为"+s1.getArea());
        System.out.println("正方形周长为"+s1.getPerimeter());

	}
}

什么是接口?

接口是比抽象类更高的抽象,它是一个完全抽象的类,即抽象方法的集合。接口使用关键字interface来声明
接口中的方法是不能在接口中实现的,只能由实现接口的类来实现。一个类可以通过关键字implements来实现。如果实现类没有实现接口中的所有抽象方法,那么该类必须声明为抽象类。

接口声明

public interface Shape {
	public double area(); // 计算面积
	public double perimeter(); // 计算周长
}

使用关键字interface 声明了一个接口Shape,并在接口内定义了两个抽象方法area()和perimeter()。

接口特性

 接口中也有变量,但是接口会隐式地指定为public static final 变量,并且只能是public,用private修饰会报编译错误。
 接口中的抽象方法具有public 和abstract 修饰符,也只能是这两个修饰符,其他修饰符都会报错。
 接口是通过类来实现的。
 一个类可以实现多个接口,多个接口之间使用逗号(,)隔开。
 接口可以被继承,被继承的接口必须是另一个接口。

实现接口

当类实现接口的时候,类要实现接口中所有的方法,否则类必须声明为抽象的类。类使用implements关键字实现接口。在类声明中,implements 关键字放在class 声明后面。

public interface Shape {
	public double area(); // 计算面积

	public double perimeter(); // 计算周长
}

public class Circle implements Shape { // Circle 类实现Shape 接口
	double radius; // 半径

	public Circle(double radius) { // 定义Circle 类的构造方法
		this.radius = radius;
	}

	public double area() { // 重写实现接口定义的抽象方法
		return Math.PI * radius * radius;
	}

	public double perimeter() {
		return 2 * Math.PI * radius;
	}
}

public class Rectangle implements Shape { // Rectangle 类实现Shape 接口
	double a; // 长或宽
	double b; // 长或宽

	public Rectangle(double a, double b) { // 定义Circle 类的构造方法
		this.a = a;
		this.b = b;
	}

	public double area() { // 重写实现接口定义的抽象方法
		return a * b;
	}

	public double perimeter() {
		return 2 * (a + b);
	}
}

在实现接口时要注意的规则

 一个类可以同时实现多个接口。
 一个类只能继承一个类,但是能实现多个接口。
 一个接口能继承另一个接口,这和类之间的继承比较相似。
 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

接口与抽象类

接口的结构和抽象类非常相似,也具有数据成员与抽象方法,但它又与抽象类不同。

接口与抽象类的相同点

  1. 都可以被继承。
  2. 都不能被直接实例化。
  3. 都可以包含抽象方法。
  4. 派生类必须实现未实现的方法。

接口与抽象类的不同点

  1. 接口支持多继承,抽象类不能实现多继承。
  2. 一个类只能继承一个抽象类,但可以实现多个接口。
  3. 接口中的成员变量只能是public static final 类型的,抽象类中的成员变量可以是各种类型的。
  4. 接口只能定义抽象方法;抽象类既可以定义抽象方法,也可以定义实现的方法。
  5. 接口中不能含有静态代码块以及静态方法(用static修饰的方法),抽象类可以有静态代码块和静态方法。

接口的高级应用

接口的多态

  • Java 中没有多继承,一个类只能有一个父类。而继承的表现就是多态,一个父类可以有多个子类,而在子类里可以重写父类的方法,这样每个子类里重写的代码不一样,自然表现形式就不一样。
  • 用父类的变量去引用不同的子类,在调用这个相同的方法的时候得到的结果和表现形式就不一样了,这就是多态,调用相同的方法会有不同的结果。
public class ShapeTest {
	public static void main(String[] args) {
		Shape s1 = new Circle(10.0); // 体现多态的地方
		System.out.println("圆形的面积是:" + s1.area());
		System.out.println("圆形的周长是:" + s1.perimeter());
		Shape s2 = new Rectangle(5.0, 10.0); // 体现多态的地方
		System.out.println("矩形的面积是:" + s2.area());
		System.out.println("矩形的周长是:" + s2.perimeter());
	}
}

在本例中,Shape 是一个接口,没有办法实例化对象,但可以用Circle 类和Rectangle 类来实例化对象,也就实现了接口的多态。实例化产生的对象s1 和s2 拥有同名的方法,但各自实现的功能却不一样。根据实现接口的类中重写的方法,实现了用同一个方法计算不同图形的面积和周长的功能。

适配接口

在实现一个接口时,必须实现该接口的所有方法,这样有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要使用其中的一些方法。为了解决这个问题,引入了接口的适配器模式,借助于一个抽象类来实现该接口所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系。写一个类,继承该抽象类,再重写需要的方法就行。

public interface InterfaceAdapter { // 定义接口
	public void email();

	public void sms();
}

public abstract class Wrapper implements InterfaceAdapter {
	// 写一个抽象类管理接口
	public void email() {
	}

	public void sms() {
	}
	// 方法体不需要具体实现,可以为空,具体类在需要时可以重写该方法
}

public class S1 extends Wrapper {
	// 继承抽象类,重写所需的方法,这里重写了email()方法,没有重写sms()方法
	public void email() {
		System.out.println("发电子邮件");
	}
}

public class Test {
	public static void main(String[] args) {
		S1 ss = new S1();
		ss.email();
	}
}

在本例中,首先定义了一个接口InterfaceAdapter,并定义了两个抽象方法email()和sms()。然后定义了一个抽象类Wrapper,并实现了两个抽象方法,但方法体为空。定义了一个类S1,重写了email()方法。这样写的好处是,定义类时不需要直接实现接口InterfaceAdapter 并实现定义的两个方法,而只需要实现并重写email()方法即可。

嵌套接口

在Java 语言中,接口可以嵌套在类或其他接口中。由于Java 中在interface 内是不可以嵌套class 的,所以接口的嵌套共有两种方式:class 内嵌套interface、interface 内嵌套interface。
1. class 内嵌套interface
这时接口可以是public、private 和package。重点在private 上,被定义为私有的接口只能在接口所在的类中实现。可以被实现为public 的类也可以被实现为private。当被实现为public 时,只能在自身所在的类内部使用。只能够实现接口中的方法,在外部不能像正常类那样上传为接口类型。
2. interface 内嵌套interface
由于接口的元素必须是public 的,所以被嵌套的接口自动就是public 的,而不能定义成private 的。在实现这种嵌套时,不必实现被嵌套的接口。

class A {
	private interface D {
		void f();
	}

	private class DImp implements D {
		public void f() {
		}
	}

	public class DImp2 implements D {
		public void f() {
		}
	}

	public D getD() {
		return new DImp2();
	}

	private D dRef;

	public void receiveD(D d) {
		dRef = d;
		dRef.f();
	}
}

public class NestingInterfaces {
	public static void main(String[] args) {
		A a = new A();
		// D 是A 的私有接口,不能在外部被访问
		// ! A.D ad = a.getD();
		// 不能从A.D 转型成A.DImpl2
		// ! A.DImp2 di2 = a.getD();
		// D 是A 的私有接口,不能在外部被访问,更不能调用其方法
		// ! a.getD().f();
		A a2 = new A();
		a2.receiveD(a.getD());
	}
}

本例中,语句A.D ad = a.getD();和a.getD().f();产生编译错误,这是因为D 是A 的私有接口,不能在外部被访问。语句A.DImp2 di2 = a.getD();的错误是因为getD()方法的返回值类型为D,不能自动向下转型为DImp2 类型。

接口回调

接口回调是指可以把使用某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象功能的接口回调。

public interface Shape {
	public double area(); // 计算面积

	public double perimeter(); // 计算周长
}

public class Circle implements Shape { // Circle 类实现Shape 接口
	double radius; // 半径

	public Circle(double radius) { // 定义Circle 类的构造方法
		this.radius = radius;
	}

	public double area() { // 重写实现接口定义的抽象方法
		return Math.PI * radius * radius;
	}

	public double perimeter() {
		return 2 * Math.PI * radius;
	}
}

public class Rectangle implements Shape { // Rectangle 类实现Shape 接口
	double a; // 长或宽
	double b; // 长或宽

	public Rectangle(double a, double b) { // 定义Circle 类的构造方法
		this.a = a;
		this.b = b;
	}

	public double area() { // 重写实现接口定义的抽象方法
		return a * b;
	}

	public double perimeter() {
		return 2 * (a + b);
	}
}

public class Show { // 定义一个类用于实现显示功能
	public void print(Shape s) // 定义一个方法,参数为接口类型
	{
		System.out.println("面积:" + s.area());
		System.out.println("周长:" + s.perimeter());
	}
}

public class Test { // 测试类
	public static void main(String[] args) {
		Show s1 = new Show();
		s1.print(new Circle(10.0));
		// 接口回调,将Shape e 替换成 new Circle(10.0)
		s1.print(new Rectangle(5.0, 10.0));
		// 接口回调,将Shape e 替换成 new Rechtangle(5.0,10.0)
		// 使用接口回调的最大好处是可以灵活地将接口类型参数替换为需要的具体类
	}
}

本例中定义了一个类Show,其中定义了一个方法print(),将Shape 类型的变量作为参数。在测试时,实例化Show,并调用print()方法,将new Circle()和new Rectangle()作为实际参数,因此会调用不同的方法,结果显示不同图形的面积和周长。