Java基础
1.Java的基本数据类型有哪些?
2.如何理解面向对象和面向过程?
面向过程:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。是一种思考问题的基础方法。
面向对象:是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
面向对象的四大特征:
- 封装 :继承是从已有类得到继承信息创建新类的过程
- 继承 :通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口
- 多态 :多态性是指允许不同子类型的对象对同一消息作出不同的响应
- 抽象 :是将一类对象的共同特征总结出来构造类的过程,包括数据抽象行为和行为抽象两方面
面向过程
- 去菜市场买五花肉
- 洗净、切块、准备作料等备用
- 下锅、上色、翻炒、炖
- 出锅
面向对象
- 五花肉备好
- 五花肉下锅
- 五花肉出锅
3.如何理解多态
父类引用指向子类的实例:例如:
List<Object> list = new ArrayList<>();
4、封装举例?
实体类的封装,将属性私有,只有本类能访问,如此就对信息进行的隐藏。对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问
5、继承?
Java是单继承的,子类从父类继承方法,使得子类具有父类相同的行为
6、char可不可以存储一个中文汉字,为什么?
char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含汉字,所以可以存储,有些特殊的汉字没有包含在Unicode中,就不能存储了,Unicode编码占用两个字节,所以,char类型的变量也是占用两个字节
7、自动拆装箱?int和integer有什么区别?
装箱:将基本数据类型转换为包装类对象
拆箱:将包装类对象转换成基本类型的值
装箱使用包装器的valueOf方法,拆箱使用包装器的intValue方法。
为什么要引入自动拆装箱?主要用于Java的集合中,集合类型只能指定包装类,List
原理?Java编译器的语法糖。
语法糖 Syntactic Sugar 糖衣语法,方便开发人员使用,JVM并不识在编译阶段解语法糖,还原为基础语法
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b); // true
System.out.println(c == d); // false
}
java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池。超过了就要申请空间创建对象了
8、 == 和 equals 的区别?
==
比较基本数据类型:比较的是变量的值
比较引用数据类型:比较的是地址值
equals
如果没重写equals方法比较的是两个对象的地址值
如果重写的了equals方法后我们往往比较的是对象中的属性的内容
equals冲Object类中继承过来的,默认的实现就是使用==
public boolean equals(Object obj){
return (this==obj)
}
9、String可以被继承吗?
不能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的
- String类是最常用的类之一,为了效率,禁止被继承和重写
- 为什么用final修饰?为了安全。String类中有很多调用底层的本地方法,调用操作系统的API,如果方法可以重写,可能被植入恶意代码,破坏程序。Java的安全性也体现在这里】
10、String buffer和String Builder的区别?
1、String buffer和String builder中的方法和功能是完全等价的
2、只是StringBuffer中的方法大都采用了synchronize关键字进行修饰,因此是线程安全的,而StringBuilder没有这个修饰,可以被认为是线程不安全的
3、在单线程的程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全而StringBuffer则每次都需要对锁进行判断,效率相对更低
11、final、finally、Finalize有什么区别?
final:修饰符(关键字) 有三种用法:修饰类、变量、和方法,
修饰类时,意味着他不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。
修饰变量时,该变量使用中不被改变,必须在声明时给定初始值,在引用中只能读取不可修改,即为常量。
修饰方法时,也同样只能使用,不能在子类中被重写。
finally:通常放在try……catch的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中
finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作,这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作
12、Object中有哪些方法?
(1)protected Object clone()--->创建并返回此对象的一个副本。
(2)boolean equals(Object obj)--->指示某个其他对象是否与此对象“相等”。
(3)protected void finalize()--->当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
(4)Class<? extendsObject> getClass()--->返回一个对象的运行时类。
(5)int hashCode()--->返回该对象的哈希码值。
(6)void notify()--->唤醒在此对象监视器上等待的单个线程。
(7)void notifyAll()--->唤醒在此对象监视器上等待的所有线程。
(8)String toString()--->返回该对象的字符串表示。
(9)void wait()--->导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
void wait(long timeout)--->导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll()方法,或者超过指定的时间量。
void wait(long timeout, int nanos)--->导致当前的线程等待,直到其他线程调用此对象的 notify()
13、集合框架简单体系图
14、ArrayList、LinkedList、Vector的区别?
ArrayList,LinkedList和Vector都继承自List接口。
ArrayList和Vector的底层是动态数组,LinkedList的底层是双向链表.
ArrayList和Vector的区别就是ArrayList是线程不安全的,Vector是线程安全的,Vector中的方法都是同步方法(synchronized),所以ArrayList的执行效率要高于Vector,它也是用的最广泛的一种集合。
我们重点比较一下ArrayList和LinkedList的区别,其实ArrayList和LinkedList之间的区别就是数组和双向链表之间的区别。
数组的特点:因为数组分配的是一块连续的内存空间,使用索引来查找元素是非常快速的。但是插入,删除数据性能比较低。
双向链表的特点,查询效率较低,因为查询一个元素需要从头部或尾部开始查询,挨个遍历每一个元素直到找到所需元素,插入,删除效率高比如我们删掉一个元素直接把它前一个元素的指针指向它后一个元素就可以了
15、HashMap源码分析?
先来看一下添加元素的过程,然后再看扩容方法。
首先要思考一个问题,既然HashMap是用数组来存数据的,而数组的类型是一个Node节点,而节点中如果只放key和value,每次添加的时候就去循环数组判断是否有同样的key,如果没有则添加,有就覆盖,然后每次根据key取值的时候也循环数组判断是否存在该key然后取出对应的value,那么可想而知,当数组中的元素量变成10、100、1000甚至成百上千万的时候,执行效率会有多低。
(1) 所以我们能想到的,HashMap也能想到,而HashMap是怎么解决这一个问题的呢?
答案是,HashMap会根据不同的key计算出数组下标,然后直接把该key存到指定的下标位置上,取的时候也是通过该key计算出位置,直接通过下标位置在数组上取,这样就不需要去循环判断取数据了,完美解决上面说到的问题。我们接着看HashMap是同通过什么样的方式计算出位置呢?
(2)首先我们了解一下Hash是什么?
Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。对不同的关键字可能得到同一散列地址,即key1≠key2,而key1的哈希值=key2的哈希值,这种现象称碰撞。(但碰撞的概率是很小的)
(3)所以HashMap会先获取到key的哈希值,但是假设当前数组长度16,获取到了hash值之后怎样才能得到获取存储的数组下标,并且让下标在0~15之中呢?(肯定不能越界对吧)
答案是这样的,我们都知道一个k1取模k2,结果肯定不会大于k2对吧,而且结果肯定在0~k2之中,所以HashMap就是这样来确定下标位置的,不过是用的另外一种方式计算,就是:(len - 1) & hash,通过按位与的方式也能达到取模的效果,而且计算速度更快。好了,先小小总结一下,HashMap首先根据key值获取到hash值,然后根据hash值获取到存放的数组位置,但是接下来又有一个问题了。
(4)如果hash发生了碰撞,就是不同的key但得到了同一个hash值,结果获得的数组位置也是同一个,本来同一个key是可以直接覆盖的(因为hashmap本来就不能存相同的key),可是这两个key又不是同一个,肯定不能覆盖,那应该怎么办呢?
所以,HashMap就加入了链表,当计算出key位置之后,发现该位置上已经有一个元素了,然后就进行判断,如果我这个key的值和已经存在的key值不相等,然后就往改元素后面添加,也就是通过Node节点指向下一个Node节点,此时该数组位置上就有一个包含两个node节点的链表了。等于当添加元素的时候,发现该数组位置上已经有值了,并且当前key值与链表中的所有key都不相等,就往这个链表中追加。可是现在又有了一个新的问题,一旦某一个数组位置上链表的长度越来越长之后,是不是又回到了当初说循环判断key值,会影响的效率问题(虽然不是每次存取元素都要循环,但还是可以优化呀),所以jdk1.8做了进一步的优化。
所以就增加了红黑树的概念,当链表中的长度为9的时候就转为红黑树,使用红黑树结构可以优化计算机的处理过程,使得进一步的提升了极端情况下的性能。(有部分博客可能没有把最初的那个node节点计算进来,写着当有8次碰撞的时候转红黑树,导致有些人认为链表中长度为8的时候转红黑树
16、HashMap在JDK1.7和1.8中的区别?
-
最重要的一点是底层结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构;
-
jdk1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而1.8则是直接调用resize()扩容;
-
插入键值对的put方法的区别,1.8中会将节点插入到链表尾部,而1.7中是采用头插;
-
jdk1.7中的hash函数对哈希值的计算直接使用key的hashCode值,而1.8中则是采用key的hashCode异或上key的hashCode进行无符号右移16位的结果,避免了只靠低位数据来计算哈希时导致的冲突,计算结果由高低位结合决定,使元素分布更均匀;
-
扩容时1.8会保持原链表的顺序,而1.7会颠倒链表的顺序;而且1.8是在元素插入后检测是否需要扩容,1.7则是在元素插入前;
-
jdk1.8是扩容时通过hash&cap==0将链表分散,无需改变hash值,而1.7是通过更新hashSeed来修改hash值达到分散的目的;
-
扩容策略:1.7中是只要不小于阈值就直接扩容2倍;而1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数不小于7就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素不小于32的时候才会再次扩容。