zoukankan      html  css  js  c++  java
  • 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("我可以飞");
    	}
    	
    }

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

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

  • 相关阅读:
    台州 OJ 3847 Mowing the Lawn 线性DP 单调队列
    洛谷 OJ P1417 烹调方案 01背包
    快速幂取模
    台州 OJ 2649 More is better 并查集
    UVa 1640
    UVa 11971
    UVa 10900
    UVa 11346
    UVa 10288
    UVa 1639
  • 原文地址:https://www.cnblogs.com/c1024/p/11011996.html
Copyright © 2011-2022 走看看