Java面试题

本文深入探讨软件工程中的高内聚、低耦合原则,解释面向对象编程的封装、继承、多态特性,对比Java中int与Integer、ArrayList与LinkedList等数据结构的区别,解析HTTP请求、Session管理、JDBC流程等网络编程知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础篇一

我所理解的高内聚是模块内部是独立完成某个单一的功能,尽可能的少而简单,也就是常说的单一责任原则。低耦合是各个模块之间相互独立存在,这样利于修改和组合。短期来看,并没有很明显的好处,甚至短期内会影响系统的开发进度,因为对开发设计人员提出了更高的要求,但长期来看,带来的好处是使程序更容易维护和修改。可复用/可扩展/够灵活/可维护/从而提升产品迭代效率
高内聚 一个类只做一种事情,一个方法只做一件事

写只写一次 改只改一次

面对对象的核心思想 :万物皆对象(保护所有的属性和方法)

:佛修今生 道修来世:
产品是一个生命迭代的过程

面对对象的三大基本特征 :封装 继承 多态:

封装:
封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。 封装的是类的属性(名词)和方法(动词) 封装可以提高代码的重用性,能够隐藏实现细节。
继承:
面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态,有二种方式,覆盖,重载。

final, finally, finalize 的区别

final
用于声明属性,方法和类, 分别表示属性不可变, 方法不可覆盖, 类不可继承.

finally
是异常处理语句结构的一部分,表示总是执行.

finalize
是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等. JVM不保证此方法总被调用.

int 和 Integer 有什么区别

int 是 Java 提供的 8 种原始数据类型之一。Java 为每个原始类型提供了封装类,Integer 是 Java 为 int 提供的封装类。
int 的默认值为 0,而 Integer 的默认值为 null,是引用类型,即 Integer 可以区分出未赋值和值为 0 的区别,int 则无法表达出未赋值的情况,
Java 中 int 和 Integer 关系是比较微妙的。关系如下:

int 是基本的数据类型;
Integer 是 int 的封装类;
int 和 Integer 都可以表示某一个数值;
int 和 Integer 不能够互用,因为他们两种不同的数据类型;

重载 Overload

表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。

重写 Override

表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,因为子类可以解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。如果父类的方法是private类型,那么,子类则不存在覆盖的限制,相当于子类中增加了一个全新的方法。

抽象类和接口有什么区别

参数抽象类接口
默认的方法实现它可以有默认的方法实现接口完全是抽象的。它根本不存在方法的实现
实现子类使用 extends 关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字 implements 来实现接口。它需要提供接口中所有声明的方法的实现
构造器抽象类可以有构造器接口不能有构造器
与正常 Java 类的区别除了你不能实例化抽象类之外,它和普通Java类没有任何区别接口是完全不同的类型
访问修饰符抽象方法可以有 public、protected 和 default 这些修饰符接口方法默认修饰符是 public。你不可以使用其它修饰符。
main 方法抽象方法可以有 main 方法并且我们可以运行它接口没有 main 方法,因此我们不能运行它。
多继承抽象方法可以继承一个类和实现多个接口接口只可以继承一个或多个其它接口
速度它比接口速度要快接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。

说说反射的用途及实现

Java 反射机制是一个非常强大的功能,在很多的项目比如 Spring,MyBatis 都都可以看到反射的身影。通过反射机制,我们可以在运行期间获取对象的类型信息。利用这一点我们可以实现工厂模式和代理模式等设计模式,同时也可以解决 Java 泛型擦除等令人苦恼的问题。
获取一个对象对应的反射类,在 Java 中有下列方法可以获取一个对象的反射类
通过 getClass() 方法
通过 Class.forName() 方法
使用 类.class
通过类加载器实现,getClassLoader()

说说自定义注解的场景及实现

登陆、权限拦截、日志处理,以及各种 Java 框架,如 Spring,Hibernate,JUnit 提到注解就不能不说反射,Java 自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过 AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志注解,就进行日志记录。反射的实现在 Java 应用层面上讲,是通过对 Class 对象的操作实现的,Class 对象为我们提供了一系列方法对类进行操作。在 JVM 这个角度来说,Class 文件是一组以 8 位字节为基础单位的二进制流,各个数据项目按严格的顺序紧凑的排列在 Class 文件中,里面包含了类、方法、字段等等相关数据。通过对 Class 数据流的处理我们即可得到字段、方法等数据。

HTTP 请求的 GET 与 POST 方式的区别

根据 HTTP 规范,GET 用于信息获取,而且应该是安全的和幂等的。
根据 HTTP 规范,POST 表示可能修改变服务器上的资源的请求。
首先是 “GET 方式提交的数据最多只能是 1024 字节”,因为 GET 是通过 URL 提交数据,那么 GET 可提交的数据量就跟 URL 的长度有直接关系了。而实际上,URL 不存在参数上限的问题,HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE 对 URL 长度的限制是 2083 字节(2K+35)。对于其他浏览器,如 Netscape、FireFox 等,理论上没有长度限制,其限制取决于操作系统的支持。注意这是限制是整个 URL 长度,而不仅仅是你的参数值数据长度。
POST 是没有大小限制的,HTTP 协议规范也没有进行大小限制
get 先向服务器发送heard 和data然后返回200 成功
而post就是向向服务器发送heard 然后服务器返回100,然后再向服务器发送data 最后服务器返回200完成 也就是说get 是一次对话 而post 他却是2次对话

cookie 和session 的区别:

  • cookie数据存放在客户的浏览器上,session数据放在服务器上。
  • _cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
  • session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
    考虑到减轻服务器性能方面,应当使用COOKIE。
  • 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
  • 将登陆信息等重要信息存放为SESSION
    其他信息如果需要保留,可以放在COOKIE中

session 分布式处理

Session 复制

在支持 Session 复制的 Web 服务器上,通过修改 Web 服务器的配置,可以实现将 Session 同步到其它 Web 服务器上,达到每个 Web 服务器上都保存一致的 Session。

优点:代码上不需要做支持和修改。
缺点:需要依赖支持的 Web 服务器,一旦更换成不支持的 Web 服务器就不能使用了,在数据量很大的情况下不仅占用网络资源,而且会导致延迟。
适用场景:只适用于Web服务器比较少且 Session 数据量少的情况。
可用方案:开源方案 tomcat-redis-session-manager,暂不支持 Tomcat8。

Session 粘滞

将用户的每次请求都通过某种方法强制分发到某一个 Web 服务器上,只要这个 Web 服务器上存储了对应 Session 数据,就可以实现会话跟踪。

优点:使用简单,没有额外开销。
缺点:一旦某个 Web 服务器重启或宕机,相对应的 Session 数据将会丢失,而且需要依赖负载均衡机制。
适用场景:对稳定性要求不是很高的业务情景。

Session 集中管理

在单独的服务器或服务器集群上使用缓存技术,如 Redis 存储 Session 数据,集中管理所有的 Session,所有的Web服务器都从这个存储介质中存取对应的 Session,实现 Session 共享。

优点:可靠性高,减少 Web 服务器的资源开销。
缺点:实现上有些复杂,配置较多。
适用场景:Web服务器较多、要求高可用性的情况。
可用方案:开源方案 Spring Session,也可以自己实现,主要是重写 HttpServletRequestWrapper 中的 getSession 方法。

基于 Cookie 管理

这种方式每次发起请求的时候都需要将 Session 数据放到 Cookie 中传递给服务端。

优点:不需要依赖额外外部存储,不需要额外配置。
缺点:不安全,易被盗取或篡改;Cookie 数量和长度有限制,需要消耗更多网络带宽。
适用场景:数据不重要、不敏感且数据量小的情况。
这四种方式,相对来说,Session 集中管理 更加可靠,使用也是最多的。

JDBC 流程

向 DriverManager 类注册驱动数据库驱动程序
调用 DriverManager.getConnection 方法, 通过 JDBC URL,用户名,密码取得数据库连接的 Connection 对象。
获取 Connection 后, 便可以通过 createStatement 创建 Statement 用以执行 SQL 语句。
有时候会得到查询结果,比如 select,得到查询结果,查询(SELECT)的结果存放于结果集(ResultSet)中。
关闭数据库语句,关闭数据库连接。

MVC 设计思想

MVC 是三个单词的首字母缩写,它们是 Model(模型)、View(视图)和 Controller(控制)。
这个模式认为,程序不论简单或复杂,从结构上看,都可以分成三层:
最上面的一层,是直接面向最终用户的”视图层”(View)。它是提供给用户的操作界面,是程序的外壳。
最底下的一层,是核心的”数据层”(Model),也就是程序需要操作的数据或信息。
中间的一层,就是”控制层”(Controller),它负责根据用户从”视图层”输入的指令,选取”数据层”中的数据,然后对其进行相应的操作,产生最终结果。

equals 与 == 的区别

== 与equals 的主要区别是:== 常用于比较原生类型,而 equals() 方法用于检查对象的相等性。
另一个不同的点是:如果 == 和 equals() 用于比较对象,当两个引用地址相同,== 返回 true。而 equals() 可以返回 true 或者 false 主要取决于重写实现。最常见的一个例子,字符串的比较,不同情况 == 和 equals() 返回不同的结果。

java 集合类

在这里插入图片描述

List 和 Set 区别

  1. List, Set 都是继承自 Collection 接口
  2. List 特点:元素有放入顺序,元素可重复。Set 特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在 set 中的位置是有该元素的 HashCode 决定的,其位置其实是固定的)
  3. List 接口有三个实现类:LinkedList,ArrayList,Vector。Set 接口有两个实现类:HashSet(底层由 HashMap 实现),LinkedHashSet

List 和 Map 区别

List 特点:元素有放入顺序,元素可重复;
Map 特点:元素按键值对存储,无放入顺序 ;
List 接口有三个实现类:LinkedList,ArrayList,Vector;
LinkedList:底层基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;
Map 接口有三个实现类:HashMap,HashTable,LinkedHashMap
Map 相当于和 Collection 一个级别的;Map 集合存储键值对,且要求保持键的唯一性;

ArrayList 与 LinkedList 区别

因为 Array 是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。Array 获取数据的时间复杂度是 O(1),但是要删除数据却是开销很大的,因为这需要重排数组中的所有数据。
相对于 ArrayList,LinkedList 插入是更快的。因为 LinkedList 不像 ArrayList 一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组,这是 ArrayList 最坏的一种情况,时间复杂度是 O(n),而 LinkedList 中插入或删除的时间复杂度仅为 O(1)。ArrayList 在插入数据时还需要更新索引(除了插入数组的尾部)。
类似于插入数据,删除数据时,LinkedList 也优于 ArrayList。
LinkedList 需要更多的内存,因为 ArrayList 的每个索引的位置是实际的数据,而 LinkedList 中的每个节点中存储的是实际的数据和前后节点的位置。
你的应用不会随机访问数据。因为如果你需要 LinkedList 中的第 n 个元素的时候,你需要从第一个元素顺序数到第 n 个数据,然后读取数据。
你的应用更多的插入和删除元素,更少的读取数据。因为插入和删除元素不涉及重排数据,所以它要比 ArrayList 要快。

ArrayList 与 Vector 区别

同步性:Vector 是线程安全的,也就是说是同步的 ,而 ArrayList 是线程不安全的,不是同步的。
数据增长:当需要增长时,Vector 默认增长为原来一倍 ,而 ArrayList 却是原来的 50% ,这样 ArrayList 就有利于节约内存空间。
说明:如果涉及到堆栈,队列等操作,应该考虑用 Vector,如果需要快速随机访问元素,应该使用 ArrayList

HashMap 和 HashTable 的区别

HashMap 几乎可以等价于 HashTable,除了 HashMap 是非 synchronized 的,并可以接受 null(HashMap 可以接受为 null 的键值 (key) 和值 (value),而 HashTable 则不行)。
HashMap 是非 synchronized,而 HashTable 是 synchronized,这意味着 HashTable 是线程安全的,多个线程可以共享一个 HashTable;而如果没有正确的同步的话,多个线程是不能共享 HashMap 的。Java 5 提供了 ConcurrentHashMap,它是 HashTable 的替代,比 HashTable 的扩展性更好。
另一个区别是 HashMap 的迭代器 (Iterator) 是 fail-fast 迭代器,而 HashTable 的 enumerator 迭代器不是 fail-fast 的。所以当有其它线程改变了 HashMap 的结构(增加或者移除元素),将会抛出 ConcurrentModificationException,但迭代器本身的 remove() 方法移除元素则不会抛出 ConcurrentModificationException 异常。但这并不是一个一定发生的行为,要看 JVM。这条同样也是 Enumeration 和 Iterator 的区别。
由于 HashTable 是线程安全的也是 synchronized,所以在单线程环境下它比 HashMap 要慢。如果你不需要同步,只需要单一线程,那么使用 HashMap 性能要好过 HashTable。
HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的。

HashSet 和 HashMap 区别

HashMap HashSet
HashMap 实现了 Map 接口 HashSet 实现了 Set 接口
HashMap 储存键值对 HashSet 仅仅存储对象
使用 put() 方法将元素放入 map 中 使用 add() 方法将元素放入 set 中
HashMap 中使用键对象来计算 hashcode 值 HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以 equals() 方法用来判断对象的相等性,如果两个对象不同的话,那么返回 false
HashMap 比较快,因为是使用唯一的键来获取对象 HashSet 较 HashMap 来说比较慢

HashMap 和 ConcurrentHashMap 的区别

放入 HashMap 的元素是 key-value 对。
底层说白了就是散列结构。
要将元素放入到 HashMap 中,那么 key 的类型必须要实现实现 hashcode 方法,默认这个方法是根据对象的地址来计算的,接着还必须覆盖对象的 equals() 方法。
ConcurrentHashMap 对整个桶数组进行了分段,而 HashMap 则没有
ConcurrentHashMap 在每一个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好,而 HashMap 没有锁机制,不是线程安全的

HashMap 的工作原理及代码实现

HashMap 基于 hashing 原理,我们通过 put() 和 get() 方法储存和获取对象。当我们将键值对传递给 put() 方法时,它调用键对象的 hashCode() 方法来计算 hashcode,让后找到 bucket 位置来储存值对象。当获取对象时,通过键对象的 equals() 方法找到正确的键值对,然后返回值对象。HashMap 使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap 在每个链表节点中储存键值对对象。

ConcurrentHashMap 的工作原理及代码实现

ConcurrentHashMap 采用了非常精妙的”分段锁”策略,ConcurrentHashMap 的主干是个 Segment 数组。Segment 继承了 ReentrantLock,所以它就是一种可重入锁(ReentrantLock)。在 ConcurrentHashMap,一个 Segment 就是一个子哈希表,Segment 里维护了一个 HashEntry 数组,并发环境下,对于不同 Segment 的数据进行操作是不用考虑锁竞争的。

String,StringBuilder StringBuffer

最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下,便于大家观看,也便于加深自己学习过程中对这些知识点的记忆,如果哪里有误,恳请指正。

这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。

首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String
  String最慢的原因:

String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。以下面一段代码为例:

1 String str=“abc”;
2 System.out.println(str);
3 str=str+“de”;
4 System.out.println(str);

如果运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

另外,有时候我们会这样对字符串进行赋值

1 String str=“abc”+“de”;
2 StringBuilder stringBuilder=new StringBuilder().append(“abc”).append(“de”);
3 System.out.println(str);
4 System.out.println(stringBuilder.toString());
  这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和

String str=“abcde”;

是完全一样的,所以会很快,而如果写成下面这种形式

1 String str1=“abc”;
2 String str2=“de”;
3 String str=str1+str2;
  那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。

2. 再来说线程安全

在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的

如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

3. 总结一下
  String:适用于少量的字符串操作的情况

StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值