【经验杂谈】为什么静态方法不能调用非静态方法?

前言

今天分析一下为什么静态方法不能调用非静态方法,但是反过来却可以调用。本文将从字节码的角度分析底层原因。

原理分析

这里先说结论:静态方法属于类,方法的参数列表没有默认隐含的this引用,这也说明了调用静态方法不需要当前对象的引用this,但是调用非静态方法需要当前对象的引用才能调用,所以这就是不能调用非静态方法的主要原因,非静态方法属于实例对象,方法的参数列表有默认隐含的this引用,同时调用静态方法不需要this,因此可以调用。

示例代码

package com.github.xfc.staticmethod;


/**
 * 为什么静态方法不能调用非静态方法
 *
 * @author xf.chen
 * @date 2021/9/2 07:31
 * @since 1.0.0
 */
public class StaticMethodTest {

    /**
     * 非静态方法
     */
    public void normalMethod(String data) {
        // 非静态方法调用静态方法
        staticMethod(data);
    }

    /**
     * 静态方法
     */
    public static void staticMethod(String data) {
        System.out.println("static:" + data);
    }


    public static void main(String[] args) {
        // 静态方法调用
        StaticMethodTest.staticMethod("hello");
        // 实例化
        final StaticMethodTest palindrome = new StaticMethodTest();
        // 非静态方法调用
        palindrome.normalMethod("xf");
        // 通过实例对象调用非静态方法调用
        palindrome.staticMethod("chen");
    }

}

上述代码非常简单,主要定义了一个静态方法和一个非静态方法,同时增加了一个main方法用于测试效果。

上述代码执行结果如下
在这里插入图片描述

字节码

 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;
}

字节码比较多,我们一段一段拆开分析。这里也推荐使用比较可读的jclasslib工具。本质与上述字节码文件内容一致。

方法概览

这个java代码重要就是如下四个方法,一个默认的无参数构造方法,以及源码里面实现的三个方法。
在这里插入图片描述

非静态方法normalMethod字节码
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
         // #2 查询常量池如下#2 com/github/xfc/staticmethod/StaticMethodTest.staticMethod:实际上就是通过类直接调用静态方法
         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;

如上述字节码内容,我们的方法实际上只有一个参数,内容如下normalMethod(String data),但是上述字节码提示locals=2代表有两个本地变量。所以我们看本地变量表LocalVariableTable

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

如上述代码所示,第0个变量为this引用。第一个为代码里面的data字段,所以第0个指令是aload_1,意思是将第2个引用类型本地变量推送至栈顶。这就是因为第0个是this` 引用。通过上述的内容,说明了非静态方法是含有隐含的对象引用的。

静态方法staticMethodc字节码
  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;

引用代码中使用了打印语句同时使用了字符串拼接,因此字节码命令变得比较多,这里我们关注重点locals=1LocalVariableTable。本地变量表如下

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

如上本地变量表可知,只有一个data变量,没有this引用,因此无法调用上面的非静态方法。到此基本知道了为什么静态方法不能调用非静态方法。

接下来扩展分析下为什么通过实例对象可以调用静态方法?

直接说结论:如果你通过实例对象调用静态方法,在编译阶段就会被优化为静态方法调用

代码回顾

在这里插入图片描述

同样通过字节码内容分析,这里直接看main方法字节码的关键命令即可

在这里插入图片描述

如上图所示,直接调用了静态方法,因此可以说明JVM会优化为直接通过类调用静态方法的形式。如下为反编译内容
在这里插入图片描述

结论

  • 静态方法属于类,方法的参数列表没有默认隐含的this引用,这也说明了调用静态方法不需要当前对象的引用this,但是调用非静态方法需要当前对象的引用才能调用,所以这就是不能调用非静态方法的主要原因。
  • 非静态方法属于实例对象,方法的参数列表有默认隐含的this引用,同时调用静态方法不需要this,因此可以调用。
  • 如果强行通过对象调用静态方法,编译器会优化为直接调用静态方法。

个人思考:平时在开发过程中,没有去对很多问题思考和验证,都是想当然的使用。因此偶尔遇到了不明所以的问题,不知道从哪里入手。以后要多思考,多实践。

补充一个指令文档:https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-6.html#jvms-6.5.aload_n

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值