Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

A Deep Dive into ClassLoader & Reflection - Dynamic Typing and Runtime Modifiable Classes in Java

Tags: class object

Sign upSign InSign upSign InHabiba GamilFollowLevel Up Coding--ListenShareI like to think of programming paradigms as territories. From imperative to declarative programming, each territory has strict laws of conduct regarding what's lawful and what's not. But it's more often than not that developers find themselves in a territory where they have to do something that's frowned upon or illegal. In these cases, they start looking for loopholes and ways to exploit the system to fulfill their needs. They sometimes discover laws that permit actions that oppose the core values of the system. In this article, I aim to show how two esoteric Java features can allow for useful antipatterns in the Java world.Object-oriented programming is one of the most commonly taught and used programming models. It specifies that logic should be built around program-defined data types. When designing programs in Java, programmers first design the core data types needed in the program then incorporate logic into their classes and define how these classes interact with each other.While this approach certainly has well-acclaimed benefits, a statically typed language such as Java lacks flexibility. It requires strict definitions for data types at compile time. This is not always feasible as developers can often encounter situations where they can’t anticipate at the time of development the exact data types their program will deal with. Moreover, Java programs do not have the ability to evolve their classes at runtime. This can also be a problem in a lot of cases. Consider the following design problems:A framework, in simple terms, is a code base that facilitates development by providing a structure of components that framework users can build on, a skeleton of sorts. User-defined types can be plugged into the framework for customization. For example, a test engineer might realize that they often implement the same steps for testing a service’s API with variance in code mainly in the datatypes representing the service itself. They recognize that a lot of their code is reusable and could be generalized into a framework that would be used in future service testing. Pure OOP provides a challenge in this case, which is that they have to build logic for ambiguous data types. A helpful framework is one that is general enough that it could be used to test a wide range of services. This fundamentally goes against statically typed languages such as Java which require data types to be manipulated through defined classes.Hot-swappable Software is software that allows for altering its components at runtime without an execution pause. For example, it's often very costly for a business to take its application down for code updates and hence it might attempt to design apps whose APIs still remain modifiable after deployment. The first challenge faced in this problem is that similar to frameworks, logic must be built around modifiable alterable components and hence ambiguous data types. Moreover, this problem has the added complexity of loading new code at runtime which is a separate additional challenge.The above two examples show two challenging problems in pure Java object-oriented programming:I explored a pairing of two Java features, reflection and Class loaders, to solve the above two problems and allow for incredibly flexible Java programs.This article is quite lengthy and dense, here’s everything discussed as a reference:Perhaps the most marketed feature of Java is the fact that it's a statically typed language.Given some Class Foo:Normally, the method doSomething() is invoked as follows:The above code is an example of static typing and invocation. The variable foo is said to be statically typed as its type is resolved to the Foo type at compile time. Similarly, the compiler links the doSomething() method invocation to the instance method defined in the Foo Class. During static typing and invocation, things like variables and method invocations can be viewed as if they commit to certain defined types before execution. This requires class definitions to come first for successful compilation.Naturally, generic frameworks that are designed to work with any type without restricting to interfaces can’t use static typing and invocation. Instead, what enables such frameworks in Java is an obscure style of programming known as reflective programming. Reflective programming allows breaking the statically typed nature of Java programs. It pushes type assignment to runtime instead of compile time, this is known as dynamic typing.According to the Java Reflection in Action (by Ira R. Forman, Nate Forman):Reflection is the ability of a running program to examine itself and its software environment, and to change what it does depending on what it finds.I would classify a developer's main work into two categories, the first is the creative aspect in designing robust maintainable programs. The second is the programming equivalent to mechanical labor work. For instance, developers often write code that depends on external modules or components. When an external module is modified, it's not uncommon for a developer to find themselves going through their code and rewriting their method calls to work with the updated module.Reflective programming can allow a program to do that instead. Instead of programmers having to do manual tedious tasks like refactoring code, patching JARs, and modifying method invocations, Reflection allows writing programs that can make choices usually made by humans like choosing between class X and class Y or invoking a new method instead of an old one.For that to make sense, one must understand how classes exist inside the Java Virtual Machine (JVM). The JVM is an isolated environment through which Java programs are executed. For the most part, it's not aware of its host device and its file system, meaning that it's not aware of the .java or .class files that are created during development and compilation (more on this in the class loaders section). The JVM has its own internal memory through which it stores all the necessary data it needs at runtime; this of course includes data about the program's classes. Its memory is composed of multiple components (Method Area, Heap Area, Stack Area, PC registers, Native Method Area)The Heap AreaThe heap area of the JVM is a dynamic memory space that stores the program's current objects, it’s where objects live and exist as entities. It's mainly divided into three areas: young generation, old generation, and Metaspace.For Java to behave the way we expect it to, the JVM must track metadata about the program which allows it to execute correctly. For example, for a particular class, the JVM must store info such as its access modifiers, methods, and their types (static method or instance method). For each method, it stores info such as its number of parameters and their types, and the methods return type. The JVM stores a program's metadata the way it knows best, though objects referred to as metaobjects. Java creators defined a special set of classes used internally by the JVM, these classes represent components of the program and give access to them. Things like a program’s classes, interfaces, and methods are modeled as objects that live in MetaSpace (Lives true to its name, everything in Java is an object, even its classes!).The java.lang package defines a class for type Class:This class defines the metaobject that represents a class. Every user-defined class will have a corresponding Class object instantiated for it in the JVM. The class Class defines a lot of interesting methods, most of which give information about the class’s structure:Similarly, java.lang defines class Method which defines metaobjects representing a method. It also contains a handful of APIs that can provide information about the structure of the particular underlying method modeled by a Method object:I would encourage you to explore these classes’ APIs and other metaobjects like Field and Annotation through the official documentation. The collective metaobjects in the MetaSpace represent the program itself.A Java type is alive at runtime. It's not a static entity but rather a living breathing object in the JVM.While it has a dedicated part of the heap, metaobjects in the MetaSpace are not restricted to internal JVM use only, they are exposed to running programs. A program can perform self-inspection by accessing the metaobjects that represent it. Moreover, metaobjects give access to the part of the program they represent. As shown above, the class Class defines a newInstance()method through which it can return an object instance of the class it represents. Similarly, class Method defines theinvoke() method through which a method it represents can be invoked.Going back to the reflection definition:Reflection is the ability of a running program to examine itself and its software environment (by accessing metaobjects such as Class objects representing its classes and discovering their internal structure), and to change what it does depending on what it finds (as they can choose to invoke the part of a program represented by a particular metaobject).okay... but how does that exactly come into play?As I hinted earlier in the section, the main need for reflection comes when dynamic typing and invocation are needed. Invoking the doSomething() method of the Foo class can be done alternatively using reflection as follows:The above code performs dynamic typing and invocation. Given specified className and methodName variables, it reflectively gets the Class object of the target class and retrieves the Method object of the target method through which it invokes the method. Notice how setting the two variables to a different class name and method name will result in the same code executing a different method as it isn’t coupled to any particular type at compile time.and finally….JUnit is a popular automation testing framework for Java applications. It automates many aspects of testing like displaying aggregated test result reports or running repeatable test cases. What’s useful in JUnit is that it's generic, it can invoke test methods of any user-defined class, and users don’t even need to adhere to an interface when designing their test classes. JUnit mainly relies on Annotations for configuration. Annotations are part of the Java language that is used to add metadata or extra information to Java programs without having any direct effect on the code they annotate. JUnit defines the @Test annotation to “mark” test methods, meaning that JUnit knows which user methods to run through this annotation. For example, a user can define the following class:JUnit runs methods test1() and test2() since they are annotated with the @Test annotation. What enables JUnit to work as intended is its extensive use of reflection. It uses reflection to discover user-defined classes at runtime and inspects them to find methods having the @Test annotation. It then dynamically invokes these methods. The base logic that operates the framework is actually relatively simple to implement using the reflection API. Its logic is as follows:The above steps are shown in the code below:Note: the above code is an example I wrote and does not belong to JUnit. However, JUnit does use reflection in its operations.While Reflection sounds initially unintuitive (and arguably produces the ugliest code), it proves to be a very useful feature when the statically typed nature of Java is restricting. Moreover, it allows developers to design dynamic systems. It also allows the automation of a lot of laborious aspects of programming. In the next section, I will explore another interesting feature of the JVM, its class loaders, which when paired with reflection allow for fundamentally open expandable systems.In Object Oriented Programming, the unit of software is a class, it represents a logical entity or block. How classes are loaded into the JVM isn’t usually of much concern or interest, classes just conveniently exist when needed. However, learning the internals of class loading in Java is useful as The JVM’s internal class loading system is actually exposed to programmers, who can intercept the class loading process and change the behavior of the JVM when needed.This has a variety of benefits and practical uses. One of them is controlling the exact class files that get loaded into the JVM, this can prevent dependency loading issues and conflicts. Another benefit to class loaders is that they allow introducing new behavior to a running program by dynamically loading new classes, this is the basis for creating plugin architectures and expandable programs in Java. Before diving into the internals of the class loading system in Java, one must understand Java’s execution model.Java takes a hybrid approach between compilation and interpretation. A Java class is first defined by a programmer in a .java file format. The Java compiler then transforms it into bytecode generating the .class file. Byte code is an intermediate language which is understood by the JVM.Java programs are executed through the Java Virtual Machine (JVM). The idea behind the Java virtual machine is that it acts like an abstract virtual computer that creates an isolated environment for running Java programs. A running JVM is mostly unaware of its host computer and defines a strict protocol for loading program files from the computer’s file system and into it for execution.The JVM consists of 3 distinct areas: ClassLoader System, Runtime Memory/Data Area, and Execution Engine. The Class Loader subsystem is the only component of the JVM that deals with the host device’s file system. It's responsible for locating class files and loading classes into the JVM. When requested to load a class, the class loader system reads its class file bytecode into the JVM then constructs the equivalent Class object and stores its methods bytecode in the heap (Recall that classes exist inside the JVM as Class objects). The steps are shown in the following diagram:The process of loading classes in the JVM is dynamic, meaning that classes aren’t preloaded but rather loaded on demand, this is known as lazy loading. On program start, the JVM loads the class containing the main() method and all the classes it references. Afterward, whenever a new type is encountered, the ClassLoader system is asked to load it. Users can define their own class loaders and integrate them with the class loading system for custom class loading.The Class Loader subsystem is composed of multiple class loader components working together, each capable of loading certain classes. It shouldn’t come as a surprise that class loaders are modeled as classes in the JVM. An individual class loader is an object of a special internal Java class (Java being Java!). The java.lang package defines abstract type ClassLoader which is essentially a class that is responsible for loading other classes into the JVM.Concrete subclasses of ClassLoader define different types of class loaders. The main difference between ClassLoader concrete types is in the types of classes they load and the paths through which they look for class files. According to the Java docs:Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system.Class loaders are structured as a hierarchy with parent-child relationships, meaning that a class loader object is a child to another class loader and is possibly a parent to some other class loader(s). There are three main class loaders used at runtime:What dictates which class loader loads a particular class is done through the Delegation hierarchy principle. This principle dictates that when a class loader receives a request to load a class, it first attempts to delegate the request to its parent. If the parent fails at loading this class, it then attempts to load it itself by searching for the .class file in its defined paths. The below diagram shows a high-level overview of the steps taken by the Class Loader Subsystem when receiving a request to load a class:If the requested class is an application-specific class defined by the programmer, it will exist in the system classpath. The Application class loader initially receives the request. It delegates to the Extension class loader which in turn delegates to the Bootstrap class loader. The Bootstrap class loader fails to find the class in its paths and hence the responsibility is delegated back to the Extension class loader which fails as well. The responsibility then is handed back to the Application class loader which should find the class if it exists.The abstract ClassLoader class is extended to define particular class loaders in the system like the Extension class loader and the Application class loader. ClassLoader defines 4 main methods used in class loading:As seen in the above code,loadClass() first begins by checking if the class has been loaded before using findLoadedClass()(The ClassLoader class keeps track of previously loaded classes to prevent any confusion in the system that might arise if the same class was loaded multiple times). If the class wasn’t loaded before, it asks its parent class loader to load it by calling its parent’s loadClass(). If the parent fails to load the class (returns ClassNotFoundException), it calls its own findClass() to attempt to load the class. If findClass() fails to return the Class object then the method throws ClassNotFoundException.As mentioned earlier, the class loading subsystem is exposed to programmers who can use it to load classes at runtime. The following is an example of a function that uses the Application Class Loader to load a new class at runtime:Programmers can also extend ClassLoader and define their own class loaders which can actively participate in the class loading process. For example, they can define their own CustomClassLoader class and use it for custom class loading.They might do that for multiple reasons. Each class loader in the system has a set of defined paths through which it looks for class files. A developer might need to load class files from an unconventional location like an external database or repository and hence needs to define a specialized class loader that connects to and loads from these external locations.Another reason could be to change some behavior in the class loading system. For example, the class loader system prevents loading a class more than once. A developer that needs to reload classes in their application might define a specialized class loader that overrides the default behavior and allows loading a class several times.While there are no strictly set rules or best practices. Real uses of class loaders show the following patterns in overriding ClassLoader behavior:The common requirement in all class loader use cases is the need to introduce new behavior to a program at runtime. The use of class loaders is usually tied with reflection as reflective programming enables programs to inspect and use newly introduced classes and hence integrate new types in the program. The next two practical examples are popular uses for class loaders, each needing to override ClassLoader behavior in a different way.Plugins are small programs that could be incorporated into a running application to extend it and provide new functionality. Consider a code editor like Visual Studio code, it aids development tasks like debugging, code testing, and version control. It also comes with an associated marketplace that has thousands of extensions that users can download. Extensions range from essentials like code editing for a certain programming language to luxury extensions like the Prettier, a code formatter used for a consistent code style (e.g. wrappers, indentation), and Better Comments extension which creates colored human-friendly code comments. Visual Studio code provides these extensions in a plug-and-play fashion.While VS code isn’t written in Java, the idea behind plugin architecture is the same, the need for extending the system or plugging new functionality at runtime. Plugin functionality in a Java application is done with the aid of reflection and class loaders. Creating a simple plugin could be done in the following steps:For the first step, a unified interface should be defined through which the application can run any plugin.All plugins should have a class implementing this interface. This interface acts as a marker. When loading plugins, the application reflectively inspects the classes to search for the class that implements this interface. It contains the run() method which is the entry point for running the plugin. Alternatively, annotations could be used to mark a plugin’s run function similar to the JUnit example.Secondly, a special class loader is defined for loading the plugin classes. This is desirable for many reasons:It would be neater to define a special path for all plugin code. The plugin class loader searches for class files in this path and could be defined as follows:The PluginClassLoader overrides findClass() which loads classes from the plugin path which is set in the path variable. (Note how PluginClassLoader does not override loadClass() since plugin class loaders shouldn’t interfere with the delegation model)Finally, a protocol needs to be established for installing and running plugins, that is how the application adds, stores, and runs plugins and the steps taken by application users to install and run their custom plugins. Externally, installation steps can be:Internally, the application can define a PluginManager class that handles installing and running plugins (This class’s functions are linked to the plugin install/run commands or buttons). It can store all currently installed plugins in a hashtable containing (plugin name, Plugin object) pairs and define functions loadPlugin() and runPlugin() for loading and running plugins respectively. The following is an example of PluginManager:As I mentioned earlier, every valid plugin should have a class implementing the Plugin interface. The loadPlugin() method creates an instance of PluginClassLoader and uses it to load all plugin classes from the specified plugin folder. It uses reflection to inspect plugin classes and discover the class that implements the Plugin interface and creates an instance of it. This instance is then stored in the plugins hashtable to run the plugin when needed. The runPlugin() method runs a specific plugin by getting its Plugin object from the plugins hashtable invoking the run() method.The PluginManager class allows additional functionality to be added to a running application. It does this using class loaders and reflection. The next example is a more complex use of class loaders.It's quite often that business requirements and operations change. Services or APIs provided by a business’s software can require modification down the line. Sometimes these modifications need to be done dynamically at runtime without a service restart. Since the unit of software in Java is a class, Java applications can dynamically update their logic by modifying their classes.Dynamic class modification is a challenging problem and still an ongoing area of research in the Java world. There are several approaches to achieve this with some modifying the compiler and others modifying the JVM. However, users of the Java programming language can achieve the effect of class modification by carefully crafting their programs with the aid of class loaders and reflection. Class loaders can’t directly modify already loaded classes but they can reload the class file of an updated class. To achieve the class modification effect, the newly loaded class should replace the old class, this can be done using design patterns.An active class is a loaded class that has instances in the JVM. Replacing an active class is a complex problem as there are many steps involved:For the first step in replacing a class, the updated class file must be loaded into the JVM. There is a nuance to this, which is that the ClassLoader class keeps track of all previously loaded classes by default and prevents loading the same class twice (check loadClass() implementation above). As mentioned earlier, a class in the JVM is identified by both its fully-qualified name (which contains the class’s name and the package which the class was loaded from like org.example.Foo ) and the class loader that loaded it. A class Foo loaded twice by two different class loaders will be seen as two different classes in the JVM (will have two different Class objects). There are two possible workarounds to reload a class Foo after it was modified:CustomClassLoader overrides loadClass() and always loads a modifiable class Foo when requested to load it (it does this by calling itsfindClass() method which reads the most updated class file and returns the new Class object). This will allow the Foo class to be reloaded on demand. Notice that reloading Foo does not have any direct effect on the application. Here’s how the system looks after the Foo class is reloaded after some update to its class file:Foo version 2 Class Object is not “seen” by the application and Foo version 1 is still in use and has active instances. There is no direct way to replace the Foo version 1 Class object with the Foo version 2 Class object. The next step is redirecting the application to use Foo Version 2. This is done using factory pattern and reflection.Factory pattern is a creational pattern mainly used when object creation logic needs to be abstracted. This is done by creating a Factory class which is responsible for creating objects of a certain type. Internally this factory can be configured with the concrete types it should create with the desired rules. According to the classic Design Patterns: Elements of Reusable Object-Oriented Software book:The key need for the factory pattern is that one object needs to create other objects but can’t know which particular concrete object to create beforehandRephrasing for our use case, a need for the factory pattern would be when one object needs to create other objects but can’t know which version of the object to create beforehand. For the aim of reloading classes, a modifiable class should have a Factory object. This object keeps track of the most updated version of a class by saving its Class object. It's also responsible for creating Foo objects and does so reflectively.The first step in using the factory pattern is creating an interface for the modifiable class. This allows all dependent code to treat all the different versions of Foo the same.A modifiable class (ex. ConcreteFoo) implements the Foo interface. Next, a FooFactory is created as follows:The FooFactory keeps track of the current Foo implementation using the implClass variable. It defines a newInstance() method which is a factory method as it “manufactures” a Foo object. It uses reflection to instantiate a Foo object using the current implementing class. The reload() method is called whenever the class is updated, it uses the CustomClassLoader to load the new Class object and then updates the implClass variable. This causes all subsequent calls to newInstance() to return instances of the updated version of the class.Creating Foo objects across the application should happen exclusively through the FooFactory class to ensure consistency in the behavior of a Foo type. So far, the FooFactory ensures that all future uses of Foo will use the most updated class. The following code reloads a modifiable class and then creates a new instance of it using the FooFactory:Here’s how the system looks after the execution of the above code:While definitely a step in the right direction since all subsequent usage of Foo will use Foo Version 2 (the updated class), old instances of Foo Version 1 still need to be replaced by equivalent instances of Foo Version 2 to complete the replacement.An object exists in Java because it is referenced by some variable. Once an object is no longer used by any variable, it’s garbage-collected (deleted from the heap). If Foo objects exist at some point during execution, it's because some variables belonging to some objects are referencing them. For example, a class Bar might use Foo objects in its internal methods and hence, its references should ideally be updated to use the latest version of Foo for a full replacement effect to take place throughout the application.Keeping track of all program objects that have internal references to Foo objects and updating their references would be a maintenance nightmare, especially if Foo objects are heavily used throughout the program or are used by other modifiable classes. Instead, the proxy pattern offers a neat solution. As a start, the Foo interface is updated as follows:The added evolve() method specifies that each class implementing the Foo interface must define logic for creating an instance of itself that can act as a replacement for another Foo object. This method will be used to map objects of an old Foo implementation to objects from a new implementation.Next Proxy pattern is used to replace old class instances.A Proxy in Java is an object which acts as a substitute for another object. It externally looks and acts like another object by implementing its interfaces but internally forwards its method calls to an actual instance of the object it imitates (its target), it might do extra processing before forwarding method calls to its target. Below is the sequence diagram of a proxy’s method call:The key functionality achieved by the proxy pattern is that it allows method intercession. Interceding a method call means dynamically controlling the behavior that results from a method call. The proxy object is a middleman between the client code and a target object and can decide the resulting behavior from a method call. It can, for example, forward the method to a different target object or not forward the method call at all.When reloading a class, running instances of the old version might still be used by other active classes in the application. For modifiable class Foo, Foo proxies can intercede method calls made by other application classes and forward the method call to the correct version of a Foo object. Consider the following:Using Foo proxies prevents other application classes from directly using Foo objects. This allows controlling the usage of the Foo class and simplifies interchanging Foo objects to simply setting the target of a proxy and not being concerned with the outside environment of the proxy and how Foo objects are being used in the system.Factory pattern can be used to implement replaceable classes while Proxy pattern can be used to implement replaceable objects.Before diving into the implementation. There are two important terminologies:A proxy class is a class that implements an interface or a list of interfaces while a proxy instance is an instance of a proxy class.Normally, to use the proxy pattern with a Foo type. Two classes need to be defined. The first is a legitimate class that implements the Foo interface while the second is the proxy class which also implements the Foo interface but has an internal target Foo instance to which it forwards its method its calls to.Java’s reflection utilities (java.lang.reflect package) define Proxy API which automates the above steps. Java’s Proxy can dynamically create a proxy class given a list of interfaces and return proxy instances of it. The following is the partial definition of Proxy:Without diving into much of the class’s details. The method that is mainly used in creating proxies is the newProxyInstance() method. This method returns a proxy object given a list of interfaces. It first checks if the input array of interfaces has a proxy class implementing them. If they don’t, one is created for them dynamically before returning an instance of it.This method takes 3 arguments:The newProxyInstance() method simply creates a class that implements a set of interfaces to act as a proxy class and returns an instance of that class. Programmers still need to define what exactly should a proxy object do when receiving a method call. This is done through invocation handlers. An invocation handler is an object responsible for handling all method calls received by a proxy object. In other words, each proxy has an associated InvocationHandler object to which it forwards its requests.The following is the InvocationHandler interface definition:A proxy object forwards its method calls to its InvocationHandler using the invoke() method. Its arguments are:These inputs are all that an invocation handler needs to invoke a specific method. Developers create their custom InvocationHandler for their own use case. The following is the invocation handler defined for Foo:The FooIH class has a Foo target variable which stores its target object. This object is an instance of the most updated version of a modifiable Foo class. Its invoke()method does not do any preprocessing but simply reflectively invokes the input method using its target object.The following diagram shows all the objects involved when a proxy object method is invoked:The proxy object forwards a method call to its InvocationHandler which in turn uses reflection to invoke the method on its target using the Method object of the invoked method.This is the updated FooFactory class after adding proxy logic:The following are the main updates in FooFactory:Note the usage of WeakReference object wrappers on the proxies list instead of storing a regular ArrayList of proxy objects.The reason behind this is that by directly storing references to Foo proxies, they are never garbage collected, even when they are no longer being used by the application. The WeakReference type allows referencing an object but does not prevent it from being garbage collected if it doesn’t have at least one strong reference. When an object is garbage collected, it’s WeakReference returns null.The FooFactory now can completely replace a Foo Class and all its instances from the system. When reload() is invoked, an active modifiable Foo class will be transformed as follows:This is indeed a complete class replacement! Note that after a while, the JVM will unload Foo Version 1 as it's no longer being used.In this article, I explored Reflection and Class Loaders and their possible uses. Reflection allows dynamic typing and invocation while class loaders can dynamically load classes at runtime. They allow for flexible, configurable, and expandable systems. As useful as they are in some cases, there are a few things to keep in mind:Note: I’m working on a mini-application that serves as a full example that provides plugin functionality and modifiable classes. I will post its GitHub link here once finished.----Level Up CodingComputer Science Engineer - I love writing about Software Engineering and lifeVictor TimiinLevel Up Coding--31Arslan AhmadinLevel Up Coding--17Arslan AhmadinLevel Up Coding--24Attila VágóinLevel Up Coding--103Voice Of Silence--The Code Bean--Suvesh Agnihotri--Prabhash Dilhan Akmeemana--Berkay HaberalinStackademic--6mehmoodGhaffar--1HelpStatusWritersBlogCareersPrivacyTermsAboutText to speechTeams



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

A Deep Dive into ClassLoader & Reflection - Dynamic Typing and Runtime Modifiable Classes in Java

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×