Retrofit源码分析及理解

参考文档:

12W字;2022最新Android11位大厂面试专题(一) - 掘金

Retrofit 版本号:2.9.0

Retrofit简单来说,就是对OkHttp上层进行了封装,已达到用户方便使用管理网络请求的目的。

Retrofit内部有着简捷、优雅的结构设计。当然对于新手来说,Retrofit也有着不宜理解和上手的问题。不过瑕不掩瑜,Retrofit仍然是一款优秀的网络请求框架。

这边文章除了进行源码分析,还有个人对Retrofit的一些浅见和感悟。仅供读者参考。

一.使用方法

早期网络请求方式,一般都是设计一个OKHttpUtil之类的工具类,实现对Okhttp请求的封装,入参传入url、请求参数param,回调函数callback。这样的请求方式对新用户来说是非常易于上手和容易理解的,但为什么还要使用Retrofit呢,它到底能给我们带来什么呢?

先看看使用方法吧:

使用示例中的接口来自玩Android - wanandroid.com - 每日推荐优质文章

1.添加Retrofit依赖

implementation("com.squareup.retrofit2:retrofit:2.9.0")
// retrofit gson转换器
implementation("com.squareup.retrofit2:converter-gson:2.9.0")

2.创建一个接口

用于管理不同的网络请求

这里第一个接口返回的类型为Call<T>,此处的Call为Retrofit中的接口,并非Okhttp中的Call(虽然他们接口中定义的方法很类似),如果不特别说明,后面源码分析中的也是这个Call。

interface MainService {@GET("article/list/{index}/json")fun paperList(@Path("index") index: Int): Call<MainPaperList>@GET("hotkey/json")suspend fun hotkey(): HotKey@POST("hotkey/json")suspend fun hotkey2(@Body req: TestReq): HotKey
}

3.创建Retrofit并创建接口实现的实例

创建Retrofit,完成配置。

这里的接口实现使用Java动态代理方式。

// 1.Retrofit创建
val retrofit = Retrofit.Builder().baseUrl("https://www.wanandroid.com/").addConverterFactory(GsonConverterFactory.create()).build()// 2.创建网络请求接口示例
val service = retrofit.create(MainService::class.java)

4.发起请求

这里给出异步请求的示例,默认情况下回调函数会在主线程运行。

动态代理实现中,对调用接口方法进行解析,包括接口参数、返回值和注解,根据这些配置项使用OKHttp进行网络请求。

这里使用反射方式进行解析,可能会稍有耗时,但解析完成后会放在缓存中,再次请求时从缓存中取用。

fun queryPaperList() {service.paperList(0).enqueue(object : Callback<MainPaperList> {override fun onResponse(call: Call<MainPaperList>,response: Response<MainPaperList>) {// 请求成功}override fun onFailure(call: Call<MainPaperList>, t: Throwable) {// 请求异常}})
}

5.推荐个IDEA插件

在IDEA插件搜索框搜索Retrofit就可以出来。

1.RestfulTool Retrofit

这个插件可以浏览并快速定位到自己定义的Retrofit接口。免费使用。

2.Retrofit Assistant

这个插件功能强大,方便用户快速创建和浏览网络请求的定义的接口,有30天免费试用期,之后使用需要付费。

可以参考插件作者的这篇文章了解:Retrofit的好基友-RetrofitAssistant插件 - 掘金

二.为什么好用

Retrofit有以下优点,分开来解读

1.写法简洁

用接口进行网络请求管理对于初学者来说确实比较怪异,有一定学习曲线,需要花时间来熟悉用法和实现原理。但这样的写法非常简洁方便,声明式的方式定义和处理网络请求可以减少了样板代码的编写。

2.主流支持

同时Retrofit内部实现了对RxJava和协程(2.6.0版本之后)的支持,以及对Json、xml数据格式的支持。这些支持方式免去了使用者去费劲编写转换代码。

3.可扩展

如果需要对请求数据和返回数据做一些转换或者特殊处理,Retrofit同样提供了入口允许用户进行一些特殊处理,比如设置自己的okhttpClient;如需对请求数据和返回数据做一些转换或者特殊处理,可以设置自定义的converterFactory;还可以配置callAdapterFactory,实现特殊的请求方式。

Retrofit存在着大量的设计模式,这些设计模式实现了代码的解耦课可拓展性,这里配置中出现converterFactory和callAdapterFactory都是工厂方法模式的使用。有一些经验的开发者觉得阅读起来会觉得很舒服,而初学者则会有一定的阅读困难。不过很多设计模式都会有这样的问题。设计模式已经广泛应用在各个开源组件的代码中,学习和使用设计模式是提高代码理解能力的必备功课吧。

三.结构总览

Retrofit经营着一家高档餐厅,餐厅里共有五个部门。第一个部门是主厨部门,一个主厨只负责一道菜的指挥和监督工作。还有四个部门,分别归四个管家管理,每个管家负责对小厨娘进行招募,每个部门的小厨娘只负责做菜的其中一个环节。

当一道菜品需要制作时,会组建一个厨师团队进行菜品制作的部署工作,这个团队安排一个主厨负责,还有几个小厨娘供主厨指挥,在部署工作进行中还会招募一些其他帮手。

几个个小厨娘各司其职,互相配合,在主厨指挥下,完成任务,厨师负责最后摆盘,供客人食用。

四个管家:callFactory、converterFactories、callAdapterFactories、callbackExecutor

主厨团队:serviceMethodCache

请记住这四个管家,他们将在后面源码中频繁出现。

四.源码分析

源码中关键地方都做了中文注释

1.人员预备:Retrofit和Retrofit.Build

这里主要做基础的建设工作。

Retrofit使用建造者模式创建:

val retrofit = Retrofit.Builder()....build()

先看看Retrofit的内部的成员变量和构造方法:

public final class Retrofit {// 接口缓存。一个接口请求的方法为一个Method,对应一个ServiceMethod,ServiceMethod内部有着网络请求相关配置private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();// OkHttp的接口,创建Okhttp请求(Call)的工厂接口,只有一个实现类:OkHttpClientfinal okhttp3.Call.Factory callFactory;// 网络请求基地址,即host地址final HttpUrl baseUrl;// 将请求或者返回结果进行转换的工厂类列表,比如Retrofit提供的GsonConverterFactory提供了将出入参和Gson进行转换的Converterfinal List<Converter.Factory> converterFactories;// callAdapter工厂集合,CallAdapter就是Call的转换器(Retrofit中的Call),callAdapterFactory会根据自己的能力返回合适的CallAdapterfinal List<CallAdapter.Factory> callAdapterFactories;// Call请求的回调方法运行的executor,默认Retrofit定义的MainThreadExecutor,即运行在主线程的Executorfinal @Nullable Executor callbackExecutor;// 字面意思就是提前验证,如果是true的话,当retrofit.create创建接口实现类时,就进行接口的解析和初始化,生成相应的ServiceMethod,放入缓存map中final boolean validateEagerly;Retrofit(okhttp3.Call.Factory callFactory,HttpUrl baseUrl,List<Converter.Factory> converterFactories,List<CallAdapter.Factory> callAdapterFactories,@Nullable Executor callbackExecutor,boolean validateEagerly) {this.callFactory = callFactory;this.baseUrl = baseUrl;this.converterFactories = converterFactories; // Copy+unmodifiable at call site.this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.this.callbackExecutor = callbackExecutor;this.validateEagerly = validateEagerly;}
}

Retrofit.Builder中的成员变量除了多了一个Platform,其他与Retrofit的那些一样,Builder里的方法也是对这些值得设置。

这里的Platform要重点说明一下

Platform提供了对Android和Java平台的支持,也就是说Retrofit不仅可以用在Android开发,还可以用于后端Java开发。

Platform具体提供了以下内容:

1.默认的CallAdapterFactory。通过defaultCallAdapterFactories方法,其中有DefaultCallAdapterFactory(将异步请求结果的回调使用callbackExecutor运行)、CompletableFutureCallAdapterFactory.INSTANCE(将Call转换为CompletableFuture)。

2.默认的ConverterFactory。通过defaultConverterFactories方法,只有OptionalConverterFactory.INSTANCE(将结果转换成Optional)。

3.默认的callbackExecutor。通过defaultCallbackExecutor,这里只有Android平台会提供一个MainThreadExecutor。

我们只分析Android平台。可以认为,Platform提供了Builder进行初始化时四个管家其中三个的默认配置,即converterFactories、callAdapterFactories、callbackExecutor的默认配置(CompletableFutureCallAdapterFactory和OptionalConverterFactory基本上是用不上,此处忽略)。

看看Builder的build方法,是如何完成Retrofit的创建工作:

    public Retrofit build() {if (baseUrl == null) {throw new IllegalStateException("Base URL required.");}okhttp3.Call.Factory callFactory = this.callFactory;if (callFactory == null) {callFactory = new OkHttpClient();}Executor callbackExecutor = this.callbackExecutor;if (callbackExecutor == null) {callbackExecutor = platform.defaultCallbackExecutor();}// Make a defensive copy of the adapters and add the default Call adapter.List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));// Make a defensive copy of the converters.List<Converter.Factory> converterFactories =new ArrayList<>(1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());// Add the built-in converter factory first. This prevents overriding its behavior but also// ensures correct behavior when using converters that consume all types.converterFactories.add(new BuiltInConverters());converterFactories.addAll(this.converterFactories);converterFactories.addAll(platform.defaultConverterFactories());return new Retrofit(callFactory,baseUrl,unmodifiableList(converterFactories),unmodifiableList(callAdapterFactories),callbackExecutor,validateEagerly);}

build方法非常清晰,将Platform提供的用户的配置和Platform提供的默认支持加入,完成Retrofit的创建。

如果没有设置callFactory,那么久创建一个OkHttpClient对象。BuiltInConverters提供了对接口返回值为Void、Unit的支持,以及对注解为Streaming的支持。

2.报菜员就位:Retrofit.create

这一部分是接口实例化工作。

这里分析下面这行代码的源码

val service = retrofit.create(MainService::class.java)
  public <T> T create(final Class<T> service) {// 如果validateEagerly为true,这里直接对service进行解析和初始化,否则将在第一次运行时进行初始化validateServiceInterface(service);return (T)Proxy.newProxyInstance(service.getClassLoader(),new Class<?>[] {service},new InvocationHandler() {private final Platform platform = Platform.get();private final Object[] emptyArgs = new Object[0];@Overridepublic @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable {// 如果是Object的方法,则直接调用这个方法。这里限制只能是接口// If the method is a method from Object then defer to normal invocation.if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);}// 如果是接口默认方法,则调用默认方法,如果不是则调用loadServiceMethod(method).invokeargs = args != null ? args : emptyArgs;return platform.isDefaultMethod(method)? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);}});}

源码中使用了Java提供的动态代理的方式对接口进行实例化。

这里简单讲一下,当用户使用service这个对象调用接口方法时,会被分发到InvocationHandler,并调用起invoke方法,传入这个方法的method和入参args。

Method是Java反射包中的一个类,通过method,我们可以轻松获取到这个方法的注解和入参,由此便能实现将接口转换成网络请求。这是Retrofit核心,理解了这里,再使用Retrofit,便会觉得豁然开朗。

loadServiceMethod(method).invoke(args)这句是重点,分开来分析,先loadServiceMethod,再分析invoke(ServiceMethod的方法)。

3.分配主厨:loadServiceMethod

这一步正是Retrofit偷梁换柱的动作,将注解(菜单)转化成各种对象(菜品),并分配主厨上场。

  ServiceMethod<?> loadServiceMethod(Method method) {// 先从缓存中查找ServiceMethod<?> result = serviceMethodCache.get(method);if (result != null) return result;synchronized (serviceMethodCache) {result = serviceMethodCache.get(method);if (result == null) {result = ServiceMethod.parseAnnotations(this, method);serviceMethodCache.put(method, result);}}return result;}

先从缓存中查找,如果没有便通过ServiceMethod.parseAnnotations(this, method)获取到ServiceMethod,放入缓存,并返回。

abstract class ServiceMethod<T> {static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);Type returnType = method.getGenericReturnType();if (Utils.hasUnresolvableType(returnType)) {// 方法返回值不能包含类型变量(泛型)或者通配符throw methodError(method,"Method return type must not include a type variable or wildcard: %s",returnType);}if (returnType == void.class) {throw methodError(method, "Service methods cannot return void.");}return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);}abstract @Nullable T invoke(Object[] args);
}

首先创建了requestFactory,将requestFactory传递给厨师HttpServiceMethod,然后返回。

有请主厨HttpServiceMethod闪亮登场,只见他左边张小龙(requestFactory),右边赵小虎(responseConverter),前方展小昭(callFactory)。

private final RequestFactory requestFactory;
private final okhttp3.Call.Factory callFactory;
private final Converter<ResponseBody, ResponseT> responseConverter;HttpServiceMethod(RequestFactory requestFactory,okhttp3.Call.Factory callFactory,Converter<ResponseBody, ResponseT> responseConverter) {this.requestFactory = requestFactory;this.callFactory = callFactory;this.responseConverter = responseConverter;
}

在这里,requestFactory成为了主厨的帮手。

下面看看RequestFactory有什么能力来帮助主厨。

先来看看RequestFactory.parseAnnotations方法和RequestFactory有哪些成员变量:

final class RequestFactory {static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {return new Builder(retrofit, method).build();}private final Method method;private final HttpUrl baseUrl;final String httpMethod;// 相对url,例如注解@GET后面的值private final @Nullable String relativeUrl;// 注解Headers解析,格式为 Name: Valueprivate final @Nullable Headers headers;// 注解Header中Content-Type的值private final @Nullable MediaType contentType;// 是否有bodyprivate final boolean hasBody;// 是否是FormEncodedprivate final boolean isFormEncoded;// 是否是Multipartprivate final boolean isMultipart;// 方法的每个入参都有一个对应的parameterHandlersprivate final ParameterHandler<?>[] parameterHandlers;// 是否是kotlin的suspend方法final boolean isKotlinSuspendFunction;// 省略以下内容
}

RequestFactory依旧是工厂,但不是抽象工厂,这个工厂是Request工厂,就是创建用来创建okhttp3.Request的工厂。除了创建Request,这里对方法的注解、入参、入参注解进行了解析。

    Builder(Retrofit retrofit, Method method) {this.retrofit = retrofit;this.method = method;// 获取方法注解,返回值为Annotation[]this.methodAnnotations = method.getAnnotations();// 获取方法参数类型,返回值为Type[]this.parameterTypes = method.getGenericParameterTypes();// 获取参数中的注解,返回值为Annotation[][]this.parameterAnnotationsArray = method.getParameterAnnotations();}

这里的parseAnnotations,依旧是建造者模式来创建。

Builder.build方法对RequestFactory进行创建:

    RequestFactory build() {// 解析方法注解,例如GET、POST、HTTP、Multipart、FormUrlEncoded等等for (Annotation annotation : methodAnnotations) {parseMethodAnnotation(annotation);}// 略过错误检查代码...// 解析每个入参和其注解,生成对应的ParameterHandlerint parameterCount = parameterAnnotationsArray.length;parameterHandlers = new ParameterHandler<?>[parameterCount];for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {parameterHandlers[p] =parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);}// 略过错误检查代码...return new RequestFactory(this);}

说一下这里的ParameterHandler,下方是ParameterHandler的源码,这是是一个用于处理请求参数的抽象类,内部只有一个抽象方法apply。在后面创建Request请求时(RequestBuilder),按顺序执行ParameterHandler列表中每个ParameterHandler的apply方法,对RequestBuilder进行处理。这又是一个巧妙使用设计模式的地方,这里用到责任链模式和策略模式。

abstract class ParameterHandler<T> {abstract void apply(RequestBuilder builder, @Nullable T value) throws IOException;// 忽略其他
}

责任链模式:ParameterHandler的实现类可以形成一个责任链,每个处理器负责处理特定类型的参数,如果当前处理器无法处理参数,则将参数传递给下一个处理器。这样可以很好地组织和管理参数的处理流程。

策略模式:ParameterHandler可以根据不同的参数类型采用不同的处理策略,每个实现类都代表一种具体的处理策略,通过接口的多态性可以灵活地切换处理策略。

这种设计模式使得Retrofit可以动态地处理各种类型的请求参数,并且方便扩展和维护。

RequestFactory源码中可以看到久违的Retrofit的注解,这里开始完成对注解和参数的解析。如果对这些注解处理感兴趣,可以看这个类。

我们再来分析分析HttpServiceMethod的parseAnnotations方法。

HttpServiceMethod的parseAnnotations方法,从名字可以看出,仍然是对注解的解析

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;boolean continuationWantsResponse = false;boolean continuationBodyNullable = false;Annotation[] annotations = method.getAnnotations();Type adapterType;if (isKotlinSuspendFunction) {// ...//  暂时不分析对kotlin挂起函数的支持} else {adapterType = method.getGenericReturnType();}// 从callAdapterFactories中获取到合适的CallAdapter,对Call进行转换CallAdapter<ResponseT, ReturnT> callAdapter =createCallAdapter(retrofit, method, adapterType, annotations);Type responseType = callAdapter.responseType();// 忽略部分检查错误的代码// 从converterFactories中获取到合适的responseConverterConverter<ResponseBody, ResponseT> responseConverter =createResponseConverter(retrofit, method, responseType);okhttp3.Call.Factory callFactory = retrofit.callFactory;if (!isKotlinSuspendFunction) {return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);} else {// ...// 暂时不分析对kotlin挂起函数的支持}
}

暂时不分析kotlin挂起函数,先看普通函数的处理。拿到requestFactory、合适的callAdapter、合适的responseConverter,组成了新的CallAdapted,CallAdapted是HttpServiceMethod的一个子类。

  static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {private final CallAdapter<ResponseT, ReturnT> callAdapter;CallAdapted(RequestFactory requestFactory,okhttp3.Call.Factory callFactory,Converter<ResponseBody, ResponseT> responseConverter,CallAdapter<ResponseT, ReturnT> callAdapter) {super(requestFactory, callFactory, responseConverter);this.callAdapter = callAdapter;}@Overrideprotected ReturnT adapt(Call<ResponseT> call, Object[] args) {return callAdapter.adapt(call);}}

主厨CallAdapted上场了。

loadServiceMethod方法,最终得到的就是这个CallAdapted,它也有着自己的四个厨娘的调用权:requestFactory(功能:对请求的解析和创建)、callFactory(默认就是OkhttpClient)、responseConverter和callAdapter。

4.工作部署:invoke

HttpServiceMethod.invoke一调用,主厨开始进行最后的部署工作,并分配了一个部署指挥官。主厨将三个帮手的指挥权传递给部署指挥官,交由他完成下一步工作。

@Override
final @Nullable ReturnT invoke(Object[] args) {Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);return adapt(call, args);
}

invoke方法只有两行,创建一个OkHttpCall对象,执行adapt(call, args)。

先要说明一点,这里的OkHttpCall是Retrofit包中的一个类,并不是OkHttp包中的,但这个类封装了对OKHttp中Call请求的创建和处理。

看看OkHttpCall的构造函数和它的成员变量,有requestFactory、callFactory,、responseConverter,通过这几个,它的能力便可以想象得到。

再回到adapter方法的分析,loadServiceMethod返回的CallAdapted对adapt进行了重写,返回了callAdapter.adapt(call)。

来看看默认的CallAdapterFactory和其提供的Adapter:

final class DefaultCallAdapterFactory extends CallAdapter.Factory {@Nullableprivate final Executor callbackExecutor;DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {this.callbackExecutor = callbackExecutor;}@Nullablepublic CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {if (getRawType(returnType) != Call.class) {return null;} else if (!(returnType instanceof ParameterizedType)) {throw new IllegalArgumentException("Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");} else {// 如果没有使用SkipCallbackExecutor注解,那就返回ExecutorCallbackCallfinal Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType)returnType);final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class) ? null : this.callbackExecutor;return new CallAdapter<Object, Call<?>>() {public Type responseType() {return responseType;}public Call<Object> adapt(Call<Object> call) {return (Call)(executor == null ? call : new ExecutorCallbackCall(executor, call));}};}}static final class ExecutorCallbackCall<T> implements Call<T> {final Executor callbackExecutor;final Call<T> delegate;ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {this.callbackExecutor = callbackExecutor;this.delegate = delegate;}public void enqueue(final Callback<T> callback) {Objects.requireNonNull(callback, "callback == null");this.delegate.enqueue(new Callback<T>() {public void onResponse(Call<T> call, Response<T> response) {ExecutorCallbackCall.this.callbackExecutor.execute(() -> {if (ExecutorCallbackCall.this.delegate.isCanceled()) {callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));} else {callback.onResponse(ExecutorCallbackCall.this, response);}});}public void onFailure(Call<T> call, Throwable t) {ExecutorCallbackCall.this.callbackExecutor.execute(() -> {callback.onFailure(ExecutorCallbackCall.this, t);});}});}public boolean isExecuted() {return this.delegate.isExecuted();}public Response<T> execute() throws IOException {return this.delegate.execute();}public void cancel() {this.delegate.cancel();}public boolean isCanceled() {return this.delegate.isCanceled();}public Call<T> clone() {return new ExecutorCallbackCall(this.callbackExecutor, this.delegate.clone());}public Request request() {return this.delegate.request();}public Timeout timeout() {return this.delegate.timeout();}}
}

这里返回了ExecutorCallbackCall,对传入的OkHttpCall对象做了一层简单的封装,enqueue方法将回调函数运行在主线程中。可以认为这里使用了代理模式。

这里简单理解为部署完成,最后部署指挥官便是ExecutorCallbackCall。

5.做菜了:enqueue

当用户要菜时(发起请求时),部署指挥官开始指挥工作,稍后便将美食制作完成:

service.paperList(0)
.enqueue(object : Callback<MainPaperList> {override fun onResponse(call: Call<MainPaperList>,response: Response<MainPaperList>) {// 请求成功}override fun onFailure(call: Call<MainPaperList>, t: Throwable) {// 请求异常}
})

部署指挥官的工作就是指挥,底层工作仍是别人执行:

分析便止于此。

我想以容易理解的方式进行源码解读,但是我给出的一个餐厅模型对于源码中部分逻辑仍然难以适配,因此只做入门理解使用。

因为能力有限,以上分析会有部分理解错误之处,希望各位提出。

五.总结

Retrofit作为一个轻量的网络请求封装库,却有着丰富和优雅的设计。这是非常值得大家分析学习的。

在分析源码的过程中,下面几点会更好地帮助到你:

1.理解类名

优秀的框架通过类名便可以看出这个类功能的一二。

比如XXFactory,一般都是工厂模式,对应会有一个create方法区创建一个XX。

XXAdapter和XXConvert,名字更是直接表明是一个适配器或者转换类。

2.看看类的成员变量,判断类的能力

面向对象的一大特征便是封装。类的成员变量,便是类所封装和承载的信息。对于不熟悉的类,不妨先看看他的成员变量有哪些,看看他有什么能力。

3.阅读注释

有时候看注释也会不理解,但不妨碍注释给我的启示作用。

推荐一个免费的IDEA插件Translation,助你轻松应对英文注释。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/669152.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣热门100题刷题笔记 - 10. 正则表达式匹配

力扣热门100题 - 10. 正则表达式匹配 题目链接&#xff1a;10. 正则表达式匹配 题目描述&#xff1a; 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 . 和 * 的正则表达式匹配。 . 匹配任意单个字符 * 匹配零个或多个前面的那一个元素 所谓匹配&#xff…

asqlcell,一个超强的 Python 库!

前言 大家好&#xff0c;今天为大家分享一个超强的 Python 库 - asqlcell。 Github地址&#xff1a;https://github.com/datarho/asqlcell Python asqlcell 是一个用于执行异步数据库操作的开源库&#xff0c;它允许开发者通过异步的方式与数据库进行交互&#xff0c;提高了数…

如何使用VS Code编写小游戏并实现公网游玩本地游戏【内网穿透】

文章目录 前言1. 编写MENJA小游戏2. 安装cpolar内网穿透3. 配置MENJA小游戏公网访问地址4. 实现公网访问MENJA小游戏5. 固定MENJA小游戏公网地址 前言 本篇教程&#xff0c;我们将通过VS Code实现远程开发MENJA小游戏&#xff0c;并通过cpolar内网穿透发布到公网&#xff0c;分…

七月论文审稿GPT第2.5版:微调GPT3.5 turbo 16K和llama2 13B以扩大对GPT4的优势

前言 自去年7月份我带队成立大模型项目团队以来&#xff0c;我司至今已有5个项目组&#xff0c;其中 第一个项目组的AIGC模特生成系统已经上线在七月官网第二项目组的论文审稿GPT则将在今年3 4月份对外上线发布第三项目组的RAG知识库问答第1版则在春节之前已就绪至于第四、第…

ai平滑工具的使用方法

ai中想要使用平滑工具来处理线条&#xff0c;该怎么是哦用哪个恩&#xff1f;下面我们就来看看详细的教程。 1、我们通过一个例子演示平滑工具的使用&#xff0c;先新建文件&#xff0c;在左侧工具箱中找到平滑工具。 文章源自四五设计网-https://www.45te.com/39726.html 2、…

CSS:三列布局

三列布局是指左右两列定宽&#xff0c;中间自适应。最终效果如下&#xff1a; HTML&#xff1a; <div class"container"><div class"left"></div><div class"center"></div><div class"right">…

001集:open语句打开文件及文件类型(二进制、文本文件)详解——vba

open用法可以用来打开文件、文件夹或网页&#xff0c;也可以用来运行某一应用程序、文件或网页。一般来说&#xff0c;只要在开始菜单中可以找到某个应用程序&#xff0c;我们就可以使用open命令打开该应用程序;另外&#xff0c;在打开某个文件或网页时&#xff0c;也可以使用o…

设计模式学习笔记05(小滴课堂)

讲解Adapeter设计模式和应用场景 接口的适配器案例实战 代码&#xff1a; 定义一个接口&#xff1a; 编写适配器&#xff1a; 写我们的商品类&#xff1a; 会员类&#xff1a; 这样我们不同的需求可以根据需要去实现不同的接口方法&#xff0c;而不用实现全部接口方法。 适配…

Qt案例 在对QGraphicsView视图修改和撤销修改图元操作时,使用命令模式实现。

当项目中有QGraphicsView视图直接修改图元的功能时&#xff0c;常会有CtriZ和CtrlY这种执行与撤销图元修改的功能&#xff0c;以便于在修改图元后能够进行一个还原/执行操作&#xff0c;此时就适合使用命令模式设计来实现这个功能。 以下示例在WINDOWS系统&#xff0c;Qt Creat…

echarts step line

https://ppchart.com/#/ <template><div class"c-box" ref"jsEchart"></div> </template><script> import * as $echarts from echarts // 事件处理函数 export default {props: {// 需要传递的数据data: {type: Array,defa…

字符串匹配算法(z函数模版)来自灵神。

一个字符串s求出s的z[i]&#xff0c;z[i]表示以s[i:n]这一段和s[0:n]的从前往后的连续相等字母个数。 比如 abacaba,z[2] (acaba与abacaba比较) 1。

SpringBoot整合Flowable最新教程(一)Flowable介绍

一、Flowable 入门介绍 代码实现文章&#xff1a;SpringBoot整合Flowable最新教程&#xff08;二&#xff09; 官网地址&#xff1a;https://www.flowable.org/   Flowable6.3中文教程&#xff1a;中文教程地址   可以在官网下载对应的jar包在本地部署运行&#xff0c;官方…

【IC设计】Windows下基于IDEA的Chisel环境安装教程(图文并茂)

Chisel环境安装教程 第一步 安装jdk&#xff0c;配置环境变量第二步 安装sbt&#xff0c;不用配置环境变量第三步 安装idea社区版第四步 离线安装scala的idea插件第五步 配置sbt换源1.切换目录2.创建repositories文件3.配置sbtconfig.txt文件 第六步 使用chisel-tutorial工程运…

CISCRISC? CPU架构有哪些? x86 ARM?

编者按&#xff1a;鉴于笔者水平有限&#xff0c;文中难免有不当之处&#xff0c;还请各位读者海涵。 是为序 我猜&#xff0c;常年混迹CSDN的同学应该不会没听说过CPU吧&#xff1f; 但你真的了解CPU吗&#xff1f;那笔者问你CPU有哪些架构呢&#xff1f; 如果你对你的答案…

FCIS 2023:洞悉网络安全新态势,引领创新防护未来

随着网络技术的飞速发展&#xff0c;网络安全问题日益凸显&#xff0c;成为全球共同关注的焦点。在这样的背景下&#xff0c;FCIS 2023网络安全创新大会应运而生&#xff0c;旨在汇聚业界精英&#xff0c;共同探讨网络安全领域的最新动态、创新技术和解决方案。 本文将从大会的…

JVM 性能调优 - Java 中的四种引用(4)

为什么会有四种引用 我们先回顾下在 Java 虚拟机内存体系(1) 中提到了的垃圾回收算法 1、引用计数法 原理:给对象添加一个引用计数器,每当有一个地方引用它,计数器的值就加一。每当有一个引用失效,计数器的值就减一。当计数器值为零时,这个对象被认为没有其他对象引用,…

JDK和Spring的SPI机制原理分析

目录 一、JDK 二、Spring框架介绍 三、SPI机制原理 一、JDK JDK是Java Development Kit的缩写&#xff0c;是Java开发工具包的意思。它是用于开发Java应用程序和运行Java程序的软件包。JDK包含了Java编译器&#xff08;javac&#xff09;和Java虚拟机&#xff08;JVM&#…

【快速上手QT】01-QWidgetQMainWindow QT中的窗口

总所周知&#xff0c;QT是一个跨平台的C图形用户界面应用程序开发框架。它既可以开发GUI程序&#xff0c;也可用于开发非GUI程序&#xff0c;当然我们用到QT就是要做GUI的&#xff0c;所以我们快速上手QT的第一篇博文就讲QT的界面窗口。 我用的IDE是VS2019&#xff0c;使用QTc…

【NodeJS】005- MongoDB数据库

1.简介 1.1 Mongodb 是什么 MongoDB 是一个基于分布式文件存储的数据库&#xff0c;官方地址 https://www.mongodb.com/ 1.2 数据库是什么 数据库&#xff08;DataBase&#xff09;是按照数据结构来组织、存储和管理数据的 应用程序 1.3 数据库的作用 数据库的主要作用就是…

Python实现排序算法

目录 一&#xff1a;快速排序 二&#xff1a;合并排序 三&#xff1a;冒泡排序 四&#xff1a;插入排序 五&#xff1a;选择排序 一&#xff1a;快速排序 def quicksort(arr): if len(arr) < 1: return arr pivot arr[len(arr) // 2] le…