面向对象程序设计概述
面向过程:先确定如何操作数据,再决定数据的结构。适用于小规模问题
面向对象OOP:先决定数据的结构,在考虑操作数据的算法。适用于大规模问题
类
- 类是构造对象的模板或蓝图
- 封装是处理对象的一个重要概念,就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式
实例字段:对象中的数据数据
方法:操作数据的过程
对象的状态:特定对象有一组特定的实例字段值,这些值的集合就是这个对象的当前状态。
- OOP原则:
- 封装:绝对不能让类中的方法直接访问其他类的实例字段
- 扩展:可以通过扩展其他类扩展来构建新类
对象
- 对象的状态改变必须通过调用方法实现(如果不经过方法改变对象状态,说明破坏了封装性)
- 对象的状态不能完全描述一个对象,每个对象有一个唯一标识。
- 作为同一个类的实例,每个对象的标识总是不同的,状态往往也存在着差异
类之间的关系
- 依赖(uses- a):一个类的方法使用另一个类的对象
------->
- 聚合(has-a):类A的对象包含类B的对象
◇——
- 继承(is-a):一个更特殊的类和一个更一般的类之间的关系
——▷
使用预定义类
对象与对象变量
- 对象变量并没有时间包含一个对象,它只是引用一个对象
- 在Java中,任何对象变量的值都是对存储在另一个地方某个对象的引用
- new操作符的返回值也是一个引用
- Java对象都存储在堆中,当一个对象包含另一个对象变量时,它只是包含着另一个堆对象的指针
- Java中如果使用一个没有初始化的指针,运行时系统将会产生一个运行时错误,同时不必担心内存管理问题,垃圾回收器会处理相关的事宜
- Java中必须使用clone方法获得对象的完整副本
Java中的LocalDate类
- Java类库包含两种保存时间的类:
- Date类:表示时间点
- LocalDate类:日历表示法表示日期
- 一般通过这三种静态工厂方法构造一个LocalDate
LocalDate.of(2020,4,20);
LocalDate.now();
LocalDate.parse("2021-04-20");
实用方法:
isLeapYear()
此LocalDate是否闰年
minusWeeks() minusDays()
减去星期/天之后的表示
plusWeeks() plusDays()
加上星期/天之后的表示
getMonthValue()
获得月的值
getDayOfMonth()
获得该LocalDate的day
getDayOfWeek() getMonth()
返回枚举值,获得该天的枚举值(1-7),获得该月的枚举值(1~12)
- 访问器方法:只访问对象而不修改对象的方法(如plusDays返回一个新的对象,原对象还在)
- 更改器方法:调用方法之后修改对象的内容
在C++中,带有const后缀的方法是访问器方法,没有的是更改器方法。而在Java中,这两种方法没有明面上的语法区别
用户自定义类
- 主力类:通常没有main方法,却有自己的实例字段和实例方法(例如:自定义User类)
- 一个源文件中只能有一个公共类,但是可以有任意数量的非公共类
多个源文件的使用
- 将两个类存放在一个单独的类文件中,例如:Employee类存放在文件EmployeeTest.java中,
然后键入以下指令javac EmployeeTest.java 此时并没有显示的编译Employee.java,不过编译器会发发现需要使用这两个类时,会自动搜索Employee.java,并编译。
如果编译器发现Employee.java较已有的Employee.java已有更新,那么Java编译器会自动的编译这个文件
构造器方法
- 构造器与类名同名,构造器总是结合new运算符来调用,不能对一个已存在的对象调用构造器来达到重新设置实例字段的目的
用var声明局部变量(在Java10中)
- 在Java10中,如果可以从变量的初始值推导出他们的类型,那么可以使用var关键字声明局部变量,而无需指定类型
- var关键字只能用于方法中的局部变量,参数和字段的类型必须声明。
使用null引用
- 定义一个类时,最好清楚哪些字段可能为null,对于初始化时字段可能null,一般有两种解决方案
宽容型:将null参数转换成一个非null值
if(n == null) name = "unknown"; else name = n;
Java9中有一个方法name = Object.requireNonNull(n,"unknown");
达到同样的效果
严格型:直接拒绝null参数
Object.requireNonNull(n, "the name cannot be null");
隐式参数和显式参数
- 隐式参数:出现在方法名前的对象的参数
- 显示参数:位于方法名后面括号里的数值
每个方法中,用this关键字指示隐式参数,这样可以将 实例字段 和 局部变量 明显的区分开来
封装的优点
- 注意不要编写返回可变对象引用的访问器方法(getter方法),例如返回一个Date类
因为Date类中有setDate()方法,所以Date对象可变,也就意味着破坏了封装性
如果需要返回一个可变对象的引用,首先应该对他进行clone,然后再返回克隆的对象副本
私有方法
- 只要方法是私有的,类设计者就可以确信它不会在别处使用,所以可以删去,而如果是公共的,那么可能会因为其他其他代码依赖这个方法
final字段
- final修饰符对于 基本类型 或者不可变类(类中所有方法都不会改变其对象,如String类)的字段尤其有用。
- 对于可变的类,使用final会造成混乱,final关键字只表示存储在该 变量 中的 对象引用 不会再指向另一个不同的对象,而这个对象还是可以更改
静态字段与静态方法
static的含义
静态字段
- 静态字段属于类(即使没有创建该类的对象,静态字段也存在),每个类只有一个这样的字段。创建的多个对象共享这同一个静态字段
非静态字段:每个对象都有自己的一个副本
静态常量
- 静态常量,设置为公共没问题,因为不允许再将它赋值为另一个值
(System类中的setOut()方法可以将System.out设置为不同流,这是因为setOut方法是一个原生方法,不是在Java语言中实现的,因此可以绕开Java的访问控制机制。)
静态方法
- 静态方法是一个没有this参数的方法(可直接通过类名调用)
- 可以使用对象来调用静态方法,不过很容易造成混乱,因为方法计算的结果与该对象无关。建议使用类名直接调用静态方法
- 两种情况下使用静态方法:
- 方法不需要访问对象状态,如Math.pow
- 方法只需要访问类的静态字段
工厂方法
- 静态方法还有一种,类似LocalDate和NumberFormat 类使用静态工厂方法来构造对象。
使用这种方式构造对象的原因有两个:
- 无法命名构造器:构造器名字必须与类名相同,但是有事希望有两个不同的名字,分别得到货币实例和百分比实例
- 使用构造器时,无法改变所构造对象的类型。静态工厂方法可以,如工厂方法实际将返回DecimalFormat类对象,这是NumberFormat的一个子类
方法参数
- Java中,方法参数
- 方法不能修改基本数据类型的参数(数值型或布尔型)
- 方法可以改变对象参数的状态
- 方法不能让一个对象参数引用一个新的对象
对象构造
重载
- 多个方法有相同的名字,不同的参数,便出现了重载。
编译器必须挑选出具体调用那一个方法,它用各个方法首部中的 参数类型 和 值类型来匹配
这个过程称为 重载解析
- 方法签名:方法名 + 参数类型 注意返回类型不是方法签名的一部分
默认字段初始化
- 如果在构造器中没有显式的为字段设置初始值,会自动赋为初始值(0,false,null)
- 方法中的局部变量必须要明确的初始化,否则会报错
- 类中的字段,没有初始化会默认初始化
无参数的构造器
- 无参构造默认将所有字段设置为默认值
- 当且仅当类没有任何其他构造器的时候,才会得到一个默认的无参构造器
显示字段初始化
- 可以在类定义时,为每个实例字段设置一个有意义的初始值(如果一个类的所有构造器希望把某个特定的实例字段设置为同一个值,这个就有用)
- 初始值也可以不是常量值,利用方法调用初始化一个字段,使用类中的静态变量,每次调用改变这个静态变量的值即可
参数名
- 参数变量会遮蔽同名的实例字段,但是给实例字段加上this就可以避免这种情况
调用另一个构造器
- 如果构造器的第一个语句形如
this(...)
,这个构造器将会调用另一个构造器
采用这种方式使用this关键字非常有用,对公共的构造器代码只需要编写一次即可
C++中一个构造器不能调用另一个构造器
初始化块
- 一个类的生命中,可以包含任意多个代码块,只要构造这个类的对象,这些块就会被执行
(这种机制不常见,通常使用构造器初始化对象。同时,为了避免混淆,初始化块放在字段定义之后,避免重复定义带来的麻烦)
- 而静态初始化在类的第一次加载的时候,会进行初始化
包
包名通常是域名逆序来写
类的导入
- 一个类可以使用所属包中的所有类,以及其他包中的公共类
- 两种方法访问另一个包中的公共类:
- 完全限定名:包名之后跟着类名
java.time.LocalDate today = java.time.LocalDate.now();
- import导入:import应该位于源文件的顶部,package语句的后面
静态导入
- 使用import语句导入 静态方法 和 静态字段,而不只是 类
在包中增加类
- 编译器在编译源文件时不检查目录结构,忽略package语句,可以进行编译(如果它不依赖其他包)
但是,虚拟机运行时如果包与目录不匹配,就会找不到类
Jar文件
jar文件是压缩的,它使用了我们熟悉的zip压缩格式
清单文件
- 除了类文件,图像的其他资源外,每个JAR文件还包含一个清单文件(manifest) ,用于描述文档的特殊特性
可执行JAR文件
- jar命令中的e指定程序的入口点,指定执行jar文件时启动的类
文档注释
JDK包含一个很有用的工具,javadoc,它可以由源文件生成一个HTML文档,
注释的插入
- javadoc从以下项中抽取信息:
- 模块
- package
- public的class和interface
- public和protected的字段,构造器,方法
- 应该为以上各个部分添加注释
/** */
- 注释中的内容是 自由格式文本,标记以@开始,如@param
- 第一句为概要性句子,javadoc将抽取第一句生成概要页
- 自由格式文本中可以使用HTML标签,如
<strong></strong>
表强调,<img>
插入图片
其他文件图片的文件,应该放到包含源文件目录的一个子目录doc-files中
类注释
- 类注释必须放在import语句之后,类定义之前
- 没有必要在每一行的开始都加*号,但大部分IDE都会自动加星号
方法注释
- @param 可以占据多行,可以使用HTML,一个方法的param必须放在一起
- @return 可以多行,使用HTML
- @throws 添加异常抛出注释
字段注释
- 只需要对公共字段(静态常量static final)建立文档
通用注释
- @since 引入该条目 始于版本
- @author 可用多个
- @version 当亲版本
类设计技巧
1, 保证数据私有
2. 对数据初始化(避免错误)
- 不要再类中使用过多基本类型(不易重复利用)
- 不是所有的字段都需要单独的getter和setter,字段访问器和字段更改器(有一些实例字段不希望其他人修改)
- 分解职责过多的类
- 类名和方法名要充分体现职责(见名知意)
- 优先使用不可变的类(避免多线程并发同时更新一个对象带来的麻烦)