Java面向对象之抽象类与接口
⚡️前言⚡️
本篇文章为面向对象部分的第三篇文章,前两篇文章见链接包和继承、组合与多态。抽象类和接口都是继承关系中父类的更进一步,结合前两篇文章来阅读更易理解掌握。
🍉博客主页:****🍁【如风暖阳】🍁
🍉精品Java专栏【Javase】、【Java数据结构】
🍉欢迎点赞 👍 收藏 ⭐留言评论 📝私信必回哟😁🍉本文由 【如风暖阳】 原创,首发于 CSDN🙉
🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言
🍉博客中涉及源码及博主日常练习代码均已上传码云(gitee)
📍内容导读📍
🍅抽象类
🍓语法规则
在上一篇文章【组合与多态】打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个抽象方法(abstract method), 包含抽象方法的类我们称为**抽象类(abstract class)**。
注意事项:
1.什么是抽象方法:一个没有具体实现的方法,被abstract修饰
abstract class Shape { abstract public void draw(); }
2.抽象类不能被实例化(不能被new)
3.因为不能被实例化,所以这个抽象类只能被继承
4.抽象类当中,也可以包含和普通类一样的成员和方法(和普通的类一样就是多了一层验证)
5.一个普通类,继承了一个抽象类,那么这个普通类当中,需要重写这个抽象类的所有抽象方法
6.抽象类的最大作用就是为了被继承
7.一个抽象类A如果继承了一个抽象类B,那么这个抽象类A,可以不实现抽象父类B的抽象方法
8.结合第七点,当A类再次被一个普通类继承后,那么A和B这两个抽象类当中的所有抽象方法,必须被重写
9.抽象类和抽象方法不能被final,private修饰(互相矛盾,抽象类就是为了被继承和重写的而被这两个关键字修饰后不能再改变)
🍓抽象类的作用
抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验.
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,
使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就相当于常量嘛?
但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们. 充分利用编译器的校验, 在实际开发中是非常有意义的
🍅接口
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法和字段,而接口中包含的方法都是抽象方法, 字段只能包含静态常量.接口其实就是性能更专一且兼具多继承功能的抽象类
🍓注意事项
1.使用interface来修饰
interface IA {}
2.接口当中的普通方法,不能有具体的实现(非要实现只能通过关键字default来修饰这个方法,其作用和普通类相同)
3.接口当中可以有static方法
4.接口中所有方法默认都是public的
5.抽象方法默认是public abstract的
6.接口也不能够被实例化(接口时抽象类的更进一步,抽象类就不能被实例化,接口相同)
7.类和接口之间的关系是通过关键字implements实现的
8.当一个类实现了一个接口,就必须重写接口中的抽象方法
9.接口当中的成员变量,默认是public static final修饰的
interfaceIShape{voiddraw();//抽象方法,默认被public abstract修饰int num =10;//public static final int num = 10;}
10.当一个类实现一个接口后,重写这个方法的时候,这个方法前面必须加上public(因为接口中的抽象方法默认被public修饰,子类的重写方法的访问权限应该大于等于父类重写方法的访问权限)
一个错误的代码
interfaceIShape{abstractvoiddraw();// 即便不写public,也是public }classRectimplementsIShape{voiddraw(){//应该加上publicSystem.out.println("□");//权限更加严格了,所以无法覆写。}}
11.一个类可以通过关键字extends继承一个抽象类或者普通类,但是只能继承一个类;但是可以通过关键字implements实现多个接口,接口之间使用逗号隔开
12.类与类之间的关系通过extends操作,类与接口之间的关系通过implements操作,那么接口与接口之间会存在什么样的关系呢?
接口与接口之间可以使用extends来操作他们的关系,此时其意为:拓展(相当于就是类与类之间的继承,所以用extends关键字)
一个接口B通过extends来拓展另一个接口A的功能,此时当一个类C通过implements实现这个接口B的时候,此时重写的方法不仅仅是B的抽象方法,还有它从C接口拓展来的抽象方法
🍓理解接口
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.
现在我们通过类来表示一组动物.
classAnimal{protectedString name;publicAnimal(String name){this.name = name;}}
另外我们再提供一组接口, 分别表示 “会飞的”, “会跑的”, "会游泳的 ”
interfaceIFlying{voidfly();}interfaceIRunning{voidrun();}interfaceISwimming{voidswim();}
接下来我们创建几个具体的动物
猫, 是会跑的.
classCatextendsAnimalimplementsIRunning{publicCat(String name){super(name);}@Overridepublicvoidrun(){System.out.println(this.name +"正在用四条腿跑");}}
鱼, 是会游的
classFishextendsAnimalimplementsISwimming{publicFish(String name){super(name);}@Overridepublicvoidswim(){System.out.println(this.name +"正在用尾巴游泳");}}
青蛙, 既能跑, 又能游(两栖动物)
classFrogextendsAnimalimplementsIRunning,ISwimming{publicFrog(String name){super(name);}@Overridepublicvoidrun(){System.out.println(this.name +"正在往前跳");}@Overridepublicvoidswim(){System.out.println(this.name +"正在蹬腿游泳");}}
还有一种神奇的动物, 水陆空三栖, 叫做 “鸭子”
classDuckextendsAnimalimplementsIRunning,ISwimming,IFlying{publicDuck(String name){super(name);}@Overridepublicvoidfly(){System.out.println(this.name +"正在用翅膀飞");}@Overridepublicvoidrun(){System.out.println(this.name +"正在用两条腿跑");}@Overridepublicvoidswim(){System.out.println(this.name +"正在漂在水上");}}
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 .
猫是一种动物, 具有会跑的特性.
青蛙也是一种动物, 既能跑, 也能游泳
鸭子也是一种动物, 既能跑, 也能游, 还能飞
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力.
例如, 现在实现一个方法, 叫 “散步”
publicstaticvoidwalk(IRunning running){
running.run();}
在这个 walk 方法内部, 我们并不关注到底是哪种动物, 只要参数是会跑的就行
Cat cat =newCat("小猫");walk(cat);Frog frog =newFrog("小青蛙");walk(frog);// 执行结果
小猫正在用四条腿跑
小青蛙正在往前跳
甚至参数可以不是 “动物”, 只要会跑!
classRobotimplementsIRunning{privateString name;publicRobot(String name){this.name = name;}@Overridepublicvoidrun(){System.out.println(this.name +"正在用轮子跑");}}Robot robot =newRobot("机器人");walk(robot);// 执行结果
机器人正在用轮子跑
🍓常用两个接口
🍉Comparable
我们在对数组进行排序时使用以下代码:
importjava.util.Arrays;publicclassTest1{publicstaticvoidmain(String[] args){int[]arr={1,5,3,4,9};Arrays.sort(arr);System.out.println(Arrays.toString(arr));}}
但如果我们给对象数组排序,就不能仅仅通过这么简单的方法来实现排序了,接下来我们一起来看看如何对对象数组进行排序。
importjava.util.Arrays;classStudent{publicint age;publicdouble score;publicString name;publicStudent(int age,double score,String name){this.age = age;this.score = score;this.name = name;}@Override//这个重写方法时为了打印对象数组的具体成员publicStringtoString(){return"Student{"+"age="+ age +", score="+ score +", name='"+ name +'\''+'}';}}publicclassTest1{publicstaticvoidmain(String[] args){Student[] students=newStudent[3];
students[0]=newStudent(18,85.0,"A");
students[1]=newStudent(17,80.5,"B");
students[2]=newStudent(19,90.3,"C");System.out.println(Arrays.toString(students));Arrays.sort(students);System.out.println(Arrays.toString(students));}}
在对对象数组进行排序时发生以上异常,那么这是为什么呢?
因为再对象中有多个成员属性,在对对象数组进行排序时,不能确定对哪一个成员属性进行排列
所以我们引出了比较器来专门解决对象数组排序的问题,需要让类连接接口Comparable(注意接口后的<>中写比较的类型)并重写compareTo方法,实现对象数组的指定元素排序
classStudentimplementsComparable<Student>{publicint age;publicdouble score;publicString name;publicStudent(int age,double score,String name){this.age = age;this.score = score;this.name = name;}@OverridepublicStringtoString(){return"Student{"+"age="+ age +", score="+ score +", name='"+ name +'\''+'}';}======================================================================================@OverridepublicintcompareTo(Student o){if(this.age>o.age)return1;elseif(this.age==o.age)return0;elsereturn-1;}}
//运行结果:
=================================================================================
上边的重写方法也可以写成如下形式
@Override//升序publicintcompareTo(Student o){returnthis.age - o.age;}@Override//降序publicintcompareTo(Student o){returnthis.age - o.age;}
对于其中的compareTo方法,谁调用它谁就是this,传过去的参数就是o,如果返回的值大于0,表示this大于o;等于0,两者相等;小于0表示this小于o。如果需要降序排列,将this与o的位置换一换即可。
如果要进行字符串或者其他类的比较,需要调用类中所提供的compareTo方法进行比较,比如字符串:
=================================================================================@OverridepublicintcompareTo(Student o){returnthis.name.compareTo(o.name);}
按两个学生的年龄进行排序
publicclassTest1{publicstaticvoidmain(String[] args){Student student1=newStudent(18,85.0,"A");Student student2=newStudent(17,80.5,"B");System.out.println(student1.compareTo(student2));}}//运行结果:1
说明第一个学生的年龄比第二个学生的年龄大(o1.data>o2.data返回正数,o1.data==o2.data返回0,o1.data<o2.data返回负数)
所以,如果自定义类型的数据要进行大小的比较,一定要实现可以比较的接口,但是这个接口有个很大的缺点:对类的侵入性非常强,一旦写好了,不敢轻易改动,因此实际当中常常使用比较器Comparator,下面就来聊一聊Comparator。
🍉Comparator(比较器)
publicinterfaceComparator<T>{intcompare(T o1,T o2);...}
通过调用上边这个接口就可以实现对象数组中指定元素的比较,以下为代码实现,这样就将需要比较的类的属性从原类中抽取出来,对类的侵入性大大减小
importjava.util.Comparator;classAgeComparatorimplementsComparator<Student>{@Overridepublicintcompare(Student o1,Student o2){return o1.age-o2.age;}}classScoreComparatorimplementsComparator<Student>{@Overridepublicintcompare(Student o1,Student o2){return(int)(o1.score-o2.score);}}classNameComparatorimplementsComparator<Student>{@Overridepublicintcompare(Student o1,Student o2){return o1.name.compareTo(o2.name);}}classStudent{publicint age;publicdouble score;publicString name;publicStudent(int age,double score,String name){this.age = age;this.score = score;this.name = name;}@OverridepublicStringtoString(){return"Student{"+"age="+ age +", score="+ score +", name='"+ name +'\''+'}';}
我们先使用比较器来比较两个学生的年龄:
publicclassTest1{publicstaticvoidmain(String[] args){Student student1=newStudent(18,85.0,"A");Student student2=newStudent(17,80.5,"B");AgeComparator ageComparator =newAgeComparator();System.out.println(ageComparator.compare(student1,student2));}}//运行结果:1
说明第一个学生的年龄比第二个学生的年龄大(o1.data>o2.data返回正数,o1.data==o2.data返回0,o1.data<o2.data返回负数)
对学生数组排序:
publicclassTest1{publicstaticvoidmain(String[] args){Student[] students=newStudent[3];
students[0]=newStudent(18,85.0,"A");
students[1]=newStudent(17,80.5,"B");
students[2]=newStudent(19,90.3,"C");AgeComparator ageComparator =newAgeComparator();Arrays.sort(students,ageComparator);System.out.println("按年龄排序:");System.out.println(Arrays.toString(students));ScoreComparator scoreComparator=newScoreComparator();Arrays.sort(students,scoreComparator);System.out.println("按分数排序:");System.out.println(Arrays.toString(students));NameComparator nameComparator=newNameComparator();Arrays.sort(students,nameComparator);System.out.println("按姓名排序:");System.out.println(Arrays.toString(students));}}//默认排序为升序,如果需要降序排列,同样的道理,将o1与o2交换一下位置就好了。
运行结果:
与Comparable接口相比较,Comparator(比较器)更加灵活且对类的侵入性非常弱,但究竟使用哪一种接口,还需要根据具体的业务需求来决定。
⚡️最后的话⚡️
总结不易,希望uu们不要吝啬你们的👍哟(^U^)ノ~YO!!如有问题,欢迎评论区批评指正😁
版权归原作者 如风暖阳 所有, 如有侵权,请联系我们删除。