Why can't a non-static method call a static method?

Preface

Today, I'll analyze why static methods can't call non-static methods, but can instead.This article will analyze the underlying cause from the perspective of byte codes.

Principle analysis

This concludes that static methods belong to classes, and there is no default implied this reference in the method's parameter list. This also indicates that calling static methods does not require a reference to the current object, but calling non-static methods requires a reference to the current object to invoke, so this is the main reason that non-static methods cannot be invoked. Non-static methods belong to instance objects.The method's parameter list has a default implicit this reference, and this is not required to call a static method, so it can be called.

Sample Code

package com.github.xfc.staticmethod;


/**
 * Why static methods cannot call non-static methods
 *
 * @author xf.chen
 * @date 2021/9/2 07:31
 * @since 1.0.0
 */
public class StaticMethodTest {

    /**
     * Non-static method
     */
    public void normalMethod(String data) {
        // Non-static method calls static method
        staticMethod(data);
    }

    /**
     * Static method
     */
    public static void staticMethod(String data) {
        System.out.println("static:" + data);
    }


    public static void main(String[] args) {
        // Static Method Call
        StaticMethodTest.staticMethod("hello");
        // instantiation
        final StaticMethodTest palindrome = new StaticMethodTest();
        // Non-static method call
        palindrome.normalMethod("xf");
        // Invoke non-static method calls through instance objects
        palindrome.staticMethod("chen");
    }

}

The above code is very simple, mainly defining a static method and a non-static method, and adding a main method to test the effect.

The results of the above code execution are as follows

Byte Code

 Last modified 2021-9-2; size 1074 bytes
  MD5 checksum df4162d6ec0e9c271672f25e14c6b50e
  Compiled from "StaticMethodTest.java"
public class com.github.xfc.staticmethod.StaticMethodTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #16.#36        // java/lang/Object."<init>":()V
   #2 = Methodref          #11.#37        // com/github/xfc/staticmethod/StaticMethodTest.staticMethod:(Ljava/lang/String;)V
   #3 = Fieldref           #38.#39        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Class              #40            // java/lang/StringBuilder
   #5 = Methodref          #4.#36         // java/lang/StringBuilder."<init>":()V
   #6 = String             #41            // static:
   #7 = Methodref          #4.#42         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #4.#43         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Methodref          #44.#45        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #10 = String             #46            // hello
  #11 = Class              #47            // com/github/xfc/staticmethod/StaticMethodTest
  #12 = Methodref          #11.#36        // com/github/xfc/staticmethod/StaticMethodTest."<init>":()V
  #13 = String             #48            // xf
  #14 = Methodref          #11.#49        // com/github/xfc/staticmethod/StaticMethodTest.normalMethod:(Ljava/lang/String;)V
  #15 = String             #50            // chen
  #16 = Class              #51            // java/lang/Object
  #17 = Utf8               <init>
  #18 = Utf8               ()V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               LocalVariableTable
  #22 = Utf8               this
  #23 = Utf8               Lcom/github/xfc/staticmethod/StaticMethodTest;
  #24 = Utf8               normalMethod
  #25 = Utf8               (Ljava/lang/String;)V
  #26 = Utf8               data
  #27 = Utf8               Ljava/lang/String;
  #28 = Utf8               staticMethod
  #29 = Utf8               main
  #30 = Utf8               ([Ljava/lang/String;)V
  #31 = Utf8               args
  #32 = Utf8               [Ljava/lang/String;
  #33 = Utf8               palindrome
  #34 = Utf8               SourceFile
  #35 = Utf8               StaticMethodTest.java
  #36 = NameAndType        #17:#18        // "<init>":()V
  #37 = NameAndType        #28:#25        // staticMethod:(Ljava/lang/String;)V
  #38 = Class              #52            // java/lang/System
  #39 = NameAndType        #53:#54        // out:Ljava/io/PrintStream;
  #40 = Utf8               java/lang/StringBuilder
  #41 = Utf8               static:
  #42 = NameAndType        #55:#56        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #43 = NameAndType        #57:#58        // toString:()Ljava/lang/String;
  #44 = Class              #59            // java/io/PrintStream
  #45 = NameAndType        #60:#25        // println:(Ljava/lang/String;)V
  #46 = Utf8               hello
  #47 = Utf8               com/github/xfc/staticmethod/StaticMethodTest
  #48 = Utf8               xf
  #49 = NameAndType        #24:#25        // normalMethod:(Ljava/lang/String;)V
  #50 = Utf8               chen
  #51 = Utf8               java/lang/Object
  #52 = Utf8               java/lang/System
  #53 = Utf8               out
  #54 = Utf8               Ljava/io/PrintStream;
  #55 = Utf8               append
  #56 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #57 = Utf8               toString
  #58 = Utf8               ()Ljava/lang/String;
  #59 = Utf8               java/io/PrintStream
  #60 = Utf8               println
{
  public com.github.xfc.staticmethod.StaticMethodTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 31: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/github/xfc/staticmethod/StaticMethodTest;

  public void normalMethod(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: invokestatic  #2                  // Method staticMethod:(Ljava/lang/String;)V
         4: return
      LineNumberTable:
        line 38: 0
        line 39: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/github/xfc/staticmethod/StaticMethodTest;
            0       5     1  data   Ljava/lang/String;

  public static void staticMethod(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #4                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        10: ldc           #6                  // String static:
        12: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: return
      LineNumberTable:
        line 46: 0
        line 47: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      26     0  data   Ljava/lang/String;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: ldc           #10                 // String hello
         2: invokestatic  #2                  // Method staticMethod:(Ljava/lang/String;)V
         5: new           #11                 // class com/github/xfc/staticmethod/StaticMethodTest
         8: dup
         9: invokespecial #12                 // Method "<init>":()V
        12: astore_1
        13: aload_1
        14: ldc           #13                 // String xf
        16: invokevirtual #14                 // Method normalMethod:(Ljava/lang/String;)V
        19: aload_1
        20: pop
        21: ldc           #15                 // String chen
        23: invokestatic  #2                  // Method staticMethod:(Ljava/lang/String;)V
        26: return
      LineNumberTable:
        line 52: 0
        line 54: 5
        line 56: 13
        line 58: 19
        line 59: 26
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      27     0  args   [Ljava/lang/String;
           13      14     1 palindrome   Lcom/github/xfc/staticmethod/StaticMethodTest;
}

There are a lot of byte codes. Let's split up the analysis one by one.A more readable jclasslib tool is also recommended here.Essentially consistent with the byte code file content described above.

Method Overview

Important to this java code are the following four methods, a default parameterless construction method, and three methods implemented within the source code.

Nonstatic method normalMethod byte code

public void normalMethod(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         // The #2 query constant pool is as follows #2 com/github/xfc/staticmethod/StaticMethodTest.staticMethod: essentially calling static methods directly from a class
         1: invokestatic  #2                  // Method staticMethod:(Ljava/lang/String;)V
         4: return
      LineNumberTable:
        line 38: 0
        line 39: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/github/xfc/staticmethod/StaticMethodTest;
            0       5     1  data   Ljava/lang/String;

As with the above byte code content, our method actually only has one parameter, which is as follows: NoralMethod (String data), but the above byte code hints that locals=2 represents two local variables.So let's look at the local variable table LocalVariableTable

LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/github/xfc/staticmethod/StaticMethodTest;
            0       5     1  data   Ljava/lang/String;

As shown in the code above, the 0th variable is the this reference.The first is the data field inside the code, so the 0 instruction is aload_1, which means pushing the second reference type local variable to the top of the stack.That's because the tenth is this`reference.From the above, it is shown that non-static methods contain implicit object references.

Static Method staticMethodc Byte Code

  public static void staticMethod(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #4                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        10: ldc           #6                  // String static:
        12: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: return
      LineNumberTable:
        line 46: 0
        line 47: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      26     0  data   Ljava/lang/String;

The reference code uses both print statements and string splicing, so there are more byte code commands, and here we focus on locals=1 and LocalVariableTable.The local variable table is as follows

  LocalVariableTable:
    Start  Length  Slot  Name   Signature
        0      26     0  data   Ljava/lang/String;

As you can see from the local variable table above, there is only one data variable and there is no this reference, so the above non-static method cannot be called.You now have a basic understanding of why static methods cannot call non-static methods.

Why can static methods be invoked through instance objects in the next extended analysis?

To put it straight: If you invoke a static method through an instance object, it will be optimized for static method calls during the compilation phase

Code Review

Also through byte code content analysis, look directly at the main method byte code key commands

As shown in the figure above, static methods are invoked directly, so it can be said that the JVM is optimized to invoke static methods directly through classes.The following is decompiled content

conclusion

  • Static methods belong to classes, and there is no default implicit this reference in the method's parameter list. This also indicates that calling a static method does not require a reference to the current object, but calling a non-static method requires a reference to the current object to invoke, so this is the main reason that a non-static method cannot be invoked.
  • A non-static method is an instance object, and its parameter list has a default implicit this reference, while calling a static method does not require this, so it can be called.
  • If you force a static method to be called through an object, the compiler optimizes to call the static method directly.

Personal thinking: In the development process, it is taken for granted that many questions are not considered and validated.So I occasionally encounter problems I don't know where to start.In the future, we should think more and practice more.

Add an instruction document:https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-6.html#jvms-6.5.aload_n

Keywords: Java

Added by donkru on Fri, 03 Sep 2021 05:12:30 +0300