Java开发面试题汇总 -- 精选版(附答案)

最近事情太多,没太时间写公众号。今天抽空再整理整理面试中的那点事吧,帮助那些正在找工作或想跳槽找工作的兄弟姐妹们。

前面我己写过多篇推文,相信关注此公众号的伙伴们已经了解掌握了不少。从目前流行的开发技术、常见的面试问题以及问题的答案都已经写的特别清楚了,今天我在之前的基础上,再基于面个人的经验继续精选一些面试题给大家阅读参考。

 

1,Java的反射

Java 反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java 的反射机制。反射也就是动态加载对象,并对对象进行剖析。Class 类与java.lang.reflect 类库一起对反射的概念进行了支持,该类库包含了Field,Method,Constructor类(每个类都实现了Member 接口)。

反射的作用:

1),在运行时判断任意一个对象所属的类

2),在运行时构造任意一个类的对象

3),在运行时判断任意一个类所具有的成员变量和方法

4),在运行时调用任意一个对象的方法

 

优点:可以动态的创建对象和编译,最大限度发挥了java的灵活性。

缺点:对性能有影响。使用反射基本上一种解释操作,告诉JVM我们要做什么并且满足我们的要求,这类操作总是慢于直接执行java代码。

如何使用java的反射? 

a. 通过一个全限类名创建一个对象

1) Class.forName("全限类名"); 

2) 类名.class; 获取Class<?> clazz 对象  

3) 对象.getClass();   

b. 获取构造器对象,通过构造器new出一个对象 

1) Clazz.getConstructor([String.class]); 

2) Con.newInstance([参数]); 

c. 通过class对象创建一个实例对象(就相当与new类名()无参构造器) 

1) Clazz.newInstance();

d. 通过class对象获得一个属性对象

1) Field c=clazz.getFields(); 获得某个类的所有的公共(public)的字段,包括父类中的字段。

2) Field c=clazz.getDeclaredFields(); 获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。

e、通过class对象获得一个方法对象 

1)Clazz.getMethod("方法名",class…..parameaType);(只能获取公共的) 

2)Clazz.getDeclareMethod("方法名"); (获取任意修饰方法,不能执行私有) 

3) M.setAccessible(true);(让私有的方法可以执行) 

f. 让方法执行 

Method.invoke(obj实例对象,obj可变参数);

2,Spring的源码分析及实现

Spring是一个开源的、轻量级的Java 开发框架。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。Spring的核心是控制反转(IOC)和面向切面(AOP)。

什么是IOC?

控制反转(IOC)就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转给容器来帮忙实现。那么必然的我们需要创建一个容器,同时需要一种描述来让容器知道需要创建的对象与对象的关系。这个描述最具体表现就是我们可配置的文件。IOC容器最主要是完成了对象的创建和依赖的管理注入等。

Spring IOC体系结构:

Spring Bean的创建是典型的工厂模式,这一系列的Bean工厂,也即IOC容器为开发者管理对象间的依赖关系提供了很多便利和基础服务,在Spring中有许多的IOC容器的实现供用户选择和使用,其相互关系如下:

BeanFactory作为最顶层的一个接口类,它定义了IOC容器的基本功能规范,BeanFactory 有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory。IOC容器接口BeanFactory:

public interface BeanFactory {    //对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象,    //如果需要得到工厂本身,需要转义     String FACTORY_BEAN_PREFIX = "&";    //根据bean的名字,获取在IOC容器中得到bean实例     Object getBean(String name) throws BeansException;    //根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。    Object getBean(String name, Class requiredType) throws BeansException;    //提供对bean的检索,看看是否在IOC容器有这个名字的bean    boolean containsBean(String name);    //根据bean名字得到bean实例,并同时判断这个bean是不是单例     boolean isSingleton(String name) throws NoSuchBeanDefinitionException;    //得到bean实例的Class类型     Class getType(String name) throws NoSuchBeanDefinitionException;    //得到bean的别名,如果根据别名检索,那么其原名也会被检索出来        String[] getAliases(String name); }

在BeanFactory里只对IOC容器的基本行为作了定义,根本不关心你的bean是如何定义怎样加载的。正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心。

而要知道工厂如何产生对象,就需要看具体的IOC容器实现,spring提供了许多IOC容器的实现。如XmlBeanFactory,ClasspathXmlApplicationContext等。其中XmlBeanFactory就是针对最基本的ioc容器的实现,这个IOC容器可以读取XML文件定义的BeanDefinition(XML文件中对bean的描述),如果说XmlBeanFactory是容器中的屌丝,ApplicationContext应该算容器中的高富帅了。

ApplicationContext是Spring提供的一个高级的IoC容器,它除了能够提供IoC容器的基本功能外,还为用户提供了以下的附加服务。从ApplicationContext接口的实现,我们看出其特点:

1, 支持信息源,可以实现国际化。(实现MessageSource接口);

2, 访问资源。(实现ResourcePatternResolver接口,这个后面要讲);

3, 支持应用事件。(实现ApplicationEventPublisher接口);

Spring AOP

AOP即面向切面编程。可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。AOP技术利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

Spring对AOP的支持

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

   1)、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了;

   2)、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB;

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

    1)、定义普通业务组件

    2)、定义切入点,一个切入点可能横切多个业务组件

    3)、定义增强处理,增强处理就是AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

下面给出一个Spring AOP的.xml文件模板,名字叫做aop.xml,之后的内容都在aop.xml上进行扩展:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:aop="http://www.springframework.org/schema/aop"    xmlns:tx="http://www.springframework.org/schema/tx"    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"></beans>

 

3,动态代理(cglib 与 JDK)

JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

   1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;

   2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP;

   3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换;

如何强制使用CGLIB实现AOP?

    1) 添加CGLIB库,SPRING_HOME/cglib/*.jar

  2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

JDK动态代理和CGLIB字节码生成的区别?

    1)  JDK动态代理只能对实现了接口的类生成代理,而不能针对类;

    2)  CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final;

4,分布式锁的实现方案

分布式锁一般有三种实现方式:

1. 数据库锁;

2. 基于Redis的分布式锁;

3. 基于ZooKeeper的分布式锁。

基于数据库锁实现

方案1:

CREATE TABLE `methodLock` (  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',  `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',  `desc` varchar(1024) NOT NULL DEFAULT '备注信息',  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',  PRIMARY KEY (`id`),  UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

当我们想要锁住某个方法时,执行以下SQL:

insert into methodLock(method_name,desc) values (`method_name`,`desc`);

:想要释放锁的话,需要执行以下Sql:

delete from methodLock where method_name ='method_name';

 

方案2:

CREATE TABLE `method_lock` (  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',  `method_name` varchar(64) NOT NULL COMMENT '锁定的方法名',  `state` tinyint NOT NULL COMMENT '1:未分配;2:已分配',  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  `version` int NOT NULL COMMENT '版本号',  `PRIMARY KEY (`id`),  UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

获取锁:

 select id, method_name, state,version from method_lock where state=1 and method_name='methodName';

占有锁:

update t_resoure set state=2, version=2, update_time=now() where method_name='methodName' and state=1 and version=2;

缺点:

       1、这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。

       2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。

       3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。

       4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。

解决方案:

        1、数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。

        2、没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。

        3、非阻塞的?搞一个while循环,直到insert成功再返回成功。

        4、非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。

基于redis的分布式锁

实现方案:

 

try{  lock = redisTemplate.opsForValue().setIfAbsent(lockKey, LOCK);  logger.info("cancelCouponCode是否获取到锁:"+lock);  if (lock) {    // TODO    redisTemplate.expire(lockKey,1, TimeUnit.MINUTES); //成功设置过期时间    return res;  }else {    logger.info("cancelCouponCode没有获取到锁,不执行任务!");  }}finally{  if(lock){      redisTemplate.delete(lockKey);    logger.info("cancelCouponCode任务结束,释放锁!");      }else{    logger.info("cancelCouponCode没有获取到锁,无需释放锁!");  }}

缺点:

在这种场景(主从结构)中存在明显的竞态:
    客户端A从master获取到锁,
    在master将锁同步到slave之前,master宕掉了。
    slave节点被晋级为master节点,
    客户端B取得了同一个资源被客户端A已经获取到的另外一个锁。安全失效!

基于zookeeper实现

方案:可以直接使用zookeeper第三方库Curator客户端,这个客户端中封装了一个可重入的锁服务。

Curator提供的InterProcessMutex是分布式锁的实现。acquire方法用户获取锁,release方法用于释放锁。

缺点:

   性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上。

三种方案的比较

上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。

从理解的难易程度角度(从低到高):数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高):Zookeeper >= 缓存 > 数据库

从性能角度(从高到低):缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低):Zookeeper > 缓存 > 数据库

5,Java设计模式部分搞笑解读

1、工厂模式,

搞笑解读:—追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说“来四个鸡翅”就行了。麦当劳和肯德基就是生产鸡翅的Factory。
工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。

2,单例模式

搞笑解读:俺有6个漂亮的老婆,她们的老公都是我,我就是我们家里的老公Sigleton,她们只要说道“老公”,都是指的同一个人,那就是我(白日美梦)。

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的“单一实例”的需求时才可使用。

3,适配器模式

搞笑解读:在一次聚会碰到了一个很漂亮的乌克兰MM,可不会说乌克兰语,她也不会说普通话,只好求助于会乌克兰语的朋友,他作为我们之间的Adapter,让我们可以相互交谈了(也不知道他会不会耍我)。
适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。

4,代理模式

搞笑解读:跟MM在网上聊天,一开头总是“hi,你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?”这些话,真烦人,写个程序做为我的Proxy吧,凡是接收到这些话都设置好了自动的回答,接收到其他的话时再通知我回答,酷吧。

代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些 情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可 以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。

领取福利啦,关于Java的学习资料!感兴趣的开发者请关注此公众号,关注成功后发送“Java学习”关键词,你就可以获得资源下载链接!之后也会有更多资料持续免费送出。

posted @ 2019-07-18 21:01  码农大哥  阅读(13174)  评论(0编辑  收藏  举报