面向对象是什么?
在前面的章节中,我们设计程序总是趋向于设计过程来解决问题,更加准确的来说,通过"算法+数据结构"来设计程序。如果是求解一个简单的问题,这样做是完全没有问题的。但是现实生活中,人们的需求总是变化莫测,所以需要一种复用性更强的方式来设计程序,这个时候,OOP(Object-oriented programming)
则以面向对象方式闪亮登场了。
OOP其实不是Java语言的专属,在其他语言上,你也可以看到面向对象的影子,例如:C#
、TypeScript
、C++
等都是经典的面向对象编程语言。
在面向对象的世界里面,程序都是一个个对象组成的,通过对象间的消息传递来完成作业。
对象到底是什么?
这个一个很大的课题,这里简单讲讲一些基本的概念.
- 描述一个对象,首先要有一个建模的模板,在Java中,这个模板称为类;对象大多数是对现实生活的抽象,比如与时间相关的操作,在面向对象里面就对应一个类:Date
- 类是对象的模板,对象是类的实例,这里的关系就像-人都拥有名字和身份证,但是每个人都拥有自己的名字和身份证,那么"人"和"张三"之间的关系就是类与对象的关系.
- 对象内部封装了数据,对外提供方法进行通信.数据的表现称为状态,方法则是对象的行为,通过行为可以改变对象的状态
面向对象的三大基本原则
封装
封装的概念比较容易理解,先以一个生活的例子作为举例:
一台电视机,内部可能有很多复杂的设计结构和硬件设备,那么直接让用户去操作这些内部的零件可能会造成很大的困扰,于是厂商就提供了一系列的按钮、遥控器来让用户更好地操作电视机。
这其实就是一个封装的过程,内部细节对用户屏蔽,用户只需要知道,通过这个"按钮"我可以达成怎样的效果即可。这在程序中的体现则是:调用一个工具类的方法,我需要输入什么,它会给我输出什么.
继承
继承是为了更好地扩展与复用,通过继承可以直接得所扩展的类的所有能力(通常称为父类),如此一来可以避免重复性的工作;同时,继承也意味这这组对象拥有部分相同的特征,那么它们的"类型"也是一致的.
例如,在Java中,你会常常看到这样的写法.
User person = new Person();
Java只支持单继承.
多态
多态的方式可以有很多种不同的呈现:
- 面对同一个消息,不同的对象会产生不同的不同的行为,这在Java中体现为重写-Override.
- 相同名字的方法,通过不同的参数进行区分,调用时支持不同的方法匹配相应的方法.这便是重载的体现.
这里引用《Java编程思想》中的一句话:发送消息给对象时,如果程序不知道接收的具体类型是什么,但最终执行是正确的,这就是对象的“多态性”(Polymorphism)。面向对象的程序设计语言是通过“动态绑定”的方式来实现对象的多态性的。
类之间的关系
- uses-a: 类A中的一个方法操纵了另一个类的对象,那么类A的这个功能便依赖了B.程序中应该尽量减少类与类之间的依赖关系,降低类与类之间的耦合度.
- has-a: 类A中持有类B作为自己的成员,例如一个User类持有了一组角色.这便是聚合关系.
public class User{
/**
* 角色列表
*/
private List<Role> roles;
}
- is-a: 继承关系,例如:类A继承自类B.
什么是Java对象?
谈起Java中的对象,不得不提起类.要生产一个对象,首先得描述一个模板,就像工厂里面,我们想造一个手机壳,那么我们首先得设计一个模具,再根据这个模具加工出手机壳。
类与对象之间的关系也是如此:
类中可以描述一个对象的特征:例如field(成员变量)
、constructor构造器
、method方法
、class-类型
.
程序中可以通过new
关键字来触发构造器,进而获取一个对象,通过调用方法,可以改变对象的状态.
new与构造器
使用new
关键字来生产一个对象,在new
的过程中,会自动触发构造器方法.
其中,对于构造方法的概述为:
- 构造器方法与类名一致.
- 一个类可以声明多个构造器方法.
- 构造器方法支持多态性.
- 构造器方法不需要声明返回值.
- 搭配修饰符可以控制构造器方法的使用范围.
口说无凭,我们通过代码进行讲解:
- 定义一个用户类
package com.tea.modules.model;
public class User
/**
* 用户名称
*/
private String userName;
/**
* 密码
*/
private String password;
/**
* 这是一个构造器方法
*/
public User(){
System.out.println("一个User对象被创建出来了");
}
}
- 生产对象
User user = new User();
- Result
我们这里验证了一个问题,使用new去生产对象实例的时候,会自动触发构造器方法,这个过程也称为初始化.
- 如果直接声明对象,那么你仅仅只得到一个指针
错误示范
User user;
System.out.println(user.toString());
无参构造器
定义Java类的时候,如果你不需要自定义创建对象的过程,你完全可以不写构造器方法,编译器会为你自动创建一个无参的构造器.
如果显式声明了构造器,那么编译器便不会为你创建构造器了.
static
被static修饰的方法代表静态方法,它不归属于对象,而是类.
在类中定义static初始化方法,往往只会执行一次,因为加载出类(对象的模板)之后,就可以直接根据模板来生产对象了,也就是说,要得到一个User对象,必须先加载出一个User.class.
static方法无法通过this
关键字进行调用,需要使用"类名.方法"的方式进行调用.
// 静态方法
Math.pow(1, 2);
new的时候,底层做了什么事情?
为了更好的了解这个问题,我们将User类的age
属性声明为int(当然实际开发中不提倡这样做),这里为了重写toString方法,我使用了lombok
进行自动生成.
- User.class
package com.tea.modules.model;
import lombok.Data;
@Data
public class User {
private String userName;
private String password;
private int age;
static {
System.out.println("我是一个静态方法,我是类加载的时候就会发生的事件");
}
{
System.out.println("我是一个初始化时执行的方法,我比构造器的优先级要高");
}
public User(){
System.out.println("一个User对象被创建出来了");
}
}
- Test
package com.tea.modules.java8.oop;
import com.tea.modules.model.User;
/**
* 理解面向对象思想-对象的构造器与初始化
* @author jaymin
* @since 2021/4/10 16:15
*/
public class OOPDemo {
public static void main(String[] args) {
User user = new User();
System.out.println(user.toString());
}
}
- Result
-
查看编译后的指令
为了解决这个问题,可以使用javap -c OOPDemo.class
查看JVM编译后的指令集.
- 关于new的大致过程
- 我们知道,要生产对象就需要找到类,我们定义的java类最终会编译为一个
.class
文件,JVM首先要将这个文件加载到内存中,得到一个User.class
对象. - 执行被
static
修饰的代码块. - 执行
new
关键字的相关操作,为当前对象分配内存空间. - 对对象中的所有字段设置为零值(引用类型置为null,基础类型置为默认值,比如int的默认值为0).
- 执行代码块,即在字段定义处的初始化动作.
- 执行构造器(constructor)方法.
- 你获得了一个对象的引用.
- 图示
网友评论