如何从打击Java代码生成Go代码?

I want to write a Java2Go generator, but I find it hard to express polymorphism (e.g.: formal arg is Base class, but real arg is Sub class), how do I express blow code in Go?

class Base{
    public int i;     
  }

class Sub extends Base{ 
  }

class Test{
   public static int test(Base base){
          base.i = 99;
          return base.i;
   }
   public static void main(String [] args){
       Sub sub = new Sub();
       System.out.println(test(sub));
   }
}

You'll need to duplicate your code or make wrappers that calls a common utility function that it'll basically be madness.

There's no elegant way to do a "function by function translation". The elegant way to do it is to write your program in a different way.

A Go program will fundamentally have to be structured differently. From years of writing object oriented code it's a hard habit to shake, but when I figure out the "Go-like" way to solve the problems it usually works out to be simpler over time.

Having proper inheritance and all that appears to save code and keep things neat and tidy, but - at least in my experience - over years of development it easily ends up like a tangled mess, or at least hard to dig into if you haven't worked with the code for a while.

Go's interfaces are much more limited, but it'll force you to keep things simpler and more "obvious". The separation between the different classes is explicit.

There are also some advantages; it's much easier to "mix" types than with inheritance after you get some experience how to do it. Another "trick" is that a type can satisfy more than one interface for example.

If you've been writing object oriented code forever then it'll take some practice getting used to the new tools. I'd suggest instead of writing a translator, just try to write some Go based tools. It'll be fun. :-)

Here's what I came up with.

  • When extending a class, add the parent class as a embedded struct and attach a hidden method to the child class/struct to convert back to the parent class/struct.
  • For functions that accept Objects, have them accept pointers to structs.

Run from start.go

File: Test.go

package Test
import (
  "fmt"
)
type Base struct{
    I int
}
type Sub struct {
   Base
}
func (s *Sub) _toBase() *Base{
  return &s.Base
}
func Test( base *Base) int{
      base.I = 99;
      return base.I;
}
func Main( args []string) error{
   sub := Sub{}
   fmt.Println(Test(sub._toBase()));
   return nil
}

File: start.go

package main
import (
    "so-java2go/javaStructs/Test"
    "os"
)
func main(){
    Test.Main(os.Args)
}

File: Test.java

class Base{
    public int i;     
  }

class Sub extends Base{ 
  }

class Test{
   public static int test(Base base){
          base.i = 99;
          return base.i;
   }
   public static void main(String [] args){
       Sub sub = new Sub();
       System.out.println(test(sub));
   }
}

If you just have member variables in your classes, then you can use embedding. However, this solution isn't going to extend to the case where you also want methods on your classes, which can be overridden in subclasses, because the name-collisions will prevent your Go code from compiling.

You could compile classes to a raw-memory struct with a vtable (as if you were compiling to assembler or C), but then you'd have to implement your own garbage collector. Assuming that's not what you want, you can extend the vtable idea to include methods to return the address of member variables, which will allow you to use Go interfaces as a cheap way of implementing the vtable. Here's some code, slightly compressed to reduce the appearance of the boilerplate.

package main

import "fmt"

type Base struct {
    i int
}
func (b *Base) get_i() *int { return &b.i }
func NewBase() *Base { return &Base{} }

type Sub struct {
    parent Base
}
func NewSub() *Sub { return &Sub{*NewBase()} }
func (s *Sub) get_i() *int { return s.parent.get_i() }

type BaseI interface {
    get_i() *int
}

func test(b BaseI) int {
    *b.get_i() = 99
    return *b.get_i()
}

func main() {
    s := NewSub()
    fmt.Println(test(s))
}

Methods will need to be name-mangled, since Java allows overloading. You'll find it fun to figure out exactly which method needs to be called at a given call site, depending on the type of the object and the types of all the method arguments :)

In fact, lots of things will end up needing to be name-mangled. For example, the above code translates the class names 'Base' and 'Sub' directly, but what if I'd called one of the classes 'main', or I'd called 'Sub', 'NewBase' (which didn't appear in the original Java source, but appeared during the translation process)? Typically, translated code would look more like this to avoid these sorts of problems:

type Java_class_Base struct {
    Java_member_i Java_basetype_int
}

There's plenty of other hurdles. For example, the code above translates Java int to Go int, but the two are not the same. Go's int32 is closer, but still behaves differently (for example, Java specifies what happens on overflow, but Go doesn't). This means that even a simple expression like x = x + 1 is hard to translate, since you're going to have to write your own add function to make sure the translated code behaves identically to the Java code. Another example is that every object can potentially act as a thread-reentrant lock (since it can be synchronised on). That means that you're going to have to decide how to translate that, which is going to involve having a notion of Java thread, and being able to figure out which Java thread your translated call is executing on.

Good luck! There's a lot of difficulties ahead, but it's a fun challenge to make a compiler.