前端傳入的 json 數據如何被解析成 Java 對象作為 API入參,API 返回結果又如何將 Java 對象解析成 json 格式數據返回給前端,其實在整個數據流轉過程中,HttpMessageConverter 起到了重要作用;另外在轉換的過程我們可以加入哪些定製化內容?
核心邏輯代碼:
// 對於String類型的,直接 append 返回,不轉json if ("java.lang.String".equals(type.getTypeName())) { try { // 1.先解析json對象,如果不是json對象的,走catch邏輯 Object jsonObject = JSON.parse(value.toString()); objectWriter.writeValue(generator, jsonObject); } catch (Throwable e) { log.error("OvsHttpMessageConverter writeInternal,JSON.parse(value.toString()) = {}", value, e); // 2.不是json對象的,就原樣輸出string objectWriter.writeValue(generator, value); } }
自定義的 HttpMessageConverter :
package com.alibaba.ovs.operationcenter.config;import com.alibaba.fastjson.JSON;import com.fasterxml.jackson.core.JsonEncoding;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.core.util.DefaultIndenter;import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;import com.fasterxml.jackson.databind.*;import com.fasterxml.jackson.databind.ser.FilterProvider;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpOutputMessage;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageNotWritableException;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.http.converter.json.MappingJacksonValue;import org.springframework.util.TypeUtils;import java.io.IOException;import java.lang.reflect.Type;/** * @author: Jack * 2020-08-24 23:10 */@Slf4jpublic class OvsHttpMessageConverter extends MappingJackson2HttpMessageConverter { private static final MediaType TEXT_EVENT_STREAM = new MediaType("text", "event-stream"); public OvsHttpMessageConverter() { } public OvsHttpMessageConverter(ObjectMapper objectMapper) { super(objectMapper); } @Override protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { log.info("OvsHttpMessageConverter writeInternal,type={},object={}", type.getTypeName(), JSON.toJSONString(object)); MediaType contentType = outputMessage.getHeaders().getContentType(); JsonEncoding encoding = getJsonEncoding(contentType); JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding); try { Object value = object; Class<?> serializationView = null; FilterProvider filters = null; JavaType javaType = null; if (object instanceof MappingJacksonValue) { MappingJacksonValue container = (MappingJacksonValue) object; value = container.getValue(); serializationView = container.getSerializationView(); filters = container.getFilters(); } ObjectWriter objectWriter = (serializationView != null ? this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer()); // 對於String類型的,直接 append 返回,不轉json if ("java.lang.String".equals(type.getTypeName())) { try { // 1.先解析json對象,如果不是json對象的,走catch邏輯 Object jsonObject = JSON.parse(value.toString()); objectWriter.writeValue(generator, jsonObject); } catch (Throwable e) { log.error("OvsHttpMessageConverter writeInternal,JSON.parse(value.toString()) = {}", value, e); // 2.不是json對象的,就原樣輸出string objectWriter.writeValue(generator, value); } } else { writePrefix(generator, object); if (type != null && value != null && TypeUtils.isAssignable(type, value.getClass())) { javaType = getJavaType(type, null); } if (filters != null) { objectWriter = objectWriter.with(filters); } if (javaType != null && javaType.isContainerType()) { objectWriter = objectWriter.forType(javaType); } SerializationConfig config = objectWriter.getConfig(); if (contentType != null && contentType.isCompatibleWith(TEXT_EVENT_STREAM) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) { DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:")); objectWriter = objectWriter.with(prettyPrinter); } objectWriter.writeValue(generator, value); writeSuffix(generator, object); } generator.flush(); } catch (JsonProcessingException ex) { throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex); } }}
WebMvcConfig代碼:
package com.alibaba.ovs.operationcenter.config;import com.fasterxml.jackson.databind.DeserializationFeature;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.module.SimpleModule;import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;import org.springframework.context.annotation.Configuration;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;import java.nio.charset.StandardCharsets;import java.util.ArrayList;import java.util.List;/** * @author: Jack * 2020-08-14 18:53 */@EnableWebMvc@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // MappingJackson2HttpMessageConverter httpMessageConverter = new MappingJackson2HttpMessageConverter(); OvsHttpMessageConverter httpMessageConverter = new OvsHttpMessageConverter(); ObjectMapper objectMapper = new ObjectMapper(); /** * 序列換成json時,將所有的long變成string, 因為js 中得 Number 數字類型不能包含所有的 java long 值 (js中會被截斷) * 參考文章: https://blog.csdn.net/universsky2015/article/details/108010953 */ SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(simpleModule); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); httpMessageConverter.setObjectMapper(objectMapper); // supportedMediaTypes List<MediaType> supportedMediaTypes = new ArrayList<>(httpMessageConverter.getSupportedMediaTypes()); supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); supportedMediaTypes.add(MediaType.TEXT_HTML); supportedMediaTypes.add(MediaType.TEXT_PLAIN); supportedMediaTypes.add(MediaType.TEXT_XML); supportedMediaTypes.add(new MediaType("application", "*+json", StandardCharsets.UTF_8)); httpMessageConverter.setSupportedMediaTypes(supportedMediaTypes); converters.add(httpMessageConverter); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // Spring Boot自動配置本身不會自動把/swagger-ui.html這個路徑映射到對應的目錄META-INF/resources/下面。 // 這個swagger-ui.html 相關的所有前端靜態文件都在springfox-swagger-ui.jar裡面。 registry.addResourceHandler("swagger-ui.html") .addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); } @Override public void addInterceptors(InterceptorRegistry registry) { // 多個攔截器組成一個攔截器鏈 // addPathPatterns 用於添加攔截規則 // excludePathPatterns 用戶排除攔截 //添加攔截器 registry.addInterceptor(new CheckpreloadInterceptor()); super.addInterceptors(registry); }}
HttpMessageConverter是什麼?
其中,
HttpMessageConverter 接口介紹
org.springframework.http.converter.HttpMessageConverter 是一個策略接口,接口說明如下:/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.http.converter;import java.io.IOException;import java.util.List;import org.springframework.http.HttpInputMessage;import org.springframework.http.HttpOutputMessage;import org.springframework.http.MediaType;/** * Strategy interface that specifies a converter that can convert from and to HTTP requests and responses. * * @author Arjen Poutsma * @author Juergen Hoeller * @since 3.0 */public interface HttpMessageConverter<T> { /** * Indicates whether the given class can be read by this converter. * @param clazz the class to test for readability * @param mediaType the media type to read (can be {@code null} if not specified); * typically the value of a {@code Content-Type} header. * @return {@code true} if readable; {@code false} otherwise */ boolean canRead(Class<?> clazz, MediaType mediaType); /** * Indicates whether the given class can be written by this converter. * @param clazz the class to test for writability * @param mediaType the media type to write (can be {@code null} if not specified); * typically the value of an {@code Accept} header. * @return {@code true} if writable; {@code false} otherwise */ boolean canWrite(Class<?> clazz, MediaType mediaType); /** * Return the list of {@link MediaType} objects supported by this converter. * @return the list of supported media types */ List<MediaType> getSupportedMediaTypes(); /** * Read an object of the given type from the given input message, and returns it. * @param clazz the type of object to return. This type must have previously been passed to the * {@link #canRead canRead} method of this interface, which must have returned {@code true}. * @param inputMessage the HTTP input message to read from * @return the converted object * @throws IOException in case of I/O errors * @throws HttpMessageNotReadableException in case of conversion errors */ T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; /** * Write an given object to the given output message. * @param t the object to write to the output message. The type of this object must have previously been * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}. * @param contentType the content type to use when writing. May be {@code null} to indicate that the * default content type of the converter must be used. If not {@code null}, this media type must have * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have * returned {@code true}. * @param outputMessage the message to write to * @throws IOException in case of I/O errors * @throws HttpMessageNotWritableException in case of conversion errors */ void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;}
Strategy interface that specifies a converter that can convert from and to HTTP requests and responses. 簡單說就是 HTTP request (請求)和response (響應)的轉換器。該接口有隻有5個方法,簡單來說就是獲取支持的 MediaType(application/json之類),接收到請求時判斷是否能讀(canRead),能讀則讀(read);返回結果時判斷是否能寫(canWrite),能寫則寫(write)。
Spring Boot 預設配置
我們寫 Demo 沒有配置任何 MessageConverter,但是數據前後傳遞依舊好用,是因為 SpringMVC 啟動時會自動配置一些HttpMessageConverter,在 WebMvcConfigurationSupport 類中添加了預設 MessageConverter:
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) { StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); stringConverter.setWriteAcceptCharset(false); messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(stringConverter); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new SourceHttpMessageConverter<Source>()); messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { messageConverters.add(new AtomFeedHttpMessageConverter()); messageConverters.add(new RssChannelHttpMessageConverter()); } if (jackson2XmlPresent) { ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build(); messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper)); } else if (jaxb2Present) { messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); } if (jackson2Present) { ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build(); messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper)); } else if (gsonPresent) { messageConverters.add(new GsonHttpMessageConverter()); } }
我們看到很熟悉的 MappingJackson2HttpMessageConverter ,如果我們引入 jackson 相關包,Spring 就會為我們添加該 MessageConverter,但是我們通常在搭建框架的時候還是會手動添加配置 MappingJackson2HttpMessageConverter,為什麼? 先思考一下:
當我們配置了自己的 MessageConverter, SpringMVC 啟動過程就不會調用 addDefaultHttpMessageConverters 方法,且看下面代碼 if 條件,這樣做也是為了定製化我們自己的 MessageConverter
protected final List<HttpMessageConverter<?>> getMessageConverters() { if (this.messageConverters == null) { this.messageConverters = new ArrayList<HttpMessageConverter<?>>(); configureMessageConverters(this.messageConverters); if (this.messageConverters.isEmpty()) { addDefaultHttpMessageConverters(this.messageConverters); } extendMessageConverters(this.messageConverters); } return this.messageConverters; }
數據流轉解析
數據的請求和響應都要經過 DispatcherServlet 類的 doDispatch(HttpServletRequest request, HttpServletResponse response) 方法的處理
請求過程解析
看 doDispatch 方法中的關鍵代碼:
// 這裡的 Adapter 實際上是 RequestMappingHandlerAdapterHandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); if (!mappedHandler.applyPreHandle(processedRequest, response)) { return;}// 實際處理的handlermv = ha.handle(processedRequest, response, mappedHandler.getHandler()); mappedHandler.applyPostHandle(processedRequest, response, mv);
從進入handle之後我先將調用棧粘貼在此處,希望小夥伴可以按照調用棧路線動手跟蹤嘗試:
readWithMessageConverters:192, AbstractMessageConverterMethodArgumentResolver (org.springframework.web.servlet.mvc.method.annotation)readWithMessageConverters:150, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)resolveArgument:128, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)resolveArgument:121, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)getMethodArgumentValues:158, InvocableHandlerMethod (org.springframework.web.method.support)invokeForRequest:128, InvocableHandlerMethod (org.springframework.web.method.support)
// 下面的調用棧重點關注,處理請求和返回值的分叉口就在這裡
invokeAndHandle:97, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)invokeHandlerMethod:849, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)handleInternal:760, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)handle:85, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)doDispatch:967, DispatcherServlet (org.springframework.web.servlet)
這裡重點說明調用棧最頂層 readWithMessageConverters 方法中內容:
// 遍歷 messageConvertersfor (HttpMessageConverter<?> converter : this.messageConverters) { Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass(); // 上文類關係圖處要重點記住的地方,主要判斷 MappingJackson2HttpMessageConverter 是否是 GenericHttpMessageConverter 類型 if (converter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter; if (genericConverter.canRead(targetType, contextClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]"); } if (inputMessage.getBody() != null) { inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType); body = genericConverter.read(targetType, contextClass, inputMessage); body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType); } break; } } else if (targetClass != null) { if (converter.canRead(targetClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]"); } if (inputMessage.getBody() != null) { inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType); body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage); body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType); } break; } }}
然後就判斷是否canRead,能讀就read,最終走到下面代碼處將輸入的內容反序列化出來:
protected Object _readMapAndClose(JsonParser p0, JavaType valueType) throws IOException { try (JsonParser p = p0) { Object result; JsonToken t = _initForReading(p); if (t == JsonToken.VALUE_NULL) { // Ask JsonDeserializer what 'null value' to use: DeserializationContext ctxt = createDeserializationContext(p, getDeserializationConfig()); result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt); } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) { result = null; } else { DeserializationConfig cfg = getDeserializationConfig(); DeserializationContext ctxt = createDeserializationContext(p, cfg); JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType); if (cfg.useRootWrapping()) { result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser); } else { result = deser.deserialize(p, ctxt); } ctxt.checkUnresolvedObjectId(); } // Need to consume the token too p.clearCurrentToken(); return result; } }
到這裡從請求中解析參數過程就到此結束了,趁熱打鐵來看將響應結果返回給前端的過程
返回過程解析
在上面調用棧請求和返回結果分叉口處同樣處理返回的內容:
writeWithMessageConverters:224, AbstractMessageConverterMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)handleReturnValue:174, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)handleReturnValue:81, HandlerMethodReturnValueHandlerComposite (org.springframework.web.method.support)// 分叉口invokeAndHandle:113, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
重點關注調用棧頂層內容,是不是很熟悉的樣子,完全一樣的邏輯, 判斷是否能寫canWrite,能寫則write:
for (HttpMessageConverter<?> messageConverter : this.messageConverters) { if (messageConverter instanceof GenericHttpMessageConverter) { if (((GenericHttpMessageConverter) messageConverter).canWrite( declaredType, valueType, selectedMediaType)) { outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage); if (outputValue != null) { addContentDispositionHeader(inputMessage, outputMessage); ((GenericHttpMessageConverter) messageConverter).write( outputValue, declaredType, selectedMediaType, outputMessage); if (logger.isDebugEnabled()) { logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType + "\" using [" + messageConverter + "]"); } } return; } } else if (messageConverter.canWrite(valueType, selectedMediaType)) { outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage); if (outputValue != null) { addContentDispositionHeader(inputMessage, outputMessage); ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage); if (logger.isDebugEnabled()) { logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType + "\" using [" + messageConverter + "]"); } } return; }}
我們看到有這樣一行代碼:
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
我們在設計 RESTful API 接口的時候通常會將返回的數據封裝成統一格式,通常我們會實現 ResponseBodyAdvice<T> 接口來處理所有 API 的返回值,在真正 write 之前將數據進行統一的封裝
@RestControllerAdvice()public class CommonResultResponseAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof CommonResult) { return body; } return new CommonResult<Object>(body); }}
整個處理流程就是這樣。
將各種常用 HttpMessageConverter 支持的MediaType 和 JavaType 以及對應關係總結在此處:
參考資料:
https://www.jianshu.com/p/3e1de3d02dd8
https://blog.csdn.net/BryantLmm/article/details/85163590Kotlin開發者社區
專注分享 Java、 Kotlin、Spring/Spring Boot、MySQL、redis、neo4j、NoSQL、Android、JavaScript、React、Node、函數式編程、編程思想、"高可用,高性能,高實時"大型分布式系統架構設計主題。
High availability, high performance, high real-time large-scale distributed system architecture design。
分布式框架:Zookeeper、分布式中間件框架等
分布式存儲:GridFS、FastDFS、TFS、MemCache、redis等
分布式資料庫:Cobar、tddl、Amoeba、Mycat
雲計算、大數據、AI算法
虛擬化、雲原生技術
分布式計算框架:MapReduce、Hadoop、Storm、Flink等
分布式通信機制:Dubbo、RPC調用、共享遠程數據、消息隊列等
消息隊列MQ:Kafka、MetaQ,RocketMQ
怎樣打造高可用系統:基於硬體、軟體中間件、系統架構等一些典型方案的實現:HAProxy、基於Corosync+Pacemaker的高可用集群套件中間件系統
Mycat架構分布式演進
大數據Join背後的難題:數據、網絡、內存和計算能力的矛盾和調和
Java分布式系統中的高性能難題:AIO,NIO,Netty還是自己開發框架?
高性能事件派發機制:線程池模型、Disruptor模型等等。。。合抱之木,生於毫末;九層之臺,起於壘土;千裡之行,始於足下。不積跬步,無以至千裡;不積小流,無以成江河。
Kotlin 簡介
Kotlin是一門非研究性的語言,它是一門非常務實的工業級程式語言,它的使命就是幫助程式設計師們解決實際工程實踐中的問題。使用Kotlin 讓 Java程式設計師們的生活變得更好,Java中的那些空指針錯誤,浪費時間的冗長的樣板代碼,囉嗦的語法限制等等,在Kotlin中統統消失。Kotlin 簡單務實,語法簡潔而強大,安全且表達力強,極富生產力。
Java誕生於1995年,至今已有23年歷史。當前最新版本是 Java 9。在 JVM 生態不斷發展繁榮的過程中,也誕生了Scala、Groovy、Clojure 等兄弟語言。
Kotlin 也正是 JVM 家族中的優秀一員。Kotlin是一種現代語言(版本1.0於2016年2月發布)。它最初的目的是像Scala那樣,優化Java語言的缺陷,提供更加簡單實用的程式語言特性,並且解決了性能上的問題,比如編譯時間。 JetBrains在這些方面做得非常出色。
Kotlin語言的特性
用 Java 開發多年以後,能夠嘗試一些新的東西真是太棒了。如果您是 Java 開發人員,使用 Kotlin 將會非常自然流暢。如果你是一個Swift開發者,你將會感到似曾相識,比如可空性(Nullability)。 Kotlin語言的特性有:
1.簡潔
大幅減少樣板代碼量。
2.與Java的100%互操作性
Kotlin可以直接與Java類交互,反之亦然。這個特性使得我們可以直接重用我們的代碼庫,並將其遷移到 Kotlin中。由於Java的互操作性幾乎無處不在。我們可以直接訪問平臺API以及現有的代碼庫,同時仍然享受和使用 Kotlin 的所有強大的現代語言功能。
3.擴展函數
Kotlin 類似於 C# 和 Gosu, 它提供了為現有類提供新功能擴展的能力,而不必從該類繼承或使用任何類型的設計模式 (如裝飾器模式)。
4.函數式編程
Kotlin 語言一等支持函數式編程,就像Scala一樣。具備高階函數、Lambda 表達式等函數式基本特性。
5.默認和命名參數
在Kotlin中,您可以為函數中的參數設置一個默認值,並給每個參數一個名稱。這有助於編寫易讀的代碼。
6.強大的開發工具支持
而由於是JetBrains出品,我們擁有很棒的IDE支持。雖然Java到Kotlin的自動轉換並不是100% OK 的,但它確實是一個非常好的工具。使用 IDEA 的工具轉換Java代碼為 Kotlin 代碼時,可以輕鬆地重用60%-70%的結果代碼,而且修改成本很小。
Kotlin 除了簡潔強大的語法特性外,還有實用性非常強的API以及圍繞它構建的生態系統。例如:集合類 API、IO 擴展類、反射API 等。同時 Kotlin 社區也提供了豐富的文檔和大量的學習資料,還有在線REPL。
A modern programming language that makes developers happier. Open source forever
圖來自《Kotlin從入門到進階實戰》 (陳光劍,清華大學出版社)
圖來自《Kotlin從入門到進階實戰》 (陳光劍,清華大學出版社)
https://kotlinlang.org/