Java核心技术阅读笔记(三)对象与类

对象与类

目录

  1. 面向对象程序设计概述
  2. 使用预定义类
  3. 用户自定义类
  4. 静态字段和静态方法
  5. 方法参数
  6. 对象构造
  7. JAR文件
  8. 文档注释

1. 面向对象程序设计概述

  • 相关概念
概念名 解释
class 构造对象的模版或蓝图
instance 由class构造对象的过程称为创建class的instance(实例)
encapsulation(封装) 将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式
实例字段 对象中的数据
方法 操作数据的过程
当前状态(state) 作为一个类的实例,特定对象都有一组特定的实例字段值,这些值的集合就是当前对象的状态
更改器方法 对访问对象进行修改的方法
访问器方法 只访问对象而不对对象进行修改的方法

实现封装的关键:绝对不能让类中的方法直接访问其他类的实例字段,程序只能通过对象的方法与对象数据进行交互。(意味着一个类可以完全改变存储数据的方式,而对外操作的接口并没有发生改变。)

  • 对象的三个主要特性
特性 解释
对象的behavior 可以对对象完成哪些操作,或者可以对对象应用哪些方法
对象的state 当调用那些方法时,对象会做出什么响应
对象的identity 如何区分具有相同行为和状态的不同对象
  • 识别类
    首先从识别类开始,然后再为各个类添加方法;在分析问题的过程中寻找名词,方法对应动词。对于每一个动词都要识别出完成该动作的对象。
  • 类之间的关系
关系 解释
依赖(uses-a) 一个类使用或操作另一个类的对象
聚合(has-a) 类A的对象包含类B的对象
继承(is-a) 类A扩展类B

2. 使用预定义类

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

  • 对象与对象变量
new Data();//构造器的名称应该与类名相同
Date birthday = new Data();//为了经常使用,把构造器构造的对象赋值给一个对象变量

如果没有对对象变量进行赋值的话不能使用这个对象的方法,出现编译错误。

3. 用户自定义类

  • 基本格式
    workhorse class(主力类):通常该类没有main方法,但是却有自己的实例字段和实例方法
class className{
   field1
   field2
   ...
   contructor1
   contructor2
   ...
   method1
   method2
}

  • 构造器
  1. 构造器与类同名
  2. 每个类可以有1个以上的构造器
  3. 构造器可以有0个、1个或多个参数
  4. 构造器没有返回值
  5. 构造器总是伴随着new操作符一起调用

  • 使用var声明局部变量
    1. 如果可以从变量的初始值推导出它们的类型,那么可以用var关键字声明局部变量,而无需指定类型。
    2. 如果不需了解任何Java API就能从等号右边明显看出类型,在这种情况下使用var
    3. var关键字只能用于方法中的局部变量,参数和字段的类型必须声明
    4. 一般对数值类型不会使用var

  • 使用null引用
    如果对null值使用一个方法,会产生一个NullPointerException异常。这是一个很严重的错误,类似“索引越界”。定义一个类时,最好清楚地知道哪些字段可能为null。
1. 宽容型
把null参数转换成一个适当的非null值
if(n==null) name="unknown"
else name = n;
java 9 提供了一个方法:
name = Objects.requireNonNullElse(n,"unknown");
2. 严格型
直接拒绝null参数
Objects.requireNonNull(n,"The name cannot be null");
name = n;

如果要接受一个对象引用作为构造参数,要考虑是不是真的希望接受可有可无的值,否则应该使用“严格法”

static <T> void requireNonNull(T obj);
static <T> void requireNonNull(T obj,String message);
static <T> void requireNonNull(T obj,Supplier<String> messageSupplier);
如果obj是null,这些方法会抛出一个NullPointerException异常而没有消息或给定的消息。
static <T> void requireNonNullElse(T obj,T defaultObj);
static <T> void requireNonNullElse(T obj,Supplier<T> messageSupplier);
如果obj不是null则返回obj,或者如果obj为null则返回默认对象

  • 隐式参数和显式参数
    隐式参数就是方法调用的目标或接受者。[this指定]
    显式参数就是形参列表中的参数

  • 封装的优点
如果想要获得或者设置实例字段的值,需要提供下面三项内容:
1. 一个私有的数据字段
2. 一个公共的字段访问器方法
3. 一个公共的字段更改器方法
⚠️不要编写一个返回可变对象引用的访问器方法
如果这样的话可以在类外对该对象进行更改,破坏了封装性。因为两个指针指向同一片区域。

如果需要返回一个可变对象的引用,首先应该对它使用clone,对象克隆是指存放在另一个新位置上的对象副本。
class Emploee{
 ...
 public Date getHireDay(){
   return (Date) hireDay.clone();
  }
}
⚠️如果需要返回一个可变数据字段的副本,就应该使用clone

  • 私有方法
    有时希望将一个计算代码分解成若干个独立的方法,通常这些辅助方法不应该成为公共接口的一部分,这是由于它们往往与当前的实现关系非常紧密,或者需要一个特殊协议或者调用次序。这样的方法设置为私有方法。
    ⚠️只要方法时私有的,类的设计者就可以确信它不会在别处被使用,所以可以将其删去;如果一个方法是公共的,就不能简单地将其删除,因为可能会有其他代码依赖这个方法。

  • final实例字段
    这样的字段必须在构造对象的时初始化。确保在每一个构造器执行之后,这个字段的值已经设置,并且以后不能再进行修改。
final修饰符对于类型为基本类型或者不可变类的字段尤其有用
如果类中的所有方法都不会改变其对象,这样的类就是不可变的类。对于可变的类如果使用final可能会造成混乱。

4. 静态字段与静态方法

  • 静态字段(类字段)
class Employee{
  private static int nextID = 1;
  private int id;
  ...
}

所有类的实例共享nextID字段,而有各自的id字段


  • 静态常量
public static final double PI = 3.14
这样可以直接在类外使用Math.PI

  • 静态方法
    静态方法是没有this参数的方法,不在对象上使用的方法。(在一个非静态方法中,this参数是隐式参数)
    ⚠️静态方法不能访问实例字段,因为它不能在对象上执行操作,但是静态方法可以访问静态字段。
可以直接使用类名来调用静态方法:
ClassName.staticMethod()
在下面两种情况下可以使用静态方法:
1. 方法不需要访问对象状态,因为它需要的所有参数都是通过显式参数提供
2. 方法只需要访问类的静态字段

  • 工厂方法
    静态方法的另一种常见用途,类似LocalDate和NumberFormat的类使用静态工厂方法来构造对象。生成不同风格的格式化对象。
    为什么不使用构造器完成操作呢?
1. 无法命名构造器。构造器的名字必须与类名相同,但是这里希望有两个不同的名字,分别得到货币实例和百分比实例
2. 使用构造器对象时,无法改变所构造对象的类型。而工厂方法实际上将返回DecimalFormat类的对象,这是一个NumberFormat的一个子类

  • main方法
    main方法不对任何对象进行操作。事实上,在启动程序时还没有任何对象,静态的main方法讲执行并构造程序所需的对象。因为要在没有实例的情况下使用main方法,也暗示了main方法时static的。
    ⚠️每个类中都可以有一个main方法可以用来对某个类进行测试(用于单元测试)
java Employee(待测试的类)

5. 方法参数

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

有两种类型的方法参数:
1. 基本数据类型(数字、布尔)
2. 对象引用

方法得到的是对象引用的副本,原来的对象引用和这个副本的对象副本都引用同一个对象。对象引用是按值传递的。

方法不能修改基本数据类型的参数
方法可以改变对象参数的状态
方法不能让对象参数引用一个新的对象

6. 对象构造

  • 重载
签名(signature)
要完整地描述一个方法,需要指定方法名以及参数类型(返回值不算)
var messages = new StringBuilder();
var todolist = new StringBuilder("to do");
重载解析:查找匹配的过程

  • 默认字段初始化
    如果在构造器中没有显式地为字段设置初值,那么就会自动地被赋为默认值:数值=0,boolean=false,对象引用=null
    ⚠️字段与局部变量的一个重要区别:方法中的局部变量必须明确地初始化,但是在类中,如果没有初始化类中的字段则将会自动地初始化为默认值。

  • 无参数的构造器
public Emploee(){
name = "";
salary = 0;
hireDay = LocalDate.now();
}

只有当class一个构造器都没有的时候系统才提供一个默认的构造器,其他情况都不会有,所以为了提供一个默认的,一般都自己写一个默认的。


  • 显式字段初始化
    就是直接在定义字段的时候赋给初值,在执行构造器之前完成这个操作,如果一个类的所有构造器都希望把某个特定字段设置为同一个值,这个语法就特别有用。初始值不一定是常量值:
class Employee{
private static int nextId;
private int id = assignId();
...
private static int assignId(){
  int r = nextId;
  nextId++;
  return r;
 }

  • 调用另一个构造器
this(其他构造器的参数格式);
//调用同一个类的另一个构造器

  • 初始化块
Java初始化数据字段的方法:
1. 在构造器中设置值
2. 在声明中赋值
3. 初始化块

首先运行初始化块,然后才运行构造器的主体部分。

⚠️调用构造器的具体步骤:

- 如果构造器的第一行调用了另一个构造器,则基于所提供的参数执行第二个构造器
- 否则,所有数据字段初始化为默认值;按照在类声明中出现的顺序,执行所有字段初始化方法和初始化块
- 执行构造器主体代码

7. 包

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

  • 类的导入
    一个类可以访问所属包中的所有类,以及其他包中的公共类。
 import packageName;
 import java.util.*;//只能用*导入一个包

在包中定位类是编译器的工作

  • 静态导入
    有一种import语句允许导入静态方法和静态字段,而不只是类。
import static java.lang.System.*;
//可以使用类中的静态方法和静态字段而不需要加类名前缀
  • 在包中增加类
    要想将类放入包中,就必须将包的名字放在源文件的开头,即放在定义这个包中各个类的代码之前。如果没有在源文件中放置package语句,这个源文件中的类就属于无名包。无名包没有包名。
package com.horstmann.corejava;
public class Employee{
...
}
  • 包访问
    标记为public的部分可以由任意类使用,标记为private的部分只能由定义它们的类使用,如果没有指定public/private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。
  • 类路径
    (pothole)
  • 设置类路径
    (pothole)

8. JAR文件

(pothole)

9.文档注释

  • 注释的插入
  • 类注释
  • 方法注释
  • 字段注释
  • 通用注释
  • 包注释
  • 注释抽取

你可能感兴趣的:(Java核心技术卷I)