抽象类
什么是抽象类
并不是所有的类都是用来描述对象的,这样的类就是抽象类
例如,矩形,三角形都是图形,但图形类无法去描述具体图形,所以它的draw方法无法具体实现,这个方法就可以没设计成抽象方法,这个类就是抽象类
抽象类语法
被abstract修饰的类是抽象类,abstract修饰的方法是抽象方法,抽象方法不用给出具体的实现体
如上,draw方法没有具体实现体,也就是不用写代码块
继承了抽象类的子类必须重写所有的抽象方法。如下:
如何使用该抽象类及其子类
这就是一种使用方法,在main函数中调用了Test中的静态方法draw(注意一定要是静态的,在静态方法中不可以调用非静态方法,因为非静态方法的调用依赖对象,而静态方法不依赖对象,所以静态方法默认没有this参数,也就无法调用非静态方法)
draw方法的参数是Shape类,而我们传参时传的是其子类,这里就发生了向上转型;
在draw方法中调用shape.draw()时,又会发生动态绑定;
总之这也是多态。
抽象类的特性
1.不能实例化本抽象类的对象
但是可以用它实例化一个子类对象,也就是可以发生向上转型
因为向上转型后,对象的本质还是其子类,只不过用父类来接收了,并不代表实例化了一个父类对象。
2.抽象方法不可以是private权限,也不可以被final,static修饰
首先,子类重写方法的访问权限要大于等于父类;
其次,final修饰的方法是静态方法,static修饰的方法在方法区;
总之,被private,final,static修饰的方法不可以被重写
3.继承抽象类的子类必须重写其方法,否则子类也要被abstract修饰,然后它的继承者要将其父类以及父类的父类中所有的抽象方法重写
4.抽象类中不一定有抽象方法,但由抽象方法的一定是抽象类
5.抽象类中也可以有普通方法和成员
但它里面的普通成员变量及方法只能通过子类对象来调用
其中,shape引用是发生了向上转型的,而reck就是一个子类的引用,由于有继承关系,所以可以通过子类引用来调用抽象类的方法
6.抽象类中可以有构造方法,供子类创建对象时初始化父类的成员变量,但注意,构造方法不能是抽象方法,因为构造方法不可以被重写
抽象类的作用
很多工作不应由父类完成,而应由子类来完成,但如果是一个普通类,用父类的引用去调用某些方法时就不会报错,而要是抽象类就会报错
就比如画图,你要是用父类shape来调用draw方法,如果不是抽象类就能正常编译,但由于图形有很多种,所以shape这个引用画不出具体的图形;而要是抽象类就会及时报错,提醒你调用具体的子类来画图
接口
什么是接口
接口就是公共的行为标准,大家在实现时,只要符号规范标准就可以通用。
具体点说,接口就是多个类的公共规范,是一种引用数据类型。
语法规则
接口要用interface关键字定义,内部是抽象方法;例如:
接口的使用
有了接口就必须要有具体的类来使用这就要用到implements关键字
public class 类名 implements 接口名{
}
如果这个类还和其他类由继承关系,则应该:
public class 类名 extends 父类名 implements 接口名{
} 表示该子类继承了某个类并且还有某个功能
既然都有继承关系了,为什么还要有接口呢?一个父类可以引出多个子类,它们都是由共性的,但也有特性,这些特性就不适合写在父类里面,这时就可以提供接口,有什么功能就使用什么接口
举例:
接口的特性
1.接口默认是被abstract修饰的,因为它里面有抽象方法
interface USB等价于abstract interface USB
2.接口中的方法默认是被public abstract修饰的(也只能是这种权限,其他的都会报错)
void func(); 等价于public abstract void func();
这也好理解,接口是一种公共标准,一定得是公开的抽象的
3.接口是一种引用类型,但不能直接new一个接口对象
4.接口中可以有成员变量,但这些变量默认是public static final修饰的。既然默认被final修饰了,那么在定义的时候就必须初始化
首先 int a=10;等价于public static final int a=10;
其次,不可以直接int a;必须初始化
5.接口中一般不可以有普通方法,但被static default修饰的方法除外
但注意:static void func(){}等价于public static void func(){},不可以改成除public的其他权限,default也一样
对于被static修饰的方法,可以直接用接口名调用,而default修饰的方法只能用子类对象调用或者被重写
例如:
package Demo2;
interface A{
void Testa();
static void Testb(){
System.out.println("A 的static方法");
}
default void Testc(){
System.out.println("A的default方法");
}
}
class B implements A{
@Override
public void Testa() {
System.out.println("B重写的testa方法");
}
@Override
public void Testc() {
A.super.Testc();
}
}
public class Test {
public static void main(String[] args) {
A.Testb();
B b=new B();
b.Testa();
b.Testc();
}
}
用static修饰的方法可以在main中直接用接口名调用,但default方法要么被重写,要么用子类实例化的对象来调用
6.重写接口中的抽象方法时,只可以设置为public权限,因为接口中的抽象方法默认是public的,所以子类的方法权限要大于等于public
7.接口中不能有静态代码块和构造方法
8.如果它的子类没有重写抽象方法,那这个类必须设置为抽象类
举例
abstract interface USB{
void openDevice() ;
void closeDevice();
}
class Mouse implements USB{
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
public void click(){
System.out.println("疯狂点击鼠标");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
}
class KeyBoard implements USB{
@Override
public void openDevice() {
System.out.println("打开键盘");
}
public void input(){
System.out.println("疯狂打字");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘");
}
}
class Computer {
public void powerOn() {
System.out.println("打开电脑");
}
public void powerOff() {
System.out.println("关闭电脑");
}
public void useDevice(USB usb){
usb.openDevice();
if(usb instanceof Mouse){
Mouse mouse=(Mouse) usb;
mouse.click();
}
else if(usb instanceof KeyBoard){
KeyBoard keyBoard=(KeyBoard) usb;
keyBoard.input();
}
usb.closeDevice();
}
}
public class Test {
public static void main(String[] args) {
Computer computer=new Computer();
computer.powerOn();
KeyBoard keyBoard=new KeyBoard();
Mouse mouse=new Mouse();
computer.useDevice(keyBoard);
computer.useDevice(mouse);
computer.powerOff();
}
}
如上,电脑有usb接口,可以实现打开关闭键盘鼠标的操作
注意,电脑本身不需要usb接口来打开,所以电脑是一个独立的类,它通过自己的powerOn powerOff 方法来打开,然后它又有使用设备的方法,然后就是设备要通过usb接口来打开关闭,即usb.openDevice(); usb.closeDevice();但是在打开后,还要工作,可是只用USB类无法调用设备独有的功能,所以发生了向下转型,注意instanceof的判断和括号的类型强转
然后在main函数中,首先实例化一个电脑对象,将电脑打开,再实例化键盘和鼠标,然后就是电脑要开始使用设备了,调用useDevice函数(这里发生了向上转型,因为是用USB这个引用去接收各种需要USB的对象)。最后就关闭电脑
实现多个接口
在Java中,类和类之间只能单继承,一个类只能有一个父类,但是一个类可以有多个特殊的功能,即一个类可以有多个接口
class 类名 implements 接口1,接口2
例如:青蛙会跑也会游泳,鸭子会跑会游泳也会飞,如下
class Animal{
String name;
int age;
public void eat(){
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
}
interface Running{
public abstract void run() ;
}
interface Swimming{
public abstract void swim();
}
interface Flying{
public abstract void fly();
}
class Dog extends Animal implements Running{
public Dog(String name, int age) {
super(name, age);
}
@Override
public void run() {
System.out.println("正在用囧囧跑步步");
}
@Override
public void eat() {
System.out.println("吃狗粮");
}
}
class Flog extends Animal implements Running,Swimming{
@Override
public void run() {
System.out.println("正在用俩只大脚掌跑步步");
}
@Override
public void swim() {
System.out.println("正在用那小手手游泳");
}
public Flog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("吃蛙粮");
}
}
class Duck extends Animal implements Running,Flying,Swimming{
@Override
public void run() {
System.out.println("正在小跑");
}
@Override
public void swim(){
System.out.println("正在划水");
}
@Override
public void fly() {
System.out.println("正在扇翅膀飞飞");
}
public Duck(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("正在吃鸭粮");
}
}
这就是一个典型的例子
那如何使用呢?
在main函数中可以调用这些static修饰的方法。在=========的上方,是多态的典型例子,将子类对象给到父类,再发生动态绑定,调用重写的方法,
在============的下方就是在使用接口了。注意public static void run(Running run),这个方法的参数是Running这个引用类型,当有此功能的对象给到这个引用类型时,就是发生了向上转型,所以在run.run()时,表面上是调用了接口的抽象方法,实际上是在调用子类的重写的方法
其实,只要是有这个功能的类,就可以实现这个接口
接口间的继承
在Java中,类和类之间是单继承的,但接口与接口可以多继承
package Demo4;
interface A {
void testA();
}
interface B {
void testB();
}
interface C extends A,B{
void testC();
void testA();
}
class TestDemo1 implements C {
@Override
public void testC() {
}
@Override
public void testA() {
}
@Override
public void testB() {
}
}
public class Test {
}
接口举例
comparable
想要比较俩个学生的大小
单纯这样是不行的,我们要指定如何比较,拿什么比较,这就要用到comparable接口
这是一个接口,尖括号里面的T是要比较的数据的类型,如果要比较student,就在里面写student。这个接口里面有一个抽象方法,所以我们要重写这个抽象方法;以用年龄比较为例
注意,尖括号不能省略
但是,如果需求改变,又想用姓名比较,我们就又得把重写的方法改了,而不可以再写一个重写方法(抽象方法只可以被重写一份)很麻烦
所以就有了下面的比较器
comparator
它里面有很多抽象方法,但我们只用到了比较,所以重写比较即可
先看例子
这是年龄比较器,那要是名字比较器呢?不能直接返回o1.name-o2.name,因为name是String类型,不可以相加减。这时就需要用到String下面的compareTo方法
使用的时候就要实例化比较器这个对象
com
上面这个是String类下面的compareTo方法,其实是String类使用了comparable接口,所以重写了comepareTo方法
总结
comparable对类的侵入性比较强,是要比较哪个类,就要让哪个类implements它,是在类里面重写方法。一旦写死了,就只可以用这一种比较方法
comparator更加灵活,只要传入要比较的对象即可,只不过注意要实例化比较器对象因为comparator这是一个接口,里面的抽象方法无法调用,只有放到类里面才能发挥作用,需要我们按自己的意愿重写
再例如
我们可以这样对整型数组排序,那能否对学生数组排序呢?
显然是不可以的,来看一下源码
这里用到了Comparable接口,即将俩个对象强转为了comparable类型,所以要想成功排序,Student类就要使用这个接口,并且重写这个comepareTo方法
然后就可以正常排序了,但最好再自己重写一个toString方法,要不然就会出现上面的打印情况(上面这个最终是调用的object类的toString方法,而object类是所有子类的父类,所以当我们再Student类中重写了方法后就会调用我们自己的方法),如下
其实,sort有很多重载方法,比如下面这个:
它是有俩个参数,其中一个是要排序的数组,另一个就是Comparator这个接口(但不是说要传这个接口,而是要传实现这个接口的类型的引用)
那么我们就可以如下这种方式来排序
总结:
既然对于整型数组可以直接用arrays.sort排序,那就说明一个事实,Integer这个类一定implements了comparable这个接口,我们可以看一下源码:
自己写一个排序方法
因为排序不仅仅是为一个类提供,而是为多个类提供,所以参数要设置成Comparable类的数组,这样的话,只要是实现了这个接口的类,都可以用到这个排序方法。注意,里面的交换语句其实交换的是引用的指向(相当于C语言中指针的指向)。