• Java中的抽象类详解,它存在的意义在哪里?


    学习抽象类前先理解下面这段话:


    问你个问题,你知道什么是“东西”吗?什么是“物体”吗? 
    “麻烦你,小王。帮我把那个东西拿过来好吗” 
    在生活中,你肯定用过这个词--东西。 
    小王:“你要让我帮你拿那个水杯吗?” 
    你要的是水杯类的对象。而东西是水杯的父类。通常东西类没有实例对象,但我们有时需要东西的引用指向它的子类实例。 
    
    你看你的房间乱成什么样子了,以后不要把东西乱放了,知道么? 
    又是东西,它是一个数组。而数组中的元素都是其子类的实例。 
    --------- 
    上面讲的只是子类和父类。而没有说明抽象类的作用。抽象类是据有一个或多个抽象方法的类,必须声明为抽象类。抽象类的特点是,不能创建实例。 
    
    这些该死的抽象类,也不知道它有什么屁用。我非要把它改一改不可。把抽象类中的抽象方法都改为空实现。也就是给抽象方法加上一个方法体,不过这个方法体是空的。这回抽象类就没有抽象方法了。它就可以不在抽象了。 
    
    当你这么尝试之后,你发现,原来的代码没有任何变化。大家都还是和原来一样,工作的很好。你这回可能更加相信,抽象类根本就没有什么用。但总是不死心,它应该有点用吧,不然创造Java的这伙传说中的天才不成了傻子了吗? 
    
    接下来,我们来写一个小游戏。俄罗斯方块!我们来分析一下它需要什么类?
    我知道它要在一个矩形的房子里完成。这个房子的上面出现一个方块,慢慢的下落,当它接触到地面或是其它方块的尸体时,它就停止下落了。然后房子的上面又会出现一个新的方块,与前一个方块一样,也会慢慢的下落。在它还没有死亡之前,我可以尽量的移动和翻转它。这样可以使它起到落地时起到一定的作用,如果好的话,还可以减下少几行呢。这看起来好象人生一样,它在为后来人努力着。
    当然,我们不是真的要写一个游戏。所以我们简化它。我抽象出两个必须的类,一个是那个房间,或者就它地图也行。另一个是方块。我发现方块有很多种,数一下,共6种。它们都是四个小矩形构成的。但是它们还有很多不同,例如:它们的翻转方法不同。先把这个问题放到一边去,我们回到房子这个类中。
    
    房子上面总是有方块落下来,房子应该有个属性是方块。当一个方块死掉后,再创建一个方块,让它出现在房子的上面。当玩家要翻转方法时,它翻转的到底是哪个方块呢?当然,房子中只有一个方块可以被翻转,就是当前方块。它是房子的一个属性。那这个属性到底是什么类型的呢?方块有很多不同啊,一共有6种之多,我需要写六个类。一个属性不可能有六种类型吧。当然一个属性只能有一种类型。
    
    我们写一个方块类,用它来派生出6个子类。而房子类的当前方块属性的类型是方块类型。它可以指向任何子类。但是,当我调用当前方块的翻转方法时,它的子类都有吗?如果你把翻转方法写到方块类中,它的子类自然也就有了。可以这六种子类的翻转方法是不同的。我们知道'田'方块,它只有一种状态,无论你怎么翻转它。而长条的方块有两种状态。一种是‘-’,另一种是‘|’。这可怎么办呢?我们知道Java的多态性,你可以让子类来重写父类的方法。也就是说,在父类中定义这个方法,子类在重写这个方法。
    
    那么在父类的这个翻转方法中,我写一些什么代码呢?让它有几种状态呢?因为我们不可能实例化一个方块类的实例,所以它的翻转方法中的代码并不重要。而子类必须去重写它。那么你可以在父类的翻转方法中不写任何代码,也就是空方法。
    
    我们发现,方法类不可能有实例,它的翻转方法的内容可以是任何的代码。而子类必须重写父类的翻转方法。这时,你可以把方块类写成抽象类,而它的抽象方法就是翻转方法。当然,你也可以把方块类写为非抽象的,也可以在方块类的翻转方法中写上几千行的代码。但这样好吗?难道你是微软派来的,非要说Java中的很多东西都是没有用的吗?
    
    当我看到方块类是抽象的,我会很关心它的抽象方法。我知道它的子类一定会重写它,而且,我会去找到抽象类的引用。它一定会有多态性的体现。
    
    但是,如果你没有这样做,我会认为可能会在某个地方,你会实例化一个方块类的实例,但我找了所有的地方都没有找到。最后我会大骂你一句,你是来欺骗我的吗,你这个白痴。
    
    把那些和“东西”差不多的类写成抽象的。而水杯一样的类就可以不是抽象的了。当然水杯也有几千块钱一个的和几块钱一个的。水杯也有子类,例如,我用的水杯都很高档,大多都是一次性的纸水杯。
    

    抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。

    抽象类定义是很简单的,这里不写官方的语言,就用白话介绍,抽象类本质是一个类,没问题,那么类里面一般都是有方法的,方法包括方法名和方法体,这是常识对不对,那么什么是抽象类呢?如果一个类里面有一种方法只有方法名却没有方法体,这样的类就是抽象类!

    看以下栗子:

    public abstract class TestAbstract {
    	//这是一个抽象方法,
    	public abstract void run(); 
    	//当然这里面也可以是普通的方法
    	public void eat() {
    		System.out.println("我是一个在抽象类里面的普通方法");
    	}
    }

    这里为了区别普通的类,我们一般加abstract这个关键字,我们就认为他是一个抽象类。既然是一个类,那么普通类的属性他都有,它也可以写普通的方法。

    这里就有人说了,那这个有什么用呢?没有实现体,就是调用也没用啊,JDK也想到这个了,所以呢他是不让你直接实例化调用的,因为没用啊,对吧,这也是为什么抽象类不可以直接实例化自己,这里说实例化自己有些人不明白,说人话就是不可以自己创建一个自己的对象出来,他只能是子类的引用来创建父类的对象。

    举个栗子:

    public static void main(String[] args) {
    		/**
    		 * 抽象类是不可以自己实例化自己的,只能实例化自己的子类,因为只有子类才有方法的实现,自己实例化自己是没有意义的。况且就是自己
    		 * 里面有普通方法的实现,他的子类都是可以使用的。
    		 */
    		TestAbstract t = new TestA01();
    		
    	}

    回到之前的话题,既然有些方法不可以实现,写了做什么呢?难道就为了那几个可以实现的方法?当然不是的,这里的抽象类是为了子类更好的实现。

    我们举个简单的例子:我们有一个动物的类,里面有一个Run的方法,这个时候我们需要继承他,一只狗说我会跑,老虎说我也会跑,孔雀说我也会跑,这个时候每一个子类都要继承他,而且由于Run方法已经被父类实现了,所以每一个都要重写方法体,是不是很麻烦,这个时候JDK就说了,既然那么多类需要继承他,我直接不实现这个方法,你们谁用谁实现算了。这个就是抽象类存在的意义!

    说的比较官方一些的话,就是抽象类可以将设计和实现分离,你写你的抽象类,我写我的实现方法。这也是为什么说抽象方法必须被继承才有意义!

    举个栗子:

    class TestA01 extends TestAbstract{
    	/**
    	 * @Override 是注解,JDK5.0以后的新特性,重写的意思,也就是说,如果是注解了的话,就是重写的方法,名字是不可以改的, 如果去掉注解,说明不是重写的方法
    	 * 名字是可以改掉的。
    	 */
    	@Override
    	public void run() {
    		System.out.println("我是子类的run()");
    	}
    	
    }
    • 有抽象方法的类必然是抽象类
    •  抽象类不可以被实例化,不能被new来实例化抽象类
    •  抽象类可以包含属性,方法,构造方法,但是构造方法不能用来new实例,只能被子类调用
    •  抽象类只能用来继承
    •  抽象类的抽象方法必须被子类继承

    下面我们说一下接口:

    接口是我觉得Java里面相当伟大的一个发明,为什么呢?听我说完,接口我们可以认为本质也是一个类,只是修饰符改为了interface,类的修饰符是Class而已,那么接口是干嘛呢?前面讲了抽象类的使用,接口就是一个比抽象类还要抽象的类,前面说抽象类里面可以写普通的方法,说明还不够抽象,抽象的不够彻底,接口说干脆一不做二不休,规定只能写抽象方法算了,所以说接口是比抽象方法更抽象的类。

    举个栗子:

    public interface MyIinterface {
    	/**
    	 * 接口里面只有常量和抽象方法
    	 */
    	/*public static final   接口中常量定义时都有这个,写不写都是这样*/String MAX_GREAD = "BOSS";
    	int MAX_SPEED = 120;
    	/*public abstatic  这里一样写不写都是public,因为不用public的话没有意义*/ void test01();
    	public int test02(int a, int b);
    	
    }

    有的人说不能被继承,不是和抽象类一样吗?为什么不写abstract关键字呢?不能被普通方法调用,不是静态变量吗?是的,说的都对,所以JDK这里不管你写不写,都是默认前面有这些修饰词的,上面我写的很明白!

    上面有句话说不同public的话没有意义,其实写到这里我们可以基本认为接口和抽象类是一种规则了,它规定你这样用,你只要继承或者实现,就必须要按照他的来,所以我们对应到现实生活中的话,就是说是一种规则,既然是规则就是给别人看的,你一个公司制定出来了规章制度,不公布,别人怎么遵守?一个道理,如果不用public修饰别人引用不到,和不规定是一样的。所以JDK也明白,所以这里的方法你写不写public他都默认帮你加上!

    下面讲实现

    我们说了,抽象类也好,接口也好,不继承,不实现都是没有意义的,但是因为接口里面只有抽象方法,所以他必须被实现才有意义,不然就会被垃圾回收机制System.gc给回收掉,前面的文章说过了垃圾回收的原理,这里不做赘述,但是为什么不继承呢?有人说了?既然要被实现里面的方法,直接继承不行了吗?是的,但是类的继承只能是单继承,所以,如果一个类里面有很多的接口,怎么做?所以只能是实现!

    但是有人说了,如果很多接口,最后一个继承了上面的所有接口,那我实现的时候是不是需要实现所有接口的方法?答案是肯定的。

    举个栗子:

    package com.gaojizu.TestInterface;
    /**
     * 测试接口的多继承
     * @author admin
     *
     */
    public interface InterFaceA {
    
    	void aaa();
    }
    /**
     * 接口B
     * @author admin
     *
     */
    interface InterFaceB{
    	void bbb();
    }
    /**
     * 接口C
     * @author admin
     *
     */
    interface InterFaceC extends InterFaceA,InterFaceB{
    	void ccc();
    }
    /**
     * 实现类
     * @author admin
     *
     */
    class TestClass implements InterFaceC{
    
    	@Override
    	public void aaa() {
    		System.out.println("TestClass.aaa()");
    		
    	}
    
    	@Override
    	public void bbb() {
    		System.out.println("TestClass.bbb()");
    		
    	}
    
    	@Override
    	public void ccc() {
    	   System.out.println("TestClass.ccc()");
    		
    	}
    	
    }

    其实这里也不难理解,继承了就是拥有了父接口的抽象方法,自然就必须实现他。

    那有人说了,我这里如果在子类里面声明了一个变量,那我直接用父接口的对象调用行不行呢?

    举个栗子:

    class Plane implements FlyAble{
    
    	String name;
    	@Override
    	public void fly() {
    		System.out.println("我可以飞");
    		
    	}
    	
    }

    这里有一个name,我测试的时候是不是可以直接使用呢?当然不是,需要强制转换:

    看例子:

    public static void main(String[] args) {
    		/**
    		 * 这里的接口是不可以自己实例化自己的,原因和抽象类是一样的,里面只有抽象方法,没有实现的,所以是实例化没有意义的
    		 * 那么直接f是不可以调出子类里面的属性的,原因很简单,他是FlyAble的对象,那么他就只能找到自己下面的属性和方法
    		 * 是没有办法知道子类的属性和方法的,想知道的话,就强制转换为子类的对象就行了。下面是例子
    		 */
    		FlyAble f = new Plane();
    		//强制转换为Plane类
    		Plane p = (Plane)f;
    		p.name = "test";
    	}

    其实这里用我们生活中的例子也是一样可以理解的,我们有一个会飞的类,他创建了一个天鹅的对象出来,天鹅说我会下蛋,那按照我们代码的逻辑来想,会飞的应该都会下蛋,显然不是,飞机也会飞,但是不会下蛋,怎么可以下蛋呢?将实例化出来的对象给一个具体使用的类,也就是天鹅!这里也是多态的一个体现,你给一个笼统的概念,然后具体的使用是什么就是什么的思想!

    最后一点:实现是可以多实现的!

    前面我们说接口的继承是可以多继承的,看明白,是接口可以多继承,类一样是单继承,实现是可以多实现的,你说我既可以飞,也可以跑,飞和跑在两个接口里面怎么办?可以同时实现:

    看栗子:

    public interface FlyAble {
    
    	int MAX_SPEED = 11000;
    	int MIN_SPEED = 1;
    	void fly();
    }
    /**
     * 攻击的接口
     * @author admin
     *
     */
    interface Attack{
    	void tack();
    }
    
    class Plane implements FlyAble{
    
    	String name;
    	@Override
    	public void fly() {
    		System.out.println("我可以飞");
    		
    	}
    	
    }
    /**
     * 可以实现多个接口
     * @author admin
     *
     */
    class men implements FlyAble,Attack{
    
    	@Override
    	public void tack() {
    		System.out.println("我可以攻击");
    		
    	}
    
    	@Override
    	public void fly() {
    		System.out.println("我可以飞");
    	}
    	
    }

    那么接口存在的意义就不用说了吧,很明显了,为了更好的将设计与实现分离。

    设计师写完需要的接口,别的不用管了,怎么实现是下面的事情了,这样不仅仅可以提高开发效率,也可以更好的维护。当然如果只有一个人开发,那就没必要分接口和类了!

  • 相关阅读:
    Scanner类
    16 String类
    15_面向对象_01
    14_面向对象_01_2 完整格式
    14_面向对象_01_1 成员变量 成员方法
    13_面向对象辅助看懂实例
    面向对象的基本概念
    Servlet_03 进阶随笔 两种域的运用调用方式
    Servlet_03 资源跳转 关键字
    Servlet_02 资源跳转
  • 原文地址:https://www.cnblogs.com/c1024/p/11011996.html
Copyright © 2020-2023  润新知