站长百科知识网

站长百科知识网

理解Java中的反射机制,解析反射的使用原理及其中的动态代理机制

style="text-indent:2em;">大家好,今天来为大家分享如何理解java中的反射的一些知识点,和java不建议用getter的问题解析,大家要是都明白,那么可以忽略,如果不太清楚的话可以看看本篇文章,相信很大概率可以解决您的问题,接下来我们就一起来看看吧!

本文目录

  1. 如何优雅的设计Java异常
  2. 如何理解java中的反射
  3. java类面试题哪里有哦
  4. Java程序员都用哪些idea插件常用的

如何优雅的设计Java异常

如何选择异常类型

异常的类别

正如我们所知道的,java中的异常的超类是java.lang.Throwable(后文省略为Throwable),它有两个比较重要的子类,java.lang.Exception(后文省略为Exception)和java.lang.Error(后文省略为Error),其中Error由JVM虚拟机进行管理,如我们所熟知的OutOfMemoryError异常等,所以我们本文不关注Error异常,那么我们细说一下Exception异常。

Exception异常有个比较重要的子类,叫做RuntimeException。我们将RuntimeException或其他继承自RuntimeException的子类称为非受检异常(uncheckedException),其他继承自Exception异常的子类称为受检异常(checkedException)。本文重点来关注一下受检异常和非受检异常这两种异常。

如何选择异常

从笔者的开发经验来看,如果在一个应用中,需要开发一个方法(如某个功能的service方法),这个方法如果中间可能出现异常,那么你需要考虑这个异常出现之后是否调用者可以处理,并且你是否希望调用者进行处理。

如果调用者可以处理,并且你也希望调用者进行处理,那么就要抛出受检异常,提醒调用者在使用你的方法时,考虑到如果抛出异常时如果进行处理。

相似的,如果在写某个方法时,你认为这是个偶然异常,理论上说,你觉得运行时可能会碰到什么问题,而这些问题也许不是必然发生的,也不需要调用者显示的通过异常来判断业务流程操作的,那么这时就可以使用一个RuntimeException这样的非受检异常。

好了,估计我上边说的这段话,你读了很多遍也依然觉得晦涩了。

那么,请跟着我的思路,在慢慢领会一下。

什么时候才需要抛异常

首先我们需要了解一个问题,什么时候才需要抛异常?异常的设计是方便给开发者使用的,但不是乱用的,笔者对于什么时候抛异常这个问题也问了很多朋友,能给出准确答案的确实不多。

其实这个问题很简单,如果你觉得某些”问题”解决不了了,那么你就可以抛出异常了。比如,你在写一个service,其中在写到某段代码处,你发现可能会产生问题,那么就请抛出异常吧,相信我,你此时抛出异常将是一个最佳时机。

应该抛出怎样的异常

了解完了什么时候才需要抛出异常后,我们再思考一个问题,真的当我们抛出异常时,我们应该选用怎样的异常呢?究竟是受检异常还是非受检异常(RuntimeException)呢?

我来举例说明一下这个问题,先从受检异常说起,比如说有这样一个业务逻辑,需要从某文件中读取某个数据,这个读取操作可能是由于文件被删除等其他问题导致无法获取从而出现读取错误,那么就要从redis或mysql数据库中再去获取此数据,参考如下代码,getKey(Integer)为入口程序.

ok,看了以上代码以后,你也许心中有一些想法,原来受检异常可以控制义务逻辑,对,没错,通过受检异常真的可以控制业务逻辑,但是切记不要这样使用,我们应该合理的抛出异常,因为程序本身才是流程,异常的作用仅仅是当你进行不下去的时候找到的一个借口而已,它并不能当成控制程序流程的入口或出口,如果这样使用的话,是在将异常的作用扩大化,这样将会导致代码复杂程度的增加,耦合性会提高,代码可读性降低等问题。

那么就一定不要使用这样的异常吗?其实也不是,在真的有这样的需求的时候,我们可以这样使用,只是切记,不要把它真的当成控制流程的工具或手段。那么究竟什么时候才要抛出这样的异常呢?要考虑,如果调用者调用出错后,一定要让调用者对此错误进行处理才可以,满足这样的要求时,我们才会考虑使用受检异常。

接下来,我们来看一下非受检异常呢(RuntimeException),对于RuntimeException这种异常,我们其实很多见,比如java.lang.NullPointerException/java.lang.IllegalArgumentException等,那么这种异常我们时候抛出呢?

当我们在写某个方法的时候,可能会偶然遇到某个错误,我们认为这个问题时运行时可能为发生的,并且理论上讲,没有这个问题的话,程序将会正常执行的时候,它不强制要求调用者一定要捕获这个异常,此时抛出RuntimeException异常,举个例子,当传来一个路径的时候,需要返回一个路径对应的File对象:

上述例子表明,如果调用者调用getFiles(String)的时候如果path是空,那么就抛出空指针异常(它是RuntimeException的子类),调用者不用显示的进行try…catch…操作进行强制处理.这就要求调用者在调用这样的方法时先进行验证,避免发生RuntimeException.如下:

应该选用哪种异常

通过以上的描述和举例,可以总结出一个结论,RuntimeException异常和受检异常之间的区别就是:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。

场景介绍和技术选型

架构描述

正如我们所知,传统的项目都是以MVC框架为基础进行开发的,本文主要从使用restful风格接口的设计来体验一下异常处理的优雅。

我们把关注点放在restful的api层(和web中的controller层类似)和service层,研究一下在service中如何抛出异常,然后api层如何进行捕获并且转化异常。

使用的技术是:spring-boot,jpa(hibernate),mysql,如果对这些技术不是太熟悉,读者需要自行阅读相关材料。

业务场景描述

选择一个比较简单的业务场景,以电商中的收货地址管理为例,用户在移动端进行购买商品时,需要进行收货地址管理,在项目中,提供一些给移动端进行访问的api接口,如:添加收货地址,删除收货地址,更改收货地址,默认收货地址设置,收货地址列表查询,单个收货地址查询等接口。

构建约束条件

ok,这个是设置好的一个很基本的业务场景,当然,无论什么样的api操作,其中都包含一些规则:

添加收货地址:入参:用户id收货地址实体信息约束:用户id不能为空,且此用户确实是存在的收货地址的必要字段不能为空如果用户还没有收货地址,当此收货地址创建时设置成默认收货地址—删除收货地址:入参:约束:判断此收货地址是否是用户的收货地址判断此收货地址是否为默认收货地址,如果是默认收货地址,那么不能进行删除用户id不能为空,且此用户确实是存在的收货地址不能为空,且此收货地址确实是存在的用户id收货地址id更改收货地址:入参:收货地址id用户id不能为空,且此用户确实是存在的用户id约束:收货地址不能为空,且此收货地址确实是存在的判断此收货地址是否是用户的收货地址默认地址设置:入参:约束:收货地址不能为空,且此收货地址确实是存在的判断此收货地址是否是用户的收货地址用户id不能为空,且此用户确实是存在的用户id收货地址id收货地址列表查询:入参:用户id约束:用户id不能为空,且此用户确实是存在的单个收货地址查询:入参:收货地址id用户id不能为空,且此用户确实是存在的用户id约束:收货地址不能为空,且此收货地址确实是存在的判断此收货地址是否是用户的收货地址

约束判断和技术选型

对于上述列出的约束条件和功能列表,我选择几个比较典型的异常处理场景进行分析:添加收货地址,删除收货地址,获取收货地址列表。

那么应该有哪些必要的知识储备呢,让我们看一下收货地址这个功能:

添加收货地址中需要对用户id和收货地址实体信息就行校验,那么对于非空的判断,我们如何进行工具的选择呢?传统的判断如下:

上边的例子,如果只判断uid为空还好,如果再去判断address这个实体中的某些必要属性是否为空,在字段很多的情况下,这无非是灾难性的。

那我们应该怎么进行这些入参的判断呢,给大家介绍两个知识点:

Guava中的Preconditions类实现了很多入参方法的判断jsr303的validation规范(目前实现比较全的是hibernate实现的hibernate-validator)如果使用了这两种推荐技术,那么入参的判断会变得简单很多。推荐大家多使用这些成熟的技术和jar工具包,他可以减少很多不必要的工作量。我们只需要把重心放到业务逻辑上。而不会因为这些入参的判断耽误更多的时间。

如何优雅的设计Java异常

domain介绍

根据项目场景来看,需要两个domain模型,一个是用户实体,一个是地址实体.

Addressdomain如下:

Userdomain如下:

ok,上边是一个模型关系,用户-收货地址的关系是1-n的关系。上边的@Data是使用了一个叫做lombok的工具,它自动生成了Setter和Getter等方法,用起来非常方便,感兴趣的读者可以自行了解一下。

dao介绍

数据连接层,我们使用了spring-data-jpa这个框架,它要求我们只需要继承框架提供的接口,并且按照约定对方法进行取名,就可以完成我们想要的数据库操作。

用户数据库操作如下:

收货地址操作如下:

正如读者所看到的,我们的DAO只需要继承JpaRepository,它就已经帮我们完成了基本的CURD等操作,如果想了解更多关于spring-data的这个项目,请参考一下spring的官方文档,它比不方案我们对异常的研究。

Service异常设计

ok,终于到了我们的重点了,我们要完成service一些的部分操作:添加收货地址,删除收货地址,获取收货地址列表.

首先看我的service接口定义:

我们来关注一下实现:

添加收货地址

首先再来看一下之前整理的约束条件:

入参:

用户id收货地址实体信息

约束:

用户id不能为空,且此用户确实是存在的收货地址的必要字段不能为空如果用户还没有收货地址,当此收货地址创建时设置成默认收货地址

先看以下代码实现:

其中,已经完成了上述所描述的三点约束条件,当三点约束条件都满足时,才可以进行正常的业务逻辑,否则将抛出异常(一般在此处建议抛出运行时异常-RuntimeException)。

介绍以下以上我所用到的技术:

Preconfitions.checkNotNull(Tt)这个是使用Guava中的com.google.common.base.Preconditions进行判断的,因为service中用到的验证较多,所以建议将Preconfitions改成静态导入的方式:当然Guava的github中的说明也建议我们这样使用。BeanValidators.validateWithException(validator,address);这个使用了hibernate实现的jsr303规范来做的,需要传入一个validator和一个需要验证的实体,那么validator是如何获取的呢,如下:

@Configuration

publicclassBeanConfigs{

@Bean

publicjavax.validation.ValidatorgetValidator(){

returnnewLocalValidatorFactoryBean();

}

}

他将获取一个Validator对象,然后我们在service中进行注入便可以使用了:

那么BeanValidators这个类是如何实现的?其实实现方式很简单,只要去判断jsr303的标注注解就ok了。

那么jsr303的注解写在哪里了呢?当然是写在address实体类中了:

写好你需要的约束条件来进行判断,如果合理的话,才可以进行业务操作,从而对数据库进行操作。

这块的验证是必须的,一个最主要的原因是:这样的验证可以避免脏数据的插入。如果读者有正式上线的经验的话,就可以理解这样的一个事情,任何的代码错误都可以容忍和修改,但是如果出现了脏数据问题,那么它有可能是一个毁灭性的灾难。程序的问题可以修改,但是脏数据的出现有可能无法恢复。所以这就是为什么在service中一定要判断好约束条件,再进行业务逻辑操作的原因了。

此处的判断为业务逻辑判断,是从业务角度来进行筛选判断的,除此之外,有可能在很多场景中都会有不同的业务条件约束,只需要按照要求来做就好。

对于约束条件的总结如下:

基本判断约束(null值等基本判断)实体属性约束(满足jsr303等基础判断)业务条件约束(需求提出的不同的业务约束)

当这个三点都满足时,才可以进行下一步操作ok,基本介绍了如何做一个基础的判断,那么再回到异常的设计问题上,上述代码已经很清楚的描述如何在适当的位置合理的判断一个异常了,那么如何合理的抛出异常呢?

只抛出RuntimeException就算是优雅的抛出异常吗?当然不是,对于service中的抛出异常,笔者认为大致有两种抛出的方法:

抛出带状态码RumtimeException异常抛出指定类型的RuntimeException异常

相对这两种异常的方式进行结束,第一种异常指的是我所有的异常都抛RuntimeException异常,但是需要带一个状态码,调用者可以根据状态码再去查询究竟service抛出了一个什么样的异常。

第二种异常是指在service中抛出什么样的异常就自定义一个指定的异常错误,然后在进行抛出异常。

一般来讲,如果系统没有别的特殊需求的时候,在开发设计中,建议使用第二种方式。但是比如说像基础判断的异常,就可以完全使用guava给我们提供的类库进行操作。jsr303异常也可以使用自己封装好的异常判断类进行操作,因为这两种异常都是属于基础判断,不需要为它们指定特殊的异常。但是对于第三点义务条件约束判断抛出的异常,就需要抛出指定类型的异常了。

对于

定义一个特定的异常类来进行这个义务异常的判断:

然后将此处改为:

or

ok,通过以上对service层的修改,代码更改如下:

这样的service就看起来稳定性和理解性就比较强了。

删除收货地址

入参:

用户id收货地址id

约束:

用户id不能为空,且此用户确实是存在的收货地址不能为空,且此收货地址确实是存在的判断此收货地址是否是用户的收货地址判断此收货地址是否为默认收货地址,如果是默认收货地址,那么不能进行删除

它与上述添加收货地址类似,故不再赘述,delete的service设计如下:

设计了相关的四个异常类:NotFindUserException,NotFindAddressException,NotMatchUserAddressException,DefaultAddressNotDeleteException.根据不同的业务需求抛出不同的异常。

获取收货地址列表

入参:

用户id

约束:

用户id不能为空,且此用户确实是存在的

代码如下:

api异常设计

大致有两种抛出的方法:

抛出带状态码RumtimeException异常抛出指定类型的RuntimeException异常

这个是在设计service层异常时提到的,通过对service层的介绍,我们在service层抛出异常时选择了第二种抛出的方式,不同的是,在api层抛出异常我们需要使用这两种方式进行抛出:要指定api异常的类型,并且要指定相关的状态码,然后才将异常抛出,这种异常设计的核心是让调用api的使用者更能清楚的了解发生异常的详细信息。

除了抛出异常外,我们还需要将状态码对应的异常详细信息以及异常有可能发生的问题制作成一个对应的表展示给用户,方便用户的查询。(如github提供的api文档,微信提供的api文档等),还有一个好处:如果用户需要自定义提示消息,可以根据返回的状态码进行提示的修改。

api验证约束

首先对于api的设计来说,需要存在一个dto对象,这个对象负责和调用者进行数据的沟通和传递,然后dto->domain在传给service进行操作,这一点一定要注意。

第二点,除了说道的service需要进行基础判断(null判断)和jsr303验证以外,同样的,api层也需要进行相关的验证,如果验证不通过的话,直接返回给调用者,告知调用失败,不应该带着不合法的数据再进行对service的访问,那么读者可能会有些迷惑,不是service已经进行验证了,为什么api层还需要进行验证么?这里便设计到了一个概念:编程中的墨菲定律,如果api层的数据验证疏忽了,那么有可能不合法数据就带到了service层,进而讲脏数据保存到了数据库。

所以缜密编程的核心是:永远不要相信收到的数据是合法的。

api异常设计

设计api层异常时,正如我们上边所说的,需要提供错误码和错误信息,那么可以这样设计,提供一个通用的api超类异常,其他不同的api异常都继承自这个超类:

publicclassApiExceptionextendsRuntimeException{

protectedLongerrorCode;

protectedObjectdata;

publicApiException(LongerrorCode,Stringmessage,Objectdata,Throwablee){

super(message,e);

this.errorCode=errorCode;

this.data=data;

}

publicApiException(LongerrorCode,Stringmessage,Objectdata){

this(errorCode,message,data,null);

}

publicApiException(LongerrorCode,Stringmessage){

this(errorCode,message,null,null);

}

publicApiException(Stringmessage,Throwablee){

this(null,message,null,e);

}

publicApiException(){

}

publicApiException(Throwablee){

super(e);

}

publicLonggetErrorCode(){

returnerrorCode;

}

publicvoidsetErrorCode(LongerrorCode){

this.errorCode=errorCode;

}

publicObjectgetData(){

returndata;

}

publicvoidsetData(Objectdata){

this.data=data;

}

}

然后分别定义api层异常:

ApiDefaultAddressNotDeleteException,ApiNotFindAddressException,ApiNotFindUserException,ApiNotMatchUserAddressException。

以默认地址不能删除为例:

publicclassApiDefaultAddressNotDeleteExceptionextendsApiException{

publicApiDefaultAddressNotDeleteException(Stringmessage){

super(AddressErrorCode.DefaultAddressNotDeleteErrorCode,message,null);

}

}

AddressErrorCode.DefaultAddressNotDeleteErrorCode就是需要提供给调用者的错误码。错误码类如下:

ok,那么api层的异常就已经设计完了,在此多说一句,AddressErrorCode错误码类存放了可能出现的错误码,更合理的做法是把他放到配置文件中进行管理。

api处理异常

api层会调用service层,然后来处理service中出现的所有异常,首先,需要保证一点,一定要让api层非常轻,基本上做成一个转发的功能就好(接口参数,传递给service参数,返回给调用者数据,这三个基本功能),然后就要在传递给service参数的那个方法调用上进行异常处理。

此处仅以添加地址为例:

这里的处理方案是调用service时,判断异常的类型,然后将任何service异常都转化成api异常,然后抛出api异常,这是常用的一种异常转化方式。相似删除收货地址和获取收货地址也类似这样处理,在此,不在赘述。

api异常转化

已经讲解了如何抛出异常和何如将service异常转化为api异常,那么转化成api异常直接抛出是否就完成了异常处理呢?答案是否定的,当抛出api异常后,我们需要把api异常返回的数据(jsonorxml)让用户看懂,那么需要把api异常转化成dto对象(ErrorDTO),看如下代码:

@ControllerAdvice(annotations=RestController.class)

classApiExceptionHandlerAdvice{

/**

*Handleexceptionsthrownbyhandlers.

*/

@ExceptionHandler(value=Exception.class)

@ResponseBody

publicResponseEntity<ErrorDTO>exception(Exceptionexception,HttpServletResponseresponse){

ErrorDTOerrorDTO=newErrorDTO();

if(exceptioninstanceofApiException){//api异常

ApiExceptionapiException=(ApiException)exception;

errorDTO.setErrorCode(apiException.getErrorCode());

}else{//未知异常

errorDTO.setErrorCode(0L);

}

errorDTO.setTip(exception.getMessage());

ResponseEntity<ErrorDTO>responseEntity=newResponseEntity<>(errorDTO,HttpStatus.valueOf(response.getStatus()));

returnresponseEntity;

}

@Setter

@Getter

classErrorDTO{

privateLongerrorCode;

privateStringtip;

}

}

ok,这样就完成了api异常转化成用户可以读懂的DTO对象了,代码中用到了@ControllerAdvice,这是springMVC提供的一个特殊的切面处理。

当调用api接口发生异常时,用户也可以收到正常的数据格式了,比如当没有用户(uid为2)时,却为这个用户添加收货地址,postman(Googleplugin用于模拟http请求)之后的数据:

总结

只从如何设计异常作为重点来讲解,涉及到的api传输和service的处理,还有待优化,比如api接口访问需要使用https进行加密,api接口需要OAuth2.0授权或api接口需要签名认证等问题,文中都未曾提到,重心在于异常如何处理,所以只需关注涉及到异常相关的问题和处理方式就可以了。

如何理解java中的反射

JAVA中的反射无处不在,不仅在jdk中存在,还在诸如spring,mybatis,设计模式等中广泛使用!

首先要知道的是,JAVA可以算做编译型语言,大多数的类,方法都在编译时已经明确,这显然不能满足于我们所有的需求,使用反射就可以在运行时动态加载,通过类可以构造对象,准确的知道它的属性,方法等全部信息!

JAVA中的反射方式有以下几种:

①Class.forName(“类全路径”);

②对象名.getClass

③基本类型的包装类:Boolean.TYPE,Integer.TYPE等!

反射的作用有:

1,可以在运行时动态获得对象,

2,在运行时动态获取一个类的构造器,方法,变量,注解等!

3,实现动态代理。

反射的缺点:因为是在运行时获取,没有JAVA的预编译,在运行时性能存在问题!

反射的实际使用场景:

1,spring中实例化对象的时候如果都用new来创建对象,那将会十分繁琐,而且加入新类型的时候也需要重新new,spring的做法是让这些所有对象继承自beanDefinition,这样在实例化的时候,只要传入父类和子类类型即可!

2,动态代理:springaop中的注释模式就是用了动态代理,比如JdkDynamicAopProxy

反射在实际开发中也是体现一个开发人员水平高低的参考,要让这种思想深深烙入心里,在实际开发中解决很多后期扩展困难的问题!需要反射Demo的可以私信我索取,近期一直在分享JAVA开发方面的东西,有些很不错,敬请关注。。

java类面试题哪里有哦

对于大部分人而言,在找工作之前少不了准备工作,有一份全面细致面试题将帮助大家减少许多麻烦。所以准备找工作的小伙伴可以看参考一下哦!

ThreadLocal(线程变量副本)

Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量。采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。ThreadLocal在Spring中发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影。Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。

“你能不能谈谈,javaGC是在什么时候,对什么东西,做了什么事情?”

在什么时候:1、新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minorGC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。2、大对象以及长期存活的对象直接进入老年区。3、当每次执行minorGC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次FullGC以尽可能地获得老年区的空间。

对什么东西:

从GCRoots搜索不到,而且经过一次标记清理之后仍没有复活的对象。

做什么:

新生代:复制清理;老年代:标记-清除和标记-压缩算法;永久代:存放Java中的类和加载类的类加载器本身。GCRoots都有哪些:1.虚拟机栈中的引用的对象2.方法区中静态属性引用的对象,常量引用的对象3.本地方法栈中JNI(即一般说的Native方法)引用的对象。

Synchronized与Loc

Synchronized与Lock都是可重入锁,同一个线程再次进入同步代码的时候。可以使用自己已经获取到的锁。

Synchronized是悲观锁机制,独占锁。而Locks.ReentrantLock是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

ReentrantLock适用场景

1、某个线程在等待一个锁的控制权的这段时间需要中断;

2、需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程,锁可以绑定多个条件;

3、具有公平锁功能,每个到来的线程都将排队等候。

StringBuffer是线程安全的,每次操作字符串,String会生成一个新的对象,而StringBuffer不会;StringBuilder是非线程安全的。

fail-fast是什么?

fail-fast:机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

happens-before

happens-before:如果两个操作之间具有happens-before关系,那么前一个操作的结果就会对后面一个操作可见。1、程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。2、监视器锁规则:对一个监视器锁的解锁,happens-before于随后对这个监视器锁的加锁。3、volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。4、传递性:如果Ahappens-beforeB,且Bhappens-beforeC,那么Ahappens-beforeC。5、线程启动规则:Thread对象的start()方法happens-before于此线程的每一个动作。

Volatile和Synchronized的不同点

Volatile和Synchronized四个不同点:1、粒度不同,前者针对变量,后者锁对象和类;2、syn阻塞,volatile线程不阻塞;3、syn保证三大特性,volatile不保证原子性;

4、syn编译器优化,volatile不优化volatile具备两种特性:

保证此变量对所有线程的可见性,指一条线程修改了这个变量的值,新值对于其他线程来说是可见的,但并不是多线程安全的;禁止指令重排序优化。

Volatile如何保证内存可见性:

1、当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。

2、当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

同步:就是一个任务的完成需要依赖另外一个任务,只有等待被依赖的任务完成后,依赖任务才能完成。异步:不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,只要自己任务完成了就算完成了,被依赖的任务是否完成会通知回来。(异步的特点就是通知)。打电话和发短信来比喻同步和异步操作。阻塞:CPU停下来等一个慢的操作完成以后,才会接着完成其他的工作。非阻塞:非阻塞就是在这个慢的执行时,CPU去做其他工作,等这个慢的完成后,CPU才会接着完成后续的操作。非阻塞会造成线程切换增加,增加CPU的使用时间能不能补偿系统的切换成本需要考虑。

CAS

CAS(CompareAndSwap)无锁算法:CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

线程池的作用

线程池的作用:在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。第三:提高线程的可管理性。常用线程池:ExecutorService是主要的实现类,其中常用的有Executors.newSingleThreadPool()、newFixedThreadPool()、newcachedTheadPool()、newScheduledThreadPool()。

类加载器工作机制

装载:将Java二进制代码导入jvm中,生成Class文件。连接:

a)校验:检查载入Class文件数据的正确性;

b)准备:给类的静态变量分配存储空间;

c)解析:将符号引用转成直接引用。初始化:对类的静态变量,静态方法和静态代码块执行初始化工作。双亲委派模型:类加载器收到类加载请求,首先将请求委派给父类加载器完成,用户自定义加载器-应用程序加载器-扩展类加载器-启动类加载器。

Redis数据结构

String—字符串(key-value类型)

Hash—字典(hashmap)Redis的哈希结构可以使你像在数据库中更新一个属性一样只修改某一项属性值List—列表实现消息队列Set—集合利用唯一性SortedSet—有序集合可以进行排序可以实现数据持久化

索引:B+,B-,全文索引

Mysql的索引是一个数据结构,旨在使数据库高效的查找数据。常用的数据结构是B+Tree,每个叶子节点不但存放了索引键的相关信息还增加了指向相邻叶子节点的指针,这样就形成了带有顺序访问指针的B+Tree,做这个优化的目的是提高不同区间访问的性能。什么时候使用索引:

1、经常出现在groupby,orderby和distinc关键字后面的字段。

2、经常与其他表进行连接的表,在连接字段上应该建立索引。

3、经常出现在Where子句中的字段。

4、经常出现用作查询选择的字段。

SpringIOC

Spring支持三种依赖注入方式,分别是属性(Setter方法)注入,构造注入和接口注入。在Spring中,那些组成应用的主体及由SpringIOC容器所管理的对象被称之为Bean。Spring的IOC容器通过反射的机制实例化Bean并建立Bean之间的依赖关系。简单地讲,Bean就是由SpringIOC容器初始化、装配及被管理的对象。获取Bean对象的过程,首先通过Resource加载配置文件并启动IOC容器,然后通过getBean方法获取bean对象,就可以调用他的方法。SpringBean的作用域:Singleton:SpringIOC容器中只有一个共享的Bean实例,一般都是Singleton作用域。Prototype:每一个请求,会产生一个新的Bean实例。Request:每一次http请求会产生一个新的Bean实例。

代理都有哪些?

代理的共有优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。Java静态代理:代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,代理对象可以在调用目标对象相应方法前后加上其他业务处理逻辑。缺点:一个代理类只能代理一个业务类。如果业务类增加方法时,相应的代理类也要增加方法。Java动态代理:Java动态代理是写一个类实现InvocationHandler接口,重写Invoke方法,在Invoke方法可以进行增强处理的逻辑的编写,这个公共代理类在运行的时候才能明确自己要代理的对象,同时可以实现该被代理类的方法的实现,然后在实现类方法的时候可以进行增强处理。实际上:代理对象的方法=增强处理+被代理对象的方法

JDK和CGLIB生成动态代理类的区别:JDK动态代理只能针对实现了接口的类生成代理(实例化一个类)。此时代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑。CGLIB是针对类实现代理,主要是对指定的类生成一个子类(没有实例化一个类),覆盖其中的方法。

SpringMVC运行原理

1、客户端请求提交到DispatcherServlet。

2、由DispatcherServlet控制器查询HandlerMapping,找到并分发到指定的Controller中。

3、Controller调用业务逻辑处理后,返回ModelAndView。

4、DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图。

5、视图负责将结果显示到客户端。

一个Http请求

DNS域名解析–发起TCP的三次握手–建立TCP连接后发起http请求–服务器响应http请求,浏览器得到html代码–浏览器解析html代码,并请求html代码中的资源(如javascript、css、图片等)–浏览器对页面进行渲染呈现给用户。

设计存储海量数据的存储系统:设计一个叫“中间层”的一个逻辑层,在这个层,将数据库的海量数据抓出来,做成缓存,运行在服务器的内存中,同理,当有新的数据到来,也先做成缓存,再想办法,持久化到数据库中,这是一个简单的思路。

主要的步骤是负载均衡,将不同用户的请求分发到不同的处理节点上,然后先存入缓存,定时向主数据库更新数据。读写的过程采用类似乐观锁的机制,可以一直读(在写数据的时候也可以),但是每次读的时候会有个版本的标记,如果本次读的版本低于缓存的版本,会重新读数据,这样的情况并不多,可以忍受。

Session与Cookie

Session与Cookie:Cookie可以让服务端跟踪每个客户端的访问,但是每次客户端的访问都必须传回这些Cookie,如果Cookie很多,则无形的增加了客户端与服务端的数据传输量,而Session则很好地解决了这个问题,同一个客户端每次和服务端交互时,将数据存储通过Session到服务端,不需要每次都传回所有的Cookie值,而是传回一个ID,每个客户端第一次访问服务器生成的唯一的ID,客户端只要传回这个ID就行了,这个ID通常为NAME为JSESSIONID的一个Cookie。这样服务端就可以通过这个ID,来将存储到服务端的KV值取出了。

分布式Session框架

1、配置服务器,Zookeeper集群管理服务器可以统一管理所有服务器的配置文件;

2、共享这些Session存储在一个分布式缓存中,可以随时写入和读取,而且性能要很好,如Memcache,Tair;

3、封装一个类继承自HttpSession,将Session存入到这个类中然后再存入分布式缓存中;

4、由于Cookie不能跨域访问,要实现Session同步,要同步SessionID写到不同域名下。

InputStream

适配器模式:将一个接口适配到另一个接口,JavaI/O中InputStreamReader将Reader类适配到InputStream,从而实现了字节流到字符流的准换。装饰者模式:保持原来的接口,增强原来有的功能。FileInputStream实现了InputStream的所有接口,BufferedInputStreams继承自FileInputStream是具体的装饰器实现者,将InputStream读取的内容保存在内存中,而提高读取的性能。

Spring事务配置方法

1、切点信息,用于定位实施事物切面的业务类方法。2、控制事务行为的事务属性,这些属性包括事物隔离级别,事务传播行为,超时时间,回滚规则。Spring通过aop/txSchema命名空间和@Transaction注解技术来进行声明式事物配置。

Mybatis

每一个Mybatis的应用程序都以一个SqlSessionFactory对象的实例为核心。首先用字节流通过Resource将配置文件读入。

通过SqlSessionFactoryBuilder().build方法创建SqlSessionFactory,然后再通过SqlSessionFactory.openSession()方法创建一个SqlSession为每一个数据库事务服务。经历了Mybatis初始化–创建SqlSession–运行SQL语句,返回结果三个过程。

Servlet和Filter的区别

整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。

Filter有如下几个用处:Filter可以进行对特定的url请求和相应做预处理和后处理。在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest。根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。在HttpServletResponse到达客户端之前,拦截HttpServletResponse。根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

实际上Filter和Servlet极其相似,区别只是Filter不能直接对用户生成响应。实际上Filter里doFilter()方法里的代码就是从多个Servlet的service()方法里抽取的通用代码,通过使用Filter可以实现更好的复用。

Filter和Servlet的生命周期:1、Filter在web服务器启动时初始化。2、如果某个Servlet配置了1,该Servlet也是在Tomcat(Servlet容器)启动时初始化。3、如果Servlet没有配置1,该Servlet不会在Tomcat启动时初始化,而是在请求到来时初始化。4、每次请求,Request都会被初始化,响应请求后,请求被销毁。5、Servlet初始化后,将不会随着请求的结束而注销。6、关闭Tomcat时,Servlet、Filter依次被注销。

HashMap与HashTable的区别

1、HashMap是非线程安全的,HashTable是线程安全的。2、HashMap的键和值都允许有null值存在,而HashTable则不行。3、因为线程安全的问题,HashMap效率比HashTable的要高。

HashMap的实现机制:

1、维护一个每个元素是一个链表的数组,而且链表中的每个节点是一个Entry[]键值对的数据结构。

2、实现了数组+链表的特性,查找快,插入删除也快。

3、对于每个key,他对应的数组索引下标是inti=hash(key.hashcode)(len-1);

4、每个新加入的节点放在链表首,然后该新加入的节点指向原链表首。

Linux常用命令

cd,cp,mv,rm,ps(进程),tar,cat(查看内容),chmod,vim,find,ls

死锁的必要条件

1、互斥至少有一个资源处于非共享状态;

2、占有并等待;

3、非抢占;

4、循环等待。

解决死锁,第一个是死锁预防,就是不让上面的四个条件同时成立。二是,合理分配资源。三是使用银行家算法,如果该进程请求的资源操作系统剩余量可以满足,那么就分配。

进程间的通信方式

管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

有名管道(namedpipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

消息队列(messagequeue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

信号(sinal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

共享内存(sharedmemory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

套接字(socket):套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

Switch能否用string做参数?

在Java7之前,switch只能支持byte、short、char、int或者其对应的封装类以及Enum类型。在Java7中、String支持被加上了。

Object有哪些公用方法?

a、方法equals测试的是两个对象是否相等;

b、方法clone进行对象拷贝;

c、方法getClass返回和当前对象相关的Class对象;

d、方法notify,notifyall,wait都是用来对给定对象进行线程同步的。

Java的四种引用,强弱软虚,以及用到的场景

利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。

通过软可及对象重获方法实现Java对象的高速缓存:比如我们创建了一Employee的类,如果每次需要查询一个雇员的信息。哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这是需要消耗很多时间的。我们可以通过软引用和HashMap的结合,先是保存引用方面:以软引用的方式对一个Employee对象的实例进行引用并保存该引用到HashMap上,key为此雇员的id,value为这个对象的软引用,另一方面是取出引用,缓存中是否有该Employee实例的软引用,如果有,从软引用中取得。如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,并保存对这个新建实例的软引用。

强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出OutOfMemoryError错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。

弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当JVM进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。

虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。

Hashcode的作用,与equal有什么区别?

同样用于鉴定2个对象是否相等的,java集合中有list和set两类,其中set不允许元素重复实现,那个这个不允许重复实现的方法,如果用equal去比较的话,如果存在1000个元素,你new一个新的元素出来,需要去调用1000次equal去逐个和他们比较是否是同一个对象,这样会大大降低效率。

hashcode实际上是返回对象的存储地址,如果这个位置上没有元素,就把元素直接存储在上面,如果这个位置上已经存在元素,这个时候才去调用equal方法与新元素进行比较,相同的话就不存了,散列到其他地址上。

Override和Overload的含义以及区别

a、Overload顾名思义是重新加载,它可以表现类的多态性,可以是函数里面可以有相同的函数名但是参数名、返回值、类型不能相同;或者说可以改变参数、类型、返回值但是函数名字依然不变。b、就是ride(重写)的意思,在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数,当子类在调用这一函数时自动调用子类的方法,而父类相当于被覆盖(重写)了。具体可前往C++中重载、重写(覆盖)的区别实例分析查看。

抽象类和接口的区别

a、一个类只能继承单个类,但是可以实现多个接口;

b、抽象类中可以有构造方法,接口中不能有构造方法;

c、抽象类中的所有方法并不一定要是抽象的,你可以选择在抽象类中实现一些基本的方法。而接口要求所有的方法都必须是抽象的;

d、抽象类中可以包含静态方法,接口中不可以;

e、抽象类中可以有普通成员变量,接口中不可以。

解析XML的几种方式的原理与特点

DOM:消耗内存:先把xml文档都读到内存中,然后再用DOMAPI来访问树形结构,并获取数据。这个写起来很简单,但是很消耗内存。要是数据过大,手机不够牛逼,可能手机直接死机。

SAX:解析效率高,占用内存少,基于事件驱动的:更加简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。

PULL:与SAX类似,也是基于事件驱动,我们可以调用它的next()方法,来获取下一个解析事件(就是开始文档,结束文档,开始标签,结束标签),当处于某个元素时可以调用XmlPullParser的getAttributte()方法来获取属性的值,也可调用它的nextText()获取本节点的值。

wait()和sleep()的区别

sleep来自Thread类,和wait来自Object类。

调用sleep()方法的过程中,线程不会释放对象锁。而调用wait方法线程会释放对象锁。

sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU。

sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒。

JAVA中堆和栈的区别,说下java的内存机制

基本数据类型比变量和对象的引用都是在栈分配的;

堆内存用来存放由new创建的对象和数组;

类变量(static修饰的变量):程序在一加载的时候就在堆中为类变量分配内存,堆中的内存地址存放在栈中;

实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量,是根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的”物理位置”,实例变量的生命周期–当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存;

局部变量:由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放。

JAVA多态的实现原理

a、抽象的来讲,多态的意思就是同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

b、实现的原理是动态绑定,程序调用的方法在运行期才动态绑定,追溯源码可以发现,JVM通过参数的自动转型来找到合适的办法。

Java程序员都用哪些idea插件常用的

日常使用的普通插件其他人已经回答很多了,我来为大家介绍几个比较新颖的AI辅助编码插件,包含国外的Kite、Codota、TabNine、GitHubCopilot,国内的阿里云Cosy、AIXcoder。

先总结一下个人体验的简单感受:

TabNine支持的语言及IDE种类最丰富,本地模式内存占用高;GitHubCopilot提供的代码补全/生成效果更惊艳,希望未来收费不太贵;阿里云Cosy的代码搜索功能比较实用,可以与其他工具搭配使用;Kite仅适合Python开发者,补全不算惊艳;Codota已经不再维护了,可以不考虑了;AIXcoder中规中矩,缺少亮点;一、GitHubCopilot

安装方式:IntelliJIDEA插件市场搜索“GitHubCopilot”。

GitHub近期发布的的代码智能生成插件,目前支持VSCode、JetBrains等IDE平台,不同于其他代码补全工具只提供最多一行的补全结果,Copilot能通过代码上下文以及语言描述,生成整个代码片段,无疑是开发者的编码利器。它是如何做到的呢?下文中提到TabNine使用的是GPT-2深度学习模型,而Copilot使用的是OpenAI据说烧了数千万美元研发出来的GPT-3模型,Copilot在GPT-3模型基础之上通过对GitHub的开源代码进行学习,得到了Codex模型,该模型具备强大的代码生成能力。但是由于该模型非常庞大,需要有足够的硬件支撑,所以Copilot目前只能通过邀测的形式小范围试用。从FAQ中的信息显示,Copilot未来可能不会大面积免费开放,未来想要尝试该工具的开发者要准备好预算。

GitHubCopilot提供了片段级的代码补全功能,可以用于注释生成代码、生成单元测试等场景。

提供了远程服务模式,需要将代码上传到远端,所以如果是企业的开发者可能要注意数据安全了能够通过代码上下文、注释及语言描述生成方法级的代码片段

GitHubCopilot是一款非常好用的代码生成工具,适合生成通用的工具代码、leetcode算法、单元测试等场景的高频代码,而对业务代码的生成则依然存在语法错误的问题,可惜Copilot只提供了远程服务模式,并且基于Copilot当前的技术路线也不太可能提供离线模式,这点可能会带来潜在的隐私及代码数据的泄露风险,希望以后收费不会太贵。

个人体验评价

语言及IDE支持:????☆☆☆代码补全:????????☆代码搜索:不支持二、TabNine

安装方式:IntelliJIDEA插件市场搜索“TabNine”。

TabNine是一款比较年轻的开发工具,刚发布的时候还是比较惊艳的,那个时候OpenAI刚开源GPT-2模型不久,TabNine基于GPT-2模型在海量代码数据上进行调优,打造出了一款针对代码的深度学习引擎,它能智能识别代码的上文信息,提供长序列的代码补全结果。目前,已经被Codota公司收购,并主推该工具,宣称支持所有主流的开发语言。

TabNine提供代码智能补全功能,支持以下特性:

免费版只提供较为基础的补全功能,收费的Pro版本补全效果更好远程服务模式,代码需要上传到远程服务器,但是模型更强大,本地内存占用少离线模式,能够把模型下载到用户本地,无法联网也可使用补全,但是本地内存占用高,有时候CPU会飙高针对专业版提供了适配企业/私有代码的能力,是需要收费的

TabNine能提供长序列的代码补全,在我体验过的同类工具中,TabNine是支持的开发语言种类以及IDE平台最多的工具。但是与其他工具相同,它会推荐出不完整的代码以及存在语法错误的代码。从部分开发者的反馈了解到,有部分开发者喜欢TabNine即时学习代码模式的能力,这点是相比其他工具比较不错的,虽然目前提取代码模式的能力还存在比较多的缺陷,但是相信未来会逐渐完善。

个人体验评价

语言及IDE支持:??????????代码补全:??????☆☆代码搜索:不支持三、阿里云Cosy

安装方式:IntelliJIDEA插件市场搜索“Cosy”或者“AlibabaCloudAICodingAssistant”。

阿里云的智能编码插件Cosy于2021年10月份发布,到现在才两个月时间,是个相对年轻的工具。目前仅支持Java语言。通过其帮助文档了解到,它使用深度学习模型加语法分析结合的技术,通过深度学习模型强力的学习能力生成长序列代码,同时又通过结合语法分析的能力去纠正模型生成的错误。我个人比较喜欢的是Cosy的代码搜索功能,它从GitHub和StackOverflow中抽取出了常用的代码片段,让我能很方便的在IDE中直接搜索需要的信息。

阿里云Cosy提供的代码智能补全仅提供IntelliJIDEA插件,支持Java语言,相比于其他工具支持的语言种类比较少,相比于TabNine,在补全效果上有一些优化,减少了补全半截的情况。

支持整行的代码补全结果离线模式,Cosy会在首次启动时把模型下载到本地,之后便可断网使用

我个人比较喜欢的是阿里云Cosy的代码示例搜索功能,支持以下特性:

在IDE内支持指定API的代码示例搜索在IDE内支持通过功能描述搜索StackOverflow、GitHub等来源的开源代码片段

阿里云Cosy的代码补全在TabNine的基础上更进一步的解决了部分生成错误代码的缺陷,但是在细节打磨上还存在一些问题,与TabNine、GitHubCopilot相比各有千秋。Cosy的代码搜索功能相比其他工具确实是一个比较好的亮点,让我的日常“抄”代码轻松很多。

个人体验评价

语言及IDE支持:??☆☆☆☆代码补全:??????☆☆代码搜索:????????☆四、Kite

安装方式:IntelliJIDEA插件市场搜索“Kite”。

说到业界在代码智能补全领域发布最早的工具,Kite便是其中之一,它成立于2014年,在Atom/Vim/Spyder等编辑器发布代码智能提示插件,初期只面向Python开发者,当前扩展到面向绝大部分主流开发语言。并于2020年初发布JetBrains插件,2021年初发布VSCode插件。Kite支持目前主流的16种开发语言以及16种代码编辑器,但是从我的使用体验上来看,其最擅长的还是Python语言,毕竟是Kite最早支持的语言类型。

Kite提供基于机器学习的代码智能补全功能,支持以下特性:

远程服务模式,代码需要上传到远程服务器离线模式,能够把模型下载到用户本地,无法联网也可使用补全专业版能针对用户的代码库进行训练

Kite也提供了Python官方文档搜索功能

支持PythonAPI的官方文档及少量示例

Kite适合Python开发者,毕竟Kite是以Python起家,在Python语言上打磨了很多年,虽然不像其他同类工具一样,能提供非常惊艳的长序列代码补全,但是它对IDE自带补全的排序优化效果还是很不错的。Kite的API文档搜索能搜索到常用的PythonAPI官方文档,但是大部分能在IDE中通过查看API定义查看,代码的使用示例比较缺乏。

个人体验评价

语言及IDE支持:????????☆代码补全:????☆☆☆代码搜索:????☆☆☆五、Codota

安装方式:IntelliJIDEA插件市场搜索“Codota”。

除了Kite,业界另一个涉足最早涉足该领域的工具是Codota,Codota成立于2013年,在2014年下旬发布第一个Jetbrains插件版本,主要面向Java开发者。但是,目前Codota公司收购TabNine之后,已经放弃了Codota这款插件,并将老产品改名为了TabNine。

Codota提供了代码补全功能,在Java语言上效果比较好:

基于程序分析、统计分析的规则化代码补全,主要能针对部分类名、常用的代码模板进行补全

Codota提供了IDE内的代码示例搜索功能(支持Java、JavaScript)

支持搜索引用了指定API的代码示例

在其他代码补全工具发布之前,Codota还是一个比较不错的开发利器,特别是丰富了IDE自带的代码模板,可以帮助开发者节省很多低级编码的时间,Codota也有一些用于生成代码的小功能,这里就不再扩展。但是在目前AI爆发的时代,Codota的产品演进没有跟上步伐,所以Codota公司已经放弃该产品的演进,转而大力发展收购的TabNine。

个人体验评价

语言及IDE支持:????☆☆☆代码补全:????☆☆☆代码搜索:??????☆☆六、AIXcoder

安装方式:IntelliJIDEA插件市场搜索“AIXcoder”。

AIXcoder从天眼查了解到其成立于2017年,根据官网主页的信息,它也采用了深度学习的技术,并且对模型进行了压缩,以便于提升用户下载模型的速度,减少用户本地CPU的开销。AIXcoder提供了对用户本地代码库学习的工具,但是免费版有次数限制,而且学习本地代码库的时候电脑会比较卡。让我有点疑惑的是,AIXcoder官网主页视频中演示了IDE内的代码搜索功能,但是在实际插件中只提供了一个跳转到浏览器中的按钮,并不能在IDE内直接搜索(难道是需要付费的?)。目前发布了Jetbrains及VSCode插件,主要支持Java、Python、C/C++、JavaScript、Typescript、Go、PHP等语言。

AIXcoder提供的代码智能补全与Kite比较相似,都会对IDE自带的补全结果进行排序优化,只是AIXcoder在Java上效果好一些,支持以下特性:

远程服务模式,代码需要上传到远程服务器,速度更快一点,卡顿感会减少离线模式,会把模型下载到本地

官方主页的演示视频说是支持IDE内的代码搜索,但是我安装插件后发现只能跳转到网页端的搜索页面上,无法在IDE内搜索,难道这块功能是需要收费的?总的来说AIXcoder在刚发布的时候还是很不错的,但是最近发布了太多同类工具,AIXcoder的代码补全体验相比其他工具算不上出彩,希望AIXcoder能继续提升。

个人体验评价

语言及IDE支持:??????☆☆

代码补全:????☆☆☆

代码搜索:IDE内不支持

好了,本文到此结束,如果可以帮助到大家,还望关注本站哦!

深入理解 Java 反射和动态代理

标签:# 反射# 我的# 理解# 如何# 网站