面向对象编程

类与对象

在基本概念中,已经了解了类与对象的基本应用,下面再回顾一下CAuto类的定义。

Java
public class CAuto {
	public String model;
		public void drive() {
		System.out.println(model + "行驶中...");
	}
}

定义类时,我们使用了class关键字;本例中的CAuto类,已经定义了一个model变量,称为类的字段(field)或属性(property),用于存储对象数据。drive()方法则用于显示行驶信息。

本课我们将继续了解面向对象编程中的更多内容,首先了解instanceof运算符,它用于判断一个对象是否为某个类的实例;还以CAuto类为例,如下面的代码。

Java
public class Hello {
	public static void main(String[] args) {
		CAuto car = new CAuto();
		System.out.println(car instanceof CAuto);
	}
}

执行会显示true,即car对象是CAuto类的一个实例。

访问级别

在定义CAuto类,以及它的成员model属性和drive()方法是地,我们都使用了public关键字,这样定义的类及成员就可以在定义区域的外部使用了。如果将model属性定义中的public修改为private,就不能通过CAuto类的实例来调用它。

在Java中的访问级别共有四种,分别是:

  • public,公共成员,可以在定义区域的外部调用。
  • protected,受保护成员,只能在定义的类和其子类中访问,在讨论“继承”时会有相关应用。
  • private,私有成员,只能定义的区域内访问。
  • 默认访问级别,如果不使用public、proteced和private关键字指定访问级别,则使用默认访问级别,它类似于public级别,但只能在定义的包(package)中使用。

此外,在方法和流程控制结构等代码结构中,定义的变量和常量只能其定义的结构内部使用,而且,在嵌套的结构中,不能重新定义同名变量,如下面的代码。

Java
public class Hello {
	public static void main(String[] args) {
		int i = 0;
		for(int i=0; i<10;i++) {
			//
		}
	}
}

代码中,先在main()中定义了i变量,然后又在for循环中使用i作为循环变量,这样是不允许的,会显示下图中的错误提示。

字段与属性

我们说过,在类中定义的变量称为字段(field)或属性(property)。不过,在面向对象编程术语中,我们更多的会使用“属性”,这里,我们做一下约定:类中定义的非公开的(private或property)变量或对象称为字段,而可以在外部设置或读取数据的变量和对象称为属性。

我们继续改写CAuto类,为汽车添加一个表示车门数量的属性。现在需要考虑的问题是,使用什么样的形式来定义这个属性。如果直接使用int类型的变量,那么,车门数量设置为负数也是可以的,这显然是不科学的。

如果需要限制或检查属性的数据,可以使用setter和getter方法完成属性数据的设置和读取工作,如下面的代码就是修改后的CAuto.java文件。

Java
public class CAuto {
	// 型号
	public String model;
	// 行驶方法
	public void drive() {
		System.out.println(model + "行驶中...");
	}
	// 车门数量
	private int doorCount;
	public void setDoorCount(int num) throws Exception {
		if(num>=0 && num<=5)
			doorCount = num;
		else
			throw new Exception("车门数量应该在0到5之间");
	}
	public int getDoorCount() {
		return doorCount;
	}
}

本例,我们添加了私有字段doorCount用于保存车门数量,但在类的外部,只能通过setDoorCount()方法设置,并使用getDoorCount()方法读取;其中,在setDoorCount()方法的定义中,使用throws关键字指定方法可能会抛出Exception异常类型,方法中,当车门在0(F-1)到5(SUV)之间时,设置doorCount字段的值,超出此范围时就抛出一个异常。

在使用CAuto类的方法定义中同样需要使用throws关键字指定可能抛出的异常类型,如下面的代码就是修改后的main()方法。

Java
public class Hello {
	public static void main(String[] args) throws Exception {
		CAuto car = new CAuto();
		car.setDoorCount(10);
		System.out.println(car.getDoorCount());
	}
}

执行代码会抛出下图中的异常信息。

当设置车门数在0到5时,代码就没有问题了,如下面的代码。

Java
public class Hello {
	public static void main(String[] args) throws Exception {
		CAuto car = new CAuto();
		car.setDoorCount(2);
		System.out.println(car.getDoorCount());
	}
}

执行代码,会显示数字2。

方法

类中,方法用于执行特定的任务,其应用要素包括:访问级别等修饰符、返回值类型、方法名、参数、方法体,当然还可以使用throws关键字指定可能抛出的异常类型。下图中显示了main()方法的结构。

这里定义的要素包括:

  • 修饰符,指定main()方法为公共的(public)静态(static)方法,稍后会介绍静态成员的概念。
  • 返回值类型,这里指定void表示方法没有返回值。如果方法需要返回值,则可以指定为具体的类型,如int、long、CAuto等等。
  • 方法名,即main。
  • 参数,这里指定了一个参数args,类型为字符串数组,即String[],此参数可以将命令行参数带入main方法。
  • 可能抛出异常类型,使用throws关键字指定。
  • 方法体,方法执行任务的代码区域,如果需要返回数据,可以在方法体中使用return关键字完成。

下面的代码,我们定义了在个静态的add()方法,用于两个int整数的相加运算。

Java
public class Hello {
	public static void main(String[] args) throws Exception {
		int num1 = 10;
		int num2 = 99;
		System.out.println(add(num1, num2));
	}
		// int数据相加
	private static int add(int x, int y) {
		return x + y;
	}
}

执行代码会显示109。

方法重载是指,可以创建多个同名的方法,并定义不同的参数序列;在调用方法时,可以根据参数类型自动匹配最合适的那个版本。如下面的代码,我们又定义两个方法,分别用于long和double类型的加法运算。

Java
public class Hello {
	public static void main(String[] args) throws Exception {
		double num1 = 10.9;
		long num2 = 99;
		System.out.println(add(num1, num2));
	}
	// int数据相加
	private static int add(int x, int y) {
		System.out.println("int add method");
		return x + y;
	}
	// long数据相加
	private static long add(long x, long y) {
		System.out.println("long add method");
		return x + y;
	}
	// double数据相加
	private static double add(double x, double y) {
		System.out.println("double add method");
		return x + y;
	}
}

为了清楚到底调用了哪个方法,我们在重载的add()方法中添加了一个打印信息的语句。请注意main()方法中的代码,分别定义了一个double和一个long类型的数据,然后调用add()方法进行运算,代码执行结果如下图。

我们可以看到,一个double和一个long数据相加时,调用了两个double数据相加的add()方法,这也符合前面课程中提到的数据类型自动转换的基本原则。

定义函数的参数时,有一种情况是,数据的类型相同,但数量不一定,此时,可以使用可变长(variable-length)参数,如下面的代码,我们使用add()方法执行2个以上int数据的累计运算。

Java
public class Hello {
	public static void main(String[] args) throws Exception {
		System.out.println(add(1,2));
		System.out.println(add(1,2,3));
		System.out.println(add(1,2,3,4));
	}
	// int数据相加
	private static int add(int x, int y, int...moreNums) {
		int sum = x + y;
		for(int i=0;i<moreNums.length;i++)
			sum += moreNums[i];
		return sum;
	}
}

代码执行结果如下图。

本例的add()方法中,由于加法运算最少需要两个运算数,所以,前两个参数定义为int类型,第三个参数,int关键字后使用...符号指定这是一个数量不定int类型参数数组;方法体中,可变长参数可以使用从0开始的索引值访问,如果没有指定可变化参数,则它的length属性为0。

在main()方法中分别调用了2个、3个和4个参数的add()方法,我们可以看到,使用了可变长参数的add()方法都可以应对。

构造方法

前面的代码,在创建CAuto类实例时使用了CAuto()构造方法,但它并不是我们定义的,暂时就不考虑它从哪儿来了。

接下来,我们就要创建自定义的构造方法了,如下面的代码。

Java
public class CAuto {
	// 构造函数
	public CAuto(String sModel, int iDoorCount) throws Exception {
		model = sModel;
		setDoorCount(iDoorCount);
	}
	//
	public CAuto(String sModel) throws Exception {
		this(sModel, 4);
	}
	//
	public CAuto(int iDoorCount) throws Exception {
		this("", iDoorCount);
	}
	//
	public CAuto() throws Exception {
		this("", 4);
	}
	// 型号
	public String model;
	// 行驶方法
	public void drive() {
		System.out.println(model + "行驶中...");
	}
	// 车门数量
	private int doorCount;
	public void setDoorCount(int num) throws Exception {
		if(num>=0 && num<=5)
			doorCount = num;
		else
			throw new Exception("车门数量应该在0到5之间");
	}
	public int getDoorCount() {
		return doorCount;
	}
}

代码中,我们定义了四个构造方法,第一个定义了两个参数,分别指定车辆型号和车门数量,这是一个可以设置完整数据的构造方法;第二个和第三个构造方法使用了一个参数,分别只指定车辆型号和车门数量;第四个构造方法没有参数。其中,后三个构造方法都通过this关键字调用了第一个构造方法,this关键字表示当前类实例,可以方便在类中调用各种成员,在继承结构中,也可以通过this和super关键字来区分本类实例和超类实例,下一课会有相关介绍。

下面的代码,我们在main()方法中使用新创建的构造函数创建CAuto类的实例。

Java
public class Hello {
	public static void main(String[] args) throws Exception {
		CAuto car = new CAuto("C20");
		System.out.println(car.model);
		System.out.println(car.getDoorCount());
	}
}

代码执行结果如下图。

本例调用了包含车辆型号的构造方法,但它内部实际调用了CAuto(String,int)构造方法,车门数量默认设置为4。

静态成员

前面,在CAuto中定义的model属性、drive()方法、setDoorCount()方法、getDoorCount()方法等要素都属性实例成员,它们只能通过类的实例(对象)来调用。而另一种封装代码代码的形式是使用静态成员(也称为类成员),静态成员则直接使用类名访问,或者通过静态导入后直接使用。

在类中定义变量或对象时添加static关键字,就成为静态成员;同样的,在定义方法时使用static关键字就会成为静态方法。如下面的代码,我们在CAuto类中添加了一个静态字段和一个静态方法。

Java
public class CAuto {
	// 静态字段
	private static int objectCounter;
    // 静态方法
	public static int getCounter() {
		return objectCounter;
	}
    // 静态构造器
	static {
		objectCounter = 0;
	}
	// 构造函数
	public CAuto(String sModel, int iDoorCount) throws Exception {
		model = sModel;
		setDoorCount(iDoorCount);
		// 对象创建计数
		objectCounter++;
	}
    // 其它成员
}

本例中,我们定义和修改的静态相关代码包括:

  • 静态字段objectCounter,用于保存共创建了多少个CAuto类的实例。
  • 静态方法getCounter(),返回创建了多少个实例。这样,就只能在类的外部读取objectCounter字段的数据,而不能随意修改它。
  • 静态构造器,直接使用static关键字和一对花括号定义,用于静态数据的初始化操作。
  • 在主构造方法中,每次创建对象都会将objectCounter字段加1,这样就可以统计共创建了多个CAuto实例。由于基它构造方法实际了是调用主构造方法,所以,无论使用哪个构造,都会累计创建的对象数量。

下面的代码演示了这些成员的应用。

Java
public class Hello {
	public static void main(String[] args) throws Exception {
		CAuto car = new CAuto("C20");
		CAuto racer = new CAuto("couper-X1",2);
		CAuto suv = new CAuto("x-20",5);
		System.out.println(CAuto.getCounter());
	}
}

本例共创建了三个CAuto类的实例,分别是car、racer和suv,执行代码会显示3。

如果在类中定义常量,可以使用static final修饰符,如下面的代码,我们在CAuto中定义了一个最大车门数量的常量。

Java
public class CAuto {
	// 常量
	public static final int MAX_DOORS = 5;
    // 其它代码
}

静态导入

静态导入可以简化静态资源的访问,如下面的代码,我们使用静态导入来引用Math类。

Java
import static java.lang.Math.*;
public class Hello {
	public static void main(String[] args){
		System.out.println(PI);
		System.out.println(sqrt(9));
	}
}

本例,我们使用静态导入,导入了java.lang包中Math类的所有成员,然后在main()方法中显示了Math类中的PI常量值,以及9的算术平方根。代码执行结果如下图。

本站内容均为原创作品,转载请注明出处,本页面网址为:http://caohuayu.com/chy/article/Article.aspx?code=cc001007