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 是采用拉链法解决哈希冲突的。

  1. 链表法是将相同 hash 值的对象组成一个链表放在 hash 值对应的槽位;
  2. 用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查 (亦称探测) 技术在散列表中形成一个探查 (测) 序列。 沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址 (即该地址单元为空) 为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。
  3. 再散列就是重新用个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 异常处理机制中,

  1. 如果抛出的是 Exception 的类型,则必须进行 try …catch 进行处理。
  2. 如果抛出的是 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 值进行修改的方法就是重新创建一个字符串。
不可变的好处

  1. 可以缓存 hash 值
    因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
  2. String Pool 的需要
    如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
  3. 安全性
    String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。
  4. 线程安全
    String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

注:
String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使用 synchronized 进行同步