“核心技术:对象与类”的版本间差异
(→oop) |
|||
(未显示同一用户的10个中间版本) | |||
第22行: | 第22行: | ||
* 对象状态的改变必须通过调用方法实现(封装的意义) | * 对象状态的改变必须通过调用方法实现(封装的意义) | ||
* 开发过程中,分析问题中的名词对应着类和类的实例域,动词对应着类的方法; | * 开发过程中,分析问题中的名词对应着类和类的实例域,动词对应着类的方法; | ||
* 对象的封装应该实现: | |||
*: 一个私有的数据与; | |||
*: 一个公有的域访问器方法; | |||
*: 一个共有的域更改器方法; | |||
=== 类之间的关系 === | === 类之间的关系 === | ||
第29行: | 第35行: | ||
[[File:类关系的UML符号.png|800px]] | [[File:类关系的UML符号.png|800px]] | ||
==== 实例(UML)==== | |||
===== 关联(Association) ===== | |||
# 单向关联: | |||
#: [[File:单向关联.png|400px]] | |||
# 双向关联: | |||
#: [[File:双向关联.png|400px]] | |||
# 自身关联: | |||
#: [[File:自身关联.png|400px]] | |||
# 多维关联: | |||
#: [[File:多维关联.png|400px]] | |||
===== 聚合(Aggregation) ===== | |||
:[[File:聚合(Aggregation).png|400px]] | |||
===== 组合(复合,Composition) ===== | |||
:[[File:组合(复合,Composition).png|400px]] | |||
* 聚合的成员可独立,复合的成员必须依赖于整体才有意义 | |||
===== 泛化(Generalization) ===== | |||
泛化指的是类与类之间的继承关系和类与接口之间的实现关系。 | |||
# 继承: | |||
#: [[File:泛化(Generalization):继承.png|400px]] | |||
# 实现: | |||
#: [[File:泛化(Generalization):实现.png|400px]] | |||
== 使用预定义类 == | == 使用预定义类 == | ||
* 并非所有的类都具有面向对象特征;(如,Math、Date类,只封装了功能,没有也不需要隐藏数据) | |||
=== 对象与对象变量 === | |||
# 构造器(constructor)是一种特殊的方法,用来构造并初始化对象; | |||
# 构造器的名字应该与类名相同; | |||
# 一个对象变量并不包含一个对象,而是引用一个对象; | |||
# 局部变量不会自动地初始化为null,必须通过new或将其设置为null来进行初始化; | |||
* Java中要获得对象的完整拷贝,应该使用“clone”方法; | |||
=== Date 与 LocalDate 类 === | |||
<pre> | |||
Date类的时间,使用一个固定时间点(纪元,epoch)的毫秒数(可正可负)来表示的; | |||
纪元:UTC时间 1970年1月1日 00:00:00 | |||
UTC(Coordinated Universal Time),与GMT(Greenwich Mean Time,格林威治时间)一样。 | |||
</pre> | |||
Date 类提供的日期处理并没有太大的用途。如“December 31, 1999, 23:59:59”这样的时间只是阳历的固有习惯,虽然遵循了世界上大多数地区使用的阳历表示法。但是,统一时间点采用中国的农历或希伯来的阴历表示就很不一样。<br/> | |||
类库设计者决定将保存时间与给时间点命名分开,所以标准Java类库分别包含了两个类: | |||
# 一个是用来表示时间点的'''Date'''类 | |||
# 另一个是用来表示大家熟悉的日历表示法的'''LocalDate'''类 | |||
*(Java SE 8 引入了另一些类来处理日期和时间的不同方面) | |||
将时间与日历分开就是一种很好的面向对象设计。 | |||
关于LocalDate的使用: | |||
* 不要使用构造器来构造LocalDate的对象,而应该使用静态工厂方法; | |||
# 用当前时间构造一个新对象: | |||
#: <syntaxhighlight lang="java"> | |||
LocalDate.now() | |||
</syntaxhighlight> | |||
# 提供年月日来构造一个特定时间的对象: | |||
#: <syntaxhighlight lang="java"> | |||
LocalDate.of(1999, 12, 31) | |||
</syntaxhighlight> | |||
# 将构造的对象保存在一个对象变量中: | |||
#: <syntaxhighlight lang="java"> | |||
LocalDate newYearEve = LocalDate.of(1999, 12, 31); | |||
</syntaxhighlight> | |||
# 有了LocalDate对象,就可以使用getYear、getMonthValue、getDayOfMonth来得到年、月、日,以及其他操纵日历的方法: | |||
#: <syntaxhighlight lang="java"> | |||
int year = newYearEve.getYear(); | |||
... | |||
</syntaxhighlight> | |||
* '''不应该使用Date类中诸如getYear、getMonth、getDay等方法'''。 | |||
* 调用后会修改对象状态的方法是“更改器方法”,只访问对象而不修改对象的方法是“访问器方法” | |||
== 用户自定义类 == | == 用户自定义类 == | ||
* 构造器总是伴随 new 操作符的执行被调用;而不能对一个已经存在的对象调用构造器来重设实例域。 | |||
* 不应该在构造器中定义域实例域重名的局部变量 | |||
构造器: | |||
# 与类同名 | |||
# 每个类可以有一个以上的构造器 | |||
# 构造器可以有0、1或多个参数 | |||
# 构造器没有返回值 | |||
# 构造器总是伴随着 new 操作一起调用 | |||
== 静态域与静态方法 == | |||
=== 静态变量 === | |||
静态域、类域,即静态变量; | |||
如: | |||
<syntaxhighlight lang="java"> | |||
public static int nextId = 1; | |||
</syntaxhighlight> | |||
* 类的所有实例将会共享一个静态域 | |||
=== 静态常量 === | |||
# 静态:static | |||
# 常量:final | |||
<syntaxhighlight lang="java"> | |||
public static final double PI = 3.1415926; | |||
</syntaxhighlight> | |||
* 对于 public: | |||
*# 不应该把域设置为 public,因为每个类对象都可以对共有域进行修改; | |||
*# 但将公用常量设置为 public 没问题,因为被 final修饰,不会被更改; | |||
* 关于final: | |||
*: “System”类中有“setOut”方法,可以将“System.out”,设置为不同的流(“public final static PrintStream out = null;”) | |||
*: 此处可以修改 final 变量 out的值,是因为“setOut”调用了一个“Native”方法(“private static native void setOut0(){}”),可以绕过Java的存取控制机制。 | |||
== | === 静态方法 === | ||
# 静态方法可以使用类名、类的实例调用; | |||
# 静态方法只能访问静态域,而不能访问实例域,即不能操作对象; | |||
* 关于静态方法main:每个类可以有一个main方法,用作单元测试 | |||
=== 工厂方法 === | |||
静态方法的另一种常见用途:类似 LocalDate 和 NumberFormat 的类使用静态工厂方法来构造对象。 | |||
== 方法参数 == | == 方法参数 == | ||
# 按值调用(“call by value”):表示方法接受的是调用者提供的值。 | |||
# 按引用调用(“call by reference”):表示方法接收的是调用者提供的变量地址。 | |||
# 一个方法可以修改传递引用锁对应的变量值,而不能修改值传递调用所对眼的变量值 | |||
'''Java 总是采用 <div><span style="color:Chocolate; font-weight:bold; font-size:150%;">按值调用</span></div>'''!!! | |||
即,方法得到的是所有参数值的一个拷贝,并不能修改传递值给它的变量的内容。 | |||
Java中方法参数的使用情况: | |||
# 一个方法'''不能修改一个基本数据类型的参数'''; | |||
# 一个方法'''可以更改一个对象参数的状态'''; | |||
# 一个方法'''不能让对象参数引用一个新的对象'''; | |||
== 对象构造 == | == 对象构造 == | ||
=== 重载 === | |||
* 重载(overloading):多个方法有相同的名字、不同的参数。如参数个数或类型不同的构造函数; | |||
* 方法的签名(signature):方法名以及参数类型(返回值不是方法签名的一部分); | |||
=== 默认域初始化 === | |||
如果在构造器中没有显示地给域赋予初值,那么就会被自动地赋为默认值(数值为0,布尔值为false,对象引用为null)。 | |||
=== 显示域初始化 === | |||
在执行构造器之前,先执行赋值操作(当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时,十分有用)。 | |||
<syntaxhighlight lang="java"> | |||
class Employee | |||
{ | |||
private static int nextId; | |||
private int id = assingId(); | |||
... | |||
private static int assingId() | |||
{ | |||
int r = nextId; | |||
nextId++; | |||
return r; | |||
} | |||
} | |||
</syntaxhighlight> | |||
=== 无参构造器 === | |||
* 当类没有提供任何构造器时,系统会提供一个默认的无参构造器。 | |||
*: 这个构造器将所有的实例域设置为默认值; | |||
* 当类中提供了至少一个构造器,但未提供无参构造器时,如果构造对象没有提供参数会被视为不合法。 | |||
=== 调用另一个构造器 === | |||
如果构造器的第一个语句形如“this(...)”,则构造器将会调用同一个类的另一个构造器: | |||
<syntaxhighlight lang="java"> | |||
public Employee(double s) | |||
{ | |||
this("Employee #" + nextId, s); | |||
nextId++; | |||
} | |||
</syntaxhighlight> | |||
* 这样公共的构造器代码部分只编写一次即可。 | |||
=== 初始化块 === | |||
初始化数域的方法: | |||
# 在构造器中设置值 | |||
# 在声明中赋值 | |||
# 初始化块 | |||
<syntaxhighlight lang="java"> | |||
class Employee | |||
{ | |||
private static int nextid; | |||
private int id; | |||
private String name; | |||
private double salary; | |||
//初始化块 | |||
{ | |||
id = nextId; | |||
nextId++; | |||
} | |||
public Employee(String n, double s) | |||
{ | |||
name = n; | |||
salary = s; | |||
} | |||
public Employee() | |||
{ | |||
name = ""; | |||
s = 0; | |||
} | |||
... | |||
} | |||
</syntaxhighlight> | |||
* 首先运行初始化块,然后再运行构造器的主体部分 | |||
即,无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。 | |||
==== 【关于:Java类对象初始化顺序】 ==== | |||
参考:'''[[深入理解JVM:虚拟机类加载机制#初始化]]''' | |||
# 父:静态变量 | |||
#: 静态初始化块 | |||
# 子:静态变量 | |||
#: 静态初始化块 | |||
# main | |||
# 父:变量 | |||
#: 初始化块 | |||
#: 构造器 | |||
# 子:变量 | |||
#: 初始化块 | |||
#: 构造器 | |||
* 先初始化静态内容 | |||
* 先初始化父类的内容 | |||
* 变量先于初始块,初始快先于构造器 | |||
=== 对象析构 与 finalize方法 === | |||
# 面向过程的程序设计语言,有显示的析构刚嘎,如C++ | |||
# 而Java中有GC,不需要手动回收内存,所以Java不支持析构器 | |||
但,当某些对象使用了内存以外的资源(如文件等),这种情况下当资源不再需要时,将其回收和再利用将十分重要。<br/> | |||
可以为任何一个类添加'''finalize'''方法,用于回收资源: | |||
* finalize 将在垃圾回收器清楚对象之前调用; | |||
* 但是不能知道调用的时间,所以不应该依赖于它进行资源回收; | |||
== 包 == | == 包 == | ||
Java中使用包(package)来将类组织起来,用于确定类名的唯一性(不同包下的同名类不冲突)。 | |||
* 所有标准的Java包,都处于java(核心)和javax(扩展)包层次中; | |||
* 嵌套的包之间没有任何关系,如“java.util”与“java.util.jar” | |||
=== 类的导入 === | |||
一个类可以使用所属包中的所有类,以及其他包中的公有类(pubic class)。 | |||
使用其他包的公有类: | |||
# <syntaxhighlight lang="java"> | |||
java.time.LocalDate today = java.time.LocalDate.now(); | |||
</syntaxhighlight> | |||
# <syntaxhighlight lang="java"> | |||
//源文件头部 | |||
import java.util.*; | |||
//代码中 | |||
LocalDate today = LocalDate.now(); | |||
</syntaxhighlight> | |||
* 只能使用“*”导入一个包,而不能使用“import java.*”或“import java.*.*”来导入所有java前缀的包; | |||
=== 静态导入 === | |||
import 导入静态方法和静态域: | |||
<syntaxhighlight lang="java"> | |||
// 源文件头部 | |||
import static java.lang.System.*; // 或导入特定的方法或域“import static java.lang.System.out”、“import static java.lang.System.exit” | |||
//代码中,可以使用System类的静态方法和静态域,而不必加类名前缀 | |||
out.println("Goodbyr, world!"); // System.out | |||
exit(0); // System.exit | |||
</syntaxhighlight> | |||
=== 将类放入包中 === | |||
要将类放入包中,则必须将包的名字放在源文件的开头: | |||
<syntaxhighlight lang="java"> | |||
package com.eijux.wiki; | |||
public class Wikioe | |||
{ | |||
... | |||
} | |||
</syntaxhighlight> | |||
=== 包作用域 === | |||
包密封(package sealing):如果将一个包密封起来,就不能再向这个包添加类了。 | |||
== 类路径 == | == 类路径 == | ||
== 文档注释 == | == 文档注释 == |
2024年10月12日 (六) 12:42的最新版本
oop
面向对象的程序时由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。
- 面向对象:
- 面向过程:
- 面向切面:
类 与 对象
- 类是构造对象的模板或蓝图,对象是类的实例化
- 由类构造(construct)对象的过程称为创建类的实例(instance)
- 封装(encapsulation,有时称为数据隐藏):将数据(实例域,instance field)和行为(方法,用于操纵数据)组合在一个包中,并对对象的使用者隐藏了数据的实现方式
- 对象的实例域的几个就是这个对象的当前状态(state)
- 通过扩展一个类来建立另一个类的过程称为继承(inheritance)
- “Object”是所有类的超类
- 对象状态的改变必须通过调用方法实现(封装的意义)
- 开发过程中,分析问题中的名词对应着类和类的实例域,动词对应着类的方法;
- 对象的封装应该实现:
- 一个私有的数据与;
- 一个公有的域访问器方法;
- 一个共有的域更改器方法;
类之间的关系
- 依赖(“uses-a”):一个类的方法操纵另一个类的对象
- 聚合(“has-a”):一个类中包含另一个类的对象
- 继承(“is-a”):一个类由另一个类扩展而来
实例(UML)
关联(Association)
聚合(Aggregation)
组合(复合,Composition)
- 聚合的成员可独立,复合的成员必须依赖于整体才有意义
泛化(Generalization)
泛化指的是类与类之间的继承关系和类与接口之间的实现关系。
使用预定义类
- 并非所有的类都具有面向对象特征;(如,Math、Date类,只封装了功能,没有也不需要隐藏数据)
对象与对象变量
- 构造器(constructor)是一种特殊的方法,用来构造并初始化对象;
- 构造器的名字应该与类名相同;
- 一个对象变量并不包含一个对象,而是引用一个对象;
- 局部变量不会自动地初始化为null,必须通过new或将其设置为null来进行初始化;
- Java中要获得对象的完整拷贝,应该使用“clone”方法;
Date 与 LocalDate 类
Date类的时间,使用一个固定时间点(纪元,epoch)的毫秒数(可正可负)来表示的; 纪元:UTC时间 1970年1月1日 00:00:00 UTC(Coordinated Universal Time),与GMT(Greenwich Mean Time,格林威治时间)一样。
Date 类提供的日期处理并没有太大的用途。如“December 31, 1999, 23:59:59”这样的时间只是阳历的固有习惯,虽然遵循了世界上大多数地区使用的阳历表示法。但是,统一时间点采用中国的农历或希伯来的阴历表示就很不一样。
类库设计者决定将保存时间与给时间点命名分开,所以标准Java类库分别包含了两个类:
- 一个是用来表示时间点的Date类
- 另一个是用来表示大家熟悉的日历表示法的LocalDate类
- (Java SE 8 引入了另一些类来处理日期和时间的不同方面)
将时间与日历分开就是一种很好的面向对象设计。
关于LocalDate的使用:
- 不要使用构造器来构造LocalDate的对象,而应该使用静态工厂方法;
- 用当前时间构造一个新对象:
LocalDate.now()
- 提供年月日来构造一个特定时间的对象:
LocalDate.of(1999, 12, 31)
- 将构造的对象保存在一个对象变量中:
LocalDate newYearEve = LocalDate.of(1999, 12, 31);
- 有了LocalDate对象,就可以使用getYear、getMonthValue、getDayOfMonth来得到年、月、日,以及其他操纵日历的方法:
int year = newYearEve.getYear(); ...
- 不应该使用Date类中诸如getYear、getMonth、getDay等方法。
- 调用后会修改对象状态的方法是“更改器方法”,只访问对象而不修改对象的方法是“访问器方法”
用户自定义类
- 构造器总是伴随 new 操作符的执行被调用;而不能对一个已经存在的对象调用构造器来重设实例域。
- 不应该在构造器中定义域实例域重名的局部变量
构造器:
- 与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有0、1或多个参数
- 构造器没有返回值
- 构造器总是伴随着 new 操作一起调用
静态域与静态方法
静态变量
静态域、类域,即静态变量; 如:
public static int nextId = 1;
- 类的所有实例将会共享一个静态域
静态常量
- 静态:static
- 常量:final
public static final double PI = 3.1415926;
- 对于 public:
- 不应该把域设置为 public,因为每个类对象都可以对共有域进行修改;
- 但将公用常量设置为 public 没问题,因为被 final修饰,不会被更改;
- 关于final:
- “System”类中有“setOut”方法,可以将“System.out”,设置为不同的流(“public final static PrintStream out = null;”)
- 此处可以修改 final 变量 out的值,是因为“setOut”调用了一个“Native”方法(“private static native void setOut0(){}”),可以绕过Java的存取控制机制。
静态方法
- 静态方法可以使用类名、类的实例调用;
- 静态方法只能访问静态域,而不能访问实例域,即不能操作对象;
- 关于静态方法main:每个类可以有一个main方法,用作单元测试
工厂方法
静态方法的另一种常见用途:类似 LocalDate 和 NumberFormat 的类使用静态工厂方法来构造对象。
方法参数
- 按值调用(“call by value”):表示方法接受的是调用者提供的值。
- 按引用调用(“call by reference”):表示方法接收的是调用者提供的变量地址。
- 一个方法可以修改传递引用锁对应的变量值,而不能修改值传递调用所对眼的变量值
Java 总是采用
!!!
即,方法得到的是所有参数值的一个拷贝,并不能修改传递值给它的变量的内容。
Java中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数;
- 一个方法可以更改一个对象参数的状态;
- 一个方法不能让对象参数引用一个新的对象;
对象构造
重载
- 重载(overloading):多个方法有相同的名字、不同的参数。如参数个数或类型不同的构造函数;
- 方法的签名(signature):方法名以及参数类型(返回值不是方法签名的一部分);
默认域初始化
如果在构造器中没有显示地给域赋予初值,那么就会被自动地赋为默认值(数值为0,布尔值为false,对象引用为null)。
显示域初始化
在执行构造器之前,先执行赋值操作(当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时,十分有用)。
class Employee
{
private static int nextId;
private int id = assingId();
...
private static int assingId()
{
int r = nextId;
nextId++;
return r;
}
}
无参构造器
- 当类没有提供任何构造器时,系统会提供一个默认的无参构造器。
- 这个构造器将所有的实例域设置为默认值;
- 当类中提供了至少一个构造器,但未提供无参构造器时,如果构造对象没有提供参数会被视为不合法。
调用另一个构造器
如果构造器的第一个语句形如“this(...)”,则构造器将会调用同一个类的另一个构造器:
public Employee(double s)
{
this("Employee #" + nextId, s);
nextId++;
}
- 这样公共的构造器代码部分只编写一次即可。
初始化块
初始化数域的方法:
- 在构造器中设置值
- 在声明中赋值
- 初始化块
class Employee
{
private static int nextid;
private int id;
private String name;
private double salary;
//初始化块
{
id = nextId;
nextId++;
}
public Employee(String n, double s)
{
name = n;
salary = s;
}
public Employee()
{
name = "";
s = 0;
}
...
}
- 首先运行初始化块,然后再运行构造器的主体部分
即,无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。
【关于:Java类对象初始化顺序】
参考:深入理解JVM:虚拟机类加载机制#初始化
- 父:静态变量
- 静态初始化块
- 子:静态变量
- 静态初始化块
- main
- 父:变量
- 初始化块
- 构造器
- 子:变量
- 初始化块
- 构造器
- 先初始化静态内容
- 先初始化父类的内容
- 变量先于初始块,初始快先于构造器
对象析构 与 finalize方法
- 面向过程的程序设计语言,有显示的析构刚嘎,如C++
- 而Java中有GC,不需要手动回收内存,所以Java不支持析构器
但,当某些对象使用了内存以外的资源(如文件等),这种情况下当资源不再需要时,将其回收和再利用将十分重要。
可以为任何一个类添加finalize方法,用于回收资源:
- finalize 将在垃圾回收器清楚对象之前调用;
- 但是不能知道调用的时间,所以不应该依赖于它进行资源回收;
包
Java中使用包(package)来将类组织起来,用于确定类名的唯一性(不同包下的同名类不冲突)。
- 所有标准的Java包,都处于java(核心)和javax(扩展)包层次中;
- 嵌套的包之间没有任何关系,如“java.util”与“java.util.jar”
类的导入
一个类可以使用所属包中的所有类,以及其他包中的公有类(pubic class)。
使用其他包的公有类:
java.time.LocalDate today = java.time.LocalDate.now();
//源文件头部 import java.util.*; //代码中 LocalDate today = LocalDate.now();
- 只能使用“*”导入一个包,而不能使用“import java.*”或“import java.*.*”来导入所有java前缀的包;
静态导入
import 导入静态方法和静态域:
// 源文件头部
import static java.lang.System.*; // 或导入特定的方法或域“import static java.lang.System.out”、“import static java.lang.System.exit”
//代码中,可以使用System类的静态方法和静态域,而不必加类名前缀
out.println("Goodbyr, world!"); // System.out
exit(0); // System.exit
将类放入包中
要将类放入包中,则必须将包的名字放在源文件的开头:
package com.eijux.wiki;
public class Wikioe
{
...
}
包作用域
包密封(package sealing):如果将一个包密封起来,就不能再向这个包添加类了。