写这篇文章是为了记录一下学习中被忽略的知识点,这些知识点虽然知道听说过,但对它们的概念和作用都很模糊,如果别人问起为什么,自己还真解释不上来,因此做个记录,方便以后回顾以及大家一起学习。

一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?

主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会默认生成一个不带参数且没有任何执行动作的构造方法。

在Java中定义一个不做任何事且没有参数的构造方法的作用

Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则系统会自动调用父类中没有参数的构造方法。当此时父类中只定义了有参数的构造方法而没有定义无参数的构造方法,然后在子类的构造方法中又没有使用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到无参数的构造方法可供执行。所以我们要在父类里加上一个不做任何事且没有参数的构造方法。

重载和重写的区别

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。

重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。

try-catch-finally

  • try块:用于捕获异常。其后可跟零个或多个catch块,如果没有catch块,则必须跟一个finally块。
  • catch块: 用于处理try捕获的异常。
  • finally块:无论是否捕获或者处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。

注意,一下几种情况finally块不会被执行: 1.在finally语句块中发生了异常; 2.在前面的代码中用了System.exit()退出; 3.程序所有的线程死亡; 4.关闭CPU。

final/finally/finalize的区别

final

Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)

  • 当用final修饰一个类时,表明这个类不能被继承
  • 当final修饰一个类中的某个方法,这个类的子类不能重写覆盖这个被修饰的类,也就是说子类是不能够存在和父类一模一样的方法。
  • final修饰变量,该变量表示常量,只能被赋值一次,赋值后值不能被修改。

finally

在异常处理时提供finally块来执行清楚操作。论是否捕获或者处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。

finalize

是方法名。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除之前做必要的清理工作。这个方法是在垃圾收集器在确定了,被清理对象没有被引用的情况下调用的。

finalize是在Object类中定义的,因此,所有的类都继承了它。子类可以覆盖finalize()方法,来整理系统资源或者执行其他清理工作。

HashMap

HashMap的特点

  • HashMap是基于哈希表的Map接口实现的。
  • HashMap底层采用的是Entry数组和链表实现的。
  • HashMap是采用key-value形式存储,其中key是可以允许为null,但是只能有一个,并且key不允许重复(如果重复则新值会覆盖旧值)。
  • HashMap是线程不安全的。
  • HashMap存入的顺序和遍历的顺序可能不一致(无序)。
  • HashMap保存数据的时候通过计算key的hash值来决定存储的位置。

HashMap的工作原理是什么?

HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,计算并返回的hashCode是用于找到Map数组的bucket位置来储存Node 对象。这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Node 。

HashMap源代码

要看HashMap的源代码,我们还是从HashMap的构造方法开始一步一步的讲解。

小总结:可以看出HashMap构造的时候会初始化16个容量,并且负载因子是0.75。负载因子是什么呢?我们后面讲。

小总结:这个构造方法没有什么可说的,只是多了些验证。在这里呢?构造方法就算初始化完毕了。

我们知道HashMap最常用的方法也就是put方法了,那么下面我们就着重去探究一下put方法的实现原理,也就是对HashMap的一个透彻理解。

put方法注释说明

这段代码好好的研读,请仔细往下看:

第一步: 直接判断 table==EMPTY_TABLE ,那么这个table是什么呢?看下图:

那么这个Entry又是说什么东东呢?

这个Entry是Map的一个静态内部类,里面最重要的属性有key、value和next三个属性值,在这里,我想大家已经猜到了,这个key和value是不是我们put的时候的key和value呢?答案是的,这个next又是干嘛用的呢?实际上这个Entry的的数据结构是一个单链表,这个next的属性的值还是这个Entry,表示的是当前的节点的下一个节点是哪个Entry。

好啦,源代码看到这里,我们知道,在put方法中,直接判断table是否为null,那么很显然到目前为止我们的table肯定是为null的,那么继续看如果table为null则要执行的代码。看下图:

哇塞,可以很直观的看到,我们实际上是初始化了一个Entry数组,而我们HashMap中的数据都是保存在了Entry[]里面了。

小总结:HashMap其实就是一个线性的Entry数组,而在Entry这个对象中保存了key和value,至于Entry对象中的next的具体作用是干嘛的,稍等做介绍哦。

第二步: 判断key是否为null。从这里可以看出,当判断key如果为null的话,并没有抛出什么异常错误,很显然HashMap是支持key为null的。那么就来看看key如果为null,会怎么处理呢?

小总结:首先去循环遍历这个Entry数组,判断是否有key为null的情况,如果有则新值覆盖掉旧值。如果没有key为null的情况,则hash值为0,数据存储在这个Entry数组的第0个位置,也就是table[0],具体方法可以查看addEntry方法,在这里呢,我就不再演示了。

第三步: 通过hash方法对key进行计算hash散列值,并且根据这个散列值查找这个要保存的值应该存储到table这个数组中的哪个索引位置。

第四步: 循环变量这个Entry数组,并且判断是否有重复的元素添加进去。

小总结:当去变量这个Entry数组的时候,去判断两个Entry对象的key的hash是否相同则仅仅表示它们的存储位置是相同的,然后继续判断两个Entry的key是否相等或者equals是否相等,如果条件都满足,则表示要添加的元素,key已经重复,则直接将新值覆盖掉旧值,并且return返回,一旦条件不满足,则直接将添加的元素添加到Entry对象中。

好啦,这个就是整个HashMap的底层原理。现在有的朋友可能会有产生这样的问题:如果计算的key的hash值相等,但是equals方法不相等,那么计算出来的要存储的位置不就冲突了吗?那么如果保存呢?

解决:实际上这种担忧是有必要的,因为我们完全有可能就是说计算的key的hash值和另一个key的hash值是相等的,那么这个时候呢,如果key的equals方法又不相等,那么这个时候我要保存的value值应该存储到table中的哪个索引上呢?实际上,这种情况叫做hash冲突,学习过数据结构的朋友应该都知道,解决hash冲突的方法有很多,但是在Java中,解决冲突的办法是采用的是链表来解决的。还记得这个Entry的next属性吗?对了,这个next属性就是用来记录这个链表上的下一个Entry。

HashMap的内容摘自http://baijiahao.baidu.com/s?id=1601416041995350500&wfr=spider&for=pc

volatile关键字的基本作用和原理

volatile关键字可以实现线程间的可见性,之所以可以实现这一点,原因在于JVM会保证被volatile修饰的变量,在线程栈中被线程使用时都会主动从共享内存(堆内存/主内存)中以实时的方式同步一次;另一方面,如果线程在工作内存中修改了volatile修饰的变量,也会被JVM要求立马刷新到共享内存中去。因此,即便某个线程修改了该变量,其他线程也可以立马感知到变化从而实现可见性.

未完待续...

2019-03-26

⬆︎TOP