首先,Java中,除了8种基本数据类型。其他皆为对象。
Java的8种基本数据类型:byte short int long float double char boolean. 基本数据类型的值,是直接存储在线程的方法栈中的而对象的值存储在堆(Heap)中,在方法栈的栈帧里,只存了对象的地址引用PS:详见JVM内存模型
面向对象主要有
三个基本特征:封装,继承,多态。
五个基本原则: SOLID原则,即:
- 单一职责原则(SRP)
- 开放封闭原则(OCP)
- 里氏替换原则(LSP)
- 接口隔离原则(ISP)
- 依赖倒置原则(DIP)
三个基本特征
基本特征的体现
封装
主要概念是指:隐藏对象的属性和实现细节,仅对外暴露公共的访问方式。封装是面向对象最基本的特征之一,是类和对象概念的主要特性。
良好的封装所具有的优点:
1,隐藏信息和细节,提高安全性
2,良好的封装可以减少耦合性,提高复用性
3,对类内部结构可以随意修改,只要保证公有接口始终返回正确的结果即可。
在Java中的体现:
public default protected private
等设置访问权限的关键字
例如,类的属性私有化
private
修饰,提供公有的
setter getter
方法对值进行获取和修改。
同时,还可以在
setter getter
函数里进行数据校验和对返回值做限制。这就是提高安全性的体现。
继承
继承是一种联结类的层次模型,Java允许并且鼓励代码的重用,继承提供了一种明确表示共性的方法,来使代码可以进行复用。
继承的出现,让类与类之间产生了关联,提供了多态的前提条件。
讲到继承,就要提到三个东西:构造器,protected关键字,向上转型:
1,构造器:我们已经知道,父类中private修饰的属性和方法子类是无法继承的,而还有一个无法继承的就是 构造器。构造器只能调用而不能被继承。
类在实例化的时候会调用自身构造器,而java编译器会默认在子类构造器的第一行用
super()
先调用父类的无参构造器。如果父类没有无参构造器,则子类在实例化的时候会报错。这个时候就需要显示的在子类构造器第一行(必须在第一行)指定构造器
super(args)
来指定调用父类哪个构造器。
2,protected关键字:
proctected
关键字修饰的属性和方法,可以隔绝外部其他类的访问,却可以让子类有权限访问。
但是最好还是把属性都以
private
修饰,父类中希望子类可以访问到,又不希望公有的方法,可以用
protected
修饰
3,向上转型:最典型的还是
List list = new ArrayList()
将子类的实例声明为父类的类型,提高了通用性,也隐藏了子类的具体实现。因为这是一个由专用向通用的转换,所以总是安全的。唯一缺陷就是专有属性和方法的丢失。
但是继承同时又存在缺陷:
1),继承是一种强耦合关系,子类会由于父类的改变而改变
2),继承破坏了封装的特性,对于父类而言,它的实现和细节对于子类来说都是透明的
所以到底要不要继承:《Thinking in java》提供了一个解决方案:问一问自己是否需要从子类向父类进行向上转型。如果必须向上转型,则继承是必要的,但是如果不需要,则应当好好考虑自己是否需要继承
使用继承的时候,一定要注意,两个类之间是从属关系,父子关系 的。子类应该是基于父类原有的特性,派生出来,并在无需编父类代码的情况下,提供扩展的功能和方法。
不要为了使用某个类的其中一个特性,而去继承该类。
多态
多态,指的就是:
程序中定义的引用变量所指向的具体类型,和通过该引用变量发出的方法调用,在编程期间并不确定,而是在程序运行期间才确定。
如何理解多态:
因为在程序运行时才确定到具体的类,所以不用修改源码,就能让引用变量绑定到不同的类型实现上,从而导致引用调用的方法发生改变,即不修改具体代码,就可以让程序在运行时改变绑定的具体代码,表现不同的运行状态。这就是多态性。
多态的好处:
多态允许不同类对象,对同一个消息作出响应。即同一个消息可以根据发送对象的不同而采用不同的行为方式(发送消息就是函数调用)。
主要好处如下:
1)可替换性。多态对已存在的代码具有可替换性。
2)可扩充性。新增加的子类不影响已经存在的类结构。
3)接口性。多态是父类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的。
4)灵活性。
5)简化性。
Java中多态的体现:
- 重写(覆盖)
- 动态链接(动态调用)
- 重载
多态的三个必要条件:
- 继承
- 重写
- 向上转型。
多态的作用:
我们知道,封装可以隐藏细节,使得代码模块化,继承可以扩展已存在的模块化代码(父类),实现代码的复用。
而多态,则是实现了接口的复用,多态的作用,就是在类的实现和派生的时候,保证使用基类下面,任意一个子类实例的属性和方法,都可以正确调用。
虚拟机如何实现多态:
在JVM中,是使用了动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法.
重载严格意义上并不属于多态,重载的具体实现是:编译器根据不同的参数表,对同名函数的名称做修饰,然后这些同名函数就变成了不同的函数。对重载函数的调用,在编译期间就已经确定了,是静态的(注意!是静态的),因此,重载和多态无关。
真正和多态相关的是重写,当子类重写了父类中的函数后,父类的 指针,根据赋值给它不同的子类对象指针,动态的调用属于子类的该函数,这样在编译期间是无法确定的,只有在运行期间,才会把动态链接转变为直接引用(称为动态链接,详见JVM内存模型-栈帧)
五个基本原则
- 单一职责原则(Single-Resposibility Principle SRP)
对一个而言,应该仅有一个引起它变化的原因
本原则是我们非常熟悉的\”高内聚原则\”的引申。同时,本原则还揭示了内聚性和耦合性:如果一个类承担的职责过多,那么这些职责就会相互依赖,一个职责的变化可能会影响另一个职责的履行。其实OOP的实质,就是合理进行类的职责分配
- 开—闭原则(Open-Closed principle OCP)
软件应该是可以扩展的,但是不可以修改
也就是对扩展开放,对修改封闭。当变化来临时,不需要(或者不允许)修改原来的代码,只需要在原有的基础上扩展(同时原有的代码也要求支持扩展),那么这个软件设计就是满足开闭原则的。
此原则在Java中最典型的体现就是:抽象类 抽象基类 和 接口。通过抽象类,把一些不可变的操作封装起来,而提供抽象接口供子类实现,来实现各自变化的需求。
这个原则应用在类的设计中,要满足该原则就要充分的考虑到接口封装,抽象机制和多态。
- 里氏替换原则(Liskov-Substituion Principle LSP)
子类型必须能够替换掉它们的基类型
本原则和开闭原则关系密切,正是基于子类的可替换性,才使得基类可以无需修改,只要子类继承就可以实现扩展特性。这是保证继承复用的基础
在Java中的典型体现就是 基于接口的框架设计,例如JDBC,集合类。
JDBC只提供了基本的接口,返回的对象类型也是接口,这样就在选择返回对象的时候,有了更大的灵活性:只要是继承了返回接口类型的子类实例,都可以作为结果返回。而服务提供者无需暴露子类的实现,调用者也无需关心子类的实现。而提供者在对实现进行优化升级时,对调用者也是不可见,同时也没有影响的。
集合类提供了一些基本集合的接口,例如 List Map Set。我们可以在声明时用这些接口类型作为声明对象,无需关心具体的实现类型是如何操作的,集合框架可以很好的把实现类型和代码隐藏起来,对调用者透明。例如List可以用来接收ArrayList,也可以用来接收LinkedList。可以用来接收任何实现了List接口的类的实例。
以上两点,充分的利用了里氏替换原则的特性,实现了封装的基本特征。
- 接口隔离原则(Interface-Segregation Principle ISP)
多个专用接口优于一个单一的通用接口
本原则是单一职责原则用于接口设计的自然结果。基本思想就是,不要让客户端依赖他们不需要的接口。
一个接口应该保证,实现该接口的实例对象可以只呈现一个单一的角色。这样当接口发生改变时,对其他客户端造成的影响会更小。
把多个不同职责的功能分到不同的接口中去,提高代码的灵活性和稳定性,降低耦合性。
- 依赖倒置原则(Dependecy-Inversion Principle DIP)
抽象不应该依赖于细节,细节应该依赖于抽象
具体来说就是,软件设计中,高层不依赖于低层,两者都依赖于抽象。抽象应该依赖于抽象,而不依赖于具体实现细节。即:对接口编程
在Java中的体现还是,接口 和 抽象类
拿最简单的Spring IOC来说,当我们注入的时候,接收参数类型应该是接口,而注入的对象,可以是实现了接口的各种子类,当我们想改变接口表现的特性的时候,无需修改代码,只要修改注入的实现类对象就可以。
RPC框架(例如Dubbo)开放的接口也同样基于这个原则。
上面说的JDBC,集合类框架 都基于这个原则来提供实现。提供接口对象,而把实现类隐藏在内部。