抽象类
抽象类的格式:
public abstract class AbstractPerson {
}
一个抽象类通过abstract关键字来定义
抽象方法的格式:用abstract修饰,只有方法签名,没有方法体
public abstract class AbstractPerson {
public abstract void method();
}
图中的method方法就是一个抽象方法。
抽象类的作用:对事物的分析、设计得到得抽象概念。
抽象类得特点:
1.一个抽象类中可以没有抽象方法,但是如果一个类中有抽象方法这个类就必须是抽象类,抽象类中可以同时有抽象方法和普通方法,抽象类中也可以只有普通方法,也可以什么方法都没有,抽象类中还可以有静态方法。
public abstract class AbstractPerson {
}
上面这个就是一个抽象类,里面什么方法都没有,编译不报错。
可以看到,一个普通类里面有抽象方法会报编译错误,因为一个类中有抽象方法这个类就必须是抽象类。
上面可以看到,我们的generalMethod()是一个普通方法,但是编译能通过。说明一个抽象类中可以有普通方法。
一个类中可以什么方法都没有,我们一开始在抽象类里面就已经演示了,在这就不演示了。
还有就是一个类里面可以只有普通方法:
抽象类有构造方法,但是抽象类不能被直接实例化
接下来,我们来看看抽象类中的构造方法。
抽象类中的构造方法和我们普通类的构造方法没什么区别,唯一的区别就是,抽象类不能直接实例化,而普通类可以。
那我们来看看是不是真的不能直接实例化。
可以看到,我们在实例化一个抽象类对象时,编译器是无法通过的,那是不是说这个构造方法不能用呢?那我们接下来就来验证这个构造器能不能用,我们之前学过,子类继承了父类,在子类的默认构造方法中会默认调用父类的无参构造方法,那我们用一个类来继承这个抽象类,看看能不能调用父类的无参构造。
首先,我们能看到的是编译器是能通过的,那运行会不会出错呢?那我们通过一个测试类来new一个子类对象就知道了。
根据结果,我们可以知道父类的构造方法是能被调用的,只是我们不能通过抽象类本身直接实例化,抽象类不能被直接实例化,那我们怎么去用里面的方法和属性呢?
我们可以通过向上转型的方法来使用父类中的方法和属性。
抽象类中可以有常量、变量,抽象类被一个类继承了,那么继承了抽象类的子类必须要重写抽象类中所有的抽象方法,如果不重写,那这个子类也必须为一个抽象类
抽象类中可以有变量常量。
可以看到,抽象类中有变量和常量,编译是可以通过的。
紧接着,就是抽象类中的抽象方法了。
我们先是在一个抽象类中定义一个抽象方法
public abstract class AbstractPerson {
public abstract void method();
}
紧接着,我们可以看到继承了这个抽象类的子类编译报错了。
编译报错:The type Woman must implement the inherited abstract method AbstractPerson.method()
它的意思是我们必须去实现AbstractPerson这个抽象类里面的method()这个抽象方法,并且,它给出了两个解决的方法,一个是实现这个抽象方法,一个是将Woman这个子类变为一个抽象类。
这也是我们说的如果子类不重写父类中的抽象方法,那么这个子类就必须也是一个抽象类。
那我们来思考一下,普通方法和静态方法子类能重写吗?
普通方法可以进行重写,静态方法不可以重写,因为静态方法是属于类的。想要验证这个我们可以通过继承时学的@Override来验证,如果我们的@Override检查可以通过,说明可以重写,检查报错,说明不可以重写。
接口
接口定义格式:
public interface Animal {}
接口通过interface这个关键字定义。
接口的作用:接口主要是拿来作为一种约定、规范的作用。
接口的特点:
接口里面只有抽象方法,接口中只有常量(不全对)
首先,接口中只有抽象方法。
之前我们说过,抽象方法是用abstract这个关键字来修饰的,可是从图中,我们看到generalMehod()这个方法没用abstract这个关键字修饰,编译也是通过了,其实不然,接口中,我们如果不用abstract这个关键字修饰,系统会默认给我们加上public astract来修饰这个方法,所以generalMehod()这个依然是个抽象方法。
注意:接口中的方法必须要用public修饰,因为接口是要被别的类实现的,如果不为public修饰那么就有的类会无法实现这个方法。
可以看到图中,我们定义了一个变量,系统编译错误了。报的错是:The blank final field a may not have been initialized,这个报错的意思是我们这个被final修饰的字段(属性)没有被初始化,说明我们a这个属性是一个常量,之前的学习里也有说过,被final修饰的属性就是一个常量,所以从这里可以知道接口中不可以定义变量。
但是需要注意的是,常量也可以分为静态常量还是普通的常量,这个也很好验证,我们通过类名来调用一下看看能能调用这个常量a。
可以看到,编译是可以通过的,并且运行也是可以的,说明什么呢?
这就说明了,我们在接口中定义的常量都是静态常量,并且和方法一样,我们如果不写static final来修饰这个常量,系统会自动为我们加上。
但是需要注意的是,静态常量属于一个类的,所以这个常量只能通过接口名来调用,不能通过实现了这个接口的类的类名调用。
接口没有构造方法,不能被直接实例化
接口中是没有构造方法的,我们可以在接口中写一个构造方法
可以明显看到,编译是不通过的,说明接口中是不可以有构造方法的。
我们也可以通过去创建一个接口对象来看看接口能不能被直接初始化。
其实,我们也可以不去尝试,因为接口连构造方法都没有,怎么去创建对象?
但是为了探究这个正确性,我们还是去尝试一下
编译报错,结果是不能的。
一个类实现了接口就要实现接口里面所有的抽象方法,除非实现接口的这个类是抽象类
我们可以现在Animal这个接口中定义一些抽象方法,再用一个类来实现这个接口。
public interface Animal {
void eat();
void sleep();
}
此时问题就来了,接口怎么被一个类实现呢?
一个类想要实现一个接口需要通过implements这个关键字来实现。
格式如下:
但是有个问题,编译器报错了。报错:The type Dog must implement the inherited abstract method Animal.sleep(),是不是我们写错了呢?不是的,是因为我们一个类要想实现一个接口就要重写接口里面的所有抽象方法,不重写接口的抽象方法的话就要求这个实现接口的这个类也是一个抽象类。
我们对接口里的抽象方法进行重写
public class Dog implements Animal{
@Override
public void eat() {
// TODO Auto-generated method stub
}
@Override
public void sleep() {
// TODO Auto-generated method stub
}
}
这样编译器就不会报错了。
接口可以继承多个接口
在以前的学习中,类只能直接继承一个类,但是在接口中,接口是可以直接继承多个接口。
public interface Biology {
}
public interface Animal {
}
public interface Person extends Animal,Biology{
}
我们一个接口是可以继承多个接口的。
学完了接口的使用和特点,那我们再来聊聊接口的使用时需要注意的地方。
比如,我们之前学的上下转型和继承。一个类是可以直接继承一个并且实现多个接口的。那么如果父类中的一个方法和接口中的一个方法重名了,那么我们如果通过上转型的方式来实例化一个子类对象,那么这个子类对象调用的是谁的方法呢?
我们接下来写一下这个代码
public interface Animal {
void eat();
}
public class Person{
public void eat() {
System.out.println("Person中的eat");
}
}
public class American extends Person implements Animal{
@Override
public void eat() {
System.out.println("Amrican的eat");
}
}
输出的结果是:Amrican的eat
这是子类重写的方法,说明上面这种情况它调用的是子类的方法。
那我们再来看看我们这个上转型对象能不能够向下转型呢?
public class Test {
public static void main(String[] args) {
Person person=new American();
person.eat();
American american=(American)person;
american.eat();
}
}
编译器没有报错
输出结果:
Amrican的eat
Amrican的eat
说明是可以向下转型的,但是这种向下转型我们称它是安全的。
向下转型有两种,一种是安全的,一种是不安全的。需要注意的是不安全的我们是无法实现的,因为我们说过,接口不能直接实例化,所以不安全的向下转型我们是不可以做到的,抽象类也是如此。
那再想想,我们能不能将person这个对象向下转型为Aniaml类型的呢?
public class Test {
public static void main(String[] args) {
Person person=new American();
person.eat();
Animal animal=(Animal)person;
animal.eat();
}
}
}
}
编译没有报错
运行结果:
Amrican的eat
Amrican的eat
结果是可以的。
说到最后,我们来回顾一下我们前面说接口的特点的第一点,为什么说他不全对呢?因为在jdk1.8之前,这个说法是没问题的,但是在1.8之后,我们可以在接口中定义默认方法和静态方法。jdk9后接口又增加了新的特性。
public interface Animal {
void eat();
//1.8才有的特性,子类不可以重写
static void staticMethod() {
}
//1.8后才有的特性,子类可以重写
public default void defaultMethod() {
}
}
格式如上面代码
但是值得注意的是,我们之前有说过,静态的东西是属于一个类的,所以一个类如果实现了有静态方法的接口,我们是不可以对这个静态方法进行重写的,怎么判断可不可以,我们只要用@Override来验证一下就可以了。
默认方法的话我们是可以对它进行重写的,重写的要求和我们继承时一样。
接口和我们的抽象类有点相似,但是还是有许多地方是不一样的。
最后,我们来比较一下接口和抽象类的区别吧
接口 | 抽象类 |
---|---|
接口在jdk1.8之前只能有抽象方法和静态常量,jdk1.8之后可以有默认的方法和静态方法 | 抽象类中可以有普通方法、抽象方法、常量、变量 |
接口没有构造方法 | 抽象类有构造方法 |
接口中修饰方法的访问修饰符只能时public | 抽象类中的抽象方法可以用public,protected,default修饰 |
接口可以继承多个接口 | 抽象类可以直接继承一个类 |
以上就是接口和抽象类的内容,以后发现有错再来修改。