Lab2-ADT & OOP 回顾
在忙于干活与忙于划水的薛定谔叠加态中度过一个月后想起了博客,考虑到如果自己再不回顾之前的实验+复习软件构造内容就要和肯尼迪和安倍晋三一桌打复活赛结果会很悲惨的情况,决定打开博客开始码字。
现在对Lab2- ADT & OOP的内容进行回顾
目录Static与非Static的区别(变量与方法)
Static/非Static变量
在C语言的学习中已经接触过,static修饰下变量全局可用,可以在当前整个程序源码中任意位置供其他函数调用。
在所有函数外初始化的数据结构默认视为被static修饰的变量。
# include <stdio.h>
# include <stdlib.h>
# include <windows.h>
// 计时用 基于含Windows api的头文件
// 这里初始化的数据是整个源码内可用的
LARGE_INTEGER temp; // 用于存储性能计数器的值
LONGLONG start, end, freq; // 用于存储开始时间、结束时间和频率
int origin_num[N];
int num1[N];
int main()
{
int static overall_mark = 0; // 函数中也可使用
// ...
}
而在Python,Java等面向对象的编程语言中,类内static修饰的变量称为类变量/全局变量/成员变量。如同C中static修饰的变量在运行之初初始化加载一样,这些变量是在类被加载时候就被初始化的,只要类存在,类变量就存在。
非static修饰的成员变量是在对象new出来的时候划分存储空间,是与具体的对象绑定的,该成员变量仅为当前对象所拥有的。
在引用类变量时,我们直接通过类名.变量名调用,对象在引用实例变量时只能通过对象名.变量名调用。在类中调用成员变量时直接调用或者以类名.变量名方式调用,实例变量则用this或者直接调用。
例如:
public class Test1 {
private static String testKey = "Init Key";
private String testObjectKey;
public Test1(){
testObjectKey = "Init Object Key";
}
public static void main(String[] args){
Test1 testObject = new Test1();
System.out.println(testObject.testKey);
System.out.println(Test1.testObjectKey);
}
}
在上面的代码中,main方法里第2行代码会提示Warning:
无法从 static 上下文引用非 static 字段 'testObjectKey'
第三行代码则直接在testObjectKey显示红色,并在主方法内第一行代码处报错:
通过实例引用访问 static 成员 'Test1.testKey'
在之前的Lab1的P3中,如下:
public class FriendshipGraph {
// Object "person" existed in current Friendship graph.
private static Set<String> personSet;
public FriendshipGraph(){
personSet = new HashSet<>();
}
笔者对FriendshipGraph类内用于统计图内已有Person顶点的集合属性personSet错误地添加了static关键字,并在主方法中直接通过属性名调用personSet。这样做就导致在测试类内先运行的测试方法会将之前的图数据保留,从而影响后面测试方法的结果。
笔者并未意识到这个static关键字的问题,选择在构造方法内将这个共用的静态控件重新初始化为一新的空集合,这样可以解决先运行的测试方法结果影响后面测试方法的结果的问题,但一旦遇到同时需要统计多个友谊图的情况,便会出现后一个友谊图覆盖前一个友谊图的问题。
最终修改代码,删去static关键字并在主方法中初始化一个FriendshipGraph对象,用对象名.属性名访问每个对象单独的personSet属性用于统计,问题解决。
private static Set
personSet; -> private staticSetpersonSet; (in main method)
FriendshipGraph friendshipGraph = new FriendshipGraph();
personSet -> friendshipGraph.personSet
方法上
静态方法使用static关键字修饰,属于类而不属于对象,在实例化对象之前我们便可以通过“类名.方法名”调用静态方法。
- 在静态方法中,可以调用静态方法。
- 在静态方法中,不能调用非静态方法。
- 在静态方法中,可以引用static变量。
- 在静态方法中,不能引用非static变量。
- 在静态方法中,不能使用super和this关键字(涉及父类与当前类的对象,静态方法不能先于这些对象产生)。
非静态方法不含有static关键字,属于对象而不属于类。必须通过new关键字创建对象后再通过对象调用。
- 在普通方法中,可以调用普通方法。
- 在普通方法中,可以调用静态方法。
- 在普通方法中,可以引用类变量和成员变量。
- 在普通方法中,可以使用super和this关键字。
静态方法的生命周期跟相应的类一样长,类内带static的方法与变量会随着类的定义而被分配和装载入内存中。直至线程结束,这些静态的属性和方法才会被销毁。
非静态方法的生命周期和类的实例化对象一样长,只有当类实例化了一个对象,非静态方法才会被创建,而当这个对象被销毁时,非静态方法也马上被销毁。
main方法不带static关键字时,在IDEA环境下会提示Warning:
方法 'main' 没有签名 'public static void main(String[])'
如果main方法非静态,就意味着Java虚拟机在执行的时候需要先创建一个对象才能调用main方法,我们为作为程序的入口的main方法再创建一个额外的对象显然是没有必要的。
重写Hashcode和Equals方法
在做对象间的比较时,我们通常采用equals方法来判断它们是否相等。查阅Object的euqals实现:
public boolean equals(Object obj) {
return (this == obj);
}
这里的符号“==”是这样定义的:
- 对于基本类型(int,byte,boolean,long,short,double,float,char),==操作比较值相等
- 对于对象类型,比较两个对象的内存地址。
- 对于封装类型和基本类型间的比较,编译器会转换为基本类型后再比较
一个拼接好的hello world的字符串和一个new出来的hello world作比较,它们的内容是一致的而地址值不同,这时候用equals判断它们不相等,这显然是不符合我们需要的。
重写equals方法,目的就是实现符合我们需要的“合乎情理”的情景。如果不重写的话,源码中就仅仅会比较俩个对象的地址值。
那么hashcode的重写又是否是必要的?只重写Hashcode和equals其中的一个方法可行吗?
首先一般程序开发中有规定:两个对象如果使用equals比较为true,那么它们的hash值必须一样,因而我们重写equals方法的同时,都会重写hashCode。
只重写equals方法是可行的,但对于数据量庞大的对象,比如这样一个情况:在做俩个对象A和B的比较时,B对象是存放在集合里的,且集合里的数据多达几万条,调用equals方法来一个一个比较嘛效率就必然会出现明显的下降。这个时候,hashcode方法直接进行比较显然更为合理。
只重写hashcode方法,由于规定对象equals比较为true则hash值必须一样,也就是说hashcode相等是两个对象实际相等的必要条件,因此是只重写hashcode方法是不成立的。
对于大多数我们需要进行比较,涉及判断是否相等的类,需要进行equals与hashcode的重写。
@Override
public int hashCode() {
final int prime = 31;
int res = 1;
res = prime * result + ((Key1 == null) ? 0 : Key1.hashCode());
res = prime * result + ((Key2 == null) ? 0 : Key2.hashCode());
return res;
}
hashcode方法的重写规则可以参考数据结构课中哈希部分的内容结合实际情况来确定。一般来说可以在重写时使用“31”作为系数进行hashcode的构造,这是因为31是质数,乘法构造可以使hashcode保持唯一,并且31作为一个质数具有如下性质:
n * 31 等价于 (n * 2 * 2* 2 * 2 * 2 - n) or (n << 5) - n
总结
- static是类的,是一开始就加载的,是陪你走过岁月长河的
- 非static是对象的,是随new对象随产生的,是生不带来死不带走的
- 对于大多数我们需要进行比较,涉及判断是否相等的类,需要进行equals与hashcode的重写。
例如P1中的FriendshipGraph,Person类涉及比较需要进行equals与hashcode重写,至于FriendshipGraph我们并不考虑需要对两个图进行比较的情况,因而重写它的equals与hashcode方法是不必要的。
参考资料
https://blog.csdn.net/LINDAMAN00/article/details/97898803
https://zhuanlan.zhihu.com/p/258751142
https://zhuanlan.zhihu.com/p/60871182