Bootstrap

2022年面试,整理全网初、中、高级常见 Java 面试题

面试题答案见微信小程序 “Java 精选面试题”,3000 + 道面试题。内容持续更新中包含基础、集合、并发、JVM、Spring、Spring MVC、Spring Boot、Spring Cloud、Dubbo、MySQL、Redis、MyBaits、Zookeeper、Linux、数据结构与算法、项目管理工具、消息队列、设计模式、Nginx、常见 BUG 问题、网络编程等。

————————————————

面向对象编程有哪些特征?

一、抽象和封装

类和对象体现了抽象和封装

抽象就是解释类与对象之间关系的词。类与对象之间的关系就是抽象的关系。一句话来说明:类是对象的抽象,而对象则是类得特例,即类的具体表现形式。

封装两个方面的含义:一是将有关数据和操作代码封装在对象当中,形成一个基本单位,各个对象之间相对独立互不干扰。二是将对象中某些属性和操作私有化,已达到数据和操作信息隐蔽,有利于数据安全,防止无关人员修改。把一部分或全部属性和部分功能(函数)对外界屏蔽,就是从外界(类的大括号之外)看不到,不可知,这就是封装的意义。

二、继承

面向对象的继承是为了软件重用,简单理解就是代码复用,把重复使用的代码精简掉的一种手段。如何精简,当一个类中已经有了相应的属性和操作的代码,而另一个类当中也需要写重复的代码,那么就用继承方法,把前面的类当成父类,后面的类当成子类,子类继承父类,理所当然。就用一个关键字 extends 就完成了代码的复用。

三、多态

没有继承就没有多态,继承是多态的前提。虽然继承自同一父类,但是相应的操作却各不相同,这叫多态。由继承而产生的不同的派生类,其对象对同一消息会做出不同的响应。 JDK、JRE、JVM 之间有什么关系?

1、JDK

JDK (Java development Toolkit),JDK 是整个 Java 的核心,包括了 Java 的运行环境(Java Runtime Environment),一堆的 Java 工具(Javac,java,jdb 等)和 Java 基础的类库(即 Java API 包括 rt.jar).

Java API 是 Java 的应用程序的接口,里面有很多写好的 Java class,包括一些重要的结构语言以及基本图形,网络和文件 I/O 等等。

2、JRE

JRE(Java Runtime Environment),Java 运行环境。在 Java 平台下,所有的 Java 程序都需要在 JRE 下才能运行。只有 JVM 还不能进行 class 的执行,因为解释 class 的时候,JVM 需调用解释所需要的类库 lib。JRE 里面有两个文件夹 bin 和 lib,这里可以认为 bin 就是 JVM,lib 就是 JVM 所需要的类库,而 JVM 和 lib 合起来就称 JRE。

JRE 包括 JVM 和 JAVA 核心类库与支持文件。与 JDK 不同,它不包含开发工具 ----- 编译器,调试器,和其他工具。

3、JVM

JVM:Java Virtual Machine(Java 虚拟机)JVM 是 JRE 的一部分,它是虚拟出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM 有自己完善的硬件构架,入处理器,堆栈,寄存器等,还有相应的指令系统。

JVM 是 Java 实现跨平台最核心的部分,所有的 Java 程序会首先被编译为 class 的类文件,JVM 的主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 的指令集或 OS 的系统调用。Java 面对不同操作系统使用不同的虚拟机,一次实现了跨平台。JVM 对上层的 Java 源文件是不关心的,它关心的只是由源文件生成的类文件 如何使用命令行编译和运行 Java 文件?

编译和运行 Java 文件,需了解两个命令:

1)javac 命令:编译 java 文件;使用方法: javac Hello.java ,如果不出错的话,在与 Hello.java 同一目录下会生成一个 Hello.class 文件,这个 class 文件是操作系统能够使用和运行的文件。

2)java 命令: 作用:运行.class 文件;使用方法:java Hello, 如果不出错的话,会执行 Hello.class 文件。注意:这里的 Hello 后面不需要扩展名。

新建文件,编写代码如下:

public class Hello{


public static void main(String[] args){



System.out.println("Hello world,欢迎关注微信公众号“Java精选”!");


}
}

文件命名为 Hello.java,注意后缀为 “java”。

打开 cmd,切换至当前文件所在位置,执行 javac Hello.java,该文件夹下面生成了一个 Hello.class 文件

输入 java Hello 命令,cmd 控制台打印出代码的内容 Hello world,欢迎关注微信公众号 “Java 精选”! 说说常用的集合有哪些?

Map 接口和 Collection 接口是所有集合框架的父接口

Collection 接口的子接口包括:Set 接口和 List 接口。

Set 中不能包含重复的元素。List 是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。

Map 接口的实现类主要有:HashMap、Hashtable、ConcurrentHashMap 以及 TreeMap 等。Map 不能包含重复的 key,但是可以包含相同的 value。根据键得到值,对 map 集合遍历时先得到键的 set 集合,对 set 集合进行遍历,得到相应的值。

Set 接口的实现类主要有:HashSet、TreeSet、LinkedHashSet 等

List 接口的实现类主要有:ArrayList、LinkedList、Stack 以及 Vector 等

Iterator,所有的集合类,都实现了 Iterator 接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:

hasNext () 是否还有下一个元素

next () 返回下一个元素

remove () 删除当前元素 进程与线程之间有什么区别?

进程是系统中正在运行的一个程序,程序一旦运行就是进程。

进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。

一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。线程与进程的一个主要区别是,统一进程内的一个主要区别是,同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存(一个进程无法直接访问另一进程的内存)。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写这些栈内存。

线程是进程的一个实体,是进程的一条执行路径。

线程是进程的一个特定执行路径。当一个线程修改了进程的资源,它的兄弟线程可以立即看到这种变化。 什么是 JVM?

Java 程序的跨平台特性主要是指字节码文件可以在任何具有 Java 虚拟机的计算机或者电子设备上运行,Java 虚拟机中的 Java 解释器负责将字节码文件解释成为特定的机器码进行运行。

因此在运行时,Java 源程序需要通过编译器编译成为.class 文件。

众所周知 java.exe 是 java class 文件的执行程序,但实际上 java.exe 程序只是一个执行的外壳,它会装载 jvm.dll(windows 下,下皆以 windows 平台为例,linux 下和 solaris 下其实类似,为:libjvm.so),这个动态连接库才是 java 虚拟机的实际操作处理所在。

JVM 是 JRE 的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

JVM 有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java 语言最重要的特点就是跨平台运行。

使用 JVM 就是为了支持与操作系统无关,实现跨平台。所以,JAVA 虚拟机 JVM 是属于 JRE 的,而现在我们安装 JDK 时也附带安装了 JRE (当然也可以单独安装 JRE)。 什么是事务?

事务(transaction)是指数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)。

通俗的说就是事务可以作为一个单元的一组有序的数据库操作。如果组中的所有操作都成功,则认为事务成功,即使只有一个操作失败,事务也不成功。如果所有操作完成,事务则提交,其修改将作用于所有其他数据库进程。如果一个操作失败,则事务将回滚,该事务所有操作的影响都将取消。 MySQL 事务都有哪些特性?

事务的四大特性:

1 、原子性

事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做

2 、一致性

事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。

3 、隔离性

一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

4 、持续性

也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。 MyBatis 是什么框架?

MyBatis 框架是一个优秀的数据持久层框架,在实体类和 SQL 语句之间建立映射关系,是一种半自动化的 ORM 实现。其封装性要低于 Hibernate,性能优秀,并且小巧。

ORM 即对象 / 关系数据映射,也可以理解为一种数据持久化技术。

MyBatis 的基本要素包括核心对象、核心配置文件、SQL 映射文件。

数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。 什么是 Redis?

redis 是一个高性能的 key-value 数据库,它是完全开源免费的,而且 redis 是一个 NOSQL 类型数据库,是为了解决高并发、高扩展,大数据存储等一系列的问题而产生的数据库解决方案,是一个非关系型的数据库。但是,它也是不能替代关系型数据库,只能作为特定环境下的扩充。

redis 是一个以 key-value 存储的数据库结构型服务器,它支持的数据结构类型包括:字符串(String)、链表(lists)、哈希表(hash)、集合(set)、有序集合(Zset)等。为了保证读取的效率,redis 把数据对象都存储在内存当中,它可以支持周期性的把更新的数据写入磁盘文件中。而且它还提供了交集和并集,以及一些不同方式排序的操作。 什么是 Spring 框架?

Spring 中文翻译过来是春天的意思,被称为 J2EE 的春天,是一个开源的轻量级的 Java 开发框架, 具有控制反转(IoC)和面向切面(AOP)两大核心。Java Spring 框架通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

Spring 框架不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。

1)IOC 控制反转

对象创建责任的反转,在 Spring 中 BeanFacotory 是 IOC 容器的核心接口,负责实例化,定位,配置应用程序中的对象及建立这些对象间的依赖。XmlBeanFacotory 实现 BeanFactory 接口,通过获取 xml 配置文件数据,组成应用对象及对象间的依赖关系。

Spring 中有 3 中注入方式,一种是 set 注入,另一种是接口注入,另一种是构造方法注入。

2)AOP 面向切面编程

AOP 是指纵向的编程,比如两个业务,业务 1 和业务 2 都需要一个共同的操作,与其往每个业务中都添加同样的代码,通过写一遍代码,让两个业务共同使用这段代码。

Spring 中面向切面编程的实现有两种方式,一种是动态代理,一种是 CGLIB,动态代理必须要提供接口,而 CGLIB 实现是由 = 有继承。 什么是 Spring MVC 框架?

Spring MVC 属于 Spring FrameWork 的后续产品,已经融合在 Spring Web Flow 中。

Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。

使用 Spring 可插入 MVC 架构,从而在使用 Spring 进行 WEB 开发时,可以选择使用 Spring 中的 Spring MVC 框架或集成其他 MVC 开发框架,如 Struts1(已基本淘汰),Struts2(老项目还在使用或已重构)等。

通过策略接口,Spring 框架是高度可配置的且包含多种视图技术,如 JavaServer Pages(JSP)技术、Velocity、Tiles、iText 和 POI 等。

Spring MVC 框架并不清楚或限制使用哪种视图,所以不会强迫开发者只使用 JSP 技术。

Spring MVC 分离了控制器、模型对象、过滤器以及处理程序对象的角色,这种分离让它们更容易进行定制。 什么是 Spring Boot 框架?

Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。

Spring Boot 框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

2014 年 4 月发布第一个版本的全新开源的 Spring Boot 轻量级框架。它基于 Spring4.0 设计,不仅继承了 Spring 框架原有的优秀特性,而且还通过简化配置来进一步简化了 Spring 应用的整个搭建和开发过程。

另外 Spring Boot 通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。 什么是 Spring Cloud 框架?

Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。

Spring Cloud 并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

Spring Cloud 的子项目,大致可分成两大类:

一类是对现有成熟框架 “Spring Boot 化” 的封装和抽象,也是数量最多的项目;

第二类是开发一部分分布式系统的基础设施的实现,如 Spring Cloud Stream 扮演的是 kafka, ActiveMQ 这样的角色。

对于快速实践微服务的开发者来说,第一类子项目已经基本足够使用,如:

1)Spring Cloud Netflix 是对 Netflix 开发的一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST 客户端、请求路由等;

2)Spring Cloud Config 将配置信息中央化保存,配置 Spring Cloud Bus 可以实现动态修改配置文件;

3)Spring Cloud Bus 分布式消息队列,是对 Kafka, MQ 的封装;

4)Spring Cloud Security 对 Spring Security 的封装,并能配合 Netflix 使用;

5)Spring Cloud Zookeeper 对 Zookeeper 的封装,使之能配置其它 Spring Cloud 的子项目使用;

6)Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件中的一部分,它基于 Netflix Eureka 做了二次封装,主要负责完成微服务架构中的服务治理功能。注意的是从 2.x 起,官方不会继续开源,若需要使用 2.x,风险还是有的。但是我觉得问题并不大,eureka 目前的功能已经非常稳定,就算不升级,服务注册 / 发现这些功能已经够用。consul 是个不错的替代品,还有其他替代组件,后续篇幅会有详细赘述或关注微信公众号 “Java 精选”,有详细替代方案源码分享。 Spring Cloud 框架有哪些优缺点?

Spring Cloud 优点:

1)服务拆分粒度更细,有利于资源重复利用,有利于提高开发效率,每个模块可以独立开发和部署、代码耦合度低;

2)可以更精准的制定优化服务方案,提高系统的可维护性,每个服务可以单独进行部署,升级某个模块的时候只需要单独部署对应的模块服务即可,效率更高;

3)微服务架构采用去中心化思想,服务之间采用 Restful 等轻量级通讯,比 ESB 更轻量,模块专一性提升,每个模块只需要关心自己模块所负责的功能即可,不需要关心其他模块业务,专一性更高,更便于功能模块开发和拓展;

4)技术选型不再单一,由于每个模块是单独开发并且部署,所以每个模块可以有更多的技术选型方案,如模块 1 数据库选择 mysql,模块 2 选择用 oracle 也是可以的;

5)适于互联网时代,产品迭代周期更短。系统稳定性以及性能提升,由于微服务是几个服务共同组成的项目或者流程,因此相比传统单一项目的优点就在于某个模块提供的服务宕机过后不至于整个系统瘫痪掉,而且微服务里面的容灾和服务降级机制也能大大提高项目的稳定性;从性能而言,由于每个服务是单独部署,所以每个模块都可以有自己的一套运行环境,当某个服务性能低下的时候可以对单个服务进行配置或者代码上的升级,从而达到提升性能的目的。

Spring Cloud 缺点:

1)微服务过多,治理成本高,不利于维护系统,服务之间接口调用成本增加,相比以往单项目的时候调用某个方法或者接口可以直接通过本地方法调用就能够完成,但是当切换成微服务的时候,调用方式就不能用以前的方式进行调试、目前主流采用的技术有 http api 接口调用、RPC、WebService 等方式进行调用,调用成本比单个项目的时候有所增加;

2)分布式系统开发的成本高(容错,分布式事务等)对团队挑战大

2)独立的数据库,微服务产生事务一致性的问题,由于各个模块用的技术都各不相同、而且每个服务都会高并发进行调用,就会存在分布式事务一致性的问题;

3)分布式部署,造成运营的成本增加、相比较单个应用的时候,运营人员只需要对单个项目进行部署、负载均衡等操作,但是微服务的每个模块都需要这样的操作,增加了运行时的成本;

4)由于整个系统是通过各个模块组合而成的,因此当某个服务进行变更时需要对前后涉及的所有功能进行回归测试,测试功能不能仅限于当个模块,增加了测试难度和测试成本;

总体来说优点大过于缺点,目前看来 Spring Cloud 是一套非常完善的微服务框架,目前很多企业开始用微服务,Spring Cloud 的优势是显而易见的。 什么是消息队列?

MQ 全称为 Message Queue 消息队列(MQ)是一种应用程序对应用程序的通信方法。

消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。

MQ 是消费生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取队列中的消息。

消息生产者只需要把消息发布到 MQ 中而不用管谁来获取,消息消费者只管从 MQ 中获取消息而不管是谁发布的消息,这样生产者和消费者双方都不用清楚对方的存在。 

目前在生产环境,使用较多的消息队列有 ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ 等。 消息队列有哪些应用场景?

列举消息队列场景,异步处理,应用解耦,流量削锋,日志处理和消息通讯五个场景。

1、异步处理场景

用户注册后,需要发注册邮件和注册短信。传统的做法有两种

1)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。

2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。

按照以上描述,用户的响应时间相当于是注册信息写入数据库的时间,也就是 50 毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是 50 毫秒。因此采用消息队列的方式,系统的吞吐量提高到每秒 20 QPS。比串行提高了 3 倍,比并行提高了两倍。

2、应用解耦场景

用户购买物品下单后,订单系统需要通知库存系统。传统的做法是订单系统调用库存系统的接口。该模式的缺点:

1)假如库存系统无法访问,则订单减库存将失败,从而导致订单失败;

2)订单系统与库存系统耦合;

如何解决以上问题呢?

订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。

库存系统:订阅下单的消息,采用拉 / 推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。

假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后订单系统写入消息队列就不再关心其他的后续操作,从而实现订单系统与库存系统的应用解耦。

3、流量削锋场景

流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中被广泛应用。

秒杀活动,一般会因为流量并发过大,导致流量暴增,应用宕机,从而为解决该问题,一般在应用前端加入消息队列。

控制活动的人数,缓解短时间内高流量压垮应用。当用户的请求,服务器接收后,先写入消息队列。如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面,而其秒杀业务根据消息队列中的请求信息,再做后续处理。

4、日志处理场景

日志处理是指将消息队列用在日志处理中,比如 Kafka 的应用,解决大量日志传输的问题。日志采集客户端,负责日志数据采集,定时写受写入 Kafka 队列,而 Kafka 消息队列,负责日志数据的接收,存储和转发,日志处理应用订阅并消费 kafka 队列中的日志数据。

5、消息通讯

消息通讯是指消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。 什么是 Linux 操作系统?

Linux 全称 GNU/Linux,是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 的多用户、多任务、支持多线程和多 CPU 的操作系统。

伴随着互联网的发展,Linux 得到了来自全世界软件爱好者、组织、公司的支持。它除了在服务器方面保持着强劲的发展势头以外,在个人电脑、嵌入式系统上都有着长足的进步。使用者不仅可以直观地获取该操作系统的实现机制,而且可以根据自身的需要来修改完善 Linux,使其最大化地适应用户的需要。 

Linux 不仅系统性能稳定,而且是开源软件。其核心防火墙组件性能高效、配置简单,保证了系统的安全。

在很多企业网络中,为了追求速度和安全,Linux 不仅仅是被网络运维人员当作服务器使用,甚至当作网络防火墙,这是 Linux 的一大亮点。

Linux 具有开放源码、没有版权、技术社区用户多等特点,开放源码使得用户可以自由裁剪,灵活性高,功能强大,成本低。尤其系统中内嵌网络协议栈,经过适当的配置就可实现路由器的功能。这些特点使得 Linux 成为开发路由交换设备的理想开发平台。 什么是数据结构?

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。

数据结构(data structure)是带有结构特性的数据元素的集合,它研究的是数据的逻辑结构和数据的物理结构以及它们之间的相互关系,并对这种结构定义相适应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。

简而言之,数据结构是相互之间存在一种或多种特定关系的数据元素的集合,即带 “结构” 的数据元素的集合。“结构” 就是指数据元素之间存在的关系,分为逻辑结构和存储结构。

数据的逻辑结构和物理结构是数据结构的两个密切相关的方面,同一逻辑结构可以对应不同的存储结构。算法的设计取决于数据的逻辑结构,而算法的实现依赖于指定的存储结构。

数据结构的研究内容是构造复杂软件系统的基础,它的核心技术是分解与抽象。

通过分解可以划分出数据的 3 个层次;再通过抽象,舍弃数据元素的具体内容,就得到逻辑结构。类似地,通过分解将处理要求划分成各种功能,再通过抽象舍弃实现细节,就得到运算的定义。

上述两个方面的结合可以将问题变换为数据结构。这是一个从具体(即具体问题)到抽象(即数据结构)的过程。

然后,通过增加对实现细节的考虑进一步得到存储结构和实现运算,从而完成设计任务。这是一个从抽象(即数据结构)到具体(即具体实现)的过程。 什么是设计模式?

设计模式(Design pattern) 是解决软件开发某些特定问题而提出的一些解决方案也可以理解成解决问题的一些思路,通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性好。使用设计模式最终的目的是实现代码的高内聚和低耦合。

高内聚低耦合是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低。

目的是使程序模块的可重用性、移植性大大增强。

通常程序结构中各模块的内聚程度越高,模块间的耦合程度就越低。

内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事,它描述的是模块内的功能联系;耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 什么是 Zookeeper?

ZooKeeper 由雅虎研究院开发,ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,后来托管到 Apache,是 Hadoop 和 Hbase 的重要组件。

ZooKeeper 是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务。

ZooKeeper 的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

ZooKeeper 包含一个简单的原语集,提供 Java 和 C 的接口。

ZooKeeper 代码版本中,提供了分布式独享锁、选举、队列的接口,代码在 $zookeeper_home\src\recipes。其中分布锁和队列有 Java 和 C 两个版本,选举只有 Java 版本。

于 2010 年 11 月正式成为 Apache 的顶级项目。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

分布式应用程序可以基于 ZooKeeper 实现数据发布与订阅、负载均衡、命名服务、分布式协调与通知、集群管理、Leader 选举、分布式锁、分布式队列等功能。 应用服务 8080 端口被意外占用如何解决?

1)按键盘 WIN+R 键,打开后在运行框中输入 “CMD” 命令,点击确定。

2)在 CMD 窗口,输入 “netstat -ano” 命令,按回车键,即可查看所有的端口占用情况。

3)找到本地地址一览中类似 “0.0.0.0:8080” 信息,通过此列查看 8080 端口对应的程序 PID。

4)打开任务管理器,详细信息找到对应的应用 PID(若不存在通过设置可以调出来),右键结束任务即可。 什么是 Dubbo 框架?

Dubbo(读音 [ˈdʌbəʊ])是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成。

Dubbo 提供了六大核心能力:面向接口代理的高性能 RPC 调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。

Dubbo 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

核心组件

Remoting: 网络通信框架,实现了 sync-over-async 和 request-response 消息机制;

RPC: 一个远程过程调用的抽象,支持负载均衡、容灾和集群功能;

Registry: 服务目录框架用于服务的注册和服务事件发布和订阅。 什么是 Maven?

Maven 即为项目对象模型(POM),它可以通过一小段描述信息来管理项目的构建,报告和文档的项目管理工具软件。

Maven 除了以程序构建能力为特色之外,还提供高级项目管理工具。

由于 Maven 的缺省构建规则有较高的可重用性,所以常常用两三行 Maven 构建脚本就可以构建简单的项目。

由于 Maven 面向项目的方法,许多 Apache Jakarta 项目发文时使用 Maven,而且公司项目采用 Maven 的比例在持续增长,相比较 Gradle,在之后的篇幅中会说明,欢迎大家关注微信公众号 “Java 精选”。

Maven 这个单词来自于意第绪语(犹太语),意为知识的积累,最初在 Jakata Turbine 项目中用来简化构建过程。

当时有一些项目(有各自 Ant build 文件),仅有细微的差别,而 JAR 文件都由 CVS 来维护。于是希望有一种标准化的方式构建项目,一个清晰的方式定义项目的组成,一个容易的方式发布项目的信息,以及一种简单的方式在多个项目中共享 JARs。 应用层中常见的协议都有哪些?

应用层协议(application layer protocol)定义了运行在不同端系统上的应用程序进程如何相互传递报文。

应用层协议

1)DNS:一种用以将域名转换为 IP 地址的 Internet 服务,域名系统 DNS 是因特网使用的命名系统,用来把便于人们使用的机器名字转换为 IP 地址。

现在顶级域名 TLD 分为三大类:国家顶级域名 nTLD;通用顶级域名 gTLD;基础结构域名。

域名服务器分为四种类型:根域名服务器;顶级域名服务器;本地域名服务器;权限域名服务器。

2)FTP:文件传输协议 FTP 是因特网上使用得最广泛的文件传送协议。FTP 提供交互式的访问,允许客户指明文件类型与格式,并允许文件具有存取权限。

基于客户服务器模式,FTP 协议包括两个组成部分,一是 FTP 服务器,二是 FTP 客户端,提供交互式的访问面向连接,使用 TCP/IP 可靠的运输服务,主要功能:减少 / 消除不同操作系统下文件的不兼容性 。

3)telnet 远程终端协议:telnet 是一个简单的远程终端协议,它也是因特网的正式标准。又称为终端仿真协议。

4)HTTP:超文本传送协议,是面向事务的应用层协议,它是万维网上能够可靠地交换文件的重要基础。http 使用面向连接的 TCP 作为运输层协议,保证了数据的可靠传输。

5)电子邮件协议 SMTP:即简单邮件传送协议。SMTP 规定了在两个相互通信的 SMTP 进程之间应如何交换信息。SMTP 通信的三个阶段:建立连接、邮件传送、连接释放。

6)POP3:邮件读取协议,POP3 (Post Office Protocol 3) 协议通常被用来接收电子邮件。

7)远程登录协议 (Telnet):用于实现远程登录功能。

8)SNMP:简单网络管理协议。由三部分组成:SNMP 本身、管理信息结构 SMI 和管理信息 MIB。SNMP 定义了管理站和代理之间所交换的分组格式。SMI 定义了命名对象类型的通用规则,以及把对象和对象的值进行编码。MIB 在被管理的实体中创建了命名对象,并规定类型。 Java 中的关键字都有哪些?

1)48 个关键字:abstract、assert、boolean、break、byte、case、catch、char、class、continue、default、do、double、else、enum、extends、final、finally、float、for、if、implements、import、int、interface、instanceof、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、void、volatile、while。

2)2 个保留字(目前未使用,以后可能用作为关键字):goto、const。

3)3 个特殊直接量(直接量是指在程序中通过源代码直接给出的值):true、false、null。 Java 中基本类型都有哪些?

Java 的类型分成两种,一种是基本类型,一种是引用类型。其中 Java 基本类型共有八种。

基本类型可以分为三大类:字符类型 char,布尔类型 boolean 以及数值类型 byte、short、int、long、float、double。

数值类型可以分为整数类型 byte、short、int、long 和浮点数类型 float、double。

JAVA 中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或操作系统的改变而改变。实际上《Thinking in Java》一书作者,提到 Java 中还存在另外一种基本类型 void,它也有对应的包装类 java.lang.Void,因为 Void 是不能 new,也就是不能在堆里面分配空间存对应的值,所以将 Void 归成基本类型,也有一定的道理。

8 种基本类型表示范围如下:

byte:8 位,最大存储数据量是 255,存放的数据范围是 - 128~127 之间。

short:16 位,最大数据存储量是 65536,数据范围是 - 32768~32767 之间。

int:32 位,最大数据存储容量是 2 的 32 次方减 1,数据范围是负的 2 的 31 次方到正的 2 的 31 次方减 1。

long:64 位,最大数据存储容量是 2 的 64 次方减 1,数据范围为负的 2 的 63 次方到正的 2 的 63 次方减 1。

float:32 位,数据范围在 3.4e-45~1.4e38,直接赋值时必须在数字后加上 f 或 F。

double:64 位,数据范围在 4.9e-324~1.8e308,赋值时可以加 d 或 D 也可以不加。

boolean:只有 true 和 false 两个取值。

char:16 位,存储 Unicode 码,用单引号赋值。 为什么 Map 接口不继承 Collection 接口?

1)Map 提供的是键值对映射(即 Key 和 value 的映射),而 Collection 提供的是一组数据并不是键值对映射。

2)若果 Map 继承了 Collection 接口,那么所实现的 Map 接口的类到底是用 Map 键值对映射数据还是用 Collection 的一组数据呢?比如平常所用的 hashMap、hashTable、treeMap 等都是键值对,所以它继承 Collection 是完全没意义,而且 Map 如果继承 Collection 接口的话,违反了面向对象的接口分离原则。

接口分离原则:客户端不应该依赖它不需要的接口。

另一种定义是类间的依赖关系应该建立在最小的接口上。

接口隔离原则将非常庞大、臃肿的接口拆分成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。

接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署,让客户端依赖的接口尽可能地小。

3)Map 和 List、Set 不同,Map 放的是键值对,List、Set 存放的是一个个的对象。说到底是因为数据结构不同,数据结构不同,操作就不一样,所以接口是分开的,还是接口分离原则。 Collection 和 Collections 有什么区别?

java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection 接口在 Java 类库中有很多具体的实现。

Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式。其直接继承接口有 List 与 Set。

Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set

java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于 Java 的 Collection 框架。 堆和栈的概念,它们有什么区别和联系?

在说堆和栈之前,我们先说一下 JVM(虚拟机)内存的划分:

Java 程序在运行时都要开辟空间,任何软件在运行时都要在内存中开辟空间,Java 虚拟机运行时也是要开辟空间的。JVM 运行时在内存中开辟一片内存区域,启动时在自己的内存区域中进行更细致的划分,因为虚拟机中每一片内存处理的方式都不同,所以要单独进行管理。

JVM 内存的划分有五片:

1)寄存器;

2)本地方法区;

3)方法区;

4)栈内存;

5)堆内存。

重点来说一下堆和栈:

栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for 循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。

堆内存:存储的是数组和对象(其实数组就是对象),凡是 new 建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java 有垃圾回收机制不定时的收取。

比如主函数里的语句 int [] arr=new int [3]; 在内存中是怎么被定义的:

主函数先进栈,在栈中定义一个变量 arr, 接下来为 arr 赋值,但是右边不是一个具体值,是一个实体。实体创建在堆里,在堆里首先通过 new 关键字开辟一个空间,内存在存储数据的时候都是通过地址来体现的,地址是一块连续的二进制,然后给这个实体分配一个内存地址。数组都是有一个索引,数组这个实体在堆内存中产生之后每一个空间都会进行默认的初始化(这是堆内存的特点,未初始化的数据是不能用的,但在堆里是可以用的,因为初始化过了,但是在栈里没有),不同的类型初始化的值不一样。所以堆和栈里就创建了变量和实体:

那么堆和栈是怎么联系起来的呢?

刚刚说过给堆分配了一个地址,把堆的地址赋给 arr,arr 就通过地址指向了数组。所以 arr 想操纵数组时,就通过地址,而不是直接把实体都赋给它。这种我们不再叫他基本数据类型,而叫引用数据类型。称为 arr 引用了堆内存当中的实体。可以理解为 c 或 “c++” 的指针,Java 成长自 “c++” 和 “c++” 很像,优化了 “c++”

如果当 int [] arr=null;

arr 不做任何指向,null 的作用就是取消引用数据类型的指向。

当一个实体,没有引用数据类型指向的时候,它在堆内存中不会被释放,而被当做一个垃圾,在不定时的时间内自动回收,因为 Java 有一个自动回收机制,(而 “c++” 没有,需要程序员手动回收,如果不回收就越堆越多,直到撑满内存溢出,所以 Java 在内存管理上优于 “c++”)。自动回收机制(程序)自动监测堆里是否有垃圾,如果有,就会自动的做垃圾回收的动作,但是什么时候收不一定。

所以堆与栈的区别很明显:

1)栈内存存储的是局部变量而堆内存存储的是实体;

2)栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;

3)栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。 Class.forName 和 ClassLoader 有什么区别?

在 java 中对类进行加载可以使用 Class.forName () 和 ClassLoader。 ClassLoader 遵循双亲委派模型,最终调用启动类加载器的类加载器,实现的功能是 “通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到 JVM 中。

Class.forName () 方法实际上也是调用的 ClassLoader 来实现的。

通过分析源码可以得出:最后调用的方法是 forName () 方法,方法中的第 2 个参数默认设置为 true,该参数表示是否对加载的类进行初始化,设置为 true 时会对类进行初始化,这就意味着会执行类中的静态代码块以及对静态变量的赋值等操作。

也可以自行调用 Class.forName (String name, boolean initialize,ClassLoader loader) 方法手动选择在加载类的时候是否要对类进行初始化。

JDK 源码中对参数 initialize 的描述是:if { @code true} the class will be initialized,大概意思是说:当值为 true,则加载的类将会被初始化。 为什么要使用设计模式?

1)设计模式是前人根据经验总结出来的,使用设计模式,就相当于是站在了前人的肩膀上。

2)设计模式使程序易读。熟悉设计模式的人应该能够很容易读懂运用设计模式编写的程序。

3)设计模式能使编写的程序具有良好的可扩展性,满足系统设计的开闭原则。比如策略模式,就是将不同的算法封装在子类中,在需要添加新的算法时,只需添加新的子类,实现规定的接口,即可在不改变现有系统源码的情况下加入新的系统行为。

4)设计模式能降低系统中类与类之间的耦合度。比如工厂模式,使依赖类只需知道被依赖类所实现的接口或继承的抽象类,使依赖类与被依赖类之间的耦合度降低。

5)设计模式能提高代码的重用度。比如适配器模式,就能将系统中已经存在的符合新需求的功能代码兼容新的需求提出的接口 。

6)设计模式能为常见的一些问题提供现成的解决方案。

7)设计模式增加了重用代码的方式。比如装饰器模式,在不使用继承的前提下重用系统中已存在的代码。 为什么 String 类型是被 final 修饰的?

1、为了实现字符串池

final 修饰符的作用:final 可以修饰类,方法和变量,并且被修饰的类或方法,被 final 修饰的类不能被继承,即它不能拥有自己的子类,被 final 修饰的方法不能被重写, final 修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。

String 为什么要被 final 修饰主要是为了” 安全性 “和” 效率 “的原因。

final 修饰的 String 类型,代表了 String 不可被继承,final 修饰的 char [] 代表了被存储的数据不可更改性。虽然 final 修饰的不可变,但仅仅是引用地址不可变,并不代表了数组本身不会改变。

为什么保证 String 不可变呢?

因为只有字符串是不可变,字符串池才有可能实现。字符串池的实现可以在运行时节约很多 heap 空间,不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么 String interning 将不能实现,反之变量改变它的值,那么其它指向这个值的变量值也会随之改变。

如果字符串是可变,会引起很严重的安全问题。如数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接或在 socket 编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则改变字符串指向的对象值,将造成安全漏洞。

2、为了线程安全

因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

3、为了实现 String 可创建 HashCode 不可变性

因为字符串是不可变的,所以在它创建的时候 HashCode 就被缓存了,不需要重新计算。使得字符串很适合作为 Map 键值对中的键,字符串的处理速度要快过其它的键对象。这就是 HashMap 中的键往往都使用字符串。 ​final 关键字的基本用法?

在 Java 中 final 关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面从这三个方面来了解一下 final 关键字的基本用法。

1、修饰类

当用 final 修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用 final 进行修饰。final 类中的成员变量可以根据需要设为 final,但是要注意 final 类中的所有成员方法都会被隐式地指定为 final 方法。

在使用 final 修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为 final 类。

2、修饰方法

下面这段话摘自《Java 编程思想》第四版第 143 页:

“使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的 Java 版本中,不需要使用 final 方法进行这些优化了。“

因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为 final 的。即父类的 final 方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。

final 修饰的方法表示此方法已经是 “最后的、最终的” 含义,亦即此方法不能被重写(可以重载多个 final 修饰的方法)。此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中 final 修饰的方法同时访问控制权限为 private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与 final 的矛盾,而是在子类中重新定义了新的方法。(注:类的 private 方法会隐式地被指定为 final 方法。)

3、修饰变量

final 成员变量表示常量,只能被赋值一次,赋值后值不再改变。 当 final 修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果 final 修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final 要求值,即地址的值不发生变化。

final 修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

当函数的参数类型声明为 final 时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。 如何理解 final 关键字?

1)类的 final 变量和普通变量有什么区别?

当用 final 作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且 final 变量一旦被初始化赋值之后,就不能再被赋值了。

2)被 final 修饰的引用变量指向的对象内容可变吗?

引用变量被 final 修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的

3)final 参数的问题

在实际应用中,我们除了可以用 final 修饰成员变量、成员方法、类,还可以修饰参数、若某个参数被 final 修饰了,则代表了该参数是不可改变的。如果在方法中我们修改了该参数,则编译器会提示你࿱

;