Java核心技术之第四章——对象与类

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

类是构造对象的模板或蓝图。

由类构造对象的过程称为创建类的实例。

封装是与对象有关的一个重要概念。封装不过是将数据行为组合在一个中,并对对象的使用者隐藏了数据的实现方式。

对象中的数据称为实例域操纵数据的过程称为方法对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态。无论何时,只要向对象发送一个消息,它的状态就有可能发生改变。

实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域。程序仅通过对象的方法与对象数据进行交互。封装给对象赋予了“黑盒”特征。

使用预定义类

并不是所有的类都具有面向对象特征。如Math类,在程序中可以使用Math类的方法(如Math.random),并只需要知道方法名和参数,而不必了解它的具体实现过程。这正是封装的关键。

对象与对象变量

要想使用对象,就必须首先构造对象,并指定其初始状态。然后,对对象应用方法。

在Java程序设计语言中,使用构造器构造新实例构造器是一种特殊的构造方法,用来构造并初始化对象。构造器的名字应该与类名相同。因此Date类的构造器名为Date。要想构造一个Date对象,需要在构造器前面加上new操作符。如:new Date(),这个表达式构造了一个新对象。这个对象被初始化为当前的日期和时间。

//如果需要的话,也可以将这个对象传递给一个方法:
System.out.println(new Date());       //需要导入java.util.Date包

也可以将一个方法应用于刚刚创建的对象。Date类中有一个toString方法,该方法将返回日期的字符串描述。

String s = new Date().toString();

在这两个例子中,构造的对象仅使用了一次。通常,希望构造的对象可以多次使用,因此,需要将对象存放在一个变量中:

Date birthday = new Date();
//引用新构造的对象变量birthday

在对象与对象变量之间存在一个重要的区别:

Date deadline;    //定义了一个对象变量deadline,它可以引用Date类型的对象。

但是,一定要认识到:变量deadline不是一个对象,实际上也没有引用对象。此时并不能将任何Date方法应用于这个变量中。

s = deadline.toString();
//会产生编译错误

必须首先初始化变量deadline。

//方法一、用新构造的对象初始化这个变量:
    deadline = new Date();
//方法二、让这个变量引用一个已存在的对象:
    deadline = birthday;

现在,这两个对象变量(birthday和deadline)引用同一个对象Date(Date)

一定要认识到:一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。

在Java中,任何对象变量的值都是对存储在另外一个地方的的一个对象的引用。new操作符的返回值也是一个引用。

Dead deadline = new Date();
//表达式new Date()构造了一个Date类型的对象,并且它的值是对新创建对象的引用。一个引用存储在变量deadline中。

可以显式地将对象变量设置为null,表明这个对象变量目前并没有引用任何对象。

deadline = null;

如果将一个方法应用于一个值为null的对象上,那么就会产生运行时错误。

用户自定义类

构造器

Java的所有对象都是在堆中构造的。

public class EmployeeTest {        //在一个源文件中,只能有一个公有类,但可以有任意数目的非公有类
	public static void main(String[] args) {
		Employee[] staff = new Employee[2]; // 创建对象数组
		staff[0] = new Employee("LBJ", 75000);    //构造器总是伴随着new操作一起调用
		staff[1] = new Employee("KD", 60000);
		for (Employee e : staff) {
			System.out.println(e.toString());
		}
	}
}

class Employee {
	private String name;    //实例域
	private double salary;

	public Employee(String n, double s) {    //Employee构造器与类同名,可以有0个、1个或多个参数
		name = n;
		salary = s;    //构造器没有返回值
      //String name = n;   //Error。不要在构造器中定义与实例域重名的局部变量。此处声明局部变量name,此变量只能在构造器中内部访问,因为其屏蔽了同名的实例域。
	}

	@Override
	public String toString() {
		return "Employee [name=" + name + ", salary=" + salary + "]";
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}

构造器(即构造方法)与其他的方法有一个重要的不同。构造器总是伴随着new操作符的执行被调用,而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的。

总结:

1、构造器与类同名

2、每个类可以有一个以上的构造器

3、构造器可以有0个、1个或多个参数

4、构造器没有返回值

5、构造器总是伴随着new操作一起调用

封装的优点

以下是典型的访问器方法。由于它们只返回实例域值,因此又称为域访问器

public String getName() {
	return name;
}

在有些时候,需要获得或设置实例域的值。因此,应该提供下面三项内容:

1、一个私有的数据域;

2、一个公有域的访问器方法;

3、一个公有域的域更改器方法。

虽然这样做比提供一个简单的公有数据域复杂些,但却有下列明显的好处:

1、可以改变内部实现,除了该类的方法之外,不会影响其他代码。

//例如,将存储名字的域改为:
String firstname;
//那么getName方法可以改为:
public String getFirstname() {    //通过eclipse的getter和setter即可
	return firstname;
}
//那么toString方法则改为:
public String toString() {
	return "Employee [name=" + firstname + ", salary=" + salary + "]";
}

对于这点改变,程序的其他部分完全不可见。

2、为了进行新旧数据表示之间的转换,访问器方法和更改器方法有可能需要做许多工作。但是,这又为我们带来了第二点好处:更改器方法可以执行错误检查,然而直接对域进行赋值将不会进行这些处理。

注意不要编写返回引用可变对象的访问器方法,否则会破坏封装性。如果需要返回一个可变对象的引用,应该首先对它进行克隆。凭经验可知,如果需要返回一个可变数据域的拷贝,就应该使用clone

final实例域

对于可变的类,使用final修饰符可能会对读者造成混乱。例如:

private final StringBuilder evaluations;

在Employee构造器中会初始化为

evaluations = new StringBuilder();

final关键字只是表示存储在evaluation变量中的对象引用不会再指示其他StringBuilder对象。不过这个对象可以更改:

public void giveGoldStar(){
    evaluations.append(LocalDate.now() + " : Gold star!\n");
}

静态域

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

//假定需要给每一个雇员赋予唯一的标识码。这里给Employee类添加一个实例域id和静态域nextId:
class Employee{
    private static int nextId = 1;
    private int id;
    ...
}

现在,每一个雇员对象都有一个自己的id域,但这个类的所有实例将共享一个nextId域。换句话说,如果有1000个Employee类的对象,则有1000个实例域id;但是,只有一个静态域nextId。即使没有一个雇员对象,静态域nextId也存在。

总结:静态域属于类,而不属于任何独立的对象。

静态域常量

例子:System.out        他在System类中声明如下:

public class System{    //由于每个类对象都可以对公有域进行修改,所以,最好不要将域设计为public
    ...
    public static final PrintStream out = ...;    //而公有常量(即final域)却没问题。因为out被声明为final,所以不允许再将其他打印流赋给它
    ... 
}
    System.out = new PrintStream(....);    //Error-----out是公有常量

System类中有一个“特例”——setOut方法,它可以将System.out设置为不同的流。这是因为setOut方法是一个本地方法,而不是用Java语言实现的。本地方法可以绕过Java语言的存取控制机制。

静态方法

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

1、一个方法不需要访问对象状态,其所需参数都是通过显式参数(显式参数即方法名后面括号中的数值,隐式参数即关键字this所表示的参数)提供(如:Math.pow)

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

方法参数

方法参数共有两种类型:

  1. 基本数据类型(数字、布尔值)
  2. 对象引用

在程序设计语言中将参数传递给方法(或函数的专业术语):

  1. 按值调用:表示方法接收的是调用者提供的值。
  2. 按引用调用:表示方法接收的是调用者提供的变量地址

个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。

如果想编写一个修改数值参数值的方法,就需要使用在org.omg.CORBA包中定义的持有者(holder)类型,包括IntHolder、BooleanHolder等。每个持有者类型都包含一个公有(!)域值,通过它可以访问存储在其中的值。

public static void triple(IntHolder x){
    x.value = 3*x.value;
}

Java程序设计语言总是采用按值调用,也就是说方法得到的是所有参数值的一个拷贝。特别是,方法不能修改传递给它的任何参数变量的内容

public void raiseSalary(double byPercent){
    double raise = salary.byPercent/100;
    salary += raise; 
}
public static void tripleSalary(Employee x){
    x.raise.Salary(200);
}

//当调用
    harry = new Employee(...);
    tripleSalary(harry);

/*
  具体的执行过程为:
    1、x被初始化为harry值的拷贝,这里是一个对象的引用
    2、raiseSalary方法应用于这个对象引用。x和harry同时引用的那个Employee对象的薪金提高了200%
    3、方法结束后,参数变量x不再使用。当然,对象变量harry继续引用那个薪金增至3倍的雇员对象
*/

方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。

总结:

  1. 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
  2. 一个方法可以改变一个对象参数的状态。
  3. 一个方法不能让对象参数引用一个新的对象。
发布了50 篇原创文章 · 获赞 33 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/leilei107/article/details/94063094