Java 虚拟机是不是允许重载返回类型?

Posted

技术标签:

【中文标题】Java 虚拟机是不是允许重载返回类型?【英文标题】:Does Java virtual machine allow overloading on return type?Java 虚拟机是否允许重载返回类型? 【发布时间】:2016-04-09 08:38:10 【问题描述】:

我已经通过了这个presentation。

Slide No:26 引用了那个

Java language does not allow overloading on return type

Java Virtual machine does allow overloading on return type

这些说法是真的吗?如果两个陈述都成立,如何使代码可编译,以便 jvm 运行代码?

我有一个关于这个主题的 SE 问题:

Java - why no return type based method overloading?

提前致谢。

【问题讨论】:

该问题中的 This answer 完全不矛盾,而是完全同意。 据我所知,返回类型是 VM 中损坏的方法名称的一部分,因此具有相同签名的返回类型执行的两个方法具有不同的损坏名称,因此是不同的方法JVM。然而,java 语言不支持这一点。 【参考方案1】:

这些说法完全正确。

请记住,Java 是两件事——一是语言,二是虚拟机。虽然限制语言不允许基于类型的方法重载使 Java 成为一种更易于使用的语言,但 JVM 仍然可以允许这样做以使其更强大。

作为一种语言,Java 有一个编译器,它强制执行使 Java 成为一种比允许这样做更简单、更容易的编程语言的规则。为此,它限制了您可以做什么,但仅限于 Java 语言本身。在 JVM 上运行 Scala 或 Ruby 之类的东西需要不同的规则和特性,在这个级别上,重要的是 JVM 具有灵活性,这使得 JVM 取得了如此巨大的成功,以至于它可以在如此多的平台和设备上找到。

在一种可以通过返回类型重载的语言中,它很容易出错,不支持该功能的决定是故意使 Java 成为一种不易出错的编程语言。编译器如何知道您打算调用哪个函数?

另一方面,JVM 是一个低级、高度优化的虚拟机,它的存在是为了运行字节码,而不是 Java。因此,以这种方式限制 JVM 是不明智的,因为它应该能够运行根本不是从 Java 生成的字节码。

另一个例子是多重继承,它在 Java 中不可用,但没有什么能阻止您编写支持多重继承并将其编译为字节码的语言。这会使您的语言更难使用,并且可能更容易出错,但如果您需要该功能,JVM 不会阻止您。

【讨论】:

C++ not 也支持按返回类型重载。我并不反对您的回答,只是以您使用 C++ 为例。 @pcarter - 你是 100% 正确的 - 我真是个白痴。现在你让我想知道我过去使用哪种语言,返回类型决定了调用的内容。我会马上修改答案,谢谢你让我诚实。 @Ewald 所以 JVM 允许相同的签名(和不同的返回类型),例如:int a = methodA(); --> choose method that return int. 你能给我使用这个特性的语言吗? @hqt 你在谈论基于 JVM 的语言吗?我对大多数 JVM 语言的内部工作原理并不熟悉,所以我不知道。我认为原始 Pascal(和 Turbo Pascal?)允许根据返回类型进行覆盖。【参考方案2】:

除了answer by Ewald,这里还有一个小演示,它表明实际上可以根据返回类型进行重载。可以有一个类有两个具有相同名称和参数的方法,只是返回类型不同。

(这是一个纯 Java 程序的事实模糊了语言和 JVM 之间的界限,并且使回答“Java”是否允许这种重载的问题更加困难,但我认为 Ewald 已经解释过这很好——当然,我在为这个演示“作弊”......:)

程序使用Apache ByteCode Engineering Library (BCEL) 在运行时生成和加载这样的类。然后,它创建这个类的一个实例,列出并调用所有(声明的)方法。

package ***.returntypes;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import org.apache.bcel.Constants;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.Type;

public class DifferentReturnTypesDemo

    public static void main(String[] args) throws Exception
    
        ClassGenerator classGenerator = new ClassGenerator();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        classGenerator.create(baos);

        ByteArrayClassLoader byteArrayClassLoader = new ByteArrayClassLoader(
            baos.toByteArray());
        Class<?> c = byteArrayClassLoader.loadClass(
            "***.returntypes.Generated");
        byteArrayClassLoader.close();
        Object instance = c.newInstance();
        for (Method method : c.getDeclaredMethods())
        
            System.out.println(method);
            method.invoke(instance, (Object[]) null);
        
    


class ByteArrayClassLoader extends URLClassLoader

    private final byte data[];

    ByteArrayClassLoader(byte data[])
    
        super(new URL[0]);
        this.data = data;
    

    @Override
    protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    
        return defineClass(name, data, 0, data.length);
    


class ClassGenerator

    private InstructionFactory instructionFactory;
    private ConstantPoolGen constantPool_cp;
    private ClassGen classGen;

    public ClassGenerator()
    
        classGen = new ClassGen("***.returntypes.Generated",
            "java.lang.Object", "Generator.java", Constants.ACC_PUBLIC |
                Constants.ACC_SUPER, new String[] );

        constantPool_cp = classGen.getConstantPool();
        instructionFactory = new InstructionFactory(classGen, constantPool_cp);
    

    public void create(OutputStream out) throws IOException
    
        createCreateConstructor();
        createMethodReturningInt();
        createMethodReturningFloat();
        classGen.getJavaClass().dump(out);
    

    private void createCreateConstructor()
    
        InstructionList instructionList = new InstructionList();
        MethodGen method = new MethodGen(Constants.ACC_PUBLIC, Type.VOID,
            Type.NO_ARGS, new String[0], "<init>",
            "***.returntypes.Generated", instructionList,
            constantPool_cp);
        instructionList.append(InstructionFactory.createLoad(Type.OBJECT, 0));
        instructionList.append(instructionFactory.createInvoke(
            "java.lang.Object", "<init>", Type.VOID, Type.NO_ARGS,
            Constants.INVOKESPECIAL));
        instructionList.append(InstructionFactory.createReturn(Type.VOID));
        method.setMaxStack();
        method.setMaxLocals();
        classGen.addMethod(method.getMethod());
        instructionList.dispose();
    

    private void createMethodReturningInt()
    
        // Create a public, no-arguments method named "print" that
        // returns an int value
        InstructionList instructionList = new InstructionList();
        MethodGen method = new MethodGen(Constants.ACC_PUBLIC, Type.INT,
            Type.NO_ARGS, new String[0], "print",
            "***.returntypes.Generated", instructionList,
            constantPool_cp);

        // Generate the "System.out.println" instructions
        instructionList.append(instructionFactory.createFieldAccess(
            "java.lang.System", "out", new ObjectType("java.io.PrintStream"),
            Constants.GETSTATIC));
        instructionList.append(new PUSH(constantPool_cp, "return int"));
        instructionList.append(instructionFactory.createInvoke(
            "java.io.PrintStream", "println", Type.VOID,
            new Type[]  Type.STRING , Constants.INVOKEVIRTUAL));

        // Generate the return instruction
        instructionList.append(new PUSH(constantPool_cp, 123));
        instructionList.append(InstructionFactory.createReturn(Type.INT));

        method.setMaxStack();
        method.setMaxLocals();
        classGen.addMethod(method.getMethod());
        instructionList.dispose();
    

    private void createMethodReturningFloat()
    
        // Create a public, no-arguments method named "print" that
        // returns a float value
        InstructionList instructionList = new InstructionList();
        MethodGen method = new MethodGen(Constants.ACC_PUBLIC, Type.FLOAT,
            Type.NO_ARGS, new String[0], "print",
            "***.returntypes.Generated", instructionList,
            constantPool_cp);

        // Generate the "System.out.println" instructions
        instructionList.append(instructionFactory.createFieldAccess(
            "java.lang.System", "out", new ObjectType("java.io.PrintStream"),
            Constants.GETSTATIC));
        instructionList.append(new PUSH(constantPool_cp, "return float"));
        instructionList.append(instructionFactory.createInvoke(
            "java.io.PrintStream", "println", Type.VOID,
            new Type[]  Type.STRING , Constants.INVOKEVIRTUAL));

        // Generate the return instruction
        instructionList.append(new PUSH(constantPool_cp, 456.789f));
        instructionList.append(InstructionFactory.createReturn(Type.FLOAT));

        method.setMaxStack();
        method.setMaxLocals();
        classGen.addMethod(method.getMethod());
        instructionList.dispose();
    

输出是

public int ***.returntypes.Generated.print()
return int
public float ***.returntypes.Generated.print()
return float

表明签名相同的方法出现两次,只是返回类型不同——而且,使用反射,这两个方法仍然可以被调用。

【讨论】:

优秀。明天测试一下。 这太棒了。您正在绕过标准的 Java 编译器,非常狡猾,但却是一种非常有趣的方法。去表明我真的根本不懂Java。干得好,你得到我的投票。

以上是关于Java 虚拟机是不是允许重载返回类型?的主要内容,如果未能解决你的问题,请参考以下文章

重载和重写

java_函数的重载

Java虚拟机-字节码指令

hyper-v中可以设置的虚拟机网络类型都有哪些

《深入理解Java虚拟机》- 重载与重写

深入理解Java虚拟机——运行时栈帧结构(方法返回地址和附加信息)