JAVA学习脚印4: 对象与类的概念

JAVA学习脚印4: 对象与类的概念

本节将记录java程序设计中的对象与类的概念。

 

java语言是完全面向对象的。面向对象程序设计是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。

1.对象的要素

 

一个对象主要有两个主要特性:

  • 对象的行为:即可以对对象施加那些操作或施加哪些方法?
  • 对象的状态:即施加方法时,对象如何相应?

对象的行为,由对象可以执行的方法来实现;对象的状态,一般由对象拥有的实例变量来记录。例如现在有一个购物车对象,它要维持自己的状态,就需要相应的实例变量,例如货物编号、货物名称、货物数量等等变量;而用户使用购物车对象时,可以利用其提供的方法,例如加入货物、删除货物、货物结算等方法来为购物提供便利。

2.类与对象

 

如何构建一个对象呢? 从面向对象程序设计出发,首先来设计一个符合描述这个对象行为和状态的类。类是构造对象的模板或蓝图。我们可以把类想象成住房设计的图纸,而把对象想象成一栋具体的房子。例如,上文提到的购物车,要设计一个类来实现购物车对象,则可以这样描述一个购物车类:


我们可以利用类来创建一个实际变量也就是一个对象,每个对象都会有自己的实例变量和方法。

3.类的特点

 

 

类有三个主要特性:

Ø 封装:封装就是将数据和方法组合在一起,并对对象的使用者隐藏了数据的实现方式。封装的关键在于绝对不能让类中的方法直接访问其他类的实例域,而让程序仅仅通过对象的方法与数据进行交互。

Ø 继承:继承是一个类拓展另一个类而建立一个功能更加广泛的方法。这个拓展后的新类具有拓展类的全部属性和方法,在新类中只需要提供那些仅适用于这个新类的新方法和数据域即可。其中,已经存在的类被称为超类或父类,新类被称为子类或派生类。通过继承可以有效提高代码重用,简化代码。

Ø 多态:多态特性允许在程序中出现超类对象的任何地方都可以用它的子类对象置换它。通过适用多态我们可以编写出引进新型子类时也不必修改的程序。

关于这三个特性,后续将会逐一解释。

4.初步认识类

 

1)使用系统库提供的类

java标准库中已经内置了许多类,用于处理常见问题,使用系统库提供的类能节省时间,提高效率。java系统中时间点和日历是分开来处理的,分别对应系统中的两个类:java.util.DateJava.util.GregorianCalendar。通过例4-1我们来打印当前月的日历,代码如下:

4-1  PrintCalendar.java

package com.learningjava;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;

/**
 * this program show a calendar of the current month
 * the programe adapt from the book 《Core Java,Volume I:Fundamentals》
 * @version 1.1 2013-08-07
 * @author origin by Cay Horstmann
 * @author adapt by wangdq
 */
public class PrintCalendar {
	public static void main(String[] args) {
		//adjust the calendar to fit chinese area
		Locale.setDefault(Locale.CHINA);
		
		//construct a GregorianCalendar 
		GregorianCalendar calendar = new GregorianCalendar();
		//get the current day and month
		int currentDay = calendar.get(Calendar.DAY_OF_MONTH);
		int currentMonth = calendar.get(Calendar.MONTH);
		//get the weekday index of the first day 
		int firstDayOfWeek = calendar.getFirstDayOfWeek();
		//get the weekday index of the current day 
		int weekday = calendar.get(Calendar.DAY_OF_WEEK);
		//set the day of  calendar to the first day
		calendar.set(Calendar.DAY_OF_MONTH,1);
		
		//calculate the white space before printing the first day
		int indent = 0;
		while(weekday != firstDayOfWeek ) {
			indent++;
			calendar.add(Calendar.DAY_OF_MONTH,-1);
			weekday = calendar.get(Calendar.DAY_OF_WEEK);
		}
		
		//print weekday names ,use weekday index instead of number
		String[] weekdayNames = new DateFormatSymbols().getShortWeekdays();
		do {
			System.out.printf("%5s", weekdayNames[weekday]);
			calendar.add(Calendar.DAY_OF_MONTH,1);
			weekday = calendar.get(Calendar.DAY_OF_WEEK);
		}while(weekday != firstDayOfWeek );
		System.out.println();
		
		//print the white space before print the first day 
		for(int i = 0;i < indent;++i) 
			System.out.printf("%7s"," ");
		
		//finish printing the days in the current month
		calendar.set(Calendar.DAY_OF_MONTH,1);
		do {    
			    int day = calendar.get(Calendar.DAY_OF_MONTH);
			    if(day == currentDay) {
			    	System.out.printf("%4d*", day);
			    } else {
			    	System.out.printf("%5d", day);
			    }
			    System.out.print("  ");//print two white space
				calendar.add(Calendar.DAY_OF_MONTH,1);
				weekday = calendar.get(Calendar.DAY_OF_WEEK);
				if(weekday == firstDayOfWeek) {
					System.out.println();
					}
		}while(currentMonth == calendar.get(Calendar.MONTH));
	}
}

程序运行效果如下:

星期日  星期一  星期二  星期三  星期四  星期五  星期六

                                                  1          2         3  

    4      5      6     7*      8     9     10  

   11     12     13     14     15    16     17  

   18     19     20     21     22    23     24  

   25     26     27     28     29    30     31

注意,使用DateFormatSymbols().getShortWeekdays() 获取的weekdayNames对象,应该使用weekday数值索引来获取其对应的名称,而不要直接使用数字,例如:

weekdayNames[0]得到的不是第一个字符串,实际上它的内容如下图所示:



2)自定义类

假设我们要定义一个类用来表示公司雇员Employee这样一些对象,那么我们首先需要明确

Employee类需要保存那些实例变量,又要支持哪些方法。

我们可以这样简单的描述:



例4-2给出了这个类及其测试代码:

4-2  EmployeeTest.java


package com.learningjava;

import java.util.Date;
import java.util.GregorianCalendar;

/**
 * test the employee class
 * origin by the book 《Core Java,Volume I:Fundamentals》
 * @version 1.1 2013-08-07
 */
public class EmployeeTest {
	public static void main(String[] args) {
		Employee[] staffs = new Employee[2];
		staffs[0] = new Employee("李明",5000,1985,9,4);
		staffs[1] = new Employee("王涛",8000,1979,8,12);
		for(Employee item : staffs)
		      item.raiseSalary(10);
		for(Employee item : staffs)
			System.out.println(item);
	}
}
/**
 * a class to descript employee
 * origin by the book 《Core Java,Volume I:Fundamentals》
 * @version 1.1 2013-08-07
 */
class Employee {
    //for unit test
	public static void main(String[] args) {
		Employee e = new Employee("Tom",5000,1986,9,4);
		System.out.println(e);
	}
	/**
	 * @param name name to set
	 * @param salary salary to set
	 * @param hireday hireday to set
	 */
	public Employee(String name, double salary, Date hireday) {
		this.name = name;
		this.salary = salary;
		this.hireday = hireday;
		setId();
	}
	/**
	 * 
	 * @param name name to set
	 * @param salary salary to set
	 * @param year month day  to create a GregorianCalendar
	 */
	public Employee(String name, double salary, int year,int month,int day) {
		this.name = name;
		this.salary = salary;
		GregorianCalendar calendar = new GregorianCalendar(year,month-1,day);
		this.hireday = calendar.getTime();
		setId();
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + 
				", salary=" + salary + ", hireday="+ hireday+"]";
	}
	public void raiseSalary(double percent) {
		double raise = salary*percent/100;
		salary += raise;
	}
	public String getName() {
		return name;
	}
	public double getSalary() {
		return salary;
	}
	public Date getHireday() {
		return hireday;
	}
	public int getId() {
		return id;
	}
	private void setId() {
		this.id = nextId;
		nextId++;
	}
	private String name; 
	private double salary;
	private Date hireday;
	private int id;
	private static int nextId = 1;
}

下面通过上面的代码来分析类的书写和设计。

类的结构,可以抽象为如下表示:


class ClassName
{
   constructor1
   constructor2
   …
   method1
   method2
   …
   filed1
   filed2
   ...
}

 关于构造器有几点需要注意:

· 如果在编写一个类的时候没有编写构造器,系统会提供默认构造器,这个默认构造器将将所有的实例域设置为默认值;如果类中提供了至少一个构造器,则系统不会在提供默认构造器,需要用户决定是否手动添加。

· 构造器的调用有一个顺序问题:

1)所有的数据域被初始化为默认值(0,false或者null)

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

3)如果构造器第一条语句调用了第二个构造器(自己的其他构造器或者父类构造器),则执

    行第二个构造器主体。注意调用其他构造器必须是第一条语句,例如:

public Employee(String name,double salary) {
		this.name = name;
		this.salary = salary;
	}
	public Employee(double salary) {
		nextId++;
		this("Employee"+nextId,salary);
	}


出现错误: Constructor call must be the first statement in a constructor

       需将this调用的构造器放到第一条语句的位置。

 4)执行这个构造器的主体。

类的访问权限

 

类中的实例域及其初始化

 类中的实例域根据需要可以设置成静态域、常量域、静态常量域三种。

 静态域的特点是将实例变量声明为静态的,则每个类中只有一个这样的域,而每个对象却有所有实例域的一个拷贝。例如Emplyoee类中的nextId域就是一个静态域,它有这个类所有对象共享一个nextId域,静态域属于类而不属于任何对象,静态域又被称为类域

常量域即将实例变量声明为final型,例如Employee的姓名域声明为:

 private final  String name;后则姓名一旦被赋值就不可再更改。

静态常量域即声明为static final的实例变量,例如Math类中PI即被声明为:

public static final double PI = 3.1415926...;

  实例域的初始化有四种方式:默认域初始化、显式域初始化、构造器初始化、初始化块初始化。

默认域初始化,是在当构造器中没有显式的给域赋值时自动给实例域赋予默认值的行为。数值为0,布尔型为false,对象引用为null。

显式域初始化即在声明实例变量时直接给其赋值的行为。这样在构造器执行之前,先执行赋值操作。

构造器初始化初始化是通过构造器给实例域赋值的行为。

初始化块分为对象初始化块和静态初始块。对象初始化块即是:

class Employee {
    //对象初始化块
    {
        id = nextId;
       nextId++;
    }
}

无论使用哪个构造器构造对象,首先要运行对象初始化块部分再运行构造器的主体部分。

不建议使用对象域初始化块来初始化实例域。

另外还有静态初始化块,当类第一次加载的时候,将会运行静态初始化块中的代码,可以对静态域进行初始化,例如:

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

 类的方法

· 私有方法:由于公有数据具有危险性,因此应该将所有的数据域设置为私有的;对于类中的辅助方法,不需要对外公开的那些方法,也应该声明为私有的,这样改动它时对外界就没有影响。

· 公有方法:类提供给外部的接口,通过公有方法,可以让对象执行其上的操作。

· 静态方法:静态方法是一种不能向对象实施操作的方法。在静态方法中不能操作对象,因此不能在静态方法中访问实例域,但是可以访问自生类中的静态域。建议使用类名来调用静态方法,而不要用对象调用静态方法。

静态一词的理解是:属于类且不属于类对象的变量和函数。 

· finalize方法:与c++不同,java中有自动的垃圾回收器,不需要人工回收内存,所以java不支持析构器。如果某个对象使用了内存之外的其他资源,当资源不再需要时,将其回收则很重要,java中可以为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象之前调用,可以利用这个方法做人工清理工作,当然也可以使用类似close之类的方法来实现相应的清理工作。

  

 

 

类的单元测试

在我们编写的EmployeeTest.java文件中EmployeeTest和Employee类中都有一个main方法,但是这并不影响程序执行。当要单独测试Employee类时,可以使用java Employee;

而要测试包含Employee类的工程时,只需要测试公有类,例如 java EmployeeTest即可。

下面是运行效果:

wangdq@wangdq:~/workspace/EmployeeTest/bin$ java com.learningjava.Employee
Employee [id=1, name=Tom, salary=5000.0, hireday=Thu Sep 04 00:00:00 CDT 1986]
wangdq@wangdq:~/workspace/EmployeeTest/bin$ java com.learningjava.EmployeeTest
Employee [id=1, name=李明, salary=5500.0, hireday=Wed Sep 04 00:00:00 CST 1985]
Employee [id=2, name=王涛, salary=8800.0, hireday=Sun Aug 12 00:00:00 CST 1979]

这一特性对于单元测试是十分方便的。

5.方法参数与方法返回值问题

1) java中总是采用值调用

 程序设计中一般存在两种方法参数传递方式即值传递和地址传递,即值调用和引用调用。

 java中总是采用值调用,方法得到的是所有参数值的一个拷贝,特别是方法不能修改传递 给它的任何变量的内容。

方法参数公公有两种类型,一种是基本数据类型,这一种的值传递理解起来简单,还有一种是对象引用。方法参数的使用情况需要注意三点:

  • 一个方法不能修改一个基本数据类型的参数
  • 基本数据类型采用值传递方式,修改的只是形参的值,而没有影响到实参的值。
  • 一个方法可以改变一个对象参数的状态

并不是说采用值传递方式就不能改变对象的状态了,例如:

 Employee harry = new Employee("Harry",50000);
 tripleSalary(harry);
private static void tripleSalary(Employee x) //works
{
	x.raiseSalary(200);
}

这种情况下调用 tripleSalary函数还是能够更新 Employee的salary的。

 实际上,方法通过形参得到的是对象引用的拷贝,而对象引用及其他的拷贝同时引用同一个对象。

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

java中的对象参数,实际是引用型的相当于c++中的对象指针。

例如函数调用

Employee a = new Employee("Alice",70000);
Employee b = new Employee("Bob",60000);
swap(a,b);
 void swap(Employee x, Employee y) //dosen't work
	{
		Employee temp = x;
		x = y;
		y = temp;
	}


并没有改变原始对象引用a,b交换对象引用。

这相当于c++中的函数:

void swap(Employee * x,Employee* y)
{
     Employee *temp = x;
     x = y;
     y = temp;
}

而不是:

//利用指针引用交换指针
void swap(Employee * &x,Employee* &y)
{
     Employee *temp = x;
     x = y;
     y = temp;
}


至此,我们熟悉了java中对象与类的概念,学习了java中类中的几个主要特性。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值