【Java核心技术卷I笔记】第四章 类和对象

本文详细介绍了面向对象编程的概念,以Java为例,讲解了类、对象、封装、继承等核心概念。类作为构造对象的模板,封装数据和行为,提供访问器和更改器方法。对象是类的实例,具有特定状态。文章还讨论了如何使用预定义类如Date和LocalDate,以及如何创建用户自定义类。此外,解释了静态域、静态方法、工厂方法和main方法的作用。最后,涉及对象构造、初始化块、方法参数、包管理和类路径等相关内容。
摘要由CSDN通过智能技术生成

对象与类

4.1 面向对象程序设计概述

面向对象程序设计(OOP)是当今主流的程序设计范型。Java是完全面向对象的
面向对象的程序由对象组成,每个对象包含对用户公开的特定功能和隐藏的实现部分。在OOP中,不必关心对象的具体实现,只要能满足用户需求即可

4.1.1 类

是构造对象的模板,由类构造对象的过程称为创建类的实例
封装从形式上看,是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式,对象中的数据称为实例域,操纵数据的过程叫做方法。对每个特定的类实例都有一组特定的实例域值,这些值的集合就是这个对象的当前状态
实现封装的关键在于绝不能让类中的方法直接访问其他类的实例域。程序仅仅通过对象的方法和对象数据进行交互。封装给对象赋予了“黑盒”特征。
继承是OOP的另一个工具。继承的类拥有被继承类的全部方法和属性,在新类中只需提供适用于这个新类的方法和数据域即可。

4.1.2 对象

4.1.3 识别类

4.1.4 类之间的关系

  • uses-a 依赖
  • has-a 聚合
  • is-a 继承

4.2 使用预定义类

Java中,没有类就无法做任何事。例如前面接触的Math类,但它不具有面向对象特征。下面给出更典型的Date类为例

4.2.1 对象与对象变量

要使用对象,就必须先构造对象,指定初始状态。然后应用对象方法。
Java中使用构造器构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。
构造器的名字和类名相同,因此Date类的构造器名为Date。要构造一个Date对象,需要在构造器前面加上new操作符

Date birthday = new Date();
// Date有toString方法 返回日期的字符串描述
System.out.println(birthday.toString());

请添加图片描述

4.2.2 Java类库中的LocalDate

标准Java类库包含两个类:用来表示具体时间点的Date类,以及日历表示法的LocalDate
应当使用静态工厂方法调用构造器

LocalDate.now()	// 返回调用这个构造器时的时间
LocalDate newYearsEve = LocalDate.of(1999, 12, 31);	// of方法,用年月日构造一个特定日期的对象

可以获取LocalDate对象的信息,修改信息

int year = newYearsEve.getYear();	// 1999
newYearsEve = newYearsEve.plusDay(1000);	// 增加1000天
4.2.3 更改器方法和访问器方法

更改器方法:改变原先对象的状态,而不是生成新的对象
访问器方法:只访问而不修改对象状态
一个简单的例子,感受使用类的接口编程

import java.time.DayOfWeek;
import java.time.LocalDate;

public class Test {
    public static void main(String[] args) {
        LocalDate date = LocalDate.now();
        int month = date.getMonthValue();
        int today = date.getDayOfMonth();

        date = date.minusDays(today-1); // 月初
        DayOfWeek weekday = date.getDayOfWeek();    // WEDNESDAY (...
        int value = weekday.getValue(); // 星期几 3-Wed

        System.out.println("Mon\tTue\tWed\tThu\tFri\tSat\tSun");
        for(int i=0; i<value-1; ++i){
            System.out.print("\t"); // 空出前面的部分
        }
        while(date.getMonthValue() == month){
            System.out.print(date.getDayOfMonth());
            if(date.getDayOfMonth() == today){
                System.out.print("*\t");
            }else{
                System.out.print("\t");
            }
            date = date.plusDays(1);
            if(date.getDayOfWeek().getValue() == 1) System.out.println();   // 换行
        }
        if(date.getDayOfWeek().getValue() != 1) System.out.println();
    }
}

请添加图片描述
请添加图片描述

4.3 用户自定义类

现在开始设计复杂应用程序所需的各种类,这些类通常没有main方法,但有自己的实例域和实例方法。
创建一个完整的程序,应当将若干类组合在一起,其中只有一个类有main方法

import java.time.LocalDate;

public class Test {
    public static void main(String[] args){
        Employee[] staff= new Employee[3];
        // 构造
        staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);
        staff[1] = new Employee("Harry Cracker", 50000, 1989, 10, 1);
        staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);
        // 调用raiseSalary方法
        for(Employee e : staff){
            e.raiseSalary(5);
        }
        // 调用访问器
        for(Employee e :staff){
            System.out.println("name = "+e.getName()+", salary = "+e.getSalary()+", hireDay = "+e.getHireDay());
        }
    }
}
class Employee {
    private String name;
    private double salary;
    private LocalDate hireDay;
    // 构造器
    public Employee(String n, double s, int year, int month, int day) {
        name = n;
        salary = s;
        hireDay = LocalDate.of(year, month, day);
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public LocalDate getHireDay() {
        return hireDay;
    }

    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }
}
  1. 方法标记为public意味着任何类的任何方法都可以调用这些方法
  2. private实例域确保只有Employee类自身的方法能够访问这些实例域,其他类的方法不可以
  3. 构造器与类同名。构造对象时,构造器会运行,将实例域初始化为所希望的状态。构造器总是伴随着new操作符的调用而执行,不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的。构造器没有返回值,一个类可以有多个构造器

4.3.5 隐式参数

public void raiseSalary(double byPercent) {
        double raise = this.salary * byPercent / 100;
        this.salary += raise;
    }
}
  1. 每一个方法中,关键字this表示隐式参数,为调用该方法的对象
  2. Java中所有方法都必须在类的内部定义。不同于C++,这些方法不一定inline。这取决于Java虚拟机

4.3.6 封装的优点

4.3.7 基于类的访问权限

一个方法可以访问所属类的所有对象的私有数据

class Employee{
	...
	public boolean equals(Employee other){
		return name.equals(other.name);
	}
}
// 调用
if(harry.equals(boss)){...}

这个方法访问harryboss的私有域,这是合法的,他们都是Employee对象

4.3.8 私有方法

private方法指出。
不会被外部的其他类调用

4.3.9 final实例域

将实例域定义为final,在构建对象时必须初始化,并且之后不能再修改。

4.4 静态域和静态方法

4.4.1 静态域

如果将域设置为static,每个类中只有一个这样的域。静态域属于类,不属于任何独立对象

4.4.2 静态常量

例如

public class Math{
	...
	public static final double PI = 3.14159265358979323846;
	...
}
// 可以直接用 Math.PI 获得这个常量

public class System{
	...
	public static final PrintStream out = ...;
	...
}

4.4.3 静态方法

静态方法是一种不能向对象实施操作的方法,例如Math类的pow方法就是一个静态方法。

Math.pow(x, a);	// 不使用任何Math对象

静态方法不能访问实例域,因为他不能操作对象;但是静态方法可以访问自身类中的静态域。可以通过类名调用
以下情况需要使用静态方法

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

4.4.4 工厂方法

静态方法常见用途:静态工厂方法。例如LocalDate.nowLocalDate.of方法构造对象
为什么不用构造器呢?

  1. 无法命名构造器,构造器的名字必须和类名相同,有时候希望采用不同的名字
  2. 使用构造器时无法改变对象的类型,在继承时可能需要构造子类对象

4.4.5 main方法

main方法也是一个静态方法,他不对任何对象进行操作。事实上在启动程序时没有任何一个对象,静态main方法将执行并创建程序所需的对象

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

以下代码总结了static用法以及main方法做单元测试

public class Test {
    public static void main(String[] args) {
        Employee[] staff = new Employee[3];
        staff[0] = new Employee("Tom", 40000);
        staff[1] = new Employee("Dick", 60000);
        staff[2] = new Employee("Harry", 65000);

        for (Employee e :
                staff) {
            e.setId();
            System.out.println("name="+e.getName()+", id="+e.getId()+", salary="+e.getSalary());
        }

        int n = Employee.getNextId();
        System.out.println("Next available id="+n);
    }
}
class Employee{
    private static int nextId = 1;
    private String name;
    private double salary;
    private int id;

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

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public int getId() {
        return id;
    }

    public void setId(){
        id = nextId ++;
    }

    public static int getNextId() {
        return nextId;
    }

    public static void main(String[] args) {
        Employee e = new Employee("Harry", 50000);
        System.out.println(e.getName()+" "+e.getSalary());
    }
}

请添加图片描述

4.5 方法参数

Java程序设计语言总是采用按值调用
然而方法参数有两种类型:基本数据类型、对象引用。
一个方法不可能修改一个基本数据类型的参数,而对象作为引用就不一样了。
方法得到对象引用的拷贝,对象引用以及它的拷贝同时引用一个对象
方法不能让对象参数引用一个新的对象

下面程序演示了参数调用

public class Test{
    public static void main(String[] args) {
        /*
        Test1: Methods can't modify numeric parameters
         */
        System.out.println("Testing tripleValue:");
        double percent = 10;
        System.out.println("Before: percent="+percent);
        tripleValue(percent);
        System.out.println("After: percent="+percent);

        /*
        Test2: Methods can change the state of object parameters
         */
        System.out.println("\nTesting tripleSalary:");
        Employee harry = new Employee("Harry", 50000);
        System.out.println("Before: salary="+harry.getSalary());
        tripleSalary(harry);
        System.out.println("After: salary="+harry.getSalary());

        /*
        Test3: Methods can't attach new objects to object parameters
         */
        System.out.println("\nTesting swap:");
        Employee a = new Employee("Alice", 70000);
        Employee b = new Employee("Bob", 60000);
        System.out.println("Before: a="+a.getName());
        System.out.println("Before: b="+b.getName());
        swap(a, b);
        System.out.println("After: a="+a.getName());
        System.out.println("After: b="+b.getName());
    }

    public static void tripleValue(double x){   // doesn't work
        x = 3*x;
        System.out.println("End of method: x="+x);
    }

    public static void tripleSalary(Employee x){    // works
        x.raiseSalary(200);
        System.out.println("End of method: salary="+x.getSalary());
    }

    public static void swap(Employee x, Employee y){
        Employee temp = x;
        x = y;
        y = temp;
        System.out.println("End of method: x="+x.getName());
        System.out.println("End of method: y="+y.getName());
    }

}

class Employee{
    private String name;
    private double salary;

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

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public void raiseSalary(double byPercent){
        double raise = salary*byPercent/100;
        salary += raise;
    }
}

请添加图片描述

4.6 对象构造

4.6.1 重载

多个方法有相同的名字,不同的参数,便产生了重载。编译器根据各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配挑出相应的方法。
Java允许重载任何方法而不只是构造器方法。完整描述一个方法需要指出方法名和参数类型,称为方法的签名。返回类型不属于签名的一部分,因此不能有两个名字相同、参数类型相同却返回不同类型值的方法。

4.6.2 默认域初始化

构造器中没有显式地给实例域赋值,那么会自动地赋为默认值:数值为0,布尔值为false,对象引用为null
不明确地对域初始化,不是一种好习惯。

4.6.3 无参数的构造器

  1. 当且仅当类没有提供任何构造器时,系统会自动提供一个无参数构造器,默认初始化实例域
  2. 如果提供了至少一个构造器,但没有提供无参数的构造器,那么构造对象时没有提供参数不合法。

4.6.4 显式域初始化

可以在类定义中,直接将一个值赋给任何域,例如

class Employee{
	private String name = "";
	...
}

在执行构造器之前,先执行赋值操作。
初始值不一定是常量值,可以调用方法对域进行初始化

class Employee{
	private static int nextId = 0;
	// 调用方法赋值初始化实例域
	private int id = assignId();
	...
	private static int assignId(){
		int r = nextId;
		nextId ++;
		return r;
	}
	...
}

4.6.5 参数名

为了可读性,建议参数变量用同样的名字将实例域屏蔽起来,实例域采用this.访问

4.6.6 调用另一个构造器

this的另一个含义是调用同一个类的另一个构造器,例如

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

4.6.7 初始化块

前面讲过两种初始化数据域的方法:

  1. 在构造器中设置
  2. 在声明中赋值

实际上Java提供第三种机制,称为初始化块
在一个类的声明中可以包含多个代码块,只要在构造类的对象,这些块就会被执行。例如

class Employee{
	private static int nextId;
	private int id;
	// 初始化块,建议放在域定义后面
	{
		id = nextId;
		nextId ++;
	}
	// 方法
	...
}

如果对静态域进行初始化的代码比较复杂,可以使用静态的初始化块

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

下面的程序演示了上面几种特性

import java.util.Random;

public class Test {
    public static void main(String[] args) {
        Employee[] staff = new Employee[3];
        staff[0] = new Employee("Harry", 40000);
        staff[1] = new Employee(60000);
        staff[2] = new Employee();
        for (Employee e : staff){
            System.out.println("name = "+e.getName()+", id = "+e.getId()+", salary = "+e.getSalary());
        }
    }
}

class Employee{
    // 静态域
    private static int nextId;
    // 实例域
    private int id;
    private String name = "";   // 声明中初始化
    private double salary;
    // 静态初始化块
    static{
        Random generator = new Random();
        nextId = generator.nextInt(10000);  // 0-9999
    }
    // 初始化块
    {
        id = nextId;
        ++ nextId;
    }
    // 三个重载的构造器
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    public Employee(double salary) {
        this("Employee #"+nextId, salary);   // 通过this调用另一个构造器
    }
    public Employee() {
        // 默认初始化
        // name-""
        // salary-0
        // id-初始化块
    }
    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }
}

请添加图片描述

4.6.8 对象析构和finalize方法

Java有自动的垃圾回收器,不支持析构器
某些对象使用了内存资源之外的其他资源,资源不再被需要时,需要回收。
可以为任何一个类添加finalize方法。他将在垃圾回收器清除对象之前被调用。实际中不建议使用
如果某个资源在使用完毕之后需要立刻被关闭,那么需要人工管理,可以用close方法完成。(7.2.5)

4.7 包

Java允许使用将类组织起来。借助包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理
标准Java类库分布在多个包中,包括java.langjava.utiljava.net等。标准Java包具有一个层次结构
如同硬盘目录嵌套,也可以使用嵌套层次组织包。所有Java包都处于javajavax包层次中
使用包的主要目的是确保类名的唯一性。建议将公司的因特网域名以逆序形式作为包名
从编译器角度,嵌套的包没有任何关系,例如java.util包和java.util.jar毫无关系,每一个都有独立的类集合

4.7.1 类的导入

一个类可以使用所属包中的所有类,以及其他包中的共有类
可以使用import语句导入类,就不用每次都写出类的全称
import语句位于源文件顶部,但在package语句后面
例如import java.util.*导入java.util包中所有的类
需要注意的是,只能用星号导入一个包,而不能import java.*导入以java为前缀的所有包

发生命名冲突时,就需要考虑包的名字了。例如java.utiljava.sql都有Date
如果只需要用一个Date,那么可以

import java.util.*;
import java.sql.*;
import java.util.Date;

如果都需要用,那么只能在类名前加完整包名了 TAT

4.7.2 静态导入

import除了可以导入类,还可以导入静态方法和静态域

import static java.lang.System.*;

out.println("Goodbye, world");	// System.out
exit(0);	// System.exit

4.7.3 类放入包中(待补充

package语句。如果不添加,那么就是放在一个默认包中

4.8 类路径(待补充

4.9 文档注释(待补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值