Java面向对象相关概念
Java是一种基于面向对象概念的编程语言,使用高度抽象化来解决现实世界的问题。 面向对象的方法将现实世界中的对象进行概念化,以便于在应用之间进行重用。例如:椅子、风扇、狗和电脑等。
Java里的类(Class)是一个蓝图、模板,或者称之为原型,它定义了同一类事物的相同属性和行为。实例(Instance)是某个类的一个具体实现,同一个类所有的实例拥有相同的属性。举例来说,你可以定义一个类叫做“房子(House)”,这个类拥有一个属性叫做“房间数(number of room)”,这样你就可以创建一个“房间数”为2的“房子”实例,你还可以创建一个“房间数”为3的“房子”实例,等等等等。
优点:
面向对象软件开发的若干优点在于:
- 模块化,维护成本低;
- 更好的代码重用,具备继承性,开发更为敏捷;
- 更好的代码可靠性和灵活性;
- 对现实世界进行建模,易于理解;
- 对象水平的抽象;
- 从一个开发阶段向另一个开发阶段过渡更为简便。
面向对象软件系统(OOPS)的四大主要特征为:
- 封装(Encapsulation)
- 继承(Inheritance)
- 多态(Polymorphism)
- 抽象(Abstraction)
封装(Encapsulation)
封装机制在对象之间提供了一种隐藏域可见性的协议。Java中使用可见性修饰符private将方法和变量限制在类内部。Java提供的可见性修饰符包括public/ default/ protected/ private,用来在不同层面上隐藏变量、方法和类,但最终目的在于封装那些不需要进行修改的东西。实践表明,每个类应该只存在一种被修改的原因,而封装(Encapsulate)让这种“唯一原因”原则成为现实。
同时,最佳实践表明,封装意味着将会经常改变的东西隐藏起来,以防止对其他类造成破坏。
封装的优点
- 我们可以通过隐藏属性来保护对象的内部状态;
- 能够防止对象之间不恰当的相互作用,进而促进代码模块化;
- 增强可用性;
- 在特定对象之间维护互访协议;
- 封装以促进代码维护;
- 可以独立地进行代码修改
多态(Polymorphism)
多态是指使用相同的接口来应对不同的底层形式(例如数据类型)的能力。这就意味着同一个类可以使用一个共同的接口来实现多种不同的功能,并能通过传递特定的类引用来动态触发这些功能。
一个经典的多态的实例为“形状”类(Shape),以及所有继承Shape的类,如方形(square)、圆形(circle)、多面体(dodecahedron)、不规则多边形(irregular polygon)和长条(splat)等。在这个例子中,每个类中都拥有一个自己的Draw()函数,客户端程序代码可以简简单单地如下所示:
1
Shape shape=newSquare ();
执行Shape.area() 可以得到任何形状的正确面积。
多态的美妙之处在于,不同类里的代码不需要知道自己所在的是哪个类,它们的使用方式都是一样的。
面向对象的编程语言在运行时所实现的多态过程叫做动态绑定。
注:多态是指根据调用函数的对象来选择更具针对性的方法的特性。当没有抽象类的时候就可以使用多态。
多态的优点
- 可用于创建可重用代码:一旦类被创建,实施和测试,就可以直接进行使用而不考虑具体的代码细节;
- 提供更为泛化和松耦合的代码;
- 编译时间更短,开发更为敏捷;
- 动态绑定;
- 可以使用同一个接口的不同方法来实现不同的功能;
- 可以使用相同的方法签名来代替完全实施。
方法覆盖实现多态:覆盖涉及到两个不同的方法,一个父类的方法,另一个则是子类中的方法,两个方法具有相同的函数名和方法签名。
覆盖可以以不同的方式对不同的对象类型定义相同的操作,例如:
1
2
3
4
while(it.hasNext()) {
Shape s = (Shape) it.next();
totalArea += s.area(dim);//多态方法调用,将根据对象类型自动调用正确的方法
}
方法重载、Ad-hoc多态性和静态多态
重载涉及的是同一个类内具有相同名称,但方法签名不同的多个方法。可以用不同的方法为不同的数据定义相同的操作。我们经常所说的静态多态实际上并不是真正意义上的多态。
方法重载实际上就是指两个方法使用相同的名称,但参数不同。这与继承和多态完全没有关系。重载方法不是覆盖方法。[Head First Java深入浅出]
Java中基于泛型的参数多态性
当进行类声明时,一个属性域名称可以与多种不同的数据类型相关联,一个方法也可以与不同的参数类型和返回类型相关联,Java支持使用泛型的参数多态性。例如,一个list对象可以通过泛型来接收它所包含的数据类型:
1
List
为什么在Java里我们不能覆盖静态(static)方法?
覆盖依赖于具体的类实例。多态的关键之处在于你可以继承一个类,而该类的子类所定义的实例对象又可以对父类中定义的方法进行了覆盖。而静态方法是不依赖与具体实例的,因此覆盖这一概念不适用于静态方法。
在Java设计早起有两点考虑直接导致了这一现象。第一是对性能方面的考虑:之前人们对Smalltalk语言(一种面向对象编程语言)运行太慢(垃圾回收和多态调用所致)的批评不绝于耳,Java的设计者决定回避这一弊端。第二是考虑到Java的预期受众主要是C++开发人员,而使静态方法能直接被调用刚好能迎合C++编程人员的开发习惯,同时由于不用上溯类层级结构来查找要调用的方法,而是直接调用指定类中的特定方法,这一设计使得代码运行非常快速。
继承(Inheritance)
继承是指派生类中包含了基类中的所有的行为(即方法)和状态(即变量),并能通过该派生类进行访问。继承的关键好处在于它提供了代码重用和避免重复的一遍机制。
继承类通过重用父类的方法并添加一些新的功能来扩展应用程序的功能。这回导致紧耦合,如果你想对父类进行修改,你必须清楚其所有子类的具体细节以防止阻断。
这是一种软件复用性,新类(子类)继承已有的父类,重用父类的特征并能添加一些新的功能。
因此,举例来说,如果你有一个Parent类和一个扩展(使用关键字extends)Parent类的Child类,那么Child类继承了Parent类所有特征。
优点
- 促进重用性;
- 建立逻辑“is a”关系,如:Dog is an animal.
- 使代码模块化;
- 避免冲突。
缺点
- 紧耦合:子类的实现依赖于父类,导致紧耦合。
抽象(Abstraction)
抽象意味着只需要开发类的接口和功能声明,而不需要实现具体的实施细节。抽象类呈现的是接口,而不需要具体实现,将对象的实施与行为或实现分离开来,进而通过隐藏无关的细节来减少复杂度。
优点
- 通过使用抽象,我们可以将不同类别的东西分离开来;
- 经常需要修改的属性和方法可以被分离出来形成一个单独的类别,而那些主要留下的部分就不需要进行修改了,进而增强面向对象的分析与设计(OOAD)原则,即“代码应该易于扩展,而不应该经常修改”;
- 简化领域模型的表征。
抽象和封装的区别:
封装作为一种策略,被用作广义抽象的一部分。封装是与对象状态相关的——对象将自己的状态封装起来并对外界不可见,类外部的用户只能通过该类的方法来与其进行交互,但不能直接改变其状态。因此,类可以将与状态相关的实施细节通过抽象隔离开来。
抽象是一个更泛化的概念,可以通过子类来实现具体的功能。例如:在Java标准库中,List是“一串事物”的抽象,ArrayList和LinkedList是List的两个具体的类型,作用于抽象List的代码同样抽象地不指明具体所使用的List类型。
如果没有通过封装隐藏底层状态,也就不可能进行抽象处理。也就是说,如果一个类的内部状态全部都是公开的,内部功能无法被更改,该类也就无法进行抽象。
什么是抽象类和抽象方法?
在程序设计过程中,你希望基类只是其派生类的一个接口,也就是说,你不希望任何人能实例化该基类。你只是想隐式(可以实现多态性)地提出它,以便可以使用它的接口。那么你可以使用abstract关键字来定义一个抽象类。
为该抽象类设定一些限制,所有使用该抽象类的子类都必须实现其中的抽象方法,并提供多态性。
抽象类中可以既包括抽象方法和具体方法,如果一个方法是抽象方法,其所在的类必须被声明成抽象类。反之不然,如果一个类是抽象类,其中不一定包括抽象方法。
如果一个方法只提供了方法签名,但没有被具体实现,则这个方法是一个抽象方法,该方法的具体实现是在扩展该抽象类的子类中进行的。
抽象方法不能被实例化,其他类只能扩展它。
什么时候使用抽象类?
抽象类定义了一些默认的行为,而将具体的功能留给子类来实现。例如:List是一个接口,而抽象类AbstractList提供了List的默认方法,这些默认方法可以被子类ArrayList继承或重新定义。
什么是接口?
Interface关键字使得接口相比于抽象类更进了一步,接口中不能定义实现的方法。实现(使用关键字implements)接口的非抽象类必须实现该接口的所有方法。接口是面向对象(OO)中的一个非常有用和常用的概念,它将接口和具体实现分离开来,并保证数据安全性:
接口是抽象类的延伸,java了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,但是推荐继承和接口共同使用,因为这样既可以保证数据安全性又可以实现多重继承。
使用接口的优点
- 多重继承;
- 能对操作进行松耦合的抽象,可以分离实现任何功能, JDBC, JPA, JTA等等;
- 开发接口而不需要具体实现;
- 使用动态绑定的多态——揭示一个对象的编程接口,而无需展现其具体的实现细节;
- 抽象层:分离问题。
接口和抽象类之间的区别
- 接口是一种协议,要实现接口的类需要根据接口中定义地来实现接口,它只是一个提供了方法声明的空壳;
- 抽象类定义了一些通用方法,其子类可以定义新的具体或特殊的方法;
- 抽象类中的方法和参数可以被定义成任何可见性的,而接口中的所有方法必须由public可见性修饰符定义;
- 继承一个抽象类,子类需要实现其中的抽象方法,然而接口可以扩展另一个接口而无需实现其中的方法;
- 子类只能继承单个抽象类,而一个接口或类可以实现多个接口;
- 继承抽象类的子类可以以相同或更低的可见性实现父类中的抽象方法,而实现接口的类只能以与原抽象方法相同的可见性实现接口中的方法;
- 接口没有构造函数,抽象类有;
- 接口中的变量都是final型的,而抽象类中可以包含非final型变量;
- 接口中的成员默认是public类型的,但抽象类中的成员的访问类型可以是public,protected和默认类型。
合成
代码的重用性可以通过集成和合成来实现,但是用合成实现代码重用比继承居右更好的封装性,因为对后端代码的修改无需任何对仅依赖于前端类的代码的破坏。
合成石实现类之间“has-a”关系的设计技术,我们可以使用Java的继承或对象合成来实现代码重用。
合成表示的是对象之间的关系,以椅子chair为例,一把椅子chair有一个座位seat,一个靠背back,几条腿legs,词组“has a”表示一把椅子所包含,或所使用的其他实体,这种“has a”关系就是合成的基础。
优点:
- 控制可见性
- 运行时可以更换实现方法
- 松耦合,接口不依赖与具体实现。
合成与继承之间的区别?
No.
合成Composition (has a)
继承Inheritance (is a)
1
主张多态和代码重用
主张多态和代码重用
2
运行时已经获得对象
编译时动态获得对象
3
运行时可以替换实施
编译时可以更换实施
4
松耦合,子类的存在并不依赖与父类(特别是接口驱动的情形下)
紧耦合,子类严格依赖于父类
5
当House中有一个Bathroom时可以使用,不能说House是一种Bathroom
继承是单向的,例如:House是一个Building,但Building不是一个House
注:不要仅仅只为了代码重用而使用继承,如果类之间不存在“is a”关系,建议采用合成来实现代码重用。
对象关系中合成与聚合的区别:
聚合:聚合是一个类属于一个集合的关系。描述的是一种“部分与整体”的关系,“整体”不存在的情况下,“部分”是可以存在的,这是一种弱关系类型,没有循环依赖性。例如,订单和产品的关系。
合成:合成也是一个类隶属于一个集合的关系。描述的是一种只有“整体”存在,“部分”才能存在的“部分与整体”的关系。在这种关系下,如果“整体”被删除,则“部分”也就不复存在,体现的是一种强关系类型。例如,多边形和组成多边形的点,订单和订单明细。
参考:
- http://stackoverflow.com/
- http://en.wikipedia.org/
- Effective Java™