文章目录
1. 接口的概念
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
2. 语法规则
接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。
public interface 接口名称{
// 抽象方法
// public abstract 是固定搭配,可以不写
public abstract void method1(); //1
public void method2();//2
abstract void method3();//3
void method4();//4
// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}
- 创建接口时, 接口的命名一般以大写字母 I 开头.
- 接口的命名一般使用 “形容词”(able) 词性的单词.
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
3. 接口使用
接口不能直接使用,必须要有一个 “实现类” 来 “实现” 该接口,实现接口中的所有抽象方法。
public class 类名称 implements 接口名称{
// ...
}
注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。
请实现笔记本电脑使用USB鼠标、USB键盘的例子
- USB接口:包含打开设备、关闭设备功能
- 笔记本类:包含开机功能、关机功能、使用USB设备功能
- 鼠标类:实现USB接口,并具备点击功能
- 键盘类:实现USB接口,并具备输入功能
Computer类:
public class Computer{
public void powerOn(){
System.out.println("开机.....");
}
public void powerDrown(){
System.out.println("关机....");
}
//使用USB服务的,一定是实现USB接口的,那么就一定能使用USB来接收
public void usbDevice(IUSB usb){
usb.openDevice();
if(usb instanceof KeyBoard){
KeyBoard keyBoard = new KeyBoard();
keyBoard.inPut();
}else if(usb instanceof Mouse){
Mouse mouse = new Mouse();
mouse.click();
}
usb.closeDevice();
}
}
IUSB接口:
public interface IUSB {
void openDevice();
void closeDevice();
}
KeyBoard类:
public class KeyBoard implements IUSB{
@Override
public void openDevice() {
System.out.println("打开键盘....");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘....");
}
public void inPut(){
System.out.println("键盘输入.....");
}
}
Mouse类:
public class Mouse implements IUSB{
@Override
public void openDevice() {
System.out.println("打开鼠标.....");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标.....");
}
public void click(){
System.out.println("鼠标点击.....");
}
}
Test类:
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
//使用鼠标设备
computer.usbDevice(new Mouse());
//使用键盘设备
computer.usbDevice(new KeyBoard());
computer.powerDrown();
}
}
4. 接口特性
- 接口类型是一种引用类型,但是不能直接new接口的对象
public class Test {
public static void main(String[] args) {
IUSB iusb = new IUSB();
}
}
输出结果:java: demo1.IUSB是抽象的; 无法实例化
- 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是public abstract,其他修饰符都会报错)
public interface USB {
// Error:(4, 18) java: 此处不允许使用修饰符private
private void openDevice();
void closeDevice();
}
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
public interface USB {
void openDevice();
// 编译失败:因为接口中的方式默认为抽象方法
// Error:(5, 23) java: 接口抽象方法不能带有主体
void closeDevice(){
System.out.println("关闭USB设备");
}
}
- 重写接口中方法时,不能使用默认的访问权限
public interface USB {
void openDevice(); // 默认是public的
void closeDevice(); // 默认是public的
}
public class Mouse implements USB {
@Override
void openDevice() {
System.out.println("打开鼠标");
}
// ...
}
// 编译报错,重写USB中openDevice方法时,不能使用默认修饰符
// 正在尝试分配更低的访问权限; 以前为public
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
public interface USB {
double brand = 3.0; // 默认被:final public static修饰
void openDevice();
void closeDevice();
}
public class TestUSB {
public static void main(String[] args) {
System.out.println(USB.brand); // 可以直接通过接口名访问,说明是静态的
// 编译报错:Error:(12, 12) java: 无法为最终变量brand分配值
USB.brand = 2.0; // 说明brand具有final属性
}
}
- 接口中不能有静态代码块和构造方法
public interface USB {
// 编译失败
public USB(){
}
{} // 编译失败
void openDevice();
void closeDevice();
}
- 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
- 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
- jdk8中:接口中还可以包含default方法。
- 接口当中不能有普通方法的实现。
5. 实现多个接口
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物。
// IFlying接口:
public interface IFlying {
public void fly();
}
// IRunning接口:
public interface IRunning {
public void run();
}
//ISwimming接口:
public interface ISwimming {
public void swim();
}
Animal类:
public class Animal {
public String name;
public Animal(String name){
this.name = name;
}
public void eat(){
System.out.println(this.name + "正在吃饭....");
}
}
Cat类:
public class Cat extends Animal implements IRunning{
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃猫粮....");
}
@Override
public void run() {
System.out.println(this.name + "正在跑.....");
}
}
Dog类:
public class Dog extends Animal implements IRunning, ISwimming{
public Dog(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃狗粮....");
}
@Override
public void run() {
System.out.println(this.name + "正在跑....");
}
@Override
public void swim() {
System.out.println(this.name + "正在游泳.....");
}
}
Duck类:
public class Duck extends Animal implements IFlying, ISwimming, IRunning{
public Duck(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃鸭粮......");
}
@Override
public void fly() {
System.out.println(this.name + "正在飞....");
}
@Override
public void run() {
System.out.println(this.name + "正在跑....");
}
@Override
public void swim() {
System.out.println(this.name + "正在游....");
}
}
Test类:
public class Test {
public static void eat(Animal animal){
animal.eat();
}
public static void swim(ISwimming iSwimming){
iSwimming.swim();
}
public static void fly(IFlying iFlying){
iFlying.fly();
}
public static void main(String[] args) {
Dog dog = new Dog("辣条");
Cat cat = new Cat("咪咪");
Duck duck = new Duck("小黄鸭");
eat(dog);
eat(cat);
eat(duck);
swim(dog);
swim(duck);
fly(duck);
}
}
注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。
提示, IDEA 中使用 ctrl + i 快速实现接口
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 .
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力.
6. 接口之间的继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字.
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
...
}
通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”. 此时实现接口创建的 Frog 类, 就继续要实现 run 方法, 也需要实现 swim 方法.
接口间的继承相当于把多个接口合并在一起
7. 接口使用实例
实例1:给对象数组排序
Student类:
public class Student{
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Test类:
public class Test {
public static void main(String[] args) {
Student student1 = new Student("小步", 12);
Student student2 = new Student("小高", 11);
// 要比较student1 和student2 的大小,不能使用 > 或者 < 来比较
// 使用 > 和 < 号来比较,比较的是student1引用和student2引用里面存放的地址。
// Java当中的引用不能直接通过大于 小于来比较
// if(student1 > student2){
//
// }
}
}
要比较这两个对象的大小,首先考虑的是:究竟是根据这两个对象的姓名比较还是根据这两个对象的年龄来比较。所以当两个对象进行比较的时候,我们想要有一个具体的比较规则。要想让两个对象比较,可比较,能够按照指定的方式来进行比较。我们就要实现一个接口 Comparable。
Student类:
public class Student implements Comparable<Student>{
//只要这个类实现了compareable接口,那它就具备了这个对象比较大小的资格
//这个<>中的类型参数是什么,就代表要比较的是什么
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//要实现Comparable接口就要重写compareTo方法
@Override
public int compareTo(Student o) {
//compareTo方法中的具体实现要按照具体的你想要比较的规则来实现
//按照年龄比较
// if(this.age > o.age){
// return 1;
// } else if (this.age == o.age) {
// return 0;
// }else {
// return -1;
// }
//优化后:
return this.age-o.age;
}
}
Test类:
public class Test {
public static void main(String[] args) {
Student student1 = new Student("小步", 12);
Student student2 = new Student("小高", 11);
// 此时是student1调用的compareTo()
// 所以此时在student类中的this就是student1,o就是student2
if(student1.compareTo(student2) > 0){
System.out.println("student1 > student2");
} else if (student1.compareTo(student2) == 0) {
System.out.println("student1 == student2");
}else {
System.out.println("student1 < student2");
}
}
}
Comparable接口当中只有一个方法 comparaTo()
假如还是上面未实现Comparable接口的学生类,现在有下面一段代码来实现学生数组的一个排序:
Test类:
public class Test {
public static void main(String[] args) {
Student student1 = new Student("小步", 12);
Student student2 = new Student("小高", 11);
Student student3 = new Student("小王", 13);
Student[] students = new Student[]{
student1, student2, student3
};
System.out.println(Arrays.toString(students));
//调用Arrays.sort()方法进行排序
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
此时这个students数组中存放的是三个学生对象,此时使用Arrays.sort()来进行排序的时候,并没有比较的规则,此时,程序会报错
显示类型转换异常,灰色的部分是源码报错的地方,蓝色的部分是当前你写的源代码报的异常。
编译器的蓝色部分提示我们Test.java的第22行Arrays.sort()出问题了,点进去Arrays.sort()的源码,并且灰色部分提示我们320行出了问题。。
此时并不能将student强转成Comparable接口,student并没有实现Comparable接口,所以代码就出现了类型转换错误。
下面我们来模拟实现一个sort()方法:
Student类:
public class Student implements Comparable<Student>{
//只要这个类实现了compareable接口,那它就具备了这个对象比较大小的资格
//这个<>中的类型参数是什么,就代表要比较的是什么
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//要实现Comparable接口就要重写compareTo方法
@Override
public int compareTo(Student o) {
//compareTo方法中的具体实现要按照具体的你想要比较的规则来实现
//按照年龄比较
// if(this.age > o.age){
// return 1;
// } else if (this.age == o.age) {
// return 0;
// }else {
// return -1;
// }
//优化后:
return this.age-o.age;
}
}
Test类:
public class Test {
//传一个数组,可以用Comparable来接收
public static void bubbleSort(Comparable[] comparables){
for (int i = 0; i < comparables.length-1; i++) {
for (int j = 0; j < comparables.length-i-1; j++) {
if(comparables[j].compareTo(comparables[j+1]) > 0){
Comparable tmp = comparables[j];
comparables[j] = comparables[j+1];
comparables[j+1] = tmp;
}
}
}
}
public static void main(String[] args) {
Student student1 = new Student("小步", 12);
Student student2 = new Student("小高", 11);
Student student3 = new Student("小王", 13);
Student[] students = new Student[]{student1, student2, student3};
System.out.println(Arrays.toString(students));
//调用Arrays.sort()方法
// Arrays.sort(students);
bubbleSort(students);
System.out.println(Arrays.toString(students));
}
}
输出结果:
[Student{name='小步', age=12}, Student{name='小高', age=11}, Student{name='小王', age=13}]
[Student{name='小高', age=11}, Student{name='小步', age=12}, Student{name='小王', age=13}]
问:如果想要实现从大到小排序呢?
答:只需要将 if(comparables[j].compareTo(comparables[j+1]) > 0){
改成 if(comparables[j+1].compareTo(comparables[j]) > 0){
即可。
或者将 return this.age-o.age;
改成 return o.age - this.age;
。
问:如果是要根据姓名大小进行排序呢?
答:只需要将重写的compareTo()方法中的实现变成 return this.name.compareTo(o.name);
。
那为什么这里的name是String类型的,是一个引用类型,却可以直接使用compareTo()方法呢?
我们点进去String的源码,可以看到它实现了Compareble接口。
虽然但是看着我们实现的代码风平浪静,但是它是有非常大的隐患的。
我们在重写的comparaTo方法中的改成 o.age - this.age
了,有很多这个类的调用者,都是按照这个compareTo调用并按照这个业务逻辑来实现的,如果有一天,我们需要根据姓名来比较,我偷偷的将原来的 o.age - this.age
屏蔽掉,然后改成 return this.name.compareTo(o.name);
此时,改完之后就会可能会影响原来的业务逻辑。
所以一个类实现Comparable接口,在这个类当中重写CompareTo方法在一般情况下,只是起到了一种默认的比较方式。也就是说,一旦是写好了 o.age - this.age
,就以后不要变了,这个类一旦被启用,实现整个业务逻辑时都会使用这样的比较方式,如果改变compareTo中的比较方式,可能会影响在这之前所有的代码。所有,Comparable的使用是有局限性的。
8. Comparator
AgeComparator类:
public class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
定义这个类的目的就是想要实现根据年龄来比较大小的功能,这里实现了Comparator接口,重写了compare方法 。
那么此时就可以实现下列代码来使用这个比较方法:
public static void main(String[] args) {
Student student1 = new Student("小步", 12);
Student student2 = new Student("小高", 11);
AgeComparator ageComparator = new AgeComparator();
if(ageComparator.compare(student1, student2) > 0){
System.out.println("student1 > student2");
} else if (ageComparator.compare(student1, student2) == 0) {
System.out.println("student1 == student2");
}else {
System.out.println("student1 < student2");
}
}
同样也可以实现根据姓名比较大小的功能:
public class NameComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
8.1 Clonable接口和深拷贝
Object类中有一个clone方法:
这个方法是由native修饰的,代表这个方法的底层代码是由C++代码实现的。
另外这个方法的修饰限定符是protected修饰的,代表这个方法只能在同一个包中访问,而这个Object类是在java.lang这个包下面的,我所定义的Person是在demo1这个包下面的,处于两个不同包,要想访问只能子类调用,Person就是Object的子类,Object是所有类的父类。
Person类:
public class Person implements Cloneable{
public String name;
public Person(String name) {
this.name = name;
}
//重写clone方法,要不然对象person点不出来clone,clone 是父类Object的方法
@Override
protected Object clone()
throws CloneNotSupportedException {
// 不支持克隆异常:当在调用clone方法时,有可能这个方法不支持克隆,
// 需要在编译的时候解决这个异常
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
Test类:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException{
Person person = new Person("zhangsan");
Person person1 = (Person) person.clone();
// 1.强制类型转换:重写的clone方法的返回值是Object类型的。
// 2.解决当前类是否支持克隆----实现Cloneable接口
// 3.解决编译时异常----throws CloneNotSupportedException
}
}
这个Cloneable接口中并没有方法,是一个空接口。
问:空接口有什么作用?
答:标记当前类是可以被克隆的。
Money类:
public class Money {
public Double money = 9.9;
}
Person类:
public class Person implements Cloneable{
public String name;
public Money money;
public Person(String name) {
this.name = name;
money = new Money();
}
//重写clone方法,要不然对象person点不出来clone,clone 是父类Object的方法
@Override
protected Object clone()
throws CloneNotSupportedException {
// 不支持克隆异常:当在调用clone方法时,有可能这个方法不支持克隆,
// 需要在编译的时候解决这个异常
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
Test类:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException{
Person person = new Person("zhangsan");
Person person1 = (Person) person.clone();
System.out.println("修改前:");
System.out.println(person.money.money);
System.out.println(person1.money.money);
person1.money.money = 521.0;
System.out.println("修改后:");
System.out.println(person.money.money);
System.out.println(person1.money.money);
}
}
输出结果: 修改前:
9.9
9.9
修改后:
521.0
521.0
此时的克隆,只是把当前对象克隆了,但是没克隆money.所以此时money这个对象被两个引用同时指向。那么此时通过person1把money的9.9改掉,那么此时person中的money也会被改掉。
我们 把这种拷贝 叫做 浅拷贝。
Money类:
public class Money implements Cloneable{
public Double money = 9.9;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Person implements Cloneable{
public String name;
public Money money;
public Person(String name) {
this.name = name;
money = new Money();
}
//重写clone方法,要不然对象person点不出来clone,clone 是父类Object的方法
@Override
protected Object clone()
throws CloneNotSupportedException {
Person tmp = (Person) super.clone();
tmp.money = (Money) this.money.clone();
//要想能够实现 money 的 clone 就必须在Money类中实现Cloneable接口,并重写clone方法
return tmp;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
Test类:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException{
Person person = new Person("zhangsan");
Person person1 = (Person) person.clone();
System.out.println("修改前:");
System.out.println(person.money.money);
System.out.println(person1.money.money);
person1.money.money = 521.0;
System.out.println("修改后:");
System.out.println(person.money.money);
System.out.println(person1.money.money);
}
}
输出结果:修改前:
9.9
9.9
修改后:
9.9
521.0
当person调用clone时,来到了Person类中的clone方法,此时会创建一个临时的tmp对象来作为接收,利用这个临时接收的tmp对象来实现拷贝一份新的money对象,然后返回这个tmp对象,此时person1接收了这个临时对象,与此同时,tmp是一个局部变量,Person类中的clone方法走完了之后,创建的临时对象tmp的内存就会被回收。
此时,这个拷贝 就是深拷贝。