Core Java 笔记(三)

对象与类

 

三个重要概念:

  • 封装

  • 继承

  • 多态

类之间的常见关系有:

  • 依赖(uses-a):一个类的方法操纵另一个类的对象

  • 聚合(has-a):一个类的对象包含另一个类的对象

  • 继承(is-a):一个类是另一个类的拓展(子集)

应尽可能将相互依赖的类减至最少,用软件工程的术语来说,就是让类的之间的耦合度最小

表达类关系的统一建模语言符号:

区分对象引用和对象实体

一个对象变量(引用)并没有实际包含一个对象,而仅仅存放了这个对象实体的内存地址,换句话说,任何对象变量的值都是对存储在堆(heap)中的一个对象的引用。可以将 Java 的对象变量看作 C++ 的对象指针(而不是 C++ 的引用),这个值可以是 null ,表明目前没有指向任何对象。

对引用的值进行拷贝,结果如下:

一、自定义类

一个源文件只能有一个公有类,但可以有任意数目的非公有类,源文件名必须与公有类的名字相同,否则无法编译。

关于多个源文件的编译方式(顺序),书中原话:可以认为 Java 编译器内置了“make”功能

构造器

  • 总是伴随着 new 操作被调用

  • 不能对一个已经存在的对象调用构造器

  • 如果没有编写构造器,默认的无参数构造器会将实例域设置为默认值

  • 如果编写了构造器,但不包括无参数的构造器,则构造对象时没有参数会被视为不合法

方法

隐式参数是出现在实例方法名前的类对象(方法调用的目标或接受者),在方法内部用 this 表示;列在方法声明中的属于显式参数。

不要编写返回引用可变对象的访问器方法,原因参考上面所说的引用与实体的区别。如果需要返回一个可变数据域的拷贝,应该使用 clone 方法。

方法可以访问所属类的私有特性(所有对象的私有数据),而不限于访问调用者的私有特性。

重载

方法名以及参数类型(不包括返回类型)构成了方法的签名(signature)。如果多个方法有相同的名字、不同的参数类型或顺序,便产生了重载(overload),编译器通过用各个方法给出的参数类型,与特定方法调用所使用的值类型进行匹配,挑选出相应的方法。 

final

用 final 修饰的实例域必须在构建对象时进行初始化,在后面的操作中不能再对它进行修改,大都应用于基本(primitive)类型域,或不可变(immutable)类的域(该类中的每个方法都不会改变其对象)。

对可变的类使用 final 修饰符可能会难以理清关系,例如:

public class Employee {
    private final StringBuilder evaluations;
    // ... 
    Employee() {
        evaluations = new StringBuilder(); 
     // evaluations 不可引用其他 StringBuilder 对象,但可以对当前对象的状态进行更改:
// evaluations.append(str);
// ...
}
}

static

在绝大多数面向对象语言中,静态域被称为类域,每个类中只有一个这样的域,由这个类的所有对象共享。

静态方法是一种没有隐式参数的方法,用类名进行调用(不提倡用其对象的引用),用于以下情况:

  • 一个方法不需要访问对象状态,所需参数皆显式提供

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

  • 工厂方法(factory method)

  • main 方法

二、按值调用(传递)

  • 按值调用(call by value):方法接收的是调用者提供的值

  • 按引用调用(call by reference):方法接收的是调用者提供的变量地址

Java 总是按值调用,方法得到的是参数值的拷贝,不能修改传递参数变量的内容。下面两幅图能很好地分清“不可以修改变量”和“可以修改引用指向的对象”之间的不同:

 总之,一个方法

  • 不能修改一个基础数据类型的参数

  • 可以改变一个对象参数的状态

  • 不能让对象参数引用一个新的对象

 

三、构造对象

初始化

必须明确地初始化方法中的局部变量,但如果没有初始化类中的域,将会被自动初始化为默认值(0 / false / null)。

显式的域初始化方法:

  • 定义实例域时直接赋值

  • 用 this 关键字调用另一个构造器(节省代码)

  • 初始化块

class Person {
    private int age;
    private boolean sex;
    private String name;
    private double asset;

    // initialization block
    {
        asset = 0;
    }

    // ...
}

在一个类的声明中,可以包含多个代码块,这些块会在构造对象时执行,运行顺序先于构造器。调用构造器的具体处理步骤如下:

  1. 所有数据域初始化为默认值

  2. 按照类声明中的次序,依次执行所有域初始化语句和初始化块

  3. 如果构造器第一行调用了第二个构造器,执行第二个构造器主体

  4. 执行这个构造器主体

静态初始化

在初始化块之前使用 static 关键字可以对静态域进行初始化,适用于无法准确知道赋值内容的时候:

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

类第一次加载时就会进行静态域的初始化,所有的静态域初始化语句、静态初始化块都将按照定义的顺序进行。

析构

Java 有自动的垃圾回收器(GC),不需要人工回收内存,因此 Java 不支持析构器。不过,如果对象使用了内存之外的其他资源(比如文件),那么当资源不再需要时,应该将其回收和再用。可以在类中添加 finalize 方法,它会在 GC 清除对象之前调用以回收资源。

由于难以预料 GC 什么时候会清除对象,所以不要依赖 finalize 方法来回收短缺的资源。如果某个资源需要在使用后立刻关闭,那么可以在对象的方法中调用一个相应的 close 方法。

四、包

名字相同的类放置于不同的包中,就不会产生冲突。借助包可以方便组织自己的代码,并与别人提供的代码库分开管理。标准的 Java 包具有一个层次结构,它们都处于 java 和 javax 包层次中。不过从编译器的角度来看,嵌套的包之间没有任何关系,每一个包的类集合都是独立的。

导入

一个类可以使用所属包中的所有类,以及导入的其他包中的公有类。 import 语句可以导入一个特定的类或者整个包(用 *),这是一种引用包含在包中的类的简明描述。只能用星号导入一个包,不存在一条语句导入多个包的情况。Java 中的 package  和 import 语句类似于 C++ 中的 namespace 和 using 指令。

前面提到过静态导入,结合 static 关键字并在类名后再接上星号,就可以使用指定类的静态方法和静态域,不必再加类名前缀。例如:

import static java.lang.System.*;
// ...
    out.println("good good study");
    exit(0);
// ...

作用域

如果一个类、方法或变量没有指定为 private 或 public,则这个部分可以被同一个包中的所有方法访问(默认访问权限)。对类来说,这种设计有一定的方便性,但对变量来说并不合适,有时会忘记加上修饰符,从而破坏封装性。因此,最好记得在声明变量时对访问权限作出显式标记。

五、类设计技巧

  • 一定要保证数据私有

  • 一定要对数据初始化

  • 尽量用其他类替代多个相关的基本类型的使用

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

  • 将职责过多的类进行分解

  • 名字要体现职责

  • 优先使用不可变的类

猜你喜欢

转载自www.cnblogs.com/zzzt20/p/11441017.html