Java(四)对象与类

2014-11-24 02:25:40 · 作者: · 浏览: 0

1、系统已有类

所有的 java 对象都存储在堆中。

java 类 实例:

import java.text.DateFormatSymbols;
import java.util.*;
public class CalendarTest 
{
	public static void main(String[] args)
	{
		//构造一个日历对象,并用当前日期和时间进行初始化
		GregorianCalendar d = new GregorianCalendar();
		
		int today = d.get(Calendar.DAY_OF_MONTH);//获取当时的日
		int month = d.get(Calendar.MONTH);//获取当时的月
		int year = d.get(Calendar.YEAR);//获取当时的年
		int todayWeek = d.get(Calendar.DAY_OF_WEEK);//获取当时星期几
		
		d.set(Calendar.DAY_OF_MONTH,1);//将d设置为这个月的第一天
		int weekday = d.get(Calendar.DAY_OF_WEEK);//获取这一天是星期几
		
		String[] weekdayNames = new DateFormatSymbols().getShortWeekdays();//转换星期几的格式
		
		//输出当前的年月日星期,因为月从0为基准,所以这里要加1
		System.out.println();
		System.out.println("Today is : "+year+"年"+(month+1)+"月"+today+"日"+weekdayNames[todayWeek]);
		System.out.println();
		int firstDayOfWeek = d.getFirstDayOfWeek();//获取当前地区星期的起始日
		
		int indent = 0;
		while (weekday != firstDayOfWeek)//如果weekday不是星期的起始日,将日期后退,
		{                                //直接是起始日为止,计算离当前天的距离
			indent++;
			d.add(Calendar.DAY_OF_MONTH, -1); 
			weekday = d.get(Calendar.DAY_OF_WEEK);
		}
		
		do //依次输出星期的缩写字符串
		{
			System.out.printf("%4s", weekdayNames[weekday]);//第一次循环时weekday是星期的起始日
			d.add(Calendar.DAY_OF_MONTH, 1); 
			weekday = d.get(Calendar.DAY_OF_WEEK);
		}
		while (weekday != firstDayOfWeek);//当不是星期的起始日时继续循环
		System.out.println();
		
		for (int i = 1; i <= indent; i++)//由开头进行缩进上面求出的距离
			System.out.print("     ");//每次输出5个空格
		
		d.set(Calendar.DAY_OF_MONTH, 1);//设置 d 为这个月的第一天
		do
		{
			int day = d.get(Calendar.DAY_OF_MONTH);
			System.out.printf("%4d", day);//输出日
			
			if (day == today) System.out.print("*");//若是今天,则后面加上 *,不是则加空格
			else System.out.print(" ");
			
			d.add(Calendar.DAY_OF_MONTH,1);//将d设置为下一天
			weekday = d.get(Calendar.DAY_OF_WEEK);//获取星期
			
			if (weekday == firstDayOfWeek) System.out.println();//如果是每个星期的第一天则换行输出
		}
		while (d.get(Calendar.MONTH) == month);//如果d还是在当前的月就继续循环
		
		if (weekday != firstDayOfWeek) System.out.println();//结束时,若不是星期起始日则换行
	}
}

运行结果:
\


< http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPGgzPjKhotPDu6fX1Lao0uXA4DwvaDM+CjxwPiAgICAgINKqz+u0tL2o0ru49s3q1fu1xLPM0PKjrNOmuMO9q8j0uMnA4Nfpus/U2tK7xvCjrMbk1tDWu9PQ0ru49sDg09AgbWFpbiC3vbeooaM8L3A+CjxwPiAgICAgILHg0LTA4Mv5ssnTw7XEt+cmIzI2Njg0O8rHwOC1xLe9t6jU2seww+ajrNPy1Nq688PmoaM8L3A+CjxwPiAgICAgIM7EvP7D+7HY0OvT6yBwdWJsaWMgwOC1xMP719bP4MalxeSjrNTa0ru49tS0zsS8/tbQo6zWu8Tc09DSu7j2uavT0MDgo6y1q8rHv8nS1NPQyM7S4sr9xL+1xLfHuavT0MDgPC9wPgo8cD4gICAgICDKtcD9o7o8L3A+CjxwPjwvcD4KPHByZSBjbGFzcz0="brush:java;">import java.util.*; public class EmployeeTest { public static void main(String[] agrs) { Employee[] staff = new Employee[3]; staff[0] = new Employee("Ai",2000,1992,1,12); staff[1] = new Employee("Bi",3000,1993,8,29); staff[2] = new Employee("Ci",2500,1992,11,5); for (Employee e : staff) e.raiseSalary(5); for (Employee e : staff) System.out.println("name="+e.getName()+",salary=" +e.getSalary()+",hireDay="+e.getHireDay()); } } class Employee { public Employee(String n, double s, int year, int month, int day) //构造器 { name = n; salary = s; GregorianCalendar calendar=new GregorianCalendar(year,month-1,day); hireDay = calendar.getTime(); } public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } //实例域 private String name; private double salary; private Date hireDay; }
\


Employee 类,包含一个构造器和4个方法,还有三个实例域用来存放将要操作的数据。

(1)构造器:与类名相同,在构造 Employee 类的对象时,构造器被运行,以便将实例域初始化为所希望的状态。

每一个类有一个以上的构造器,构造器可以有任意个参数,没有返回值,总是伴随着 new 操作一起调用(C++程序员容易忘记这点)

(2)隐式参数与现实参数:

public void raiseSalary(double byPercent)
{
	double raise = salary * byPercent / 100;
	salary += raise;
}

raiseSalary 方法有两个参数,第一个参数时方法名前的 Employee 类对象,称为隐式参数,没有出现在方法声明中

第二个参数是在方法名后面括号里的 double byPercent ,称为显式参数。

在每一个方法中,关键字 this 表示隐式参数。如果需要的话,可以用下列方式编写 raiseSalary 方法:

public void raiseSalary(double byPercent)
{
	double raise = this.salary * byPercent / 100;
	this.salary += raise;
}

注意,java 与 C++ 不同,java中,所有的方法都必须在类的内部定义,但并不表示他们是内联方法。


(3) getName方法、getSalary方法和 getHireDay 方法,都是典型的访问器方法,由于它们只返回实例域值,也称为域访问器

(4)final 实例域,构建对象时必须初始化这样的域,初始化之后不能进行修改。

3、静态域与静态方法

关键字 static 被解释为:属于类且不属于对象的变量和函数。

(1)静态域

如果将域定义为 static,每个类中只有一个这样的域。每一个对象对于所有的实例域都有自己的拷贝,而却都共享一个静态域。

(2)静态常量

静态变量用的少,但静态常量却使用的比较多。跟静态域功能差不多。

(3)静态方法

静态方法是一种不能向对象实施操作的方法,所以不能在静态方法中访问实例域。但是静态方法可以访问自身类中的静态域。

可以认为静态方法是没有 this 参数的方法。可以使用类名来调用静态方法

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

a、一个方法不需要访问对象状态,其所参数都是通过显示参数提供

如: Math.pow(x,a) 计算 x 的 a 次幂,不使用任何 Math 对象。

b、一个方法只需要访问类的静态域

如:

public static int getNextId()
{
     return nextId; //返回的是一个静态域
}

可以通过类名调用这个方法: int n = Employee.getNextId();

4、方法参数

在 java 程序设计语言中,方法参数的使用情况:

a、一个方法不能修改一个基本数据类型的参数(即数值型和布尔型)

b、一个方法可以改变一个对象参数的状态

c、一个方法不能实现让对象参数引用一个新的对象

5、对象构造

(1)重载

如果多个方法有相同的名字、不同的参数,便产生了重载。

要完整的描述一个方法,需要指出方法名以及参数类型。这叫做方法的签名。返回类型不是签名的一部分。

(2)默认域初始化

如果在构造器中没有显示地给域赋初值,那么就会被自动赋为默认值:数值为0,布尔值为 false,对象引用为 null。

(3)默认构造器

默认构造器是指没有参数的构造器。

如果一个类没有编写构造器,系统会提供一个默认构造器,这个默认构造器将所有的实例域设置为默认值

(4)调用另一个构造器

关键字 this 引用方法的隐式参数,然后,还有另一个含义:如果构造器的第一个语句形如 this(...),这个构造器

将调用同一个类的另一个构造器,如:

public Employee(double s)
{
      //调用 Employee(String,double)
      this("Employee #" + nextId, s);
      nextId++;
}

(5)初始化块

初始化数据域的方法有三种:在构造器中设置值,在声明中赋值,还有就是称为初始化块。

在一个类的声明中可以包含多个代码块,只要构造类的对象,这些块就会被执行。例如:

class Employee
{
	public Employee(String n, double s)
	{
		name = n;
		salary = s;
	}
	public Employee()
	{
		name = "";
		salary = 0;		
	}
	...
	
	private static int nextId;
	
	private int id;
	private String name;
	private double salary;
	...
	
	//初始化块
	{
		id = nextId;
		nextId++;
	}
	
}

调用构造器的具体处理步骤:

a、所有数据域被初始化为默认值

b、按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块

c、如果构造器第一行调用了第二个构造器,则执行第二个构造器主体

d、执行这个构造器的主体

如果对类的静态域进行初始化的代码比较复杂,那么可以使用静态的初始化块,将代码放在一个块中,并标记关键字 static。

示例:

static
{
	Random generator = new Random();
	nextId = generator.nextInt(10000);
}

//功能是将雇员Id的初始值赋予一个小于10000的随机整数

实例:

import java.util.*;
public class ConstructorTest 
{
	public static void main(String[] agrs)
	{
		Employee[] staff = new Employee[3];
		
		staff[0] = new Employee("Ai",20000);
		staff[1] = new Employee(50000);
		staff[2] = new Employee();
		
		for (Employee e : staff)
			System.out.println("name="+e.getName()+",id="
					+e.getId()+",salary="+e.getSalary());
	}
}

class Employee
{
	public Employee(String n, double s)
	{
		name = n;
		salary = s;
	}
	
	public Employee(double s)
	{
		//调用  Employee(String,double)构造器
		this("Employee #" + nextId, s);
	}
	
	public Employee() //默认构造器
	{
		
	}
	
	public String getName()
	{
		return name;
	}
	
	public double getSalary()
	{
		return salary;
	}
	
	public int getId()
	{
		return id;
	}
	
	private static int nextId;
	
	private int id;
	private String name = ""; //实例域初始化
	private double salary;
	
	static //静态初始块
	{
		Random generator = new Random();//构造一个新的随机数生成器
		nextId = generator.nextInt(10000);//返回一个 0到9999之间的随机数
	}
	
	//对象初始化块
	{
		id = nextId;
		nextId++;
	}
}

运行结果:


(6)对象析构与 finalize 方法

java 有自动的垃圾回收器,不需要人工回收内存,所以不支持析构器。

当然,如果某些对象使用了内存之外的其他资源,例如,文件或使用了系统资源的另一个对象的句柄,这种情况下,需要回收。

可以为任何一个类添加 finalize 方法,这个方法将在垃圾回收器清除对象之前调用。

6、包

java 允许使用包(package)将类组织起来。

使用包的主要原因是确保类名的唯一性,将同名的类放到不同的包中,就不会产生冲突,为了确保包名的绝对唯一性,Sun

公司建议将公司的因特网域名以逆序的形式作为包名,并且对于不同的项目使用不同的子包。

(1)类的导入

两种方式访问另一个包中的公有类:

a、在每个类名之前添加完整的包名,如:java.util.Date today = new java.util.Date(); ,显然不方便

b、简单常用的方式是使用 import 语句,可以使用 import 语句导入一个特定的类或者整个包。

如: import java.util.*;

注意,只能使用星号(*)导入一个包,而不是所有包。当导入多个包,放生命名冲突时,应明确指出调用哪个包。

(2)静态导入:可在包名前加上 static,就可以使用包类的静态方法和静态域,而不必加类名前缀。

(3)将类放入包中:必须将包名放在源文件开头

package 包名;

类{...}

若没有放置 package 语句,则源文件默认放置在一个默认包(default package)

7、类设计技巧

(1)一定将数据设计为私有

(2)一定要对数据初始化

(3)不要在类中使用过多的基本数据类型

(4)不是所有的域都需要独立的域访问器和域更改器

(5)使用标准格式进行类的定义

一定采用下面的顺序书写类的内容:

公有访问特性部分

包作用域访问特性部分

私有访问特性部分

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

实例方法

静态方法

实例域

静态域

(6)将职责过多的类进行分解

(7)类名和方法名要能够体现它们的职责