Java中的对象与类
一、面向对象概述
面向对象程序是由对象组成,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。对于一些规模较小的问题,将其分解为过程的开发方式比较理想。而面向对象更加适用于解决规模较大的问题。要想实现一个简单的web浏览器可能需要大约2000个过程,这些过程可能需要对一组全局对象进行操作。采用面向对象的设计风格,可能只需要大约100个类,每个类平均包含20个方法。后者更容易掌握,也容易找到bug。
类是构造对象的模板。由类构造对象的过程称为创建类的实例。封装继承多态是面向对象编程的三个特性。
类之间的关系主要有依赖,聚合,继承。
- 如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另一个类
- 聚合关系意味着类A的对象包含类B的对象。
- 继承是一种用户表示特殊与一般关系的。
二、使用预定义类
在Java中没有类就无法做任何事情,然而,不是所有的类都具有面向对象特征。
声明一个对象变量,只有经过初始化,才能让他指向一个对象,也就是有了这个对象的引用。一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。
局部变量不会自动初始化为null,必须通过调用new或者将他们设置为null进行初始化。
日期有各种各样的形式,在java标准类中有两个类:一个是表示时间点的Date类,另一个日历表示的GregorianCalendar类,用来将时间和日历分开。Date类提供了before和after两个方法来比较两个时间点。
三、使用自定义的类
多个源文件
在一个类中,文件名必须与public类名字相匹配,在一个源文件中只能有一个共有类,但可以有任意数目的非共有类。
当一个文件中含有多个类的时候,编译器会创建多个.class 文件,并且,如果他们之间有调用关系,他们会自动进行编译。例如有个文件叫做TestA.java,这个文件中有两个类一个是TestA,另一个是A,并且TestA调用了A,那么我们只需要进行直接编译TestA就好,A在需要的时候就会自动编译。
构造器
构造器的特点:
- 构造器与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有0,1或者多个参数
- 构造器没有返回值
- 构造器总是伴随着new一起使用
Java所有的对象都是在堆中构造的。(这个不一定)。
不要再构造器中定义与实例域重名的局部变量,否则有可能发生错误。必须注意在所有的方法中不要命名与实例域同名的变量。
隐式参数与显示参数
在Java中,所有的方法都必须在类内定义,但并不代表他们是内联方法。是否将某个方法设置为内联方法是Java虚拟机的任务。
封装
不要编写返回引用可变对象的访问器方法。在下面的例子中就违反了这个设计原则:
class Employee{
private Data hireday;
...
public Date getHireDay(){
return Date hireDay;
}
...
}
如果temp通过Employee对象的get方法获取到了Date,那么temp是可以对hireDay进行修改的,这就破坏了封装性。
如果需要返回一个可变对象的引用,应该首先对他进行克隆。对象克隆是指放在另一个位置上的对象副本。修改后的代码:
class Employee{
private Data hireday;
...
public Date getHireDay(){
return hireDay.clone;
}
...
}
基于类的访问权限
我们知道,方法可以访问所调用对象的私有数据。一个方法可以访问所属类的所有对象的私有数据。
public class Employee {
...
public boolean equals(Employee other){
return name.equals(other.name);
}
...
}
这样是合法的,在C++中,也有同样的原则。
final实例域
可以将实例域定义为final,构建对象时必须初始化这样的域。确保在一个构造器执行之后,他的值被确定,并且不能被修改。final域的值是可以在构造函数中进行赋值的。如果使用final来修饰一个引用类型比如privavte fianl Date name;这个name这个引用不能变,并不是说引用的对象不能变。
四、静态域与静态方法
静态域
如果将域定义为static,每个类中只有一个这样的域。而每一个对象对于所有的实例域却都有一份自己的拷贝。它属于类,不属于任何一个独立的对象。
静态常量
静态变量用的比较少,但静态常量却用的比较多。
比较常用的一个静态常量是System.out。它在System类中声明:
public static final PrintStream out = ...;
这样不允许将其他流赋值给他。如果查看System类的话发现有个SetOut方法,它可以将System.out设置为不同的流。这个方法是一个本地方法,可以绕过Java语言的存取控制机制。
静态方法
静态方法是一种不能向对象实施操作的方法。静态方法可以访问自身中的静态域,不可以方法对象中的实例域。在下面两种情况下需要使用静态方法:
- 一个方法不需要访问对象状态,其所需参数都是通过显示参数提供(方法后面的括号中提供)
- 一个方法只需要访问类的静态域
工厂方法
静态方法还有一种常见的用途,即当做工厂方法。
main方法
静态方法不需要创建对象,mian方法不对任何对象进行操作。移动一个类的时候main方法会首先执行。
五、方法参数
按值调用:表示接收的是调用者提供的值。
按引用调用:表示方法接收的是调用者提供的变量地址。
一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。方法参数共有两种类型:
- 基本数据类型
- 对象引用
Java程序设计语言对对象采用的不是引用调用,实际上,对象引用进行的是值传递。
- 一个方法不能修改一个基本数据类型的参数。
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
C++有值调用和引用调用。
六、对象构造
重载
如果多个方法,有相同的名字,不同的参数,便产生了重载。
默认域初始化
如果构造器中没有显示的给域赋予初值,那么就会自动的设为默认值:数值为0、布尔值为false,对象引用为null。这样可能会发生错误或者降低代码可读性,最好不要这么做。
无参数的构造器
如果自己没有定义的话,编译器会自动提供一个无参数的构造函数;(只有没有定义构造函数的时候才会自己生成)
如果提供了至少一个构造器,但是没有提供无参数的构造器,砸在构造对象是如果没有提供参数就会被视为不合法。
显示域初始化
显示赋值语句优先于构造方法执行。
public class User {
private String name = "han";
public User(){
System.out.println(name);
}
public static void main(String[] args) {
new User();
}
}
上面的语句可以正常执行。
参数名
在Java中有一种常用的技巧,它基于这样的事实:参数变量用同样的名字将实例域屏蔽起来。下面是一个实例:
public Employee(String name,double salary){
this.name = name;
this.salary = salary;
}
调用另一个构造器
关键字this引用方法的隐式参数。然而,这个关键字还有另外一个含义。如果构造器的第一个语句形如this(...),这个构造器将调用同一个类的另一个构造器。下面是一个典型的例子。
public Employee(double s){
//calls Employee(String, double)
this("Employee#"+nextId, s);
nextId++;
}
在C++中不允许一个构造函数去调用另一个构造函数。
初始化块
初始化数据域的方法:
- 在构造器中设置值
- 在声明中赋值
实际上,Java还有第三种机制,称为初始化块。在一个类中可以含有多个初始化块。例如:
class Employee{
private int nextId;
private int id;
private String name;
private double salary;
{
id = nextId;
nextId++;
}
public Employee(String n,double s){
name = n;
salary = s;
}
public Employee(){
name = "";
salary = 0;
}
}
在这个实例中,无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。首先运行初始化块,然后才运行构造器的主体部分。
创建类的时各个函数的执行过程:静态块,初始化块,构造函数
如果有继承关系的话会先加载父类,然后加载子类。执行构造函数的时候。Hei继承了Employee类。
由于初始化数据域有多种途径,所以列出构造过程的所有路径可能相当混乱。下面是具体步骤:
1)所有数据域被初始化为默认值(0,false,null)
2)安装在类声明中的顺序,依次执行所有域初始化语句和初始化块。
3)如果构造器的第一行调用了第二个构造器,则执行第二个构造器主体
4)执行这个构造函数的主体
最好不要让程序依赖于程序初始化的顺序,不然容易降低代码可读性,增加产生错误的可能。
如果对类的静态域进行初始化的代码比较复杂那么可以使用静态的初始化块。在类第一次加载的时候,将会进行静态域的初始化。与实例域一样,除非将他们显示设置成其它值,否则默认的值是0,false或者null。所有的静态初始化语句及静态初始化块都将按顺序执行。
对象析构与finalize方法
finalize 方法将在垃圾回收器清除对象之前调用。
七、包
一般建议使用公司域名的倒序作为包名。
静态导入
import语句不仅可以导入类,还增加了导入静态方法和静态域的功能。
例如:import static java.lang.System.out;可以直接使用out.println("s");
将类放入包中
要想将一个类放入包中,就必须将包的名字放在源文件的开头。如果没有写包名,这个源文件中的类就放在一个叫做default package中。默认包是一个没有名字的包。如果写了一个包名,运行时就要到包的最外层进行运行比如一个文件包名a.b.c.A;那么编译的时候就要到a的上一层目录中。
编译器在编译源文件的时候不检查目录结构。例如,假定有一个源文件开头有下列语句:package com.mycompany;即使这个源文件没有在子目录com/mycompany下,也可以进行编译。如果它不依赖其他包就不会发生错误。但是最终的程序无法运行,这是因为虚拟机找不到类文件。
包作用域
public可以被所有调用,private只能被定义他们的类使用。如果没有指定public和private,这个部分可以被同一个包中的所有方法访问。
八、类的设计技巧
- 一定要保证数据私有
- 一定要对数据初始化
- 不要过多的使用基本数据类型(可以类中套类)
- 不是所有的域都需要独立的域访问器和域更改器
- 将职责过多的类分解
- 类名和方法名要体现他们的职责