面向对象概述
面向对象
Java语言是一种面向对象的程序设计语言,而面向对象思想是一种程序设计思想,我们在面向对象思想的指引下,使用Java语言去设计、开发计算机程序。 这里的对象泛指现实中一切事物,每种事物都具备自己的属性和行为。面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。它区别于面向过程思想,强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。
面向过程
面向过程就是面向解决问题的过程进行编程,围绕着“自顶向下,逐步细化”的核心,分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用去实现。
区别与联系
面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。
面向对象是模型化的,你只需抽象出一个类,将事件的特征和行为封装起来,需要什么功能直接使用就可以了,不必去一步一步的实现,更不需知道其具体实现,只要会使用即可。
面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象。
面向对象四大特性
抽象
抽象是将一类对象的共同特征总结出来构造类的过程,包括数据(特征)抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
封装
Java 中封装分为广义封装和狭义封装,具体如下:
狭义封装
1. 权限封装:
Java中,可以使用访问修饰符对类、变量、方法和构造方法的访问权限进行限制或者说保护,本身就是java封装的体现。Java 支持 4 种不同的访问权限。
分类
private : 在同一类内可见。使用对象:变量、方法、内部类。 注意:不能修饰类(外部类)。
default :(即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法、内部类。 注意:不能修饰类(外部类)。基类的 protected 成员属性是包内可见的,并且对子类可见;若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。属性可见(子类,孙子类都可以),方法看来源于哪里,就是哪里所在的包以及子类(只有直系子类)。
public : 对所有类可见。使用对象:类、接口、变量、方法。
2. 数据封装
数据封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性一对 getXxx 方法 、 setXxx 方法,数据封装把一个对象的行为细节规范化,提供一些可以被外界调用的行为一个公共方法。隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。
3. 接口封装
通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口或者抽象类。面向对象
的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。简单的来说,接口封装就是暴露功能,屏蔽实现细节的封装。
4. 包封装
包封装是Java中为了解决与其他冲突包冲突的方案。
java包一般分为3-6层。
第一层:包前缀( indi → 个体项目, pers →个人项目,priv→私有项目,team→团队项目,com→公司项目)
第二层:甲方公司域名倒写 如(com.baidu)
第三层:项目名称 如 (CRM)
第四层:模块信息(模块信息可以有多层) 如(111.222.333 这是拥有三层模块信息的项目)
第五层:功能顶层包 如(dao vo service action 等)
第六层:实现类层 impl
有些功能顶层包是没有实现类层的。
广义封装
模块封装
模块就是代码和数据的封装体。模块的代码被组织成多个包,每个包中包含Java类和接口;模块的数据则包括资源文件和其他静态信息。JDK 9中模块系统指出,将类型和资源封装在模块中,并仅导出其他模块要访问其公共类型的软件包。 模块封装是按照功能划分界限,暴露类和接口信息的。
remote封装(如EJB 微服)
Java RMI 指的是远程方法调用 (Remote Method Invocation)。它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法。可以用此方法调用的任何对象必须实现该远程接口。Java RMI不是什么新技术(在Java1.1的时代都有了),但却是是非常重要的底层技术。大名鼎鼎的EJB都是建立在rmi基础之上的,现在还有一些开源的远程调用组件,其底层技术也是rmi。
微服务架构是一种架构风格和架构思想,它倡导我们在传统软件应用架构的基础上,将系统业务按照功能拆分为更加细粒度的服务,所拆分的每一个服务都是一个独立的应用,这些应用对外提供公共的API,可以独立承担对外服务的职责,通过此种思想方式所开发的软件服务实体就是“微服务”,而围绕着微服务思想构建的一系列结构(包括开发、测试、部署等),我们可以将它称之为“微服务架构”。
以上都是remote封装的实例。
继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
好处:
1. 提高代码的复用性。
2. 类与类之间产生了关系,是多态的前提。
特点:
1. Java只支持单继承,不支持多继承,保证同类的功能唯一性。
2. Java支持多层继承(继承体系)。
注意:
1. 子类拥有父类非 private、非同包的default修饰的的属性和方法。
2. 子类不能继承父类的构造方法 和非同包父类protected的实例方法(父类继承其父类的方法)。
3. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
4. 子类可以用自己的方式实现父类的方法。
多态
多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。
Java实现多态有三个必要条件:继承(实现)、重写、向上转型(父类对象引用指向子类对象,接口引用指向实现类对象)。
继承、实现:在多态中必须存在有继承关系的子类和父类。继承是多个子类对同一方法的重写;实现是实现接口并覆盖接口中同一方法
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。父类引用指向子类对象,使用上还是父类,但因为其本质上是子类的实例对象,当我们需要访问其属性和方法时,虽然可以通过父类引用指向子类对象去访问子类继承父类的属性、方法以及重写的方法,但是子类独有的属性和方法就不能访问,此时我们需要进行强制类型转换,也就是向下转型。接口引用指向实现类对象亦是同理;父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。向上转型使得同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为,从而提高了程序的拓展性。
只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。
什么是多态机制?Java语言是如何实现多态的?
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
多态分为编译时多态和运行时多态。方法重载(overload)实现的是编译时的多态性(也称为前绑定),它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而方法重写(override)实现的是运行时的多态性(也称为后绑定);一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。运行时的多态,就是动态绑定机制,是面向对象最精髓的东西。
面向对象编程的六大原则
1. 开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2. 里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3. 依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4. 接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5. 迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6. 合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
类与接口
类的定义
程序员将某一类具有相同属性和行为的事物抽象描述出来叫做类。对象是类的实例化。java中万物皆对象,类是引导对象干活的抽象化。
抽象类和接口
抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类 Airplane,将鸟设计为一个类 Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。
抽象类是半虚半实的类,因为抽象类可以有抽象方法(由abstract修饰的方法,只做方法的声明,不做方法的实现),也可以有普通方法,此外子类必须重写抽象父类的抽象方法,除非子类也是抽象类,并且抽象类不可以直接实例化。接口是全虚的“类”,因为其方法一般认为默认公共的抽象方法,属性默认为公共的静态的常量。此外子类必须实现父接口中的所有抽象方法,除非子类也是接口或者抽象类(接口和接口之间用继承 extends),接口不可以直接实例化。
抽象类与接口的区别如下:
接口和父类
关于接口和父类的关系,因为父类一般都是抽象类,我上面已经阐述过抽象类和接口的区别了,下面我再解释一下父类和接口其他的区别。
java支持单根继承,所以一个类只能继承一个父类;而接口可以被多实现。父类与子类的关系是“is-a”关系,表面上是一种继承,实际上是一种封装,就是子类去复用父类的属性和方法。而接口与实现类 是“has-a ”的关系。是一种实现类的扩展。
面向修改是封闭的,面向扩展是开放的。如果要扩展一个已有类的功能, 我们通常不会去修改该类的源码,而是新建一个类,继承该类,并实现扩展接口。
依赖、关联、聚合与组合
依赖
依赖(Dependency)关系是类与类之间的联接。依赖关系表示一个类依赖于另一个类的定义。例如,一个人(Person)可以买车(car)和房子(House),Person类依赖于Car类和House类的定义,因为Person类引用了Car和House。与关联不同的是,Person类里并没有Car和House类型的属性,Car和House的实例是以参量的方式传入到buy()方法中去的。一般而言,依赖关系在Java语言中体现为局域变量、方法的形参,或者对静态方法的调用。
依赖关系比较好区分,它是耦合度最弱的一种,在java中表现为局域变量、方法的形参,或者对静态方法的调用,如下面的例子:Driver类依赖于Car类,Driver的三个方法分别演示了依赖关系的三种不同形式。
class Car {
public static void run(){
System.out.println("汽车在奔跑");
}
}
class Driver {
//使用形参方式发生依赖关系
public void drive1(Car car){
car.run();
}
//使用局部变量发生依赖关系
public void drive2(){
Car car = new Car();
car.run();
}
//使用静态变量发生依赖关系
public void drive3(){
Car.run();
}
}
关联
关联(Association)关系是类与类之间的联接,它使一个类知道另一个类的属性和方法。关联可以是双向的,也可以是单向的。在Java语言中,关联关系一般使用成员变量来实现。
关联关系在java中一般使用成员变量来实现,有时也用方法形参的形式实现。依然使用Driver和Car的例子,使用方法参数形式可以表示依赖关系,也可以表示关联关系,毕竟我们无法在程序中太准确的表达语义。在本例中,使用成员变量表达这个意思:车是我自己的车,我“拥有”这个车。使用方法参数表达:车不是我的,我只是个司机,别人给我什么车我就开什么车,我使用这个车。
class Driver {
//使用成员变量形式实现关联
Car mycar;
public void drive(){
mycar.run();
}
...
//使用方法参数形式实现关联
public void drive(Car car){
car.run();
}
}
组合
类的组合是选用已有类的对象作为类的属性,表达的是有一个的包含关系,即程序中的部件由对象组装而成它的成员。
组合是一种受限制的聚合形式,代表了part-of关系;其中的两个实体(或者类)是高度依赖于彼此的。就好比如:人类和心脏,人类需要心脏来生存,心脏也需要人体才能生存。
换句话说,当类(实体)彼此依赖并且它们的寿命相同时(如果一个人死了,那么另一个也是死去),那么它就是一个组合。例如:如果没有人类,心脏就没有意义了。
假如赋予如下语义:车是司机的必须有的财产,要想成为一个司机必须要先有辆车,车要是没了,司机也不想活了。而且司机要是不干司机了,这个车就砸了,别人谁也别想用。那就表示组合关系了。一般来说,为了表示组合关系,常常会使用构造方法来达到初始化的目的,例如上例中,加上一个以Car为参数的构造方法。
public Driver(Car car){
mycar = car;
}
聚合
聚合是一种特殊的关联形式,代表了has-a关系;它是类(或实体)之间的一种单向关系。例如:钱包和钱,钱包会有钱,钱里面不会包含钱包;这是一种天生的单向关系。
在聚合的关系中,两种类(或实体)是可以单独存在的,不会相互影响;也就是说:一个类(或实体)的是否存在不会影响与之聚合的其他类的存在与否。
class Driver {
//使用成员变量形式实现聚合关系
Car mycar;
public void drive(){
mycar.run();
}
}
假如给上面代码赋予如下语义:车是一辆私家车,是司机财产的一部分。则相同的代码即表示聚合关系了。聚合关系一般使用setter方法给成员变量赋值。
聚合和组合的区别
聚合代表了has-a关系,一种单向关系;聚合中的两种类(或实体)是可以单独存在的,不会相互影响。组合代表了part-of关系,组合中的两个实体(或者类)是高度依赖于彼此的,它们之间会相互影响。
变量与方法
成员变量与局部变量的区别有哪些
变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中值可以变的储存空间的表示。
成员变量:方法外部,类内部定义的变量
局部变量:类的方法中的变量。
成员变量和局部变量的区别
作用域
成员变量:即静态特征 、成员变量、全局变量;针对整个类有效。
局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)
存储位置
成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。
局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。
生命周期
成员变量:随着对象的创建而存在,随着对象的消失而消失
局部变量:当方法调用完,或者语句结束后,就自动释放。
初始值
成员变量:有默认初始值。
局部变量:没有默认初始值,使用前必须赋值。
使用原则
在使用变量时需要遵循的原则为:就近原则
首先在局部范围找,有就使用;接着在成员位置找。
静态变量和实例变量区别
静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。
实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。
静态变量与普通变量区别
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
还有一点就是static成员变量的初始化顺序按照定义的顺序进行初始化。
方法
方法是对行为的隐藏实现,是动态特征,是函数。
一个返回非void类型返回值的方法称为函数;一个返回void类型返回值的方法叫做过程。
一个源文件中只能有一个public类(和类名相同,且main方法必须在其中),一个源文件可以有多 个非public类。
构造方法
名字与类名相同;没有返回值,但不能用void声明构造函数;生成类的对象时自动执行,无需调用。
子类的所有构造方法内部, 第一行会(隐式)自动先调用父类的无参构造函数super(),除非自定义super(参数),否则父类的无参构造必须写,要不然子类的无参构造无法使用,因为父类的无参构造不存在。
静态方法和实例方法有何不同?
静态方法和实例方法的区别主要体现在两个方面:
在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
在一个静态方法内调用一个非静态成员为什么是非法的?
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
构造方法<普通代码块(对象声明时,构造方法前就可以执行)<静态代码块(与类相关)
三者出现顺序如下:
父类静态代码块
子类静态代码块
父类普通代码块
父类构造方法
子类普通代码块
子类构造方法
内部类
将一个类定义在另一个类里面或者一个方法里面。内部类本身就是类的一个属性,与其他属性定义方式一致。
成员内部类
成员内部类是最普通的内部类,成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。外部类访问内部类的时候必须创建内部类的对象。
public class Outer {
private static int radius = 1;
private int count =2;
class Inner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
System.out.println("visit outer variable:" + count);
}
}
}
创建成员内部类对象的一般形式为: 外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名()。
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内,并且定义在静态方法中的局部类只能访问外部类的静态变量和方法,其他和成员内部类一致。
public class Outer {
private int out_a = 1;
private static int STATIC_b = 2;
public void testFunctionClass(){
int inner_c =3;
class Inner {
private void fun(){
System.out.println(out_a);
System.out.println(STATIC_b);
System.out.println(inner_c);
}
}
Inner inner = new Inner();
inner.fun();
}
public static void testStaticFunctionClass(){
int d =3;
class Inner {
private void fun(){
// System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
System.out.println(STATIC_b);
System.out.println(d);
}
}
Inner inner = new Inner();
inner.fun();
}
}
编译器会默认为成员内部类添加了一个指向外部类对象的引用。
匿名内部类
匿名内部类是唯一一种没有构造器的类,匿名内部类也是不能有访问修饰符和 static 修饰符的,匿名内部类可以直接实例化接口和抽象类,当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。匿名内部类可以无条件访问外部类的所有成员属性和成员方法,但外部类不能方法匿名内部类的属性和方法。
public class Outer {
private void test(final int i) {
new Service() {
public void method() {
for (int j = 0; j < i; j++) {
System.out.println("匿名内部类" );
}
}
}.method();
}
}
//匿名内部类必须继承或实现一个已有的接口
interface Service{
void method();
}
局部内部类和匿名内部类只能访问局部final变量。
静态内部类
定义在类内部的静态类,就是静态内部类。
public class Outer {
private static int radius = 1;
static class StaticInner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
}
}
}
创建静态内部类对象的一般形式为: 外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()
局部内部类和匿名内部类访问局部变量的时候,变量必须要加上final
为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
内部类的优点
1. 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
2. 内部类不为同一包的其他类所见,具有很好的封装性;
3. 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
4. 匿名内部类可以很方便的定义回调。
内部类有哪些应用场景
1. 一些多算法场合
2. 解决一些非面向对象的语句块。
3. 适当使用内部类,使得代码更加灵活和富有扩展性。
4. 当某个类除了它的外部类,不再被其他的类使用时。