长沙某公司面经总结 - 失败版

1.Java语言的特征

Java的三大特性:封装、继承、多态

面向对象是利于语言对现实事物进行抽象。面向对象具有以下特征:

  • 继承:继承是从已有类得到继承信息创建新类的过程

  • 封装:封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口

  • 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应

1.继承:

只要两个类满足 is-a 的关系,就可以使用继承关系实现,继承的特点:

项目描述
子类子类拥有父类所有在子类中可以访问到的成员属性和成员方法
扩展子类可以重写父类中的方法,也可以定义独有的方法
限制类只能单继承,只允许有一个父类

需要区分两个概念:

项目描述
方法重载(Overload)发生在一个类中,只要求方法名相同、参数列表不同,与访问权限修饰符和方法返回值类型无关。
方法重写(Override)发生在继承关系的子类中,要求返回值类型、方法名、参数列表都相同,且访问权限不低于父类中的方法。

多态

  多态指同一行为有不同的表现形式,多态又分为编译时多态和运行时多态。

表现形式描述
编译时多态指方法重载,在编译期,根据调用方法时传递的参数列表,确定调用哪一个重载方法
运行时多态指方法重写,在运行期间确定调用父类方法还是子类方法

平时说的多态指的是运行时多态,它有三个必要条件:

  • 继承

  • 重写

  • 父类引用指向子类对象:Parent p = new Child();

2.定义一个类时,可不可以通过static关键字进行修饰?static 修饰的类有哪些特点

static是静态修饰符,一般用来修饰类中的成员。方便在没有创建对象的情况下来进行调用。

  • static关键字修饰类(只能修饰内部类)

public class StaticTest {
     // static关键字修饰内部类
     public static class InnerClass{
         InnerClass(){
             System.out.println("=======静态内部类=======");
         }
         public void InnerMethod(){
             System.out.println("========静态内部方法========");
         }
     }
 ​
     public static void main(String[] agrs){
         // 直接通过statictest类名来访问静态内部类
         InnerClass innerClass = new StaticTest.InnerClass();
         // 静态内部类可以和普通类一样使用
         innerClass.InnerMethod();
     }
 }

结果 :

 =======静态内部类=======
 ========静态内部方法========

如果没有用static修饰InterClass,则只能new 一个外部类实例。再通过外部实例创建内部类。


如果一个类要被声明为static的,只有一种情况,就是静态内部类。如果在外部类声明为static,程序会编译都不会过。

特点:

  1. 静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法

  2. 静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法。

  3. 静态内部类可以单独初始化:

 Inner i = new Outer.Inner();

普通内部类初始化:

 Outer o = new Outer();
 ​
 Inner i = o.new Inner();

静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计。

3.线程的创建方式**

  1. 继承Thread类创建线程

  2. 实现Runnable接口创建线程

  3. 使用Callable和Future创建线程 有返回值

  4. 使用线程池创建线程

 #### 代码演示
 import java.util.concurrent.*;
 public class threadTest{
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         //继承thread
         ThreadClass thread = new ThreadClass();
         thread.start();
         Thread.sleep(100);
         System.out.println("#####################");
  
         //实现runnable
         RunnableClass runnable = new RunnableClass();
         new Thread(runnable).start();
         Thread.sleep(100);
         System.out.println("#####################");
  
         //实现callable
         FutureTask futureTask = new FutureTask(new CallableClass());
         futureTask.run();
         System.out.println("callable返回值:" + futureTask.get());
         Thread.sleep(100);
         System.out.println("#####################");
  
         //线程池
         ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
         threadPoolExecutor.execute(thread);
         threadPoolExecutor.shutdown();
         Thread.sleep(100);
         System.out.println("#####################");
  
         //使用并发包Executors
         ExecutorService executorService = Executors.newFixedThreadPool(5);
         executorService.execute(thread);
         executorService.shutdown();
     }
 }
  
 class ThreadClass extends Thread{
     @Override
     public void run() {
         System.out.println("我是继承thread形式:" + Thread.currentThread().getName());
     }
 }
  
 class RunnableClass implements Runnable{
     @Override
     public void run(){
         System.out.println("我是实现runnable接口:" + Thread.currentThread().getName());
     }
 }
  
 class CallableClass  implements Callable<String> {
     @Override
     public String call(){
         System.out.println("我是实现callable接口:");
         return "我是返回值,可以通过get方法获取";
     }
 }

4. 线程池的创建方式

什么是线程池?

线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。

池化思想在计算机的应用也比较广泛,比如以下这些:

  • 内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。

  • 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。

  • 实例池(Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。

线程池的优势主要体现在以下 4 点:

  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。

  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。

  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低统的稳定性。使用线程池可以进行统一的分配、调优和监控。

  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

同时阿里巴巴在其《Java开发手册》中也强制规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

 说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

知道了什么是线程池以及为什要用线程池之后,我们再来看怎么用线程池。线程池使用

线程池的创建方法总共有 7 种,但总体来说可分为 2 类:

  • 一类是通过 ThreadPoolExecutor 创建的线程池;

  • 另一个类是通过 Executors 创建的线程池。

线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过ThreadPoolExecutor 创建的):

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;

  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;

  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;

  4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;

  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;

  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。

  7. ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。

单线程池的意义从以上代码可以看出 newSingleThreadExecutor 和 newSingleThreadScheduledExecutor 创建的都是单线程池,那么单线程池的意义是什么呢?答:虽然是单线程池,但提供了工作队列,生命周期管理,工作线程维护等功能。

那接下来我们来看每种线程池创建的具体使用。

使用示例如下:

 public static void fixedThreadPool() {
    
 // 创建 2 个数据级的线程池
     ExecutorService threadPool = Executors.newFixedThreadPool(2);
  
     // 创建任务
     Runnable runnable = new Runnable() {
         @Override
         public void run() {
             System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
         }
     };
  
     // 线程池执行任务(一次添加 4 个任务)
     // 执行任务的方法有两种:submit 和 execute
     threadPool.submit(runnable);  // 执行方式 1:submit
     threadPool.execute(runnable); // 执行方式 2:execute
     threadPool.execute(runnable);
     threadPool.execute(runnable);
 }

执行结果如下

如果觉得以上方法比较繁琐,还用更简单的使用方法,如下代码所示:

public static void fixedThreadPool() {
     // 创建线程池
     ExecutorService threadPool = Executors.newFixedThreadPool(2);
     // 执行任务
     threadPool.execute(() -> {
         System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
     });
 }

具体可见: 原文链接:面试官:线程池有哪几种创建方式,能详细的说下么?_面试题线程池的创建方式有几种-CSDN博客

5.mybatis 的优缺点

一、MyBatis 框架的优点

  1. 与JDBC相比,减少了50%以上的代码量。

  2. MyBatis是最简单的持久化框架,小巧并且简单易学。

  3. MyBatis灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL可以写在XML里(还可以以注解方式写到Java代码中),从程序代码中彻底分离,降低耦合度,便于统一管理和优化,可重用。

  4. 提供XML标签,支持编写动态SQL语句(XML中使用 if, else 等)。

  5. 提供映射标签,支持对象与数据库的ORM字段关系映射(可以在XML中配置映射关系,也可以使用注解配置映射关系)。

二、MyBatis 框架的缺点

  1. SQL语句的编写工作量较大,尤其是字段多、关联表多时,更是如此,对开发人员编写SQL语句的功底有一定要求。

  2. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

  3. 与JDBC相比,增加了调试代码和查错的难度(使用了ORM需要额外花费时间学习ORM的框架,调试错误比JDBC难很多)。封装程度越高,查错越难。

  4. JDBC可以单步调试,MyBatis不能单步调试。

MyBatis 的优缺点

1.SQL语句与代码分离,存放于XML配置文件中: 优点:便于维护管理,不用在java代码中找这些语句; 缺点:JDBC方式可以用断点的方式调试,但是Mybatis不能用断点的方式调试,需要通过log4j日志输出日志信息帮助调试,然后在配置文件中修改。

2.用逻辑标签控制动态SQL的拼接: 优点:用标签代替编写逻辑代码; 缺点:拼接复杂SQL语句时,没有代码灵活,拼写比较复杂。不要使用变通的手段来应对这种复杂的语句。

3.查询的结果集与java对象自动映射: 优点:保证名称相同,配置好映射关系即可自动映射或者,不配置映射关系,通过配置列名=字段名也可完成自动映射。 缺点:对开发人员所写的SQL依赖很强。

4.编写原生SQL: 优点:接近JDBC,比较灵活。 缺点:对SQL语句依赖程度很高;并且属于半自动,数据库移植比较麻烦,比如mysql数据库变成Oracle数据库,部分的sql语句需要调整。

6.post 请求缺点

Get和Post在面试中一般的区别

从HTTP的报文层面看: post更安全,不会作为url的一部分,不会被缓存、保存在服务器日志、以及浏览器浏览记录中. post发送的数据更大(get有url长度限制) post能发送更多的数据类型(get只能发送ASCII字符) post比get慢 post用于修改和写入数据,get一般用于搜索排序和筛选之类的操作(淘宝,支付宝的搜索查询都是get提交),目的是资源的获取,读取数据

从数据库的层面看: get 更符合幂等性和安全 性。

其他方面看比如: get可以被缓存,被存储,而post 不能

从安全性来看: POST的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的Security的含义,比如:通过GET提交数据,用户名和密码将明文出现在URL上,因为

  1. 登录页面有可能被浏览器缓存,

  2. 其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用GET提交数据还可能会造成Cross-site request forgery攻击。

Get是向服务器发索取数据的一种请求,而Post是向服务器提交数据的一种请求,在FORM(表单)中,Method默认为"GET",实质上,GET和POST只是发送机制不同,并不是一个取一个发!

要弄清楚GET,POST原理

一.为什么get比post更快

1.post请求包含更多的请求头 ,因为post需要在请求的body部分包含数据,所以会多了几个数据描述部分的首部字段(如:content-type),这其实是微乎其微的。

2.最重要的一条,post在真正接收数据之前会先将请求头发送给服务器进行确认,然后才真正发送数据

post请求的过程:

  1. 浏览器请求tcp连接(第一次握手)

  2. 服务器答应进行tcp连接(第二次握手)

  3. 浏览器确认,并发送post请求头(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)

  4. 服务器返回100 Continue响应

  5. 浏览器发送数据

  6. 服务器返回200 OK响应

get请求的过程:

  1. 浏览器请求tcp连接(第一次握手)

  2. 服务器答应进行tcp连接(第二次握手)

  3. 浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)

  4. 服务器返回200 OK响应

也就是说,目测get的总耗是post的2/3左右,这个口说无凭,网上已经有网友进行过测试。

3.get会将数据缓存起来,而post不会

可以做个简短的测试,使用ajax采用get方式请求静态数据(比如html页面,图片)的时候,如果两次传输的数据相同,第二次以后消耗的时间将会在10ms以内(chrome测试),而post每次消耗的时间都差不多。经测试,chrome和firefox下如果检测到get请求的是静态资源,则会缓存,如果是数据,则不会缓存,但是IE什么都会缓存起来,当然,应该没有人用post去获取静态数据吧,反正我是没见过。

4.post不能进行管道化传输

http权威指南中是这样说的:http的一次会话需要先建立tcp连接(大部分是tcp,但是其他安全协议也是可以的),然后才能通信,如果 每次连接都只进行一次http会话,那这个连接过程占的比例太大了!于是出现了持久连接:在http/1.0+中是connection首部中添加keep-alive值,在http/1.1中是在connection首部中添加persistent值,当然两者不仅仅是命名上的差别,http/1.1中,持久连接是默认的,除非显示在connection中添加close,否则持久连接不会关闭,而http/1.0+中则恰好相反,除非显示在connection首部中添加keep-alive,否则在接收数据包后连接就断开了。

出现了持久连接还不够,在http/1.1中,还有一种称为管道通信的方式进行速度优化:把需要发送到服务器上的所有请求放到输出队列中,在第一个请求发送出去后,不等到收到服务器的应答,第二个请求紧接着就发送出去,但是这样的方式有一个问题:不安全,如果一个管道中有10个连接,在发送出9个后,突然服务器告诉你,连接关闭了,此时客户端即使收到了前9个请求的答复,也会将这9个请求的内容清空,也就是说,白忙活了……此时,客户端的这9个请求需要重新发送。这对于幂等请求还好(比如get,多发送几次都没关系,每次都是相同的结果),如果是post这样的非幂等请求(比如支付的时候,多发送几次就惨了),肯定是行不通的。

所以,post请求不能通过管道的方式进行通信!很有可能,post请求需要重新建立连接,这个过程不跟完全没优化的时候一样了么?所以,在可以使用get请求通信的时候,不要使用post请求,这样用户体验会更好,当然,如果有安全性要求的话,post会更好。管道化传输在浏览器端的实现还需考证,貌似默认情况下大部分浏览器(除了opera)是不进行管道化传输的,除非手动开启!

二、get传参最大长度的理解误区

总结 (1)http协议并未规定get和post的长度限制 (2)get的最大长度限制是因为浏览器和web服务器限制了URL的长度 (3)不同的浏览器和web服务器,限制的最大长度不一样 (4)要支持IE,则最大长度为2083byte,若支持Chrome,则最大长度8182byte

2.误解 (1)首先即使get有长度限制,也是限制的整个URL的长度,而不仅仅是参数值数据长度,http协议从未规定get/post的请求长度限制是多少 (2)所谓的请求长度限制是由浏览器和web服务器决定和设置的,各种浏览器和web服务器的设定均不一样,这依赖于各个浏览器厂家的规定或者可以根据web服务器的处理能力来设定。IE 和 Safari 浏览器 限制 2k,Opera 限制4k,Firefox 限制 8k(非常老的版本 256byte),如果超出了最大长度,大部分的服务器直接截断,也有一些服务器会报414错误。

3.各个浏览器和web服务器的最大长度总结 浏览器 (1)IE:IE浏览器(Microsoft Internet Explorer) 对url长度限制是2083(2K+53),超过这个限制,则自动截断(若是form提交则提交按钮不起作用)。 (2)firefox:firefox(火狐浏览器)的url长度限制为 65536字符,但实际上有效的URL最大长度不少于100,000个字符。 (3)chrome:chrome(谷歌)的url长度限制超过8182个字符返回本文开头时列出的错误。 (4)Safari:Safari的url长度限制至少为 80 000 字符。 (5)Opera:Opera 浏览器的url长度限制为190 000 字符。Opera9 地址栏中输入190000字符时依然能正常编辑。 服务器 (1)Apache:Apache能接受url长度限制为8 192 字符 (2)IIS:Microsoft Internet Information Server(IIS)能接受url长度限制为16384个字符。这个是可以通过修改的(IIS7) configuration/system.webServer/security/requestFiltering/requestLimits@maxQueryStringsetting.

7.mysql数据库的存储过程

我们可以把 存储过程 看成是一些 SQL 语句的集合,中间加了点逻辑控制语句。存储过程在业务比较复杂的时候是非常实用的,比如很多时候我们完成一个操作可能需要写一大串 SQL 语句,这时候我们就可以写有一个存储过程,这样也方便了我们下一次的调用。存储过程一旦调试完成通过后就能稳定运行,另外,使用存储过程比单纯 SQL 语句执行要快,因为存储过程是预编译过的。

存储过程在互联网公司应用不多,因为存储过程难以调试和扩展,而且没有移植性,还会消耗数据库资源。

阿里巴巴 Java 开发手册里要求禁止使用存储过程。

阿里巴巴Java开发手册: 禁止存储过程

8.事务的类型

PROPAGATION_REQUIRED (propagation_required)--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

PROPAGATION_SUPPORTS(propagation_supports)--支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY(propagation_mandatory)--支持当前事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW(propagation_requires_new)--新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED(propagation_not_support)--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER(propagation_never)--以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED(propagation_nested)--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

名称解释
PROPAGATION_REQUIRED (propagation_required)支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS(propagation_supports)支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY(propagation_mandatory)支持当前事务,如果当前没有事务,就抛出异常
PROPAGATION_REQUIRES_NEW(propagation_requires_new)新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED(propagation_not_support)以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER(propagation_never)以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED(propagation_nested)如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与 'propagation_required' 类似的操作。

9.序列化一个对象时,我不想对其中一个属性序列化该怎么办

当使用 java 自带的序列化方式序列化对象时, 如果想要排除掉某些属性, 可以使用 transient 关键字修饰

1、transient 修饰的属性,是不会被序列化的; 2、static 修饰的属性,是不会被序列化的

// 该属性不再被序列化和反序列化
private transient String name;

使用Jackson注解: @JosnIgnore

该注解一般使用在成员变量上,表示,在Jackson序列化的时候,忽略该属性。 使用情况:一些不需要返回给前端的属性可以使用该注解忽略掉

@Data
@Builder
public class User {
    private String username;
    @JsonIgnore
    private String password;
    private Date brithday;
    private String headImage;
    @JsonIgnore
    private Integer account;
    @JsonIgnore
    private String address;
    @JsonIgnore
    private String mobile;
}

10.springmvc 的工作流程

  • 用户发送请求至前端控制器 DispatcherServlet

  • DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。

  • 处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet 。

  • DispatcherServlet 调用 HandlerAdapter 处理器适配器

  • HandlerAdapter 经过适配调用具体的处理器 (Controller,也叫后端控制器)。

  • Controller执行完成返回 ModelAndView

  • HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet

  • DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器

  • ViewReslover 解析后返回具体 View

  • DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。

  • DispatcherServlet 响应用户

11.HashMap与HashSet的区别

HashMapHashSet
实现了 Map 接口实现 Set 接口
存储键值对仅存储对象
调用 put()向 map 中添加元素调用 add()方法向 Set 中添加元素
HashMap 使用键(Key)计算 hashcodeHashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以equals()方法用来判断对象的相等性

HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是 直接调用 HashMap 中的方法。)

HashSet是通过HasMap来实现的,HashMap的输入参数有Key、Value两个组成,在实现HashSet的时候,保持HashMap的Value为常量,相当于在HashMap中只对Key对象进行处理。

HashMap的底层是一个数组结构,数组中的每一项对应了一个链表,这种结构称“链表散列”的数据结构,即数组和链表的结合体;也叫散列表、哈希表。

  1. 实现接口不同:

    • HashMap实现了Map接口

    • HashSet实现了set接口

  1. 存储内容不同:

    • HashMap存储的是键值对

    • HashSet存储的是对象

  1. 添加元素的方法不同:

    • HashMap调用put()方法向map中添加元素

    • HashSet调用add()方法向map中添加元素

  1. 计算hashCode的方式不同

    • HashMap使用键(key)来计算hashCode

    • HashSet使用成员对象来计算hashcode的值,对于两个对象来说,他们的hashcode值可能相同,所以用equals()方法来判断对象的相等性。如果两个对象不相等的话返回false

  1. 效率

    • HashMap相对于HashSet较快,因为它是使用唯一的键获取对象。

存储对象过程

一、HahMap存储对象过程

  1. 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值(非常大)经过某种算法计算以后,得到在Entry数组中的存放位置。 情况1:如果此位置上的数据为空,此时的key1-value1添加成功。

  1. 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值: 情况2:如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。

  1. 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较: 情况3:如果equals()返回false:此时key1-value1添加成功。

  1. 如果equals()返回true:使用value1替换value2。

二、HashSet存储对象过程

  1. 往HashSet添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值

  2. 然后通过元素的哈希值经过移位等运算,就可以算出该元素在哈希表中的存储位置。 情况1: 如果算出元素存储的位置目前没有任何元素存储,那么该元素可以直接存储到该位置上。 情况2: 如果算出该元素的存储位置目前已经存在有其他的元素了,那么会调用该元素的equals方法与该位置的元素再比较一次,如果equals返回的是true,那么该元素与这个位置上的元素就视为重复元素,不允许添加,如果equals方法返回的是false,那么该元素运行添加。

12.一个普通方法中定义的普通变量,存储在哪个区?

局部变量是在代码块或方法中定义的变量或是方法的参数,所以题目意思即,局部变量存储在Java的哪个区?

成员变量与局部变量的区别?

成员变量 vs 局部变量

成员变量 vs 局部变量

  • 语法形式:从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。

  • 存储方式:从变量在内存中的存储方式来看,如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存

  • 生存时间:从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。

  • 默认值:从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。

13.谈谈你对反射机制的理解

反射是Java语言里面的一个特性,它能够再程序运行的过程中去构造任意一个类对象,并且可以获取任意一个类的成员变量、成员方法和属性,以及调用任意一个对象方法。

通过反射的能力,可以让Java语言支持动态获取程序的信息,以及动态调用方法的能力。再Java中,专门有一个 java.lang.reflect 这样一个包,来实现反射相关的一些类库,包括Construct、Field、Method等等这样一些类,分别取获取类的构造方法、成员变量和方法信息。

在反射使用的场景中,可以使用动态生成的代理类来提升代码的复用性,在spring框架中有大量用到反射,比如用反射来实例化Bean对象等等。

Java反射的优点:

  1. 增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作。

  2. 提高代码的复用性,比如动态代理,就是用道理反射来实现。

  3. 可以在运行时轻松获取任意一个类的方法,属性,并且还能通过反射进行动态调用

Java反射缺点:

  1. 反射会涉及到动态类型的解析,所以JVM无法对这些代码进行优化,导致性能要比非反射调用更低。

  2. 使用反射以后,代码的可读性会下降。

  3. 反射可以绕过一些限制访问的属性或者方法,可能会导致破坏代码本身的抽象性。


九月份的面试,当时准备的过于匆促,过了HR面到了技术面就直接挂了,太垃圾了

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫一只啦啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值