抽象类概述
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有 意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。类和类之间具有共同特征,将这些共同特征提取出来,形成的就是抽象类。类本身是不存在的,所以抽象类无法创建对象《无法实例化》。
抽象类与抽象方法
- 用abstract关键字来修饰一个类,这个类叫做抽象类。
- 用abstract来修饰一个方法,该方法叫做抽象方法。 抽象方法:只有方法的声明,没有方法的实现。以分号结束: 比如:public abstract void talk();
抽象方法
使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式:
抽象类
如果一个类包含抽象方法,那么该类必须是抽象类。抽象的方法只需在抽象类中,提供声明,不需要实现,起到了一个强制的约束作用,要求子类必须实现
定义格式:
抽象类的成员特点
- 成员变量:既可以是变量也可以是常量
- 构造方法:可以有空参构造也可以有参构造
- 成员方法:可以有抽象方法也可以有普通方法
抽象的使用
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父 类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。
代码演示
定义抽象类
/* 抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。 抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。 如何使用抽象类和抽象方法: 1. 不能直接创建new抽象类对象。 2. 必须用一个子类来继承抽象父类。 3. 子类必须覆盖重写抽象父类当中所有的抽象方法。 覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。 4. 创建子类对象进行使用。 */ public abstract class Animal { // 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。 public abstract void eat(); // 这是普通的成员方法 public void normalMethod() { } }
定义实现类
package cn.itcast.day09.demo11; public class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼"); } }
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。
定义测试类
package cn.itcast.day09.demo11; public class DemoMain { public static void main(String[] args) { // Animal animal = new Animal(); // 错误写法!不能直接创建抽象类对象 Cat cat = new Cat(); cat.eat(); } }
注意事项
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
- 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
- 理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
- 理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设 计。
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
- 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
抽象类不能被 final 修饰
- 抽象方法不能被 final 修饰,因为抽象方法就是被子类实现的
Java语言中凡是没有方法体的方法都是抽象方法。不对,错误的。
- Object类中就有很多方法都没有方法体,都是以“;”结尾的,但他们都不是抽象方法,例如:public native int hashCode();这个方法底层调用了C++写的动态链接库程序。前面修饰符列表中没有:abstract。有一个native。表示调用JVM本地程序。
应用
- 抽象类中可以包含方法实现,可以将一些公共的代码放到抽象类中,另外在抽象类中可以定义一些抽象的方法,这样就会存在一个约束,而子类必须实现我们定义的方法,如:teacher 必须实现 printInfo 方法,Student 也必须实现 printInfo 方法,方法名称不能修改,必须为 printInfo,这样就能实现多态的机制,有了多态的机制,我们在运行期就可以动态的调用子类的方法。所以在运行期可以灵活的互换实现。
接口
什么是接口?
- 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方 法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又 没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能" 的关系。
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。接口(interface)是抽象方法和常量值定义的集合。
接口的特点
- 用interface来定义。
- 接口中的所有成员变量都默认是由public static final修饰的。
- 接口中的所有抽象方法都默认是由public abstract修饰的。
- 接口中没有构造器。
- 接口采用多继承机制。
- 接口支持多继承,一个接口可以继承多个接口。
定义格式
示例:
/* 接口: 1、接口也是一种“引用数据类型”。编译之后也是一个class字节码文件。 2、接口是完全抽象的。(抽象类是半抽象。)或者也可以说接口是特殊的抽象类。 3、接口怎么定义,语法是什么? [修饰符列表] interface 接口名{} 4、接口支持多继承,一个接口可以继承多个接口。 5、接口中只包含两部分内容,一部分是:常量。一部分是:抽象方法。接口中没有其它内容了。只有以上两部分。 6、接口中所有的元素都是public修饰的。(都是公开的。) 7、接口中的抽象方法定义时:public abstract修饰符可以省略。 8、接口中的方法都是抽象方法,所以接口中的方法不能有方法体。 9、接口中的常量的public static final可以省略。 */ public class Test01{ public static void main(String[] args){ // 访问接口的常量。 System.out.println(MyMath.PI); // 常量能重新赋值吗? //错误: 无法为最终变量PI分配值 //MyMath.PI = 3.1415928; //错误: 无法为最终变量k分配值 //MyMath.k = 111; } } // 定义接口 interface A{ } // 接口支持继承 interface B extends A{ } // 一个接口可以继承多个接口(支持多继承) interface C extends A, B{ } // 我的数学接口 interface MyMath{ // 常量 //public static final double PI = 3.1415926; // public static final可以省略吗? double PI = 3.1415926; // k是不是常量????是。 // 接口中随便写一个变量就是常量。 // 常量:值不能发生改变的变量。 int k = 100; // 抽象方法 //public abstract int sum(int a, int b); // 接口当中既然都是抽象方法,那么在编写代码的时候,public abstract可以省略吗?可以的 int sum(int a, int b); // 接口中的方法可以有方法体吗?错误: 接口抽象方法不能带有主体 // 相减的抽象方法 int sub(int a, int b); }
注意事项:
- 定义Java类的语法格式:先写extends,后写implements class SubClass extends SuperClass implements InterfaceA{ }
- 一个类可以实现多个接口,接口也可以继承其它接口。
- 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
- 接口的主要用途就是被实现类实现。(面向接口编程)
- 与继承关系类似,接口与实现类之间存在多态性
- 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲, 接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义 (JDK7.0及之前),而没有变量和方法的实现。
- 接口A和接口B虽然没有继承关系,但是写代码的时候,可以互转。编译器没意见。但是运行时可能出现:ClassCastException
接口和抽象类的区别
- 接口描述了方法的特征,不给出实现,一方面解决 java 的单继承问题,实现了强大的可接插性
- 抽象类提供了部分实现,抽象类是不能实例化的,抽象类的存在主要是可以把公共的代码移植到抽象类中
- 面向接口编程,而不要面向具体编程(面向抽象编程,而不要面向具体编程)
- 优先选择接口(因为继承抽象类后,此类将无法再继承,所以会丧失此类的灵活性)
继承和实现都存在的话,代码应该怎么写?
/* 继承和实现都存在的话,代码应该怎么写? extends 关键字在前。 implements 关键字在后。 */ public class Test04{ public static void main(String[] args){ // 创建对象(表面看Animal类没起作用!) Flyable f = new Cat(); //多态。 f.fly(); // 同一个接口 Flyable f2 = new Pig(); // 调用同一个fly()方法,最后的执行效果不同。 f2.fly(); Flyable f3 = new Fish(); f3.fly(); } } // 动物类:父类 class Animal{ } // 可飞翔的接口(是一对翅膀) // 能插拔的就是接口。(没有接口你怎么插拔。) // 内存条插到主板上,他们之间有接口。内存条可以更换。 // 接口通常提取的是行为动作。 interface Flyable{ void fly(); } // 动物类子类:猫类 // Flyable是一个接口,是一对翅膀的接口,通过接口插到猫身上,让猫变的可以飞翔。 class Cat extends Animal implements Flyable{ public void fly(){ System.out.println("飞猫起飞,翱翔太空的一只猫,很神奇,我想做一只猫!!"); } } // 蛇类,如果你不想让它飞,可以不实现Flyable接口 // 没有实现这个接口表示你没有翅膀,没有给你插翅膀,你肯定不能飞。 class Snake extends Animal{ } // 想飞就插翅膀这个接口。 class Pig extends Animal implements Flyable{ public void fly(){ System.out.println("我是一只会飞的猪!!!"); } } // 鱼(默认实际上是存在继承的,默认继承Object。) /* class Fish extends Object implements Flyable{ } */ class Fish implements Flyable{ //没写extends,也是有的,默认继承Object。 public void fly(){ System.out.println("我是六眼飞鱼(流言蜚语)!!!"); } }
Java 7及以前的JDK
那么接口中可以包含的内容有常量:
/* 接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。 从效果上看,这其实就是接口的【常量】。 格式: public static final 数据类型 常量名称 = 数据值; 备注: 一旦使用final关键字进行修饰,说明不可改变。 注意事项: 1. 接口当中的常量,可以省略public static final,注意:不写也照样是这样。 2. 接口当中的常量,必须进行赋值;不能不赋值。 3. 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则) */ public interface MyInterfaceConst { // 这其实就是一个常量,一旦赋值,不可以修改 public static final int NUM_OF_MY_CLASS = 12; }
也可以包含的内容抽象方法
/* 在任何版本的Java中,接口都能定义抽象方法。 格式: public abstract 返回值类型 方法名称(参数列表); 注意事项: 1. 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract 2. 这两个关键字修饰符,可以选择性地省略。 3. 方法的三要素,可以随意定义。 */ public interface MyInterfaceAbstract { // 这是一个抽象方法 public abstract void methodAbs1(); // 这也是抽象方法 abstract void methodAbs2(); // 这也是抽象方法 public void methodAbs3(); // 这也是抽象方法 void methodAbs4(); }
从Java 8开始
接口里允许定义默认方法
/* 从Java 8开始,接口里允许定义默认方法。 格式: public default 返回值类型 方法名称(参数列表) { 方法体 } 备注:接口当中的默认方法,可以解决接口升级的问题。 */ public interface MyInterfaceDefault { // 抽象方法 public abstract void methodAbs(); // 新添加的默认方法 public default void methodDefault() { System.out.println("这是新添加的默认方法"); } }
从Java 9开始
接口当中允许定义私有方法
/* 问题描述: 我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。 但是这个共有方法不应该让实现类使用,应该是私有化的。 1. 普通私有方法,解决多个默认方法之间重复代码问题 格式: private 返回值类型 方法名称(参数列表) { 方法体 } 2. 静态私有方法,解决多个静态方法之间重复代码问题 格式: private static 返回值类型 方法名称(参数列表) { 方法体 } */ public interface MyInterfacePrivateA { public default void methodDefault1() { System.out.println("默认方法1"); methodCommon(); } public default void methodDefault2() { System.out.println("默认方法2"); methodCommon(); } private void methodCommon() { System.out.println("AAA"); System.out.println("BBB"); System.out.println("CCC"); } }
注意事项
- 若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突。 解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。
- 若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。
接口的使用
1. 接口不能直接使用,必须有一个“实现类”来“实现”该接口。
格式:
public class 实现类名称 implements 接口名称 {
// ...
}
2. 接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。实现:去掉abstract关键字,加上方法体大括号。
3. 创建实现类的对象,进行使用。
接口在开发中的作用
- 接口在开发中的作用,类似于多态在开发中的作用。多态:面向抽象编程,不要面向具体编程。降低程序的耦合度。提高程序的扩展力。
总结一句话:三个字“解耦合”
- 面向接口编程,可以降低程序的耦合度,提高程序的扩展力。符合OCP开发原则。接口的使用离不开多态机制。(接口+多态才可以达到降低耦合度。)
- 接口可以解耦合,解开的是谁和谁的耦合!!!任何一个接口都有调用者和实现者。接口可以将调用者和实现者解耦合。调用者面向接口调用。实现者面向接口编写实现。
- 以后进行大项目的开发,一般都是将项目分离成一个模块一个模块的,模块和模块之间采用接口衔接。降低耦合度。
类型和类型之间的关系:
is a(继承)、has a(关联)、like a(实现)
- is a:Cat is a Animal(猫是一个动物)凡是能够满足is a的 表示“继承关系”A extends B
- has a:I has a Pen(我有一支笔)凡是能够满足has a关系的表示“关联关系” 关联关系通常以“属性”的形式存在。
- like a: Cooker like a FoodMenu(厨师像一个菜单一样)凡是能够满足like a关系的表示“实现关系”实现关系通常是:类实现接口。A implements B
类和接口的关系
- 类与类的关系:继承关系,只能单继承,但是可以多层继承
- 类与接口的关系:实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
- 接口与接口的关系: 继承关系,可以单继承,也可以多继承