Java Baseic Two

== 与 equal 方法

Java 中判断两个变量是否相等有两个方法: == 和 equal 方法。

当使用 == 是,如果两个变量为基本数据类型,且都是数值类型,那么只要求两个变量的值相等,则为 true;如果两个变量为引用类型,那么只有当它们指向同一个对象时,返回值才为 true。但是 == 不可用于比较类型上没有父子关系的两个对象

equal 为 Object 的方法,所有的引用变量都可以调用该方法。但是使用 equal 方法判断两个对象是否相等的标准和 == 是一致的,都是在两个变量指向同一个对象时,才会返回 true。直接使用 Object 的 equal 方法没有什么意义,一般需要我们自己定义 equal 方法自定义相等的标准。

在平常的开发中我们会有这样的一个误区: == 是判断两个变量是否相等, equal 是用来判断两个变量的值是否相等,其实后半句话有一个前提,就是变量的类型为 String ,这是因为 String 已经为我们重写了 equal 方法,用于判断两个字符串的值是否相等,具体可以参见 String 的源码。

所以在两个变量皆为引用数据类型是, == 与 equal 方法没有什么区别,都是判断两个变量是否指向同一个变量。

字符串

1
2
3
String a = "abc";
String b = "abc";
String c = "a" + "b" + "c";

此时堆内存中只会分配一份存储 “abc” 的内存, 变量 a、b、c 的引用地址都指向该内存地址。

常量池

常量池,主要是用来管理编译时被确定并且保存在 .class 文件中的数据,包括类、方法、接口中的常量以及字符串常量。

类成员

被 static 修饰的变量、方法、初始化块、内部类等称为类变量。类变量属于类,在第一加载类的时候,系统会为类变量分配内存空间并进行初始化,直到该类被卸载,类变量占用的内存从会被标记成垃圾。

类变量中不能访问非 static 变量成员(变量、方法等),因为 类成员的作用域比非 static 变量成员的作用域大,存在类变量已经被初始化但是实例变量为被初始化的情况,此时程序就会出现异常。

单例类

如果一个类只能创建一个实例,那么这个类称为 单例类

根据良好性封装原则,我们需要:

  1. 把类构造器函数隐藏
  2. 提供 public 方法获取类的实例对象,并用 static 方法修饰,因为调用该方法前该对象还不存在,所以该方法应该属于类。

final 修饰符

变量

final 修饰的变量不可重复赋值

final 修饰成员变量

初始化位置:

  • 初始化块
  • 构造器
  • 声明处

final 修饰类变量
初始化位置:

  • 初始化块
  • 声明处

与普通实例变量不同,final 修饰的变量(实例变量和类变量)必须显式的初始化,系统不会为 final 修饰的变量的执行默认初始化。

  • final 修饰局部变量

必须显式的初始化初始值。

final 修饰基本数据类型和引用数据类型

final 修饰基本数据变量,则该变量不能被改变。
final 修饰引用数据类型变量,说明这个变量指向的内存地址不可更改,但是并不是此对象的各个属性不可变。

1
2
3
4

final Person person = new Person(20,"Mike");
person.setName("Jim");
person.age = 21;

final 、宏变量与宏替换

只要满足三个条件,这个 final 变量就不再是一个变量,而相当于一个直接量:

  1. 使用 final 修饰
  2. 定义该 final 变量时指定初始值
  3. 该初始值在编译器时被确定下来。

那么这个变量称为 宏变量

1
final int a =  5;

那么对于这个程序来说,根本不存在变量 a,出现变量a 的地方全部被 5 替换。

为了说明宏变量的不同之处,看下例:

1
2
3
4
5
6
7
8
String str1 = "ab";
String str2 = "a" + "b";
sout(str1==str2);// true

String str3 = "a";
String str4 = "b";
String str5 = str3 + str4;
sout(str1 == str5);//false

至于第一个日志结果为 true ,应该十分容易理解:str1 在编译时确定值,会被存储在常量池中,str2 通过显示的 拼装后(不是分别创建“a”和“b”) 存入 常量池,但是常量池中已经存在相同的值,就不会重新创建,直接将该值的内存地址指向 str2,所以结果为 true。

而第二条日志中,str5 在编译期不会初始化,需要在运行期通过计算获得,其值会被分配在 堆内存去中,所以 str1 和 str5 的引用的地址不同,其打印结果为 false。

同理有:

1
2
String str6 = new String("ab"); 
sout(str1 == str6);//false

但是如果 str3 和 str4 被 final 修饰,两者就会变成宏变量,两变量出现的位置会直接被值替换,此时 str5 在编译期就会确定初值。

1
2
3
4
final String str3 = "a";
final String str4 = "b";
String str5 = str3 + str4;
sout(str1 == str5);//false

final 方法

final 方法不可被重写,但是可以被重载。

final 类

final 类不能拥有子类。

不可变类(immutable)

不可变类为创建该类的实例后,该实例的实例变量不可变。

Java 提供的 8 个包装类和 String 为不可变类。

自定义不可变类需要遵循以下规则:

  1. 使用 private final 修饰成员变量
  2. 提供构造器,传入初始值
  3. 仅提供 getter 方法
  4. 如果有必要,重新 equal() 和 hashcode() 方法自定义相等的逻辑