== 和 equals 的区别是什么?
在没有重写equals的情况下,二者没有区别,因为equals这个方法的底层代码也是用==来比较的。
==:比较的是内存地址,如果两个基本数据类型比较,则比较的是两者字面上的值是否相同;如果是引用数据类型,则比较的是两者是否指向同一内存地址;
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
public class test1 {
public static void main(String[] args) {
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false,非同一对象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
说明:String这个类的equals是被官方重写过的,比较的是二者的字符串的内容是否相同,而不是内存地址。
hashCode 与 equals (重要)
为什么重写equals时必须重写hashCode方法?
答:我先概述一下
两个对象的equals()为true,则hashCode()必然一致;
两个对象的hashCode()一致,则equals()不一定为true;
接下来我们以HashSet集合来说明:
当向HashSet集合中存储元素时,HashSet会先判断你要添加的这个元素和已经存在的每个元素的hashCode值是否一致、重复,如果说不重复则可以添加,连equals都不用判断,因为hashCode值不同,则必然两个对象不一样;
反之,如果已经存在这样的hashCode值,则再用equals判断,为false则可以添加,为true则不可以添加。
假设我们定义了一个实体类,然后在我们的规则中这个类创建出的对象只要属性不同,则HashSet中是允许放入的,因为没重复嘛,言下之意就是属性相同就不允许放入,但HashSet可不这么认为,new出来的对象都是在堆中的,hashCode必然各自不同,那不同就是允许添加的,可这不符合我们的意思,我们认为相同属性就是同一个对象,好的,所以要重写hashCode方法。
在使用 HashMap 的时候,用 String 做 key 有什么好处?
HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。
Java中只有值传递
Java程序设计语言总是采用按值调用。也就是说,方法得到的是原本变量的一份拷贝,不管是基本数据类型的值得拷贝,还是引用类型的内存地址的拷贝,而且方法不能修改传递给它的任何参数变量的原本的内容。
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);// 10
System.out.println("num2 = " + num2);// 20
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);// 20
System.out.println("b = " + b);// 10
}
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);// 1
change(arr);
System.out.println(arr[0]);// 0
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
最具误导性的来了
public class Test {
public static void main(String[] args) {
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());// 小张
System.out.println("s2:" + s2.getName());// 小李
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());// 小李
System.out.println("y:" + y.getName());// 小张
}
因为swap方法的括号中接收的内存地址的拷贝,拷贝即原本的变量和当前的变量互不相关,唯一相关的就是后续拷贝改动二者共同指向的内存地址中的内容的时候,原本的变量的值也会受到影响,原本的变量指针也是指向该内存地址的,这个是不变的规律,你不得不承认虽然是拷贝,但是二者还是指向的同一个内存地址,example2不就是虽然接收的是拷贝,但拷贝的影响还是作用到了原本的数组引用嘛,因为它直接改的是堆内存中对象的内容,指向都是同一个。
final finally finalize区别
- final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
- finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
- finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。
用最有效率的方法计算 2 乘以 8
2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)。
Integer的缓存池机制
如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 则new instance();
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 将3自动装箱成Integer类型
int c = 3;
System.out.println(a == b); // false 两个引用没有引用同一对象
System.out.println(a == c); // true a自动拆箱成int类型再和c比较
System.out.println(b == c); // true
Integer a1 = 128;
Integer b1 = 128;
System.out.println(a1 == b1); // false 超过-128~127Integer会new新的实例,所以二者的内存地址不一致,指向的不是同一个内存空间。
Integer a2 = 127;
Integer b2 = 127;
System.out.println(a2 == b2); // true 仍在缓存池范围内,直接引用已有的变量。
}