Java核心技术——第4章

面向对象程序设计概述

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

从根本上说,只要对象满足要求,不必关心其功能的具体实现过程。

与传统的结构化程序区别:

  • 传统的结构化程序通过一系列过程(算法)来求解问题,算法是第一位,数据结构第二位
  • OOP则是数据第一位,然后考虑操作数据的方法

相关概念

  • 类:构造对象的模板或蓝图

  • 封装:将数据和行为组合在一个包中,并对对象的使用者隐藏数据的实现方式

    封装的关键:绝不能让类中的方法直接访问其他类的实例域

  • 继承:拓展一个类来建立另外一个类的过程

如何识别类

分析问题时找名词和动词,名词对应类,动词对应方法

对象

三个主要特性

  • 对象的行为:由可调用的方法决定
  • 对象的状态:状态的改变必须通过调用方法实现——封装的体现
  • 对象的标识:同一个类创建的实例,标识永远是不同的

类与类之间的关系

  • 依赖(uses-a):一个类的方法操纵另一个类的对象,应尽可能避免这种关系
  • 聚合(has-a):类A的对象包含类B的对象
  • 继承(is-a):表示特殊与一般的关系

使用预定义类

对象与对象变量

  • 构造器:特殊的方法,名字与类名相同,用于构造对象

    new Date()
    
  • 对象变量:引用一个对象,并不是包含一个对象,类似于指针

    Date birthday = new Date();
    

    局部变量不会自动初始化为null,必须通过调用new或设置为null进行初始化

更改器方法和访问器方法

  • 更改器方法:调用后对象的状态改变
  • 访问器方法:调用后对象的状态不变,只访问不修改

用户自定义类

一个源文件中只能有一个公有类,但可以有任意数量的非公有类

如果将每个类单独放在一个源文件中时,可以用通配符一起编译,也可以只编译共有类所在的源文件

关键字public:任何类的任何方法都可以调用

关键字private:只有本类中的方法可以访问

构造器

  • 构造器与类同名
  • 构造器总是伴随着new操作符的执行被调用,而不能对一个已存在的对象调用
  • 不要在构造器中定义与实例域重名的局部变量

隐式参数与显式参数

number007.raiseSalary(5);
  • 隐式参数:方法的调用者,不出现在方法的声明中,即上面调用代码中的number007,在方法声明时可用关键字this表示隐式参数
  • 显式参数:明显地列在方法声明中的参数

封装的优点

要获取或设置实例域的值,应提供:

  1. 一个私有的数据域
  2. 一个公有的访问器方法——可以改变方法的内部实现,而不会影响其他代码
  3. 一个公有的更改器方法——可以执行错误检测

注:不要编写返回引用可变对象的访问器方法

例:

class Employee{
	private Date hireDay;
	public Date getHireDay()
	{
		return hireDay;
	}
}

其中Data类对象是可变的,如果返回的hireDay改变,则会同时改变类中的私有数据域,破坏了封闭性

如果要返回一个可变对象的引用,则首先对它进行克隆

final实例域

  • 应用于基本类型域、不可变类的域:相当于是常量,其值不能改变

    不可变类:类中的每个方法都不会改变其对象

  • 应用于可变域:相当于c中const int*指针,不可以指向其他的对象,但是修改它指向的对象

静态域与静态方法

静态域

关键字static,类的所有实例共享静态域,属于类,不属于任何一个独立对象,可以通过类名来访问

静态常量

public class Math
{
	public static final double PI = 3.14;
}

可采用Math.PI访问

如果没有static,则变成Math的实例域,需要Math类对象访问,并且每个对象都有一份拷贝

静态方法

静态方法是一种不能向对象实施操作的方法,不能访问实例域,因为只有对象才能访问实例域,但可以访问自身类中的静态域

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

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

关键词static含义:属于类但不属于对象的变量和函数

工厂方法

也属于静态方法,直接使用类名调用,用于构造对象

为何不用构造器?

  1. 无法命名构造器,该类不同种类的构造方法,要加以区分则使用工厂方法
  2. 使用构造器时,无法改变所构造的对象类型

main方法

main方法也是静态方法

main方法不对任何对象进行操作

每个类都可以有一个main方法,常用于对类进行单元测试

方法参数

  • 按值调用:方法接收的是调用者提供的值
  • 按引用调用:方法接收的是调用者提供的变量地址

Java总是按值调用,即方法得到的时所有参数值的一个拷贝

注意Java的对象引用

public static void swap(Employee x,Employee y)
{
	Employee tmp=x;
	x=y;
	y=tmp;
}

这是不能交换x,y所引用的对象,只是交换了x,y的拷贝

image-20200412095509885

总结:

  • 一个方法不能修改一个基本数据类型的参数(数值型或布尔型)
  • 一个方法可以改变一个对象参数的状态
  • 一个方法不能让一个对象参数引用一个新的对象

对象的构造

重载

如果多个方法有相同的名字、不同的参数,则产生重载,返回类型不同不可以,即不能有两个名字相同、参数类型相同,但返回类型不同的方法

默认域初始化

如果没有在构造器中显式给域赋予初值,则会自动被赋为默认值(数值0,布尔值false,对象引用null)

区别于局部变量,局部变量必须被明确的初始化

无参数的构造器

类中没有构造器时,系统会提供一个默认的无参数构造器

类中一旦有构造器,则系统提供的构造器无效

显式域初始化

可以给任何域显式的赋予初值,这样的赋值会在构造器之前执行,而且赋值不一定是常数,可以调用方法进行初始化

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

参数名

参数变量如果和实例域的名称相同,则会覆盖实例域,此时要想访问实例域需要通过this关键词

好的习惯:

在参数前加前缀”a“

public Employee(String aName,double aSalary)
{
	name = aName;
	salary = aSalary;
}

或者通过this

调用另一个构造器

如果构造器的第一个语句形如this(...),这个构造器将调用同一个类的另一个构造器

public Employee(double s)
{
	this("Employee #"+nextId,s);
	nextId++;
}

初始化块

设置一个代码块,在其中放入域初始化语句,则可在构造器执行之前对域进行初始化

调用构造器的具体步骤:

  1. 所有数据域被初始化为默认值
  2. 按照在类中出现的次序,依次执行所有域初始化语句和初始化块
  3. 如果构造器第一行调用了第二个构造器,则执行第二个构造器
  4. 执行这个构造器主体

一般通过提供一个初始化值初始化静态域,如果初始化语句比较复杂,则可以使用静态初始化块

  • Java允许用包(package)将类组织起来,方便组织自己的代码
  • 使用包主要原因是确保类名的唯一性
  • 从编译器的角度看,嵌套包之间没有任何联系
  • 包的命名习惯:以域名的倒序作为包名——保证包名的唯一性

类的导入

一个类可以使用所属包中的所有类,以及其他包中的公有类

如何访问公有类:

  1. 在类名前加上完整的包名

  2. 使用import导入相关的类,然后就可以直接使用类中的方法

    注:import 可以用*导入多个类,但是不能导入多个包

如果导入的包中含有同名的类怎么办:

  1. 如果只需使用其中一个类,则再次import这个特定的类
  2. 如果两个类都要使用,则在使用前加上完整的包名

静态导入

import 不仅可以导入类,还能导入静态方法和静态域

注意:不能静态导入类!!

import static java.lang.Math.*;

这样就不用加类名前缀了,如sqrt(pow(x,2)+pow(y,2))

将类放入包中

要想将一个类放入包中,就必须将包的名字放在源文件的开头

package com.horstmann.coreJava;

对应的源文件会被放在com/horstmann/coreJava目录下

包作用域

类、方法、变量:

  • public:可以被任意类使用
  • private:只能被定义它们的类使用
  • 没有指定:可以被同一个包中的所有方法访问

类设计技巧

  1. 一定要保证数据私有

  2. 一定要对数据初始化

  3. 不要在类中使用过多的基本类型

    用其他类代替多个相关的基本类型

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

  5. 将职责过多的类进行分解

  6. 类型和方法名要能体现它们的职责

  7. 优先使用不可变的类

    更改对象的问题在于:如果多个线程试图同时更新一个对象,就会发生并发更改,其结果不可预料

原文地址:https://www.cnblogs.com/xkf97/p/12688338.html