Java笔记(一):对象与类

该笔记摘自《Java核心技术 卷一》第三、四章

第三章 基本程序设计结构

在注释中,如下写法将产生语法错误:

// \unicode...

Java中整形和布尔值类型不能相互转换,故如下写法将产生语法错误:

if (x = 0) { // Error:(6, 15) java: 不兼容的类型: int无法转换为boolean
    ...
}

数值类型之间的合法转换,实线箭头表示无损的转换,虚线箭头代表有可能产生精度缺失的转换。

数值类型之间的合法转换
数值类型之间的合法转换

当使用结合赋值运算符的时候,如果运算符得到一个值,其类型与左侧操作数的类型不同,就会发生强制转换。

int x = 3;
x += 3.5;
// x 输出 6

String类没有提供用于修改字符串的方法。

千万不要使用==来比较来个字符串是否相等,这个运算符只能判断两个字符串是否放在同一位置上。

String a = "Hello";
if (a == "Hello") {
    // probably true
}
if (a.substring(0, 3) == "Hel") {
    // probably false
}

构建字符串可以使用StringBuilder

image-20190225153437167
image-20190225153437167

更快的方法打印出数组中的值:Arrays.toString(array)

数组初始化与匿名数组:

int[] a = {1,2,3,4,5}; // 常规
new int[] {1,2,3,4,5}; // 创建匿名数组
a = new int[] {3,4,5,6,7} // 不创建新变量的情况下重新初始化数组

在Java中允许数组长度为0,即:

int[] a = new int[0]; // 创建了一个长度为0的数组,注意长度为0的数组与null不同

数组拷贝:

int[] b = a;
b[1] = 3;  // 此时的a[1]也被赋值为3

// 仅拷贝数组的值,第二个参数用于控制新数组的大小
int[] b = Arrays.copyOf(a, a.length);

第四章 对象与类

区分对象和对象变量:

一定要认识到 : 一个对象变量并没有实际包含一个对象,而仅仅引用一个对象.。任何对象变量的值都是对存储在另外一个地方的一个对象的引用。

例如Date date = new Date();

表达式new Date()构造了一个Date对象,并且它的值是对这个对象的引用,这个引用存储在变量date中

在Java中必须使用clone获得对象的完整拷贝。

更改器方法与访问器方法的区别:

调用更改器方法,原来对象的状态会发生改变;

调用访问器方法,原来对象的状态不会发生改变。

基于类的访问权限:

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

public boolean equals(Employee other) {
    return this.name.equals(other.name);
}

final实例域:

构建对象时必须初始化这样的域(构造函数),这个域的值被设置后,在后续的操作中不会被修改。

静态域与静态方法(static)

静态域属于类,不属于任何独立的对象,一个类的所有对象将共享该类的静态域。

静态方法是没用this参数的方法,this参数可以认为是这个方法的隐式参数。

一个类的静态方法不能访问动态实例域,但是可以访问自身类中的静态域

在下面两种情况需要使用静态方法:

  1. 一个方法不需要访问类的对象,所有的参数都是通过显式参数提供的(Math。pow)
  2. 一个方法只需要访问类的静态域

工厂方法:

静态方法还有另一种用途就是类似LocalDate和NumberFormat类使用静态工厂方法构造对象。

LocalDate.of(...);
LocalDate.now();

为什么要用静态工厂方法来构造对象呢?

image-20190226194757862
image-20190226194757862

方法参数:

Java程序设计语言永远采用按值调用,方法总是得到所有参数值的一个拷贝。

一种容易混淆的情况:

Employee类有方法如下:

public static void tripleValue(Employee e) {
 e.raiseSalary(200);
}

在Main类中调用:

Employee.tripleValue(harry);

发现harry对象变量所引用的对象的salary也发生了改变,这难道还是值传递吗?

答案仍然是值传递,因为对象引用的拷贝与原来的对象变量引用的是同一个对象。

还是这个容易混淆的例子:

首先编写一个交换两个雇员对象的方法

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

然后执行该方法:

Employee a = new Employee("Alice", ...);
Employee b = new Employee("Bob", ...);
swap(a, b);

那么请问a和b的引用有没有调换呢?

答案还是没有,因为a和b传参到swap中时,生成了两个拷贝为x和y。最初x引用Alice,y引用Bob,最后只是x和y的引用互换,并没有影响到最初的a和b所以Java的对象引用依然是值传递

image-20190226201350811
image-20190226201350811

对象构造

Java中要完整的描述一个方法,需要指出方法名和参数类型,这叫做方法的签名

返回类型不是签名的一部分,也就是说不能有两个名字相同、参数类型也相同但是返回值类型不同的方法

无参数构造器:

当类没有提供任何构造器的时候,系统才会提供一个默认的构造器;如果当前类提供了至少一个构造器但是没有提供无参数构造器,构造函数没有提供参数就会视为不合法

调用另一个构造器(this关键字的用法):

如果构造器的第一个(必须放在第一个,否则报错)语句刑如this(...),这个构造器将调用同一个类的另一个构造器

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

当调用new Employee(10000)时,Employee(String, double)将被自动执行。

初始化块(不常见):

在一个类的声明中可以包含多个代码块i,只要构造类的对象,这些块就会被执行。

class Employee {
    ...
    {
        id = nextId;
        nextId++;
    }
    
    public Employee(...) {...}
}

无论哪个构造器构造对象,id域都在对象初始化块中被初始化。且首先运行初始化块,然后才运行构造器的主体部分。

由于Java有自动的垃圾回收器,所以Java不支持析构器


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

import java.util.*;
import java.time.LocalDate;

静态导入:

import static java.lang.System.*;  // 导入静态域
out.println("...");

import static java.lang.System.out;  // 导入特定的方法域

编译器在编译源文件的时候不检查目录结构。假如有一个特定源文件开头有下列语句:

package com.mycompany;

即使这个源文件没有在子目录com/mycompany下,也可以进行编译。如果它不依赖于其他包就不会出现编译错误,但是最终的程序将无法运行,除非先将所有类文件移到正确的位置上。

如果没有指定public或private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。


文档注释

javadoc工具:可以由源文件生成一个HTML文档。

给每个文档/**...*/注释标记后紧跟着是自由格式文本,标记由@开始,如@author或@param。

在自由格式文本中,键入等宽代码需使用{@code … }而不是使用<code></code>,这样一来就不用操心对代码的<字符转义了。

(具体用法可以查阅工具书)

类设计技巧

  1. 一定要保证数据私有
  2. 一定要对数据初始化,最好不要依赖于系统默认值(java会对对象的实力域进行初始化)
  3. 不要在类中使用过多的基本类型,而是用其他类代替多个相关的基本类型的使用。

    private String street;
    private String city;
    private String state;
    private int zip;
    
    // 可以用一个Address类代替上面的实例域
  4. 不是所有的域都需要独立的域访问器或域更改器
  5. 将职责过多的类进行分解
  6. 类名和方法名要能够体现它们的职责
  7. 优先使用不可变的类

添加新评论