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

3
我已经阅读了这个演示文稿
第26张幻灯片引用如下:
Java language does not allow overloading on return type

Java Virtual machine does allow overloading on return type

这些陈述是正确的吗?如果两个陈述都是正确的,如何使代码可编译以便jvm运行代码?

我在这个主题上有一个SE问题:

Java - 为什么没有基于返回类型的方法重载?

提前致谢。


1
那个问题中的这个答案完全没有矛盾,反而完全一致。 - Tunaki
2
据我所知,返回类型是虚拟机中混淆方法名称的一部分,因此具有相同签名但返回类型不同的两个方法具有不同的混淆名称,从而在JVM中被视为不同的方法。然而,Java语言本身不支持这一点。 - Turing85
2个回答

9
这些陈述是完全正确的。
记住Java有两个方面——一是语言,二是虚拟机。尽管将语言限制为不允许基于类型的方法重载使Java成为一个更简单易用的语言,但JVM仍然可以允许这样做,使其更加强大。
作为一种语言,Java有一个编译器来执行规则,使Java比允许这种规则的语言更简单、更容易编程。为此,它限制了你可以做的事情,但只限于Java语言本身。在JVM上运行类似Scala或Ruby的东西需要不同的规则和特性,在这个层面上,JVM允许灵活性非常重要,这也是JVM如此成功并出现在许多平台和设备上的原因。
在允许按返回类型重载的语言中,这将非常容易出错,而不支持该功能的决定是有意为之,以使Java成为一个较少出错的编程语言。编译器怎么知道你想调用哪个函数呢?
另一方面,JVM是一个低级别、高度优化的虚拟机,存在于运行字节码而不是Java。因此,限制JVM的这种方式是不明智的,因为它应该能够运行根本不是从Java生成的字节码。
另一个例子是多重继承,在Java中不可用,但没有任何东西阻止你编写支持多重继承的语言并将其编译成字节码。这会使你的语言更难使用,潜在地更容易出错,但如果你需要该功能,JVM不会阻止你。

C++也不支持按返回类型重载。我并不反对你的答案,只是你用C++作为例子有问题。 - pcarter
@pcarter - 你是100%正确的 - 我真是个白痴。现在你让我想知道我以前用过什么语言,其中返回类型决定了调用什么。我会立即修改答案,感谢你让我保持诚实。 - Ewald
@Ewald,JVM允许在方法签名相同但返回类型不同的情况下使用相同的方法,例如:int a = methodA(); --> 选择返回int的方法。你能告诉我哪些编程语言使用了这个特性吗? - hqt
@hqt 你是在说基于JVM的语言吗?我对大多数JVM语言的内部工作原理不是很熟悉,所以我不知道。我认为最初的Pascal(和Turbo Pascal?)允许根据返回类型进行覆盖。 - Ewald

6
除了Ewald的回答之外,这里有一个小演示,展示了基于返回类型进行重载是真正可行的。可以有一个单一的类,其中包含两个方法,它们具有相同的名称和参数,只有返回类型不同。
(这是一个纯Java程序的事实,模糊了语言和JVM之间的边界,使得回答“Java”是否允许这种重载变得更加困难,但我认为Ewald已经解释得很好了 - 当然,我为这个演示“作弊”... :))
该程序使用Apache ByteCode Engineering Library (BCEL)在运行时生成和加载这样的类。然后,它创建此类的实例,并列出并调用所有(声明的)方法。
package stackoverflow.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(
            "stackoverflow.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("stackoverflow.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>",
            "stackoverflow.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",
            "stackoverflow.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",
            "stackoverflow.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 stackoverflow.returntypes.Generated.print()
return int
public float stackoverflow.returntypes.Generated.print()
return float

显示同名签名的方法出现了两次,只是返回类型不同-并且使用反射,仍然可以调用这两个方法。


太好了。明天会测试它。 - Ravindra babu
这太棒了。你绕过了标准的Java编译器,真是太狡猾了,但这是一个非常有趣的方法。这表明我真的一点也不懂Java。干得好,你得到了我的支持。 - Ewald

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接