目录
1.抽象类
1.1 抽象类的定义与使用
抽象类只是在普通类的基础上扩充了一些抽象方法而已,所谓的抽象方法指的是只声明而未实现的方法(没有方法体)。所有抽象方法要求使用abstract关键字来修饰,并且抽象方法所在的类也一定要使用abstract关键字来定义,表示抽象类。
例子:定义一个抽象类
abstract class Person {
//属性
private String name;
//普通方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//{}为方法体,所有抽象方法上不包含方法体
public abstract void getPersonInfo(); //抽象方法
}
发现:抽象类比普通类多了一些抽象方法而已。
抽象类中包含抽象方法,而抽象方法不包含方法体,既没有具体实现。因此抽象类不能直接产生实例化对象。
抽象类的使用原则:
所有抽象类必须有子类。
抽象类的子类必须覆写抽象类的所有抽象方法(子类不是抽象类)【方法覆写一定要考虑权限问题,权限尽量都使用public】
抽象类的对象可以通过对象多态性利用子类为其实例化。
private与abstract不能同时使用。
例子:抽象类的使用
abstract class Person{
//属性
private String name;
//普通方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//{}为方法体,所有抽象方法上不包含方法体
public abstract void getPersonInfo(); //抽象方法
}
class Student extends Person{
public void getPersonInfo() {
System.out.println("I am student");
}
}
public class Test {
public static void main(String[] args) {
//实例化子类,向上转型
Person2 per=new Student();
per.getPersonInfo();
}
}
1.2 抽象类的相关规定
抽象类只是比普通类多了一些抽象方法而已,因此在抽象类中也允许提供构造方法,并且子类也照样遵循对象实例化流程。实例化子类时一定先调用父类的构造方法。
如果父类没有无参构造,那么子类构造必须使用super明确指出使用父类哪个构造方法。
范例:一段特殊代码
abstract class A{
public A(){ //3.调用父类构造
this.print(); //4.调用被子类覆写的方法
}
public abstract void print();
}
class B extends A{
private int num=100;
public B(int num){ //2.调用子类实例化对象
super(); //3.银汉一行语句,实际要先实现父类构造
this.num=num; //7.为类中属性初始化
}
@Override
public void print() { //5.此时子类对象的属性还没有被初始化
System.out.println(this.num); //6.对应其数据类型的默认值
}
}
public class Test{
public static void main(String[] args) {
new B(30); //1.实例化子类对象
}
}
结果为:
结论:如果使用构造方法,那么对象中的属性一定都是其对应数据类型的默认值。
关于对象实例化:
对象的实例化操作实际上需要以下几个步骤:
- 进行类加载
- 进行类对象的空间的空间开辟
- 进行类对象中的属性初始化(构造方法)
1.抽象类中允许不定义任何的抽象方法,但是此时抽象类依然无法直接创建实例化对象
2.抽象类一定不能使用final声明,因为使用final声明的类不允许有子类;而抽象类必须有子类;相应的,抽象方法也不能使用private定义,因为抽象方法必须被覆写。
3.抽象类也分为内部抽象类和外部抽象类。内部抽象类中也可以使用static定义来描述外部抽象类。
范例:观察内部抽象类
abstract class A{ //此类结构出现的几率很低
public abstract void printA();
abstract class B{
public abstract void printB();
}
}
class X extends A{
public void printA(){}
class Y extends B{
public void printB(){}
}
}
如果现在外部抽象类中使用了static那么就是语法错误。但是内部抽象类允许使用static
1.3 抽象类的作用
抽象类存在的最大意义就是为了被继承。
抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法。
提问:普通的类也可以被继承,普通的方法也可以被重写,为啥非得用抽象方法呢?
使用抽象方法相当于多了一重编译器的校验。使用抽象类的场景就如上面的代码,实际工作不应该由父类完成。而是由子类完成,那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的,但是父类是抽象类就会在实例化的时候提示错误,让我们尽早的发现问题。
很多语法存在的意义都是为了“预防出错”,例如我们曾经用过的final也是类似。创建的变量用户不去修改,不就相当于常量嘛,但是加上final能够在不小心误修改的时候,让编译器及时提醒我们。
充分利用编译器的校验,在实际开发中是非常有意义的。
2.接口
抽象类与普通类相比最大的特点是约定了子类的实现要求,但是抽象类存在单继承局限。如果要约定子类的实现要求并避单继承局限就需要接口。
在以后的开发过程中,接口优先(在一个操作既可以使用抽象类又可以使用接口的时候,优先考虑使用接口)
3.1 接口的基本概念
接口定义:接口就是抽象方法和全局常量的集合,在java中接口使用interface关键字定义。
例子:定义一个简单的接口
interface IMessage{
public static final String MSG="Hello"; //全局变量
public abstract void print(); //抽象方法
}
子类如果想要使用接口,那么就必须使用implements关键字来实现接口,同时,一个子类可以实现多个接口,【可以使用接口来实现多继承的概念】对于接口的子类(不是抽象类)必须覆写接口中的全部抽象方法。随后可以利用子类的向上转型通过实例化子类来得到接口的实例化对象。
范例:观察子类实现接口&父接口间的相互转换
interface IMessage{
public static final String MSG="Hello"; //全局变量
public abstract void print(); //抽象方法
}
interface INews{
public abstract String getNews();
}
class MessageImp implements IMessage,INews{
@Override
public void print() {
System.out.println(IMessage.MSG);
}
@Override
public String getNews() {
return IMessage.MSG;
}
}
public class Test {
public static void main(String[] args) {
IMessage m=new MessageImp(); //子类向上转型,为父接口实例化对象
//调用被子类覆写过的方法
m.print();
INews n=(INews) m;
System.out.println(n.getNews());
}
}
结果为:
真正new的子类才有意义
总结:
- 使用interface定义一个接口
- 接口中的方法一定是抽象方法,因此省略abstract
- 接口中的方法一定是public,因此可省略public
- 类使用implements继承接口,此时表达的含义不再是“扩展”,而是“实现”
2.2 接口使用限制
- 接口中只允许public权限(不管是属性还是方法,其权限都是public)
- 只要是方法就使用public进行定义。
- 接口中的所有属性默认为:public static final ****,一般不写
- 接口中的所有方法默认为:public abstract ****,一般不写
- 在以后编写接口的时候,99%的接口只提供抽象方法,很好在接口中提供全局常量;
- 为了保持代码的简洁性,接口中的方法和属性不要加任何修饰符号,public也不要加;
- 当一个子类即需要实现接口又需要继承抽象类时,请先使用extends继承一个抽象类,而后使用implements实现多个接口。
- 一个抽象类可以使用implements实现多个接口,但是接口不能继承抽象类。
- 范例:抽象类实现接口
interface Message{
void print();
}
abstract class News implements Message{
//News为抽象方法,可以不实现Message中的抽象方法
//抽象类中方法前面abstract不能省略,否则就是普通方法
public abstract void getNews();
}
class MessageI extends News{
@Override
public void print() {
System.out.println("Hello");
}
@Override
public void getNews() {
System.out.println("HELLO");
}
}
public class Test {
public static void main(String[] args) {
Message m=new MessageI();
m.print();
//MessageI是抽象类和接口的共同子类
News n=(News) m;
n.getNews();
}
}
结果为:
- 一个接口可以使用extends继承多个父接口
interface A{
public void printA();
}
interface B{
public void printB();
}
//接口多继承
interface C extends A,B{
public void printC();
}
class Imp implements C{
@Override
public void printA() {
System.out.println("A");
}
@Override
public void printB() {
System.out.println("B");
}
@Override
public void printC() {
System.out.println("C");
}
}
public class Test {
public static void main(String[] args) {
Imp imp=new Imp();
imp.printA();
imp.printB();
imp.printC();
}
}
结果为:
- 接口可以定义一系列的内部结构,包括:内部普通类,内部接口。其中使用static定义的内部接口就相当于一个外部接口。
2.3 接口的应用
接口在实际开发之中有三大核心应用环境:
- 定义操作标准
- 表示能力
- 在分布式开发之中暴露远程服务方法
3.抽象类与接口的区别(重点)
区别 | 抽象类(abstract) | 接口(interface) |
1 | 普通方法+抽象方法 | 抽象方法+全局常量 |
2 | 使用extends关键字继承抽象类 | 使用implements实现接口 |
3 | 一个抽象类可以实现若干个接口 | 接口不能继承抽象类,当但是借口可以使用extends关键字继承多个父接口 |
4 | 可以有各种权限 | public |
5 | 一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 |
- 抽象类描述的是“是不是”的问题,而接口描述的是“有没有”的问题;接口强调特定功能的实现,而抽象类强调所属关系。
- 接口只有定义,不能有方法的实现,而抽象类可以有定义与实现,方法可在抽象类中实现。
- 实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
- 接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
- 接口被用于常用的功能,便于日后维护和添加删除,而抽象类更倾向于充当公共类的角色,不适用于日后重新对立面的代码修改。功能需要累积时用抽象类,不需要累积时用接口。
(注:此处来自于此文章)
除了单继承的局限之外,实际上使用抽象类和接口都是类似的。在实际开发中,抽象类的设计比接口复杂。
- 接口是Java的核心。
- 开发之中优先考虑接口,以避免单继承局限。
- 抽象类是模板,有层次感。
- 接口则更关心行为与混合。