文章目录
1.抽象类
1.1 什么是抽象类?
抽象类的概念是:在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
这概念如何理解?现在我有三个类,如下:
public class Animal {
public String name;
public int age;
public void bark(){
//......
}
}
class Cat extends Animal{
//......
public void bark(){
System.out.println("喵喵喵~");
}
}
class Dog extends Animal{
//......
public void bark(){
System.out.println("汪汪汪~");
}
}
这里的Cat
与Dog
继承了Animal
,并且重写了bark()
这个方法。但是这里的Animal
是动物类,没有一个具体的叫声,所以无法具体实现bark()
,索性我们就不实现这个方法了。这种没有实际工作的方法, 我们可以把它设计成一个抽象方法, 包含抽象方法的类我们称为抽象类。
这里的Animal
,因为没有包含足够的信息来描绘一个具体的对象,所以我们可以把Animal设计成一个抽象类。
1.2 抽象类的语法
一个类如果被abstract修饰称为抽象类,抽象类中被abstract修饰的方法称为抽象方法,抽象方法不用给出具体的实现体,就拿上面的例子。
public abstract class Animal {
public String name;
public int age;
//抽象方法:被abstract修饰的方法,没有方法体
public abstract void bark();
}
class Cat extends Animal{
//......
public void bark(){
System.out.println("喵喵喵~");
}
}
class Dog extends Animal{
//......
public void bark(){
System.out.println("汪汪汪~");
}
}
注意:抽象类也是类,在抽象类中可以增加普通方法、构造方法和属性。
public abstract class Animal {
//普通属性
public String name;
public int age;
//可以有构造方法
public Animal(String name,int age){
this.name = name;
this.age = age;
}
//可以增加普通方法
public void eat(){
//.......
}
//抽象方法:被abstract修饰的方法,没有方法体
public abstract void bark();
}
1.3 抽象类的特性
(1)抽象类不能直接被实例化。
在抽象方法内部可能存在“没有实现的方法”,故无法被实例化,如果使用new
会报错:
abstract class Animal{
//普通属性
public String name;
public int age;
//可以有构造方法
public Animal(String name,int age){
this.name = name;
this.age = age;
}
//可以增加普通方法
public void eat(){
System.out.println("我正在进食!");
}
//抽象方法:被abstract修饰的方法,没有方法体
public abstract void bark();
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
}
}
结果:
(2)抽象类必须要被继承,并且要重写里面的抽象方法。
如果要使用抽象类,那么必须得继承。
class Cat extends Animal{
public Cat(){}
public Cat(String name,int age){
super(name,age);
}
//重写bark方法
@Override
public void bark() {
System.out.println(name + ":喵喵喵~");
}
}
public class Main {
public static void main(String[] args) {
Animal cat = new Cat("小白",6);
cat.bark();
}
}
结果:(ps:从下面可以看出抽象类也是可以发生多态的)
(3)抽象类不能被final
修饰,因为抽象类必须要被继承。
(4)抽象方法不能被final
和static
修饰,并且抽象方法的访问权限要大于private,因为抽象方法要被子类重写。
(5)抽象类中可以没有抽象方法,但是有抽象方法的类必须是抽象类。
2.接口
2.1 接口是什么?
在上面的抽象类那介绍过,有的时候类里的方法没有必要实现,因而有了抽象类。但是在抽象类里面,还是有存在其它普通成员的必要,这样就显得十分混乱。
那有没有一种用来装这些未具体实现的方法(抽象方法)的集合,这种集合就是接口。这里要注意的是在jdk1.8之后,接口中是可以使用非抽象方法的。可以把接口抽象成一种“功能”,当一个类需要某种功能时就可以实现这个接口(案例在后文)。
2.2 接口的语法
接口与类的定义方式差不多,只是将class
换成interface
。
public interface 接口名称{
//.......
}
那接口是怎么使用的呢?类与类的关系可以是"继承",那么类与接口之间的关系是"实现"(某个类实现了某个接口)。
继承关系中用extends
,实现接口就用implements
其形式👇。
public class 类名称 implements 接口名称{
// ......
}
2.3 接口的特性
(1)接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(也就是什么修饰符都不写的情况下)。
public interface Test{
//抽象方法1
public abstract void method1();
//抽象方法2 public被省略了
abstract void method2();
//抽象方法3 abstract被省略了
public void method3();
//抽象方法4 public abstract都被省略了
void method4();
}
注意这里只能是 public abstract
,其他修饰符都会报错,为什么会报错?我认为:接口是来服务类的,要保证任何位置的类都能实现这个接口。
(2)任何非抽象类实现接口的时候必须要重写接口中的所有的抽象方法。 这跟抽象类是一样的。
(3)接口不能被实例化。
(4)接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final
变量,并且必须要初始化不然报错。
public interface Test{
//public final static修饰
public final static int a = 1;
//默认的情况: public final static被省略
int b = 2;
//抽象方法1
public abstract void method1();
//抽象方法2 public被省略了
abstract void method2();
//抽象方法3 abstract被省略了
public void method3();
//抽象方法4 public abstract都被省略了(推荐这种写法,简洁)
void method4();
}
(5)接口中不能使用代码块、构造方法等初始化程序。
(6)接口中能使用非抽象方法的情况。
①JDK 1.8 及以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default
关键字修饰。
②JDK 1.8 及以后,接口里可以有静态方法,用static
修饰而没有用abstract
修饰的方法可以实现方法体。
③JDK 1.9 及以后,允许将方法定义为 private
,就是用private
修饰而没有用abstract
修饰的方法可以实现方法体。
interface Test{
//default方法
public default void method5(){
System.out.println("我是用 default 修饰的方法!!!");
}
//静态方法
public static void method6(){
System.out.println("我是静态方法!!!");
}
//我是 private 方法
private void method7(){
System.out.println("我是用 private 修饰的方法!!!");
}
}
2.4 接口回调
接口回调就是相当于继承中的多态,不能说是相似,只能说是一模一样。
interface Inter{
public abstract void A();
}
class Test implements Inter{
//重写A()方法
public void A(){
System.out.println("我是Test类中的A()方法!!!");
}
//......
}
public class Main2{
public static void main(String[] args) {
//接口回调,与多态类似
Inter test = new Test();
test.A();
}
}
结果:
但是这时访问不了Test自己的成员,只能访问Inter中的方法,这跟继承中向上转型的用法是一样的;那如果换成是Test test = new Test()
那么就可以访问全部的成员。
2.5 一个类实现多个接口
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。
语法格式:
class 类名 implements 接口1,接口2,……{
//......
}
案例:
class Animal2{
public String name;
public Animal2(String name){
this.name = name;
}
}
//会飞的
interface IFlying {
void fly();
}
//会跑的
interface IRunning {
void run();
}
//会游泳的
interface ISwimming {
void swim();
}
//狗不仅会跑,还会游泳
class Dog extends Animal2 implements IRunning,ISwimming{
public Dog(String name){
super(name);
}
//重写run()
public void run() {
System.out.println(this.name + "正在往前跑");
}
//重写swim()
public void swim() {
System.out.println(this.name + "正在四驱蹬腿游泳");
}
}
//鸭子不仅会跑还会游泳,甚至会飞。
class Duck extends Animal2 implements IRunning,ISwimming,IFlying{
public Duck(String name){
super(name);
}
//重写run()
public void run() {
System.out.println(this.name + "正在往前跑");
}
//重写swim()
public void swim() {
System.out.println(this.name + "正在双驱蹬腿游泳");
}
//重写fly()
public void fly() {
System.out.println(this.name + "正在飞翔");
}
}
public class Main3 {
public static void walk(IRunning running){
running.run();
}
public static void main(String[] args) {
Dog dog = new Dog("大黄狗");
walk(dog);
Duck duck = new Duck("小黄鸭");
walk(duck);
}
}
结果:
大黄狗正在往前跑
小黄鸭正在往前跑
这里就很好的呈现了把接口想成某一种“功能”的思想。比如动物中的狗有跑、游泳的能力,那么就让Dog
类实现这两个接口,使Dog
类拥有这两个“功能”。
2.6 接口间的继承
接口也可以像类那样进行接口与接口之间的继承,甚至接口与接口之间可以多继承。
//会飞的
interface IFlying {
void fly();
}
//会跑的
interface IRunning {
void run();
}
//会游泳的
interface ISwimming {
void swim();
}
interface IAnthropic extends IFlying,IRunning,ISwimming{
//......
}
当某一个类实现IAnthropic
接口的时候就需要重写前面三个接口的所有抽象方法。
2.7 使用接口的案例:给对象数组排序(Comparable接口)
先实现一个学生类,并对一个学生数组排序。
class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
}
public class Main4 {
public static void main(String[] args) {
Student student1 = new Student("张三",58);
Student student2 = new Student("李四",100);
Student student3 = new Student("王五",80);
Student student4 = new Student("小红",60);
Student[] students = {student1,student2,student3,student4};
}
}
我们如何对students
数组进行排序呢?我们可以想起普通的数组排序使用的是Arrays.sort()
,如果用在这里是否可以?
可以发现报错了,我们看看这个错误是什么意思:“不能将Student转换为java.lang.Comparable类”。这是什么意思?在回答这个问题前,先看看这个“Comparable”是什么。
我们点进源码👇:
可以看到Comparable
是一个接口,并且只有compareTo()
这一个抽象方法。这个接口翻译过来就是“可比较的”意思,我们想要对Student
排序前提是Student
得有“可比较的”的能力,显然Student
是没有这个能力的,那么就需要实现这个接口。
现在回归问题“不能将Student转换为java.lang.Comparable类”。
①我们的Student
类为什么要转换为Comparable
类型?
答:这里有“转换”这个词,什么情况下才会涉及到转换?那就是向上转型
或者接口回调
的情况下才会涉及到转换;
那么为什么要进行接口回调
?回想前文的接口回调
,就是用来调用自己内部的方法,也就是说Arrays.sort()
源码里面会将Student
类转换为Comparable
来调用compareTo()
方法,之所以要转换是因为在Arrays.sort()
源码的视角来看我不知道外面传进来的是什么具体的类型,有可能是“学生”、“老师”等类型,这时候就需要一个固定且已知的类型,很显然这个类型就是Comparable
。
那么这里为什么要调用compareTo()
?因为普通类型之间的比较大小就是数学上的那种形式,而像Student
这样的对象之间比较大小就要一个规定的规则compareTo()
就是你自己定的规则。
②我们的Student
类为什么会转换失败?
答:因为Student
类没有实现接口Comparable
,所有的引用类型之间的转换都要依仗于“继承”或者“实现某个接口”。换言之,向上转型
或者接口回调
必须得有“父类”或者“实现了接口”才能实现。
Comparable
使用:
class Student implements Comparable {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
//重写Object中的toString()的方法,方便打印。
public String toString() {
return "[" + this.name + ":" + this.score + "]";
}
//重写compareTo()方法,我们自己定一个比较规则,我们定:用分数来比较学生的大小。
public int compareTo(Object o) {
Student s = (Student) o;
return this.score - s.score;
}
}
public class Main4 {
public static void main(String[] args) {
Student student1 = new Student("张三",58);
Student student2 = new Student("李四",100);
Student student3 = new Student("王五",80);
Student student4 = new Student("小红",60);
//student1与student2比较大小
int a = student1.compareTo(student2);
System.out.println(a);
//student2与student3比较大小
int b = student2.compareTo(student3);
System.out.println(b);
}
}
结果:
-42
20
compareTo()
的返回值是一个负整数、零或正整数这三种情况之一。返回正整数时谁大谁小是根据你定的规则来的,我这里为this.score - s.score
。
回到正题,熟悉了compareTo()
后,我们来进行排序。
public class Main4 {
public static void main(String[] args) {
Student student1 = new Student("张三",58);
Student student2 = new Student("李四",100);
Student student3 = new Student("王五",80);
Student student4 = new Student("小红",60);
Student[] students = {student1,student2,student3,student4};
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
结果:
这是升序,降序怎么搞?把compareTo()
中的this.score - s.score
改为s.score-this.score
就行了,以下是改了之后的结果:
为什么会这样呢?我就通俗易懂地来说吧:假如这时候比较到this与this的后一个元素s了。你可以认为在Arrays.sort()
中compareTo()
返回值为正整数时(即this大于s),会交换this与s(Arrays.sort()
默认是降序)。
在Arrays.sort()
的视角,返回值为正整数时它会认为this大于s,然后交换。如果我们将this.score - s.score
改为s.score-this.score
,(this大于s)这时compareTo()
返回的就是负数,sort()
会认为this比s小所以就不交换了,然而实际上this是比s大的,大的放在后面不就是降序了嘛。