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

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值