CoreJavaVI类和对象笔记

对象和类


OOP简介

作者首先举著名PC生产商,如CompaqDell,引入了面向对象(OOP)的概念。OOP只在意对象(Object)的功能,而不关心对象的具体实现;好比PC生产商购入能满足某种需求的配件,不细究其中的实现过程。OOP将功能细分,让每个对象实现其中的一部分,对象间可以通过方法调用(Method calls)完成其所需的任务。但是OOP有良好的数据封装性(encapsulation),某个对象不能直接操作其他对象的内部数据,所有通信的唯一方式就是方法调用,比如访问对象a的一个数据aData,可以a.getData()

Pascal语言发明者Niklaus Wirth提出了著名的0公式Algorithms + Data Structures = Programs。与传统的结构化编程(Structured Programming)先设计算法(如何操作数据),再考虑数据存储的做法(如上面公式,算法在前)相反,OOP将数据结构放到了算法实现的前面。


关于OOP的术语

(Class):可以看作待创建的对象的一个模板,或者蓝图。
构造类的一个对象称作创建类的一个实例(Instance)
封装(Encapsulation,有时也叫做数据隐藏data hiding):将数据(data)和行为(behavior)一起放入某个黑盒子中,用户却看不到数据的实现。
对象中的数据称为实例域(Instance Field),而对这些数据操作的函数和过程称为方法(Method)
类的每个实例,即对象,它们的实例域的值可能是彼此不同的,这些值叫做对象的当前状态。对象的方法可能会改变这些状态值。
继承(Inheritance):通过扩展父类实现一个子类。
Java
Object是一个“Cosmic Superclass”,其它的所有类都继承自Object


对象

对象的三个关键特征:
行为(Behavior),有类的方法定义,所以同一个类的实例有相同的行为。
状态(State),对象状态的改变只能是方法调用的结果,否则认为封装已经被破坏。
标识(Identity),区别不同的对象。

同一个类的不同对象间总是(always)有不同的标识,并且状态经常(usually)互不相同(即有相同的可能)


类间关系

最通常的类间关系有:
依赖Dependence ("uses–a"):类A的方法要操作类B的某个对象,称类A依赖类B。最常见。注意:尽量减少类间的互相依赖。
聚合Aggregation ("has–a")
继承Inheritance ("is–a")

可以使用统一建模语言(UML)提供的Connector类图(class diagram)来表示类间的各种关系(参考UML手册)


OOP和传统过程化编程的比较

两种策略:
由顶而下(Top-down):将问题细化。
自底而上(bottom-up):将完成简单任务的过程组装成程序。

通过细化过程能很好地解决较小的问题,但对于大型的项目,OOP的类和方法的组织方式就能体现它的巨大优势:便于管理、小组分工,提高debug效率。

OOP和模块化(modularization)的区别:
模块间通过过程调用(procedure calls)互相通信,而非共享数据。
类是创建具有相同行为的多个对象的工厂,但我们却无法获得一个模块(module)的多个拷贝。


使用已存在的类

对象和对象变量
构造方法(Constructor):构造和初始化对象。

对象变量(Object variable)和对象是不同的,对象变量的值是存储在其他地方的对象的一个引用(reference)。声明一个对象变量后,若要使用其方法,必须用new constructor()初始化。可以给对象变量赋值null,表示没有引用任何对象,这时如果使用某个对象方法,将引发一个运行时(run-time)错误。

本地对象变量(local object variable)不会自动初始化为null,所以必须或者调用new初始化,或者设为null

书本关于时间和相关的Java两个类的一点描述:Date类表示一个特殊的时间点,时间点的参考基准是协调世界时(UTC)19701100:00:00,用两者之间相差的毫秒数(有正负)来表示,当然在使用Date的时候无需知道这些。Calendar类描述了一般的历法属性,可以通过继承这个类实现具体的历法,如我们的阴历。GregorianCalendar就是它的一个子类,用以实现公历表示。在引入Calendar后,Date的方法就不再建议使用(deprecated)

GregorianCalendar的具体使用参考文档,但有一点注意:在构造它的一个对象时,参数月份是从0开始的,所以11就表示12月份。

Mutator method:改变对象实例域的方法;Accessor method:访问实例域,而不改变它们的值。


自定义类

在一个源文件中只能有一个public类,但可以有多个非public类,源文件名必须和public类同名。

也可以为每个类单独创建一个源文件。书本举例:Employee.javaEmployeeTest.java,在编译时使用通配符,
javac Employee*.java
或者直接
javac EmployeeTest.java

Java编译器会去搜索Employee.class 文件,若没找到,自动编译Employee.javaEmployee.class。而且Java编译器还带有类似于Unix make自动重编译的功能:检查Employee.java的时间戳,若比Employee.class要新,则自动重新编译该文件。 


若类A中的方法用public修饰,则任何类的任何方法中都能调用类Apublic方法。

若类A中的实例域用private修饰,则只有类A中的方法才能访问它们,其它任何类的任何方法都无法读写这些域。

构造方法只能和new操作符合用,不能给已经存在的对象再使用构造方法来重置实例域,否则引发编译时(Compile-time)错误。

对于构造方法,要牢记:
构造方法与类同名
一个类可以有多个构造方法

一个构造方法可以有0个,1个或多个参数
构造方法没有返回值
构造方法总是用new操作符来调用

注意:在方法中引入局部变量时不要与实例域同名,否则会隐藏同名的实例域。在使用实例域的时候可以使用this关键字,以和局部变量区分。


在使用类A的一个对象a的某个方法method(arg)时,即a.method(arg)a称作隐式参数(implicit parameter)arg称作显式参数(explicit parameter)。在方法声明时,显式参数显式地列出,隐式参数不出现。可以用this关键字表示隐式参数。

Field accessor:简单地返回实例域的值的方法。
Fidld mutator
:简单地修改实例域的值的方法。

使用private实例域和publicField accessor Fidld mutator与直接使用public实例域有两个好处:
1.
   内部实现的改变不影响其它代码
2.
   Mutator方法可以作错误检查

注意:在写返回易变对象(mutable object)accessor方法时要十分小心,易变对象很可能被改变,所以用clone()返回易变数据域的一个拷贝。


访问private数据的方法

类的方法可以访问该类的所有对象private数据。


私有(Private)方法

Private方法只能被同一个类中的其它的方法调用,因此一个没有被类中其它方法调用的private方法,可以简单地丢弃(public方法就不可以了,因为可能有其他代码的依赖)


最终(Final)实例域

Final实例域必须在对象被创建的时候初始化,而且其值不能再被改变。

如果一个类的所有实例域都是final类型的,则称这个类是不可变的(immutable),也就是说它的对象在创建后不会被改变。Immutable的一个好处是:不用担心数据共享时的不一致。


静态(Static)

对于普通的实例域,类的每个对象都有自己的一份拷贝;而static域则是这些对象之间共享的。Static域是属于类的,即使没有对象,static域照样存在。


常量

静态变量(static variable)很少使用,常见的是静态常量(static constant)。如Math.PI
public static final double PI = 3.14159265358979323846;

还有System.out
Public static final PrintStream out = …


静态(Static)方法

静态方法不对具体对象操作,没有隐式参数。静态方法中不能访问实例域,但可以访问静态域。

main方法也是一个静态方法。


工厂方法(factory method)

我们常用某些静态方法来产生某个类的一个对象,比如
NumberFormat formatter = NumberFormat.getPercentInstance();
这种方法就称作工厂方法。

为什么不使用构造方法来创建对象?1.无法给出构造方法的名字;2.工厂方法可以得到类的对象,或者该类的子类对象,而构造方法就没有这种灵活性。

 


方法参数

传值调用(call by value):方法得到的参数是调用者提供的值,即是参数的一个副本。因此,方法可以修改的只是参数的副本值,而不影响原参数。

引用调用(call by reference):方法得到的是调用者提供的变量的位置,这种方式可以改变传给方法的参数。

Java
的方法参数可以是原始类型(数字,Boolean值等),也可以是对象引用(Object reference)。但Java语言并没有使用对对象的引用调用,相反地,对象引用也是通过值传递的

Java
中方法参数的能够和不能够,
1
.方法不能够修改原始类型的参数
2
.方法能够改变对象参数的状态(用对象饮用作方法参数)
3
.方法不能够让对象参数指向一个新的对象。


对象构造

重载(overloading)
几个方法具有相同的方法名,但有不同的参数(数量或者类型),称之为重载。

编译器执行重载解析(overloading resolution):它会从一组方法中寻找与指定方法相匹配的,最后如果有参数不匹配或者有多个匹配,将发生一个编译时错误(compile-time error)


默认的域初始化

如果没有在构造方法中显式地设置域的值,那么数字默认初始化为0Boolean型初始化为false,对象引用为null

和域相比,本地变量(local variables)就必须在方法中显式地初始化。


默认的构造方法

如果某个类不带构造方法,则将使用默认的构造方法,它不带参数,又称为无参构造方法(no-arg constructor),它将对域进行默认的初始化。

如果一个类提供了至少一个构造方法,但是没有提供默认的构造方法,这时如果不带任何参数地构造一个对象将是非法的。

注:默认构造方法只在没有其它构造方法的类中存在。如果想在一个具有构造方法的类中使用无参的默认构造方法,那么必须提供这样的一个默认构造方法。


显式的域初始化(Explicit field initialization)

如果类的某个域在其所有的构造方法中初始为同一个值,则可以直接在类中给该域赋值,而且可以赋给变量值


参数名

防止参数变量隐藏(shadow)与其同名的实例域,最好在实例域前加上this关键字。


调用另一个构造方法

可以在一个构造方法中使用this(…)调用另一个构造方法,this语句必须出现在第一行。

如书本上的例子,
public Employee(double s)
{
   // calls Employee(String, double)
   this("Employee #" + nextId, s);
   nextId++;
}

这样做的好处就是只需要写一遍通用的构造代码。


 初始化块(Initialization blocks)

初始化一个数据域的三种方法:
1
        在构造方法中初始化
2
        在该域声明时初始化
3
        在初始化块中初始化

在类声明中可以包含任意的代码块,这些代码块在构造该类的对象时被执行。如
class Employee
{
   public Employee(String n, double s)
   {
      name = n;
      salary = s;
   }
   public Employee()
   {
      name = "";
      salary = 0;
   }
   . . .
      //
必须在初始化块中使用之前定义
   private static int nextId;
   private int id;
   . . .
   //
初始化块的代码在构造方法之前执行
   {
      id = nextId;
      nextId++;
   }
   private String name;
   private double salary;
}

构造方法被调用时的细节
1
        所有数据被初始化成它们的默认值(0falsenull)
2
        按照类声明中的顺序,域被逐个初始化(可能在初始化块中)
3
        如果构造方法的第一行调用了另一个构造方法,则执行被调用的构造方法
4
        构造方法的方法体被执行

初始化静态(static)域的时候,可以直接提供一个初始化值,也可以在静态初始化块(static声明)中实现。后者一般实现复杂的初始化代码。

当类第一次被装载的时候静态初始化就发生了。


对象析构(Destruction)finalize方法

C++
有析构方法(destructor),对不再使用的对象进行清除,收回分配的内存。Java不支持这种手工的内存回收机制,内建自动垃圾回收机制(automatic garbage collection)

可以给任何类加上finalize方法,该方法在GC清除对象之前被调用。

如果希望在使用某个资源结束后立即关闭它,则可以使用dispose()。类声明中包含的dispose()将在该类的对象处理结束后被调用。特别的,如果类中某实例域有dispose(),则应该提供dispose代码来除去该实例域。


(Package)

使用包的一个主要原因是保证类名的唯一性(uniqueness)。为保证包名的独特性,Sun推荐用反向的国际域名表示法,如com.urname.urpackage

使用包嵌套的唯一目的是管理类名的唯一性。在编译器看来,嵌套的两个包之间完全没有关系,每个都有自己的独立的类集合。


使用包

一个类可以使用同一包中的所有类和其它包中的public类。

使用其它包中的类有两个方法,一是在使用的类名前加上完整的包名;二是使用import关键字。

使用import时,可以导入一个包中的所有类,这不会影响代码的大小。但是不能使用java前缀导入所有的包,如import java.*; import java.*.*;。如果导入的两个包中有相同的类名,则在使用该类时要在类名前加上完整的包名,以互相区别。

静态导入(Static Imports)

JDK1.5开始,import语句可以导入静态方法和静态域,如在源文件加上,

import static java.lang.System.*;

则可以直接使用System的静态方法,exit(),out.println(),而无需在方法前加上System这个类名前缀.


虚拟机如何定位类文件(classes)

在文件系统中到类文件的路径必须和包名相一致。可以用JAR工具对类文件进行归档,这样可以节省空间和访问时间。如在jre/lib下的运行时库rt.jar包含了数千个类文件。

Classpath
是基目录的集合,每个基目录的子目录下可以包含类文件。使用JDK时有两种方法指定classpath1.给编译器和字节码解释器用-classpath选项2.通过设置CLASSPATH环境变量。

Classpath
包括1.一个基目录2.当前目录3.JAR文件。

系统类文件:jre/lib, jre/lib/ext


包范围(Package scope)

如果没有指定public或者private等访问控制符,类、方法或变量能够被同一包中的所有方法访问,要特别注意变量的访问权限。

包封装(package sealing)后,就不能再向其中添加类文件。


文档注释

插入注释
每个/** …*/文档注释包括了标签(tags)和自由格式的文本,其中标签以@开头,如@author@param


类注释
位于import语句之后,类定义之前。


方法注释
首先描述方法的作用,然后可用下面的通用标签:
@param variable description
@return description
@throws class description


域注释

只需要对公共域给出注释,通常是静态常量。


 OOP类设计经验

总是让数据私有

总是初始化数据

不要在一个类中使用太多的基本类型

并不是每个域都需要它自己的accessormutator方法的

类定义时使用标准的形式


类的内容
public features
package scope features
private features

对每个部分
instance methods
static methods
instance fields
static fields

将复杂类的功能分派到多个较小类

让类名和方法名有望文生义的效果

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值