Java高频面试题之基础篇

1、什么是面向对象?

对比面向过程,是两种不同的处理问题的角度
面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象)、及各自需要做什么
比如:洗衣机洗衣服
面向过程会将任务拆解成一系列的步骤(函数) ,1、打开洗衣机–.>2、放衣服—3、放洗粉–…>4、清洗.->5烘
面向对象会拆出人和洗衣机两个对象:
人:打开洗衣机放衣服放洗粉
洗衣机:清洗烘干
从以上例子能看出,面向过程比较直接高效,而面向对象更易于复用、扩展和维护。
封装:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项内部细节对外部调用透明,外部调用无需修改或者关心内部实现
1、javabean的属性私有,提供getset对外访问,因为属性的赋值或者获取逻辑只能由javabean本身决定。而不能由外部胡乱修改
在这里插入图片描述

该name有自己的命名规则,明显不能由外部直接赋值
2、orm框架
操作数据库,我们不需要关心链接是如何建立的、sql是如何执行的,只需要引入mybatis,调方法即可
继承:继承基类的方法,并做出自己的改变和/或扩展子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的
多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。

2、JDK、JRE、JVM之间的区别

JDKJava SE Develooment K),Java标准开发包,它提供了编译、运行lava程序所需的各种工具和资源,包Java编译器、Java运行时环境,以及常用的Java类库等JRE( Java Runtime Environment),Java运行环境,用于运行Java的字节码文件。JRE中包括了NM以及VM工作所需要的类库,普通用户而只需要安装/RE来运行Java程序,而程序开发者必须安装JDK来编译、调试程序。
JVM(Java Virtual Mechina),Java虚拟机,是JRE的一部分,它是整个java实现跨平台的最核心的部分,负责运行字节码文件
我们写Java代码,用d就可以写,但是写出来的Java代码,想要运行,需要先编译成字节码,那就需要编淫器,而/DK中就包含了编泽器/avac,编泽之后的字节码,想要运行,就需要一个可以执行字节码的程序,这个程序就是JVM Java虚拟机),专门用来执行Java字节码的。
如果我们要开发Java程序,那就需要JDK,因为要编译Java源文件。
如果我们只想运行已经编译好的Java字节码文件,也就是*.class文件,那么就只需要JRE。
JDK中包含了JRE,JRE中包含了JVM。另外,NM在执行Java字节码时,需要把字节码解释为机器指令,而不同操作系统的机器指令是有可能一样的,所以就导致不同操作系统上的VM是不一样的,所以我们在安装JDK时需要选择操作系统。
另外,NM是用来执行Java字节码的,所以凡是某个代码编译之后是Java字节码,那就都能在M上运行,比如Apache Groowy,Scala and Kotin 等等。

3、==和equals方法的区别

==:如果是基本数据类型,比较是值,如果是引用类型,比较的是引用地址,
equals:具体看各个类重写equas方法之后的比较逻辑,比String类,虽然是用类型,但是String类中重写了euals方法,方法内部比较的是字符电中的各个字符是否全部相等。

4、hashCode()与equals()之间的关系

在ava中,每个对象都可以调用自己的hashode()方法得到自己的哈希值(hashode),相当于对象的指纹信息,通常来说世界上没有完全相同的两个指纹,但是在Java中做不到这么绝对,但是我们仍然可以利用hashCode来做一些提前的判断,比如:如果两个对象的hashCode不相同,那么这两个对象肯定不同的两个对象。如果两个对象的hashCode相同,不代表这两个对象一定是同一个对象,也可能是两个对象。如果两个对象相等,那么他们的hashCode就一定相同
在Jave的一些集合类的实现中,在比较两个对象是否相等时,会根据上面的原则,会先调用对象的hashode方法得到hashode进行比较如里hashode不相同,就可直接认为这两个对象不相同,如果hashCode相同,那么就会进一步调用euas()方法进行比。而equals()方法,就是用来最终确定两对象是不是相等的,通常equals方法的实现会比较重,逻辑比较多,而hashde主要就是得到一个哈希值,实际上就一个数字,相对而言比较轻,所以在比较两个对象时,通常都会先根据hashCode想比较下。
所以我们就需要注意,如果我们重写了equals()方法,那么就要注意hashCode()方法,一定要保证能遵守上述规则。
在这里插入图片描述
在这里插入图片描述

5、简述final的作用

最终的
◆ 修饰类:表示类不可被继承
◆ 修饰方法: 表示方法不可被子类覆盖,但是可以重载
◆ 修饰变量::表示变量一旦被赋值就不可以更改它的值。
(1) 修饰成员变量
如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。
(2) 修饰局部变量
系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用fnal修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对fnal变量赋初值 (仅一次)
在这里插入图片描述
(3)修饰基本类型数据和引用类型数据
如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是引用的值是可变的
在这里插入图片描述

6、为什么局部内部类和匿名内部类只能访问局部final变量

在这里插入图片描述
注:test方法的形参b必须加final修饰,不加就会报错
在这里插入图片描述
首先需要知道的一点是:内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。
这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的“copy”。这样就好像延长了局部变量的生命周期
将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?就将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。使得局部变量与内部类内建立的拷贝保持一致。

7、String、StringBuffer、StringBuilder的区别

1,String是不可变的,如果尝试去修改,会新生成一个字符串对象,StringBufer和StringBuilder是可变的2,StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更高
在这里插入图片描述

8、重载和重写的区别

重载:发生在同一个类,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类,如果父类方法访问修饰符为private则子类就不能重写该方法。
在这里插入图片描述

9、接口和抽象类的区别

◆抽象类可以存在普通成员函数,而接口中只能存在public abstract 方法
◆抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
◆抽象类只能继承一个,接口可以实现多个。
接口的设计目的,是对类的行为进行约束(更准确的说是一种“有”约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。
而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来 (否则当调用到A-B时,无法执行)。
抽象类是对类本质的抽象,表达的是 is a 的关系,比如:BMW is a car 。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。
而接口是对行为的抽象,表达的是 like a 的关系。比如:Bird like a Airraft (像飞行器一样可以飞),但其本质上 is a bird。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。
使用场景:当你关注一个事物的本质的时候,用抽象类,当你关注一个操作的时候,用接口。抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度。

10、List和Set的区别

List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素在逐一遍历,还可以使用get(int index)获取指定下表的元素
Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用lterator接口取得所有元素,在逐一遍0历各个元素

11、ArrayList和LinkedList区别

ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList (需要创建大量的node对象)
LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历
遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get()取得某一元素时都需要对list重新进行遍历,性能消耗极大。
另外不要试图使用index0f等返回元素索引,并利用其进行遍历,使用indexlof对list进行了遍历,当结果为空时会遍历整个列表。

12、HashMap和HashTable有什么区别? 其底层实现是什么?

区别 :
(1) HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全;(2)HashMap允许key和value为null,而HashTable不允许
2.底层实现:数组+链表实现
◆idk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在计算key的hash值,二次hash然后对数组长度取模,对应到数组下标
◆如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组
◆如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
◆key为null,存在下标0的位置

13、谈谈ConcurrentHashMap的扩容机制

1.7版本
1,1.7版本的ConcurrentHashMap是基于Segment分段实现的
2,每个Segment相对于一个小型的HashMap
3,每个Segment内部会进行扩容,和HashMap的扩容逻辑类似
4,先生成新的数组,然后转移元素到新数组中
5,扩容的判断也是每个Segment内部单独判断的,判断是否超过闻值
在这里插入图片描述

1.8版本
1,1.8版本的ConcurrentHashMap不再基于Segment实现
2,当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容那么该线程一起进行扩容
3,如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阔值,超过了则进行扩容
4,ConcurrentHashMap是支持多个线程同时扩容的
5,扩容之前也先生成一个新的数组
6,在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作
在这里插入图片描述

14、Jdk1.7到Jdk1.8 HashMap 发生了什么变化(底层)?

1、1.7中底层是数组+链表,1.8中底层是数组+链表+红黑树,加红黑树的目的是提高HashMap插入和查询整体效率
2、1.7中链表庙入使用的是头面法,1.8中证表通入使用的是尾法,因为1.8中入key和vaue时需要判断表素个数,所以需要遍历的表统计能表元素个数,所以正就直接使用尾播法
3、1.7中哈希算法比较复杂,存在各种移与异或运算,1.8中进行了简化,因为复杂的哈希算法的目的就是提高的列性,来提供HashMap的整体效率,而1.8中新增了红黑树,所以可以适当的简化哈希算法,节省CPU资源

15、说一下HashMap的Put方法

先说HashMap的Put方法的大体流程:
1,根据Key通过哈希算法与与运算得出数组下标
2,如果数组下标位置元素为空,则将key和value封装为Entry对象 (JDK1.7中是Entry对象,JDK18中是Node对象)并放入该位置
3,如果数组下标位置元素不为空,则要分情况讨论
a,如果是]DK1.7,则先判渐是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entv对象,并使用头插法添加到当前 位置的链表中
b,如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node
● 如里是红黑树Node,则将kev和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红里树中是否存在当前key,如果存在则更新value
● 如果此位置上的Node对象是表节点,则将key和value封装为一个链表Node并通过尾插法插入到鞋表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果大于等于8,那么则会将该链表转成红里树
● 将key和value封装为Node插入到链表或红黑树中后,再判断是否需要进行扩容,如果需要就扩容,如果不需要就结束PUT方法

16、泛型中extends和super的区别

1 <? extends T>表示包括T在内的任何T的子类
2 <? super T表示包括T在内的任何T的父类
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

17、深拷贝和浅拷贝

深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用。
1.浅拷贝是指,只会拷贝基本数类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象
2.深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的属性指向的不是同一个对象

18、HashMap的扩容机制原理

1.7版本
1,先生成新数组
2,遍历老数组中的每个位置上的链表上的每个元素
3,取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
4,将元素添加到新数组中去
5,所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
1.8版本
1,先生成新数组
2,遍历老数组中的每个位置上的链表或红黑树
3,如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去4.如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置
a,统计每个下标位置的元素个数
b,如果该位置下的元素个数超过了8,则生成一个新的红黑树,并将根节点的添加到新数组的对应位置
c,如果该位置下的元素个数没有超过8,那么则生成一个链表,并将链表的头节点添加到新数组的对应位置5,所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

19、CopyOnWriteArrayList的底层原理是怎样的

1,首先CopyOnWiteAralist内部也是用过数组来实现的,在向CopyOnwiteAraylist添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进

2,并且,写操作会加锁,防止出现并发写入丢失数据的问题
3,写操作结束之后会把原数组指向新数组
4,CopyOnWiteArraylist允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnwiteArayist会比较占内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值