流轉json專題及常見問題 - CSDN

2020-12-26 CSDN技術社區

引言

前端傳入的 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/85163590


Kotlin開發者社區

專注分享 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/

相關焦點

  • Python爬取CSDN 2020 博客之星實時數據排名
    完整代碼:import jsonimport requestsfrom datetime import datetimefrom pyecharts.components import Tablefrom pyecharts.options import ComponentTitleOpts
  • jquery 三級聯動插件專題及常見問題 - CSDN
    在實際開發中,我們的函數參數可以是ajax獲取的json對象,按照我們的格式來處理,傳入,就可以實現我們的三級聯動下拉了。是不是很棒。如果覺得不錯,給了贊吧^_^。同時如果有小夥伴覺得我的代碼可以優化的,歡迎指出。
  • python獲取網頁json內容 - CSDN
    寫爬蟲的過程中不免遇到處理json數據的情況,今天在爬取新華網新聞數據時發現使用json.loads函數時報錯:json.decoder.JSONDecodeError: Expecting value
  • 方差檢驗專題及常見問題 - CSDN
    對於兩組服從正態分布的定量數據的平均數差異的檢驗均可以採用t檢驗,常見的t檢驗有單樣本t檢驗、獨立樣本t檢驗和配對樣本t檢驗。1.單樣本T檢驗單樣本t檢驗是指對樣本平均數與總體平均數的差異進行的顯著性檢驗。即檢驗單個變量的均值是否與給定的常數之間存在差異。
  • android camera 沒圖像專題及常見問題 - CSDN
    null) { Camera.Parameters parameters = mCamera.getParameters(); parameters.setPictureFormat(PixelFormat.JPEG);//設置照片拍攝後的保存格式 mCamera.setDisplayOrientation(90);//否則方向會有問題
  • windows10卡啟動修復專題及常見問題 - CSDN
    在Windows 10上,安全模式允許加載一組基本功能和通用設備驅動程序,足以解決常見的軟體和硬體問題。例如,當計算機無法正常啟動、網絡連接問題以及應用程式或Windows Update無法下載更新時,可以使用安全模式對其進行故障排除。
  • it運維滿意度專題及常見問題 - CSDN
    企業需要發展,發展離不開我們的用戶,用戶滿意度為導向能時刻提醒我們怎麼做好運維服務工作,做好了運維服務工作,才能發展;要做好運維服務工作就得圍繞用戶滿意度,推進「紮根」工作,「紮根」就是服務更加貼近用戶實際,急用戶所急,想用戶所想,加強主動服務,幫用戶解決實際工作中的困難與問題,讓用戶「放心、舒心、開心」。紮根是行動,結果是成長,讓運維始於「紮根」,長於「大樹」。
  • 垂直行業雲計算專題及常見問題 - CSDN
    北京某三甲醫院用的就是這樣的一個服務,解決了安全問題。至於計算能力和存儲能力,這是我們雲廠商的強項,所以當把安全的問題解決之後,我們可以幫他們很大的忙,隨時擴充計算資源問:針對服務性能,從醫院的操作來說,也就是將從科研與臨床方面逐步實現上雲?
  • java 信號與槽專題及常見問題 - CSDN
    QT信號/槽在我的理解中,QT和Android都是類似的開發框架,都是由開發團隊封裝了各式各樣的接口和數據結構.將一些問題的解決方法簡單化 比如QT中將線程封裝為QThread,派生類通過重寫run方法來將代碼投入到新的線程執行,而同樣的Android中的線程是Java自帶的Thread
  • c定時 linux專題及常見問題 - CSDN
    如果由於權限問題無法完成拷貝,可以用:$ crontab <filename>其中,<filename>是你在$ H O M E目錄中副本的文件名。我建議你在自己的$ H O M E目錄中保存一個該文件的副本。我就有過類似的經歷,有數次誤刪了crontab文件(因為r鍵緊挨在e鍵的右邊)。
  • datatype在python專題及常見問題 - CSDN
  • 梯度檢驗專題及常見問題 - CSDN
  • android 虛擬機版本專題及常見問題 - CSDN
    為了解決這個問題,Google在2.2版本添加了JIT編譯器,當App運行時,每當遇到一個新類,JIT編譯器就會對這個類進行編譯,經過編譯後的代碼,會被優化成相當精簡的原生型指令碼(即native code),這樣在下次執行到相同邏輯的時候,速度就會更快。當然使用JIT也不一定加快執行速度,如果大部分代碼的執行次數很少,那麼編譯花費的時間不一定少於執行dex的時間。
  • redis 虛擬槽分區專題及常見問題 - CSDN
    數據分布分布式資料庫首先要解決把整個數據集按照分區規則映射到多個節點的問題,即把數據集劃分到多個節點上,每個節點負責整體數據的一個子集。數據分布有兩種方式1 順序分區順序分布就是把一整塊數據分散到很多機器中,如下圖所示。
  • cdn邊緣計算專題及常見問題 - CSDN
    您必須說,速度足夠快,而且我從未遇到任何問題! 你猜怎麼了? 他們已經在網絡中處於邊緣並且離您更近了,簡而言之,他們正在使用Content Delivery Network(CDN )。 CDN如何運作? (How does CDN work?)
  • python卡方分析專題及常見問題 - CSDN
    公司製造出10臺引擎供測試使用,每一臺的排放水平如下:15.6 16.2 22.5 20.5 16.4 19.4 16.6 17.9 12.7 13.9問題:公司生產的引擎是否符合政府規定呢?1.推論統計分析2.1 問題是什麼?問題是: 樣本數據是否滿足總體排放值要求?
  • spss 方法 線性回歸專題及常見問題 - CSDN
    原始數據本資訊原始數據「百度網盤」下載連結:https://pan.baidu.com/s/10KvLh3ZMGb8z2n-lXwWvcg 提取碼:fx02問題反饋如您遇到有關統計學的問題,可以通過郵件聯繫我們。
  • 雲存儲的基本實現方案專題及常見問題 - CSDN
    甚至有些公用的代碼在不同項目中存在多份拷貝,修改一份公有代碼後其他的卻忘了同步,出問題不好排查,復用率低下。2.3 安全性問題愈加嚴重業務做大了以後容易招致DDoS、惡意刷單、惡意滲透、用戶隱私洩露等安全風險。在新網絡安全法草案的規約中,對電商企業提供服務的安全性也提出了明確的標準。
  • windows 檢查php版本專題及常見問題 - CSDN
    目的:解決用戶在windows環境下運行ngoos2.0的問題。解決存在的問題業務後臺報404錯誤(a)、工程下面的_.htaccess文件名改成.htaccess文件名。
  • linux 桌面無法啟動專題及常見問題 - CSDN
    歡迎提出問題和建議。via: https://itsfoss.com/add-multiple-time-zones-ubuntu/作者:Abhishek Prakash 選題:lujun9972 譯者:geekpi 校對:wxy本文由 LCTT 原創編譯,Linux中國 榮譽推出