一、类之间的关系
局部变量不会自动的初始化为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 方法回收任何短缺资源,因为很难指定这个方法声明时候才能调用
七、类设计技巧
- 一定要保证数据的私有
- 一定要对数据初始化。Java不会对局部变量进行初始化,但hi是回对对象的实例域进行初始化。最好不要依赖于系统的默认值,而是应该显式地初始化所有地数据
- 不要在类中使用过多地基本类型
- 不是所有的域都需要独立地域访问器和域更改器
- 将职责过多的类进行分解
- 类名和方法名要能够体现它们的职责