import java.lang.reflect.*;
import java.util.*;

public class Main
{
   private final static List<Class<?>> CLASSES = List.of(
      ClassOne.class, ClassTwo.class, ClassThree.class, ClassFour.class, 
      ClassFive.class, ClassSix.class, ClassSeven.class, ClassEight.class, 
      ClassNine.class, ClassTen.class, ClassEleven.class, ClassTwelve.class
   );

   public static void main(String[] args)
   {
      int iterations = 1000000;
      long startTime = System.nanoTime();
      for (int i = 0; i < iterations; i++)
      {
         for (Class<?> cls : CLASSES)
         {
            try
            {
               cls.getDeclaredConstructor().newInstance();
            }
            catch (Exception e)
            {
               e.printStackTrace();
            }
         }
      }
      long endTime = System.nanoTime();
      long totalTime = (endTime - startTime);
      System.out.println("Total time for non-cached cls.getDeclaredConstructor().newInstance() (ms): " +  totalTime/1000000);

      startTime = System.nanoTime();
      for (int i = 0; i < iterations; i++)
      {
         for (Class<?> cls : CLASSES)
         {
            try
            {
               Class<?> ignored = cls.getComponentType();
            }
            catch (Exception e)
            {
               e.printStackTrace();
            }
         }
      }
      endTime = System.nanoTime();
      totalTime = (endTime - startTime);
      System.out.println("Total time for non-cached cls.getComponentType() (ms): " +  totalTime/1000000);

      startTime = System.nanoTime();
      for (int i = 0; i < iterations; i++)
      {
         for (Class<?> cls : CLASSES)
         {
            try
            {
               Helper.createInstance(cls);
            }
            catch (Exception e)
            {
               e.printStackTrace();
            }
         }
      }
      endTime = System.nanoTime();
      totalTime = (endTime - startTime);
      System.out.println("Total time for cached cls.getDeclaredConstructor().newInstance() (ms): " +  totalTime/1000000);

      startTime = System.nanoTime();
      for (int i = 0; i < iterations; i++)
      {
         for (Class<?> cls : CLASSES)
         {
            try
            {
               // EMPTY
            }
            catch (Exception e)
            {
               e.printStackTrace();
            }
         }
      }
      endTime = System.nanoTime();
      totalTime = (endTime - startTime);
      System.out.println("Total time for empty loop (ms): " +  totalTime/1000000);
   }

   private static class ClassOne { }
   private static class ClassTwo { }
   private static class ClassThree { }
   private static class ClassFour { }
   private static class ClassFive { }
   private static class ClassSix { }
   private static class ClassSeven { }
   private static class ClassEight { }
   private static class ClassNine { }
   private static class ClassTen { }
   private static class ClassEleven { }
   private static class ClassTwelve { }

   private static class Helper
   {
      private static final ClassValue<Constructor<?>> CONSTRUCTOR_CACHE = new ClassValue<>()
      {
         @Override
         protected Constructor<?> computeValue(Class<?> type)
         {
            try
            {
               // This block ONLY runs the very first time .get() is called for a specific class
               Constructor<?> constructor = type.getDeclaredConstructor();

               // We can set it accessible here just once, saving massive overhead later
               constructor.setAccessible(true);

               return constructor;
            }
            catch (NoSuchMethodException e)
            {
               // ClassValue requires returning a value or throwing an unchecked exception
               throw new RuntimeException("No no-arg constructor found for " + type.getName(), e);
            }
         }
      };

      public static <T> T createInstance(Class<T> clazz) {
         try {
            // .get(clazz) fetches from the cache, or calls computeValue() if missing
            Constructor<?> constructor = CONSTRUCTOR_CACHE.get(clazz);

            @SuppressWarnings("unchecked")
            T instance = (T) constructor.newInstance();
            return instance;

         } catch (Exception e) {
            throw new RuntimeException("Failed to instantiate " + clazz.getName(), e);
         }
      }
   }
}