前言:向对象编程(OO,ObjectOriented,面向对象)是以对象为中心,以类和继承为构造机制的软件开发系统方法,是20世纪90年代软件开发方法的主流。到了现在,他依然是我们学习Java路上的一个必经之路。
在上一篇博客中,组合、多态、抽象类已经讲完了,本篇我们来介绍接口这个麻烦玩意。
每日一图:
面向对象编程:- 一.接口
- 1.语法规则
- 第一点
- 第二点
- 第三点
- 第四点
- 第五点
- 第六点
- 第七点
- 第八点
- 第九点
- 2.实现多个接口
- 3.接口使用实例
- ①Comparable
- ②Comparator
- ③Clonable接口
- 6.总结
概念:接口是抽象类的更进一步。抽象类中还可以包含非抽象方法,和字段。而接口中包含的方法都是抽象方法,字段只能包含静态常量。
1.语法规则
首先我们来了解接口的语法规则,对于接口来说,我们有以下的点:
第一点接口是使用interface来修饰的。比如:interface IA{}。
像其他语法一样,接口也有其特有的修饰词,也就是interface。
//例子 interface IA { void draw(); }
第二点
接口当中的普通方法,不能有具体的实现。如果非要实现,要通过关键字default来修饰这个方法,也可以有static的方法。
这里的意思就是说,对于接口中,普通的方法是不能具体的实现的,比如这样子的,他就会报错:
interface IA{ public void fun(){ //error:Interface abstract methods cannot have body //翻译:接口抽象方法不能有主体 } }
但是它可以允许有默认的方法和静态方法以及没用具体实现的普通方法,也就是用default修饰的方法或者是static定义的方法,比如:
interface IA{ public void fun(); //普通无实现方法 public abstract void func(); //抽象方法 default public void func1(){ System.out.println("hello!"); //default修饰的方法 意味着接口默认的方法 不能被重写 } public static void func2(){ System.out.println("hello!"); //static定义的方法 也不能被重写 只有IA自己调用的这些方法 } }
上面的代码都是没有报错的:
第三点
接口当中,里面所有的方法都是public的,而抽象方法是public abstaract的。
大家有注意的都会看见,上面的代码除了标注释的,也有一些代码是灰色的,这种就是默认代码,就算你没写他也是默认是这个。
所以当你写这些方法的时候,有时候也可以省略,但最后还是保留,让代码更具完整性,然后可以更具可读性。
第四点
接口是不可以被通过关键字new来实例化的。
对于接口,本身就是抽象类更进一步的抽象,抽象类都不可以被实例化了,抽象更抽象当然不可以去实例化,我们也可以在IDEA中尝试一下:
public static void main(String[] args) { IA ia = new IA(); //error :'IA' is abstract; cannot be instantiated //翻译:IA 是抽象的;不能实例化 }
第五点
类和接口之间的关系通过implements来实现的,一个类实现一个接口,就必须重写接口当中的抽象方法。
对于前面几点,这个接口又是抽象类更进一步的抽象,不能实例化,又不能实现具体方法,那要这个接口干嘛,这就到了接口和类的关系了:
类通过implements来实现连接接口,而且类去连接这个接口,需要重写接口当中所有的抽象方法。
//例子 interface IA{ public abstract void func(); //抽象方法 } class Rect implements IA{ @Override public void func() { System.out.println("hello!"); } //如果不重写就会报错 //类“rect”必须声明为抽象,或者在‘IA’中实现抽象方法func() }
那么现在就可以去创建一个接口的引用,对应到一个子类的实例,就可以实现了,这时候我们就可以借助类去实例化了。
class Rect implements IA{ @Override public void func() { System.out.println("hello!"); } } public class TestDemo { public static void main(String[] args) { IA ia = new Rect(); ia.func(); //打印hello! } }
同时我们也可以实现多态,也可以发生多态绑定的:
class Rect implements IA{ @Override public void func() { System.out.println("hello!"); } } class Flower implements IA{ @Override public void func() { System.out.println("world!"); } } public class TestDemo { public static void drawMap(IA ia){ ia.func(); } public static void main(String[] args) { Rect rect = new Rect(); Flower flower = new Flower(); drawMap(rect); drawMap(flower); } }
第六点
接口类当中的成员变量默认是public static final修饰的。
当我们直接在接口中定义成员变量的时候,如果我们没用赋值,就会报错,在其他地方,即使是局部变量也是需要初始化的:
interface IA{ public int a ; //error:Variable 'a' might not have been initialized(未赋初值) }
而在接口中的不同点就是变量默认是public static final修饰的,是独一份的。
第七点
当一个类实现一个接口之后,重写这个方法的时候,这个方法前面必须加上public,因为重写中,子类的方法权限一定要大于等于父类。
我们上面说到,我们的接口方法不加修饰的时候,默认是public修饰的,所以这也是相当于父类是以public修饰的方法,子类重写的时候只能等于父类的public权限了。
试图分配较弱的访问权限(“包-私有”);是公开的
interface IA{ public int a =10; public abstract void func(); //抽象方法 } class Rect implements IA{ @Override void func() { //error:“Rect”与“demo1”中的“func()”冲突。‘IA’;试图分配较弱的访问权限(“包-私有”) System.out.println("hello!"); } }
所以我们必须在重写方法的前面加上public。
第八点
一个类可以通过关键字extends继承一个抽象类或者普通类,但是只能继承一个类。同时,也可以通过implements实现多个接口,接口之间用逗号隔开就好。
这里我们说明一下:一个类,是可以同时继承类然后同时实现接口的,但是顺序不能改变,先写继承,再写接口:
interface IA{ public int a =10; public abstract void func(); } interface IB{ public abstract void func5(); } class hhh{ //父类 public void func6(){ System.out.println("测试来了!"); } } class Rect extends hhh implements IA,IB{ //先继承 后接口 而且这里有IA,IB两个接口 @Override public void func() { System.out.println("hello!"); } @Override public void func5() { System.out.println("hello!"); //所以要重写IA和IB两个接口里面的抽象方法 } }
这就是接口和类之间的联系。
第九点
接口和接口之间,可以使用extends来 *** 作他们的关系,此时这里面的意思是:拓展。
对于两个接口来说,一个接口B通过extends 来拓展另一个接口C的功能。此时当一个类D通过implements实现这个接口B的时候,此时重写的不仅仅是B的抽象方法,还要他从C接口拓展来的方法。
也就是说是接口嘛就是接上了就拥有了里面的东西,所以我们通过extends来 *** 作接口之间,就是相当于拓展了接口里面的东西。
interface IA{ public int a =10; public abstract void func(); } interface IB extends IA{ //接口A来拓展接口B public abstract void func5(); } class wet implements IB{ //在实现的时候需要把拓展的A的方法也实现 @Override public void func() { } @Override public void func5() { } }
前面这些就是对于接口一些晦涩难懂的知识点和语法,实际上这些知识点并不是说你看一次或者写下记下就可以的,需要大量的代码去理解运用,然后慢慢掌握。从完全不懂到听懂到理解,这需要时间。
2.实现多个接口
前面我们写了那么长一段东西都是语法,虽然也举例子了,但是还是不能切身体会到,接口到底是拿来干嘛的,接下来我们来学习一些接口的用处:
首先我们写一个父类Animal:
class Animal{ protected String name; //定义名字 public Animal(String name){ this.name = name;//构造方法 } }
然后我们定义一个子类Brid:
class Dog extends Animal{ public Dog(String name){ super(name); } }
这时候我们会想一个东西,这只鸟好像会的东西有点少,能不能加点东西给它,比如飞,那我们在哪里加呢,在Animal里面加吗,那所以Animal都会跑吗,比如狗,会飞吗,还有猪,会飞吗?所以这个动物的一些特性,是不具普遍性的,这时候我们就可以用接口:
class Animal{ protected String name; public Animal(String name){ this.name = name; } } interface iFlying{ void fiy();//接口 让鸟可以飞 } class Dog extends Animal implements iFlying{ public Bird(String name){ super(name); } @Override public void fiy() { System.out.println(this.name + "正在飞!");//重写 } }
在这里,我们因为并不是所有的动物都会飞,所有我们并不能直接写进Animal中去,如果我们想写另一个类来实现,要考虑到一个类只能继承一个类,这时候就有接口去解决我们Java中只能继承一个类的缺陷,从而实现相当于继承多个类的功能。
我们再来创建几个会跑的,会游的:
interface IRunning { void run(); } interface ISwimming { void swim(); }
然后创建动物,猫是会跑步的,然后青蛙是又会游泳又会跑步的:
class Cat extends Animal implements IRunning { public Cat(String name) { super(name); } @Override public void run() { System.out.println(this.name + "正在用四条腿跑"); } } class Frog extends Animal implements IRunning, ISwimming { public Frog(String name) { super(name); } @Override public void run() { System.out.println(this.name + "正在往前跳"); } @Override public void swim() { System.out.println(this.name + "正在蹬腿游泳"); } } //小技巧:IDEA中快捷键ctrl+i可以快速导入接口的构造方法
所以这里就完完全全体现了接口的用处,可以实现多个接口帮助一个类完成它独特的功能,然后在上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口。
这就是说:
继承表达的含义是 is - a 语义,而接口表达的含义是 具有 xxx 特性。
比如说:
猫是一种动物,具有会跑的特性。
青蛙也是一种动物,既能跑,也能游泳。
鸭子也是一种动物,既能跑,也能游, 还能飞。
而这个设计的好处是大大滴有!! 时刻牢记多态的好处,让程序猿不需要记住类型。有了接口之后,类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力。
更多的例子:
class Robot implements IRunning { private String name; public Robot(String name) { this.name = name; } @Override public void run() { System.out.println(this.name + "正在用轮子跑"); } } Robot robot = new Robot("机器人"); walk(robot); // 执行结果 //机器人正在用轮子跑
3.接口使用实例
上面我们已经了解到了接口的用处和用法,现在我们来学习一下三个常用接口,看看接口的实际使用到底有哪些妙处。
①Comparable首先我们先不用管Comparable是什么,我们先看一下下面的代码,当我们要进行排序的时候,是不是可以用排序方法了进行排序:
public static void main(String[] args) { int [] a = {3,1,2}; System.out.println(Arrays.toString(a)); Arrays.sort(a); System.out.println(Arrays.toString(a)); //输出结果:3,1,2 //输出结果:1,2,3 }
那我有时候需要排序的并不是整数,而是一个类,类里面有很多的东西,我进行比较的时候用 Arrays.sort();还可以吗,那么它是以哪个作为排序的比较量呢?比如说:
class Student { public int age;//年龄 public String name;//姓名 public double score;//成绩 public Student(int age, String name, double score) { this.age = age; this.name = name; this.score = score; } @Override public String toString() { return "Student{" + "age=" + age + ", name='" + name + ''' + ", score=" + score + '}'; } } public class Test { public static void main(String[] args) { Student[] students = new Student[3]; students[0] = new Student(12, "bit", 98.9); students[1] = new Student(6, "abc", 88.9); students[2] = new Student(18, "zhangsan", 18.9); System.out.println(Arrays.toString(students)); Arrays.sort(students);//比较的时候拿什么比? System.out.println(Arrays.toString(students)); //error } }
那么这时候会显示一堆的错误信息,简单来说就是Arrays.sort();方法不知道你按照哪个量去进行排序,这时候就错误信息了。
那么这时候就可以提出我们的Comparable接口了,在我们的类中接上一个Comparable接口,并且格式是实现Comparable<类名>,并实现Comparable接口的方法:
class Student implements Comparable{ //实现 Comparable 接口 public int age; public String name; public double score; public Student(int age, String name, double score) { this.age = age; this.name = name; this.score = score; } @Override public String toString() { return "Student{" + "age=" + age + ", name='" + name + ''' + ", score=" + score + '}'; } @Override public int compareTo(Student o) { //实现 Comparable 接口的方法 if(this.age > o.age) { return 1; }else if(this.age == o.age) { return 0; }else { return -1; } } } public class Test { public static void main(String[] args) { Student[] students = new Student[3]; students[0] = new Student(12, "bit", 98.9); students[1] = new Student(6, "abc", 88.9); students[2] = new Student(18, "zhangsan", 18.9); System.out.println(Arrays.toString(students)); Arrays.sort(students);//默认是从小到大的排序 System.out.println(Arrays.toString(students)); } }
解答1:Comparable接口中的方法是什么?
我们可以在IDEA中查看一下其底层代码,不难看出,在Comparable接口中只有一个实现的方法compareTo:
解答2:Comparable接口中的方法是怎么实现的?
在上面的 sort 方法中会自动调用 compareTo 方法,compareTo 的参数是 Object,其实传入的就是 Student 类型的对象。
然后比较当前对象和参数对象的大小关系(按年龄来算)。
如果当前对象应排在参数对象之前, 返回小于 0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0。
public int compareTo(Student o) { //实现 Comparable 接口的方法 if(this.age > o.age) { return 1; }else if(this.age == o.age) { return 0; }else { return -1; } }
注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则。
②Comparator
所以如果自定义类型去进行大小比较,一定要实现可比较的接口。但是这个接口有一个很大的确定就是对类的侵入性太强,一旦写好,不能轻易改动。
比如说我突然想比较的不是年龄,去比较成绩,那我需要重写方法,再比如我比较的是姓名,那就要重写并且寻找比较String类型的接口了。
所以当我们这个时候就可以写一些类去接这接口,然后再实例化这个类,因为Arrays.sort当中,有一种比较方法是可以接收这种类的参数的,比如说我比较的是年龄:
class AgeComparator implements Comparator{ @Override public int compare(Student o1, Student o2) { return o1.age-o2.age;//重写接口Comparator方法 } } public class Test { public static void main(String[] args) { Student[] students = new Student[3]; students[0] = new Student(12, "bit", 98.9); students[1] = new Student(6, "abc", 88.9); students[2] = new Student(18, "zhangsan", 18.9); AgeComparator ageComparator = new AgeComparator(); //实例化这个类 System.out.println(Arrays.toString(students)); Arrays.sort(students,ageComparator); //有一种方法可以比较中放入Comparator接口 System.out.println(Arrays.toString(students)); } }
当然其他两个量也是可以的:
class ScoreComparator implements Comparator{ @Override public int compare(Student o1, Student o2) { return (int)(o1.score-o2.score); } } class NameComparator implements Comparator { @Override public int compare(Student o1, Student o2) { return o1.name.compareTo(o2.name); } }
虽然这里我们要多写一些代码,但是这样子对类的侵入性非常的小,我们只需要写我们需要的接口就可以去进行比较,不需要改变比较类中的东西。总结起来就是:灵活,对类的侵入性小。
③Clonable接口
Java 中内置了一些很有用的接口, Clonable 就是其中之一。
Object 类中存在一个 clone 方法,调用这个方法可以创建一个对象的 “拷贝”。
但是要想合法调用 clone 方法,必须要先实现 Clonable 接口,否则就会抛出 CloneNotSupportedException 异常。
class Animal implements Cloneable { private String name; @Override public Animal clone() { Animal o = null; try { o = (Animal) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; } } public class TestDemo { public static void main(String[] args) { Animal animal = new Animal(); Animal animal2 = animal.clone(); System.out.println(animal == animal2); } } //输出false
6.总结
抽象类和接口都是 Java 中多态的常见使用方式.。都需要重点掌握,同时又要认清两者的区别。
核心区别: 抽象类中可以包含普通方法和普通字段,这样的普通方法和字段可以被子类直接使用(不必重写),而接口中不能包含普通方法,子类必须重写所有的抽象方法。
区别列表:
这就是本篇Java中的面向对象编程3的全部内容啦,关于面向对象的学习,就学习到这里了,接下来我们要做一个系统来巩固一下我们之前学到的继承多态接口等内容!!欢迎关注。一起学习,共同努力!也可以期待这个系列接下来的博客噢。
链接:都在这里! Java SE 带你从零到一系列
还有一件事:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)