为了不违背“相同对象必须要有相同hash值"的约定,对于基本数据类型==比较的是数值是否相等,对于引用类型数据==比较的是对象地址是否同等,在object中equal方法也是默认比较两个对象地址是否相同。但是String类中重写了equals方法比较的是两个对象的内容,假如我们创建了两个内容相同的对象,那么按我们平常的理解它应该是同一个对象且hash值相同,此时需要重写equal方法保证内容相同的两个对象是同一个对象重写hashCode方法保证内容相同的两个对象的哈希值相同,不然默认使用object中的equals比较地址则认为不是同一个对象。
Object类中equals方法源码:
public boolean equals(Object obj){
return (this==obj);
}
哈希冲突是:当两个不同的数(哈希值)经过哈希函数计算后得到了同一个结果,即他们会被映射到哈希表的同一个位置时,即称为发生了哈希冲突,简单来说就是哈希函数算出来的地址被别的元素占用了。
解决哈希冲突的方法有:
1、开放定址法:我们在遇到哈希冲突时,去寻找一个新的空闲的哈希地址。
(1)线性探测法
当我们的所需要存放值的位置被占了,我们就往后面一直加1并对m取模直到存在一个空余的地址供我们存放值,取模是为了保证找到的位置在0~m-1的有效空间之中。
公式:h(x)=(Hash(x)+i)mod (Hashtable.length);(i会逐渐递增加1)
(2)平方探测法(二次探测)
当我们的所需要存放值的位置被占了,会前后寻找而不是单独方向的寻找。公式:h(x)=(Hash(x) +i)mod (Hashtable.length);(i依次为+(i^2)和-(i^2))
2、再哈希法:同时构造多个不同的哈希函数,等发生哈希冲突时就使用第二个、第三个……等其他的哈希函数计算地址,直到不发生冲突为止。虽然不易发生聚集,但是增加了计算时间。
3、链地址法:将所有哈希地址相同的记录都链接在同一链表中。
4、建立公共溢出区:将哈希表分为基本表和溢出表,将发生冲突的都存放在溢出表中。
C++ 中可以实现多继承,而 Java 只能实现单继承。
数据结构有数组、链表、栈、队列、堆、树、Map 等。
HashMap 在 JDK1.7 之前采用数组加链表的形式实现,在 1.8 之后使用了红黑树:通过底层数组存储对象节点,采用链地址法,把新增对象节点连接在当前地址的节点下面,且规定当链表长度大于 8 时,将链表转换成红黑树结构。(详细讲解可以看其他博客或源码)
首先来解释一下什么是引用传递,什么是值传递。
在java中当参数是基本类型使用的是值传递当参数是引用类型时使用引用传递。当传的是基本类型时,传的是值的拷贝,对拷贝变量的修改不影响原变量;当传的是引用类型时,传的是引用地址的拷贝,但是拷贝的地址和真实地址指向的都是同一个真实数据,因此可以修改原变量中的值;(但是同为引用类型的String比较特殊)当传的是String类型时,虽然拷贝的也是引用地址,指向的是同一个数据,但是String的值不能被修改(Sting 是final修饰的char[]),给String赋值是重新new了一个String对象,改变的是新对象的值,因此无法修改原变量中的值。
例如:
//基本数据类型:
public void test() {
int a = 1;
change(a);
System.out.println("a的值:" + a);
}
private void change(int a) {
a = a + 1;
}
// 输出a的值:1
//引用数据类型
public void test() {
User user = new User();
user.setAge(18);
change(user);
System.out.println("年龄:" + user.getAge());
}
private void change(User user) {
user.setAge(19);
}
// 输出年龄:19
//引用数据类型:
public void test() {
User user = new User();
user.setAge(18);
change(user);
System.out.println("年龄:" + user.getAge());
}
private void change(User user) {
user = new User();
user.setAge(19);
}
// 输出年龄:18
//String类型:
public void test() {
String str = "hello";
change(str);
System.out.println(str);
}
private void change(String str) {
str = "world";
}
//输出值:hello
1、static变量
static变量也称为静态变量,静态变量和非静态变量的区别:
2、static方法
静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。
3、static代码块
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。利用静态代码块可以对一些static变量进行赋值。
4、final关键字
final代表的是最终、不可改变的意思,可以被用来修饰类、变量和成员方法。final修饰一个类时,这个类会成为最终类,不可被继承。final修饰成员方法时,这个成员方法不能被重写。final修饰成员变量,只能被赋值一次且不可变,且必须是在这个成员变量所在的类对象创建之前被赋值。
5、static和final一块用表示什么
static final用来修饰成员变量和成员方法,可简单理解为“全局常量”! 对于变量,表示一旦给值就不可修改,并且通过类名可以访问。 对于方法,表示不可被重写,并且可以通过类名直接访问。
面向过程是一种以过程为中心的编程思想,在处理某件事的时候,以正在进行什么为主要目标,分步骤完成目标。而面向对象的思想是将事物抽象为对象,赋予其属性和方法,通过每个对象执行自己的方法来完成目标。面向过程效率更高,而面向对象耦合低(易复用),扩展性强,易维护。
浅拷贝:浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。
深拷贝:深拷贝和浅拷贝是针对复杂数据类型(对象及数组)来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。
深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。 深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。
不可变类是指实例的属性不能被修改的类。一个不可变类的实例对象从被创建出来,它的成员变量就不能被修改。Java 平台的类库中包含许多的不可变类,比如 String、基本类型的包装类等。不可变类比一般的更加安全。
方法区有一块特殊存储区域 String Pool,当创建 String 时,如果在 String Pool 中找到相同的字符串值,则会返回一个已存在 String 的引用而不会新建一个对象。假设 String 是可变的,则会导致其他引用这个字符串值的 String 的值发生变化
API 和 SPI 都是制定接口传输数据。API 是由实现方负责定义和实现,调用方只负责调用的 API。而 SPI 是指由调用方制定的接口,这个接口由实现方针对接口进行不同的实现,再由调用方选择实现方。例如在 JDBC 连接数据库时,针对不同的数据库需要不同的驱动实现,JDBC 提供了驱动接口,由不同的实现方进行实现了这些不同的驱动,然后我们就可以在 JDK 中引入这些实现了的驱动包进行使用。
抽象类不能实例化,抽象类如何实例化呢?参照多态的方式,通过子类对象实例化,这叫抽象类多态
抽象类的子类,要么重写抽象类中所有的抽象方法,要么是抽象类