This article appears in the Interview Cheat Sheet series on Github at github.com/cosen1024/J… Domestic Gitee(downloadable PDF) : gitee.com/cosen1024/J…

This article covers common questions on String, wrapper types, reflection, generics, serialization, exceptions, and IO.

This article is included in the Interview Cheat Sheet series at github.com/cosen1024/J…

This topic covers Java basics, Java concurrency, JVM, MySQL, Redis, Spring, MyBatis, Kafka, operating systems, and computer networking.

Sit tight. It’s time to start

1. What is the difference between character constants and string constants?

  1. Formal: A character constant is a single character caused by a single quote, and a string constant is several characters caused by a double quote.

  2. Meaning: a character constant is equivalent to an integer value (ASCII value), can participate in expression operations; A string constant represents an address value (the location of the string in memory, which is equivalent to an object;

  3. Memory size: character constants are only 2 bytes; String constants take up several bytes (at least one end-of-character flag) (note: char takes up two bytes in Java).

2. What is string constant pool?

There are three main concepts of constant pool in Java: global string constant pool, class file constant pool, and runtime constant pool. What we are talking about is the global string constant pool. If you want to understand this, you can see the difference between several constant pools in Java.

The JVM in order to improve performance and reduce the memory overhead, avoid repeating characters to create, it maintains a special piece of memory space, namely the string pool, when you need to use the string to string pool to see if the string already exists, if there is, it can be used directly, if not, initialization, and put the string in a string constants in the pool.

The location of the string constant pool also varies with JDK versions. In JDK6, the location of the constant pool is in the permanent generation (method area) where objects are stored. In JDK7, the location of the constant pool is in the heap, and the constant pool stores references. In JDK8, permanent generations (method areas) are replaced by meta-spaces.

3. String STR =”aaa” same as String STR =new String(“aaa”)?New String (" aaa ");How many string objects are created?

  • useString a = "aaa";When running, the program will look for the string “AAA” in the constant pool. If not, it will put the string “AAA” into the constant pool and assign its address to A. If so, assign the address of the found “AAA” string to A.
  • Use String b = new String(“aaa”); ‘, the program creates a new space in the heap to hold the new object, and puts the “AAA” string into the constant pool. It creates two objects, and creates a new space in the heap to hold the new object regardless of whether the “AAA” string is in the constant pool.

For specific analysis, see the following code:

 @Test
    public void test(a){
        String s = new String("2");
        s.intern();
        String s2 = "2";
        System.out.println(s == s2);


        String s3 = new String("3") + new String("3");
        s3.intern();
        String s4 = "33";
        System.out.println(s3 == s4);
    }
Copy the code

Running results:

jdk6
false
false

jdk7
false
true
Copy the code

This code outputs false false in JDk6, but false true in JDk7. Let’s explain it line by line.

Let’s start with the intern() function:

Intern (); intern (); intern (); intern ();

In JDK1.6, an intern checks whether a string constant is in the string constant pool. If it exists, the constant is returned. If it is not found, the constant is added to the string constant area.

Intern in JDK1.7 processing is to determine whether string constants in string constants in the pool, if there is a direct return to the constants, if not found, that the string constants in the heap, heap is dealing with the area of the object reference added to the string constants in the pool, after others get is the string constant reference, actual existence in the heap

JDK1.6

String s = new String(“2”); Two objects are created, a StringObject object in the heap and a “2” object in the constant pool. s.intern(); Find the object with the same content as s variable in the constant pool, find the object “2” with the same content already exists, return the address of object 2. String s2 = “2”; Created using a literal, look in the constant pool to see if there is an object with the same content, and return the address of object “2”. System.out.println(s == s2); S and s2 address to different objects, so return false

String s3 = new String(“3”) + new String(“3”); Two objects are created, a StringObject object in the heap and a “3” object in the constant pool. There are also two anonymous new strings (“3”) in the middle and we won’t talk about them. s3.intern(); Search the constant pool for an object with the same content as s3 variable. If no object “33” is found, create an object “33” in the constant pool and return the address of the object “33”. String s4 = “33”; Created using a literal, look in the constant pool to see if there is an object with the same content, and return the address of object “33”. System.out.println(s3 == s4); S3 and S4 address to different objects, so return false

JDK1.7

String s = new String(“2”); Two objects are created, a StringObject in the heap and a “2” object in the heap, and the reference address of the “2” object is saved in the constant pool. s.intern(); Find the object with the same content as s variable in the constant pool, find the object “2” with the same content already exists, return the reference address of object “2”. String s2 = “2”; Created using a literal, look in the constant pool to see if there is an object with the same content. If there is, return the reference address of object “2”. System.out.println(s == s2); S and s2 address to different objects, so return false

String s3 = new String(“3”) + new String(“3”); Two objects are created, a StringObject in the heap and a “3” object in the heap, and the reference address of the “3” object is saved in the constant pool. There are also two anonymous new strings (“3”) in the middle and we won’t talk about them. s3.intern(); If no “33” object is found, save the address of StringObject corresponding to S3 to the constant pool and return the address of StringObject. String s4 = “33”; Created using a literal, looks in the constant pool to see if there is an object with the same content. If there is, returns its address, which is the reference address of the StringObject object. System.out.println(s3 == s4); S3 and S4 address to the same object, so return true.

4. Is String the most basic data type?

It isn’t. There are only eight basic data types in Java: byte, short, int, long, float, double, char, Boolean. All but primitive types are referencetypes. Enumeration types introduced later in Java 5 are a special type of reference.

5. What are the features of String?

  • Immutability: String is a read-only String, and is typically an IMmutable object. To manipulate it, you create a new object and refer to it. The main function of immutable mode is to ensure data consistency when an object needs to be shared and accessed frequently by multiple threads.

  • Constant pool optimization: After a String object is created, it is cached in the String constant pool. If the same object is created next time, the cached reference is returned directly.

  • Final: Final is used to define the String class, indicating that the String class cannot be inherited, which improves system security.

6. What are the advantages of using String keys when using HashMap?

The internal implementation of a HashMap uses the hashcode of the key to determine where the value is stored. Since the string is immutable, its Hashcode is cached when the string is created and does not need to be evaluated again, making it faster than other objects.

7. What is the type of packing? What is the difference between a base type and a wrapper type?

The wrapper class for int is Integer. Since Java 5, an automatic boxing/unboxing mechanism has been introduced. Converting a basic type into a wrapper type is called boxing. On the other hand, the process of converting a packing type to a basic type is called unboxing, which makes it interchangeable.

Java provides a wrapper type for each primitive type:

Primitive types: Boolean, char, byte, short, int, long, float, double

Wrapper types: Boolean, Character, Byte, Short, Integer, Long, Float, Double

There are several main differences between the basic type and the wrapper type:

  • The wrapper type can be NULL, but the base type cannot. It makes it possible for wrapper types to apply to POJOs, but not to primitive types. So why do POJO attributes have to have wrapper types? As detailed in the “Alibaba Java Development Manual”, the query result of the database may be null, if the basic type is used, because of the automatic unboxing (converting the wrapper type to the basic type, such as converting an Integer object to an int value), NullPointerException is thrown.

  • Wrapper types can be used with generics, but primitive types cannot. Generics cannot use primitive types because they can cause compilation errors.

    List<int> list = new ArrayList<>(); // Syntax error, insert "Dimensions" to complete ReferenceType
    List<Integer> list = new ArrayList<>();
    Copy the code

    Because generics are erased at compile time, only primitive types are left, and primitive types can only be the Object class and its subclasses — primitive types are a special case.

  • The base type is more efficient than the wrapper type. Basic types store concrete values directly in the stack, whereas wrapper types store references in the heap. Obviously, wrapper types take up more memory than basic types.

8. Explain automatic packing and unpacking?

Automatic boxing: Converts basic data types back into objects

    public class Test {  
        public static void main(String[] args) {  
            // Declare an Integer object using automatic boxing :Integer num = integer.valueof (9);
	        Integer num = 9; }}Copy the code

9 is a primitive data type and cannot be assigned directly to an Integer in principle. However, with the introduction of auto-boxing/unboxing in JDK1.5, declarations can be made that automatically convert the base data type to the corresponding wrapper type, and once an object is made, all methods declared by the object can be called.

Automatic unboxing: Converts objects back to primitive data types

 public class Test {  
        public static void main(String[] args) {/ / Declare an Integer object Integer num =9;
            
            // Automatic unpacking is implicit in the calculationSystem.out.print(num--); }}Copy the code

Because the object can not be directly calculated, but to be converted to the basic data type can be added, subtracted, multiplied and divided.

9. What is the difference between int and Integer?

  • Integer is the wrapper class for int; Int is the basic data type;
  • The Integer variable must be instantiated before it can be used; The int variable is not needed;
  • Integer is actually a reference to the object that points to this new Integer object; Int stores data values directly;
  • The default value of Integer is null; The default value of int is 0.

10. Comparison of two Integer variables generated by new

Since an Integer variable is actually a reference to an Integer object, two Integer variables generated by new are never equal (because new generates two objects with different memory addresses).

Integer i = new Integer(10000);
Integer j = new Integer(10000);
System.out.print(i == j); //false
Copy the code

11. Comparison of Integer and int variables

If an Integer variable is compared with an int variable, the result is true as long as the values of the two variables are equal.

    int a = 10000;
    Integer b = new Integer(10000);
    Integer c=10000;
    System.out.println(a == b); // true
    System.out.println(a == c); // true
Copy the code

12. Comparison of non-new generated Integer variables with new Integer() generated variables

Integer variables that are not generated by new are compared to variables generated by new Integer() and the result is false. (Because non-new generated Integer variables refer to objects in the Java constant pool, whereas new Integer() generated variables refer to objects newly created in the heap, which have different addresses in memory.)

    Integer b = new Integer(10000);
    Integer c=10000;
    System.out.println(b == c); // false
Copy the code

13. Comparison of two non-new generated Integer objects

For two non-new generated Integer objects, the comparison result is true if the values of the two variables are in the range -128 to 127, and false if the values are not in the range

Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true

Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false
Copy the code

When the value is between -128 and 127, Java automatically boxes the value and caches the value. If the value is the same next time, it is directly fetched from the cache for use. Caching is done through Integer’s internal class IntegerCache. When the value is outside this range, an object is created in the heap to store.

ValueOf (int); valueOf (int); valueOf (int); valueOf (int);

public static Integer valueOf(String s, int radix) throws NumberFormatException {
        return Integer.valueOf(parseInt(s,radix));
    }
Copy the code
/** * (1) Between -128 and 127: The cache array is static final. The cache array objects are stored in the static constant pool. Static final cache[k] = new Integer(j++) Only the cache array object stores the Integer object (reference address) in the heap * * (2) from -128 to 127: Create an Integer object and return it. * /
public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high) {
            return IntegerCache.cache[i + (-IntegerCache.low)];
        }
        return new Integer(i);
    }

Copy the code

IntegerCache is an internal class of Integer.

     /** * Caches support auto-boxing object identity semantics -128 and 127 inclusive. * The cache is initialized the first time it is used. The size of the cache can be controlled by the -xx: AutoBoxCacheMax = 
      
        option. * during the VM initialization, Java. Lang. Integer. IntegerCache. High property can be set and save * / in the private system properties
      
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if(integerCacheHighPropValue ! =null) {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++) {
                cache[k] = new Integer(j++); // Create an object}}private IntegerCache(a) {}}Copy the code

14. What is reflection?

Reflection is a running state in which all properties and methods of any class can be known; For any object, you can call any of its methods and properties; This ability to dynamically retrieve information and invoke methods on objects is called the Reflection mechanism of the Java language.

15. What are the pros and cons of reflection?

Advantages: Can dynamically obtain class instances at runtime, improving flexibility; Can be combined with dynamic compilation Class. Class.forname (‘. Com. Mysql. JDBC Driver. The Class ‘); Load the MySQL driver class.

Disadvantages: Poor performance with reflection, need to parse bytecode, parsing objects in memory. The solution is to speed up reflection by turning off JDK security checks with setAccessible(true); When creating instances of a class multiple times, having a cache is much faster; ReflflectASM tool class to speed up reflection through bytecode generation.

16. How do I get the Class object in reflection?

  1. Class.forname (” Class path “); You can use this method to get Class objects when you know the full pathname of the Class.

    Class clz = Class.forName("java.lang.String");
    Copy the code
  2. The name of the class. The class. This approach is only suitable for classes where the operation is known before compilation.

    Class clz = String.class;
    Copy the code
  3. Object name.getClass().

    String str = new String("Hello");
    Class clz = str.getClass();
    Copy the code
  4. If it is a wrapper Class of a primitive Type, you can call the Type attribute of the wrapper Class to get the Class object for that wrapper Class.

17. How many classes of Java reflection apis are there?

The reflection API is used to generate information about classes, interfaces, or objects in the JVM.

  • Class: Reflection of the core Class, can get Class attributes, methods, and other information.

  • Field class: a class in the java.lang. reflec package that represents a member variable of the class that can be used to get and set property values in the class.

  • Method class: a class in the java.lang. reflec package that represents a class’s methods, which can be used to get information about a class’s methods or to execute methods.

  • Constructor class: a class in the java.lang. Reflec package that represents the Constructor of a class.

18. The steps used for reflection?

  1. Get the Class object of the Class we want to operate on. This is the core of reflection. We can call any method of the Class through the Class object.

  2. Calling a method in the Class Class is the use phase of reflection.

  3. Use the reflection API to manipulate this information.

For details, see the following examples:

public class Apple {

    private int price;

    public int getPrice(a) {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        // Normal call
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
        // Use reflection calls
        Class clz = Class.forName("com.chenshuyi.api.Apple");
        Method setPriceMethod = clz.getMethod("setPrice".int.class);
        Constructor appleConstructor = clz.getConstructor();
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:"+ getPriceMethod.invoke(appleObj)); }}Copy the code

You can see from the code that we called the setPrice method using reflection and passed the value of 14. The getPrice method is then called using reflection to print its price. The entire output of the above code is:

Apple Price:5
Apple Price:14
Copy the code

As you can see from this simple example, in general we use reflection to get an object:

  • Gets the Class object instance of the Class
Class clz = Class.forName("com.zhenai.api.Apple");
Copy the code
  • Get the Constructor object from the Class object instance
Constructor appleConstructor = clz.getConstructor();
Copy the code
  • Get the reflection class object using the newInstance method of the Constructor object
Object appleObj = appleConstructor.newInstance();
Copy the code

If you want to call a method, you need to do the following:

  • Gets the Method object of the Method
Method setPriceMethod = clz.getMethod("setPrice", int.class);
Copy the code
  • Invoke a method using the Invoke method
setPriceMethod.invoke(appleObj, 14);
Copy the code

19. Why was reflection introduced? What are the applications of reflection?

Let’s take a look at the official Oracle documentation for reflection:

As you can see from the official Oracle documentation, reflection is mainly used in the following aspects:

  • Reflection lets developers create objects from the full pathnames of external classes and use those classes to implement extended functionality.
  • Reflection lets developers enumerate all members of a class, including constructors, properties, and methods. To help developers write the right code.
  • When testing, the reflection API can be used to access private members of a class to ensure test code coverage.

In other words, Oracle wants developers to use reflection as a tool to help programmers achieve functionality that would otherwise be impossible.

Here are two examples of the most common use of reflection to illustrate the power of reflection:

The first: JDBC database connection

In JDBC operations, if you want to connect to the database, you must follow the steps above

  1. The database driver is loaded via class.forname () (via reflection, provided the relevant Jar package is introduced);
  2. Use DriverManager to connect to the database. When connecting, enter the database connection address, user name, and password.
  3. Receive the Connection through the Connection interface.
public class ConnectionJDBC {  
  
    / * * *@param args 
     */  
    // The driver is in the JAR of the JDBC driver previously configured in the classpath
    public static final String DBDRIVER = "com.mysql.jdbc.Driver";  
    // The connection address is provided separately by each database manufacturer, so it needs to be remembered separately
    public static final String DBURL = "jdbc:mysql://localhost:3306/test";  
    // User name to connect to the database
    public static final String DBUSER = "root";  
    // Password to connect to the database
    public static final String DBPASS = "";  
      
      
    public static void main(String[] args) throws Exception {  
        Connection con = null; // Represents the connection object of the database
        Class.forName(DBDRIVER); //1, use CLASS load driver, reflection mechanism is reflected
        con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); // connect to the database
        System.out.println(con);  
        con.close(); // 3. Close the database
    }  

Copy the code

The second: the use of the Spring framework, the most classic is XML configuration mode.

Spring’s process for loading beans through XML configuration schema:

  1. Load any XML or Properties configuration files in the program into memory;
  2. The Java class parses the contents of XML or Properties to obtain the bytecode string and related attribute information of the corresponding entity class.
  3. Use reflection to get an instance of a Class based on this string;
  4. Dynamically configure the properties of the instance.

The benefits of Spring doing this are:

  • You don’t have to go to new or do anything in code every time;
  • In the future, if you want to change the configuration file directly, the code maintenance is very convenient;
  • Sometimes, in order to accommodate certain requirements, it is not necessary to call another method directly in a Java class.

Simulate Spring loading XML configuration files:

public class BeanFactory {
       private Map<String, Object> beanMap = new HashMap<String, Object>();
       /** * Initialization of the bean factory@paramXML XML configuration file */
       public void init(String xml) {
              try {
                     // Read the specified configuration file
                     SAXReader reader = new SAXReader();
                     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                     // Get the specified XML file from the class directory
                     InputStream ins = classLoader.getResourceAsStream(xml);
                     Document doc = reader.read(ins);
                     Element root = doc.getRootElement();  
                     Element foo;
                    
                     / / traverse beans
                     for (Iterator i = root.elementIterator("bean"); i.hasNext();) {  
                            foo = (Element) i.next();
                            // Get the bean's attribute ID and class
                            Attribute id = foo.attribute("id");  
                            Attribute cls = foo.attribute("class");
                           
                            // Use Java reflection to get the class object by its name
                            Class bean = Class.forName(cls.getText());
                           
                            // Get information about the corresponding class
                            java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean);
                            // Get its attribute description
                            java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors();
                            // The method of setting the value
                            Method mSet = null;
                            // Create an object
                            Object obj = bean.newInstance();
                           
                            // Iterate over the bean's property property
                            for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) {  
                                   Element foo2 = (Element) ite.next();
                                   // Get the name attribute of the property
                                   Attribute name = foo2.attribute("name");
                                   String value = null;
                                  
                                   // Gets the value of the child element of the property
                                   for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) {
                                          Element node = (Element) ite1.next();
                                          value = node.getText();
                                          break;
                                   }
                                  
                                   for (int k = 0; k < pd.length; k++) {
                                          if (pd[k].getName().equalsIgnoreCase(name.getText())) {
                                                 mSet = pd[k].getWriteMethod();
                                                 // Use Java's reflection acme to call a set method on an object and set the value to itmSet.invoke(obj, value); }}}// Put the object into the beanMap, where key is the id value and value is the objectbeanMap.put(id.getText(), obj); }}catch(Exception e) { System.out.println(e.toString()); }}//other codes
}
Copy the code

20. What is the mechanism of reflection?

The Class actionClass = Class. Class.forname (" MyClass "); Object action=actionClass.newInstance(); Method Method = actionClass. GetMethod (" myMethod (int x) ",null);
method.invoke(action,null);
Copy the code

The first two lines implement class loading, linking, and initializing (the newInstance method actually calls a method using reflection), and the last two lines implement fetching a Method object from a class object and executing a reflection call.

Due to the reflection principle is complex, here is a brief description of the process, want to learn more about a friend, we can see this article: www.cnblogs.com/yougewe/p/1…

  1. Reflection takes the Class instance class.forname () and leaves the implementation not to Java, but to the JVM to load! The main thing is to get the ClassLoader first, then call the native method to get the information, and load the class to call the java.lang.ClassLoader. Finally, the JVM calls the ClassLoader back into class loading!

  2. NewInstance () does three things:

    • Permission detection, if not by directly throwing an exception;
    • Find the no-parameter constructor and cache it;
    • Calls the no-argument constructor of the concrete method, generates the instance and returns it.
  3. Get the Method object,

The above Class object is constructed by the JVM when the Class is loaded. The JVM manages a unique Class object for each Class that maintains the Class’s Method, Field, and Constructor cache. This cache can also be called the root object.

Every time getMethod gets a Method object, it holds a reference to the root object. Because of some heavyweight Method member variables (mainly MethodAccessor), we don’t want to reinitialize every time we create a Method object. So all Method objects representing the same Method share the root object’s MethodAccessor, which is copied on each creation by calling the root object’s copy Method:

 Method copy(a) { 

        Method res = new Method(clazz, name, parameterTypes, returnType,

                                exceptionTypes, modifiers, slot, signature,

                                annotations, parameterAnnotations, annotationDefault);

        res.root = this;

        res.methodAccessor = methodAccessor;

        return res;

    }
Copy the code
  1. Call the invoke() method. The flow of invoking the Invoke method is as follows:

After calling method.invoke, methodAccessor.invoke is called directly. MethodAccessor is an instance of all the method shares of the same name mentioned above, created by the ReflectionFactory.

The creation mechanism uses a method named inflation (after JDK1.4) : If the cumulative number of calls to this method <=15, NativeMethodAccessorImpl will be created, which is implemented by directly calling native methods to implement reflection. If the cumulative number of calls to the method is greater than 15, a MethodAccessorImpl assembled from bytecode is created from Java code. Call myClass.myMethod (String s). The resulting bytecode for MethodAccessorImpl is translated into Java code as follows:

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {    
    public Object invoke(Object obj, Object[] args)  throws Exception {
        try {
            MyClass target = (MyClass) obj;
            String arg0 = (String) args[0];
            target.myMethod(arg0);
        } catch (Throwable t) {
            throw newInvocationTargetException(t); }}}Copy the code

21. What are generics in Java?

Generics are a new feature in JDK1.5. ** Generics are parameterized types whose parameters are determined at compile time. ** This parameter type can be used in the creation of classes, interfaces, and methods, called generic classes, generic interfaces, and generic methods, respectively.

22. What are the benefits of using generics?

Back in JDK 1.4, when there was no concept of generics, using Object for generic, different types of processing had two disadvantages:

  1. You need to cast to the desired type each time you use it
  2. At compile time, the compiler does not know whether the conversion is normal until run time, which is not safe.

Take this example:

List list = new ArrayList();
list.add("www.cnblogs.com");
list.add(23);
String name = (String)list.get(0);
String number = (String)list.get(1);	//ClassCastException
Copy the code

The above code will run with a cast exception. This is because the second one is an Integer when we put it in, but when we get it out, we cast it to String. Sun has made the Java language more secure by reducing runtime exceptions. The concept of generics was introduced after JDK 1.5.

The motivation for generics, as described in Ideas for Java Programming, is that there are many reasons for generics, most notably the creation of container classes.

The benefits of using generics are as follows:

  1. Type safety

    • The primary goal of generics is to improve type safety in Java programs
    • ClassCastException caused by incorrect Java types can be detected at compile time
    • The earlier you make a mistake, the less costly it is
  2. Eliminate casts

    • A side benefit of generics is that you get the target type directly when you use it, eliminating many casts
    • What you get is what you need, which makes the code more readable and reduces the chance of errors
  3. Potential performance gains

    • Because of the way generics are implemented, support for generics requires (almost) no JVM or class file changes
    • All the work is done in the compiler
    • The code generated by the compiler is almost identical to the code written without generics (and casts), only more type-safe

23. What is the principle behind Java generics? What is type erasure?

Generics are a syntactic sugar. The basic principle of generics is type erasure. Generics in Java are basically implemented at the compiler level, that is: ** generics exist only at compile time, not run time. ** There is no concept of generics in compiled class files.

Type erasure: Type arguments added when using generics and removed by the compiler at compile time.

Such as:

public class Caculate<T> {
    private T num;
}
Copy the code

We’ve defined a generic class, we’ve defined an attribute member, the type of the member is a generic type, what type T is, we don’t know, it just qualifies the type. Decompile this Caculate class:

public class Caculate{
    public Caculate(){}
    private Object num;
}
Copy the code

Notice that the compiler erases the two Angle brackets after the Caculate class and defines the type of num as Object.

So are all generic types erased with Object? In most cases, generic types are replaced with Object, but in one case they are not. That is bounded types that use extends and super syntax, such as:

public class Caculate<T extends String> {
    private T num;
}
Copy the code

For generic types in this case, num is replaced with String instead of Object. This is a type-qualified syntax that says T is String or a subclass of String, so when you build Caculate you can only define T as String or a subclass of String, so whatever type you define T to be, String is the parent class, There are no type mismatches, so you can use String for type erasure.

In fact, the compiler normally compiles, erases, and returns instances where generics are used. But other than that, if a generic instance is built with generic syntax, the compiler flags the instance and watches for all subsequent method calls of the instance, doing security checks before each call and failing to call methods that are not of the specified type.

In fact, the compiler not only cares about a generic method call, it also casts some methods that return a qualified generic type. Due to type erasure, any method that returns a generic type will be erased to Object. When these methods are called, The compiler inserts an additional line of checkcast instructions for casting. This process is called generic translation.

24. What are qualified and unqualified wildcards in generics?

Qualified wildcards restrict types. There are two qualified wildcards. One is <? Extends T> This extends T> sets an upper bound on a type by ensuring that it must be a subclass of T. The other is <? Super T> it sets the lower bound of the type by ensuring that it must be a parent of T. A generic type must be initialized with a qualified type, otherwise a compilation error will result.

Unqualified wildcards? , can be replaced by any type. Such as the List
means that this collection is A collection that can hold any type; it can be List, List, List

, etc.

25. List<? Extends T> and List <? What’s the difference between super T>?

Both List declarations are examples of qualified wildcards, List<? Extends T> accepts any List of type inherited from T, and List<? Super T> can accept any List of T’s superclasses. For example the List <? Extends Number> can take a List or List.

26. You can read the List<String>Passed to an acceptance List<Object>Parameter method?

Can’t. Doing so will result in a compilation error. Because lists can store any kind of object including strings, integers, etc., whereas lists can only store strings.

List<Object> objectList;
List<String> stringList;
objectList = stringList;  //compilation error incompatible types
Copy the code

27. The judgeArrayList<String>withArrayList<Integer>Is it equal?

ArrayList<String> a = new ArrayList<String>();
ArrayList<Integer> b = new ArrayList<Integer>();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2); 
Copy the code

The output is true. Because the Class type for both ArrayList and ArrayList is always the same, arrayList.class.

So where are the strings and integers that they specify when they declare?

The answer is when the class is compiled. ** When the JVM compiles a class, it performs generic checking. If a collection is declared as a String, it checks the data when it reads or writes data to that collection to avoid storing or retrieving the wrong data.

Added: Can generics be used in Array?

Can’t. This is why Joshua Bloch, in Effective Java, suggests using lists instead of arrays, because lists provide compile-time type safety, whereas arrays do not.

What is Java serialization and deserialization?

Java serialization refers to the process of converting a Java object into a sequence of bytes, while Java deserialization refers to the process of restoring a sequence of bytes to a Java object:

  • ** serialization: ** serialization converts an object into an ordered stream of bytes for transmission over the network or storage in a local file. The core function is the preservation and reconstruction of object state. As we all know, Java objects are stored in the JVM’s heap memory, which means that if the JVM heap no longer exists, the objects disappear with it.

    Serialization provides a solution that allows you to save objects even when the JVM is down. Just like the usb flash drive we use. Serialize Java objects into a form (such as a binary stream) that can be stored or transferred, such as in a file. This way, when the object is needed again, the binary stream is read from the file and the object is deserialized from the binary stream.

  • ** Deserialization: ** The client retrieves a serialized stream of objects from a file or network and reconstructs the object through deserialization based on the state and description of the object stored in the stream.

29. Why serialization and deserialization?

Brief description: Serialization and deserialization are required for persistent or network transfer of objects in memory

In-depth description:

  1. Object serialization enables distributed objects.

Main applications such as RMI(Remote Method Invocation) Use object serialization to run services on the Remote host as if they were run on the local machine.

  1. Java object serialization not only holds the data of an object, but also recursively holds the data of every object referenced by the object.

An entire object hierarchy can be written to a byte stream, saved in a file or passed over a network connection. Object serialization enables “deep copying” of objects, that is, copying the objects themselves and the referenced objects themselves. Serializing an object may result in an entire sequence of objects.

  1. Serialization can write an in-memory class to a file or database.

For example, if a class is serialized and stored in a file, the next time it is read, the original class can be restored to memory by deserializing the data in the file. Classes can also be serialized as streaming data for transmission.

Basically, an instantiated class is converted to file storage, and the next time it needs to be instantiated, just deserialization will instantiate the class into memory and preserve all the variables and state of the class at the time of serialization.

  1. Objects, files, data, there are many different formats, difficult to uniformly transfer and save.

After serialization, it’s all byte streams, so whatever it was, it can be the same thing, and it can be transferred or saved in a common format, and when it’s done, when it’s used again, it’s deserialized and restored, so object is object, file is file.

30. What are the ways serialization is implemented?

Implement the Serializable interface or Externalizable interface.

The Serializable interface

Class implements the java.io.Serializable interface to enable its serialization capabilities. All subtypes of a serializable class are themselves serializable. The serialization interface has no methods or fields and is used only to identify serializable semantics.

For example:

import java.io.Serializable;

public class User implements Serializable {
   private String name;
   private int age;
   public String getName(a) {
       return name;
   }
   public void setName(String name) {
       this.name = name;
   }

   @Override
   public String toString(a) {
       return "User{" +
               "name='" + name +
               '} '; }}Copy the code

Serialization and deserialization are performed with the following code:

public class SerializableDemo {

   public static void main(String[] args) {
       //Initializes The Object
       User user = new User();
       user.setName("cosen");
       System.out.println(user);

       //Write Obj to File
       try (FileOutputStream fos = new FileOutputStream("tempFile"); ObjectOutputStream oos = new ObjectOutputStream(
           fos)) {
           oos.writeObject(user);
       } catch (IOException e) {
           e.printStackTrace();
       }

       //Read Obj from File
       File file = new File("tempFile");
       try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
           User newUser = (User)ois.readObject();
           System.out.println(newUser);
       } catch(IOException | ClassNotFoundException e) { e.printStackTrace(); }}}//OutPut:
//User{name='cosen'}
//User{name='cosen'}
Copy the code

Externalizable interface

Externalizable inherits from Serializable, where two abstract methods are defined: writeExternal() and readExternal().

When using the Externalizable interface for serialization and deserialization, developers need to override the writeExternal() and readExternal() methods. Otherwise, all variable values are changed to default values.

public class User implements Externalizable {

   private String name;
   private int age;

   public String getName(a) {
       return name;
   }
   public void setName(String name) {
       this.name = name;
   }
   public void writeExternal(ObjectOutput out) throws IOException {
       out.writeObject(name);
   }
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
       name = (String) in.readObject();
   }

   @Override
   public String toString(a) {
       return "User{" +
               "name='" + name +
               '} '; }}Copy the code

Serialization and deserialization are performed with the following code:

public class ExternalizableDemo1 {

  public static void main(String[] args) {
      //Write Obj to file
      User user = new User();
      user.setName("cosen");
      try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"))){
          oos.writeObject(user);
      } catch (IOException e) {
          e.printStackTrace();
      }

      //Read Obj from file
      File file = new File("tempFile");
      try(ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file))){
          User newInstance = (User) ois.readObject();
          //output
          System.out.println(newInstance);
      } catch(IOException | ClassNotFoundException e ) { e.printStackTrace(); }}}//OutPut:
//User{name='cosen'}
Copy the code

A comparison of two serializations

Implement the Serializable interface Implement the Externalizable interface
The system automatically stores the necessary information The programmer decides what information to store
Java built-in support, easy to implement, just need to implement the interface, no code support Two methods within the interface must be implemented
Performance of slightly Performance is slightly better

31. What is serialVersionUID?

SerialVersionUID is used to indicate compatibility between different versions of a class

Java’s serialization mechanism verifies version consistency by determining the serialVersionUID of a class at run time. When deserializing, the JVM compares the serialVersionUID in the incoming byte stream with the serialVersionUID of the corresponding local entity (class). If they are the same, they are considered to be the same, and deserialization can be performed; otherwise, an exception will occur that the serialized version is inconsistent.

32. Why display the value specifying serialVersionUID?

If the specified serialVersionUID is not displayed, the JVM automatically generates a serialVersionUID based on the property during serialization, which is then serialized with the property for persistence or network transport. During deserialization, the JVM will automatically generate a new version of serialVersionUID based on the attributes, and then compare this new version with the old version of serialVersionUID generated during the serialization. If they are the same, the deserialization succeeds; otherwise, an error is reported.

If the display is specified, the JVM will still generate a serialVersionUID for both serialization and deserialization, but the value will show us the specified value so that the serialVersionUID of the old and new versions is the same when deserializing.

In real development, what problems would arise if the serialVersionUID was not specified? If we didn’t change our class after we wrote it, that wouldn’t be a problem, but that’s not possible in real development. Our class would iterate over and over again, and once the class was changed, the deserialization of the old object would report an error. So in real development, we’ll show you specifying a serialVersionUID, whatever the value is, as long as it doesn’t change.

33. When will serialVersionUID be changed?

Alibaba Java Development Manual contains the following provisions:

If you want to know more, you can read this article: juejin.cn/post/684490…

34. What if some fields in Java serialization do not want to be serialized?

For variables that do not want to be serialized, use the TRANSIENT keyword modifier.

The function of the TRANSIENT keyword is to control the serialization of variables. Adding the keyword before the variable declaration can prevent the variable from being serialized to the file. After deserialization, the value of the transient variable is set to its initial value, such as 0 for int and NULL for object. Transient can only modify variables, not classes and methods.

35. Will static variables be serialized?

Don’t. Because serialization is for objects, static variables take precedence over objects and are loaded as the class is loaded, so they are not serialized.

If serialVersionUID is static, why should serialVersionUID be serialized? The serialVersionUID attribute is not serialized; the JVM automatically generates a serialVersionUID when it serializes the object, and then assigns the value of the serialVersionUID attribute specified by our display to the automatically generated serialVersionUID.

36. What is the difference between Error and Exception?

In Java, all exceptions have a common ancestor to the Throwable class in the java.lang package. The Throwable class has two important subclasses Exception and Error.

Exception and Error are both important subclasses of Java Exception handling, each containing a large number of subclasses.

  • Exception: An exception that can be handled by the program itselfcatchThis error is usually encountered and should be handled so that the application can continue to run normally.ExceptionThey can also be classified as runtimeexceptions and non-runtime exceptions.
  • ErrorErrorIt’s an error our program can’t handle. We can’t pass itcatchTo capture. For example, a system crash, out of memory, a stack overflow, etc., is not detected by the compiler. Once such errors occur, the application is usually terminated and cannot be recovered by the application itself.

37. What is the difference between an unchecked exception (runtime exception) and a checked exception (common exception)?

Unchecked Exceptions: Includes the RuntimeException class and its subclasses, which represent exceptions that may occur during running of the JVM. The Java compiler does not check for runtime exceptions. Such as: NullPointException (null pointer), a NumberFormatException (string is converted to digital), IndexOutOfBoundsException (an array), a ClassCastException (conversion), ArrayS ToreException (data storage exception, operating on an array of inconsistent types), etc.

Checked Exception: Is Exception except for RuntimeException and its subclasses. The Java compiler checks for checked exceptions. Common checked exceptions are: IO related exceptions, ClassNotFoundException, SQLException, etc.

The difference between an unchecked exception and a checked exception: Whether it is mandatory for the caller to handle the exception, if it is mandatory for the caller to handle the exception, then use the checked exception, otherwise select the unchecked exception.

38. What is the difference between throws and throws?

Exception handling in Java includes not only catching and handling exceptions, but also declaring and throwing exceptions. You can declare the exception to be thrown by the method through the throws keyword, or throw an exception object inside the method.

Throws keyword and throw keyword in the use of the following differences:

  • The throw keyword is used inside a method and can only be used to throw one exception. It can be used to throw an exception in a method or block of code.
  • Throws a list of exceptions that the method may throw. A method identifies a list of exceptions that may be thrown with throws. The method calling the method must contain code that can handle exceptions; otherwise, the method signature must declare the corresponding exception with the throws keyword.

Examples are as follows:

Throw keyword:

public static void main(String[] args) {
		String s = "abc";
		if(s.equals("abc")) {
			throw new NumberFormatException();
		} else {
			System.out.println(s);
		}
		//function();
}
Copy the code

Throws Keyword:

public static void function(a) throws NumberFormatException{
		String s = "abc";
		System.out.println(Double.parseDouble(s));
	}
	
	public static void main(String[] args) {
		try {
			function();
		} catch (NumberFormatException e) {
			System.err.println("Non-data types cannot be converted.");
			//e.printStackTrace();}}Copy the code

39. Difference between NoClassDefFoundError and ClassNotFoundException?

NoClassDefFoundError is an Error type exception that is raised by the JVM and should not be attempted to catch. This exception is caused when the JVM or ClassLoader tries to load a class and cannot find its definition in memory. This action occurs during runtime, that is, the class exists at compile time but cannot be found at runtime. It may be deleted after mutation or other reasons.

ClassNotFoundException is a checked exception that needs to be explicitly caught and processed using a try-catch, or declared with the throws keyword in the method signature. When using the Class. The class.forname, this loadClass. Or this findSystemClass dynamic Class is loaded into memory, didn’t find the Class through the incoming Class path parameters, throws the exception; Another possible reason for throwing this exception is that a class has been loaded into memory by one classloader and another loader tries to load it.

40. What are the common Java exceptions?

  • Java. Lang. IllegalAccessError: illegal access errors. This exception is thrown when an application attempts to access, modify, or call a class’s Field or method but violates the visibility declaration of the Field or method.
  • Java. Lang. InstantiationError: instantiation errors. This exception is thrown when an application attempts to construct an abstract class or interface using Java’s new operator.
  • Java. Lang. OutOfMemoryError: out of memory error. This error is thrown when the available memory is insufficient for the Java virtual machine to allocate an object.
  • Java. Lang. StackOverflowError: stack overflow error. This error is thrown when an application’s recursive calls are too deep and cause the stack to overflow or fall into an infinite loop.
  • Java. Lang. ClassCastException: class abnormal shape. Given that there are classes A and B (A is not A parent or subclass of B) and O is an instance of A, this exception is thrown when O is forced to be constructed as an instance of class B. This exception is often referred to as a cast exception.
  • Java. Lang. ClassNotFoundException: can’t find the abnormal class. This exception is thrown when an application tries to construct a class based on a string name and cannot find a class file with the name after traversing the CLASSPAH.
  • Java. Lang. ArithmeticException: arithmetic abnormal conditions. An integer divided by zero, etc.
  • Java. Lang. ArrayIndexOutOfBoundsException: array index cross-border anomalies. Thrown if the index value of an array is negative or greater than or equal to the array size.
  • Java. Lang. IndexOutOfBoundsException: index cross-border anomalies. This exception is thrown when the index value accessing a sequence is less than 0 or greater than or equal to the size of the sequence.
  • Java. Lang. InstantiationException: instantiate the exception. Throws this exception when an attempt is made to create an instance of a class that is an abstract class or interface through the newInstance() method.
  • Java. Lang. NoSuchFieldException: attributes, there is no exception. This exception is thrown when a nonexistent property of a class is accessed.
  • Java. Lang. NoSuchMethodException: there is no exception. This exception is thrown when a nonexistent method of a class is accessed.
  • Java. Lang. NullPointerException: null pointer exception. Throws this exception when an application attempts to use NULL where an object is required. For example, calling instance methods of null objects, accessing properties of null objects, calculating the length of null objects, throwing null statements, and so on.
  • Java. Lang. A NumberFormatException: abnormal number format. This exception is thrown when an attempt is made to convert a String to the specified numeric type, but the String does not conform to the format required by the numeric type.
  • Java. Lang. StringIndexOutOfBoundsException: string index crossed the exception. This exception is thrown when a character in a string is accessed using an index value that is less than 0 or greater than or equal to the sequence size.

41. Which part of the try-catch-finally can be omitted?

Catch can be omitted. More strictly, try is only good for runtime exceptions, and try+catch is good for run-time exceptions + normal exceptions. That is, if you just try a normal exception and don’t catch it, the compilation will fail because the compiler dictates that if you catch a normal exception, you must display the declaration with a catch for further processing. Run-time exceptions are not specified this way at compile time, so a catch can be omitted, and the compiler feels comfortable adding it.

In theory, any code that the compiler looks at is likely to have a problem, so even if you try everything, it’s just a layer on top of what it normally does at runtime. But once you add a try to a piece of code, you explicitly promise the compiler to catch exceptions that the code might throw instead of throwing them up. If it is a normal exception, the compiler requires that it must be caught with a catch for further processing. If there is a runtime exception, catch and discard and +finally sweep, or add a catch catch for further processing.

The addition of finally is a “sweep” processing that takes place whether or not an exception is caught.

42. In try-catch-finally, if a catch returns, will finally be executed?

Will be executed, before return.

It is bad to change the return value in finally, because if a finally block is present, the return statement in try does not return the caller immediately. Instead, it records the return value and returns it to the caller after the finally block is finished executing. Then, if the return value is modified in finally, the modified value is returned. Obviously, returning or modifying a return value in finally can be very confusing to your program, and Java can also raise the compiler’s syntax checking level to generate warnings or errors. Code Example 1:

public static int getInt(a) {
    int a = 10;
    try {
        System.out.println(a / 0);
        a = 20;
    } catch (ArithmeticException e) {
        a = 30;
        return a;
        /* * return a return 30 instead of a; A =40 * returns to the previous path again. Return 30. After the return path is formed, a is not an A variable, but a constant 30 */
    } finally {
        a = 40;
    }
	return a;
}

// Result: 30
Copy the code

Code Example 2:

public static int getInt(a) {
    int a = 10;
    try {
        System.out.println(a / 0);
        a = 20;
    } catch (ArithmeticException e) {
        a = 30;
        return a;
    } finally {
        a = 40;
        // If so, a new return path is formed. Since only one return can be made, 40 is returned directly
        returna; }}// Result: 40
Copy the code

43. How does the JVM handle exceptions?

If an exception occurs in a method, the method creates and passes to the JVM an exception object that contains the exception name, the exception description, and the state of the application at the time the exception occurs. The process of creating an exception object and passing it to the JVM is called throwing an exception. There may be a series of method calls that end up in the method that throws the exception. This ordered list of method calls is called the call stack.

The JVM follows the call stack to see if there is code that can handle exceptions, and if there is, it invokes exception handling code. When the JVM finds code that can handle an exception, it passes it the exception that occurred. If the JVM does not find a block of code that can handle the exception, the JVM passes the exception to the default exception handler (which is part of the JVM), which prints out the exception message and terminates the application. Want to deep understanding friend can look at this article: www.cnblogs.com/qdhxhz/p/10…

44. How many types of Java IO streams are there?

  • According to the direction of the stream: inputStream and outputStream;
  • According to the implementation function: node flow (can read and write data from or to a specific place, such as FileReader) and processing flow (is the connection and encapsulation of an existing stream, through the encapsulation of the stream function call data read and write, BufferedReader);
  • In terms of units of data processing: byte stream and character stream. Each stream is represented by four abstract classes :InputStream, OutputStream, Reader, and Writer. All the other diverse flows in Java are derived from them.

45. How do byte streams become character streams?

Byte InputStream character input flows through the InputStreamReader implementation, whose constructor can pass in an InputStream object.

Byte OutputStream character output flows through the OutputStreamWriter implementation, whose constructor can pass in an OutputStream object.

46. What is the difference between a character stream and a byte stream?

  • The byte stream is read and written in bytes, and the character stream is read and written in characters.
  • Byte streams are suitable for data transfer of all types of files because the computer Byte is the smallest unit in the computer that represents the meaning of information. Character streams can only handle plain text data, not other types of data, but character streams can handle text more easily than byte streams.
  • When reading and writing a file, the content needs to be processed in line, such as comparing specific characters or processing a row of data, the character stream is generally selected.
  • Only read and write files, and file content has nothing to do with the general choice of byte stream.

47. What is blocking IO? What is non-blocking IO?

I/O operations include reading and writing data to disks, sockets, and peripherals.

When a user thread initiates an IO request operation (this article uses the read request operation as an example), the kernel checks to see if the data to be read is ready. In the case of blocking IO, if the data is not ready, it will wait until the data is ready. For non-blocking IO, if the data is not ready, a flag message is returned telling the user that the data the thread is currently trying to read is not ready. When the data is ready, the data is copied to the user’s thread and a complete IO read operation is completed. In other words, a complete IO read operation consists of two phases:

1) Check whether the data is ready;

2) Copy data (the kernel copies data to the user thread).

So the difference between blocking IO and non-blocking IO is in the first phase, if the data is not ready, whether to wait while checking whether the data is ready or just return a flag message.

In Java, traditional IO is blocking IO, such as reading data through a socket. If the read() method is not ready, the current thread will block in the read method until it returns data. In non-blocking IO, when data is not ready, the read() method should return a flag telling the current thread that the data is not ready, rather than waiting.

Deep understanding to look at this article: mp.weixin.qq.com/s/p5qM2UJ1u…

48. What is the difference between BIO, NIO and AIO?

  • BIO: sync and block, implemented in the server as one thread at a time. That is, when a client has a connection request, the server needs to start a thread to process it. If the connection does not do anything, it incurs unnecessary thread overhead, which can also be alleviated by thread pooling. BIO is generally suitable for architectures with a small and fixed number of connections. It requires a lot of server resources, and the concurrency is limited to the application. It was the only choice before JDK1.4, but the application is intuitive and easy to understand.
  • NIO: Synchronization is not blocking. The pattern implemented in the server is one request at a time, that is, all connection requests sent by the client are registered with the multiplexer. The multiplexer polls for connection IO requests and starts a thread to process them. NIO is generally suitable for architectures with a large number of connections and relatively short (light operation) connections. Concurrency is limited to applications and programming is complex. It is supported starting with JDK1.4.
  • AIO: Asynchronism is not blocking. The mode realized in the server is one effective request to one thread. That is to say, the IO request of the client is completed by the operating system first, and then the server application is notified to start the thread for processing. AIO generally applies to architectures with a large number of connections and long connections (heavy operations). It fully invokes the operating system to participate in concurrent operations, and the programming is complicated. AIO is supported from JDK1.7.

49. What are the design patterns for Java IO?

The adapter pattern and the decorator pattern are used

Adapter mode:

Reader reader = new INputStreamReader(inputStream);
Copy the code

Transform the interface of one class into another that the client expects, so that two classes that would otherwise not work together because of interface mismatches can work together

  • Class Adapter: Adapter class inherits Adaptee class (source role) to implement Target interface (Target role)
  • Object adapterThe Adapter class holds instances of Adaptee objects that implement the Target interface.

Decorator mode:

new BufferedInputStream(new FileInputStream(inputStream));
Copy the code

A design pattern that dynamically adds new behavior to a class. In terms of functionality, the decorator pattern is more flexible than subclassing, allowing you to add functionality to an object rather than the entire class.

  • ConcreteComponent and Decorator implement the same Conponent and the Decorator holds Conponent objects that can pass requests.
  • The ConcreteComponent method overrides the Decorator and calls it with super, passing the request.

Here I also recommend a collection of computer books warehouse, the warehouse has hundreds of classic CS e-books, read the classic books will be deeper ~

Click this link to get you to the list of must-read books (PDF download included)

Github also has a repository at github.com/cosen1024/a… Welcome to star.