什么是面向对象?谈谈你对面向对象的理解
什么是面向对象?
对比面向过程,是两种不同的处理问题的角度。面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象),及各自需要做什么
由例子得出:面向过程高效,面向对象易于维护,扩展,复用
面上对象的三大特性
- 封装:封装的意义,在于,明确标识出允许外部使用的所有成员函数和数据项,内部实现的细节对外部调用透明,外部调用无需修改或者关心内部实现
- 继承:继承父类的方法,并可以做出自己的扩展和方法的重写
- 多态:编译类型是运行类型的父类,当调用子类重写了父类的方法时,会进行动态绑定到子类重写了的方法。基于运行类型可以为不同的子类,外部调用同一个方法,实际执行的逻辑取决于当前运行类型的子类。注意:无法调用子类特有的方法
jdk jre jvm区别和联系
JDK:Java开发工具,Java开发人员所需要安装的
JRE:Java运行环境,单纯只想运行Java程序,只需要安装jre
jvm:虚拟机,用来解释**.class**文件,解释成机器码,让操作系统能识别并执行。
**联系:**我们开发人员开发的 .Java文件由jdk中的Java工具Javac编译成 .class文件 然后 .class文件会放到jvm上面,jvm适配了很多操作系统,所以 .class文件可以一处编译到处运行。当jvm拿到了 .class文件会借助jre中的类库(lib)去解释这个 .class文件,将它翻译成机器码,然后映射到对应的操作系统,然后调用
==和equals比较
==对比的是栈中的值,基本数据类型是变量值,引用类型是堆中的内存对象的地址
equals:object中默认也是采用==比较,通常会重写这个方法
hashcode和equals比较
hashcode介绍
为什么有hashcode
比较:
final关键字的作用
- 修饰类:表示类不可继承
- 修饰方法:表示方法不可被重写
- 修饰变量:表示变量一旦被赋值就不可以更改它的值
- 修饰成员变量
- 如果final修饰的是类变量:只能在静态初始块中指定初始值,或声明该变量时指定初始值
- 如果final修饰的是成员变量:可以在非静态代码块,构造器,声明该变量时指定初始值
- 修饰局部变量
- 系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,可以在定时变量时指定默认值(后面代码不能对该变量重新赋值),也可以不指定默认值,而在使用该变量前对final赋初值。
- 修饰基本数据类和引用类型
- 基本数据类型:数值一旦初始化之后不能再改变
- 引用类型数据:在对其初始化之后便不能再让其指向另一个对象,但是引用的值是可以变的
- 修饰成员变量
为什么局部内部类和匿名内部类只能访问局部的final变量
- 内部类和外部类是处于同一级别的,内部类不会因为定义在方法中,会随着方法的执行完毕被销毁。
- 这里会产生的问题:当外部类的方法调用完毕时,局部变量会被销毁,但是内部类对象可能还存在。这里就有一个矛盾,内部类对象访问一个不存在的变量,为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样局部变量死亡后,内部类仍然可以访问,实际访问的是局部变量的copy。这样就好像延长了局部变量的生命周期。但如何保持两个复制的变量和局部变量相同,这时就需要用final修饰来确保这个值不能改变
String,StringBuilder,StringBuffer的区别
- string是不可变的,如果尝试去修改,会生成一个新的字符串,stringbuilder和stringbuffer是可变的
- stringbuilder是线程不安全的,stringbuffer是线程安全的,所以在单线程的环境下使用stringbuilder效率更高
重载和重写的区别
- 重载:发生在同一个类,方法名必须相同,(参数类型,参数个数,参数顺序)其中一个不同就可,就是看括号是否完全相同,和返回参数和访问修饰符无关
- 重写:发生在父子类,方法名,参数列表必须相同,访问修饰符必须大于等于父类方法,返回值类型(小于)等于父类返回值类型或其子类,抛出的异常小于等于父类抛出的异常。如果父类方法为private修饰,则不可以被重写。
List和Set区别
- List:有序,俺对象进入的顺序保存对象,可以重复,允许多个null值,可以使用iterator取出所有元素,再逐一遍历,还可以使用get(int index)获取指定下标的元素
- Set:无序,不可重复,只能允许一个null值,只能使用iterator取出所有元素,再逐一遍历
ArrayList和LinkedList区别
ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固
定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会
涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚
至超过linkedList(需要创建大量的node对象)
LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐
一遍历
遍历LinkedList建议使用iterator不建议使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需
要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlOf对list进行了遍历,当结
果为空时会遍历整个列表
- 1、数据结构不同
- ArrayList是Array(动态数组)的数据结构,LinkedList是Link(双向链表)的数据结构。
- 2、效率不同
- 当随机访问List(get和set操作)时,ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动
- 3、自由性不同
- ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。
- 4、主要控件开销不同
- ArrayList主要控件开销在于需要在lList列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。
HashMap和HashTable有什么区别?其底层实现是什
么?
区别 :
(1)HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全;
(2)HashMap允许key和value为null,而HashTable不允许
2.底层实现:数组+单向链表实现
jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在
- 计算key的hash值,二次hash然后对数组长度取模,对应到数组下标,
- 如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组,
- 如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
- key为null,存在下标0的位置
ConcurrentHashMap原理,jdk7和jdk8版本的区别
jdk7:
数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个
HashEntry又是一个链表结构
元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部
锁:Segment分段锁 Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影
响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment
get方法无需加锁,volatile保证
jdk8:
数据结构:synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性
查找,替换,赋值操作都使用CAS
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写
操作、并发扩容
读操作无锁:
Node的val和next使用volatile修饰,读写线程对该变量互相可见
数组用volatile修饰,保证扩容时被读线程感知
Jdk1.7到Jdk1.8 HashMap 发⽣了什么变化(底层)?
- 1.7中底层是数组+链表,1.8中底层是数组+链表+红⿊树,加红⿊树的⽬的是提⾼HashMap插⼊和
查询整体效率 - 1.7中链表插⼊使⽤的是头插法,1.8中链表插⼊使⽤的是尾插法,因为1.8中插⼊key和value时需要
判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使⽤尾插法 - 1.7中哈希算法⽐较复杂,存在各种右移与异或运算,1.8中进⾏了简化,因为复杂的哈希算法的⽬的
就是提⾼散列性,来提供HashMap的整体效率,⽽1.8中新增了红⿊树,所以可以适当的简化哈希
算法,节省CPU资源
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 13 天,点击查看活动详情







