找回密码
 会员注册
查看: 22|回复: 0

Golang与Java全方位对比总结

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64454
发表于 2024-9-20 16:44:42 | 显示全部楼层 |阅读模式
作者:xindong本文针对Golang与Java的基础语法、结构体函数、异常处理、并发编程及垃圾回收、资源消耗等各方面的差异进行对比总结,有不准确、不到位的地方还请大家不吝赐教。一、基础语法Golang:编码风格及可见域规则严格且简单;Java:来说层次接口清晰、规范,主要表现有以下这些。1、变量a、变量声明及使用在Java中:变量可以声明了却不使用publicstaticStringtoString(intnum){intdata=num;returnString.valueOf(num);}  Golang中:声明的变量必须被使用,否则需要使用_来替代掉变量名,表明该变量不会比使用到func toString(num int) string { data := num     // data没有使用者,无法编译 return strconv.Itoa(num)}func toString(num int) string { _ := num       // 正常编译 return strconv.Itoa(num)}b、变量声明及初始化在Java中:如果在方法内部声明一个变量但不初始化,在使用时会出现编译错误;public void compareVariable() {        int age;        Object object;        System.out.println(age); // 编译错误        System.out.println(object); // 编译错误}在Golang中:对于基本类型来讲,声明即初始化;对于引用类型,声明则初始化为nil。func compareVariable() { var age int var hashMap *map[string]int fmt.Println(num) // num = 0 fmt.Println(hashMap) //  &hashMap== nil}2、作用域规则Java:对方法、变量及类的可见域规则是通过private、protected、public关键字来控制的,具体如下作用域当前类同一package子孙类其他packagepublic√√√√protected√√√×default(无修饰词)√√××private√×××Golang:控制可见域的方式只有一个,当字段首字母开头是大写时说明其是对外可见的、小写时只对包内成员可见。3、逗号ok模式在使用Golang编写代码的过程中,许多方法经常在一个表达式返回2个参数时使用这种模式:,ok,第一个参数是一个值或者nil,第二个参数是true/false或者一个错误error。在一个需要赋值的if条件语句中,使用这种模式去检测第二个参数值会让代码显得优雅简洁。这种模式在Golang编码规范中非常重要。这是Golang自身的函数多返回值特性的体现。例如:if _, ok := conditionMap["page"]; ok {      //} 4、结构体、函数以及方法a、结构体声明及使用在Golang中区别与Java最显著的一点是,Golang不存在“类”这个概念,组织数据实体的结构在Golang中被称为结构体。函数可以脱离“类”而存在,函数可以依赖于结构体来调用或者依赖于包名调用。Golang中的结构体放弃了继承、实现等多态概念,结构体之间可使用组合来达到复用方法或者字段的效果。Golang声明一个结构体并使用:// User  定义User结构体type User struct { Name string Age  int}// 使用一个结构体func main() { personPoint := new(User) // 通过new方法创建结构体指针 person1 := User{}        // 通过Person{}创建默认字段的结构体 person2 := User{  Name: "xiaoHong",  Age:  21, } fmt.Println(personPoint) // &{ 0 } fmt.Println(person1)     // { 0 } fmt.Println(person2)     // {xiaoHong 21 }}Java声明实体并使用:class User {    private String name;    private int age;    public User(String name, int age) {        this.name = name;        this.age = age;    }    public void setName(String name) {        this.name = name;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public int getAge() {        return age;    }    public String print() {        return "{name = " + name + ",age = " + age + "}";    }}public class Demo {    public static void main(String[] args) {        User user = new User("xiaohong", 29);        System.out.println("user信息:" + user.print());       }}//执行结果user信息:{name = xiaohong,age = 29}b、函数和方法的区别在Java中:所有的“函数”都是基于“类”这个概念构建的,也就是只有在“类”中才会包含所谓的“函数”,这里的“函数”被称为“方法”,可见上方声明实体并使用。在Golang中:“函数”和“方法”的最基本区别是:函数不基于结构体而是基于包名调用,方法基于结构体调用。如下实例:package entityimport "fmt"type User struct { Name string Age  int}// User结构体/指针可调用的"方法",属于User结构体func (user *User) Solve() { fmt.Println(user)}// 任何地方都可调用的"函数",不属于任何结构体,可通过entity.Solve调用func Solve(user *User) { fmt.Println(user)}func main() { userPoint := new(entity.User) // 通过new方法创建结构体指针 entity.Solve(userPoint) // 函数调用 userPoint.Solve()    // 方法调用}5、值类型、引用类型以及****指针Java:在Java中不存在显式的指针操作;8种基本数据类型是值类型,数组和对象属于引用类型。Golang:而Golang中存在显式的指针操作,但是Golang的指针不像C那么复杂,不能进行指针运算;所有的基本类型都属于值类型,但是有几个类型比较特殊,表现出引用类型的特征,分别是slice、map、channel、interface,除赋值以外它们都可以当做引用类型来使用,因此当我们这样做时,可以直接使用变量本身而不用指针。注:slice与数组的区别为是否有固定长度,slice无固定长度,数组有固定长度。值得注意的是,在Golang中,只有同长度、同类型的数组才可视为“同一类型”,譬如[]int和[3]int则会被视为不同的类型,这在参数传递的时候会造成编译错误。a、数组对比在Java中:当向方法中传递数组时,可以直接通过该传入的数组修改原数组内部值(浅拷贝)。在Golang中:则有两种情况:在不限定数组长度(为slice)时也直接改变原数组的值,当限定数组长度时会完全复制出一份副本来进行修改(深拷贝):Java的数组实践:    public static void main(String[] args) {        int[] array = {1, 2, 3};        change(array);        System.out.println(Arrays.toString(array)); // -1,2,3    }    private static void change(int[] array) {        array[0] = -1;    }Golang的数组实践://  不限定长度(即slice):func main() { var array = []int{1, 2, 3} change(array) fmt.Println(array)    // [-1 2 3]}func change(array []int) { array[0] = -1}//  限定长度(即数组): func main() { var array = [3]int{1, 2, 3} change(array) fmt.Println(array)    //[1 2 3]}func change(array [3]int) { array[0] = -1} b、对象对比在Golang中:传入函数参数的是原对象的一个全新的copy(有自己的内存地址);go对象之间赋值是把对象内存的内容(字段值等)copy过去,所以才会看到globalUser修改前后的地址不变,但是对象的内容变了。在Java中:传入函数参数的是原对象的引用的copy(指向的是同样的内存地址);Java对象之间的赋值是把对象的引用copy过去,因为引用指向的地址变了,所以对象的内容也变了。Golang的对象实践://User 定义User结构体type User struct {   Name string   Age int}// 定义一个全局的Uservar globalUser = User {   "xiaoming",   28,}// modifyUser 定义一个函数,参数为User结构体“对象”,将全局globalUser指向传递过来的User结构体“对象”func modifyUser(user User) {   fmt.Printf("参数user的地址 = %p\n",&user)    fmt.Printf("globalUser修改前的地址 = %p\n",&globalUser)   fmt.Println("globalUser修改前 = ",globalUser)   // 修改指向   globalUser = user   fmt.Printf("globalUser修改后的地址 = %p\n",&globalUser)   fmt.Println("globalUser修改后 = ",globalUser)}func main() {   var u User = User {      "xiaohong",      29,   }   fmt.Printf("将要传递的参数u的地址 = %p\n",&u)   modifyUser(u)}// 执行结果将要传递的参数u的地址 = 0xc0000ac018参数user的地址 = 0xc0000ac030globalUser修改前的地址 = 0x113a270globalUser修改前 = {xiaoming 28}globalUser修改后的地址 = 0x113a270globalUuser修改后 = {xiaohong 29}Java的对象实践验证:class User {    private String name;    private int age;    public User(String name, int age) {        this.name = name;        this.age = age;    }    public void setName(String name) {        this.name = name;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public int getAge() {        return age;    }    public String print() {        return "{name = " + name + ",age = " + age + "}";    }}public class Demo {    private static User globalUser = new User("xiaoming",28);    public static void modifyUser(User user) {        System.out.println("参数globalUser的地址 = " + user);        System.out.println("globalUser修改前的地址 = " + globalUser);        System.out.println("globalUser修改前 = " + globalUser.print());        globalUser = user;        System.out.println("globalUser修改后的地址 = " + globalUser);        System.out.println("globalUser修改后 = " + globalUser.print());    }    public static void main(String[] args) {        User user = new User("xiaohong", 29);        System.out.println("将要传递的参数user的地址 = " + user);        modifyUser(user);    }}//执行结果将要传递的参数user的地址 = com.example.demo.User@5abca1e0参数globalUser的地址 = com.example.demo.User@5abca1e0globalUser修改前的地址 = com.example.demo.User@2286778globalUser修改前 = {name = xiaoming,age = 28}globalUser修改后的地址 = com.example.demo.User@5abca1e0globalUser修改后 = {name = xiaohong,age = 29}c、指针的区别在Java中:如果传递了引用类型(对象、数组等)会复制其指针进行传递在Golang中:必须要显式传递Person的指针,不然只是传递了该对象的一个副本。Golang的指针:// User  定义User结构体type User struct { Name string Age  int}func main() { p1 := User{  Name: "xiaohong",  Age:  21, } changePerson(p1) fmt.Println(p1.Name) // xiaohong changePersonByPointer(&p1) fmt.Println(p1.Name) // xiaoming}func changePersonByPointer(user *User) { user.Name = "xiaoming"}func changePerson(user User) { user.Name = "xiaoming"}Java的指针:public class Demo {    public static void changePerson(User user) {        user.setName("xiaoming");    }    public static void main(String[] args) {        User user = new User("xiaohong", 29);        changePerson(user);        System.out.println("user信息:" + user.getName());      // xiaoming    }}class User {    private String name;    private int age;    public User(String name, int age) {        this.name = name;        this.age = age;    }    public void setName(String name) {        this.name = name;    }    public void setAge(int age) {        this.age = age;    }    public String getName() {        return name;    }    public int getAge() {        return age;    }}二、面向对象在Golang中:没有明确的OOP概念,Go语言只提供了两个关键类型:struct,interface。在Java中:面向对象语言的封装、继承、多态的特性以及“继承(extends)、实现(implements)”等关键字。1、Java的OOP与Golang的结构体组合假设有这么一个场景:动物(Animal)具备名字(Name)、年龄(Age)的基本特性,现在需要实现一个狗(Dog),且Dog需要具备Animal所需的所有特性,并且自身具备犬吠(bark())的动作。首先来看看最熟悉的Java要如何写,很简单,使用抽象类描述Animal作为所有动物的超类,DogextendsAnimal:public abstract class Animal {    String name;    int age;}public class Dog extends Animal {    public void bark() {        System.out.println(age + "岁的" + name + "在汪汪叫");    }}public class Demo {    public static void main(String[] args) {        Dog dog = new Dog();        dog.name = "小龙";        dog.age = 2;        dog.bark(); // 2岁的小龙在汪汪叫    } } 在Golang中,可以这样通过结构体的组合来实现:package mainimport "fmt"type Animal struct { Name string Age  int}type Dog struct { *Animal}func (dog *Dog) Bark() { fmt.Printf("%d岁的%s在汪汪叫", dog.Age, dog.Name)}func main() { dog := &Dog{&Animal{  Name: "小龙",  Age:  2, }} dog.Bark() // 2岁的小龙在汪汪叫...}2、侵入式与非侵入式接口在Java中:接口主要作为不同组件之间的契约存在。对契约的实现是强制的,你必须声明你的确实现了该接口。这类接口我们称为侵入式接口。“侵入式”的主要表现在于实现类需要明确声明自己实现了某个接口。在Golang中:非侵入式接口不需要通过任何关键字声明类型与接口之间的实现关系,只要一个类型实现了接口的所有方法,那么这个类型就是这个接口的实现类型。Java:管理狗的行为,可以通过以下接口实现:public interface Dog {    void Bark();}public class DogImpl implements Dog{    @Override    public void Bark() {        System.out.println("汪汪叫");    }}public class Demo {    public static void main(String[] args) {        Dog dog = new DogImpl();        dog.Bark();    // 汪汪叫    }}Golang:假设现在有一个Factory接口,该接口中定义了Produce()方法及Consume()方法,CafeFactory结构体作为其实现类型,那么可以通过以下代码实现:package entitytype Factory interface { roduce() bool Consume() bool}type CarFactory struct { roductName string}func (c *CarFactory) roduce() bool { fmt.Printf("CarFactory生产%s成功", c.ProductName) return true}func (c *CarFactory) Consume() bool { fmt.Printf("CarFactory消费%s成功", c.ProductName) return true}// --------------package mainfunc main() { factory := &entity.CarFactory{"Car"} doProduce(factory) doConsume(factory)}func doProduce(factory entity.Factory) bool { return factory.Produce()}func doConsume(factory entity.Factory) bool { return factory.Consume()}Golang的非侵入式接口优点:简单、高效、按需实现在Go中,类没有继承的概念,只需要知道这个类型实现了哪些方法,每个方法是啥行为。实现类型的时候,只需要关心自己应该提供哪些方法,不用再纠结接口需要拆得多细才合理。接口由使用方按需定义,而不用事前规划减少包的引入,因为多引用一个外部的包,就意味着更多的耦合。接口由使用方按自身需求来定义,使用方无需关心是否有其他模块定义过类似的接口Java的侵入式接口优点:层次结构清晰,对类型的动作行为有严格的管理三、异常处理在Java中:通过try..catch..finally的方式进行异常处理,有可能出现异常的代码会被try块给包裹起来,在catch中捕获相关的异常并进行处理,最后通过finally块来统一执行最后的结束操作(释放资源)。在Golang中:错误处理方式有两种方式:**,ok模式**与defer、panic及recover的组合1、Java的异常处理:public class ExceptionTest {    public static void main(String[] args) {        FileInputStream fileInputStream = null;        try{            fileInputStream = new FileInputStream("test.txt");        }catch (IOException e){            System.out.println(e.getMessage());            e.printStackTrace();            return;        }finally {            if(fileInputStream!=null){                try {                    fileInputStream.close();                } catch (IOException e) {                    e.printStackTrace();                }            }            System.out.println("回收资源");        }    }}2、Golang的异常处理:Golang的**,ok模式**。所有可能出现异常的方法或者代码直接把错误当作第二个响应值进行返回,程序中对返回值进行判断,非空则进行处理并且立即中断程序的执行。优点:这种比Java的简单很多,是Golang在异常处理方式上的一大特色。缺点:代码冗余,所有的异常都需要通过iferr!=nil{}去做判断和处理,不能做到统一捕捉和处理,容易遗漏。func main() { value, err := Bark() if err != nil {  // 返回了异常,进行处理  log.error("...异常:", err)  return err } // Bark方法执行正确,继续执行后续代码 rocess(value)}Golang的defer、panic及recoverdefer是Golang错误处理中常用的关键字,pannic及recover是Golang中的内置函数,通常与defer结合进行错误处理,它们各自的用途为:defer的作用是延迟执行某段代码,一般用于关闭资源或者执行必须执行的收尾操作,无论是否出现错误defer代码段都会执行,类似于Java中的finally代码块的作用;defer也可以执行函数或者是匿名函数:defer func() { // 清理工作} ()// 这是传递参数给匿名函数时的写法var num := 1defer func(num int) { // 做你复杂的清理工作} (num) 需要注意的是,defer使用一个栈来维护需要执行的代码,所以defer函数所执行的顺序是和defer声明的顺序相反的。defer fmt.Println(a)   defer fmt.Println(b)defer fmt.Println(c)执行结果:cbapanic的作用是抛出错误,制造系统运行时恐慌,当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止,panic和Java中的throw关键字类似,用于抛出错误,阻止程序执行。recover的作用是捕捉panic抛出的错误并进行处理,需要联合defer来使用,类似于Java中的catch代码块:func main() {      fmt.Println("main begin")      // 必须要先声明defer,否则不能捕获到panic异常      defer func() {         fmt.Println("defer begin")        if err := recover(); err != nil {            // 这里的err其实就是panic传入的内容            fmt.Println(err)         }         fmt.Println("defer end")      }()      test()      // test中出现错误,这里开始下面代码不会再执行      fmt.Println("main end") }func test() {   fmt.Println("test begin")   panic("error")   //这里开始下面代码不会再执行   fmt.Println("test end") }//执行结果main begintest begindefer beginerrordefer end注:利用recover处理panic指令,defer必须在panic之前声明,否则当panic时,recover无法捕获到panic。四、并发编程Java中CPU资源分配对象是Thread,Go中CPU资源分配对象是goroutine。JavaThread与系统线程为一一对应关系,goroutine是Go实现的用户级线程,与系统线程是m:n关系。1、Java和Golang的基本实现:在Java中,如要获得CPU资源并异步执行代码单元,需要将代码单元包装成Runnable,并创建可以运行代码单元的Thread,执行start方法启动线程。Runnable task = ()-> System.out.println("task running");Thread t = new Thread(task);t.start();Java应用一般使用线程池集中处理任务,以避免线程反复创建回收带来的开销。Runnable task = ()-> System.out.println("task running");Executor executor = Executors.newCachedThreadPool();executor.execute(task);在Golang中,则需要将代码包装成函数。使用go关键字调用函数之后,便创建了一个可以运行代码单元的goroutine。一旦CPU资源就绪,对应的代码单元便会在goroutine中执行。go func() {  fmt.Println("test task running")}()2、Java和Golang的区别:Golang语言采用了CSP(CommunicatingSequentialProcesses)的模型,其中以goroutine和channel作为主要实现手段。Java则采用了多线程模型,其中以Thread和Synchronization作为主要实现手段。Golang语言的goroutine是一种轻量级的线程,它们的创建和销毁速度比Java中的线程快得多。在Java中,创建和销毁线程都需要相当大的开销。Golang语言的channel是一种同步数据传递的机制,它可以方便地解决多道程序之间的通信问题。Java中则需要使用同步工具(如Semaphore、CountDownLatch等)来解决多线程之间的通信问题。Java和Go官方库中同步方式的对应关系JavaGolang锁synchronized,ReentrantLocksync.Mutex,oneunitbufferedchannel读写锁ReentrantReadWriteLock,StampedLocksync.RWMutex条件变量conditionsync.CondCAS/AtomicVarhandle、volatile,Atomic类atomic.Value,atomic包once单例模式sync.Oncea、Javasynchronized与GolangMutexJavasynchronized:线程A在t1时刻释放JVM锁后(monitorexit),在随后的t2时刻,若任意线程B获取到JVM锁(monintorenter),则线程A在t1时刻之前发生的所有写入均对B可见。synchronized是JVM内置锁实现,写入volatile变量相当于monitorexit,读取volatile变量相当于monintorenter。(即一把锁只能同时被一个线程获取,没有获得锁的线程只能阻塞等待)synchronized的使用:修饰一个代码块,被修饰的代码块称为同步代码块,作用范围是大括号{}括起来的代码;public void method(){   synchronized(this) {     // todo some thing    }}修饰一个方法,被修饰的方法称为同步方法,其作用范围是整个方法;public synchronized void method(){   // todo some thing}修改一个静态方法,作用范围是整个静态方法;public synchronized static void method() {   // todo some thing}修改一个类,作用范围是synchronized后面括号括起来的部分。class DemoClass {   public void method() {      synchronized(DemoClass.class) {         // todo some thing      }   }}GoMutex:Go并未像Java一样提供volatile这样基础的关键字,但其Mutex相关内存模型和synchronized或Java官方库Lock实现有十分接近语义。若goroutineA在t1时刻释放sync.Mutex或sync.RWMutex后,在随后的t2时刻,若任意goroutineB获取到锁,则goroutineA在t1时刻之前发生的所有写入均对B可见。Mutex的使用:修饰关键代码:每次只有一个线程对这个关键变量进行修改,避免多个线程同时这个关键代码进行操作。func main() { var mutex sync.Mutex count := 0 for i := 0; i 
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 00:08 , Processed in 0.819093 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表