equals和==
1.基本数据类型。byte,short,char,int,long,float,double,boolean
他们之间的比较,应用双等号(==),比较的是他们的值。
2.复合数据类型(类)
当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。
3.而equals是Object类的方法,所以如果没有重写 equals 方法(string是重写了equal方法的),equals 和 == 是等价的。 通常我们会重写 equals 方法,让 equals 比较两个对象的内容,而不是比较对象的引用(地址)
4.特例String String的equals会去比较地址。
Java有两种传递方式,值传递和引用传递。基本类型和以string str = “aaa”;这种方式创建的都是值传递,对象创建和数组都是引用传递,所以在函数的判断形参需要特别注意。
equals和hashcode
Object 类中的 hashCode 是返回对象在内存中地址转换成的一个 int 值(可以就当做地址看)。所以如果没有重写 hashCode 方法,任何对象的 hashCode 都是不相等的。通常在集合类的时候需要重写 hashCode 方法和 equals 方法.
Java集合判断两个对象是否相等的规则是:
1.首先要判断两个对象的hashCode是否相等;
如果相等,进入第二步再判断;
如果不相等,那么认为两个对象也不相等,结束判断。
2.判断两个对象用equals()是否相等。
如果这次判断也相等,则认为两个对象相等;
如果不相等,那么认为两个对象也不相等。
那么为什么这么做,毕竟只判断第二步就可以了。这是因为:运用 hashCode() 时,判断是否有相同元素的代价,只是一次哈希计算,时间复杂度为O(1),这极大地提高了数据的存储性能。而 equals() 方法是一个时间复杂度为 O(n) 的操作。因此只有出现冲突时,才放大招。
hashcode冲突
解决冲突主要有三种方法:定址法,拉链法,再散列法。HashMap 是采用拉链法解决哈希冲突的。
- 链表法是将相同 hash 值的对象组成一个链表放在 hash 值对应的槽位;
- 用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查 (亦称探测) 技术在散列表中形成一个探查 (测) 序列。 沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址 (即该地址单元为空) 为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。
- 再散列就是重新用个hash算法。
hashmap和hashtable
HashMap 是 Hashtable 的轻量级实现(非线程安全的实现)
a) 继承不同。
public class Hashtable extends Dictionary implements Map public class HashMap extends AbstractMap implements Map
b) Hashtable 中的方法是同步的,方法上syn。
c) Hashtable 中, key 和 value 都不允许出现 null 值。 在 HashMap 中, null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null 。当 get() 方法返回 null 值时,即可以表示 HashMap 中没有该键,也可以表示该键所对应的值为 null 。因此,在 HashMap 中不能由 get() 方法来判断 HashMap 中是否存在某个键, 而应该用 containsKey() 方法来判断。
d) 两个遍历方式的内部实现上不同。Hashtable、HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了 Enumeration 的方式 。
e) 哈希值的使用不同,HashTable 直接使用对象的 hashCode。而 HashMap 重新计算 hash 值。
f) Hashtable 和 HashMap 它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable 中 hash 数组默认大小是 11,增加的方式是 old*2+1。HashMap 中 hash 数组的默认大小是 16,而且一定是 2 的指数。
关于类初始化的顺序
static修饰的方法表示静态方法。
修饰的代码块是静态代码块。
父类静态初始化块 -> 子类静态初始化块 -> 父类初始化块 -> 父类构造器 -> 子类初始化块 -> 子类构造器。
初始化顺序也可以写为:
父类(静态变量、静态语句块)
子类(静态变量、静态语句块)
父类(实例变量、普通语句块)
父类(构造函数)
子类(实例变量、普通语句块)
子类(构造函数)
注意:在调用子类构造器之前,会先调用父类构造器,当子类构造器中没有使用 “super (参数或无参数)” 指定调用父类构造器时,是默认调用父类的无参构造器,如果父类中包含有参构造器,却没有无参构造器,则在子类构造器中一定要使用 “super (参数)” 指定调用父类的有参构造器,不然就会报错。
异常
Throwable下派生出 Error 类和 Exception 类。
在 JAVA 异常处理机制中,
- 如果抛出的是 Exception 的类型,则必须进行 try …catch 进行处理。
- 如果抛出的是 RuntimeException 的类型,则可以不使用 try。。catch 处理,一旦发生异常之后,将由 JVM 处理。(RuntimeException在Exception之下)
和Exception同等级的是Error,Error 类以及他的子类的实例,代表了 JVM 本身的错误。错误不能被程序员通过代码处理,Error 很少出现。因此,程序员应该关注 Exception 为父类的分支下的各种异常类。
Exception又分为了运行时异常和编译时异常。
编译时异常(受检异常)表示当前调用的方法体内部抛出了一个异常,所以编译器检测到这段代码在运行时可能会出异常,所以要求我们必须对异常进行相应的处理,可以捕获异常或者抛给上层调用方。
运行时异常(非受检异常)表示在运行时出现的异常,常见的运行时异常包括:空指针异常,数组越界异常,数字转换异常以及算术异常等。
前边说到了异常Exception应该被捕获,我们可以使用try – catch – finally 来处理异常,并且使得程序恢复正常。
Java的单继承和接口的多实现
有两个类B和C继承自A;假设B和C都继承了A的方法并且进行了覆盖,编写了自己的实现;假设D通过多重继承继承了B和C,那么D应该继承B和C的重载方法,那么它应该继承的是B的还是C的?这就陷入了矛盾,所以Java不允许多重继承。
接口定义的都是抽象的方法,而且不能在接口中实现方法。所以,接口继承多个接口,并不会使接口的结构变得很复杂。
泛型
ArrayList可以存放任意类型,如果添加了一个String类型,再添加了一个Integer类型,再使用时都以String的方式使用,因此程序就崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
List<String> arrayList = new ArrayList<String>(); 告诉JVM。我们的数组就是用来存放String的。
泛型本质是参数化类型,解决不确定对象具体类型的问题。
泛型的好处:① 类型安全,放置什么出来就是什么,不存在 ClassCastException。② 提升可读性,编码阶段就显式知道泛型集合、泛型方法等处理的对象类型。③ 代码重用,合并了同类型的处理代码。
泛型擦除
因为不管是 ArrayList<integer> 还是 ArrayList<string>,在编译完成后都会被编译器擦除成了 ArrayList。
Java 泛型擦除是 Java 泛型中的一个重要特性,其目的是避免过多的创建类而造成的运行时的过度消耗。</string></integer>
泛型中的通配符
泛型中的通配符:不确定传什么参数,都接受
? extends E : 接收E类型或者E的子类型。 上限是E ? super E : 接收E类型或者E的父类型。 下限是E
上界类型通配符:add方法受限
下界类型通配符:get方法受限
如果你想从一个数据类型里获取数据,使用 ? extends 通配符
如果你想把对象写入一个数据结构里,使用 ? super 通配符
如果你既想存,又想取,那就别用通配符
不能同时声明泛型通配符上界和下界
通配符上下限的使用情况
比如 有1.动物,猫,狗。 2.动物,猫,黑猫(猫的子类)
第一个中存的时候,都要存进去,那我定义上限动物,都能进
第二个中存的时候,都要存进去,既可以定义上限动物,也可以定义下限黑猫
第一种取的时候,都要取,定义上限,只要动物就能取。
第二种取的时候,都要取,定义上限,只要动物就可以取,定义下限黑猫,只要黑猫的父类都能取
而当我只想取猫(又不清楚什么猫)的时候,就只能定义下限,只有猫和猫的父类能被取出。
final和static关键字
- final 类不能被继承(不能有 abstract final class)(final 类内的 method 自动为 final,但不包括属性)
- final 方法可以被继承但不能被 override 不能有 abstract final method
- final 属性不能被重新赋值(可以被继承,但不可以修改)定义时可以初始化,也可以不初始化,而在语句块中初始化或者构造函数中初始化(最晚要在构造函数中初始化,只能初始化一次),final 定义的成员变量可以在代码块 (类变量则静态代码块,实例变量普通代码块) 里初始化
- final 属性只能人为赋值一次。继承于父类的 final 属性不能被修改
被 static 关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
- 修饰变量,static 修饰的变量称为静态变量、也称为类变量,类变量属于类所有,对于不同的类来说,static 变量只有一份,static 修饰的变量位于方法区中;static 修饰的变量能够直接通过 类名.变量名 来进行访问,不用通过实例化类再进行使用;
- 修饰方法,static 修饰的方法被称为静态方法,静态方法能够直接通过 类名.方法名 来使用,在静态方法内部不能使用非静态属性和方法;
- static 可以修饰代码块,主要分为两种,一种直接定义在类中,使用 static{},这种被称为静态代码块,一种是在类中定义静态内部类,使用 static class xxx 来进行定义;
static{}静态代码块与{}非静态代码块(构造代码块)
相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。
不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
注:
非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
String 为什么是不可变的
String 被设计为 final 的,表示 String 对象一经创建后,它的值就不能再被修改,任何对 String 值进行修改的方法就是重新创建一个字符串。
不可变的好处
- 可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。 - String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。 - 安全性
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。 - 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
注:
String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使用 synchronized 进行同步