一,对象与对象变量
在对象与对象变量之间存在一个重要的区别,例如:
public class TextClass {
public static void main(String[] args) {
//声明一个对象变量
Date deadline;
//创建一个对象初始化上面的对象变量
deadline = new Date();
}
}
上面首先声明了一个对象变量deadLine,它可以引用Date类型的对象。但是,变量deadLine不是一个对象,也没有引用任何对象。此时,不能将任何Date方法应用于这个变量上。如果想调用Date的方法,必须先初始化变量deadLine。
注释:java中的对象变量并没有包含一个对象,而仅仅引用一个对象。在java中任何对象变量的值都是存储在另外一个地方的一个对象的引用,new操作符返回的也是一个引用。
注释:所有的java对象都存储在堆中。
C++注释:很多人错误的认为java对象变量与C++中的引用类似。然而,C++中没有空引用,而且引用不能被赋值。可以将java对象变量看做C++的对象指针。
二,定义一个类
2.1,构造器
构造器与类同名,在创建类对象时,构造器被自定调用,来对实例域进行初始化。下面是自定义一个Student类,如下:
class Student{
private String name;
private String id;
public Student(String name, String id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
使用构造函数需要注意下面几点:
- 构造器与类同名。
- 每个类可以有一个以上的构造器。
- 构造器没有返回值。
- 构造器总是伴随new一起调用。
C++注释:java构造器的工作方式与C++构造器一样,但是,所有的java对象都是在堆中创建的,构造器总是伴随new运算符一起使用。C++程序员容易犯的错误就是忘记new运算符,如下:
Student zhangsan("zhangsan", "151050010");
上面的语句在C++中可以正常运行,但是在java中不行。
2.2,隐式参数与显式参数
方法用来操作对象以及存取它们的实例域,如下:
public class TextClass {
public static void main(String[] args) {
Student zhangsan = new Student("zhangsan", "151050010");
System.out.println("name: "+zhangsan.getName());
}
}
上面的getName方法有两个参数,第一个参数被称为隐式参数,就是方法前面的类对象,第二个参数是显式参数,就是方法名后面括号中的数值。即在每一个方法中,关键字this表示隐式参数。
C++注释:在C++中,通常是在类的外面定义方法,在java中,所有的方法都必须在类的内部定义。在类的内部定义的方法,并不代表它就是内联方法。是否将某个方法设置为内联方法是虚拟机的任务。即编译器会监视调用那些简洁、经常被调用、没有被重载以及可优化的方法。
2.3,静态域与静态方法
2.3.1,静态域
如果将域定义为static,那么这个域是属于类的,即所有的类对象都共享这个域,例如:
public class TextClass {
//声明一个静态域
private static int id = 1;
public static void main(String[] args) {
}
}
2.3.2,静态常量
静态变量使用的比较少,但是静态常量使用的比较多,例如:在Math类中定义了一个静态常量PI
public class Math{
...
public static double PI = 3.1415926;
...
}
2.3.3,静态方法
静态方法是不能对对象实施操作的方法,主要是因为静态方法中没有隐式参数。因为静态方法不能操作对象,所以不能在静态方法中访问实例域。
class Student{
private String name;
private String id;
public Student(String name, String id) {
this.name = name;
this.id = id;
}
//定义一个静态方法
public static void sayHello() {
System.out.println("hello world!");
}
}
在下面两种情况下使用静态方法:
- 一个方法不需要访问对象状态,其所有的参数都是显示传递的。
- 一个方法只需要访问类的静态域。
2.3.4,main方法
main方法不对任何对象进行操作。事实上,在程序刚开始启动时没有任何一个对象。静态的main方法将执行并创建程序所需要的对象,例如:
public class TextClass {
public static void main(String[] args) {
}
}
提示:每一个类都可以有一个main方法,这是一个常用的对类进行单元测试的技巧。
三,方法参数
java总是采用值调用,方法得到的是所有参数的一个拷贝。
3.1,基本数据类型
java中的方法不能修改一个基本数据类型的参数,例如:
public class TextClass {
public static void change(int x) {
x = x * 10;
}
public static void main(String[] args) {
int a = 20;
change(a);
System.out.println("a = " + a);
}
}
输出结果:
a = 20
3.2,对象引用
java中的方法得到的是对象引用的拷贝,因此,可以通过参数改变对象的参数,例如:
public class TextClass {
public static void change(Student obj) {
obj.setName("lisi");
}
public static void main(String[] args) {
Student zhangsan = new Student("zhangsan", "151050010");
change(zhangsan);
System.out.println("name = " + zhangsan.getName());
}
}
输出结果:
name = lisi
java对对象采用的不是引用传递,而是值传递。有些程序员认为java对对象采用的是引用传递,实际上,这种理解是不正确的。下面给出一个反例来阐述上面的问题,例如:
public class TextClass {
public static void change(Student a, Student b) {
Student temp = a;
a = b;
b = temp;
}
public static void main(String[] args) {
Student zhangsan = new Student("zhangsan", "151050010");
Student lisi = new Student("lisi", "151050011");
change(zhangsan, lisi);
System.out.println("name = " + zhangsan.getName());
}
}
输出结果:
name = zhangsan
程序分析:
如果java中对象传递采用的是引用传递,在上面的例子中a与b分别是,zhangsan与lisi的引用。在swap方法中,交换a与b的值,此时变量zhangsan与lisi保存的值也会被交换。但是,从输出结果可以看出,变量zhangsan与lisi的值并没有发生变化。说明,java中对象采用的是值传递,a与b分别保存的是变量zhangsan与lisi的值的拷贝。
四,对象构造
4.1,默认域初始化
如果在构造器中没有显示的给域赋予初值,那么就会被自动地赋为默认值:数值为0、布尔值为false、对象引用为null。然而,只有缺少经验的程序员才会这么做。如果不明确的对域进行初始化,就会影响程序的可读性。
注释:这是域与局部变量的主要不同。局部变量必须明确初始化,但是,如果没有初始化类中的域,将会被初始化为默认值。
4.2,默认构造器
默认构造器就是没有参数的构造器,例如:
class Student{
private String name;
private String id;
//默认构造器
public Student() {
name = "";
id = "";
}
public Student(String name, String id) {
this.name = name;
this.id = id;
}
}
注释:仅当类没有提供任何构造器时,系统才会提供一个默认的构造器。如果在定义类时,有提供构造器,此时系统不会生成默认的构造器。
4.3,显示域初始化
可以在类定义中,直接将一个值赋予任何域,例如:
class Student{
private String name = "";
private String id = "";
...
}
在构造器执行之前先执行赋值操作,当一个类的所有构造器,都希望把相同的值赋予某个类实域,这个方法特别有用。
C++注释:在C++中,不能直接初始化实例域,所有的域必须在构造器中初始化。但是,C++有一个特殊的初始化列表。
4.4,调用另一个构造器
如果构造器的第一个语句形如this(),这个构造器将调用同一类的另一个构造器,例如:
class Student{
private String name = "default";
private String id = "";
//默认构造器
public Student() {
name = "";
id = "";
}
public Student(String name, String id) {
//调用默认的构造器
this();
this.name = name;
this.id = id;
}
...
}
C++注释:在java中,this引用相当于C++中的this指针。但是,在C++中,一个构造器不能调用另一个构造器。在C++中,必须将抽取出的公共代码编写成一个独立的方法。
4.5,实例初始化块
在java中可以初始化块,对实例域进行初始化。在一个类的声明中可以包含多个代码块。只要构造类对象,这些块就会被执行。例如:
class Student{
private String name;
private String id;
public Student(String name, String id) {
this.name = name;
this.id = id;
}
//实例域初始化块
{
id = "";
name = "";
}
}
在上面的例子中,无论使用哪个构造器创建类对象,id与name都在初始化块中被初始化。首先,运行初始化块,然后运行构造器的主体部分。这种机制不是必须的,也不常见。通常,直接把初始化代码放在构造器中。
注释:下面是调用构造器的具体处理步骤:
- 所有的数据域被初始化为默认值。
- 按照在类中声明的顺序,依次执行所有的域初始化语句与初始化块。
- 如果构造器第一行调用了第二个构造器,则执行第二个构造器的主体。
- 执行这个构造器的主体。
4.6,静态域初始化块
可以使用静态初始化块,对类中的静态域进行初始化,注意要在初始化块的上面加上static关键字,例如:
class Student{
private String name;
private String id;
private static int age;
private static double grade;
//静态域初始化块
static
{
age = 20;
grade = 100;
}
...
}
在类被第一次加载时,将会进行静态域的初始化。与实例域一样,除非将他们设置为其它的值,否则默认的初始值是0、false、null。所有的静态初始化语句以及静态初始化块都将依照类定义的顺序执行。
五,对象析构
在C++中有显示的析构器,在析构器中,最常见的操作是回收分配给对象的内存空间。由于java有自动的垃圾回收器,不需要人工的回收内存,所以java不支持析构器。
当然,如果某些对象使用了内存之外的其他资源,例如:文件句柄。在这种情况下,当资源不在需要时,将其回收再利用显得非常的有必要。
可以为任何一个类添加finalize方法。finalize方法将在内存回收器回收对象占用的内存之前调用。但是,在实际的使用中,不要依赖finali方法来释放一些稀缺资源,因为很难知道这个方法何时会被调用。