《Java核心技术》| 对象与类

一、类之间的关系

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


二、用户自定义类

源文件名(Employee.java),必须与 public 类的名字相匹配,在一个源文件中,只能有一个共有类,但是可以有任意数目的非共有类

实例域
private String name;
private Integer age;
private double salary;

其中 named 的域是 String 对象,age 的域是 Integer 类对象。因此类通常包括类型属于某个类 类型的实例域

构造器

构造器总是伴随 new 操作符的执行被调用的

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

不要在构造器中定义与实例域重名的局部变量;但是在方法中可以定义

封装

一般的封装类,提供了三项内容
* 一个私有的数据域
* 一个公有的预访问器方法
* 一个公有的域更改器方法

public class User {
    private String name;
    private int age;
    private String email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
final 实例域

可以将实例域定义为 final,但是构建对象时候必须初始化这样的域。必须确保在每一个构造器执行之后,这个域的值被设置,并且在之后的操作中,不能在对它进行修改

如在定义的时候初始化了,则不能设置 setxxx() 方法将值改变

如果设置 private final User user1 仅仅意味着存储在 user 的对象引用在对象构造后不能改变,即 user 引用只能指向 User 对象实例,但并不意味着 user 对象就是一个常量,他依然可以调用 User 类的方法

三、静态域和静态方法

3.1 静态域

如果将域设为 static,则每个类中只有一个这样的域。而每一个对象对于所有的实例域都有自己的一份拷贝

举个例子

public class User {

    private static int nextId = 0;
    private int id;

    User() {
        nextId++;
        id++;
    }

    void print() {
        System.out.println("nextId: " + nextId);
        System.out.println("id: " + id);
    }

    public static void main(String[] args) {
        new User();
        new User();
        new User().print();
    }

}

输出:

nextId: 3
id: 1

可以看到,每次 new 的对象,都是使用的同一个静态域 nextId,因此才会在前一次的基础上加1;对于实例域,每次 new 一个对象,便重新建立一个自己的 id 域,即每次都是从 0 开始加 1

如果有 1000 个 User 对象,那么就会有 1000 个实例域 id,但是只能有 1 个 nextId。同时,静态域是属于类的,而不属于任何独立的对象

3.2 静态常量

我们使用 static final xxx = xxx 来定义和初始化常量

例如 Math 类中的静态常量

public static final double PI = 3.14159265358979323846;

我们可以直接使用 Math.PI 得到这个常量

3.3 静态方法

静态方法是没有 this 参数的方法(在一个非静态的方法中,this 参数表示这个方法的隐式参数)。因为静态方法不需要靠 new 一个对象来调用自己,它直接可以通过类来调用自己,因此和对象无关。

因为静态方法不能操作对象,因此不能再静态方法中访问实例域。但是静态方法可以访问自身类中的静态域

静态方法一般是通过 类名.静态方法名 来调用,也可以使用 对象.静态方法名,但是不推荐使用后者,因为此时对象和静态方法没有任何关系

什么时候使用静态方法:
* 一个方法不需要访问对象状态,其所需参数都是通过显示参数提供(Math.max)
* 一个方法只需要访问类的静态域

四、方法参数

先分清楚两个概念
* 按值传递:方法接收的是调用者提供的值
* 按引用传递:方法接收的是调用者提供的变量地址

《Java 核心技术》中说:“Java程序设计语言总是采用按值来调用的,即,方法得到的是所有参数值的一个拷贝。因为传递过来的相当于是一个副本,因此方法只能改变这个副本所对应的值或者对象。

4.1 将基本数据类型作为参数传递
public class ObjectTest3 {

    static void tripleValue(double x){
        x = x * 3;
    }

    public static void main(String[] args) {
        double parent = 100;
        tripleValue(parent);
        System.out.println(parent);
    }

}

结果:100

可见,传到 tripleValue() 方法里的 parent,只是 parent 的一个拷贝,即 x 也只是 parent 的一个拷贝而已,x * 3 = 30 也只是对拷贝的值做了改变,而与本来的 parent 无关,因此 parent 还是 10

4.2 将对象引用作为参数进行传递

4.3 值传递和引用传递

由于 C++ 有值传递和引用传递两种方式,那么 Java 呢?实际上,Java 是采用的值传递

class Employee {
    int x;

    Employee(int a) {
        this.x = a;
    }
}

public class ObjectTest2 {
    static void swap(Employee x, Employee y){
        Employee temp = x;
        x = y;
        y = temp;
    }

    public static void main(String[] args) {
        Employee employee = new Employee(100);
        Employee employee1 = new Employee(200);
        System.out.println("交换前:" + employee.x + " " + employee1.x);
        swap(employee, employee1);
        System.out.println("交换后:" + employee.x + " " + employee1.x);
    }
}

结果是:

交换前:100 200
交换后:100 200

这个例子很明显,如果 Java 是引用传递,那么在调用完那个方法之后,应该可以实现数据的交换,但实际上,并没有交换。

可以看到,两个对象引用传到方法之后,拷贝出了两个对象引用 x, y,x 指向 的是第一个 Employee 对象,y指向的是第二个。然后 swap() 方法中交换的其实仅仅是拷贝所指向的地址

最后,方法结束,x 和 y 也被回收,而最原始的两个对象引用 employee 和 employee1 还是指向之前的两个对象

那为什么不是引用传递呢?
其实可以联想一下 C++ 是如何进行引用传递的

void swap(int *p1, int *p2){
    int *temp;
    temp = p1;
    p1 = p2;
    p2 = temp;
}

可以看到,同样也是传入两个参数的地址,然后直接改变两个参数所在的内存地址。这个时候从内存中取出对应的数值应该就是相反的值了

然后我们再看 Java,同样传入的是地址,那么他是如何实现的呢?传入一个引用,拷贝一份,再传入一个,再拷贝一个,之后的事情就很简单了,直接对拷贝的引用所指向的对象进行操作。因此,他和 C++ 的引用传递是是不一样的。

因此,Java 本至上还是值传递

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

五、初始化块

三种初始化数据域的方法

  • 在构造器中设置值:在创建对象实例的时候初始化属性
class A {

    int a;
    String b;

    public A() {
        a = 100;
        b = "hello";
    }
}
  • 直接在声明中赋值
class A {
    int a = 1000;
    private String name = "hello";
}
  • 初始化块:只要构造类的对象,这些块就会被执行
class AA {

    static int b;

    static {
        b = 1000;
        System.out.println("static b " + b);
    }

    {
        b = 1001;
        System.out.println("code b " + b);
    }

    AA() {
        b = 1002;
        System.out.println("constructor b " + b);
    }

}

public class Test extends AA {

    static int a;

    static {
        a = 1000;
        System.out.println("static a " + a);
    }

    {
        a = 1001;
        System.out.println("code a " + a);
    }

    Test() {
        this(10);
        a = 1002;
        System.out.println("constructor a " + a);
    }

    Test(int b) {
        System.out.println("constructor a b ");
    }

    public static void main(String[] args) {
        //1
        new Test();
    }

}

去掉1,输出:

static b 1000
static a 1000

保留1,输出:

static b 1000
static a 1000
code b 1001
constructor b 1002
code a 1001
constructor a b 
constructor a 1002


六、对象析构与finalize方法

在 C++ 中,析构函数用于回收分配给对象的存储空间。由于 Java 有自动的垃圾回收器,不需要人工回收内存,所以 Java 不支持析构器

但是,某些对象使用了内存之外的其他资源,如,文件使用了系统资源的另一个对象的句柄,这个时候,如果资源不在需要时,将其回收和再利用会显得十分重要

可以为任何一个类添加 finalize 方法,该方法将在垃圾回收器清楚对象之前使用。实际应用中,不要依赖于使用 finalize 方法回收任何短缺资源,因为很难指定这个方法声明时候才能调用


七、类设计技巧

  1. 一定要保证数据的私有
  2. 一定要对数据初始化。Java不会对局部变量进行初始化,但hi是回对对象的实例域进行初始化。最好不要依赖于系统的默认值,而是应该显式地初始化所有地数据
  3. 不要在类中使用过多地基本类型
  4. 不是所有的域都需要独立地域访问器和域更改器
  5. 将职责过多的类进行分解
  6. 类名和方法名要能够体现它们的职责

猜你喜欢

转载自blog.csdn.net/babycan5/article/details/82118806