Java面试:5.05号

148 阅读15分钟

一、基础部分

1、java三大特性,都有什么用,怎么实现的,哪里用到了?

封装、继承、多态。

封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。

好处:将变化隔离,便于使用,提高重用性,提高安全性。

封装原则:将不需要对外提供的内容都隐藏起来。把属性都隐藏,提供公共方法对其访问。

应用:

1、private关键字:是一个权限修饰符。用于修饰成员(成员变量和成员函数)被私有化的成员只在本类中有效。

常用之一:将成员变量私有化,对外提供对应的set , get方法对其进行访问。提高对数据访问的安全性。

2、构造代码块和构造方法(Construct):

构造方法:用于给对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种。

特点:该函数的名称和所在类的名称相同。不需要定义返回值类型。该函数没有具体的返回值。

构造代码块: 1.构造代码块的作用与构造函数的作用的对比:

(1)构造代码块的作用:给所有对象进行统一的初始化,对象一建立就运行并且优先于构造函数,比如所有的婴儿出生都会哭。

(2)构造函数的作用:给对应的对象(new )进行初始化。构造代码块的格式: { 构造代码块; }

注意:构造代码块的大括号必须位于成员的位置上。

代码块的类别:

(1)构造代码块:在工作中经常会用到。

(2)局部代码块:大括号位于方法之内,基本上写不写没什么区别,现实开发中也很少会用到。它的作用是缩短 局部变量的生命周期,节省一点点内存。

(3)静态代码块:使用static修饰的代码块。

注意的事项:

(1)Java编译器在编译一个Java源文件的时候,会把成员变量的声明语句提前至一个类的最前端。

(2)成员变量的初始化工作其实都是在构造函数中执行的。

(3)一旦经过Java编译器编译后,那么构造代码块的代码就会被移动到构造函数中执行,构造代码块的代码是在构造函数之前执行的,构造函数中的代码是最后执行的。

(4)成员变量的显示初始化与构造代码块的代码是按照当前代码的顺序执行的。

继承:继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

在JAVA中, 被继承的类叫父类(parent class)或超类(superclass), 继承父类的类叫子类(subclass)或派生类(derivedclass)。因此, 子类是父类的一个专门用途的版本, 它继承了父类中定义的所有实例变量和方法, 并且增加了独特的元素 。

应用:

关键字:extends。使用继承 – 编写父类 – 编写子类, 继承父类

class Animal {

//公共的属性和方法

}

class Chicken extends Animal{

//子类特有的属性和方法

}

class Duck extends Animal {

}

基本语法:class Chicken extends Animal{ } 上述代码表示Chicken类继承Animal类,使用extends关键词将Animal类(父类/超类)和Chicken类(子类)连接接起来; 在继承关系下,Chicken类将拥有Animal类所有的非私有的方法和属性,Chicken类还可以拥有自己独有的方法和属性; 声明Animal类,实例化Chicken类时, Chicken类会自动向上转型为Animal类。

多态:在面向对象语言中,多态性是指一个方法可以有多种实现版本,即“一种定义,多种实现”。利用多态可以设计和实现可扩展的系统,只要新类也在继承层次中。新的类对程序的通用部分只需进行很少的修改,或不做修改。类的多态性表现为方法的多态性,方法的多态性主要有方法的重载和方法的覆盖。 重载:方法重载(overload)是指在同一个类中的多个方法可以同名但参数列表必须不同。重载表现为同一个类中方法的多态性。

重写:方法重写(override)是指子类冲定义了父类中同名的方法。 重写表现为父子与子类之间方法的多态性。

对象类型转换:基本类型的数据可以转换类型,当转换类型较高时可以自动转换,当转换类型较低时需要强制转换。对象类型也允许转换,这个转换只限于java类层次结构图上的一根枝干上,即父类和子类之间。枝干上离Object较近的为上,相反较远的为下,由此对象的类型转换分为“向上转型”和“向下转型”两种。(父类引用指向子类对象。) 向下转型:只能针对指向子类对象的基类对象引用进行 。

向上转型:子类对象转为父类对象。此处父类对象可以是接口。

2、HashMap、Hashtable、ConcurrentHashMap的原理与区别?

HashTable

底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化

初始size为11,扩容:newsize = olesize*2+1

计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap

底层数组+链表实现,可以存储null键和null值,线程不安全

初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂

扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入

插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)

当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀

计算index方法:index = hash & (tab.length – 1)

HashMap的初始值还要考虑加载因子:

哈希冲突:若干Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条Entry链,对Key的查找需要遍历Entry链上的每个元素执行equals()比较。

加载因子:为了降低哈希冲突的概率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定100/0.75=134的数组大小。

空间换时间:如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。

HashMap和Hashtable都是用hash算法来决定其元素的存储,因此HashMap和Hashtable的hash表包含如下属性:

容量(capacity):hash表中桶的数量

初始化容量(initial capacity):创建hash表时桶的数量,HashMap允许在构造器中指定初始化容量

尺寸(size):当前hash表中记录的数量

负载因子(load factor):负载因子等于“size/capacity”。负载因子为0,表示空的hash表,0.5表示半满的散列表,依此类推。轻负载的散列表具有冲突少、适宜插入与查询的特点(但是使用Iterator迭代元素时比较慢)

除此之外,hash表里还有一个“负载极限”,“负载极限”是一个0~1的数值,“负载极限”决定了hash表的最大填满程度。当hash表中的负载因子达到指定的“负载极限”时,hash表会自动成倍地增加容量(桶的数量),并将原有的对象重新分配,放入新的桶内,这称为rehashing。

HashMap和Hashtable的构造器允许指定一个负载极限,HashMap和Hashtable默认的“负载极限”为0.75,这表明当该hash表的3/4已经被填满时,hash表会发生rehashing。

“负载极限”的默认值(0.75)是时间和空间成本上的一种折中:

较高的“负载极限”可以降低hash表所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的操作(HashMap的get()与put()方法都要用到查询)

较低的“负载极限”会提高查询数据的性能,但会增加hash表所占用的内存开销

程序猿可以根据实际情况来调整“负载极限”值。 ConcurrentHashMap

底层采用分段的数组+链表实现,线程安全

通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)

Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术

有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁

扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容

Hashtable和HashMap都实现了Map接口,但是Hashtable的实现是基于Dictionary抽象类的。Java5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。

HashMap基于哈希思想,实现对数据的读写。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来存储值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞时,对象将会储存在链表的下一个节点中。HashMap在每个链表节点中储存键值对对象。当两个不同的键对象的hashcode相同时,它们会储存在同一个bucket位置的链表中,可通过键对象的equals()方法来找到键值对。如果链表大小超过阈值(TREEIFY_THRESHOLD,8),链表就会被改造为树形结构。

在HashMap中,null可以作为键,这样的键只有一个,但可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示HashMap中没有该key,也可以表示该key所对应的value为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个key,应该用containsKey()方法来判断。而在Hashtable中,无论是key还是value都不能为null。

Hashtable是线程安全的,它的方法是同步的,可以直接用在多线程环境中。而HashMap则不是线程安全的,在多线程环境中,需要手动实现同步机制。

Hashtable与HashMap另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。

先看一下简单的类图: 图片.png 从类图中可以看出来在存储结构中ConcurrentHashMap比HashMap多出了一个类Segment,而Segment是一个可重入锁。

ConcurrentHashMap是使用了锁分段技术来保证线程安全的。

锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

ConcurrentHashMap提供了与Hashtable和SynchronizedMap不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。

ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。

这里需要注意的一点是:在JDK1.8后hashmap链表长度大于8且节点数组长度大于64的时候,就把链表下所有的节点转为红黑树。

二、框架

1、SpringBoot常用注解,都有什么作用?

1.1、@SpringBootApplication,创建 SpringBoot 项目之后会默认在主类加上,可以看做是@Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合;

1.2、@EnableAutoConfiguration:启用 SpringBoot 的自动配置机制;

1.3、@ComponentScan:扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类;

1.4、@Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类。

2、Spring框架的常用注解及作用?

Spring Bean相关:

2.1、@Autowired:自动导入对象到类中;

2.2、我们一般使用 @Autowired 注解让 Spring 容器帮我们自动装配 bean。要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,可以采用以下注解实现:

@Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。

@Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。

@Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。

@Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。

2.3、@RestController注解:是@Controller和@ResponseBody的合集,表示这是个控制器 bean,并且是将函数的返回值直 接填入 HTTP 响应体中,是 REST 风格的控制器;

2.4、@Scope:声明 Spring Bean 的作用域。

四种常见的 Spring Bean 的作用域:

singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。

prototype : 每次请求都会创建一个新的 bean 实例。

request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。

session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。

2.5、Configuration:一般用来声明配置类,可以使用 @Component注解替代,不过使用Configuration注解声明配置类更加语义化。

前后端传值:

2.6、@PathVariable用于获取路径参数,@RequestParam用于获取查询参数;

2.7、@RequestBody:用于读取Request请求(可能是POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去;

读取配置信息:

2.8、@value:读取比较简单的配置信息;

2.9、@ConfigurationProperties:读取配置信息并与 bean 绑定;

参数校验:

2.10、@NotEmpty 被注释的字符串的不能为 null 也不能为空;

2.11、@Null 被注释的元素必须为 null;

2.12、@AssertTrue 被注释的元素必须为 true;

2.13、@AssertFalse 被注释的元素必须为 false;

2.14、@Email 被注释的元素必须是 Email 格式。

全局处理Controller层异常:

2.15、@ControllerAdvice :注解定义全局异常处理类;

2.16、@ExceptionHandler :注解声明异常处理方法。

JPA相关:

2.17、@Entity声明一个类对应一个数据库实体;

2.18、@Id :声明一个字段为主键;

2.19、@Column 声明字段;

2.20、@Transient:声明不需要与数据库映射的字段,在保存的时候不需要保存进数据库;

2.21、@Lob:声明某个字段为大字段;

2.22、@Transactional:在要开启事务的方法上使用;

2.23、@JsonIgnoreProperties:作用在类上用于过滤掉特定字段不返回或者不解析;

2.24、@JsonFormat一般用来格式化 json 数据;

2.25、@ActiveProfiles一般作用于测试类上,用于声明生效的 Spring 配置文件。

3、GET请求和POST请求的区别?

这里咱们将5中常见的请求类型都列举一下。

GET:请求从服务器获取特定资源。举个例子:GET /users(获取所有学生)

POST:在服务器上创建一个新的资源。举个例子:POST /users(创建学生)

PUT:更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:PUT /users/12(更新编号为 12 的学生)

DELETE:从服务器删除特定的资源。举个例子:DELETE /users/12(删除编号为 12 的学生)

PATCH:更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。

由于内容量较大,本人还要日常工作需要处理。故,将拿到的面试问题进行分批整理,希望大家能够理解。不足之处可以留言一块探讨哦!

Java面试百分百.jpg