静态域与静态方法/方法参数/对象构造
一、静态域与静态方法
1、静态域
如果将域定义为 static, 每个类中只有一个这样的域。而每一个对象对于所有的实例域却都有自己的一份拷贝。
class Employee
{
private static int nextId = 1;//静态域,所有实例将共享一个 nextId
域,即使没有对象,也存在,静态域只属于类,不属于任何独立的对象,访问形式:Employee.nextId;
private int id; //实例域,每一个雇员对象都有一个自己的 id 域
……
}
2、静态常量
静态常量用的比较多,比如Math类中定义静态常量:
public class Math
{
……
public static final double PI = 3.14159265358979323846;
……
}
可以采用Math.PI
获得这个常量。
又比如System.out
,在System类中声明:
public class System
{
……
public static final PrintStream out = ……;
……
}
3、静态方法
静态方法是一种不能面向对象实施操作的方法。例如, Math 类的 pow 方法就是一j种静态方法。表达式:Math.pow(x,a)
.可以认为静态方法时没有this参数的方法(在一个非静态方法中,this参数表示这个方法的隐式参数)
Employee 类的静态方法不能访问Id实例域, 因为它不能操作对象。但是,静态方法可以访问自身类中的静态域。
public static int getNextId()
{
return nextId;//return static field;
}
可以通过类名调用这个方法:int n = Employee.getNextId();
在下面两种情况可以使用静态方法:
- 一 方法不需要访问对象状态,其所需参数都是通过显式参数提供(例如:Math.pow)
- 一个方法只需要访问类的静态域(例如:Employee.getNextld)
4、工厂方法
静态方法还有另外一种常见的用途。类似 LocalDate 和 NumberFormat 的类使用静态工厂方法 (factory methocO 来构造对象
为什么 NumberFormat 类不利用构造器完成这些操作呢? 这主要有两个原因:
- 无法命名构造器。构造器的名字必须与类名相同。但是, 这里希望将得到的货币实例和百分比实例采用不用的名字。
- 当使用构造器时,无法改变所构造的对象类型。而 Factory 方法将返回一个 DecimalFormat类对象,这是 NumberFormat 的子类
5、main方法
main 方法也是一个静态方法。main 方法不对任何对象进行操作。事实上,在启动程序时还没有任何一个对象。静态的
main 方法将执行并创建程序所需要的对象。
二、方法参数
Java 程序设计语言总是采用按值调用(表示方法接收的是调用者提供的值),方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。
例如:
public static void trpValue(double x)
{
x = 3 * x;
}
调用时,double percent = 10; trpValue(percent);
具体执行如下:
- 1 ) x 被初始化为 percent 值的一个拷贝(也就
是 10 ) - 2 ) x 被乘以 3后等于 30。 但是 percent 仍然
是 10 (如图 4-6 所示) - 3 ) 这个方法结束之后,参数变量 X 不再使用。
方法参数共有两种类型:基本数据类型(数字、布尔值,参见上图)和对象引用(参见下图)。
如果参数是对象引用,则可以实现改变:
public static void trpSalary(Employee x)
{
x.raiseSalart(200);
}
当调用harry = new Employee(……);trpSalary(harry);
具体的执行过程为:
- 1 ) X 被初始化为 harry 值的拷贝,这里是一个对象的引用。
- 2 ) raiseSalary 方法应用于这个对象引用。x 和 harry 同时引用的那个 Employee 对象的薪金提高了 200%。
- 3 ) 方法结束后,参数变量 x 不再使用。当然,对象变量 harry 继续引用那个薪金增至 3倍的雇员对象(如图 4-7 所示)
-
总结一下 Java 中方法参数的使用情况:
•一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
•一个方法可以改变一个对象参数的状态。
•一个方法不能让对象参数引用一个新的对象。
三、对象构造
1、重载
重载:如果多个方法有相同的名字、不同的参数、便残生了重载。编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。如果编译器找不到匹配的参数, 就会产生编译时错误,因为根本不存在匹配, 或者没有一个比其他的更好。(这个过程被称为重载解析(overloading resolution)。)
Java 允许重载任何方法, 而不只是构造器方法。因此,要完整地描述一个方法,
需要指出方法名以及参数类型。这叫做方法的签名(signature)。
2、默认域初始化
如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值: 数值为 0、布尔值为 false、 对象引用为 null。
域与局部变量不同点在于:局部变量必须明确初始化,而域可以不初始化,此时自动初始化为默认值
3、无参数构造器
对象由无参数构造函数创建时, 其状态会设置为适当的默认值。Employee 类的无参数构造函数:
public Employee()
{
name = "";
salary = 0;
hireDay = LocalDate.now();
}
如果在编写一个类时没有编写构造器, 那么系统就会提供一个无参数构造器。这个构造器将所有的实例域设置为默认值。如果类中提供了至少一个构造器, 但是没有提供无参数的构造器, 则在构造对象时如果没有提供参数就会被视为不合法。
4、显示域初始化
通过重载类的构造器方法,可以采用多种形式设置类的实例域的初始状态。确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值,初始值不一定是常量值
5、参数名
通常参数用单个字符命名,但这样做有一个缺陷:只有阅读代码才能够了解参数 n 和参数 s 的含义。可以在每个参数前面加上一个前缀“ a”,参数变量也可以用同样的名字将实例域屏蔽起来。如果将参数命名为 salary, salary 将引用这个参数, 而不是实例域。 但是,可以采用 this.salary 的形式访问实例域。回想一下,this 指示隐式参数, 也就是所构造的对象。
public Employee(String name, double salary)
{
this.name = name;
this.salary = salary;
}
6、调用另一个构造器
this的两种用途:
- 关键字 this 引用方法的隐式参数
- 如果构造器的第一个语句形如 this(…), 这个构造器将调用同一个类的另一个构造器。例如
public Employee(double s)
{
//call Employee(String, double)
this("Employee #" + nextId, s);
nextId++;
}
当调用 new Employee(60000) 时, Employee(double) 构造器将调用Employee(String,double)构造器。
7、初始化块
初始化数据域的方法:
- 构造器中设置值;
- 在声明中赋值;
- 初始化块:在一个类的声明中,可以包含多个代码块。只要构造类的对象,这些块就会被执行。
class Employee
{
private static int nextId;
private int id;
private String name;
// object initialization block
private double salary;
{
id = nextId;
nextId++;
}
// 静态 object initialization block
//private static double salary;
//{
//id = nextId;
//nextId++;
//}
……
}
在这个示例中,无论使用哪个构造器构造对象,id 域都在对象初始化块中被初始化。首先运行初始化块,然后才运行构造器的主体部分
由于初始化数据域有多种途径,所以列出构造过程的所有路径可能相当混乱。下面是调用构造器的具体处理步骤:
1 ) 所有数据域被初始化为默认值(0、false 或 null。)
2 ) 按照在类声明中出现的次序, 依次执行所有域初始化语句和初始化块。
3 ) 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体
4 ) 执行这个构造器的主体.
8、对象析构与finalize方法
由于 Java 有自动的垃圾回收器,不需要人工回收内存, 所以 Java 不支持析构器(如C++中,放置一些当前对象不在使用时需要执行的清理代码)
可以为任何一个类添加 finalize 方法。finalize 方法将在垃圾回收器清除对象之前调用。在实际应用中,不要依赖于使用 finalize 方法回收任何短缺的资源, 这是因为很难知道这个方法什么时候才能够调用。