英文:
How to generate Go code from blow Java code?
问题
我想编写一个Java2Go生成器,但我发现很难表达多态性(例如:形式参数是基类,但实际参数是子类),我该如何在Go中表达下面的代码?
package main
import "fmt"
type Base struct {
i int
}
type Sub struct {
Base
}
func test(base *Base) int {
base.i = 99
return base.i
}
func main() {
sub := &Sub{}
fmt.Println(test(&sub.Base))
}
在Go中,我们可以使用嵌入结构体的方式来实现继承。在上述代码中,我们定义了一个Base
结构体和一个嵌入了Base
结构体的Sub
结构体。然后,我们定义了一个接受*Base
类型参数的test
函数,通过指针修改base.i
的值,并返回该值。在main
函数中,我们创建了一个Sub
类型的实例sub
,并将&sub.Base
作为参数传递给test
函数进行调用。最后,我们使用fmt.Println
打印出test
函数的返回值。
希望这可以帮助到你!如果有任何其他问题,请随时问我。
英文:
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));
}
}
答案1
得分: 3
你需要复制你的代码或者创建调用一个公共实用函数的包装器,这基本上是一种疯狂的做法。
没有一种优雅的方法来进行“逐个函数翻译”。优雅的方法是以不同的方式编写程序。
Go程序的结构必须有根本性的不同。多年来编写面向对象的代码已经成为一种难以改变的习惯,但当我找到“Go式”的解决问题的方式时,通常会变得更简单。
拥有适当的继承和其他功能似乎可以节省代码并保持整洁,但至少在我的经验中,多年的开发很容易变成一团乱麻,或者至少在一段时间内没有使用代码时很难理解。
Go的接口更加有限,但它会迫使你保持简单和“明显”。不同类之间的分离是显式的。
还有一些优点;在你掌握一些经验后,混合类型比继承要容易得多。另一个“技巧”是一个类型可以满足多个接口。
如果你一直在编写面向对象的代码,那么习惯于使用新工具可能需要一些练习。我建议你不要写一个翻译器,而是尝试编写一些基于Go的工具。这会很有趣的。:-)
英文:
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.
答案2
得分: 0
这是我想到的解决方案。
- 在扩展一个类时,将父类作为嵌入的结构体,并在子类/结构体中添加一个隐藏方法,用于将其转换回父类/结构体。
- 对于接受对象的函数,使其接受结构体的指针。
从start.go运行。
文件: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
}
文件:start.go
package main
import (
"so-java2go/javaStructs/Test"
"os"
)
func main(){
Test.Main(os.Args)
}
文件: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));
}
}
英文:
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));
}
}
答案3
得分: 0
如果你的类中只有成员变量,那么你可以使用嵌入。然而,这种解决方案无法扩展到你还想在类中使用方法的情况,这些方法可以在子类中被重写,因为名称冲突会导致你的 Go 代码无法编译。
你可以将类编译为带有 vtable 的原始内存结构体(就像编译成汇编语言或 C 语言一样),但是你将不得不实现自己的垃圾回收器。假设这不是你想要的,你可以扩展 vtable 的想法,包括返回成员变量地址的方法,这将允许你使用 Go 接口作为实现 vtable 的一种廉价方式。下面是一些代码,稍微压缩以减少样板代码的出现。
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))
}
方法需要进行名称重整,因为 Java 允许重载。你会发现,在给定的调用点上,根据对象的类型和所有方法参数的类型,确定应该调用哪个方法是很有趣的
实际上,很多东西最终都需要进行名称重整。例如,上面的代码直接翻译了类名 'Base' 和 'Sub',但如果我将其中一个类命名为 'main',或者我将 'Sub'、'NewBase'(在原始的 Java 源代码中没有出现,但在翻译过程中出现了)命名为 'NewBase',那该怎么办呢?为了避免这些问题,通常翻译的代码会更像这样:
type Java_class_Base struct {
Java_member_i Java_basetype_int
}
还有很多其他的障碍。例如,上面的代码将 Java 的 int
翻译为 Go 的 int
,但两者并不相同。Go 的 int32
更接近,但仍然有不同的行为(例如,Java 指定了溢出时会发生什么,但 Go 没有)。这意味着即使是一个简单的表达式 x = x + 1
也很难翻译,因为你将不得不编写自己的加法函数,以确保翻译后的代码与 Java 代码的行为完全相同。另一个例子是每个对象都可以作为可重入锁(因为它可以被 synchronized
)。这意味着你将不得不决定如何翻译它,这将涉及到对 Java 线程的概念,并能够确定你的翻译调用在哪个 Java 线程上执行。
祝你好运!前方有很多困难,但将其作为一个有趣的挑战来制作一个编译器。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论