Java中的接口以及常见的Cloneable接口用法
作者:掉了颗兔牙lx
1. 概念
接口在 Java 中是一个抽象类型,是抽象方法的集合,是抽象类的更进一步。
接口通常以 Interface 来声明。
一个类通过继承接口的方式,从而来继承接口的抽象方法。
2. 语法规则
在打印图形的示例中,父类中没有 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; }
小结:
- 使用 interface 定义一个接口.
- 子类使用 implements 实现接口,子类可以同时实现多个接口 implements 多个接口。
- 接口中的方法一定是抽象方法, 因此可以省略 abstract.
- 接口中的方法一定是 public, 因此可以省略 public.
- Cycle 使用 implements 继承接口,此时表达的含义不再是 "扩展", 而是 "实现",扩展指的是当前已经有一定的功能了, 进一步扩充功能。实现指的是当前啥都没有, 需要从头构造出来。
- 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。
- 一个类如果同时继承抽象类,实现接口,请先 extends 一个类,而后 implements 多个接口。
- Java 中允许同时实现多个接口,不允许多继承。
3. 接口的命名规则
- 我们创建接口的时候, 接口的命名一般以大写字母 I 开头。
- 接口的命名一般使用 "形容词" 词性的单词。
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性。
4. 实现多个接口
有时候我们需要一个类继承多个父类,但是在 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(); } }
5. 接口实现示例
给对象数组排序
在Arrays工具类中,sort 函数可以对普通数组进行排序,但是在如下代码中,如果使用 sort 方法进行排序,就会运行出错,抛出异常。
错误:
import java.util.Arrays; /** * 接口示例,对学生对象数组进行排序 */ public class Student implements Comparable<Student> { 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 的大小排序的数组:
6. Cloneable 接口和深、浅拷贝
6.1 Cloneable 接口
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。
拷贝分为浅拷贝和深拷贝,要注意它们的区别。
6.2 浅拷贝
可以从运行结果看出,改变了拷贝的值,随之原对象的值也发生了变化;同样的改变原对象的值,拷贝后的对象的值也发生了变化。 这是因为虽然 b1 和 b 是两个不同的对象,但他们内部包含的 a 对象却是相同的。开始时 a 的默认值都是0,将100赋给 b1 中的 a 时,b 中的 a 也就改变了,因为这两个对象中的 a 是同一个引用。
小结:浅拷贝就是当一个对象是通过另一个对象 clone 出来的,此时这两个对象虽然是独立的两个对象,但是这两个对象的内部包含的其他引用是相同的。
6.3 深拷贝
深拷贝就是当一个对象是通过另一个对象 clone 出来的,此时这两个对象是独立的两个对象,这两个对象所包含的所有其他引用也是独立的。
深拷贝的实现方式:
1. 嵌套实现 clone 方法
2. 序列化实现 clone 方法
现在开发中常见的序列化就是将一个对象转化成字符串( json),后续学习 Java web 时会用到。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。