Java核心技术(三):对象与类

一、面向对象程序设计(OOP)概述

  面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。程序中的很多对象来自标准库,还有一些是自定义的。在 OOP中,不必关心对象的具体实现,只要能够满足用户的需求即可。
  对于一些规模较小的问题,将其分解为过程的开发方式比较理想。而面向对象更加适用于解决规模较大的问题。

1.类

  • 类与对象:类是抽象的,创建类的实例(即对象)的过程是具体的。
  • 封装:从形式上看,封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。关键在于绝对不能让类中的方法直接地访问其他类的实例域。程序仅通过对象的方法与对象数据进行交互。封装给对象赋了了"黑盒"特征,这是提高重用性和可靠性的关键。
  • 实例域、状态、方法:对象中的数据称为实例域,操纵数据的过程称为方法。对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态。无论何时,只要向对象发送一个消息,它的状态就有可能发生改变。
  • 继承:可以通过扩展一个类来建立另外一个新的类。在 Java中,所有的类都源自于一个"神通广大的超类",Object类。在扩展一个已有的类时,这个扩展后的新类具有所扩展的类的全部属性和方法。在新类中,只需提供适用于这个新类的新方法和数据域就可以了。

2.对象

  三个主要特征:

  • 对象的行为:方法
  • 对象的状态:实例域
  • 对象标识

3.类之间的关系

  • 依赖(use a):如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另一个类。我们设计类时,应尽可能地减少类之间的依赖,也就是降低耦合度
  • 聚合(has a):类A的对象包含类B的对象。
  • 继承(is a):如果类 A扩展类 B,类 A 不但包含从类B继承的方法,还会拥有一些额外的功能。

二、使用预定义类

  • 实例化与初始化:

  通过构造器对对象进行实例化及初始化,就得到了该类的一个对象。
  一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。在 Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new操作符的返回值也是一个引用。下列语句∶

	Date deadline= new Date();

  有两个部分。表达式 new Date()构造了一个 Date类型的对象,并且它的值是对新创建对象的引用。这个引用存储在变量 deadline 中。
  可以显式地将对象变量设置为 null,表明这个对象变量目前没有引用任何对象。但如果将一个方法应用于一个值为 null的对象上,那么就会产生运行错误。

	deadline= null;

  Java中的对象变量就相当于C++中的对象指针。

  所有的 Java 对象都存储在堆中。当一个对象包含另一个对象变量时,这个变量依然包含着指向另一个堆对象的指针。

  在 C++ 中,指针十分令人头疼,并常常导致程序错误。在 Java语言中,如果使用一个没有初始化的指针,运行系统将会产生一个运行时错误,而不是生成一个随机的结果。同时,不必担心内存管理问题,垃圾收集器将会处理相关的事宜。

  C++ 确实做了很大的努力,它通过拷贝型构造器和复制操作符来实现对象的自动拷贝。在 Java中,必须使用 clone 方法获得对象的完整拷贝。

  • 更改器方法与访问器方法:

  对实例域做出修改的方法称为更改器方法(set方法,add方法等),仅访问实例域而不进行修改的方法称为访问器方法(get方法)。

三、用户自定义类

  • 构造器与类同名,每个类可以有一个以上的构造器,构造器可以有0个、1个或多个参数,构造器没有返回值,构造器总是伴随着 new操作一起调用。
  • 不要在构造器中定义与实例域重名的局部变量。
  • 隐式参数与显式参数:
	class Employee{
    
    
		private double salary;
		……
		public void raiseSalary(double raise){
    
    //这里的raise是显式参数
			//这里的salary前面其实有一个隐式参数,是Employee的实例化对象
			salary+=raise;
			//这句可以写成this.salary+=raise;
			//this用来表示隐式参数
		}
		……
	}
  • 封装具有多种优点,因此设计一个类时应设计私有数据域、构造器、更改器方法、访问器方法。
  • 如果需要返回一个可变对象的引用,或需要返回一个可变数据域的拷贝,应该首先对它进行克隆clone。
  • 注意不要编写返回引用可变对象的访问器方法。
  • 一个方法可以访问所属类的所有对象的私有数据。
  • 私有方法:一般类方法是由public关键字修饰的,用private关键字修饰的方法为私有方法。只要一个方法是私有的,类的设计者就可以确信它不会被外部的其他类操作调用,可以将其删去。如果方法是公有的,就不能将其删去,因为其他的代码很可能依赖它。
  • 可以将实例域定义为 final:必须确保在每一个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够再对它进行修改。final 修饰符大都应用于基本类型域,或不可变类的域(如果类中的每个方法都不会改变其对象,这种类就是不可变的类。例如String类)。

四、静态域与静态方法

1.静态域

  用static关键字修饰一个域,则该域属于类域,而不属于任何一个实例化对象,它的值可由所有该类的实例化对象共享。

2.静态常量

  在静态域中,有一种特别的域叫静态常量,即用static和final关键字修饰域,表明该域不仅是专属于类的,而且还是赋值一次就无法再次更改的。

3.静态方法

  • 用static关键字修饰一个方法,使得该方法不能向对象实施操作。
  • 可以用类名来调用该方法,也可以通过该类的实例化对象来调用(但不建议这样做,因为该方法与此对象毫无关系)
  • 因为静态方法不能操作对象,所以不能在静态方法中访问实例域。但是,静态方法可以访问自身类中的静态域。

  综上,使用静态方法的场合为:

  • 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供。
  • 一个方法只需要访问类的静态域。

五、方法参数

  • Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。
  • 然而,方法参数共有两种类型∶基本数据类型、对象引用。一个方法不可能修改一个基本数据类型的参数,而对象引用作为参数就不同了,尽管方法不能改变引用类型参数的引用指向,但却能更改其指向的基本类型的值。

六、对象构造

1.方法重载

  如果同一个类中的多个方法有相同的名字、不同的参数,便产生了重载。编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。如果编译器找不到匹配的参数,或者找出多个可能的匹配,就会产生编译时错误,这个过程被称为重载解析。

  返回类型不是方法签名的一部分。也就是说,不能有两个名字相同、参数类型也相同却返回不同类型值的方法。但是两个重载方法之间返回类型可以不同。

2.默认域初始化

  如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值∶数值为 0、布尔值为 false、对象引用为null。然而,这并不是一种良好的编程习惯。我们应该在构造器中显式地对域进行初始化。

3.无参数的构造器

  如果在编写一个类时没有编写构造器,那么系统就会提供一个无参数构造器。这个构造器将所有的实例域设置为默认值。于是,实例域中的数值型数据设置为 0、布尔型数据设置为 false 、所有对象变量将设置为 null。
  如果类中提供了至少一个构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法。

4.显式域初始化

  由于类的构造器方法可以重载,所以可以采用多种形式设置类的实例域的初始状态。确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值。这是一种很好的设计习惯。

  初始值不一定是常量。在下面的例子中,可以调用方法对域进行初始化。

	class Employee{
    
    
		private static int nextId;
		private int id=assignId();
		……
		private static int assignId(){
    
    
			int r= nextId;
			++nextId;
			return r;
		}	
		……
	}

5.用this关键字调用另一个构造器

  注意,该语句只能在构造器的第一句出现。

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

  采用这种方式使用 this 关键字非常有用,这样对公共的构造器代码部分只编写一次即可。

6.初始化块

  请看下面一段代码:

	class Employee{
    
    
		private int nextId;
		private int id;

		//初始化块
		{
    
    
			id= nextId;
			++nextId;
		}

		……
	}
  • 初始化块建议写在域定义之后。
  • 由于初始化数据域有多种途径,所以列出构造过程的所有路径可能相当混乱,下面是调用构造器的具体处理步骤∶

①所有数据域被初始化为默认值(0、false 或 null);
②按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块。
③如果构造器第一行调用了第二个构造器,则执行第二个构造器主体。
④执行这个构造器的主体。

  • 如果对类的静态域进行初始化的代码比较复杂,那么可以使用静态的初始化块,在初始化块的基础上,只需要在前面写一个static就可以了。

七、包

  • 使用包的主要原因是确保类名的唯一性。
  • 从编译器的角度来看,嵌套的包之间没有任何关系。

1.类的导入

  一个类可以使用所属包中的所有类,以及其他包中的公有类。使用其他包的公有类的方式如下:

  • 在程序中,在每个用到类的地方加上完整包名。
  • 使用 import 语句导入一个特定的类或者整个包,使用时可直接只使用类名。如下:
	import bao.*;
	import bao.lei;

2.静态导入

	import static java.lang.System.*;//导入System类中的所有静态方法和静态域,使用时不必再加类名
	import static java.lang.System.out;//导入类中特定的方法或域

3.将类放入包中

	//在程序前面写上这一句
	package com.horstmann.corejava;

  编译程序时应当写上完整的包前缀。

4.包作用域

  前面已经接触过访问修饰符 public 和 private。标记为 public 的部分可以被任意的类使用;标记为 private 的部分只能被定义它们的类使用。如果没有指定 public 或 private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。

猜你喜欢

转载自blog.csdn.net/Tracycoder/article/details/112234600