A bug triggered a contest between hair. I won an overwhelming victory. Can you keep your hair?

I met a very strange problem while debugging the company's project a few days ago. Today I'll study it here

Follow official account: Java architects alliance, update technical articles every day

scene

As a PDA interface is newly added to the company to query the historical parking flow data, I first check the qualified data from the database, and use the Stream stream Stream to circulate the list of flow data to process the circulating objects, and then put them to JSONObject. In fact, when putting the flow, I did not set the key of mMap, but each layer of the returned results during the test wrapped an mMap key for me, Here is part of the code

dayTraceParkPage = dayTraceParkService.selectByHashMap(queryMap,initPage);
List<DayTracePark> dayTraceParkList = dayTraceParkPage.getData();
if (null != dayTraceParkList && dayTraceParkList.size() > 0) {
   dayTraceParkList.stream().peek(e -> {
		
		JSONObject res = new JSONObject();
		// ...  	  Part of the code is omitted here 
		// 	
		res.put("traceParkTraceDay", e);
		jsonArray.add(res);
		}).collect(Collectors.toList());
		}
		
return AjaxUtils.printJson(new JSONResult<Object>(jsonArray,"The history flow table is not associated, and the on-site flow table is obtained successfully", true));	
		
Copy code
// Return result: I didn't set the mMap node
{
    "data": [
        {
            "mMap": {
                "traceParkTraceDay": {
                    // ...  Omit some data
                },
                "traceTimeView": "1 Month 9 days"
            }
        },
    ],
    "message": "The history flow table is not associated, and the on-site flow table is obtained successfully",
    "statusCode": 0,
    "success": true
}
Copy code

At first I thought it was jdk8 (daytraceparklist. Stream() PEEK) syntax, I switched to the ordinary enhanced for loop, but the result was the same. At first, I couldn't figure it out. Later, I found that it was the wrong package. My JSONObject was com alibaba. dubbo. Under common, in fact, I should use the bag under fastjson 😂), I shed tears when I knew the truth, but I decided to see why JSONObject under the Dubbo package automatically added an mMap node. Based on the above one, I wrote a simple main method to simulate it. The code is as follows:

// ps: I didn't create maven. I directly imported jars, dubbo-2.5.3 and gson-2.6.1
import com.alibaba.dubbo.common.json.JSONObject;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Test {
	public static void main(String[] args) {
		System.out.println(test());
	}

	private static String test() {
		JSONObject jsonObj = new JSONObject();
		
		jsonObj.put("name", "Tan Jingjie");
		Gson gson = new GsonBuilder().serializeNulls().create();
		return gson.toJson(jsonObj);
	}
}

Copy code
// Output result:
{"mMap":{"name":"Tan Jingjie"}}
Copy code

start

Because we first create a new JSONObject and then call its put method, we go back and have a look at its source code (I see an mMap here, and I doubt that the initial return value is this mMap)

// The essence of mMap is a HashMap. Private map < string, Object > mMap = new hashmap();
// That is, I put a value into jsonObj, which is equivalent to putting a value into the map
 public void put(String name, Object value)
  {
    this.mMap.put(name, value);
  }
Copy code

Then I called new GsonBuilder() serializeNulls(). Create(), new a GsonBuilder. Here I think we should call the Builder mode to create an object. In fact, we can also directly use the new Gson object to create a Gson. The reason for using GsonBuilder is that it can set various components to create special Gson objects. In fact, it is also recommended to use GsonBuilder. This point-to-point method is called chain call. Using the annotation @ Builder in Lombok can also generate a relatively complex Builder API

// new GsonBuilder().serializeNulls() only sets serializeNulls to true and directly returns a configurable Gson

// By default, Gson does not export the attribute with null value when serializing. After serializing nulls is configured, it will export the object with null value, which should be convenient for debugging
 public GsonBuilder serializeNulls()
  {
    this.serializeNulls = true;
    return this;
  }
Copy code

Continue calling create()

 public Gson create()
  {
    List factories = new ArrayList();
    // This is The factories are not the newly created factories, but the global variable private final list < typeadapterfactory > factories = new arraylist();
   // Pass the factories of GsonBuilder into the newly defined factories and give it to the newly created Gson object.
    factories.addAll(this.factories);
    // Using collections Reverse combined with certain methods can realize the descending sorting of list sets, but collections cannot be used directly Reverse (list) in descending order. I also wrote a demo below
    //
    Collections.reverse(factories); 
    factories.addAll(this.hierarchyFactories);
    addTypeAdaptersForDate(this.datePattern, this.dateStyle, this.timeStyle, factories);

    return new Gson(this.excluder, this.fieldNamingPolicy, this.instanceCreators, this.serializeNulls, this.complexMapKeySerialization, this.generateNonExecutableJson, this.escapeHtmlChars, this.prettyPrinting, this.lenient, this.serializeSpecialFloatingPointValues, this.longSerializationPolicy, factories);
  }

Copy code

After getting the instance object, let's take a look at gson toJson(jsonObj);

public String toJson(Object src)
  {
    if (src == null) {
    // If src is null, a JsonNull() object is returned
      return toJson(JsonNull.INSTANCE);
    }
    // Otherwise, the toJson() method with two input parameters is called to pass the src value and type
    // If any Object field is a generic type, but the Object itself should not be a generic type, you can use tojason (Object).
If the object is a generic type, use toJson(Object,Type)To replace
    return toJson(src, src.getClass());
  }

  public String toJson(Object src, Type typeOfSrc)
  {
    StringWriter writer = new StringWriter();
    toJson(src, typeOfSrc, writer);
    return writer.toString();
  }
  // continue
   public void toJson(Object src, Type typeOfSrc, Appendable writer)
    throws JsonIOException
  {
    try
    {
      JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer));
      toJson(src, typeOfSrc, jsonWriter);
    } catch (IOException e) {
      throw new JsonIOException(e);
    }
  }
  // The above are the toJson overloaded methods. The last call is toJson (object SRC, type typeofsrc, jsonwriter writer). Jsonwriter is the main body of Gson serialization
  
public void toJson(Object src, Type typeOfSrc, JsonWriter writer)
    throws JsonIOException
  {
  // Get the corresponding TypeAdapter according to the incoming Type. TypeToken is mainly used to obtain generic information
    TypeAdapter adapter = getAdapter(TypeToken.get(typeOfSrc));
    boolean oldLenient = writer.isLenient();
    // Set loose fault tolerance (the top-level value can not be object/array, and the number can be infinite)
    writer.setLenient(true);
    boolean oldHtmlSafe = writer.isHtmlSafe();
    // html escape
    writer.setHtmlSafe(this.htmlSafe);
    boolean oldSerializeNulls = writer.getSerializeNulls();
    // serialize 
    writer.setSerializeNulls(this.serializeNulls);
    try {
    //Output and complete the whole serialization process
      adapter.write(writer, src);
    } catch (IOException e) {
      throw new JsonIOException(e);
    } finally {
      writer.setLenient(oldLenient);
      writer.setHtmlSafe(oldHtmlSafe);
      writer.setSerializeNulls(oldSerializeNulls);
    }
  }
Copy code
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type)
  {
    TypeAdapter cached = (TypeAdapter)this.typeTokenCache.get(type);
    if (cached != null) {
      return cached;
    }

    Map threadCalls = (Map)this.calls.get();
    boolean requiresThreadLocalCleanup = false;
    if (threadCalls == null) {
      threadCalls = new HashMap();
      this.calls.set(threadCalls);
      requiresThreadLocalCleanup = true;
    }

    FutureTypeAdapter ongoingCall = (FutureTypeAdapter)threadCalls.get(type);
    if (ongoingCall != null) {
      return ongoingCall;
    }
    try
    {
      FutureTypeAdapter call = new FutureTypeAdapter();
      threadCalls.put(type, call);
 //Traverse the factories collection of Gson, in which the typeadapterfactory encapsulated by the Adapter is also in it.
      for (TypeAdapterFactory factory : this.factories) {
      //Retrieve the encapsulated Adapter from your encapsulated typeadapterfactory
        TypeAdapter candidate = factory.create(this, type);
        if (candidate != null) {
        // Set delegate
          call.setDelegate(candidate);
          // Put in cache
          this.typeTokenCache.put(type, candidate);
          return candidate;
        }
      }
      throw new IllegalArgumentException("GSON cannot handle " + type);
    } finally {
      threadCalls.remove(type);

      if (requiresThreadLocalCleanup)
        this.calls.remove();
    }
  }

// The TypeAdapter abstract class has two methods, toJson, which mainly calls write adapter write(writer, src);
public abstract class TypeAdapter<T> {
    // Type object to json
     public abstract void write(JsonWriter out, T value) throws IOException; 
     // Convert the read json to the object T of the specified type
      public abstract T read(JsonReader in) throws IOException; 

}
Copy code

In fact, the most important part of the above code should be adapter Write (writer, SRC). I debugg looked at his input parameters, as shown in the figure below. It seems that I guessed right. The inexplicable node mMap is this [external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img hsouklkn-1606207722125)( https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/48e67d96a8c345258d5c1611b65337a7 ~tplv-k3u1fbpfcp-watermark. image)]

In short, the serialization process is almost

Incoming object ObjectType →analysis ObjectType ,generate TypeAdapter→ call adapter.write(writer, src)
Copy code

Just the collections above I also tested one

long[] data = {1506326821000l, 1506327060000l, 1506326880000l, 1506327000000l, 1506326940000l, 1506326760000l, 1506326700000l};
 List list = new ArrayList<>();
 for (long key : data) {
	    list.add(key);
	 }	   
 System.out.println("primary list Value of"+list);	   
  //Re inversion
Collections.reverse(list);
System.out.println("reverse Later"+list);	   	   
	  
// ->Output result:
//Values of the original list [1506326821000, 1506327060000, 1506326880000, 1506327000000, 1506326940000, 1506326760000, 1506326700000]
//Value after reverse; [1506326700000, 1506326760000, 1506326940000, 1506327000000, 1506326880000, 1506327060000, 1506326821000]
Copy code

There is no change. In order to successfully reverse, you need to sort first and improve it

long[] data = {1506326821000l, 1506327060000l, 1506326880000l, 1506327000000l, 1506326940000l, 1506326760000l, 1506326700000l};
 List list = new ArrayList<>();
	    for (long key : data) {
	      list.add(key);
	    }
	    System.out.println("primary list Value of"+list);
	    //First ascending order
	    Collections.sort(list);
	    System.out.println("sort Value of"+list);
	    //Re inversion
	    Collections.reverse(list);
	    System.out.println("reverse after"+list);	   
 // ->Output results
 // Values of the original list [1506326821000, 1506327060000, 1506326880000, 1506327000000, 1506326940000, 1506326760000, 1506326700000]
 //  Value of sort [1506326700000, 1506326760000, 1506326821000, 1506326880000, 1506326940000, 1506327000000, 1506327060000]
 //  After reverse [1506327060000, 1506327000000, 1506326940000, 1506326880000, 1506326821000, 1506326760000, 1506326700000]

Copy code

TypeToken

Generic erase

Java generics are only valid at compile time, and this generic type will be erased at run time, so List and List are actually List types at run time.

Why choose this implementation mechanism? Can't you erase it?

In the algorithm, we can only choose two ways of design according to the application scenario, that is, sacrificing time for space or sacrificing space for time. So is "type erasure". 10 years after the birth of Java, programmers want to realize the concept (generic type) similar to C + + template. However, Java's class library is a very valuable asset in the Java ecosystem. We must ensure backward compatibility (that is, the existing code and class files are still legal) and migration compatibility (generalized code and non generalized code can be called each other) based on the above two backgrounds and considerations. We only use the concept of type erasure, so as long as the thought does not decline, the method is always more difficult than the method 😂

I can see what I saw in the online statement when I wrote the last time; Located on the use side, there is nothing written in the source code until the runtime.

Typetoken is mainly used to obtain the type information of generics. Its main idea is that if generics in List will be erased, I will use a subclass SubList extends List. In this case, the type of parent generics will be saved in the JVM

The usage scenarios of typetoken here are generally as follows:

  • Generic parent class needs to get the generic type defined by its subclass
    final TypeToken<V> typeToken = new TypeToken<V>(getClass()) {};
    classType = (Class<V>) typeToken.getRawType();  //Gets the generic type of the subclass
 Copy code
  • When you need to obtain a generic type in a method or local variable, you need to obtain the generic class of the type and construct an anonymous subclass as the generic parameter of the TypeToken. You can obtain the generic parameter type of the generic class we use through the getType() method
// Level 3 alert: attention!!! {} is used to define an anonymous class. This anonymous class inherits the TypeToken class, which is a subclass of TypeToken
final TypeToken typeToken = new TypeToken<List<Integer>>() {}; 
final Type type = typeToken.getType();
Copy code

In the above source code analysis, we only use typetoken get(typeOfSrc)

 final Class<? super T> rawType;
 // DK1.5 after the introduction of generics, all classes in Java implement the Type interface, which is the general parent interface of Class and parameterizedtype, TypeVariable < d >, genericarraytype and wildcardtype. In this way, the parameters of Type can be used to accept the arguments of the above five subclasses, or the return value Type is the parameter of Type. Unifies the types related to generics and the original Type Class
 final Type type;   
 final int hashCode;
    
 public static TypeToken<?> get(Type type) {
        return new TypeToken(type);
   }
    
  TypeToken(Type type) {
  // (Type)Preconditions.checkNotNull(type) determines that it is empty. If it is not empty, it returns type
        this.type = Types.canonicalize((Type)Preconditions.checkNotNull(type));
        // The function of this method is to return the Type of the current parametrizedtype. For example, a List returns the Type of the List, that is, the Type of the current parameterized Type itself.
        this.rawType = Types.getRawType(this.type);
        this.hashCode = this.type.hashCode();
    }
 static Type getSuperclassTypeParameter(Class<?> subclass) {
        Type superclass = subclass.getGenericSuperclass();
        if (superclass instanceof Class) {
            throw new RuntimeException("Missing type parameter.");
        } else {
           // ParameterizedType is a Java type that represents a type with generic parameters
            ParameterizedType parameterized = (ParameterizedType)superclass; 
            return Types.canonicalize(parameterized.getActualTypeArguments()[0]);
        }
 }    
Copy code

JsonWriter

In Gson, the conversion between Java objects and JSON strings is operated through character stream. JsonReader inherits from Reader to read characters, and JsonWriter inherits from Writer to write characters

 /* <h3>Examples</h3>
 * Suppose we want to encode a message flow, for example: < pre > {@ code
 * [
 *   {
 *     "id": 912345678901,
 *     "text": "How do I stream JSON in Java?",
 *     "geo": null,
 *     "user": {
 *       "name": "json_newb",
 *       "followers_count": 41
 *      }
 *   },
 *   {
 *     "id": 912345678902,
 *     "text": "@json_newb just use JsonWriter!",
 *     "geo": [50.454722, -104.606667],
 *     "user": {
 *       "name": "jesse",
 *       "followers_count": 2
 *     }
 *   }
 * ]}</pre>
 * This code encodes the above structure: < pre > {@ code
 *   public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
 *     JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
 *     writer.setIndent("    ");
 *     writeMessagesArray(writer, messages);
 *     writer.close();
 *   }
 *
 *   public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
 *     writer.beginArray();
 *     for (Message message : messages) {
 *       writeMessage(writer, message);
 *     }
 *     writer.endArray();
 *   }
 *
 *   public void writeMessage(JsonWriter writer, Message message) throws IOException {
 *     writer.beginObject();
 *     writer.name("id").value(message.getId());
 *     writer.name("text").value(message.getText());
 *     if (message.getGeo() != null) {
 *       writer.name("geo");
 *       writeDoublesArray(writer, message.getGeo());
 *     } else {
 *       writer.name("geo").nullValue();
 *     }
 *     writer.name("user");
 *     writeUser(writer, message.getUser());
 *     writer.endObject();
 *   }
 *
 *   public void writeUser(JsonWriter writer, User user) throws IOException {
 *     writer.beginObject();
 *     writer.name("name").value(user.getName());
 *     writer.name("followers_count").value(user.getFollowersCount());
 *     writer.endObject();
 *   }
 *
 *   public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
 *     writer.beginArray();
 *     for (Double value : doubles) {
 *       writer.value(value);
 *     }
 *     writer.endArray();
 *   }}</pre>
 *
 * <p>Each JonWriter may write some simple JSON stream (JSON stream)
 * This instance is thread unsafe You may get an IllegalStateException when the call fails
 */
 
// The autocolosable interface is implemented, which supports the newly added try statement with resources in JDK7. This try statement can automatically execute the resource closing process, and flush forces the cached output to be written to the stream associated with the object
public class JsonWriter implements Closeable, Flushable {
// REPLACEMENT_CHARS or HTML_ SAFE_ REPLACEMENT_ The key to chars is the value of boolean htmlSafe
  private static final String[] REPLACEMENT_CHARS;
  private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
   /**
   *Separator ':' or ':' for name and value
   */
  private String separator = ":";
 
  private boolean lenient;
/*
Whether it is configured to emit Json that can be safely included directly in HTML and XML documents. This will escape the HTML characters <, >, &, = before writing them to the stream. Without this setting, the XML/HTML encoder should replace these characters with the corresponding escape sequence.
*/
  private boolean htmlSafe;

  private String deferredName;
/* Serialize null
*/
  private boolean serializeNulls = true;
}

Copy code

summary

I feel that in the source code of Gson, the writing method of chain call is very common. The last time I read the Book Effective Java, it said that the builder mode is used when there are too many construction method parameters. When there are many parameters, it is difficult to write client code and understand it. The programmer does not know what these values mean, and must carefully calculate the parameters to find the answer. A long string of parameters of the same type can lead to some subtle bug s. If the client accidentally reverses two such parameters, the compiler will not make an error, but it will report an error at runtime. Now it is in jdk1 8 also uses chain operation. You can pay attention to the official account: Java architects alliance. You can check the access method by replying to Java in the background

Tags: Python Java Programming Big Data Spring Interview architecture

Posted by Sangre on Fri, 06 May 2022 20:42:28 +0300