本文主要是介绍【Java SE】抽象类和接口 保姆级细致教学,深入理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 抽象类
- 抽象类的概念
- 抽象类的语法
- 抽象类的特性
- 抽象类的作用
- 接口
- 接口的概念
- 接口的语法
- 接口的使用规则
- 实现多个接口
- 接口之间的继承,多继承
- 接口使用实例-数组排序
- Comparable接口的使用
- Comparator 接口的使用
- equals的使用
- equals与compareTo的区别
- 抽象类和接口的异同
抽象类
抽象类的概念
什么是抽象类呢? 嗷就是这个类它很抽象,结束!(bushi
当一个类没有足够的信息来描绘它的对象时,也就是不能实例化出具体的对象时,我们称之为抽象类。
拿我们前面用过很多遍的Animal类来举例,我们能实例化出一个Animal对象吗? 动物是个很抽象的概念,它可以是猫,可以是狗,可以是鸟等等,所以并不能实例化出一个动物对象。同样,我们在Animal类中实现了 eat() 方法,不同的动物有各自不同的 eat() 方法, 那么,我们的Animal类的 eat() 方法就没有具体实现了,因为它只能在具体的子类中才能有实现。所以我们把他定义为抽象方法,不具体实现,这个类也成了抽象类。
//将Animal实现为抽象类
abstract class Animal {String name;int age;public abstract void eat();
}class Cat extends Animal {String hair;public void eat() {System.out.println(this.name + "吃猫粮!");}
}class Dog extends Animal {String breed;public void eat() {System.out.println(this.name + "吃狗粮!");}
}
抽象类的语法
如上面代码所示,被 abstract
修饰的类被称为抽象类, 类中被abstract
修饰的成员方法称为成员方法被称为抽象方法。
//抽象类
abstract class Animal {//成员变量String name;int age;//抽象方法public abstract void eat();//普通方法public void setName(String name) {this.name = name;}
}
抽象类的特性
1. 抽象类不能直接实例化对象,必须被继承,且子类必须重写父类的抽象方法,否则,子类也只能是抽象类,也要用abstract
修饰。
Animal animal = new Animal();
//编译出错
按住 Alt
+ Enter
重写eat方法,就可以了~
这样我们就可以实例化Cat对象了!
- 抽象方法不能被
final
,static
,private
修饰,因为抽象方法必须在子类中被重写,它的使命就是被重写,被这些修饰符修饰的方法不能在子类中重写。
当抽象方法不加访问限定符时,它默认时 public 修饰的
- 抽象类中可以没有抽象方法,但是有抽象方法的类必须定义为抽象类。
- 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量。
抽象类的作用
我们知道普通类也可以被继承,普通方法也可以被重写进而发生多态,那为什么还需要抽象类呢?
使用抽象类相当于多了一重编译器校验。
因为抽象类是不能实例化对象的,当我们不小心实例化了抽象的父类的对象,编译器会报错,而如果我们实例化的是普通类父类,编译器不会报错!
类似于final
修饰的变量,不允许用户去修改,在不小心修改时编译器帮我们来报错提醒。 所以,使用抽象类可以预防出错
。
接口
接口的概念
什么是接口? 我们听过USB接口吧,可以插U盘,鼠标,键盘等等,只要插头符合USB协议,就都能使用USB接口。
所以,接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是:多个类的公共规范,也是一种引用数据类型。
接口的语法
接口的语法跟类定义类似,只需要把class
换成interface
即可。
我们来用IDEA创建一个接口:
这次选择Interface,输入接口名称,回车!
public interface 接口名称 {void method();//这两种是一样的,更推荐上面这种,更加简洁public abstract void method();
}
Tips:
1.创建接口时, 接口的命名一般以大写字母 “I ”开头。
2.接口的命名一般使用 “形容词” 词性的单词。比如我们常见的Comparable。
接口的使用规则
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性,其实接口中的方法不写修饰符是默认
public abstract
修饰的,但如果你改成其他的,比如private
就会报错。同时因为是默认public abstract
修饰的,所以方法不能有具体实现。
//报错! 不能用private修饰
private interface IMyInterface {}public interface IMyInterface {void method() {}//编译器报错,抽象方法不能有具体实现
}
- 接口类型也是一种引用类型,但是不能直接new接口的对象,不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。 值得关注的是,接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
public interface IMyInterface {void method();public abstract void method2();
}public class Test {public static void main(String[] args) {IMyInterface myInterface = new IMyInterface();//报错,不能实例化接口对象}
}
正确使用方法是:定义一个类来实现接口,使用implement
关键字
class 类名 implements 接口名 {//在类中必须重写接口的所有方法
}
现在编译器在报错,那是因为没有重写接口方法,我们现在按下Alt
+Enter
,选择 implement method 实现方法,回车。
选择所有的方法,点击OK, 完成!
- 接口中不能有静态方法和构造方法。这也是接口不能直接new对象的一个原因,因为它没有构造方法。接口只是一种规范标准。
public interface IMyInterface {void method();static void method();//报错,静态方法不能被重写IMyInterface() {}//报错,不能有构造方法}
- 重写接口中方法时,不能使用default访问权限修饰 ,也就是不能不写修饰符,那该写什么呢? 答案是必须用
public
修饰。
分析一下,为什么? 还记得我们在多态里说重写的时候吗?我们说重写方法的访问权限必须大于等于父类的方法,既然接口里的方法都是默认public
修饰的,那么重写方法只能是public
修饰了。
那有人就要说了,那不是子类重写父类方法的规则吗?还能用到接口吗?
我们发现在编译后,接口也会生成.class
文件,这跟类是类似的,而实现类implements
实现接口也类似于子类extends
继承父类,虽然不是一个东西不能相提并论,但还是有相似点的,咱们对照理解。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为
public static final
修饰。即接口中的变量是不能被修改的,得看作常量。 - 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类。
实现多个接口
在Java中,类和类之间是单继承的,一个类只能继承一个父类,不能多继承。但是接口与接口之间却可以做到多继承,一个接口可以继承多个接口,一个类也能实现多个接口。
下面我们同样通过Animal类来理解多接口和多继承。
因为我们说动物类不能具体实例化,他的eat方法在每个子类的实现不同,所以我们把他写成抽象方法,自然Animal类也就成了抽象类。
abstract class Animal {//成员变量String name;int age;//抽象方法public abstract void eat();//普通方法private void setName(String name) {this.name = name;}
}
然后,提供几个接口,我们知道动物有“会跑的”,“会飞的”,“会游泳的”,所以我们分别提供这三个接口,让具有这种特性的子类来实现它们。
public interface IRunning {void run();
}public interface IFlying {void fly();
}public interface ISwimming {void swim();
}
猫猫会跑,鱼会游泳,鸟会飞也会跑,鸭子既能跑又会游泳。我们说猫,鱼,鸟,鸭子都继承于Animal,但是,猫具有会跑的特性,鱼具有会游泳的特性,鸟具有会跑和会飞两种特性,鸭子具有会跑和会游泳两种特性。
类和类之间我们说继承,类和接口直接我们说类具有接口的特性或者类实现了接口的功能。
class Cat extends Animal implements IRunning {//重写接口方法@Overridepublic void run() {System.out.println(this.name + "正在朝你奔来!");}//重写父类方法@Overridepublic void eat() {System.out.println(this.name + " 喵喵!");}}class Fish extends Animal implements ISwimming {//重写接口方法@Overridepublic void swim() {System.out.println(this.name + "正在游向你!");}//重写父类方法@Overridepublic void eat() {System.out.println(this.name + "在吃虾米!");}
}class Bird extends Animal implements IRunning, IFlying {//重写接口方法@Overridepublic void fly() {System.out.println(this.name + "正在扑棱扑棱!");}//重写接口方法@Overridepublic void run() {System.out.println(this.name + "正在蹦蹦跳跳跑!");}//重写父类方法@Overridepublic void eat() {System.out.println("早起的鸟儿有虫吃!");}
}
接口之间的继承,多继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口, 达到复用的效果. 使用extends
关键字。
举个例子,两栖类动物既会游泳又会跑,我们实现一个两栖类(Amphibious)的接口,这个接口需要同时具备Swimming和Running的特性,所以它继承这两个接口。
interface IAmphibious extends ISwimming, IRunning {void climb();
}
注意接口继承是不需要重写接口里的方法的,都在实现接口的类里进行重写。接口继承就是缝合怪,把多个接口拼接组装在一起,方便使用。
接口使用实例-数组排序
Comparable接口的使用
如果我们相对一个整形数组排序,有很多方法,什么冒泡排序选择排序,在Java中对基本数据类型排序我们有Arrays.sort()
方法,如图:
我们知道基本数据类型的数组都是通过比较大小来排序的,那如果,对一个引用类的数组,数组里存的是对象,该这么排序呢?还能不能比较大小?
class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public class Test {public static void main(String[] args) {Student student1 = new Student("zhangsan", 19);Student student2 = new Student("lisi", 18);Student student3 = new Student("wangwu", 20);Student[] students = {student1, student2, student3};Arrays.sort(students);//报错!!System.out.println(Arrays.toString(students));}
}
我们的解决办法是,让Student类实现Comparable
接口,并重写它的compareTo()
方法,通过调用compareTo来比较两个对象。
这里Comparable接口后面的<T>
是泛型标志,后面介绍,这里理解为占位符,谁实现它就传谁的类型,这里我们写Student,并重写compareTo方法。
这里compareTo方法中只返回了0,我们还需要根据需求添加,因为compareTo方法如果调用者大于传入的参数对象,则返回大于0的数,相等返回0,小于则返回小于0的数。
如果当前对象应排在参数对象之前, 返回小于 0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0;
假如我们通过年龄来比较:
@Overridepublic int compareTo(Student o) {return this.age - o.age;}
students数组按年龄排好序了,有个疑问,我们只是写了compareTo方法,为什么就能排序了呢?
对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力,通过重写 compareTo 方法的方式, 就可以定义比较规则,在 sort 方法中会自动调用 compareTo 方法,通过比较大小来排序。参考冒泡选择排序。
不过,这样写其实不太好,因为,这样写的话,我以后调用sort只能按照年龄大小来排序了,如果我们想按照姓名来排序,或者按照成绩来排序该怎么办??? 所以这样写的缺点是:对类的侵入性太大,不建议这样写!
Comparator 接口的使用
好的写法是,自己另外实现比较器类,实现Comparator接口,重写compare方法。在sort时调用两个参数的需要传比较器的sort方法!
//年龄比较器
class AgeComparator implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.getName().compareTo(o2.getName());}
}
注意我在这里通过年龄比较,name是String类型,调用了String的compareTo方法。String的compareTo方法如图,了解即可:
我们要调用的sort方法如图:
public class Test {public static void main(String[] args) {Student student1 = new Student("zhangsan", 19);Student student2 = new Student("lisi", 18);Student student3 = new Student("wangwu", 20);Student[] students = {student1, student2, student3};System.out.println("排序前");System.out.println(Arrays.toString(students));//实例化比较器对象AgeComparator ageComparator = new AgeComparator();//调用需要比较器的sort方法Arrays.sort(students, ageComparator);System.out.println("排序后");System.out.println(Arrays.toString(students));}
}
运行结果:
这样,我们就不用在需要不同排序规则时对Student类中的compareTo方法改来改去,降低了对类的侵略性! 例如现在如果Student类中新加属性score成绩,我们需要按成绩排序,只需要实现类,类中实现Comparator接口,按成绩来比较即可。
equals的使用
equals我们在字符串中经常使用,用它来判断两个字符串内容是否相同。如图:
这个equals是String类中自带的,源码如下:
而要用equals来判断两个对象属性是否相同,能直接用吗?来试试。
我们的两个对象内容完全一样,却打印了false,为什么?坏了?我们来看看这个equals,点进去!
原因是这里的equals是继承自Object类(所有类的父类)的,Student本身没有这个方法,它返回的是直接判断两个引用是否相等,即判断地址是否相等,两个对象地址当然不同,永远都是false。
如果要用equals,就需要重写equals方法。
我们鼠标点击右键,找到万能的Generate
,选择equals and hashcode
,回车,然后一路next,就好了。
现在比较就可以了:
equals与compareTo的区别
这两个都能用来比较两个对象,但是equals用来判断两个对象内容或者属性是否都相等,而compareTo是通过对象的某个属性进行比较,可以判断是否相等,也可以判断大小。
抽象类和接口的异同
类似点:
- 编译后都会生成
.class
字节码文件- 都能将子类对象通过向上转型由抽象类引用或者接口引用。
- 都需要具体类来重写方法。
核心区别:
抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法。
码字不易,点个赞再走吧,收藏不迷路,持续更新中~
这篇关于【Java SE】抽象类和接口 保姆级细致教学,深入理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!