《Java核心技术卷1》笔记:第4章 对象与类

4.3节:用户自定义类

1. 封装性问题

P110页
警告 : 注意不要编写返回引用可变对象的访问器方法 。 在 Employee 类中就违反了这个设计原则 , 其中的 getHireDay 方法返回了一个 Date 类对象 。

class Employee
{
private Date hireDay ;
public Date getHireDayO
{
return hireDay ; / / Bad
}

LocalDate 类没有更改器方法 , 与之不同 , Date 类有一个更改器方法 setTime , 可以在这里设置毫秒数。Date 对象是可变的 , 这一点就破坏了封装性 ! 请看下面这段代码
Date 对象是可变的 , 这一点就破坏了封装性 ! 请看下面这段代码

Employee harry = . .;
Date d = harry.getHireDayO ;
double tenYearsInMilliSeconds = 10 * 365.25 * 24 * 60 * 60 * 1000 ;
d.setTime(d.getTime() - (long) tenYearsInMilliSeconds ) ;
// let ' s give Harry ten years of added seniority

外部通过 hiredate的getter方法获取Date对象,实际上 对象d 和harry对象域中的 hireday指向了同一块地址。如果修改 d ,那么原实例 harry对象中的hireday也会一起跟着变。
但是如果你的getter 只返回的是向 String、Integer 这样的不可变类型,即使外部获取了修改了,原实例中的hireday的还是不变的。

在这里插入图片描述
如果需要返回一个可变对象的引用 , 应该首先对它进行克隆 ( clone ) 。 对象 clone 是
指存放在另一个位置上的对象副本。有关对象 clone 的详细内容将在第 6 章中讨论。
下面是修改后的代码 :

class Employee
{
public Date getHireDayO
{
	return ( Date ) hireDay . cloneO ; // Ok
}

凭经验可知,如果需要返回一个可变数据域的拷贝 就应该使用 clone 方法。

在这里插入图片描述

2. 基于类的访问权限

一个类中的方法可以访问当前所属类的所有对象的私有数据, 这令很多人感到奇怪!
在这里插入图片描述
典型的调用方式是

if ( harry,equals(boss) ) 
. . .

这个方法访问 harry 的私有域 , 这点并不会让人奇怪,然而 ,它还访问了boss 的私有域。
这也是合法的 , 其原因是 boss 是 Employee 类对象 , 而 Employee 类的方法可以访问 Employee 类的任何一个对象的私有域。

3. final实例域

可以将实例域定义为 final 。 构建对象时必须初始化这样的域。 也就是说,必须确保在每一个构造器执行之后 , 这个域的值被设置 , 并且在后面的操作中,不能够再对它进行修改。
例如, 可以将 Employee 类中的name 域声明为 final , 因为在对象构建之后, 这个值不会再被修改 , 即没有 setName 方法。

class Employee
{
private final String name ;
}

final 修饰符大都应用于基本 ( primitive ) 类型域,或不可变 ( immutable )类的域 ( 如果类中的每个方法都不会改变其对象 , 这种类就是不可变的类。 例如 , String 类就是一个不可变的类 ) 。

对于可变的类,使用 final 修饰符可能会对读者造成混乱。 例如:

private final StringBuiIcier evaluations ;

在 Employee 构造器中会初始化为

evaluations = new StringBuilder ( ) ;

final 关键字只是表示存储在 evaluations 变量中的对象引用不会再指示其他 。 不过这个对象可以修该自身数据值:

public void giveGoldStarO
{
evaluations . append (LocalDate.now() + " : Gold star ! \ n " ) ;
}

4.4节:静态域与静态方法

4.4.1 静态域

如果将域定义为 static , 每个类中只有一个这样的域。 而每一个对象对于所有的实例域却都有自己的一份拷贝。
例如 ,假定需要给每一个雇员賦予唯一的标识码。 这里给 Employee类添加一个实例域 id 和一个静态域 nextld :

class Employee
{
private static int nextld = 1 ;
private int id ;
}

现在, 每一个雇员对象都有一个自己的id 域 , 但这个类的所有实例将共享一个 iiextld域。 换句话说,如果有1000 个 Employee 类的对象 , 则有 1000 个实例域 id。 但是 , 只有一个静态域 nextld。 即使没有一个雇员对象 , 静态域 nextld 也存在 。 它属于类 , 而不属于任何独立的对象。

注释 : 在绝大多数的面向对象程序设计语言中 , 静态域被称为类域 。 术语 “static ” 只是沿用了 C ++ 的叫法 , 并无实际意义。

4.4.2 静态常量

静态变量使用得比较少,但静态常量却使用得比较多 。 例如 ,在Math 类中定义了一个静态常量 :

public class Hath
{
public static final double PI = 3.14159265358979323846 ;
}

在程序中, 可以采用Math . PI 的形式获得这个常量。如果关键字 static 被省略 , PI 就变成了 Math 类的一个实例域 。 需要通过 Math 类的对象访问 PI, 并且每一个Math 对象都有它自己的一份 PI 拷贝。
另一个多次使用的静态常量是 System.out。 它在 System 类中声明 :

public cl ass System
{
public static final PrintStream out = . . . ;

前面曾经提到过, 由于每个类对象都可以对公有域进行修改 , 所以 , 最好不要将域设计为 public。 然而 ,公有常量 ( 即 final 域 ) 却没问题。 因为out 被声明为 final , 所以, 不允许再将其他打印流陚给它 :

注 : 如果查看一下 System 类 , 就会发现有一个 setOut 方法 , 它可以将 System . out 设置为不同的流。 读者可能会感到奇怪 , 为什么这个方法可以修改final 变量的值。 原因在于 , setOut 方法是一个本地方法,而不是用 Java 语言实现的 。 本地方法可以绕过 Java 语言的存取控制机制 。 这是一种特殊的方法,在自己编写程序时 , 不应该这样处理 。

4.4.3 静态方法

Employee 类的静态方法不能访问Id 实例域 , 因它不能操作实例对象 。 但是 , 静态方法可以访问自身类中的静态域 。 下面是使用这种静态方法的一个示例 :

public static int getNextldO
{
	return nextld ; // returns static field
}

可以使用类名调用静态方法 :

int n = Employee.getNextld();

注 : 其实也可以使用对象调用静态方法 。 例如 , 如果 harry 是一个 Employee 对象 , 可以用harry.getNextId ( ) 代替 Employee . getNextId ( ) 。 不过 , 这种方式很容易造成混淆 , 其原因
是 getNextld 方法计算的结果与 harry 毫无关系 。 我们建议使用类名 , 而不是对象来调用静态方法。

在下面?种情?下使用??方法 :

  • 一方法不需要访问对象实例的状态 , 其所需参数都是通过显式参数提供 ( 例如 : Math.pow )
  • 一个方法只需要访问类的静态域 ( 例如: Employee . getNextldh

术语 “ static ” 有一段不寻常的历史。起初 , C 引入关键字
static 是为了表示退出一个块后依然存在的局部变量在这种情况下 , 术语 “ static ” 是有意义的 : 变量一直存在 ,当再次进入该块时仍然存在 。 随后 , static 在 C 中有了第二种含义,表示不能被其他文件访问的全局变量和函数。 为了避免引入一个新的关键字 ,关键字 static 被重用了 。

最后 ,C ++ 第三次重用了这个关键字, 与前面赋予的含义完全不一样 ,这里将其解释为 : 属于类且不属于类对象的变量和函数。 这个含义与Java 相同 。

4.5节:方法参数

Java语言中,对于形参总是拿到实参的一份拷贝。

  • 参数是基础类型时,形参是值拷贝
  • 参数是引用类型时,形参是对象引用的一份拷贝,而不是原引用
public static void swap ( Employee x , Employee y ){ // doesn ' t work
	Employee temp = x;
	x = y;
	y = temp;
}
Employee a = new Employee ( " Alice ,..." 
Employee b = new Employee ( " Bob " , ... ) ;

swap(a,b) ;
// does a now refer to Bob , b to Alice 

显然上面这个交换方法并不会改变a,b引用指向的数据,知识改变了 swap方法中局部变量 x,y的引用。x、y交换了但是 a、b不会被影响。

这就解释了,参数传递时,没有传递同一份引用,而只是传递了一份引用的拷贝。

4.6节:构造器

4.6.3 无参构造器

如果在编写一个类时没有编写构造器 , 那么系统就会提供一个无参数构造器 。 这个构造器将所有的实例域设置为默认值。 于是 ,实例域中的数值型数据设置为 0 、 布尔型数据设置为 false、 所有对象变量将设置为 null 。

注意:如果类中提供了至少一个构造器 , 但是没有提供无参数的构造器 , 则在new 对象时却使用无参的形式创建实例,则被视为不合法。

警告 : 请记住, 仅当类没有提供任何构造器的时候 ,系统才会提供一个默认的构造器

public ClassNameQ{
// 即使没有任何语句,类的变量也会自动初始化为默认值

}

如下,在执行构造器之前就初始化变量也是一个好的习惯,也可以通过显示赋值的方式,调用方法初始化一个变量

class Employee
{
private static int nextld;
private int id = assignld();
private static int assignld(){
	int r = nextld ;
	nextld + + 
	return r;
}

4.6.7 初始化块

  • 初始化时类中的变量显示赋值会最先执行
  • 初始化块会先于构造器执行

思考:new 一个变量时,首先执行 《cinit》会执行 静态类变量的显示赋值,类静态代码块
创建实例《init》时,先执行非静态变量显示赋值、非静态代码块、最后时构造器

书中还讲了一部分静态域的显示赋值初始化、静态初始化块。因为我之前已经有所了解所以不再过多赘述。

在这里插入图片描述

4.6.8 对象析构与 finalize 方法

有些面向对象的程序设计语言, 特别是 C + + ,有显式的析构器方法, 其中放置一些当对象不再使用时需要执行的清理代码。 在析构器中 , 最常见的操作是回收分配给对象的存储空间。 由于Java 有自动的垃圾回收器, 不需要人工回收内存 , 所以Java 不支持析构器。

当然, 某些对象使用了内存之外的其他资源 ,例如, 文件或使用了系统资源的另一个对象的句柄。 在这种情况下 , 当资源不再需要时 , 将其回收和再利用将显得十分重要 。可以为任何一个类添加 finalize 方法。

这一点在JVM层面十分重要、如果一个打开的资源没有关闭、或单例对象引用了其他实例,这些是GC扫描不到的,是引发内存泄漏的潜在因素。

finalize 方法将在垃圾回收器清除对象之前调用 ,在实际应用中, 不要依赖于使用finalize 方法回收任何短缺的资源 , 这是因为很难知道这个方法什么时候才能够调用。

注释 : 有个名为 System . mnFinalizersOnExit ( true ) 的方法能够确保 finalizer 方法在 Java 关闭前被调用 。 不过, 这个方法并不安全 , 也不鼓励大家使用 。 有一种代替的方法是使用
方法 Runtime . addShutdownHook 添加 “ 关闭钓 ” ( shutdown hook ) , 详细内容请参看 API文档。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_popo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值