目录
语法规则
例子
实现多个接口
接口之间的继承
抽象类和接口的区别
接口使用实例--Comparable接口
Clonable接口
浅拷贝
深拷贝
在现实生活中,接口的例子比比皆是,比如:电源插座,主机上的USB接口等。这些插口中可以插所有符合规范的设备。通过这个例子我们知道,接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
语法规则
通过关键字interface来定义一个接口。在创建接口时,接口的命名一般以大写字母I开头。
接口中的成员默认都是public static final的,接口中的成员方法默认都是public abstract的,所以一般都不写,保持代码简洁性。
interface IShape {
//public static final int a = 10;
int a = 0;
//public abstract void draw();
void draw();
}
在接口中,不可以有普通成员方法
java8开始,允许在接口当中定义一个default方法,这个方法可以有具体的实现
interface IShape {
int a = 0;
void draw();
default public void test() {
System.out.println("test()");
}
}
接口当中的方法,如果是static修饰的方法,那么可以有具体的实现
public static void test2() {
System.out.println("static方法");
}
接口当中不能有构造方法和代码块
接口不可以通过new关键字进行实例化
类和接口之间可以通过关键字implements来实现接口,且类中要重写接口中的抽象方法
class Rect implements IShape {
@Override
public void draw() {
System.out.println("矩形");
}
}
当一个类实现接口当中的方法之后,当前类中的方法不能不加public
接口可以发生向上转型,也可以发生动态绑定
public class Test8_4 {
public static void drawMap(IShape iShape) {
iShape.draw();
}
public static void main(String[] args) {
IShape iShape = new Rect();
drawMap(iShape);
drawMap(new Flower());
}
}
输出结果:
一个接口也会产生独立的字节码文件
例子
我们来看一个具体的例子:
新建一个名为IUSB的接口
public interface IUSB {
void openDevice();//打开服务
void closeDevice();//关闭服务
}
新建一个类Mouse,通过implements实现IUSB
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("点击鼠标");
}
}
新建一个类keyBoard类,通过implements实现IUSB
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("输入");
}
}
新建一个Computer类,类中分别有三个方法,分别是打开电脑、关闭电脑、使用服务
public class Computer {
public void powerOn() {
System.out.println("打开笔记本电脑");
}
public void powerOff() {
System.out.println("关闭笔记本电脑");
}
public void useDevice(IUSB iusb) {
//启动服务
iusb.openDevice();
if (iusb instanceof Mouse) {
//如果是鼠标调用 那么将iusb强转成Mouse类并调用click方法
Mouse mouse = (Mouse) iusb;
mouse.click();
} else if (iusb instanceof keyBoard) {
//如果是键盘调用 那么将iusb强转成keyBoard类并调用inPut方法
keyBoard keyBoard = (demo4.keyBoard) iusb;
keyBoard.inPut();
}
//关闭服务
iusb.closeDevice();
}
}
下面我们来测试一下useDevice方法
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();//打开电脑
computer.useDevice(new Mouse());//鼠标
computer.useDevice(new keyBoard());//键盘
computer.powerOff();//关闭电脑
}
你可以自己分析一下程序的输出结果是什么。
这里我直接给出运行结果:
实现多个接口
在java中,类和类之间是单继承的,一个类只能有一个父类,不支持多继承,但是一个类可以实现多个接口。我们认识了很多动物,有会飞的,会跑的,会游的,现在我们写一个代码来实现它。
新建一个IFlying接口
public interface IFlying {
void fly();
}
新建一个ISwimming接口
public interface ISwimming {
void swim();
}
新建一个IRunning接口
public interface IRunning {
void run();
}
新建一个Animal类,这是一个抽象类
public abstract class Animal {
public String name;
public int age;
public abstract void eat();
//构造方法
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
}
新建一个Dog类,继承Animal,并重写Animal中的抽象方法
public class Dog extends Animal{
@Override
public void eat() {
System.out.println(this.name+"吃狗粮");
}
//构造方法
public Dog(String name,int age) {
super(name, age);
}
}
对于狗来说,他会狗刨(游泳),会跑,他的功能不止一个,通过implements实现IRunning和ISwimming,并重写接口中的方法
public class Dog extends Animal implements IRunning,ISwimming{
@Override
public void eat() {
System.out.println(this.name+"吃狗粮");
}
//构造方法
public Dog(String name,int age) {
super(name, age);
}
@Override
public void run() {
System.out.println(this.name+"用狗腿跑");
}
@Override
public void swim() {
System.out.println(this.name+"正在狗刨");
}
}
【注】一定是先继承,再实现;在java中只能继承一个类,实现多个接口。
新建一个Bird类,继承Animal,实现IRunning,IFlying接口
public class Bird extends Animal implements IFlying,IRunning{
@Override
public void eat() {
System.out.println(this.name+"吃鸟食");
}
public Bird(String name,int age) {
super(name, age);
}
@Override
public void run() {
System.out.println(this.name+"用鸟腿跑");
}
@Override
public void fly() {
System.out.println(this.name+"正在飞");
}
}
新建一个测试类Test10_1
public class Test10_1 {
public static void test1(Animal animal) {
animal.eat();
}
public static void test2(IRunning iRunning) {
iRunning.run();
}
public static void test3(ISwimming iSwimming) {
iSwimming.swim();
}
public static void test4(IFlying iFlying) {
iFlying.fly();
}
public static void main(String[] args) {
Dog dog = new Dog("小狗",2);
Bird bird = new Bird("小鸟",1);
test1(bird);
test1(dog);
System.out.println("==========================");
//dog和bird都实现了IRunning
test2(bird);
test2(dog);
System.out.println("==========================");
//只有dog实现了ISwimming
test3(dog);
System.out.println("==========================");
//只有bird实现了IFlying
test4(bird);
System.out.println("==========================");
}
}
你可以自己分析一下输出结果是什么。
这里我直接给出结果:
现在我们再新建一个Robot类,它不属于动物,但有跑的功能
public class Robot implements IRunning{
@Override
public void run() {
System.out.println("机器人在跑");
}
}
public static void main(String[] args) {
test2(new Robot());
}
输出结果为:
只有具备功能即可,十分灵活。
通过上面这个例子,我们知道,继承表达的是is-a的语义,而接口表达的是具有xxx特性。
接口之间的继承
现在我们定义三个接口A、B、C
interface A {
void testA();
}
interface B {
void testB();
}
interface C {
void testC();
}
现有一个接口具备B和C接口的功能,难道要再新建一个接口D,把B、C的功能放进去吗?
interface D {
void testB();
void testC();
}
这样写不太合适,不能解决长久的问题,我们可以用关键字extends,此时extends意为拓展
interface D extends B,C{
//D这个接口具备了B和C的功能,同时D可以也定义属于自己的功能
void testD();
}
那么当一个类实现D接口时,要重写B、C、D的方法
public class Test10_2 implements D{
@Override
public void testB() {
System.out.println("B");
}
@Override
public void testC() {
System.out.println("C");
}
@Override
public void testD() {
System.out.println("D");
}
}
接口间的继承相当于把多个接口合并在一起。
抽象类和接口的区别
区别 | 抽象类(abstract) | 接口(interface) |
结构组成 | 普通类+抽象方法 | 抽象方法+全局常量 |
权限 | 各种权限 | public |
子类使用 | 使用extends关键字继承抽象类 | 使用implements关键字实现接口 |
关系 | 一个抽象类可以实现若干接口 | 接口不能继承抽象类,但是接口可以使用extends关键字继承多个父接口 |
子类权限 | 一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 |
接口使用实例--Comparable接口
现在我们有一个Student类
class Student implements Comparable<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 +
'}';
}
}
思考一下,如果想比较两个学生的年龄大小应该如何比较呢?难道跟以前学的一样直接比较吗?我们来试一下:
我们会发现代码报错了,因为我们的student1和student2不是基本数据类型,那么此时要想比较年龄大小,我们就需要借助接口Comparable了,让Student类实现Comparable接口
我们发现代码报错了,现在我们看一下Comparable的代码:
Comparable后面的<T>叫做泛型,这里你先记为固定写法,所以我们应该这样写:
class Student implements Comparable<Student>{
……
并在Student类中重写compareTo方法:
@Override
public int compareTo(Student o) {
//return this.age - o.age;
//谁调用compareTo方法 谁就是this
if (this.age > o.age) {
return 1;
}else if (this.age < o.age) {
return -1;
}else {
return 0;
}
}
此时我们就可以在main方法中进行比较了
public static void main(String[] args) {
Student student1 = new Student("张三",10);
Student student2 = new Student("李四",15);
System.out.println(student1.compareTo(student2));//student1这个对象和student2这个对象
//比较
}
自定义类型要想比较大小,要实现Comparable接口,并重写compareTo方法来实现比较的逻辑。
再举个例子,现在我们有一个Student数组,我们想要数组按照年龄大小排序,排序我们可以调用sort()方法,因为要进行比较,所以必须要实现Comparable接口,代码如下:
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("张三",10);
students[1] = new Student("李四",5);
students[2] = new Student("王五",18);
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
输出结果:
没有实现Comparable接口就运行代码的情况在这里就不做展示了,感兴趣的朋友可以自己动手试一下。
实现Comparable接口后,我们也可以重写一个冒泡排序方法:
public static void bubbleSort(Comparable[] comparable) {
for (int i = 0; i < comparable.length; i++) {
for (int j = 0; j < comparable.length-1-i; j++) {
if (comparable[j].compareTo(comparable[j+i]) > 0) {
Comparable temp = comparable[j];
comparable[j] = comparable[j+1];
comparable[j+1] = temp;
}
}
}
}
这个接口对类的侵入性比较强,如果我们现在想根据姓名排序,方法改了之后,前面实现的根据年龄比较的方法可能就实现不了了,因此我们也有另外一种比较方式---比较器。
如果我们先要根据年龄排序,可以定义一个类AgeComparator,这个类实现Comparator接口,Comparator中有一个方法,叫做compara,你只需要在AgeComparator类中重写这个方法即可。
class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
在main方法中我们需要实例化一个AgeComparator的对象,通过这个对象引用compara方法,比较学生年龄
public static void main(String[] args) {
Student student1 = new Student("张三",10);
Student student2 = new Student("李四",15);
AgeComparator ageComparator = new AgeComparator();
System.out.println(ageComparator.compare(student1, student2));
}
根据姓名比较就定义一个NameComparator类,实现Comparator接口,那么重写方法时,name是一个String类型,应该如何比较呢?我们来看一下String这个类
我们发现在源码中String实现的是Comparable接口,那么它一定实现了comparaTo方法
所以我们就可以通过String变量直接调用这个comparaTo方法
class NameComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public static void main(String[] args) {
Student student1 = new Student("zhangsan",10);
Student student2 = new Student("lisi",15);
NameComparator nameComparator = new NameComparator();
System.out.println(nameComparator.compare(student1, student2));//14
}
通过这个方法实现,两种比较方法互不干扰,非常灵活。
Clonable接口
我们新建一个Person类
class Person {
public int age;
public Person(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
}
并在main方法中实例化一个对象
public static void main(String[] args) {
Person person1 = new Person(10);
}
现在我们的要求是,能不能把person1指向的这个对象克隆一份呢?我们需要调用一个方法clone方法。但是如果我们直接在main方法中调是会报错的
因为我们现在没有clone()这个方法。那谁有呢?Object有。(可以看这篇博客简单了解一下http://t.csdnimg.cn/qKuOZ)
我们知道,Object是所有类的父类,那现在为什么不能调用呢?因为访问权限,protected修饰的只能在同一个包同一个类、同一个包不同类以及不同包中的子类中访问。且要使用super来访问父类的属性。那现在怎么办,我们只能重写clone方法:在Person类中右键-->Generate-->Override Methods-->
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
重写了方法之后,你会发现仍然是报错的,
这是什么原因呢,我们看到重写的clone()方法上
它抛出了一个异常叫做编译时异常,解决它很简单,鼠标放在报红的地方,Alt+Enter
点击这个此时代码是这样的
现在你会发现报红变长了,这又是另一个错误了。clone()方法的返回值类型是Object,但是你用Person类接受了,向下转型范围变小了,要进行强转,强转成Person。
现在代码就不报错了。
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person(10);
Person person2 = (Person) person1.clone();
System.out.println(person1);
System.out.println(person2);
}
此时当你运行你的代码时,你会发现又报错了。
他说不支持克隆,如果你自定义的类型想要进行克隆,那么一定要实现Cloneable接口
class Person implements Cloneable{
……
我们点开接口的代码会发现没有任何东西,那这个接口有什么用呢?这个Cloneable接口叫做空接口或者标记接口:证明当前类是可以克隆的。
这个时候我们再运行代码发现可以了。
此时,clone()方法就帮你完成了克隆。当然这里也会存在一个问题,就是深拷贝和浅拷贝。什么是深拷贝,什么是浅拷贝呢?
浅拷贝
现在我们添加一个Money类,将Money类和Person类组合
class Money {
public double money = 19.9;
}
class Person implements Cloneable{
public int age;
public Money m;
public Person(int age) {
this.age = age;
this.m = new Money();
}
……
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person(10);
Person person2 = (Person) person1.clone();
System.out.println(person1.m.money);
System.out.println(person2.m.money);
}
此时我们运行代码输出的是两个19.9。
person2.m.money = 99.99;
System.out.println(person1.m.money);
System.out.println(person2.m.money);
现在我们把克隆之后的money值改成99.99,再来输出结果,理论上我们希望输出19.9和99.99,但是我们发现程序输出并不是这样的
两个money的值都改了,这种情况就叫做浅拷贝。下面我们来分析一下他的原因。
现在这个现象就是浅拷贝,并没有将对象中的对象进行克隆。
深拷贝
深拷贝就是,我们希望把对象中的对象也拷贝一份。如何做到深拷贝呢?回到代码上,跟刚刚一样,我们需要在Money类中重写clone方法并实现cloneable接口
class Money implements Cloneable{
public double money = 19.9;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
接下来我们需要调用这个clone()方法,那么怎么调用呢?person2是在Person person2 = (Person) person1.clone();这条语句执行时克隆出来的,那么我们回到Person类的clone()方法中,我们定义一个temp接受克隆出来的对象。
现在我们想让person1所指向的m这个对象也克隆一份出来,怎么调用它的clone()方法呢?很简单,谁调用方法谁就是this,现在我们已经把m克隆了一份
把克隆的这份给temp.m
最后return temp把temp给person2
@Override
protected Object clone() throws CloneNotSupportedException {
Person temp = (Person) super.clone();
temp.m = (Money) this.m.clone();
return temp;
}
temp是一个局部变量,当调用完clone()方法temp就被自动回收了。
因此main方法中的代码不用改变,我们再来运行,结果就正确了
深拷贝和浅拷贝看的是代码的实现过程。