您的位置:首页 > 编程语言 > Java开发

Java学习笔记(2):对象与类

2013-01-22 17:48 190 查看
在类之间,最常见的关系有:

依赖(“uses-a”):一个类的方法操纵另一个类的对象
聚合(“has-a”):一个类的对象包含另一个类的对象
继承(“is-a”):特殊对象与一般对象

构造器:一种特殊的方法,用来构造并初始化对象。(类似C++的构造函数?)

构造器的名字应与类名相同。

使用new构造一个对象:

new Date();


可以将这个对象传递给一个方法:

System.out.println(new Date());


可以将一个方法应用到刚刚创建的对象上:

String s = new Date().toString();


当希望多次使用构造出的对象时,可将其保存在一个变量中:

Date birthday = new Date();


对象和对象变量

Date deadline; // deadline doesn't refer to any object


该语句定义了一个对象变量deadline,它可以引用Date类型对象。但它不是一个对象,因此不能将Date方法应用于这个变量:

s = deadline.toString(); // 错误!


一个对象变量没有实际包含一个对象,仅仅引用了一个对象:

deadline = birthday; // 两个变量引用同一个对象


Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new操作符的返回值也是一个引用。

可显式将对象变量设置为null,表示目前没有引用任何对象:

deadline = null;
...
if (deadline != null)
System.out.println(deadline);


如果将一个方法应用于一个值为null的对象上,将返回一个运行时错误:

birthday = null;
String s = birthday.toString(); // runtime error!


GregorianCalendar类

Date类的实例有一个状态,即特定的时间点。

GregorianCalendar类用来表示大家熟悉的日历表示法。

GregorianCalendar类扩展了一个更加通用的Calendar类,它描述了日历的一般属性。

Date类只提供了少量的方法用来比较两个时间点,如before和after:

if (today.before(birthday))
System.out.println("Still time to shop for a gift.");


GregorianCalendar类包含的方法比Date类要多得多,特别是有几个很有用的构造器。
new GreogrianCalendar()


构造一个新对象,用于表示对象构造时的日期和时间。

还可以通过提供年、月、日构造一个表示某个特定日期午夜的日历对象:

new GregorianClendar(1999, 11, 31) // 月份从0计数,11表示十二月
new GregorianCalendar(1999, Calendar.DECEMBER, 31)


还可以设置时间:

new GregorianCalendar(1999, Calendar.DECEMBER, 31, 23, 59, 59)


查询信息通常使用get方法。
查询GregorianCalendar类的信息时通常需要使用Calendar类中定义的一些常量:

GregorianCalendar now = new GregorianCalendar();
int month = now.get(Calendar.MONTH);
int weekday = now.get(Calendar.DAY_OF_WEEK);


调用set方法,可以改变对象的状态:
deadline.set(Calendar.YEAR, 2001);
deadline.set(Calendar.MONTH, Calendar.APRIL);
deadline.set(Calendar.DAY_OF_MONTH, 15);


可同时设置年、月、日:
deadline.set(2001, Calendar.APRIL, 5);


可以为给定的日期对象增加天数、星期数、月份等:
deadline.add(Calendar.MONTH, 3); // move deadline by 3 months


GregorianCalendar类有getTime方法和setTime方法,用来获得和设置日历的时间点:
Date time = calendar.getTime();
calendar.setTime(time);


这些方法使用在GregorianCalendar类和Date类之间的转换:

GregorianCalendar calendar = new GreogrianCalendar(year, month, day);
Date hireDay = calendar.getTime();

GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(hireDay);
int year = calendar.get(Calendar.YEAR);


示例:打印当前月的日历
import java.text.DateFormatSymbols;
import java.util.*;

public class CalendarTest
{
public static void main(String[] args)
{
// 设置地区(不同地区有着不同的日期表示习惯)
Locale.setDefault(Locale.US);

GregorianCalendar d = new GregorianCalendar();
int today = d.get(Calendar.DAY_OF_MONTH);
int month = d.get(Calendar.MONTH);
d.set(Calendar.DAY_OF_MONTH, 1);	// 设置d为当月第一天
int weekday = d.get(Calendar.DAY_OF_WEEK);

int firstDayOfWeek = d.getFirstDayOfWeek(); // 当前地区星期的起始日
int indent = 0;
while (weekday != firstDayOfWeek)
{
indent++;
d.add(Calendar.DAY_OF_MONTH, -1);
weekday = d.get(Calendar.DAY_OF_WEEK);
}

// 获取表示星期几个英文缩写
String[] weekdayNames = new DateFormatSymbols().getShortWeekdays();
do
{
System.out.printf("%4s", weekdayNames[weekday]);
d.add(Calendar.DAY_OF_MONTH, 1);
weekday = d.get(Calendar.DAY_OF_WEEK);
} while (weekday != firstDayOfWeek);
System.out.println();

for (int i = 1; i <= indent; i++)
System.out.print("    ");
d.set(Calendar.DAY_OF_MONTH, 1);
do
{
int day = d.get(Calendar.DAY_OF_MONTH);
System.out.printf("%3d", day);

if (day == today) System.out.print("*");
else System.out.print(" ");

d.add(Calendar.DAY_OF_MONTH, 1);
weekday = d.get(Calendar.DAY_OF_WEEK);

if (weekday == firstDayOfWeek) System.out.println();
} while (d.get(Calendar.MONTH) == month);

if (weekday != firstDayOfWeek) System.out.println();
}
}


用户自定义类

最简单的类定义形式:

class ClassName
{
constructor1
constructor2
...
method1
method2
...
field1
field2
...
}
下面是一个非常简单的Employee类:
class Employee
{
// constructor
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}

// a method
public String getName()
{
return name;
}

// more methods
...

// instance fields

private String name;
private double salary;
private Date hireDay;
}


多个源文件的使用

一个源文件可包含多个类,也可将每个类存放在单独的原文件中,如Employee类存放在Employee.java,EmployeeTest类存放在EmployeeTest.java。

这样组织文件,有两种编译方法:

1. 使用通配符:

javac Employee*.java


2. 只编译主源文件:

javac EmployeeTest.java
当编译器发现使用了Employee类时会查找Employee.class文件,若未找到,就会查找Employee.java,并对其进行编译。

与C++不同,Java中所有的方法都必须的类内定义。

Final实例域

可以将实例域定义为final。构建对象时必须初始化这样的域,并且在后面的操作中,不能够再对它进行修改。如:

class Employee
{
...
private final String name;
}
final修饰符大都应用于基本数据类型域,或不可变类的域。

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

private final Date hiredate;

仅仅意味着存储在hiredate变量中的对象引用在对象构造之后不能被改变,而并不意味着hiredate对象是一个常量。任何方法都可以对hiredate引用的对象调用setTime更改器。

静态域和静态方法

如果将域定义为static,每个类中只有一个这样的域。而每个对象对于所有的实例域却都有自己的一份拷贝。

假定需要给每一个雇员赋予一个惟一的标识码,添加一个实例域id和一个静态域nextId:

class Employee
{
...
private int id;
private static int nextId = 1; // 属于类,不属于任何对象
}
现在,每一个对象都有一个自己的id域,但这个类的所有实例将共享一个nextId域。

静态常量

public class Math
{
...
public static final double PI = 3.14159265358979323846;
...
}
在程序中,可以使用math.PI获得这个常量。

如果关键字static被省略,PI就变成了Math类的一个实例域。需要通过Math类的对象访问PI,并且每一个Math对象都有它自己的一份PI拷贝。

静态方法

静态方法是一种不能向对象实施操作的方法。如Math类的pow方法:

Math.pow(x, a)
计算x的a次方。运算时,不使用任何Math对象。

在下面两种情况下使用静态方法:

一个方法不需要访问对象状态,其所需参数都是通过显式参数提供。
一个方法只需要访问类的静态域。

Factory方法
静态方法还有一种常见的用途。NumberFormat类使用factory方法产生不同风格的格式对象。
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
double x = 0.1;
System.out.println(currencyFormatter.format(x)); // prints $0.10
System.out.println(percentFormatter.format(x)); // prints 10%
NumberFormat类不利用构造器完成这些操作的原因:

无法命名构造器。
当使用构造器时,无法改变所构造的对象类型。

Main方法
每一个类可以有一个main方法。这是一个常用于对类进行单元测试的技巧。

方法参数
方法参数共有两种类型:

基本数据类型(数字型和布尔型)
对象引用

Java总是采用“值调用”,特别的是,方法不能修改传递给它的任何参数变量的内容(修改的只是一个拷贝):
public static void tripleValue(double x) // doesn't work
{
x = 3 * x;
}


然而,方法可以修改一个对象引用的参数:
public static void tripleSalary(Employee x) // works
{
x.raiseSalary(200);
}
总结:

一个方法不能修改一个基本数据类型的参数(即数值型和布尔型)。
一个方法可以改变一个对象参数的状态。
一个方法不能实现让对象参数引用一个新的对象。

对象构造

重载:多个方法可以有相同的名字,不同的参数(返回类型不起作用)。

默认构造器:没有参数的构造器

public Employee()
{
name = "";
salary = 0;
hireDay = new Date();
}
一个类没有任何构造器,则系统会提供一个默认构造器,将所有实例域设置为默认值。

一个类若有至少一个构造器,但没有默认构造器,则系统不提供默认构造器,若构造对象时不提供构造参数则被视为不合法。

可以在类定义域中,直接将一个值赋给任何域。在执行构造器之前,先执行赋值操作。初始值不一定是常量,也可以调用方法:

class Employee
{
...
static int assignId()
{
int r = nextId;
nextId++;
return r;
}
...
private int id = assignId();
}
C++中,不能直接初始化实例域。

Java中,木有初始化列表。

参数名

三种风格:

// 参数用单个字母,编写小构造器时常用
public Employee(String n, double s)
{
name = n;
salary = s;
}

// 参数前加前缀“a”,这样更易阅读
public Employee(String aName, double aSalary)
{
name = aName;
salary = aSalary;
}

// 参数屏蔽实例域,使用this访问
public Employee(String name, double aSalary)
{
this.name = name;
this.salary = salary;
}


调用另一个构造器

public Employee(double s)
{
// calls Employee(String, double)
this("Employee #" + nextId, s);
nextId++;
}
当调用new Employee(60000)时,Employee(double)构造器将调用Employee(String, double)构造器。

采用这种方式使用this关键字非常有用,这样对公共的构造器代码部分只编写一次即可。

初始化块

在一个类的声明中,可以包含多个代码块。只要构造类的对象,这些块就会被执行:

class Employee
{
public Employee(String n, double s)
{
name = n;
salary = s;
}
public Employee()
{
name = "";
salary = 0;
}
...
private static int nextId;

private int id;
private String name;
private double salary;
...

// object initialization block
{
id = nextId;
nextId++;
}
}
该示例中,无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。首先运行初始化块,然后才运行构造器的主体部分。(这种机制不常用)

调用构造器的具体处理步骤:

所有数据域被初始化为默认值。
按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块。
如果构造器第一行调用了第二个构造器,则执行第二个构造器主体。
执行这个构造器的主体。

静态的初始化块

如果对类的静态域进行初始化的代码比较复杂,那么可使用静态的初始化块:

// static initialization block
static
{
Random generator = new Random();
nextId = generator.nextInt(10000);
}
在类第一次加载时,会进行静态域的初始化。所有静态初始化语句以及静态初始化块都将依照类定义的顺序执行。

对象析构与finalize方法

Java有自动的垃圾回收器,不需要人工回收内存。

某些对象使用了内存之外的其他资源,当资源不再需要时,需要则需将其回收。

可以为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象之前调用。

包(package)

Java允许使用包将类组织起来。借助包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。

使用包的主要原因是确保类名的惟一性。

类的导入

一个类可以使用所属包中的所有类,以及其他包中的公有类。

可以采用两种方式访问另一个包中的公有类。

1. 每个类名前添加完整的包名:

java.util.Date today = new java.util.Date();
2. 使用import语句导入一个特定的类或者整个包:
import java.util.*;
然后就可使用:
Date today = new Date();
C++中,与包机制类似的是命名空间(namespace),不是头文件(#include)。

静态导入

import不仅可以导入类,还可以导入静态方法和静态域:

import static java.lang.System.*;
import static java.lang.System.out;


静态导入不利于代码的清晰度。但是有两个实际的应用:

算术函数。
笨重的常量。

将类放入包中
要想将一个类放入包中,就必须将包的名字放在源文件开头,包中定义类的代码之前:
package com.horstmann.corejava;

public class Employee
{
...
}
若不加入package语句,这个源文件中的类就放在一个默认包(deafult package)中。
将包中的文件放到与完整的包名匹配的子目录中。如com.horstmann.corejava包中的所有源文件应该被放置在子目录com\horstmann\corejava中。

包作用域
标记为public的部分可以被任意的类使用,标记为private的部分只能被定义它们的类使用,没有指定public或private,这个部分可以被同一个包中的所有方法访问。(和C++不同)

类路径
为了使类能够被多个程序共享,需要做到下面几点:

把类放到一个目录中,如/home/user/classdir。
将JAR文件放在一个目录中,如/home/user/archives。
设置类路径(class path)。类路径是所有包含类文件的路径的集合。

类路径包括:

基目录/home/user/classdir或c:\classes。
当前目录(.)。
JAR文件/home/user/archives/archive.jar或c:\archives\archive.jar。
最好采用-calsspath(或-cp)选项指定类路径:
java -classpath /home/user/classdir:.:/home/user/archives/archive.jar MyProg.java
或者
java -classpath c:\classdir;.;c:\archives\archive.jar MyProg.java


文档注释
JDK包含一个叫javadoc的工具,它可以由源文件生成一个HTML文档。
javadoc实用程序(utility)从下面几个特性中抽取信息:


公有类与接口
公有的和受保护的方法
公有的和受保护的域

应该为上面几部分编写注释。注释应放在所描述特性的前面,注释以/**开始,并以*/结束。

每个/**...*/文档注释在标记之后紧跟着自由格式文本(free-form text)。标记由@开始,如@author或@param。
自由格式文本的第一句应是一个概要性句子。javadoc实用程序自动地将这些句子抽取出来形成概要页。
自由格式文本中,可以使用HTML修饰符。

类注释必须放在import语句之后,类定义之前。

方法注释必须放在所描述的方法之前。除了通用标记外,还可使用:
@param variable description
@return description
@throws class descrption

例:

/**
* Raises the salary of an employee.
*  @param byPercent the percentage by which to raise the salary(e.g. 10 = 10%)
*  @return the amount of the raise
*/
public double raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
return raise;
}


域注释。只需要对公有域(通常指的是静态常量)建立文档。

通用注释

@author name
@version text
@since text(始于条目)
@deprecated text(不再使用注释)
@see reference(在“see also”部分增加一个超级链接),reference可以选择下列情形之一

package.class#feature label

<a href="...">label<a>

"text"

第一种最常见,如:

@see com.horstmann.corejava.Employee#raiseSalary(double)
建立一个链接到com.horstmann,corejava,Employee类的raiseSalary(double)方法的超链接。

如果愿意的话,可以在注释中的任何位置放置指向其他类或方法的超链接,以及插入一个专用标记,如:

{@link package.class#feature label}

包与概述注释

要想产生包注释,就需要在每一个包目录中添加一个单独的文件,可以有如下两种选择:

提供一个以package.html命名的HTML文件。在标记<BODY>...</BODY>之间的所有文本都会被抽取出来。
提供一个以package-info.java命名的Java文件。这个文件必须包含一个初始的以/**和*/界定的Javadoc注释,跟随在一个包语句之后。它不应该包含更多的代码或注释。

还可以为所有的源文件提供一个概述性的注释。这个注释将被放置在一个名为overview.html的文件中。这个文件位于包含所有源文件的父目录中。标记<BODY>...</BODY>之间的所有文本将被抽取出来。

注释的抽取
假设HTML文件将被存放在目录docDirectory下,执行以下步骤:
1. 切换到包含想要生成文档的源文件目录。
2. 如果是一个包,运行命令:
javadoc -d docDirectory nameOfPackage


或对于多个包生成文档,运行:
javadoc -d docDirectory nameOfPackage1 nameOfPackage2...
如果文件在默认包中,应运行:
javadoc -d docDirectory *.java


类设计技巧

一定要将数据设计为私有
一定要对数据初始化
不要在类中使用过多的基本数据类型
不是所有的域都需要独立的域访问器或域更改器
使用标准格式进行类的定义
将职责过多的类进行分解
类名和方法名要能够体现它们的职责

一般采用如下顺序书写类的内容:

公有访问特性部分
包作用域访问特性部分
私有访问特性部分

每一部分中,应按照下列顺序列出:

实例方法
静态方法
实例域
静态域
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: