Java SE 5th day:Object-oriented 04
2014-02-04 17:39
477 查看
(材料源于网络)
2、对象多态性的概念及应用;
3、抽象类和接口的基本概念,主要设计模式;
4、Object类的使用;
5、包装类的操作;
6、匿名内部类的定义;
1、使用final定义类:意味着这个类不能有子类,就是一个太监类。
但是从开发来讲,个人定义的类很少使用final完成,但是以后在一些系统上就经常会出现final(如String类就使用了final);
2、使用final定义方法:意味着这个方法不能被子类所覆写,此操作一般在开发中也很少直接编写。
3、使用final定义的变量即成为常量,内容不可改变,而且必须在定义的时候为其赋值。
另外通过上面的代码可以发现,常量定义的时候,每个单词的字母都是大写的,这个是属于常量的标志,而且如果现在定义属性的时候使用了public static final声明,则表示全局常量;
里面的static表示的是全局数据,而final定义的内容又是常量,一般这些常量往往会作为操作标志出现。
这种的设计在java、Web、SSH、Android之中都是会存在大量的常量的。
现在将构造方法私有化之后可以发现在类的外部已经无法直接使用关键字new为类进行对象实例化了。
现在要求在Singleton类中适当的增加属性和方法,并且保证构造方法和print( )方法不改变,目的还是实现同样的操作功能。
解决问题的步骤:
● 使用private定义的属性和方法只能在本类中访问,那么就说明这个时候只能想办法在本类中定义对象。
● 现在如果想将内部的instance返回到类的外部,按照普通属性和方法的定义肯定不行(因为普通属性和方法必须在类有明确的实例化对象的时候可以调用),可以将其定义为static类型,因为这种类型的属性和方法可以在没有实例化对象的时候进行调用。
现在程序的确是取得一个Singleton类的实例化对象,但是本程序的意义在哪里呢?
意义:如果说现在一个类中只希望有一个实例化对象的时候,则首先必须处理的就是构造方法,因为只有将构造方法隐藏起来,这样才不会随心所欲的任意调用构造方法产生新的实例化对象,而后将内部定义的实例化对象使用static声明,更表示了这个属性是一个公共属性,这样一来,无论外部有多少个对象的声明,而实际上的实例化对象只有唯一的一个,这一点非常类似与Windows中的回收站功能,各个磁盘上都有自己的回收站,但是最终实际上所操作的都是同一个回收站,这就是一个典型的单例设计的模式的优点。
但是,如果现在要想更加明确的表示出只有一个实例化对象,还差一个final关键字。
范例:完整的单例设计模式
面试题:请写出一个Singleton程序并说明其意义;
1、写出如上的操作代码;
2、意义:一个类的构造方法被限制之后,只能通过内部产生,而且使用static定义的属性是公共属性,这样,不管外部有多少个对象声明,最终只有一个实例化对象。
实际上严格来讲,单例设计模式应该分为两种:
● 饿汉式:之前所讲解的都属于此种类型,可以发现以上的程序之中,不管是否使用Singleton类,那么在类中始终会为用户准备出一个本类的实例化对象;
● 懒汉式:在类中不准备好实例化对象,而在这个对象使用之前进行实例化。
范例:修改为懒汉式
但是一般的单例设计都属于饿汉式。
不管式单例设计还是多例设计,实际上的核心都是围绕构造方法私有化进行的,所以要想限制对象的产生格式必须首先控制构造方法。
在之前已经讲解完了表和简单java类的联系,所以本程序依然符合于之前简单java类的原则。
多例设计的核心就是固定范围,以后只要是范围的表示都可以通过多例完成,对于本程序重点式理解其解决问题的思路,因为以后会有与之类似的新的实现技术。
● 封装:只是讲解了基本的private关键字的作用,但是封装会有四个方面;
● 继承:父子关系,而且最为重要的是子类可以扩充父类的功能;
● 多态:对于多态性现在有两种表现形式:
|- 形式一:方法的多态性,重载与覆写;
|- 形式二:对象多态性;
对象的多态性指的是父类对象和子类对象之间的互相转换操作上:
● 向上转型,子类变为父类实例:父类父类对象=子类实例; → 自动转换
● 向下转型,父类变为子类实例:子类子类对象=(子类)父类实例;→ 强制转换
为了更好的理解多态性的概念,首先来观察如下一段代码:
注意:以下的所有代码只是为了讲解概念,但是代码本身结构并不是以后开发中所要使用的。
本程序实例化的式子类,所以最终输出操作使用的也是被子类所覆写过的方法。
在继承的关系之中,使用哪个子类,就使用由这个子类所覆写的操作方法。
范例:向上转型
范例:向下转型
此时不要看类型,而要看式哪个子类为它进行的对象实例化操作。
范例:观察一下的程序
本程序在执行的时候出现了以下的错误提示:
ClassCastException也是除了NullPointerException之外最常见的一种错误,这种错误主要发生在两个没有关系的类对象发生强制转换操作的情况下。
记住一点:任何情况下,如果要想发生对象的向下转型,则首先必须发生对象的向上转型,这样可以用于设置两者之间的对象关系。
而且,以后为了避免在开发之中,出现这样的异常信息,建议在进行对象的向下转型的操作之前首先使用instanceof关键字判断一下一个对象是否是某一个类的实例,语法如下:
范例:使用instanceof关键字
从以上的程序中可以发现,由于通过子类实例化的对象同时是子类和父类的实例,所以可以直接进行向上或向下的转型;但是如果直接使用父类对象实例化本类对象,则一定就不再是子类的实例了,所以不能进行转换。
以上讲解清楚了对象多态性的概念,但是这个概念该怎么使用呢?
下面通过一个实际的程序来分析,例如:现在要求定义一个方法,此方法可以接收A类的任意子类的实例化对象,如果不使用对象的多态性,也可以通过方法的重载完成。
现在的功能成功的实现了功能,但是这种实现存在问题!
如果说现在的A类有5000W个子类,而且还有可以继续增加的话,那么此时的fun()方法就重载5000W次以上,很明显这种操作根本就不方便维护。
范例:利用对象多态性完成
所有的子类都可以自动的向父类对象进行转换,那么就利用这样一点。
此时,实现的功能与之前完全一样,但是此时不管A有多少个子类,这个方法都能处理,而且会根据不同的传入的子类实例,执行不同的print( )方法的操作。
但是在这里就必须有一点注意:以上的所以操作方法都是以父类的功能为主的,如果现在子类自己扩充了一些新的操作方法呢?
所以在开发之中,子类中所使用的方法尽可能是父类中已经规定好的方法,即:设计的重点在父类上。
因为程序之中现在存在了父类和子类,所以这两者之间就会存在对象的转换关系,因为在之前曾经强调过,子类对象在进行对象实例化的时候会先为父类对象实例化,所以就证明在进行子类对象实例化的转换操作中,就已经包含了父类对象,所以这两个对象之间的转换就成为了对象多态性的起点。
对象多态性总结:一个类可以有多个子类,每个子类根据不同的要求,进行父类中的方法覆写,而后父类利用向上转型的关系,接收一个指定子类的实例化对象,而最后调用的方法是由实例化对象的子类所决定(即:哪个子类进行了对象的实例化,就调用哪个子类中被覆写的方法)。
在进行继承关系设计的时候,一定要把重点放在父类的设计上,而且子类尽可能不要设计出父类没有的操作方法。而且在类的设计中,永远不要去继承一个已经实现好的类,只能继承抽象类或实现接口,因为一旦发生对象的向上转型关系后,所调用的方法一定是被子类所覆写过的方法。这就是String类之所以设计为final类的原因。
范例:思考题(了解)
回顾之前的两个概念:
● 对于类中属性的默认值及传递到构造方法中为属性初始化的数值,都必须在构造方法执行完毕之后才能被设置上;
● 在子类对象实例化之前,会默认先去调用父类的构造方法,为父类中的属性初始化,之后才是子类中的构造方法,为子类中的属性初始化。
范例:人类与学生类
这里的内存图大概如下:
在new Student()时候,实际上是先给父类(Person类)分配内存空间,再给子类(Student类)分配,这里为了简洁就不画出Person类的堆内存。虽然per栈内存指向的是Student的实例化对象,但per在声明时已经确定只有Person的操作方法,所以per是不能调用setSchool()等方法的(即,父类不能调用子类添加的操作方法)。
2.6.1 抽象类的基本概念
抽象类:类中包含一个抽象方法的类就是抽象类,抽象类要使用abstract关键字声明。
抽象方法:是一个只声明而未实现的方法,所有的抽象方法要使用abstract关键字声明,而所谓的没有实现就是指方法的后面没有“{}”,只是一个“;”;
范例:定义抽象类
可以发现抽象方法和普通方法的最大区别就在于一个“{}”上,但是抽象类本身并不能直接进行对象的实例化操作。
此时编译之后将出现如下的错误提示:
抽象类的使用原则如下:
● 抽象类必须有子类,而且子类如果不是抽象类的话,必须覆写抽象类中的全部抽象方法;
● 一个子类只能继承一个抽象类,依然使用extends关键字完成;
● 抽象类的对象不能直接进行对象的实例化操作,因为一个类的对象如果实例化之后,则肯定就表示可以通过对象调用类中的方法了,可是抽象类之中存在抽象方法(未完成的方法),所以不能直接实例化,但是却可以依靠对象的向上转型,完成抽象类对象的实例化操作。
实际上通过以上的代码,可以发现,抽象类就是比普通方法多了若干个抽象方法而已。
思考问题:
1、 抽象类可以有构造方法吗?
可以有构造方法,而且抽象类的子类对象实例化的时候也依然符合于之前之类对象的实例化流程,先调用父类中的构造方法,为父类的属性初始化,而后再调用子类自己的构造方法,所以抽象类中属性、static属性、常量等都可以进行定义;
2、 抽象类中可以没有抽象方法吗?
可以没有抽象方法,尽管没有抽象方法了,也不能直接使用关键字new直接实例化抽象类的对象;
3、 一个抽象类中能否包含内部类或内部抽象类?
可以!使用代码如下:
一般情况下,此类代码不会轻易出现;
4、 一个抽象类能否使用static关键字声明?
● 如果现在在外部的抽象类使用了static定义,则肯定不能使用;
● 而如果在内部抽象类上使用static,则就表示是一个外部类,可以使用。
5、 抽象类能否使用final声明?
不能,因为final定义的类不能有子类,而抽象类必须有子类,所以肯定不能使用final。
2.6.2 抽象类的应用 —— 模版设计模式
抽象类可以由属性、常量、普通方法、构造方法、抽象方法等几部分所组成,那么实际上这个就意味着,抽象类中多了些普通类没有的功能。
例如:人分为工人、学生、老师、这些人都可以说话,但是每一类人说话的方式肯定不同,学生和工人肯定有相似的东西,而且也肯定有各自不同的内容。
通过本程序可以发现,对于Person类而言,有两个子类,也可能有更多的子类,但是这些子类要想完成说话的这个功能实现,必须正确的覆写Person类中的getContent()方法,即:父类Person规定出了方法的操作标准,子类依照此标准进行实现。或者说,现在规定了一个用于取得说话内容的方法标准(getContent( )),只要想说话,就将此标准实现。
范例:一个人可以有多项活动:run、dance、sing等。
现在抽象类所给的就是一种操作的模版,子类依照此模版实现所以的操作,所以抽象类中最大的用处是定义一些公共的操作模版,毕竟抽象类中有一些方法还是普通方法。
2.6.3 接口的基本概念
抽象类的组成:常量、变量、构造、普通方法、抽象方法,那么如果现在一个类之中只由全局常量和抽象方法组成的特殊的类实际上就可以将其定义为接口;
接口:抽象方法和全局常量的集合,在java之中,接口使用interface关键字完成,而且在接口之中的所有全局常量和抽象方法的访问权限永远都是public。
接口的定义格式:
因为接口只由全局变量和抽象方法组成,所以接口是没有构造方法的。也可通过实例化的概念分析:实例化对象相当于给类中属性分配地址空间,但接口中都是全局变量,在实例化之前就已经分配了地址空间。
范例:定义一个接口
接口定义完成之后,由于其存在了抽象方法,所以接口中的对象也肯定不能直接进行对象的实例化,所以接口如果要想使用,则必须按照如下的方式进行:
● 接口必须有子类,子类实现(implement)接口,如果子类不是抽象类,则必须覆写全部的抽象方法;
● 接口的对象实例化,可以依靠对象的向上转型完成,子类要覆写父类中的操作方法,发生了向上转型的关系之后,所调用的方法肯定由子类覆写所决定。
范例:使用接口
一个子类可以同时实现多个接口,而一个子类只能继承一个抽象类,至少从现在来讲,接口比抽象类有一个好处:不受单继承的局限控制。
既然接口本身就是全局常量和抽象方法的组成,所以以下的两种接口定义效果是一样的:
建议:在写接口中方法的时候尽量前面还是加上“public”,这样的话至少阅读式很方便的。
如果现在一个子类想要实现接口又要继承抽象类的话,则应该采用先继承(extends),再实现(implements)。
另外,一个抽象类也可以同时实现多个接口。
可是反过来讲,一个接口却不能继承一个抽象类,但是一个接口却可以继承多个接口,以实现接口的多继承。
从实际的开发来讲,接口的多继承现阶段不会使用,而且这些实际上是属于再抽象的概念。
接口有三大主要作用:
● 作用一:可以作为一个操作的标准存在;
● 作用二:表示一种操作的能力;
● 作用三:将服务器端的方法名称暴露给远程客户端(分布式)。
2.6.4 使用接口表示标准
对于接口的名称,在生活之中应该并不陌生,例如:有USB接口,那么接口实际上表示的应该就是一个操作的标准,就拿USB设备说话。
范例:模拟一个USB设备的操作程序
可以发现,接口就是规定出一个标准,计算机认的只是接口,而对于具体的设备计算机本身并不关心,只要现在有USB接口子类对象,都可以向电脑上插入并使用,这个就是标准的产物。
之前说过,抽象类就是操作的模版,也就相当于标准,所以以上接口换成抽象类一样可以实现此功能。如下(更改部分用高亮标出):
2.6.5 表示能力
如果一个接口要想表示是某一个类具备的能力的话,则往往可以不定义任何的操作方法,例如:中国人可能会中文、或英文、或中英文。
本程序只是列出了一个代码的结构,而实际中的应用,以后再提及、说明。
2.6.6 关于接口的几点说明
由于接口的开发在实际中作用较为明显,所以下面给出几个接口的说明:
1、 接口中的所有方法都是public,而且使用接口定义方法的时候要比定义常量的情况多;
2、 在一个接口之中,除了可以定义全局常量和抽象方法之外,一个接口的内部也可以包含多个内部类、内部抽象类、内部接口,反之,抽象类也可以定义内部接口;
3、 如果一个接口的内部接口使用了static声明,则表示了此接口是外部接口,完整的名称式“外部接口.内部接口”。
对于以后的java学习之中,肯定会遇到这种接口的定义情况,此外,在以后的Android学习之中,也会出现同样的代码,而且那个时候肯定也要作为重点使用。
2.6.7 接口的实际应用(Factory,工厂设计模式)
观察下面一段代码并指出其错误。
范例:接口操作
以上的程序功能实现了,但是以上的代码有问题?
分析一下水果摊上买水果的过程。
以上的图形表示的是。顾客通过老板那里取得了一个(水果)接口的实例化对象(苹果、西瓜),用户不关心这些(水果)接口从哪里来的,只知道通过这个“老板”可以取得水果。
以上的代码,写的是Apple类,所以客户端(主方法)可以取得接口的实例化对象,如果现在不使用苹果,使用橘子操作呢?
本程序的最大问题在于耦合度加深,现在的接口和一个固定的子类永远绑定在一起。
于是现在的问题式由:A → B,而要想修改呢,则可以参考JVM的运行机制:
● 程序代码 → JVM →OS:达到可移植性;
所以现在应该将其修改为:A → C → B,以进行解耦合的操作,所以中间可以采用一个新的类。
现在程序中使用的子类可以任意的改变,而且如果以后不管有多少个Fruit的子类,客户端只和Factory类操作,不管哪个子类,不同的接口子类实现的功能也是不同的,而客户端不管是哪个子类,只按照既定的标准进行接口的操作。
注:以上接口同样可以替换成抽象类。
2.6.8 接口的实际应用(Proxy,代理设计模式)
下面观察如果一个场景:
如果要想变为程序表示,则代码开发如下:
代理设计模式的核心在于:
● 首先定义出一个表示公共操作的核心接口;
● 在此接口中定义两个子类:
|- 真实主题类:只负责具体的操作业务;
|- 代理主题类:为真实主题类的操作提供辅助功能,如果没有此类的支持,真实主题无法操作;
2.6.9 接口的实际应用(Adapter 适配器设计模式)(了解)
在正常的情况下,如果一个接口的子类肯定要覆写接口中的全部抽象方法。
但是现在MyWindow子类不需要覆写三个方法,只需要覆写一个open( )方法即可。
这个时候中间只能加入一个过渡类,这个过渡类要满足两点要求:
● 要求一:此类对接口中的方法进行假实现;
● 要求二:过渡类不能被直接使用,不能直接进行实例化对象的操作;
而现在只有抽象类可以,因为抽象类可以实现接口,抽象类可以对方法假实现,抽象类也无法直接产生实例化对象。
这种设计模式最早的时候应用在java的图形界面之中,但是现在随着图形界面的不再使用,这种代码理解意思就行了,而且以上所讲解的所有设计模式,现在只要求理解代码的结构,但是还没到用的时候。
所以,现在根据以上的表格可以发现,在很多情况下,抽象类和接口都是一种相似的概念,但是如果假设碰到程序的要求后,当抽象类和接口都能满足的情况下,优先使用接口,因为接口可以避免单继承带来的局限。
问题:抽象类、接口、普通类这之间的关系是什么?
之前的类是表示具有公共的数据集合,是一个抽象的概念,但是抽象类和接口呢?通过之前的代码讲解可以发现,实际上抽象类和接口是在类之上的产物,下面通过一张图形来表示一下。
动物:应该作为接口出现,因为是一个操作的标准
如果说抽象类是对某一类事务的再抽象,那么接口就相当于对抽象类的再次抽象,即:接口才是真正的表示出抽象概念的最高点,当然,以上的图形之中,即使不使用抽象类也可以通过接口实现,但是使用抽象类唯一的好处是,可以定义一些普通方法。
宠物猫:
宠物狗:
宠物商店:
测试宠物商店:
那么既然所有的类都是Object类的子类,就意味着Object类可以接收任意类型的子类实例。
既然Object本身也是一个类,所以这个类也肯定定义了若干个方法,对于任何一个完整的简单java类而言,都要求覆写Object类中的如下三个方法:
● 取得对象信息:publicString toString( );
● 对象比较:publicboolean equals(Object obj);
● 取得对象的Hash码:public int hashCode( );
2.9.1 取得对象信息:toString()
之前提及到:一个类的对象如果直接打印,会取得对象的地址信息。
此时的两个输出是一样的,那么可以证明出一点:在进行对象输出的时候实际上默认调用了Object类中的toStrng( )方法,此方法可以取得对象的信息,但是由于子类众多,所以此方法只是简单的返回了一个对象的地址编码,当然,不同的子类也可以根据自己的要求对toString( )方法进行覆写。
只要一打印对象就意味着要取得对象的信息,而对象的信息就可以利用覆写Object类中的toString( )方法实现。
2.9.2 对象比较:equals( )
曾经在String类中学过equals()方法的使用,实际上String也是Object类的子类,所以实际上String类中就是覆写了Object类的equals(),而严格来讲,任何一个对象的比较操作也都应该使用equals()完成,而不是像最早那样利用compare()方法编写。
以上的代码才是真正意义上的对象比较操作,也是重点的内容,那么这样一来,实际上就可以利用这些特征进行链表功能的完善。
2.9.3 接收引用数据
Object类除了可以接收任意的类对象的向上转型之外,也可以接收所有的引用数据类型,这就包含了数组和接口,但是数组和接口不像类那样,直接继承了Object,所以这个功能只能按照一个特性去记!
范例:保存数组
数组与Object没关系,这个只是作为特殊的知识点去死记的,唯一的联系就是数组属于引用数据类型。
范例:接收接口实例
接口和Object在定义上就更没关系,因为接口肯定不能继承一个类,所以这个还是从特殊的概念上去记。
范例:观察程序
如果说现在的Student类只使用一次的话,那么还有必要将其定义成一个单独的子类吗?肯定没有必要,所以在这种情况下就可以利用匿名内部类完成。
本程序分析如下:
(1)直接实例化接口对象,代码如下:
此时,是直接为接口实例化,从前面的概念应该知道,接口本身是不能直接进行实例化的,所以在接口实例化后要有一个大括号,在其中编写具体的实现方法。
(2)实现抽象方法,代码如下:
整个代码编写完后,实际上此时就是一个接口的实例化对象了,其中的抽象方法也就实现了。
匿名内部类比内部类的代码结构还要混乱,但是匿名内部类使用是非常多的,而且随着技术的发展及完善匿名内部类的使用也是越来越广泛,而匿名内部类的唯一好处是在某一个接口或抽象的子类只使用一次的情况下才能体现出来的,而且匿名内部类如果觉得编写不舒服,也可以使用单独定义子类实现。
但是对于日后框架开发,Android开发之中,都会使用到此概念。
在java之中一直提倡一种概念:“一切皆对象”。但是这里面就会存在一个问题,基本数据类型不是对象,所以所谓的包装类指的是将一个基本数据类型封装为类的形式,就好像如下的操作代码:
在系统之中已经为八种基本数据类型准备出了包装类:int(Integer)、double(Double)、byte(Byte)、short(Short)、long(Long)、float(Float)、char(Character)、boolean(Boolean);
而实际上这八个包装类,也分为两组:
● 数值型(Number子类):Integer、Double、Byte、Short、Long、Float;
● 对象型:Character、Boolean;
Number作为所有数值型操作父类,在开发之中主要的功能是取得包装类中包装的数据。
2.11.2 装箱和拆箱操作
装箱:指的是将基本数据类型变为类的形式; → 利用包装类的构造完成
拆箱:将包装类中的数据取出,变为基本数据类型的操作; → Number类完成
范例:以int型数据为例,装箱及拆箱
范例:以double型数据为例
可是以上的这种操作称为手工装箱及拆箱操作,属于JDK 1.4前的功能,而在JDK 1.5之后提供了一个自动装箱和拆箱的功能支持,而且也可以直接使用包装类进行计算了。
范例:以double为例
范例:以boolean型数据为例
而且也可以直接进行数学计算:
但是这个动能只适合于JDK 1.5之后使用,虽然这些包装类可以直接进行计算,但也存在一个问题:
2.11.3 数据类型的转换(核心)
使用包装类除了之前的装箱及拆箱功能之外,最大的特点在于可以使用包装类完成数据的转换,可以将字符变为指定的数据类型。
●以Integer为例,方法:
publicstatic intparseInt(String)throws NumberFormatException;
● 以Double为例,方法:
public staticdouble parseDouble(String) throws NumberFormatException;
● 以Boolean为例,方法:
publicstatic boolean parseBoolean(String);
范例:转换类型
范例:转换boolean类型
但是在使用如上的操作进行转换的时候,必须注意一点,数据类型必须合法,否则将出现如下错误信息:
数字格式转换异常。
那么此时已经清楚了字符串变为基本数据类型,那么如何将基本数据类型变为字符串呢?
● 方式一:任何数据类型碰到字符串之后,都变为字符串
● 方式二:在String类中提供了许多的valueOf()方法。
在实际开发之中,一定要使用“方式二”完成转换的操作,这样做性能高一些。
链表程序肯定要求满足于公共的要求,即:任何类的对象都应该保存,那么唯一可以使用的就是Object类型。
提示:使用Object类也可以保存基本数据类型:
● int → 装箱为Integer → Object;
既然使用Object可以保存任意的数据类型,所以对于查询和删除操作的话,如何比较对象就必须统一了,使用equals( )完成。
首先,本程序之中已经包含了太多的方法,那么首先定义一个接口的子类,在子类之中,假实现所有的抽象方法。
本程序只是为了讲解之后的开发所做的一个准备,而且没有必要去关心如何实现的,只需要知道接口的使用即可,链表 =动态对象数组,不限制长度。
类的结构自行设计,就要代码的关系。
范例:定义图书的操作标准,肯定要使用接口
本接口现在只是象征的写了两个方法,而实际上这个接口中的方法要进行具体的业务分析。
范例:定义图书大厦的类,里面要保存有多本书,现在不知道有多少本书,所以使用Link接口和LinkImpl类完成。
范例:定义具体的计算机类—— ComputerBook
范例:定义数学类—— MathBook
范例:表示出这几个类之间的关系
而且本程序清楚之后,实际上以后类似的功能就可以实现了,例如:
● 老板开车进城买水果;
● 一个停车场可以停放多种车辆;
● 一间教室可以有多个学生,若干个老师;
现在这种一对多关系通过链表的程序支持,可以很方便的实现。
2、单例设计(Singleton)模式的特点及作用;
3、对象多态性:子类和父类对象之间的互相转型操作;
● 对象的向上转型:父类父类对象 =子类实例; →自动完成
● 对象的向下转型:子类子类对象 =(子类)父类实例; → 强制完成
● 在对象发生向下转型的操作之前必须首先发生向上转型,以建立子类和父类对象之间的关系,而且对象多态性的最大好处是根据实例化其子类的不同,调用的被覆写的方法也不同。从开发的角度而言,80%都是向上转型,而只有20%才是向下转型的操作;
4、使用instanceof关键字可以判断某一个对象是否是某一个类的实例;
5、抽象类和接口概念、特别、区别;
6、设计模式:工厂设计(Factory)、代理设计(Proxy),此类代码要求掌握其代码结构,而对于其应用今后会提及;
7、Object类:是所有类的父类
● 主要的方法:toString()、equals()、hashCode();
● 使用Object类的对象可以接收任意的引用数据类型;
8、包装类和String类相结合,可以完成数据类型的互转换操作;
9、图书大厦的应用程序;
Java SE 5th day:Object-oriented 04
1、本次课程知识点
1、final关键字及单例设计模式;2、对象多态性的概念及应用;
3、抽象类和接口的基本概念,主要设计模式;
4、Object类的使用;
5、包装类的操作;
6、匿名内部类的定义;
2、具体内容
2.1 final关键字(重点)
final在很多时候也被称为终结器,使用final可以用于定义类、属性、方法;1、使用final定义类:意味着这个类不能有子类,就是一个太监类。
final class A{ //定义最终类 } class B extendsA{ × } |
2、使用final定义方法:意味着这个方法不能被子类所覆写,此操作一般在开发中也很少直接编写。
class A{ public final void print(){} } class B extends A{ public void print(){} × } |
class A { private final String INFO = "Hello"; public final void print() { INFO = "World"; × } } |
private static final String INFO = "Hello"; |
class A { private final int FLAG_OK = 1; private final int FLAG_ERROR = 2; public int getStatus() { // 返回某种状态 // 之前会有许多的操作代码 return FLAG_OK; } } |
2.2 构造方法私有化_单例设计模式(Singleton)(重点)
构造方法的主要功能是为类中的属性初始化,即:一个类只调用了构造方法之后,才能够开辟空间,即:构造方法是一个类对象实例化的必经之路。class Singleton { private Singleton() { } // 将构造方法私有化 public void print() { System.out.println("Hello World"); } } public class Demo { public static void main(String args[]) { Singleton sin = null;// 声明对象 sin = new Singleton();// 实例化对象 × sin.print(); // 通过对象调用方法 } } |
现在要求在Singleton类中适当的增加属性和方法,并且保证构造方法和print( )方法不改变,目的还是实现同样的操作功能。
解决问题的步骤:
● 使用private定义的属性和方法只能在本类中访问,那么就说明这个时候只能想办法在本类中定义对象。
public Singletoninstance = new Singleton(); |
class Singleton { public static Singleton instance =new Singleton(); private Singleton() { } // 将构造方法私有化 public static Singleton getInstance() { returninstance; } public void print() { System.out.println("Hello World"); } } public class Demo { public static void main(String args[]) { Singleton sin = null;// 声明对象 sin = Singleton.getInstance(); sin.print(); } } |
Hello World |
意义:如果说现在一个类中只希望有一个实例化对象的时候,则首先必须处理的就是构造方法,因为只有将构造方法隐藏起来,这样才不会随心所欲的任意调用构造方法产生新的实例化对象,而后将内部定义的实例化对象使用static声明,更表示了这个属性是一个公共属性,这样一来,无论外部有多少个对象的声明,而实际上的实例化对象只有唯一的一个,这一点非常类似与Windows中的回收站功能,各个磁盘上都有自己的回收站,但是最终实际上所操作的都是同一个回收站,这就是一个典型的单例设计的模式的优点。
但是,如果现在要想更加明确的表示出只有一个实例化对象,还差一个final关键字。
范例:完整的单例设计模式
class Singleton { public static final Singleton INSTANCE=new Singleton(); private Singleton() { } // 将构造方法私有化 public static Singleton getInstance() { return INSTANCE; } public void print() { System.out.println("Hello World"); } } public class Demo { public static void main(String args[]) { Singleton sin = Singleton.getInstance(); sin.print(); } } |
Hello World |
1、写出如上的操作代码;
2、意义:一个类的构造方法被限制之后,只能通过内部产生,而且使用static定义的属性是公共属性,这样,不管外部有多少个对象声明,最终只有一个实例化对象。
实际上严格来讲,单例设计模式应该分为两种:
● 饿汉式:之前所讲解的都属于此种类型,可以发现以上的程序之中,不管是否使用Singleton类,那么在类中始终会为用户准备出一个本类的实例化对象;
● 懒汉式:在类中不准备好实例化对象,而在这个对象使用之前进行实例化。
范例:修改为懒汉式
class Singleton { public static Singleton instance; &nbs 20000 p; private Singleton() { } // 将构造方法私有化 public static Singleton getInstance() { if (instance ==null) { instance =new Singleton();// 实例化对象 } return instance; } public void print() { System.out.println("Hello World"); } } |
2.3 多例设计(重点)
单例设计是一个类只能有一个实例化对象,而多例设计指的是在一个类中会同时存在多个实例化对象,例如:表示颜色的类有三基色:红色,绿色、蓝色,所以如果现在要想定义一个表示基色的类Color,它的对象应该只有三个取值范围。class Color { public static final Color RED =new Color("红色"); public static final Color GREEN =new Color("绿色"); public static final Color BLUE =new Color("蓝色"); private String title; private Color(String title) { this.title = title; } public String getTitle() { return this.title; } public static Color getColor(int i) { switch (i) { case 0: return RED; case 1: return GREEN; case 2: return BLUE; default: return null; } } } public class Demo { public static void main(String args[]) { Color col = Color.getColor(1); System.out.println(col.getTitle()); } } |
绿色 |
2.4 思考题(重点)
在之前已经通过程序表示出了emp、dept之间的关系了,那么下面表示出salgrade表的类设计,因为salgrade表只有固定的几项范围,那么很明显式一种多例设计,要求将salgrade定义成类,并且与之前的雇员和部门程序相匹配,可以在显示雇员信息的同时输出工资等级。在之前已经讲解完了表和简单java类的联系,所以本程序依然符合于之前简单java类的原则。
package course_2; class Emp { private int empno; private String ename; private String job; private double sal; private double comm; private Emp mgr; // 每个雇员有一个领导 private Dept dept; // 每个雇员属于一个部门 public Emp() { } public Emp(int empno, String ename, String job,double sal, double comm) { this.comm = comm; this.empno = empno; this.ename = ename; this.job = job; this.sal = sal; } public String getEmpInfo() { return "雇员信息: " + "\n" + "\t|- 编号: " +this.empno +"\n" + "\t|- 姓名: "+this.ename +"\n" + "\t|- 职位: " +this.job +"\n" + "\t|- 工资: " +this.sal +"\n" + "\t|- "+ Salgrade.getInstance(this.sal).getSalgradeInfo() + "\n" + "\t|- 奖金: " +this.comm; } public void setMgr(Emp mgr) { this.mgr = mgr; } public Emp getMgr() { return this.mgr; } public void setDept(Dept dept) { this.dept = dept; } public Dept getDept() { return this.dept; } } class Dept { private String dname; private int deptno; private String loc; private Emp[] emps; // 每个部门有多个雇员 public Dept(int deptno, String dname, String loc) { this.dname = dname; this.deptno = deptno; this.loc = loc; } public void setEmps(Emp[] emps) { this.emps = emps; } public Emp[] getEmps() { return this.emps; } public String getDeptInfo() { return "部门信息:" + "\n" + "\t|- 部门编号: " +this.deptno +"\n" + "\t|- 部门名:" +this.dname +"\n" + "\t|- 位置:" +this.loc; } } class Salgrade { private int grade; private double losal; private double hisal; private static final Salgrade[] salgrades = new Salgrade[] { new Salgrade(1, 700.0, 1200.0), new Salgrade(2, 1201.0, 1400.0), new Salgrade(3, 1401.0, 2000.0), new Salgrade(4, 2001.0, 3000.0), new Salgrade(5, 3001.0, 9999.0) }; private Salgrade(int grade, double losal, double hisal) { this.grade = grade; this.losal = losal; this.hisal = hisal; } public static Salgrade getInstance(double sal) { for (int x = 0; x < salgrades.length; x++) { if (salgrades[x].losal <= sal &&salgrades[x].hisal > sal) { return salgrades[x]; } } return null; } public String getSalgradeInfo() { return "工资等级:" + this.grade + "(" + this.losal + "~" + this.hisal + ")"; } } public class Demo { public static void main(String args[]) { Emp allEmp[] = new Emp[] {new Emp(7369, "SMISH", "CHERK", 800.0, 10), new Emp(7566,"ALLEN", "MANAGER", 2450.0, 100), new Emp(7839,"KING", "PRESIDENT", 5000.0, 0) }; Dept dept = new Dept(10,"ACCOUNT", "New York"); // 先设置雇员和领导的关系 allEmp[0].setMgr(allEmp[1]); allEmp[1].setMgr(allEmp[2]); // 设置雇员和部门的关系,每个雇员都有部门 allEmp[0].setDept(dept); allEmp[1].setDept(dept); allEmp[2].setDept(dept); // 一个部门有多个雇员 dept.setEmps(allEmp); // 信息的输出: System.out.println(dept.getDeptInfo()); System.out.println("部门人数:" + dept.getEmps().length); for (int x = 0; x < dept.getEmps().length; x++) { Emp emp = dept.getEmps()[x]; // 注意这个用法 System.out.println(emp.getEmpInfo()); // 也可以用下面这条语句输出: // System.out.println(allEmp[x].getEmpInfo()); if (emp.getMgr() !=null) { // 存在领导 System.out.println("他的领导:\n" + emp.getMgr().getEmpInfo()); } else { System.out.println("他没领导!"); } System.out.println("-----------------------------"); } } } |
部门信息: |- 部门编号: 10 |- 部门名:ACCOUNT |- 位置:New York 部门人数:3 雇员信息: |- 编号: 7369 |- 姓名: SMISH |- 职位: CHERK |- 工资: 800.0 |- 工资等级:1(700.0~1200.0) |- 奖金: 10.0 他的领导: 雇员信息: |- 编号: 7566 |- 姓名: ALLEN |- 职位: MANAGER |- 工资: 2450.0 |- 工资等级:4(2001.0~3000.0) |- 奖金: 100.0 ----------------------------- 雇员信息: |- 编号: 7566 |- 姓名: ALLEN |- 职位: MANAGER |- 工资: 2450.0 |- 工资等级:4(2001.0~3000.0) |- 奖金: 100.0 他的领导: 雇员信息: |- 编号: 7839 |- 姓名: KING |- 职位: PRESIDENT |- 工资: 5000.0 |- 工资等级:5(3001.0~9999.0) |- 奖金: 0.0 ----------------------------- 雇员信息: |- 编号: 7839 |- 姓名: KING |- 职位: PRESIDENT |- 工资: 5000.0 |- 工资等级:5(3001.0~9999.0) |- 奖金: 0.0 他没领导! ----------------------------- |
2.5 多态性(重点)
面向对象的三大特征:● 封装:只是讲解了基本的private关键字的作用,但是封装会有四个方面;
● 继承:父子关系,而且最为重要的是子类可以扩充父类的功能;
● 多态:对于多态性现在有两种表现形式:
|- 形式一:方法的多态性,重载与覆写;
|- 形式二:对象多态性;
对象的多态性指的是父类对象和子类对象之间的互相转换操作上:
● 向上转型,子类变为父类实例:父类父类对象=子类实例; → 自动转换
● 向下转型,父类变为子类实例:子类子类对象=(子类)父类实例;→ 强制转换
为了更好的理解多态性的概念,首先来观察如下一段代码:
注意:以下的所有代码只是为了讲解概念,但是代码本身结构并不是以后开发中所要使用的。
class A { public void print() { System.out.println("A"); } public void fun() { this.print(); } } class B extends A{ public void print() { // 覆写的方法 System.out.println("B"); } } public class Demo { public static void main(String args[]) { B b = new B(); b.fun(); } } |
B |
在继承的关系之中,使用哪个子类,就使用由这个子类所覆写的操作方法。
范例:向上转型
publicclass Demo { public static void main(String args[]) { A a = new B(); a.fun(); } } |
B |
public class Demo { public static void main(String args[]) { A a = new B(); // 向上转型 B b = (B) a; // 向下强制转换 b.fun(); } } |
B |
范例:观察一下的程序
public class Demo { public static void main(String args[]) { A a = new A(); B b = (B) a; // 向下强制转换 b.fun(); } } |
Exception in thread "main" java.lang.ClassCastException: A cannot be cast to B |
记住一点:任何情况下,如果要想发生对象的向下转型,则首先必须发生对象的向上转型,这样可以用于设置两者之间的对象关系。
而且,以后为了避免在开发之中,出现这样的异常信息,建议在进行对象的向下转型的操作之前首先使用instanceof关键字判断一下一个对象是否是某一个类的实例,语法如下:
对象 instanceof 类 → 返回boolean型数据 |
public class Demo { public static void main(String args[]) { A a1 = new B(); // 向上转型 System.out.println(a1 instanceof A); System.out.println(a1 instanceof B); A a2 = new A(); System.out.println(a2 instanceof A); System.out.println(a2 instanceof B); } } |
true true true false |
以上讲解清楚了对象多态性的概念,但是这个概念该怎么使用呢?
下面通过一个实际的程序来分析,例如:现在要求定义一个方法,此方法可以接收A类的任意子类的实例化对象,如果不使用对象的多态性,也可以通过方法的重载完成。
class A { public void print() { System.out.println("A"); } public void fun() { this.print(); } } class B extends A { public void print() { System.out.println("B"); } } class C extends A { public void print() { System.out.println("C"); } } public class Demo { public static void main(String args[]) { fun(new B()); fun(new C()); } public static void fun(B b) { b.fun(); } public static void fun(C c) { c.fun(); } } |
B C |
如果说现在的A类有5000W个子类,而且还有可以继续增加的话,那么此时的fun()方法就重载5000W次以上,很明显这种操作根本就不方便维护。
范例:利用对象多态性完成
所有的子类都可以自动的向父类对象进行转换,那么就利用这样一点。
package course_2; class A { public void print() { System.out.println("A"); } public void fun() { this.print(); } } class B extends A { public void print() { System.out.println("A.B"); } public void printB() { System.out.println("B"); } } class C extends A { public void print() { System.out.println("A.C"); } public void printC() { System.out.println("C"); } } public class Demo { public static void main(String args[]) { fun(new B()); fun(new C()); } public static void fun(A a) { a.fun(); if (ainstanceof B) { ((B) a).printB(); //向下强制转型 } if (ainstanceof C) { ((C) a).printC();// 向下强制转型 } } } |
A.B B A.C C |
但是在这里就必须有一点注意:以上的所以操作方法都是以父类的功能为主的,如果现在子类自己扩充了一些新的操作方法呢?
所以在开发之中,子类中所使用的方法尽可能是父类中已经规定好的方法,即:设计的重点在父类上。
因为程序之中现在存在了父类和子类,所以这两者之间就会存在对象的转换关系,因为在之前曾经强调过,子类对象在进行对象实例化的时候会先为父类对象实例化,所以就证明在进行子类对象实例化的转换操作中,就已经包含了父类对象,所以这两个对象之间的转换就成为了对象多态性的起点。
对象多态性总结:一个类可以有多个子类,每个子类根据不同的要求,进行父类中的方法覆写,而后父类利用向上转型的关系,接收一个指定子类的实例化对象,而最后调用的方法是由实例化对象的子类所决定(即:哪个子类进行了对象的实例化,就调用哪个子类中被覆写的方法)。
在进行继承关系设计的时候,一定要把重点放在父类的设计上,而且子类尽可能不要设计出父类没有的操作方法。而且在类的设计中,永远不要去继承一个已经实现好的类,只能继承抽象类或实现接口,因为一旦发生对象的向上转型关系后,所调用的方法一定是被子类所覆写过的方法。这就是String类之所以设计为final类的原因。
范例:思考题(了解)
class A { public A() { this.print(); // 2、调用的是被子类所覆写的方法 } public void print() { } } class B extends A { private int num = 10; public B(int num) { // 这里隐含着super(); // 2、 this.num = num;// 4、 this.print();// 5、 } public void print() { System.out.println("num = " +num); // 3、& 6、 } } public class Demo { public static void main(String args[]) { new B(30);// 1、 } } |
num = 0 num = 30 |
● 对于类中属性的默认值及传递到构造方法中为属性初始化的数值,都必须在构造方法执行完毕之后才能被设置上;
● 在子类对象实例化之前,会默认先去调用父类的构造方法,为父类中的属性初始化,之后才是子类中的构造方法,为子类中的属性初始化。
范例:人类与学生类
package firstCourse; class Person { private int age; private Stringname; public Person(String name,int age) { this.name = name; this.age = age; } public int getAge() { returnage; } public void setAge(int age) { this.age = age; } public String getName() { return"这个人的名字:" +name; } public void setName(String name) { this.name = name; } public void info() { System.out.println(this.getName() +"," + this.age); } } class Studentextends Person { private int age; private Stringname; private Stringschool; public Student(String name,int age, String school) { super(name, age); this.name = name; this.age = age; this.school = school; } public String getName() { return"这个学生的名字:" +this.name; } public String getSchool() { returnschool; } public void setSchool(String school) { this.school = school; } public void info() { System.out.println(this.getName() +"," + this.age +"," + this.school); } } public class Demo { public static void main(String agrs[]) { Person per = new Student("Wolex", 22,"GDPU"); per.info(); Student stu = (Student) per; System.out.println(stu.getSchool());// per是不能调用getSchool()的。 } } |
这个学生的名字:Wolex,22,GDPU GDPU |
在new Student()时候,实际上是先给父类(Person类)分配内存空间,再给子类(Student类)分配,这里为了简洁就不画出Person类的堆内存。虽然per栈内存指向的是Student的实例化对象,但per在声明时已经确定只有Person的操作方法,所以per是不能调用setSchool()等方法的(即,父类不能调用子类添加的操作方法)。
2.6 抽象类和接口(核心重点)
之前所学习的所以概念:类、对象、构造、private、static、内部类、继承、覆写、多态性,都全部是为了下面要学习的抽象类和接口所服务的,而且在实际的开发之中,还有一个原则:“一个类永远都不要去继承一个已经实现好的类,只能继承抽象类和实现接口”。2.6.1 抽象类的基本概念
抽象类:类中包含一个抽象方法的类就是抽象类,抽象类要使用abstract关键字声明。
抽象方法:是一个只声明而未实现的方法,所有的抽象方法要使用abstract关键字声明,而所谓的没有实现就是指方法的后面没有“{}”,只是一个“;”;
范例:定义抽象类
abstract class A { // 抽象类 public void print() { // 普通方法 System.out.println("Hello"); } public abstract void get(); // 抽象方法 } |
public class Demo { public static void main(String args[]) { A a = new A(); } } |
A is abstract ;cannot be instantiated |
● 抽象类必须有子类,而且子类如果不是抽象类的话,必须覆写抽象类中的全部抽象方法;
● 一个子类只能继承一个抽象类,依然使用extends关键字完成;
● 抽象类的对象不能直接进行对象的实例化操作,因为一个类的对象如果实例化之后,则肯定就表示可以通过对象调用类中的方法了,可是抽象类之中存在抽象方法(未完成的方法),所以不能直接实例化,但是却可以依靠对象的向上转型,完成抽象类对象的实例化操作。
abstract class A { // 抽象类 public void print() { // 普通方法 System.out.println("Hello"); } public abstract void get(); // 抽象方法 } class B extends A { public void get() { System.out.println("Hello"); } } public class Demo { public static void main(String args[]) { A a = new B(); // 子类对象为父类实例化 a.get(); // 调用被子类所覆写过的方法 } } |
Hello |
思考问题:
1、 抽象类可以有构造方法吗?
可以有构造方法,而且抽象类的子类对象实例化的时候也依然符合于之前之类对象的实例化流程,先调用父类中的构造方法,为父类的属性初始化,而后再调用子类自己的构造方法,所以抽象类中属性、static属性、常量等都可以进行定义;
2、 抽象类中可以没有抽象方法吗?
可以没有抽象方法,尽管没有抽象方法了,也不能直接使用关键字new直接实例化抽象类的对象;
3、 一个抽象类中能否包含内部类或内部抽象类?
可以!使用代码如下:
abstract class A { // 抽象类 public void print() { // 普通方法 System.out.println("Hello"); } public abstract void get(); // 抽象方法 private class B { public void printB() { }; } abstract class C { public abstract void printC(); } } class X extends A { public void get() { } private class Y extends C { public void printC() { }; } } |
4、 一个抽象类能否使用static关键字声明?
● 如果现在在外部的抽象类使用了static定义,则肯定不能使用;
● 而如果在内部抽象类上使用static,则就表示是一个外部类,可以使用。
abstract class A { // 抽象类 public void print() { // 普通方法 System.out.println("Hello"); } public abstract void get(); // 抽象方法 static abstract class C { public abstract void printC(); } } class X extends A.C { public void printC() { } } |
不能,因为final定义的类不能有子类,而抽象类必须有子类,所以肯定不能使用final。
2.6.2 抽象类的应用 —— 模版设计模式
抽象类可以由属性、常量、普通方法、构造方法、抽象方法等几部分所组成,那么实际上这个就意味着,抽象类中多了些普通类没有的功能。
例如:人分为工人、学生、老师、这些人都可以说话,但是每一类人说话的方式肯定不同,学生和工人肯定有相似的东西,而且也肯定有各自不同的内容。
abstract class Person { // 抽象类 private String name; private int age; public Person(String name,int age) { this.name = name; this.age = age; } public void say() { System.out.println(this.getContent()); } public abstract String getContent(); public String getName() { return this.name; } public int getAge() { return this.age; } } class Student extends Person { private double score; public Student(String name,int age, double score) { super(name, age); this.score = score; } public String getContent() { return "我是一个学生,叫:" + super.getName() + ",我今年" +super.getAge() + "岁,成绩是:" +this.score; } } class Worker extends Person { private double salary; public Worker(String name,int age, double salary) { super(name, age); this.salary = salary; } public String getContent() { return "我叫:" + super.getName() + ",我今年" +super.getAge() + "岁,工资是:"+this.salary; } } public class Demo { public static void main(String args[]) { Student stu = new Student("张三", 20, 89.0); stu.say(); Worker wor = new Worker("李四", 40, 8000.0); wor.say(); System.out.println("===================================="); Person per = new Student("张三", 20, 89.0); per.say(); Person per1 = new Worker("李四", 40, 8000.0); per1.say(); } } |
我是一个学生,叫:张三,我今年20岁,成绩是:89.0 我叫:李四,我今年40岁,工资是:8000.0 ==================================== 我是一个学生,叫:张三,我今年20岁,成绩是:89.0 我叫:李四,我今年40岁,工资是:8000.0 |
范例:一个人可以有多项活动:run、dance、sing等。
abstract class Person { // 抽象类 public static final int RUN = 1; public static final int DANCE = 2; public static final int SING = 3; public void action(int code) { switch (code) { case RUN: this.run(); break; case DANCE: this.dance(); break; case SING: this.sing(); } } public abstract void run(); public abstract void dance(); public abstract void sing(); } class Baby extends Person { public void run() { System.out.println("Baby run!"); } public void dance() { System.out.println("Baby dance!"); } public void sing() { System.out.println("Baby sing!"); } } class Student extends Person { public void run() { System.out.println("Student run!"); } public void dance() { System.out.println("Student dance!"); } public void sing() { System.out.println("Student sing!"); } } public class Demo { public static void main(String args[]) { Person per = new Student(); per.action(Person.RUN); } } |
Student run! |
2.6.3 接口的基本概念
抽象类的组成:常量、变量、构造、普通方法、抽象方法,那么如果现在一个类之中只由全局常量和抽象方法组成的特殊的类实际上就可以将其定义为接口;
接口:抽象方法和全局常量的集合,在java之中,接口使用interface关键字完成,而且在接口之中的所有全局常量和抽象方法的访问权限永远都是public。
接口的定义格式:
interface 接口名称{ 全局变量; 抽象方法; } |
范例:定义一个接口
interface A { // 定义接口 public static final String MSG ="Hello"; // 全局变量 public abstract void print(); // 抽象方法 } |
● 接口必须有子类,子类实现(implement)接口,如果子类不是抽象类,则必须覆写全部的抽象方法;
● 接口的对象实例化,可以依靠对象的向上转型完成,子类要覆写父类中的操作方法,发生了向上转型的关系之后,所调用的方法肯定由子类覆写所决定。
范例:使用接口
interface A {// 定义接口 public static final String MSG ="Hello"; // 全局变量 public abstract void print(); // 抽象方法 } interface B { public abstract void get(); } class X implements A, B { public void print() { System.out.println("X.A"); } public void get() { System.out.println("A接口中的常量:" + A.MSG); } } public class Demo { public static void main(String args[]) { A a = new X();// 子类向父类对象转型 a.print(); // 调用子类覆写的方法 B b = new X();// 子类向父类对象转型 b.get(); } } |
X.A A接口中的常量:Hello |
既然接口本身就是全局常量和抽象方法的组成,所以以下的两种接口定义效果是一样的:
完整定义: | 简化定义: |
interface A { // 定义接口 public static final String MSG ="Hello"; publicabstract void print(); } | interface A { // 定义接口 final String MSG ="Hello"; void print(); } |
如果现在一个子类想要实现接口又要继承抽象类的话,则应该采用先继承(extends),再实现(implements)。
interface A { // 定义接口 public static final String MSG ="Hello"; // 全局变量 public abstract void print(); // 抽象方法 } interface B { public abstract void get(); } abstract class C { public abstract void fun();//抽象类中不可以省略abstract } class X extends Cimplements A, B { public void print() { System.out.println("X.A"); } public void get() { System.out.println("A接口中的常量:" + A.MSG); } public void fun() { } } |
interface A { // 定义接口 public static final String MSG ="Hello"; // 全局变量 public abstract void print(); // 抽象方法 } interface B { public abstract void get(); } abstract class C implements A, B { public abstract void fun(); // 抽象类中不可以省略abstract //因为C为抽象类,所以不用覆写A、B中的抽象方法 } class X extends C { public void print() { System.out.println("X.A"); } public void get() { System.out.println("A接口中的常量:" + A.MSG); } public void fun() { } } |
interface A { // 定义接口 public void printA(); } interface B { public void printB(); } interface C extends A, B {// 同时继承了A,B两个接口 public void printC(); } class X implements C { public void printA() { } public void printB() { } public void printC() { } } |
接口有三大主要作用:
● 作用一:可以作为一个操作的标准存在;
● 作用二:表示一种操作的能力;
● 作用三:将服务器端的方法名称暴露给远程客户端(分布式)。
2.6.4 使用接口表示标准
对于接口的名称,在生活之中应该并不陌生,例如:有USB接口,那么接口实际上表示的应该就是一个操作的标准,就拿USB设备说话。
范例:模拟一个USB设备的操作程序
interface USB { public void start(); public void stop(); } class Computer { public void plugin(USB usb) { usb.start(); usb.stop(); } } class Flash implements USB { public void start() { System.out.println("U盘开始工作。"); } public void stop() { System.out.println("退出U盘。"); } } class Print implements USB { public void start() { System.out.println("打印机开始工作。"); } public void stop() { System.out.println("打印机停止工作。"); } } public class Demo { public static void main(String args[]) { new Computer().plugin(new Flash()); new Computer().plugin(new Print()); } } |
U盘开始工作。 退出U盘。 打印机开始工作。 打印机停止工作。 |
之前说过,抽象类就是操作的模版,也就相当于标准,所以以上接口换成抽象类一样可以实现此功能。如下(更改部分用高亮标出):
abstractclass USB { public abstract void start(); public abstract void stop(); } class Computer { public void plugin(USB usb) { usb.start(); usb.stop(); } } class Flash extends USB { public void start() { System.out.println("U盘开始工作。"); } public void stop() { System.out.println("退出U盘。"); } } class Print extends USB { public void start() { System.out.println("打印机开始工作。"); } public void stop() { System.out.println("打印机停止工作。"); } } public class Demo { public static void main(String args[]) { new Computer().plugin(new Flash()); new Computer().plugin(new Print()); } } |
U盘开始工作。 退出U盘。 打印机开始工作。 打印机停止工作。 |
如果一个接口要想表示是某一个类具备的能力的话,则往往可以不定义任何的操作方法,例如:中国人可能会中文、或英文、或中英文。
interface Chinese {//不定义方法,表示能力 } interface English {//不定义方法,表示能力 } class BeiJingRen implements Chinese { } class LiaoNingRen implements Chinese, English { } public class Demo { public static void main(String args[]) { action(new BeiJingRen()); action(new LiaoNingRen()); service(new LiaoNingRen()); } public static void action(Chinese c) {// 某些操作 // 执行某些操作 } public static void service(English s) { } } |
2.6.6 关于接口的几点说明
由于接口的开发在实际中作用较为明显,所以下面给出几个接口的说明:
1、 接口中的所有方法都是public,而且使用接口定义方法的时候要比定义常量的情况多;
2、 在一个接口之中,除了可以定义全局常量和抽象方法之外,一个接口的内部也可以包含多个内部类、内部抽象类、内部接口,反之,抽象类也可以定义内部接口;
3、 如果一个接口的内部接口使用了static声明,则表示了此接口是外部接口,完整的名称式“外部接口.内部接口”。
interface A{ static interface B{ public void fun(); } } class X implements A.B{ public void fun(){} } |
2.6.7 接口的实际应用(Factory,工厂设计模式)
观察下面一段代码并指出其错误。
范例:接口操作
interface Fruit { public void eat(); } class Apple implements Fruit { public void eat() { System.out.println("**吃苹果。"); } } class Orange implements Fruit { public void eat() { System.out.println("**吃橘子。"); } } public class Demo { public static void main(String args[]) { Fruit f = new Apple();// 子类向父类转型 f.eat(); } } |
** 吃苹果。 |
分析一下水果摊上买水果的过程。
以上的图形表示的是。顾客通过老板那里取得了一个(水果)接口的实例化对象(苹果、西瓜),用户不关心这些(水果)接口从哪里来的,只知道通过这个“老板”可以取得水果。
以上的代码,写的是Apple类,所以客户端(主方法)可以取得接口的实例化对象,如果现在不使用苹果,使用橘子操作呢?
本程序的最大问题在于耦合度加深,现在的接口和一个固定的子类永远绑定在一起。
于是现在的问题式由:A → B,而要想修改呢,则可以参考JVM的运行机制:
● 程序代码 → JVM →OS:达到可移植性;
所以现在应该将其修改为:A → C → B,以进行解耦合的操作,所以中间可以采用一个新的类。
interface Fruit { public void eat(); } class Apple implements Fruit { public void eat() { System.out.println("**吃苹果。"); } } class Orange implements Fruit { public void eat() { System.out.println("**吃橘子。"); } } class Factory { public static Fruit getFruit(String className) { if ("apple".equals(className)) { return new Apple(); } else if ("orange".equals(className)) { return new Orange(); } else { return null; } } } public class Demo { public static void main(String args[]) { Fruit f = Factory.getFruit(args[0]); //因为此处调用的是static方法,所以可以不用实例化 //或者: Fruit f =new Factory().getFruit(args[0]); f.eat(); } } |
java Demo apple |
** 吃苹果。 |
注:以上接口同样可以替换成抽象类。
2.6.8 接口的实际应用(Proxy,代理设计模式)
下面观察如果一个场景:
如果要想变为程序表示,则代码开发如下:
interface Subject { // 两个人共同的操作主题 public void get(); // 要回东西 } class RealSubject implements Subject { public void get() { System.out.println("只关注自己的核心业务:还钱。"); } } class ProxySubject implements Subject { private Subject sub = null; // 表示代理的委托人 public ProxySubject(Subject sub) {// 传入委托者 this.sub = sub; } public void prepare() { System.out.println("进行若干个操作前的准备。"); } public void over() { System.out.println("销毁证据。"); } public void get() { // 代理中的操作 this.prepare(); this.sub.get();// 让真实主题操作 this.over(); &n 26bc6 bsp; } } public class Demo { public static void main(String args[]) { Subject pro = new ProxySubject(new RealSubject());// 代表真实的讨债者 pro.get(); // 调用接口中的方法 } } |
进行若干个操作前的准备。 只关注自己的核心业务:还钱。 销毁证据。 |
● 首先定义出一个表示公共操作的核心接口;
● 在此接口中定义两个子类:
|- 真实主题类:只负责具体的操作业务;
|- 代理主题类:为真实主题类的操作提供辅助功能,如果没有此类的支持,真实主题无法操作;
2.6.9 接口的实际应用(Adapter 适配器设计模式)(了解)
在正常的情况下,如果一个接口的子类肯定要覆写接口中的全部抽象方法。
interface Window { public void close(); public void repair(); public void open(); } class MyWindow implements Window { public void close() { System.out.println("关闭窗户。"); } public void repair() { System.out.println("维修窗户。"); } public void open() { System.out.println("打开窗户。"); } } |
这个时候中间只能加入一个过渡类,这个过渡类要满足两点要求:
● 要求一:此类对接口中的方法进行假实现;
● 要求二:过渡类不能被直接使用,不能直接进行实例化对象的操作;
而现在只有抽象类可以,因为抽象类可以实现接口,抽象类可以对方法假实现,抽象类也无法直接产生实例化对象。
interface Window { public void close(); public void repair(); public void open(); } abstract class WindowAdapter { public void close() { } public void repair() { } public void open() { } } class MyWindow extends WindowAdapter { public void open() { System.out.println("打开窗户。"); } } |
2.7 抽象类和接口的区别(面试题,重点)
在实际开发之中,抽象类和接口是两个使用最广泛的概念,而且通过抽象类和接口的使用,可以发现两者实际上定义的都是操作的标准,有着相似的功能,在面试之中也经常出现,所以下面对这两个概念做一个完整的总结,这些总结必须记住!No | 区别点 | 抽象 | 接口 |
1 | 关键字 | abstract class | Interface |
2 | 组成 | 属性、(全局)常量、构成方法、普通方法、抽象方法、static方法 | 全局常量、抽象方法 |
3 | 权限 | 可以使用各个权限:public、private | 所有的权限都是public |
4 | 使用 | 抽象类必须有子类,子类继承抽象类 | 接口必须有子类,子类必须实现接口 |
5 | 子类关键字 | class子类extends抽象类 | class子类implements接口A,接口B,…… |
6 | 关系 | 一个抽象类可以实现多个接口 | 接口不能继承抽象类,但是可以继承多个接口 |
7 | 设计模式 | 模版设计模式 | 工厂设计、代理设计 |
两者一起结合起来可以完成适配器设计模式 | |||
8 | 局限 | 子类只能继承一个父类,有单继承局限 | 子类可以实现多个接口,避免了单继承局限 |
问题:抽象类、接口、普通类这之间的关系是什么?
之前的类是表示具有公共的数据集合,是一个抽象的概念,但是抽象类和接口呢?通过之前的代码讲解可以发现,实际上抽象类和接口是在类之上的产物,下面通过一张图形来表示一下。
动物:应该作为接口出现,因为是一个操作的标准
如果说抽象类是对某一类事务的再抽象,那么接口就相当于对抽象类的再次抽象,即:接口才是真正的表示出抽象概念的最高点,当然,以上的图形之中,即使不使用抽象类也可以通过接口实现,但是使用抽象类唯一的好处是,可以定义一些普通方法。
2.8 实例分析:宠物商店(重点)
宠物接口:interface Pet { // 定义宠物接口 public String getName(); public String getColor(); public int getAge(); // 可以增加getInfo()方法,方便输出 } |
class Cat implements Pet {// 猫是宠物,实现接口 private String name; // 宠物名字 private String color; // 宠物颜色 private int age; // 宠物年龄 public Cat(String name, String color,int age) { this.setName(name); this.setColor(color); this.setAge(age); } public void setName(String name) { this.name = name; } public void setColor(String color) { this.color = color; } public void setAge(int age) { this.age = age; } public String getName() { return this.name; } public String getColor() { return this.color; } public int getAge() { return this.age; } } |
class Dog implements Pet {// 狗是宠物,实现接口 private String name; // 宠物名字 private String color; // 宠物颜色 private int age; // 宠物年龄 public Dog(String name, String color,int age) { this.setName(name); this.setColor(color); this.setAge(age); } public void setName(String name) { this.name = name; } public void setColor(String color) { this.color = color; } public void setAge(int age) { this.age = age; } public String getName() { return this.name; } public String getColor() { return this.color; } public int getAge() { return this.age; } } |
class PetShop { // 宠物商店 private Pet[] pets; // 保存一组宠物 private int foot; public PetShop(int len) { if (len > 0) { this.pets =new Pet[len]; // 开辟数组大小 } else { this.pets =new Pet[1]; // 至少开辟一个空间 } } public boolean add(Pet pet) { // 增加的是一个宠物 if (this.foot <this.pets.length) { this.pets[this.foot] = pet;// 增加宠物 this.foot++; return true; } else { return false; } } public Pet[] search(String keyWord) { // 应该确定有多少个宠物符合要求 Pet p[] = null; int count = 0; // 记录下会有多少个宠物符合查询结果 for (int i = 0; i <this.pets.length; i++) { if (this.pets[i] !=null) { // 表示此位置有宠物 if (this.pets[i].getName().indexOf(keyWord) != -1 || this.pets[i].getColor().indexOf(keyWord) != -1) { count++; // 修改查找到的记录数 } } } p = new Pet[count];// 开辟指定的大小空间 int f = 0; // 增加元素的位置标记 for (int i = 0; i <this.pets.length; i++) { if (this.pets[i] !=null) { // 表示此位置有宠物 if (this.pets[i].getName().indexOf(keyWord) != -1 || this.pets[i].getColor().indexOf(keyWord) != -1) { p[f] = this.pets[i]; f++; // 可以不定义f变量,则以上的两行代码替换成: // p[--count] = pets[i]; } } } return p; } } |
public class PetShopDemo { public static void main(String args[]) { PetShop ps = new PetShop(5);// 五个宠物 ps.add(new Cat("白猫","白色的", 2));// 增加宠物,成功 ps.add(new Cat("黑猫","黑色的", 3));// 增加宠物,成功 ps.add(new Cat("花猫","花色的", 3));// 增加宠物,成功 ps.add(new Dog("拉步拉多","黄色的", 3));// 增加宠物,成功 ps.add(new Dog("金毛","金色的", 2));// 增加宠物,成功 ps.add(new Dog("黄狗","黑色的", 2));// 增加宠物,失败 print(ps.search("黑")); } public static void print(Pet p[]) { for (int i = 0; i < p.length; i++) { if (p[i] != null) { System.out.println(p[i].getName() +"," + p[i].getColor() + ","+ p[i].getAge()); } } } } |
黑猫,黑色的,3 |
2.9 Object类(核心重点)
在java之中,所有用户所定义的类都是会存在一个继承关系的,即:任何一个类实际上都会默认继承Object类,那么按照这种方式理解,以下类的两种定义格式是一样的:class Person { } | class Person extendsObject{ } |
class Person extends Object { } public class Demo { public static void main(String args[]) { Object obj = new Person();// 子类实例化变为父类对象 } } |
● 取得对象信息:publicString toString( );
● 对象比较:publicboolean equals(Object obj);
● 取得对象的Hash码:public int hashCode( );
2.9.1 取得对象信息:toString()
之前提及到:一个类的对象如果直接打印,会取得对象的地址信息。
class Person extends Object { } public class Demo { public static void main(String args[]) { Object per = new Person(); System.out.println(per); System.out.println(per.toString()); } } |
course_2.Person@4b09558d course_2.Person@4b09558d |
class Person extends Object { private String name; private int age; public Person(String name,int age) { this.name = name; this.age = age; } public String toString() { return "姓名:" + this.name +",年龄:" + this.age; } } public class Demo { public static void main(String args[]) { Object obj = new Person("张三", 20); System.out.println(obj.toString()); } } |
姓名:张三,年龄:20 |
2.9.2 对象比较:equals( )
曾经在String类中学过equals()方法的使用,实际上String也是Object类的子类,所以实际上String类中就是覆写了Object类的equals(),而严格来讲,任何一个对象的比较操作也都应该使用equals()完成,而不是像最早那样利用compare()方法编写。
class Person extends Object { private String name; private int age; public Person(String name,int age) { this.name = name; this.age = age; } public boolean equals(Object other) { if (this == other) {//地址相同 return true; } if (other == null) { return false; // 没有内容 } if (!(other instanceof Person)) { // 不是本类对象 return false; } Person per = (Person) other; if (this.name == per.name &&this.age == per.age) { return true; } else { return false; } } public String toString() { return "姓名:" + this.name +",年龄:" + this.age; } } public class Demo { public static void main(String args[]) { Object per1 = new Person("张三", 20); Object per2 = new Person("张三", 20); System.out.println(per1.equals(per2)); System.out.println(per1.equals("per2")); } } |
true false |
2.9.3 接收引用数据
Object类除了可以接收任意的类对象的向上转型之外,也可以接收所有的引用数据类型,这就包含了数组和接口,但是数组和接口不像类那样,直接继承了Object,所以这个功能只能按照一个特性去记!
范例:保存数组
public class Demo { public static void main(String args[]) { Object obj = new int[] { 1, 2, 3 };// 保存数组 int temp[] = (int[]) obj;// 向下转型 for (int x = 0; x < temp.length; x++) { System.out.println(temp[x]); } } } |
1 2 3 |
范例:接收接口实例
interface Person { } class Student extends Objectimplements Person { public String toString() {// 只要打印对象就调用此方法 return "学生信息"; } } public class Demo { public static void main(String args[]) { Object obj = new Student();// 子类向父类转换 System.out.println(obj); Person per = (Person) obj; // 变为接口实例 System.out.println(per); // 就调用被子类所覆写的toString() } } |
学生信息 学生信息 |
2.10 匿名内部类(次重点)
内部类指的是在一个类的内部定义了其他的操作类的情况,而匿名内部类是在抽象类和接口的基础之上衍生的。例如,观察如下一段代码。范例:观察程序
interface Person { public void print(); } class Studentextends Object implements Person { public void print() { System.out.println("我是一个学生。"); } } class Test { public void fun(Person per) { // 接收Person接口对象 per.print();// 调用接口的方法 } public void get() { this.fun(new Student());//传入子类对象 } } public class Demo { public static void main(String args[]) { new Test().get(); } } |
我是一个学生。 |
interface Person { public void print(); } class Test { public void fun(Person per) { // 接收Person接口对象 per.print();// 调用接口的方法 } public void get() { this.fun(new Person(){ // 在此地方实现接口 public void print() { // 覆写方法 System.out.println("我是一个学生。"); } }); } } public class Demo { public static void main(String args[]) { new Test().get(); } } |
我是一个学生。 |
(1)直接实例化接口对象,代码如下:
new Person(){} |
(2)实现抽象方法,代码如下:
public void get() { this.fun(new Person() { // 在此地方实现接口 public void print() { // 覆写方法 System.out.println("我是一个学生。"); } }); |
匿名内部类比内部类的代码结构还要混乱,但是匿名内部类使用是非常多的,而且随着技术的发展及完善匿名内部类的使用也是越来越广泛,而匿名内部类的唯一好处是在某一个接口或抽象的子类只使用一次的情况下才能体现出来的,而且匿名内部类如果觉得编写不舒服,也可以使用单独定义子类实现。
但是对于日后框架开发,Android开发之中,都会使用到此概念。
2.11 包装类(重点)
2.11.1 包装类的基本概念在java之中一直提倡一种概念:“一切皆对象”。但是这里面就会存在一个问题,基本数据类型不是对象,所以所谓的包装类指的是将一个基本数据类型封装为类的形式,就好像如下的操作代码:
class Int { // 包装int型 private int num; public Int(int num) { this.num = num; } public int inValues() { return this.num; } } public class Demo { public static void main(String args[]) { Int x = new Int(10);// 包装 int temp = x.inValues();//取出数据 } } |
而实际上这八个包装类,也分为两组:
● 数值型(Number子类):Integer、Double、Byte、Short、Long、Float;
● 对象型:Character、Boolean;
Number作为所有数值型操作父类,在开发之中主要的功能是取得包装类中包装的数据。
2.11.2 装箱和拆箱操作
装箱:指的是将基本数据类型变为类的形式; → 利用包装类的构造完成
拆箱:将包装类中的数据取出,变为基本数据类型的操作; → Number类完成
范例:以int型数据为例,装箱及拆箱
public class Demo { public static void main(String args[]) { Integer x = new Integer(10);// 包装 int temp = x.intValue(); //取出数据 System.out.println(temp * 2); } } |
20 |
public class Demo { public static void main(String args[]) { Double x = new Double(10.2); // 包装 double temp = x.doubleValue(); // 取出数据 System.out.println(temp * 2); } } |
20.4 |
范例:以double为例
public class Demo { public static void main(String args[]) { Double x = 10.2; // 自动包装 double temp = x; // 自动取出数据 System.out.println(temp * 2); } } |
20.4 |
public class Demo { public static void main(String args[]) { Boolean flag = true;// 自动包装 boolean temp = flag;// 自动取出数据 System.out.println(temp); } } |
true |
public class Demo { public static void main(String args[]) { Integer x = 10; Integer y = 20; System.out.println(++x * y); // 直接进行计算 } } |
220 |
public class Demo { public static void main(String args[]) { Integer x = 10; Integer y = new Integer(10); Integer z = 10; System.out.println(x == y); System.out.println(y == z); System.out.println(x == z); System.out.println(x.equals(y)); } } |
false false true true |
使用包装类除了之前的装箱及拆箱功能之外,最大的特点在于可以使用包装类完成数据的转换,可以将字符变为指定的数据类型。
●以Integer为例,方法:
publicstatic intparseInt(String)throws NumberFormatException;
● 以Double为例,方法:
public staticdouble parseDouble(String) throws NumberFormatException;
● 以Boolean为例,方法:
publicstatic boolean parseBoolean(String);
范例:转换类型
public class Demo { public static void main(String args[]) { String str = "12"; // 由数字组成 int x = Integer.parseInt(str); System.out.println(x * x); } } |
144 |
public class Demo { public static void main(String args[]) { String str = "true"; boolean x = Boolean.parseBoolean(str); if (x) { System.out.println("***********"); } } } |
*********** |
Java.lang.NumberFormatException |
那么此时已经清楚了字符串变为基本数据类型,那么如何将基本数据类型变为字符串呢?
● 方式一:任何数据类型碰到字符串之后,都变为字符串
public class Demo { public static void main(String args[]) { int x = 10; String str = x + ""; //变为String,会存在垃圾 System.out.println(str.length()); } } |
2 |
public class Demo { public static void main(String args[]) { int x = 10; String str = String.valueOf(x); //变为String System.out.println(str.length()); } } |
2 |
2.12 大型思考题一(理解)
在之前已经完成了链表的基本功能实现,但是之前的链表并不能适应于程序的开发,因为按照之前给出的程序设计来讲,类中的所有操作都必须返回后输出(处理),那么下面给出一个链表的实现接口,要求定义其子类。interface Link{ publicvoid add(Object data); //增加新数据 public void addAll(Object data[]); public int size(); //取得保存的数据长度 public boolean isEmpty(); //判断是否为空链表 public Object [] toArray(); //将链表中的数据变为对象数组返回 public boolean contains(Object data); //查找某一个元素是否存在 public Object get(intindex); //取得指定位置的元素 publicvoid remove(Object data); //删除某一个元素 } |
提示:使用Object类也可以保存基本数据类型:
● int → 装箱为Integer → Object;
public class Demo { public static void main(String args[]) { Object x = 10; // int --> Integer --> Object int y = (Integer) x;// Object --> Integer --> 拆箱 System.out.println(y * y); } } |
100 |
首先,本程序之中已经包含了太多的方法,那么首先定义一个接口的子类,在子类之中,假实现所有的抽象方法。
package course_2; interface Link { public void add(Object data); // 增加新数据 public void addAll(Object data[]); public int size(); // 取得保存的数据长度 public boolean isEmpty(); // 判断是否为空链表 public Object[] toArray();// 将链表中的数据变为对象数组返回 public boolean contains(Object data); // 查找某一个元素是否存在 public Object get(int index);// 取得指定位置的元素 public void remove(Object data); // 删除某一个元素 } class LinkImpl implements Link { private Node root; private int count = 0; private Object retData[]; // 此数组用于保存返回的数据 private int foot = 0; // 脚标 private class Node { // 定义节点类 private Object data;// 数据使用Object保存 private Node next; // 下一个节点 public Node(Object data) { this.data = data; } private void addNode(Node newNode) { if (this.next ==null) { this.next = newNode; } else { this.next.addNode(newNode); } } private void toArrayNode() { LinkImpl.this.retData[LinkImpl.this.foot++] =this.data;// 取当前数据 if (this.next !=null) { // 还有下一个元素 this.next.toArrayNode(); } } private void removeNode(Node previous, Object data) { if (this.data.equals(data)) { previous.next = this.next; } else { this.next.removeNode(this, data); } } } public void add(Object data) { if (data == null) { return; // 不能增加空数据 } Node newNode = new Node(data);// 封装数据 if (this.root ==null) { this.root = newNode; } else { this.root.addNode(newNode);// 交给Node类 } this.count++; } public void addAll(Object data[]) { if (data == null) { return; } else { for (int x = 0; x < data.length; x++) { this.add(data[x]); } } } public int size() { return this.count; } public boolean isEmpty() { return this.count == 0;//注意此用法 } public Object[] toArray() { if (this.root ==null) { return null; } this.retData =new Object[this.count];// 根据已有的数据开辟空间 this.foot = 0; this.root.toArrayNode(); return this.retData; } public boolean contains(Object data) { Object result[] = this.toArray(); for (int x = 0; x < result.length; x++) { if (result[x].equals(data)) { //为了避免传入的data值为null而出现的空指针异常, // 此处不能写成:data.equals(result[x]) return true; } } return false; } public Object get(int index) { if (index > this.count) { return null; } return this.toArray()[index]; } public void remove(Object data) { if (this.contains(data)) { if (this.root.data.equals(data)) {// 根节点满足 this.root =this.root.next; } else { this.root.next.removeNode(this.root, data); } this.count--; } } } public class Demo { public static void main(String args[]) { Link all = new LinkImpl(); all.add("A"); all.add("B"); all.add("C"); System.out.println(all.size()); System.out.println(all.isEmpty()); Object temp[] = all.toArray(); for (int x = 0; x < all.size(); x++) { System.out.print(temp[x] +"\t"); } System.out.println("\n" + all.contains("A")); System.out.println(all.contains("B")); System.out.println(all.get(1)); System.out.println(all.get(10)); all.remove("A"); System.out.println(all.size()); } } |
3 false A B C true true B null 2 |
2.13 大型思考题二(最核心)
在一个图书大厦里面有许多书,例如:计算机类的,数学类的,幼儿教育类的,现在要求通过面向对象的概念,表示出此应用的程序,要求实现图书的上架,关键字检索,下架的功能。类的结构自行设计,就要代码的关系。
范例:定义图书的操作标准,肯定要使用接口
interface Book { public String getTitle(); public String getAuthor(); } |
范例:定义图书大厦的类,里面要保存有多本书,现在不知道有多少本书,所以使用Link接口和LinkImpl类完成。
class Bookshop{ //定义图书大厦 private Link books; //保存多本书 public Bookshop(){ this.books=new LinkImpl();//准备好存放空间 } public void add(Book book){ //增加一本书 this.books.add(book); //向链表中增加 } public void delete(Book book){ this.books.remove(book); //删除一本书 } public Link search(String keyWord){ //返回一组满足条件数据 Link result=new LinkImpl(); Object obj[]=this.books.toArray();//依次判断数据 for(int x=0;x<obj.length;x++){ Book book=(Book)obj[x]; //保存的都是Book接口 if(book.getTitle().contains(keyWord) ||book.getAuthor().contains(keyWord)){ result.add(book); //向返回的结果集合中增加 } } return result; } } |
class ComputerBook implements Book { // 计算机类图书 private String title; private String author; public ComputerBook(String title, String author) { this.title = title; this.author = author; } public String getTitle() { return this.title; } public String getAuthor() { return this.author; } public String toString() { return "计算机类图书,名称是:" + this.title +",作者:" + this.author; } public boolean equals(Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (!(obj instanceof ComputerBook)) { return false; } ComputerBook b = (ComputerBook) obj; if (b.title.equals(this.title) && b.author.equals(this.author)) { return true; } return false; } } |
class MathBook implements Book { private String title; private String author; public MathBook(String title, String author) { this.title = title; this.author = author; } public String getTitle() { return this.title; } public String getAuthor() { return this.author; } public String toString() { return "数学类图书,名称是:" + this.title +",作者:" + this.author; } public boolean equals(Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (!(obj instanceof MathBook)) { return false; } MathBook b = (MathBook) obj; if (b.title.equals(this.title) && b.author.equals(this.author)) { return true; } return false; } } |
public class Demo { public static void main(String args[]) { Bookshop shop = new Bookshop(); shop.add(new ComputerBook("JAVA","李兴华")); shop.add(new ComputerBook("JSP","MLDN")); shop.add(new ComputerBook("Android","MLDN")); shop.add(new MathBook("初等数学A","unkonw")); shop.add(new MathBook("高等数学","unkonw")); shop.delete(new MathBook("初等数学A","unkonw")); Link temp = shop.search("A"); Object obj[] = temp.toArray(); for (int x = 0; x < obj.length; x++) { System.out.println(obj[x]); } } } |
计算机类图书,名称是:JAVA,作者:李兴华 计算机类图书,名称是:Android,作者:MLDN |
● 老板开车进城买水果;
● 一个停车场可以停放多种车辆;
● 一间教室可以有多个学生,若干个老师;
现在这种一对多关系通过链表的程序支持,可以很方便的实现。
3、总结
1、final关键字:定义不能被继承的类,定义不能被覆写的方法,定义常量,而且使用publicstatic final定义的常量称为全局常量,常量的每个字母都要求大写;2、单例设计(Singleton)模式的特点及作用;
3、对象多态性:子类和父类对象之间的互相转型操作;
● 对象的向上转型:父类父类对象 =子类实例; →自动完成
● 对象的向下转型:子类子类对象 =(子类)父类实例; → 强制完成
● 在对象发生向下转型的操作之前必须首先发生向上转型,以建立子类和父类对象之间的关系,而且对象多态性的最大好处是根据实例化其子类的不同,调用的被覆写的方法也不同。从开发的角度而言,80%都是向上转型,而只有20%才是向下转型的操作;
4、使用instanceof关键字可以判断某一个对象是否是某一个类的实例;
5、抽象类和接口概念、特别、区别;
6、设计模式:工厂设计(Factory)、代理设计(Proxy),此类代码要求掌握其代码结构,而对于其应用今后会提及;
7、Object类:是所有类的父类
● 主要的方法:toString()、equals()、hashCode();
● 使用Object类的对象可以接收任意的引用数据类型;
8、包装类和String类相结合,可以完成数据类型的互转换操作;
9、图书大厦的应用程序;