Java中关于抽象类的深刻理解

一、在学习抽象类之前的友情提示:

1、关于抽象类的理解:

  • 如果仅仅只是学习Java基础知识,那么很难完全理解领悟抽象类的作用;
  • 随着学习的深入,会有专门的类库的Java系统类库的讲解,会有大量的抽象类出现,通过系统提供的抽象类的设计可以加深抽象类的概念;
  • 如果想真正的将抽象类运用到极致,就必须进行一些实际的项目演练或者进行更加深入的学习。

2、考虑过为什么要使用抽象类吗?

  • 在整个面向对象程序设计之中,如果想扩充类的功能,最佳的做法是通过继承的方式来处理,但是对于普通类的继承概念来说会议一个问题存在:类在继承的时候父类是不能够对子类提出严格的覆写要求的

例:我们知道,在Java中默认提供有一个Object的祖父类,而这个Object父类之中提供有专门的对象输出方法(toString()),但是这个输出方法是根据每一个子类自己的要求来决定是否需要覆写的,也就是说Object类中并没有严格约束来控制子类的覆写。

范例:普通类继承之间的尴尬设计

class Book{ //完整的类
    //这个类已经有了明确的要求:输出对象的时候必须要提供完善的信息
    //Object类并无法对这个子类是否覆写toString方法提出严格的定义要求
    //子类可以根据自己的心情来决定是否要覆写
}

public class Demo{
    public static void main(String args[]){
        System.out.println(new Book());
    }
}

//程序执行结果:Book@2ff4acd0

这个时候发现普通类无法对子类有要求,所以自然一些标准化的操作实现就无法正确处理了,那么在这样的时候如果希望子类继承某些父类的时候可以出现一些明确的方法覆写要求,那么就必须通过抽象类的形式来描述,即:抽象类的子类和普通类的子类相比最大的区别就在于:抽象类可以明确的要求子类覆写那些特定的方法,在以后是实际的项目开发过程中,对于extends类的继承来讲,更多的时候继承的不是普通类(功能完善的类),而是抽象类。

二、抽象类的基本定义

抽象类在Java之中必须使用abstract关键字来进行类的定义,所以抽象类的基本语法形式:

[public] abstract class 抽象类名称 {}

既然现在定义为了抽象类,那么如果想对子类的方法覆写提出严格的要求,就可以通过抽象方法的形式来完成,而所谓的抽象方法指的是通过abstract定义的方法,并且抽象方法是没有方法体的

Java中的抽象的解释_Java中的抽象的解释


范例:定义抽象类

abstract class Book{ //抽象类
    //如果一个类中定义有抽象方法,那么这个类必须声明为抽象类
    public abstract void read();//抽象方法,没有方法体
    public String toString(){//定义普通方法
        return "【Book】这是一本图书的信息";
    }
}

此时已经成功地实现了抽象类的定义,同时内部也存在有一个抽象方法,但需要注意的是,抽象方法并不是具体的方法,所以抽象方法是不可能直接调用的,这样一来也就同时表明,抽象类是不能够直接进行对象实例化处理的

范例:抽象类的错误使用

public class Demo{
    public static void main(String args[]){
        Book book = new Book();
    }
}
//这时,编译时会出现错误提示:Book是抽象的,无法进行实例化。

按照正常的程序的设计来讲,如果一个类已经可以明确的产生一个实例化对象,那么这个实例化对象就可以直接进行类中的方法调用了,而抽象方法是没有实现的,所以如果想使用抽象类,则必须按照如下的原则进行:

  • 抽象类必须有子类,子类通过extends关键字只能继承一个父类(存在单继承的限制);
  • 抽象类的子类(如果不是抽象类),则必须要覆写抽象类中的全部抽象方法(子类覆写强制约定);
  • 抽象类的子类通过对象的上转型机制,为抽象类进行对象实例化。

范例:抽象类的正确使用

abstract class Book{ //抽象类
    //如果一个类中定义有抽象方法,那么这个类必须声明为抽象类
    public abstract void read();//抽象方法,没有方法体
    public String toString(){//定义普通方法
        return "【Book】这是一本图书的信息";
    }
}

class MathBook extends Book{//普通类
	public void read(){
        System.out.println("【MathBook】开始阅读数学类的图书。");
    }
}

public class Demo{
    public static void main(String args[]){
         Book book = new MathBook();//对象的上转型
       	 book.read();
    }
}


//程序的运行结果:【MathBook】开始阅读数学类的图书。

通过以上的代码可以清楚的发现,使用抽象类就是为了对子类的方法覆写提出严格的控制要求,这样就非常方便的实现对子类的要求。

三、抽象类的相关说明

经过了之前的讲解可以发现抽象类最大的特点就是可以直接针对于子类的方法覆写提出明确的要求,但是除了这些基本的实现以外,实际上抽象类也存在有一些特殊的说明。

1、抽象类由于必须被子类所继承,所以抽象类中是不允许使用final关键字来进行类的定义的。

范例:错误的抽象类定义

final abstract class Book{ //抽象类
    //如果一个类中定义有抽象方法,那么这个类必须声明为抽象类
    public abstract void read();//抽象方法,没有方法体
    public String toString(){//定义普通方法
        return "【Book】这是一本图书的信息";
    }
}
//编译时会出现错误提示:非法的修饰符组合:abstract和final

2、抽象类与普通类相比仅仅是增加了一些抽象方法的定义而已,但是抽象类里面允许不定义抽象方法,即使没有定义抽象方法也不能直接实例化抽象类对象。

范例:错误的抽象类使用

abstract class Book{ //抽象类
    //如果一个类中定义有抽象方法,那么这个类必须声明为抽象类
    public abstract void read();//抽象方法,没有方法体
    public String toString(){//定义普通方法
        return "【Book】这是一本图书的信息";
    }
}

public class Demo{
    public static void main(String args[]){
         Book book = new MathBook();//对象的上转型
    }
}
//这时,编译时会出现错误提示:Book是抽象的,无法进行实例化。

只要使用者声明了抽象类,那么不管这个抽象类是否有普通方法,都不能对其进行直接的实例化,这种开发机制属于一种比较常见的面向对象的设计结构,因为需要基于抽象类进行一些过渡上的设计,这一点需要结合接口以及后续的学习的源代码进行使用的分析。

3、抽象类之中允许提供有普通属性、普通方法、构造方法,所以抽象类子类在进行子类对象实例化(调用子类构造方法)会按照标准的子类对象实例化流程来进行处理,即:会默认调用父类中无参构造,或者通过”super()"的形式选择父类构造。

范例:观察抽象类中的构造方法的调用

abstract class Book{ //抽象类
   	private String title;
    private String author;
    private double price;
    public Book(){}
    public Book(String title,String author,double price){
        this.title = title ;
        this.author = author;
        this.price = price;
    }

    public String toString(){//定义普通方法
        return "图书名称"+this.title+"、图书作者:"+this.author+"、图书价格:"+this.price;
    }
}

class MathBook extends Book{//继承父类
    private String type;//分类
    public MathBook(){}
    public MathBook(String title,String author,double price,String type){
        super(title,author,price);//调用父类构造
        this.type = type; //保存子类的属性内容
    }
	public String toString(){
        return super.toString()+"、分类:"+this.type;
    }
}

public class Demo{
    public static void main(String args[]){
         Book book = new MathBook("线性代数","小贾",59.83,"高等数学");//对象的上转型
       	 System.out.println(book);
    }
}

//程序执行结果:图书名称:线性代数、图书作者:小贾、图书价格:59.83、分类:高等数学

4、在类之中如果定义有static方法的时候,所有的static操作结构实际上并不会受到类实例化对象的影响,所以现在也可以在一个抽象类中定义static方法,并且也可以由类名称直接调用。

范例:在抽象类中定义static方法

abstract class Book{ //抽象类
    //如果一个类中定义有抽象方法,那么这个类必须声明为抽象类
    public abstract void read();//抽象方法,没有方法体
    public static Book getBook(){//静态方法
        return new mathBook();
    }
}

class MathBook extends Book{//继承父类
	public void read(){
        System.out.println("【MathBook】开始阅读数学类的图书。");
    }
}

public class Demo{
    public static void main(String args[]){
         Book book = Book.getBook;//对象的上转型
       	 book.read();
    }
}

//程序执行结果:【MathBook】开始阅读数学类的图书。

在以后任何的结构里面,static永远都属于一种独立的操作结构,这种结构最大的特点就在于:随意放在哪个位置上都不会受到实例化对象的影响!

四、模板设计模式

在抽象类实际的应用过程中,有一种模板设计模式的结构,那么这种结构在日后学习到后期Servlet技术的时候就能够见到。

从类的基本定义的概念来讲,类属于现实生活的一种抽象,而抽象类又属于在类的基础上的进一步抽象,现在假设有如下的三种事物:书、汽车、画板,现在假设当前的程序里面只涉及到这三类的结构,对于图书和画板应该拥有读取信息的功能、画板可以实现图形绘制的功能、汽车拥有驾驶的功能,那么要求通过一种统一的操作形式实现这两种运行功能。

Java中的抽象的解释_子类_02

范例:通过具体的代码来实现行为模式的抽象

abstract class Action{//描述一个行为,他是一个总的行为的模板
    //将三个抽象方法分别对应一个全局常量的编号,这个全局常量会传入到command方法中的code参数里
    //为什么不用分别1、2、3来代替呢?
    //用1、2、3的话,READ+PAINT=RUN,这是不符合规则的。必须用一个两两值相加不等于第三个值得一组数字来给全局常量赋值
    public static final READ = 1 ;
    public static final PAINT = 5 ;
    public static final RUN = 10 ;
    
    //定义一个特定的操作行为(参数:抽象方法对应的编号)来调用抽象方法
    public void command(int code){
        //利用一个switch的结构,来实现根据特定的全局常量来调用特定的抽象方法,就相当于把每个行为都对应成遥控器上的按键一样,每个按键都有一个编码值
        switch(code){
            case READ:{
                this.read(); //调用读的操作方法
                break;
            }
            case PAINT:{
                this.paint();//调用画的操作方法
                break;
            }
            case RUN:{
                this.run();//调用跑的操作方法
                break;
            }
        }
    }
    
   	//分别定义读、画、跑的抽象方法
    public abstract void read();
    public abstract void paint();
    public abstract void run();
    
}

/**
在原本的设计理念上,Book类只需要实现read()方法即可,但现在定义了一个普通的类来继承Action类,那么这个时候必须要将Actin类中所有的抽象方法实现,即不仅要实现read()方法,还要实现paint()方法、run()方法,此时,我们解决的方法是将paint和run方法全部定义为空方法,代码中会出现非常多无用的东西,显然这样是比较浪费我们的时间和空间的。
class Book extends Action{
    public void read(){........}
    public void paint(){}
    public void run(){}
}
**/

/**
解决上述问题的方法:加一个过渡的操作(抽象类ActionAdapter继承Action类,此时,无法直接对它进行对象的实例化。在此类之中,进行抽象方法的假实现,避免子类重复实现这些抽象方法。
我们知道,抽象类中不一定必须要有抽象方法,所以我们设计一个抽象类,这个抽象类中将Action类中的方法全部作成"假实现",然后我们就可以定义普通方法,之后就可以根据自己的方式进行方法的覆写。
**/
abstract class ActionAdapter extends Action{
    public void read() {}
	public void paint() {}
	public void run() {}
}

class Book extends ActionAdapter {
	public void read() {
		System.out.println("【Book】认真读书。") ;
	}
}

class Sketchpad extends ActionAdapter { // 画板
	public void read() {
		System.out.println("【Sketchpad】读取图像的思想。") ;
	}
	public void paint() {
		System.out.println("【Sketchpad】在画板上进行绘图。") ;
	}
}

class Car extends ActionAdapter {
	public void run() {
		System.out.println("【Car】汽车在奔跑。") ;
	}
}

public class Demo {
	public static void main(String args[]) {
        // 图书的行为:图书只能实现读的行为
		Action action = new Book() ; 
        
        /**
        利用action.command(Action.全局变量)来调用特定的行为
        **/
        
        //如果此时调用的是读的行为,可以正常的实现
		action.command(Action.READ) ;
        
       //如果此时调用的是画的行为,因为图书不能实现画的行为,所以没有反应。		
   //例如遥控器,A遥控器可以在A电视上使用,但拿到B电视上使用,则有些键无法正常使用,那么按下去则没有反应。
		action.command(Action.PAINT) ;	
        
	}
}

//程序运行结果:【Book】认真读书。

通过以上的操作形式基本上就可以发现,抽象类的设计是在普通类的结构之上的处理结构,其中的Action类是一个模板,模板通过特定的操作行为”command()“会根据不同子类给出不同的处理效果,如果不支持的行为,那么即便调用了也不会有任何的反应,在后续实际项目开发之中会经常见到。同时本程序里面所给出的ActionAdapter(适配器的模式),用这种类做一个假实现,同时这个类也属于抽象类(这个是好的抽象类中没有抽象方法),无法直接进行对象的实例化,依然需要它的子类