3.1、 抽象类
看我们以前示例中的Person、Student和Employee,从我们使用的角度来看主要对Student和Employee进行实例化,Person中主要包含了一些公共的属性和方法,而Person我们通常不会实例化,所以我们可以把它定义成抽象的:
● 在java中采用abstract关键字定义的类就是抽象类,采用abstract关键字定义的方法就是抽象方法
● 抽象的方法只需在抽象类中,提供声明,不需要实现
● 如果一个类中含有抽象方法,那么这个类必须定义成抽象类
● 如果这个类是抽象的,那么这个类被子类继承,抽象方法必须被重写。如果在子类中不复写该抽象方法,那么必须将此类再次声明为抽象类
● 抽象的类是不能实例化的,就像现实世界中人其实是抽象的,张三、李四才是具体的
● 抽象类不能被final修饰(它是需要被继承的)
● 抽象方法不能被final修饰,因为抽象方法就是被子类实现的
抽象类中可以包含方法实现,可以将一些公共的代码放到抽象类中,另外在抽象类中可以定义一些抽象的方法,这样就会存在一个约束,而子类必须实现我们定义的方法,如:teacher必须实现printInfo方法,Student也必须实现printInfo方法,方法名称不能修改,必须为printInfo,这样就能实现多态的机制,有了多态的机制,我们在运行期就可以动态的调用子类的方法。所以在运行期可以灵活的互换实现。
3.1.1、采用abstract声明抽象类
public class AbstractTest01 {
public static void main(String[] args) {
//不能实例化抽象类
//抽象类是不存在,抽象类必须有子类继承
Person p = new Person();
//以下使用是正确的,因为我们new的是具体类
Person p1 = new Employee();
p1.setName("张三");
System.out.println(p1.getName());
}
}
//采用abstract定义抽象类
//在抽象类中可以定义一些子类公共的方法或属性
//这样子类就可以直接继承下来使用了,而不需要每个
//子类重复定义
abstract class Person {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
//此方法各个子类都可以使用
public void commonMethod1() {
System.out.println("---------commonMethod1-------");
}
}
class Employee extends Person {
}
class Student extends Person {
}
3.1.2、抽象的方法只需在抽象类中,提供声明,不需要实现,起到了一个强制的约束作用,要求子类必须实现
public class AbstractTest02 {
public static void main(String[] args) {
//Person p = new Employee();
//Person p = new Student();
//Person p = new Person();
p.setName("张三");
p.printInfo();
}
}
abstract class Person {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
//此方法各个子类都可以使用
public void commonMethod1() {
System.out.println("---------commonMethod1-------");
}
//public void printInfo() {
// System.out.println("------Person.printInfo()--------");
//}
//采用abstract定义抽象方法
//如果有一个方法为抽象的,那么此类必须为抽象的
//如果一个类是抽象的,并不要求具有抽象的方法
public abstract void printInfo();
}
class Employee extends Person {
//必须实现抽象的方法
public void printInfo() {
System.out.println("Employee.printInfo()");
}
}
class Student extends Person {
//必须实现抽象的方法
public void printInfo() {
System.out.println("Student.printInfo()");
}
}
3.1.3、如果这个类是抽象的,那么这个类被子类继承,抽象方法必须被覆盖。如果在子类中不覆盖该抽象方法,那么必须将此方法再次声明为抽象方法
public class AbstractTest03 {
public static void main(String[] args) {
//此时不能再new Employee了
Person p = new Employee();
}
}
abstract class Person {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
//此方法各个子类都可以使用
public void commonMethod1() {
System.out.println("---------commonMethod1-------");
}
//采用abstract定义抽象方法
public abstract void printInfo();
}
abstract class Employee extends Person {
//再次声明该方法为抽象的
public abstract void printInfo();
}
class Student extends Person {
//实现抽象的方法
public void printInfo() {
System.out.println("Student.printInfo()");
}
}
3.1.4、抽象类不能被final修饰
public class AbstractTest04 {
public static void main(String[] args) {
}
}
//不能采用final修改抽象类
//两个关键字是矛盾的
final abstract class Person {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
//此方法各个子类都可以使用
public void commonMethod1() {
System.out.println("---------commonMethod1-------");
}
//采用abstract定义抽象方法
public abstract void printInfo();
}
class Employee extends Person {
//实现抽象的方法
public void printInfo() {
System.out.println("Student.printInfo()");
}
}
class Student extends Person {
//实现抽象的方法
public void printInfo() {
System.out.println("Student.printInfo()");
}
}
3.1.5、抽象方法不能被final修饰
public class AbstractTest05 {
public static void main(String[] args) {
}
}
abstract class Person {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
//此方法各个子类都可以使用
public void commonMethod1() {
System.out.println("---------commonMethod1-------");
}
//不能采用final修饰抽象的方法
//这两个关键字存在矛盾
public final abstract void printInfo();
}
class Employee extends Person {
//实现抽象的方法
public void printInfo() {
System.out.println("Student.printInfo()");
}
}
class Student extends Person {
//实现抽象的方法
public void printInfo() {
System.out.println("Student.printInfo()");
}
}
3.1.6、抽象类中可以没有抽象方法(参见3.1.1)
4.1、 接口(行为)
接口我们可以看作是抽象类的一种特殊情况,在接口中只能定义抽象的方法和常量
- 在java中接口采用interface声明
- 接口中的方法默认都是public abstract的,不能更改
- 接口中的变量默认都是public static final类型的,不能更改,所以必须显示的初始化
- 接口不能被实例化,接口中没有构造函数的概念
- 接口之间可以继承,但接口之间不能实现
- 接口中的方法只能通过类来实现,通过implements关键字
- 如果一个类实现了接口,那么接口中所有的方法必须实现
- 一类可以实现多个接口
- 接口可以继承多个接口
4.1.1、接口中的方法默认都是public abstract的,不能更改
public class InterfaceTest01 {
public static void main(String[] args) {
}
}
//采用interface定义接口
//定义功能,没有实现
//实现委托给类实现
interface StudentManager {
//正确,默认public abstract 等同public abstract void addStudent(int id, String name);
public void addStudent(int id, String name);
//正确
//public abstract void addStudent(int id, String name);
//正确,可以加入public修饰符,此种写法较多
public void delStudent(int id);
//正确,可以加入abstract,这种写法比较少
public abstract void modifyStudent(int id, String name);
//编译错误,因为接口就是让其他人实现
//采用private就和接口原本的定义产生矛盾了
private String findStudentById(int id);
}
4.1.2、接口中的变量是public static final类型的,不能更改,所以必须显示的初始化
public class InterfaceTest02 {
public static void main(String[] args) {
//不能修改,因为final的
//StudentManager.YES = "abc";
System.out.println(StudentManager.YES);
}
}
interface StudentManager {
//正确,默认加入public static final
String YES = "yes";
//正确, 开发中一般就按照下面的方式进行声明
public static final String NO = "no";
//错误,必须赋值,因为是final的
//int ON;
//错误,不能采用private声明
private static final int OFF = -1;
}
4.1.3、接口不能被实例化,接口中没有构造函数的概念
public class InterfaceTest03 {
public static void main(String[] args) {
//接口是抽象类的一种特例,只能定义方法和变量,没有实现
//所以不能实例化
StudentManager studentManager = new StudentManager();
}
}
interface StudentManager {
public void addStudent(int id, String name);
}
4.1.4、接口之间可以[多]继承,但接口之间不能实现
public class InterfaceTest04 {
public static void main(String[] args) {
}
}
interface inter1 {
public void method1();
public void method2();
}
interface inter2 {
public void method3();
}
//接口可以继承
interface inter3 extends inter1 {
public void method4();
}
//接口不能实现接口
//接口只能被类实现
interface inter4 implements inter2 {
public void method3();
}
4.1.5、如果一个类实现了接口,那么接口中所有的方法必须实现
public class InterfaceTest05 {
public static void main(String[] args) {
//Iter1Impl实现了Inter1接口
//所以它是Inter1类型的产品
//所以可以赋值
Inter1 iter1 = new Iter1Impl();
iter1.method1();
//Iter1Impl123实现了Inter1接口
//所以它是Inter1类型的产品
//所以可以赋值
iter1 = new Iter1Impl123();
iter1.method1();
//可以直接采用Iter1Impl来声明类型
//这种方式存在问题
//不利于互换,因为面向具体编程了
Iter1Impl iter1Impl = new Iter1Impl();
iter1Impl.method1();
//不能直接赋值给iter1Impl
//因为Iter1Impl123不是Iter1Impl类型
//iter1Impl = new Iter1Impl123();
//iter1Impl.method1();
}
}
//接口中的方法必须全部实现
class Iter1Impl implements Inter1 {
public void method1() {
System.out.println("method1");
}
public void method2() {
System.out.println("method2");
}
public void method3() {
System.out.println("method3");
}
}
class Iter1Impl123 implements Inter1 {
public void method1() {
System.out.println("method1_123");
}
public void method2() {
System.out.println("method2_123");
}
public void method3() {
System.out.println("method3_123");
}
}
abstract class Iter1Impl456 implements Inter1 {
public void method1() {
System.out.println("method1_123");
}
public void method2() {
System.out.println("method2_123");
}
//再次声明成抽象方法
public abstract void method3();
}
//定义接口
interface Inter1 {
public void method1();
public void method2();
public void method3();
}
4.1.6、一类可以实现多个接口
public class InterfaceTest06 {
public static void main(String[] args) {
//可以采用Inter1定义
Inter1 inter1 = new InterImpl();
inter1.method1();
//可以采用Inter2定义
Inter2 inter2 = new InterImpl();
inter2.method2();
//可以采用Inter3定义
Inter3 inter3 = new InterImpl();
inter3.method3();
}
}
//实现多个接口,采用逗号隔开
//这样这个类就拥有了多种类型
//等同于现实中的多继承
//所以采用java中的接口可以实现多继承
//把接口粒度划分细了,主要使功能定义的含义更明确
//可以采用一个大的接口定义所有功能,替代多个小的接口,
//但这样定义功能不明确,粒度太粗了
class InterImpl implements Inter1, Inter2, Inter3 {
public void method1() {
System.out.println("----method1-------");
}
public void method2() {
System.out.println("----method2-------");
}
public void method3() {
System.out.println("----method3-------");
}
}
interface Inter1 {
public void method1();
}
interface Inter2 {
public void method2();
}
interface Inter3 {
public void method3();
}
/*
interface Inter {
public void method1();
public void method2();
public void method3();
}
*/
4.1.7、一个接口可以继承多个接口
//声明接口:interface
interface A{
public static final int CONST = 1;
void a();//接口中的方法默认都是public abstract的
}
interface K{
void k();
}
//接口可以继承多个接口
interface B extends A,K{
void b();
int d();
}
4.1.8、 接口的进一步应用
在java中接口其实描述了类需要做的事情,类要遵循接口的定义来做事,使用接口到底有什么本质的好处?可以归纳为两点:(提供了标准、规范)
● 采用接口明确的声明了它所能提供的服务
● 解决了Java单继承的问题(一个类可以实现多个接口,一个接口可以继承多个接口)
● 实现了可接插性(重要)
示例:完成学生信息的增删改操作,系统要求适用于多个数据库,如:适用于Oracle和MySQL;
第一种方案,不使用接口,每个数据库实现一个类:
//Oracle的实现
public class StudentOracleImpl {
public void add(int id, String name) {
System.out.println("StudentOracleImpl.add()");
}
public void del(int id) {
System.out.println("StudentOracleImpl.del()");
}
public void modify(int id, String name) {
System.out.println("StudentOracleImpl.modify()");
}
}
需求发生变化了,客户需要将数据移植Mysql上
Mysql的实现
public class StudentMysqlImpl {
public void addStudent(int id, String name) {
System.out.println("StudentMysqlImpl.addStudent()");
}
public void deleteStudent(int id) {
System.out.println("StudentMysqlImpl.deleteStudent()");
}
public void udpateStudent(int id, String name) {
System.out.println("StudentMysqlImpl.udpateStudent()");
}
}
调用以上两个类完成向Oracle数据库和Mysql数据存储数据
public class StudentManager {
public static void main(String[] args) {
//对Oracle数据库的支持
/*
StudentOracleImpl studentOracleImpl = new StudentOracleImpl();
studentOracleImpl.add(1, "张三");
studentOracleImpl.del(1);
studentOracleImpl.modify(1, "张三");
*/
//需要支持Mysql数据库
StudentMysqlImpl studentMysqlImpl = new StudentMysqlImpl();
studentMysqlImpl.addStudent(1, "张三");
studentMysqlImpl.deleteStudent(1);
studentMysqlImpl.udpateStudent(1, "张三");
}
}
以上代码不能灵活的适应需求,当需求发生改变需要改动的代码量太大,这样可能会导致代码的冗余,另外可能会导致项目的失败,为什么会导致这个问题,在开发中没有考虑到程序的扩展性,就是一味的实现,这样做程序是不行的,所以大的项目比较追求程序扩展性,有了扩展性才可以更好的适应需求。
第二种方案,使用接口
UML,统一建模语言
public class Student4OracleImpl implements IStudent {
public void add(int id, String name) {
System.out.println("Student4OracleImpl.add()");
}
public void del(int id) {
System.out.println("Student4OracleImpl.del()");
}
public void modify(int id, String name) {
System.out.println("Student4OracleImpl.modify()");
}
}
public class Student4MysqlImpl implements IStudent {
public void add(int id, String name) {
System.out.println("Student4MysqlImpl.add()");
}
public void del(int id) {
System.out.println("Student4MysqlImpl.del()");
}
public void modify(int id, String name) {
System.out.println("Student4MysqlImpl.modify()");
}
}
public class StudentService {
public static void main(String[] args) {
/*
IStudent istudent = new Student4OracleImpl();
IStudent istudent = new Student4MysqlImpl();
istudent.add(1, "张三");
istudent.del(1);
istudent.modify(1, "张三");
*/
//IStudent istudent = new Student4OracleImpl();
//IStudent istudent = new Student4MysqlImpl();
//doCrud(istudent);
//doCrud(new Student4OracleImpl());
//doCrud(new Student4MysqlImpl());
//doCrud(new Student4OracleImpl());
doCrud(new Student4MysqlImpl());
}
//此种写法没有依赖具体的实现
//而只依赖的抽象,就像你的手机电池一样:你的手机只依赖电池(电池是一个抽象的事物),
//而不依赖某个厂家的电池(某个厂家的电池就是具体的事物了)
//因为你依赖了抽象的事物,每个抽象的事物都有不同的实现
//这样你就可以利用多态的机制完成动态绑定,进行互换,是程序具有较高的灵活
//我们尽量遵循面向接口(抽象)编程,而不要面向实现编程
public static void doCrud(IStudent istudent) {
istudent.add(1, "张三");
istudent.del(1);
istudent.modify(1, "张三");
}
//以下写法不具有扩展性
//因为它依赖了具体的实现
//建议不要采用此种方法,此种方案是面向实现编程,就依赖于具体的东西了
/*
public static void doCrud(Student4OracleImpl istudent) {
istudent.add(1, "张三");
istudent.del(1);
istudent.modify(1, "张三");
}
*/
}
接口和抽象类的区别?
- 接口描述了方法的特征,不给出实现,一方面解决java的单继承问题,实现了强大的可接插性
- 抽象类提供了部分实现,抽象类是不能实例化的,抽象类的存在主要是可以把公共的代码移植到抽象类中
- 面向接口编程,而不要面向具体编程(面向抽象编程,而不要面向具体编程)
- 优先选择接口(因为继承抽象类后,此类将无法再继承,所以会丧失此类的灵活性)