类
构造器
一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。在Java 中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new 操作符的返回值也是一个引用。
构造器产生机制
很多人错误地认为Java 对象变量与C++ 的引用类似。然而,在C++中没有空引用, 并且引用不能被赋值。可以将Java 的对象变量看作C++ 的对象指针。例如,1
Date birthday; // Java
实际上, 等同于
1
Date* birthday; // C++
一旦理解了这一点, 一切问题就迎刃而解了。当然, 一个Date* 指针只能通过调用new进行初始化。就这一点而言,c++与Java 的语法几乎是一样的。
1
Date* birthday = new Date() ; // C++
如果把一个变量的值賦给另一个变量, 两个变量就指向同一个日期, 即它们是同一个对象的指针。在 Java 中的null 引用对应 C++ 中的 NULL 指针。所有的 Java 对象都存储在堆中。当一个对象包含另一个对象变量时, 这个变量依然包含着指向另一个堆对象的指针。
在 C++ 中,指针十分令人头疼,并常常导致程序错误。稍不小心就会创建一个错误的指针,或者造成内存溢出。在 Java 语言中, 这些问题都不复存在。如果使用一个没有初始化的指针,运行系统将会产生一个运行时错误,而不是生成一个随机的结果,同时,不必担心内存管理问题,垃圾收集器将会处理相关的事宜。
C++ 确实做了很大的努力,它通过拷贝型构造器和复制操作符来实现对象的自动拷贝。例如,一个链表( linked list ) 拷贝的结果将会得到一个新链表,其内容与原始链表相同, 但却是一组独立的链接。这使得将同样的拷贝行为内置在类中成为可能。在 Java 中, 必须使用 clone 方法获得对象的完整拷贝。使用静态工厂方法来代替构造器
这个技巧或者说注意事项在effective java中的第一章第一节就强调过,他有很多便捷之处,其中一个重要的就是可以根据产生的具体对象的特征来对方法命名,而构造器只能有一种名字(即类的名字)访问器和更改器
访问器:只访问对象元素,不会对对象本身进行修改
修改器:会对对象本身进行修改
在C++ 中,带有 const 后缀的方法是访问器方法;默认为更改器方法。但是,在Java 语言中,访问器方法与更改器方法在语法上没有明显的区别,
自定义的类
构造器使用注意事项
Java 构造器的工作方式与 C++ 一样。但是,要记住所有Java 对象都是在堆中构造的,构造器总是伴随着 new 操作符一起使用。
请注意,不要在构造器中定义与实例域重名的局部变量。例如,下面的构造器将无法设置 salary 。1
2
3
4
5
6public Employee(St ring n, double s, . .
{
String name = n; // Error
double salary = s; // Error
...
}this与隐式参数
在每一个方法中, 关键字 this 表示隐式参数。
C++ 注释: 在 C++ 中, 通常在类的外面定义方法:1
2
3
4void Employee::raiseSalary(double byPercent) // C++, not Java
{
...
}如果在类的内部定义方法,这个方法将自动地成为内联(inline)方法
1
2
3
4
5class Employee
{
int getNameQ{
return name;
} // inline in C++在 Java 中,所有的方法都必须在类的内部定义,但并不表示它们是内联方法。是否将某个方法设置为内联方法是Java 虚拟机的任务。即时编译器会监视调用那些简洁、经常被调用、没有被重载以及可优化的方法。
静态域与静态方法
静态域
如果将域定义为 static , 每个类中只有一个这样的域。而每一个对象对于所有的实例域却都有自己的一份拷贝。静态方法
静态方法是一种不能向对象实施操作的方法。
静态方法不能有实例域, 因为它不能操作对象。但是,静态方法可以访问自身类中的静态域。
在下面两种情况下使用静态方法:- 一方法不需要访问对象状态,其所需参数都是通过显式参数提供(例如:Math.pow)
- 一个方法只需要访问类的静态域(例如:Employee.getNextld)
静态工厂方法
静态工厂的功能和构造器基本一致,都是产生一个对象实例。
为什么这些类不利用构造器完成这些操作呢?这主要有两个原因:- 无法命名构造器。构造器的名字必须与类名相同。但是,这里希望将得到的不同特征的实例采用不用的名字
- 当使用构造器时,无法改变所构造的对象类型。只能是自己类型,不能是其子类
父类的静态方法能不能被重写?(2019-5-14 更新)
答案是不能。因为静态方法从程序开始运行后就已经分配了内存,也就是说已经写死了。所有引用到该方法的对象(父类的对象也好子类的对象也好)所指向的都是同一块内存中的数据,也就是该静态方法。子类中如果定义了相同名称的静态方法,并不会重写,而应该是在内存中又分配了一块给子类的静态方法,没有重写这一说。
方法参数
Java 程序设计语言总是采用按值调用
也就是说,方法得到的是所有参数值的一个拷贝特别是,方法不能修改传递给它的任何参数变量的内容。 在这里对象是个特殊情况,可以改变传入对象中成员变量的值,但是基本类型例如int、boolean并不能改变(不会报错,但跳出方法外值不变,因为变得是拷贝,而不是其本身)然而,虽然对象是特殊情况,但对象仍然是按值调用,不是引用调用;
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
对象与其他
对象构造
调用另一个构造器
如果构造器的第一个语句形如this(…) ,这个构造器将调用同一个类的另一个构造器。下面是一个典型的例子:1
2
3
4
5public Employee(double s){
// calls Employee(String, double)
this("Employee #" + nextld, s);
nextld++;
}当调用 new Employee(60000) 时,Employee(double) 构造器将调用 Employee(String ,double) 构造器。采用这种方式使用 this 关键字非常有用,这样对公共的构造器代码部分只编写一次即可。
对象析构与 finalize 方法
在析构器中,最常见的操作是回收分配给对象的存储空间。由于 Java 有自动的垃圾回收器,不需要人工回收内存,所以 Java 不支持析构器。
包
- 包的导入
在C++中,与包机制类似的是命名空间(namespace) 。在Java 中,package 与 import 语句类似于 C++ 中的 namespace 和 using 指令。
类的设计技巧
- 一定要保证数据私有
- 一定要对数据初始化
- 不要在类中使用过多的基本类型
- 不是所有的域都需要独立的域访问器和域更改器
- 将职责过多的类进行分解
- 类名和方法名要能够体现它们的职责