目录
1. 概念
2. 语法规则
3. 接口的命名规则
4. 实现多个接口
5. 接口实现示例
6. Cloneable 接口和深、浅拷贝
6.1 Cloneable 接口
6.2 浅拷贝
6.3 深拷贝
接口在 Java 中是一个抽象类型,是抽象方法的集合,是抽象类的更进一步。接口通常以 Interface 来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
在打印图形的示例中(可以看上一篇),父类中没有 Shape 没有包含别的非抽象方法,所以也可以将它设计成一个接口。
interface IShape {
void draw();
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("○");
}
}
public class Test {
public static void main(String[] args) {
IShape shape = new Rect();
shape.draw();
}
}
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
接口中只能包含抽象方法。对于字段来说, 接口中只能包含静态常量(final static)。
interface IShape {
void draw();
public static final int num = 10;
}
小结:
有时候我们需要一个类继承多个父类,但是在 Java 中只支持单继承,所以这时候就可以用多接口实现这种多继承的效果。
例子:
先通过类表示一组动物:
public class Animal {
protected String name;
// 有参构造方法
public Animal(String name) {
this.name = name;
}
}
动物都有不同的技能,比如说会飞,会游泳,会跑,要想让不同的动物具有各自不同的特点,可以将各种不同的技能设置成接口。这样也方便多种技能的动物“实现”多个技能。
public interface IFlying {
void fly();
}
public interface IRunning {
void running();
}
public interface ISwimming {
void swimming();
}
接下来就是设计几种不同的动物:
首先是,猫会跑,可以实现跑的接口方法。
public class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void running() {
System.out.println(this.name + "正在跑");
}
}
还有鱼,鱼可以游泳。
青蛙,青蛙既可以在陆地上跑跳,也可以在水中游泳,所以这个类可以实现两个接口。
public class Frog extends Animal implements ISwimming, IRunning {
public Frog(String name) {
super(name);
}
@Override
public void running() {
System.out.println(this.name + "正在跳");
}
@Override
public void swimming() {
System.out.println(this.name + "正在游泳");
}
}
有了接口后,类的调用者就不必关注具体类型,而只关注某个类具备的能力。
在 walk 方法内部,不必关注到底是哪种动物,只要关注他是否具有跑这个功能就行。其他方法也是如此。
// 多接口
public class TestAniaml {
public static void main(String[] args) {
Cat cat = new Cat("猫猫");
Frog frog = new Frog("青蛙");
Fish fish = new Fish("小鱼");
walk(frog);
walk(cat);
swim(frog);
swim(fish);
}
public static void walk(IRunning running) {
running.running();
}
public static void swim(ISwimming swimming) {
swimming.swimming();
}
}
给对象数组排序
在Arrays工具类中,sort 函数可以对普通数组进行排序,但是在如下代码中,如果使用 sort 方法进行排序,就会运行出错,抛出异常。
import java.util.Arrays;
/**
* 接口示例,对学生对象数组进行排序
*/
public class Student implements Comparable {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
this.name + '\'' +
", score=" + this.score +
'}';
}
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("小王", 87),
new Student("小赵", 90),
new Student("小敏", 89),
};
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
错误:
从报错信息以及源码上来看,可以发现所有使用 sort (Object[ ] a)方法进行排序的对象都必须实现Comparable接口。而源码中的比较类型是 int 类型,所以如果我们想要使用 sort 方法,就要先复写Comparable接口中的 compareTo 方法,传入想要比较的对象类型,也就是 Student 类。
然后就可以实现 sort 方法,运行结果成为按照 score 的大小排序的数组:
Cloneable 是 Java 中内置的接口之一。使用场景:Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 "拷贝"。但是要想合法调用 clone 方法, 必须要先实现 Cloneable 接口, 否则就会抛出 CloneNotSupportedException 异常。
/**
* Cloneable 接口
*/
public class B implements Cloneable{
@Override
protected B clone() throws CloneNotSupportedException {
return (B)super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
B b = new B();
B b1 = b.clone();
B b2 = b;
System.out.println(b == b1);
System.out.println(b == b2);
}
}
运行结果:
从结果可以看出 B 类已经具有了拷贝能力,== 看得是两个对象引用的地址是否是同一个,b 和 b2 还是一个对象只是有两个名字所以结果是 true,而 b 经过拷贝创新创建了一个新对象 b1,引用指向的地址自然不是一个,结果就是 false。
拷贝分为浅拷贝和深拷贝,要注意它们的区别。
可以从运行结果看出,改变了拷贝的值,随之原对象的值也发生了变化;同样的改变原对象的值,拷贝后的对象的值也发生了变化。 这是因为虽然 b1 和 b 是两个不同的对象,但他们内部包含的 a 对象却是相同的。开始时 a 的默认值都是0,将100赋给 b1 中的 a 时,b 中的 a 也就改变了,因为这两个对象中的 a 是同一个引用。
小结:浅拷贝就是当一个对象是通过另一个对象 clone 出来的,此时这两个对象虽然是独立的两个对象,但是这两个对象的内部包含的其他引用是相同的。
深拷贝就是当一个对象是通过另一个对象 clone 出来的,此时这两个对象是独立的两个对象,这两个对象所包含的所有其他引用也是独立的。
深拷贝的实现方式:
1. 嵌套实现 clone 方法
2. 序列化实现 clone 方法
现在开发中常见的序列化就是将一个对象转化成字符串( json),后续学习 Java web 时会用到。