Java Compile-Time vs Runtime: Constant Folding, Overloading, Generics
This article explains key Java concepts such as compile‑time constant folding, the distinction between compile‑time and runtime calculations, method overloading versus overriding, generics type erasure, annotations, exception handling, and aspect‑oriented programming, while illustrating each topic with clear code examples.
When developing and designing Java applications, it is essential to distinguish between compile‑time, runtime and build‑time concepts; understanding these helps grasp many fundamental mechanisms.
Constant folding example
Q: What is the difference between the code marked as line A and line B in the following snippet?
public class ConstantFolding {
static final int number1 = 5;
static final int number2 = 6;
static int number3 = 5;
static int number4 = 6;
public static void main(String[] args) {
int product1 = number1 * number2; // line A
int product2 = number3 * number4; // line B
}
}A: In line A the product is calculated at compile time, while in line B it is calculated at runtime. Decompiling the class file shows:
public class ConstantFolding{
static final int number1 = 5;
static final int number2 = 6;
static int number3 = 5;
static int number4 = 6;
public static void main(String[] args){
int product1 = 30;
int product2 = number3 * number4;
}
}Constant folding is a Java compiler optimisation that evaluates constant expressions during compilation.
When inspecting compiled code is useful
Java generics are implemented via type erasure; examining the compiled class file reveals how generic types are removed and can help solve generic‑related problems.
Compile‑time vs runtime constructs
1. Method overloading – occurs at compile time. The compiler selects the appropriate overloaded method based on the parameter types.
public class Example{
// method #1
public static void evaluate(String param1);
// method #2
public static void evaluate(int param1);
}Calling evaluate("My Test Argument") generates bytecode that invokes method #1.
2. Method overriding – occurs at runtime. The JVM decides which overridden method to invoke based on the actual object type.
public class A {
public int compute(int input){ return 3 * input; }
}
public class B extends A {
@Override
public int compute(int input){ return 4 * input; }
}
public int evaluate(A reference, int arg){
int result = reference.compute(arg);
}If reference holds an instance of B, the overridden method in B is called at runtime.
3. Generics (type erasure) – compile‑time. The compiler checks type correctness and rewrites generic code into non‑generic bytecode. List<String> myList = new ArrayList<String>(10); After compilation this becomes: List myList = new ArrayList(10); 4. Annotations – can be processed at compile time or retained for runtime reflection. Examples include @Override (compile‑time) and @Test (runtime).
public class MyTest{
@Test
public void testEmptyness(){
org.junit.Assert.assertTrue(getList().isEmpty());
}
}5. Exceptions
RuntimeException – unchecked, not required to be declared.
Checked exceptions – must be declared or caught; the compiler verifies them.
6. Aspect‑Oriented Programming (AOP) weaving
Compile‑time weaving – the AspectJ compiler weaves aspects into source code.
Post‑compile (binary) weaving – aspects are woven into compiled class files.
Load‑time weaving – a weaving agent modifies classes as they are loaded by the JVM.
Runtime weaving – aspects are applied to already loaded classes.
7. Other classifications
Inheritance – static, occurs at compile time.
Proxy or composition – dynamic, occurs at runtime.
Composition over inheritance
Inheritance is a mechanism for polymorphism, not for code reuse. Overusing inheritance creates tight coupling; composition provides flexibility at runtime.
Compile‑time vs runtime inheritance in Java
Java only supports compile‑time inheritance via the extends keyword.
public class Parent{
public String saySomething(){ return "Parent is called"; }
}
public class Child extends Parent{
@Override
public String saySomething(){ return super.saySomething() + ", Child is called"; }
}Runtime inheritance can be simulated with composition or proxies:
public class Child{
private Parent parent = new Parent();
public String saySomething(){ return parent.saySomething() + ", Child is called"; }
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
