上一节回归了如何以面向对象的思想去使用一些Java中的公共类,也设计了一些自己的类并介绍了设计类的基本方法和技巧,这一节我们将继续回顾这些内容,并争取从中获得新的体验和感受。

1. 静态域与静态方法

前面我们经常看到,main方法都被标记为static,我们现在就要讨论一下这个static的含义和内容。

1.1 静态域

如果将域定义为static,每个类中只有一个这样的域,而每个对象对于实例域来说都有一份自己的拷贝。比如下面这个例子:

class Employee {
...
private int id;
pirvate static int nextId = 1;
}

如果有1000个Employee对象,就有1000个实例域id。但只有一个nextId。即使没有Employee对象,nextId也存在。nextId属于类,id属于对象。下面有一个使用静态域的例子:

public void setId() {
id = nextId;
nextId++;
} // 嘉定为harry设定雇员标志码
harry.setId(); // 这个功能类似于自动编码,每一个新的雇员的id会被设置为1,2,3...

1.2 静态常量

静态的变量在程序设计中使用的还是比较少的(上一节我本来不想用书本上的例子,但是想了半天也没想到更好的。。。),但静态常量的使用还是很多的。例如我们之前提到过的Math类,定义了静态常量:

在程序中,我们可以采用Math.PI,Math.E来获得这个形式的常量。如果没有static这个关键字,我们需要通过Math类的对象来访问PI,而且每一个Math都有一个PI的拷贝。

之前我们说过,每个类对象都可以修改公有域,所以最好不要将域设计为public。然而final却没有问题,final不允许覆盖为其他的值和对象。

在System类中,有一个setOut方法,可以将System.out设置为不同的流。虽然out设置为了final,但是却可以修改是为什么呢?原因是setOut是本地方法(native method),不是用Java编写的,因此可以绕过Java的存取控制机制。我们平时编写程序不应当这样处理。

1.3 静态方法

静态方法是一种不能像对象实时操作的方法。例如Math类的pow方法就是一个静态方法。运算时,不能够使用任何Math对象,所以没有隐式的参数。可以认为静态方法是没有this参数的方法。所以静态方法不能访问实例域,却可以访问自身类中的静态域。

可以使用对象调用静态方法,不过容易造成混淆,因为静态方法属于类而不是对象,如果通过类操作会让人产生混淆,和这个对象毫无关系。

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

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

1.4 Factory方法

静态方法还有一种常见的用法,NumberFormat类采用factory方法产生不同风格的格式对象。

NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
double x = 0.1;
System.out.println(currencyFormatter.format(x)); // ¥0.10
System.out.println(percentFormatter.format(x)); // 10%

不采用不同的构造器来完成这些操作的原因主要有两个:

  • 无法为构造器命名
  • 使用构造器时,无法改变构造对象的类型

2. 方法参数

通常的成语语言在参数的传递时总是分为值传递引用传递。值传递表示方法接收的是调用者提供的值;引用传递结构的是调用者提供的变量地址。Java语言总是采用值传递,也就是说,方法得到的是所有参数值的一个拷贝,方法不能修改传递给它的任何参数变量的内容。

public static void main(String[] args) {
int a = 1;
System.out.println(a); // 输出的是1
tripleValue(a);
System.out.println(a); // 输出的还是1
} public static void tripleValue(int x) {
x = x * 3;
}

可以看到,调用这个方法之后,a的值仍然不变,具体的执行过程如下:

  1. x被初始化为a值的一个拷贝,也就是1
  2. x被乘以3后等于3,但a仍是1
  3. 方法执行结束,x消失

然而,方法参数总共有两种类型:基本数据类型;对象引用。因此,方法不能修改基本数据类型,而对象引用作为参数可以通过方法进行修改。

因此,很多人认为Java对对象采用的是引用传递,实际上这种理解是不对的。比如下面的这个程序:

public static void main(String[] args) {
Employee x = new Employee("xxx", 10, 1, 1, 1);
Employee y = new Employee("yyy", 10, 1, 1, 1);
swap(x, y);
System.out.println(x.getName());
System.out.println(y.getName());
} public static void swap(Employee x, Employee y) {
Employee temp = x;
x = y;
x = temp;
}

结束之后,x的名字还是xxx,y的还是yyy。如果Java采用的是引用传递,他们的名字应该交换才对,所以有此可以看出,Java采用的是值传递

那么为什么我们传递一个对象之后,确实可以通过方法对对象进行修改呢?因为方法可以接收对象引用,将对象的引用作为一个值拷贝给了方法的变量x,x这是引用这个对象,因此可以对对象的实例域进行操作。而上述例子我们可以分析,swap方法的x拿到了x对象的引用,y同理,这时候,我们对x,y进行了交换,如今,x指向了y对象的引用,y指向了x的引用,但是本身Employee对象x,y的引用并没有变,因此不会交换他们自身的引用,导致出现上述结果。

因此我们总结一下在Java中,方法参数的使用情况:

  • 一个方法不能修改一个基本数据类型的参数
  • 一个方法可以改变一个对象参数的状态
  • 一个方法不能实现让对象参数引用一个新的对象

3. 对象构造

前面我们已经简单的看了构造器的编写,但由于构造器很重要,所以Java提供了多种编写方式。

3.1 重载

GregorianCalendar today = new GregorianCalendar ();
GregorianCalendar deadline = new GregorianCalendar(2099, 9, 31);

这种特性叫做重载(overloading)。如果多个方法,有相同的名字不同的参数,便产生了重载。编译器自动通过参数列表来匹配具体执行那个方法。如果找不到合适的参数或者多个匹配项,就会产生编译错误(重载解析)。Java允许重载任何方法,因此,要完整的描述一个方法,需要方法名参数类型,这叫做方法的签名。由于返回值不属于签名的一部分,也就是说,不能有两个名字相同、参数类型也相同,却返回不同类型的方法。

3.2 默认域初始化

如果在构造其中,没有显示地给域赋予初值,那么就会被自动的设置为默认值:数值为0、布尔为false、对象引用为null。然而,如果不显示的对域进行初始化,会影响程序的可读性。比如我们之前的Employee对象,如果没有初始化Date对象,则引用为null,这时调用hireDay可能会产生意想不到的错误。同时,域不仅可以通过常量赋值,还可以通过方法赋值。

3.3 默认构造器

如果在编写一个类的时候,没有编写构造器,那么系统会提供一个默认的构造器。这个默认的构造器将所有的实例域设置为默认值。但如果提供了自己编写的构造器,那么系统将不会提供默认的构造器。

3.4 调用另一个构造器

关键字this引用方法的隐式参数。如果构造器的第一个语句形如this(...),那么这个构造器要调用另一个构造器。

public Employee(double x) {
this("Employee #" + nextId, x)
nextId++;
}

当调用new Employee(60000)是,Employee(double)构造器将调用Employee(String, double)构造器。这样对公共构造器代码部分只用编写一次就行。

3.5 初始化块

初始化数据域的方法前面已经介绍了两种:在构造器中设置值,在声明中赋值。其实Java还可以通过初始化块来初始化数据域。

由于初始化数据域的途径多,所以可能出现混乱,下面列出了调用构造器的具体步骤:

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

如果对类的静态域进行初始化比较复杂,可以使用静态的初始化块

public class Hello {
static { // 这个static关键字表示这个类加载时调用,没有这个的话表示初始化一个类成员的时候调用
System.out.println("Hello, World!");
}
}

3.6 对象析构与finalize方法

Java有自动的垃圾回收器(GC),不需要人工回收内存,所以Java不支持析构器。当然,某些对象使用了系统之外的资源,如文件,数据库连接等,这时候当资源不再需要时,将其回首就很重要。

可以为任何类添加finalize方法,将在垃圾回收器清除对象之前调用。实际使用中最好不要使用这个方法,因为我们并不清楚这个方法的具体调用时间。

4. 包

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

使用包的主要原因是确保类名的唯一性。因此,Sun公司建议将公司的因特网域名以逆序的形式作为包名,并对不同的项目使用不同的子包。

4.1 类的导入

一个类可以使用所属包中的所有类,以及其他包中的公有类。我们可以采用两种方式访问另一个包中的公有类。

  1. 在每个类名之前添加完整的包名:java.util.Date today = new java.utill.Date();
  2. 通过import语句导入,import java.util.*; Date today = new Date();  还可以导入特定类:import java.util.Date;

通常我们应当将import语句写的尽可能详细,会使读者更加准确的知道加载了哪些类。而且使用*号只能导入一个包,而不能使用import java.*;导入多个以java为前缀的所有包。

有时候会发生命名冲突,比如java.util和java.sql包都有Date类,如果导入这两个包,会发生编译错误,编译器不知道使用哪个Date对象。这时可以采用增加特定import语句的方法来解决这个问题,如:java.util.Date;这样当编译器检测到Date对象时,会自动使用util包下的Date对象。或者当都需要使用的时候,在每个类之前加上完整的包名。

4.2 静态导入

从Jave SE 5.0开始,import不仅可以导入类,还增加了导入静态方法和静态域的功能。

例如:import static java.lang.System.*; 就可以使用System类的静态方法和静态域,而不必加类名:out.println();

实际上,这种编写不利于代码的清晰度,但是对于如Math类,使用起来却更加自然。

4.3  将类放入包中

要将一个类放入包中,就必须将包的名字放在源文件的开头。

package com.three

public class Test {
...
}

如果没有这个package语句,这个源文件就被放在一个默认包(default-package)中。包名和文件名是对应的,如com.three包中的文件在项目子目录com/three中。

4.4 包作用域

前面我们使用过public,private作为访问修饰符,如果都不加的话,就说明,可以被同一个包中的所有方法访问。

在默认的情况下,包不是一个封闭的尸体,也就是说,任何人都可以向包添加更多的类。在Java的早期版本中,通过package java.awit;可以轻松的将自己的类加入系统包中。借此访问java.awt中的资源了。从之后的版本起,JDK实现了类加载器,禁止了以“java.”开头的类。当然,用户自定义的类无法收益,但是可以通过包密封,解决问题。

类文件也可以存储在JAR(Java归档)文件中。在一个JAR中,可以包含多个压缩形式的类文件和子目录,既节约又可以改善性能。在程序中使用第三方的库文件时,通常会给出一个或多个JAR文件。

5. 文档注释

JDK有一个很有用的工具,叫做javadoc。它可以由源文件生成一个HTML文档。实际上,Java本身的API文档就是靠这个javadoc工具生成的。

在源代码中,通过以专用的定界符/**开始的注释,可以生成一个看上去很专业的文档。这种方式可以将代码与注释保存在一个地方。如果文档存入独立的文件,可能随着时间的推移,出现代码和注释不一致的问题。而是用了文档注释,只需要通过javadoc就可以很轻松的解决问题。

5.1 注释的插入

javadoc程序,从下面几个特性中抽取信息:

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

应该为上面及部分编写注释。注释应该放置在所描述特性的前面。注释以/**开始,并以*/结束。每个/**..*/文档注释在标记之后紧跟着自由格式文本,标记由@开始,如@author。自由格式文本的第一句应该是概要性的句子。javadoc实用程序自动地将这些句子抽取出来形成概要页。在自由格式文本中,额可以使用HTML修饰符。但是不要使用<h1>或<hr>,会与文档格式产生冲突。

如果文档中用到其他文件的连接,就该将这些文件放到子目录doc-files中。例如<img src="doc-files/uml.png" alt="UML diagram" >。

5.2 类注释

类注释必须放在import豫剧之后,类定义之前。例如:

/**
* The <code>System</code> class contains several useful class fields
* and methods. It cannot be instantiated.
*
* <p>Among the facilities provided by the <code>System</code> class
* are standard input, standard output, and error output streams;
* access to externally defined properties and environment
* variables; a means of loading files and libraries; and a utility
* method for quickly copying a portion of an array.
*
* @author unascribed
* @since JDK1.0
*/
public final class System {
......
}

并没有规定每一行必须用*开始,但是很多IDE提供了自动添加*的功能。

5.3 方法注释

每一个方法注释必须放在所描述的方法之前。除了通用标记之外,还可以使用以下标记:

  • @param variable description 这个标记将对当前方法的参数部分添加一个条目。这个描述可以占据多行,并使用HTML标签。一个方法所有的@param必须放在一起
  • @return description 这个标记对当前方法添加返回部分,这个描述可以跨越多行,并可以使用HTML标记
  • @throws class description 这个标记添加一个注释,表示可能抛出的异常
  /**
* Returns the unique {@link java.io.Console Console} object associated
* with the current Java virtual machine, if any.
*
* @return The system console, if any, otherwise <tt>null</tt>.
*
* @since 1.6
*/
public static Console console() {
if (cons == null) {
synchronized (System.class) {
cons = sun.misc.SharedSecrets.getJavaIOAccess().console();
}
}
return cons;
}

5.4 域注释

只需要对公有域建立文档,如:

/* The security manager for the system.
*/
private static volatile SecurityManager security = null;

5.5 通用注释

下面的标记可以用在类文档的注释中:

  • @author name 产生一个作者条目。可以使用多个@author标记,每个标记对应一个作者
  • @version text 产生一个版本条目,对应当前版本的任何描述

虾米那的标记可以用于所有的文档注释:

  • @since text 产生一个始于目录,这里的text可以是对引入特性的版本描述
  • @deprecagted text 对应类,方法或者变量添加一个不再使用的注释

5.6 包与概述注释

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

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

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

6. 类设计技巧

《Java核心技术》这本书列出了一些简单的技巧,是的设计出来的类更具有OOP的专业水准。

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

上述七条大部分还是很容易理解的,主要说一下3和5。

3:比如一个类,是People类,他有一些记录家庭地址的信息,如street,city,state等。这些数据,都是String型,我们应当做的是设计一个Address类,替换这些实例域。这样很容易对地址的变化进行处理,还可以增加国际化的处理。

5:书上提供了这样的一个顺序:

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

在每一部分中,应该按照下面的顺序列出:

  • 实例方法
  • 静态方法
  • 实例域
  • 静态域

作者认为,类的使用者对公有借口要比对私有接口实现的细节更感兴趣,并且对方法比数据更感兴趣。Sun公司建议先书写域,再书写方法。具体哪一种好并无定论,但是重要的是要保持一致性,使得别人接手的时候很容易的适应,当然,如果公司有要求,则是按公司的来。

重新学习Java——对象和类(二)的更多相关文章

  1. 重新学习Java——对象和类(一)

    之前通过记笔记的方法,对于<Java核心技术>这本书的前一章进行了重新的复习,感觉效果很好,比单独看书带来了更好的复习效果,了解了很多以前不是很注意的一些细节,但是在一些自己较为熟悉的地方 ...

  2. <java基础学习>JAVA 对象和类

    Java is an Object-Oriented Language. As a language that has the Object Oriented feature, Java suppor ...

  3. JavaSE基础(十二)--Java 对象和类

    Java 对象和类 Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 本节我们重点研究对象和类的概念. 对象:对象是类的一个实例(对象不是找个女朋友 ...

  4. Java-Runoob:Java 对象和类

    ylbtech-Java-Runoob:Java 对象和类 1.返回顶部 1. Java 对象和类 Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 ...

  5. (五)Java 对象和类

    Java 对象和类 Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 消息解析 本节我们重点研究对象和类的概念. 对象:对象是类的一个实例,有状态和行为. ...

  6. Java 教程 (Java 对象和类)

    Java 对象和类 Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 本节我们重点研究对象和类的概念. 对象:对象是类的一个实例(对象不是找个女朋友 ...

  7. Java的URL类(二)

    转:https://www.cnblogs.com/brokencolor/p/8575440.html Java的URL类(二) 实例: Java 通过HttpURLConnection Post方 ...

  8. 深入学习Java对象创建的过程:类的初始化与实例化

    在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完 ...

  9. Java 学习:对象和类

    对象和类 从认识的角度考虑是先有对象后有类.对象,是具体的事物.类,是抽象的,是对对象的抽象. 从代码运行角度考虑是先有类后又对象.类是对象的模板. 对象:对象是类的一个实例,有状态和行为. 类:类是 ...

随机推荐

  1. HashSet源码分析2

    package com.test1; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public ...

  2. Errors running builder 'JavaScript Validator' on

    eclipse编译提示Errors running builder 'JavaScript Validator' on 解决方法见下图 去掉 'JavaScript Validator' 即可

  3. Java:删除某文件夹下的所有文件

    import java.io.File;public class Test{ public static void main(String args[]){ Test t = new Test(); ...

  4. IE插件

    在OA上要直接查看word等公告文件,就必须安装office控件.要安装office控件,需要在IE浏览器中做相应的设置.如何设置呢,下面由小编具体介绍下. 工具/原料   OA IE浏览器 方法/步 ...

  5. firemonkey获取当前文件所在路径的方法

    在之前,我们知道有三种方法: ExtractFilePath(ParamStr(0)) ExtractFilePath(Application.ExeName) GetCurrentDir + '\' ...

  6. python操作mysql方法和常见问题

    http://www.cnblogs.com/ma6174/archive/2013/02/21/2920126.html 安装mysql模块 sudo easy_install mysql-pyth ...

  7. C++入门学习——模板

    为什么须要模板? 我们已经学过重载(Overloading),对重载函数而言,C++ 通过函数參数(參数个数.參数类型)的正确匹配来调用重载函数.比如.为求两个数的最大值,我们定义 max () 函数 ...

  8. 重构——Martin Fowler 阅读笔记

    重构的第一步: 为即将修改的代码建立一组可靠的测试环境. 和任何重构手法一样,当提炼一个函数时,我们必须知道可能出什么错. 安全步骤: 首先在一个函数内找到局部变量和参数.任何不会被修改的变量都可以被 ...

  9. 【bzoj1034】[ZJOI2008]泡泡堂BNB

    贪心 将双方的选手均按从强到弱排序,然后第一次扫描尽可能用当前剩下的选手中能赢对手当前最强选手中最弱的一个去赢得胜利,若无法做到,则暂时不考虑给对方最强的选手匹配对手.第二遍扫描使用同样策略去获取尽量 ...

  10. Python extensions for Windows

    Python extensions for Windows pywin32 214 Python extensions for Windows Maintainer: Mark Hammond Hom ...