20190608_浅谈go&java差异(三)

20190608_浅谈go&java差异(三)

转载请注明出处https://www.cnblogs.com/funnyzpc/p/10990703.html

第三节内容概览

  • 多线程通讯(线程安全类型 chan)
  • struct(结构体) 与 对象实体类
  • 异常(panic 与 throw)
  • 数组切片(slice)、map集合
  • 继承,实现,组合(extend&interface&abstract)
  • 包引入及管理(import、dep、module、maven)
  • 打包运行(run build)
  • 对象(receiver)函数(略)

多线程通讯(线程安全类型 chan)

  • java

java 提供了具有线程安全的类型以避免线程问题,比如AtomicLong、AtomicArray、AtomicInteger等等,其中对于字符串类型则提供了
StringBuffer类型来操作字符串,如果多个线程操作同一个jdk的数据安全类型的需要手动添加synchronized或者Lock()来保证并发数据
的安全性

public class AtomIntegerTest {
    private static final Logger LOG = LoggerFactory.getLogger(AtomIntegerTest.class);
    private static AtomicInteger atomicInt = new  AtomicInteger();
    /*
    private static AtomicLong atomicLong = new  AtomicLong();
    private static AtomicArray atomicArray = new  AtomicArray(100);
    private static AtomicBoolean atomicBoolean = new  AtomicBoolean();
    */

    @Test
    public void process01(){
        IntStream.range(0,100).parallel().forEach(i->{
            atomicInt.addAndGet(i);
        });
        LOG.info("result : {}",atomicInt);
    }
}
  • go

go语言则提供来chan关键字来辅助多协程通讯,而且go相对于java来说,他的基本数据类型也具有数据安全特性,其解决的方式有点儿类似于
消息队列的形式。

func main() {
	c := make(chan int)
	go func() {
		for i := 0; i <= 100; i = i + 1 {
			c <- i
		}
		close(c)
	}()
	j := 0
	// 这里会阻塞 直到循环执行完成
	for i := range c {
		j = j + i
		//fmt.Println(i)
	}
	fmt.Println("result : ", j)
	fmt.Println("Finished")
}

struct(结构体) 与 对象实体类

  • java

其实这方面java与go是没法比较的,go偏向于过程,而java是强面向对象的,这里仅仅阐述下各自对于数据的处理的结构差异
在java中可以说一切皆为对象,任何时候需要调用对象里面的函数必须new一个(也即创建一个),但是对于静态的方法不需要new,但是静态方法
一定是存在于对象之中的。java的数据对象定义是固定的,默认需要给参数加上getter和setter方法以做隐藏处理

public class PersonEntity {

    private String name;
    private int age;
    private boolean isAdult;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public boolean isAdult() {
        return isAdult;
    }

    public void setAdult(boolean adult) {
        isAdult = adult;
    }
}
  • go
    go 对于数据对象的处理就开放的多,比如可以使用interface{} 代替所有struct(结构体),比如他的参数赋值也可以省略
    (需要根据参数的位置来确定),同时也可以定义label来映射序列化json字段或映射数据库字段等(这个在java中需要定义注解来实现)
func main() {
	p1 := PersonEntity{"Lina", 27, true}
	p2 := PersonEntity{name: "Steve", age: 15, isAdult: false}
	fmt.Println("p1 : ", p1)
	fmt.Println("p2 : ", p2)
}

type PersonEntity struct {
	name    string
	age     int8
	isAdult bool
}

异常(panic 与 throw)

  • go

在go中没有异常抛出的概念,不过在大多数情况下均将异常放入error中返回,手动判断及处理异常;如果有显性抛出并
处理的地方需要配合defer去处理,同时抛出的异常是在panic的参数中定义

func main() {
	defer func() { // 必须要先声明defer,否则不能捕获到panic异常
		fmt.Println("process 03")
		if err := recover(); err != nil {
			fmt.Println(err) // 这里的err其实就是panic传入的内容,55
		}
		fmt.Println("process 04")
	}()
	f()
}
func f() {
	fmt.Println("process 01")
	panic("error info")
	fmt.Println("process 02")
}

  • java

java在可能出现异常的地方均会在方法上声明抛出(但这并不代表未声明的函数就一定不会抛出异常了),
这个时候需要在业务逻辑中选择抛出或者抓取处理就任由用户选择了

public class ThrowExceptionTest {
    private static final Logger LOG = LoggerFactory.getLogger(ThrowExceptionTest.class);

    @Test
    public void process01(){
        String[] strArr = {"a","b"};
        // 数组取值时越界可能会抛出异常
        LOG.info("value : {}",strArr[3]);
    }

    @Test
    public void process02()/*throws UnsupportedEncodingException*/{
        String str = "hello";
        byte[] enCodeArr = {};
        try {
            // getBytes 显式抛出异常了,需要抛出或者抓取(try catch)处理
            enCodeArr = Base64.getEncoder().encode(str.getBytes("utf-8"));
        }catch (UnsupportedEncodingException e){
            LOG.error("异常 : ",e);
        }
        LOG.info("enCode result : {}",enCodeArr);
    }
    /*
    public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null) throw new NullPointerException();
        return StringCoding.encode(charsetName, value, 0, value.length);
    }
    */
}

集合([ ]、slice、map、Array、Map)

  • java([ ]、Array、Map)
    java 的集合类型有三类:

    • [] : 且称它为定长单值数组

    • Array :可以理解是一个定长数组的管理器,它实现了不定长数组

      根据不同的算法有ArrayList、Set、TreeSet 等等

    • Map : 是一个键值对的集合类型,它的值可以是基本数据类型也可以是自定义数据类型

      它的实现也有很多 HashMap、TresMap、LinkedHashMap 等等

public class ArrayTest {
    private static final Logger LOG = LoggerFactory.getLogger(ArrayTest.class);


    @Test
    public void process01(){
        // 这里定义了长度为4的定长数组,当取或放>4个值后会抛异常
        String[] arrBase= new String[4];
        arrBase[0] = "hello";
        LOG.info("len {},{}",arrBase.length,arrBase[0]);

        // 这里定义了一个默认长度为4的不定长数组,当然是可以放入>4个值的
        List<Integer> lst = new ArrayList<Integer>(4){{
            add(0);
            add(22);
            add(-1);
        }};
        LOG.info("arr len {},{}",lst.size(),lst.toString());
    }

    @Test
    public void process02(){
        // 这里定义了一个键值对集合
        Map<String,Object> hashMap = new HashMap<String,Object>(2){{
            put("a",1);
            put("b",2);
        }};
        LOG.info("map len {},{}",hashMap.size(),hashMap.toString());
    }
}
  • go([ ]、slice、map)

go的集合有三种形式,其中数组与切片数组看似相似,其实对于内存分配有很大差异,一般实际使用后者,同时需要说明的是map也可使用make关键字
做集合优化。

go 的集合类型有三类,目前均无多算法实现:
- 数组
- 切片数组(slice)
- 键值对集合(map)

func main() {
	// 这里定义了一个不定长数组(这种描述可能不准确)
	var arr []int8
	arr = append(arr, 100)
	arr = append(arr, -1)
	fmt.Println(arr)
	// 这里使用slice 定义了一个长度为3,容量为3的数组
	arr2 := make([]string, 3, 3)
	arr2 = append(arr2, "hello")
	arr2[2] = "youth"
	//arr2 = append(arr2, "youth")
	// arr2 = append(arr2, "good")
	// arr2 = append(arr2, "morning")
	fmt.Println(cap(arr2), len(arr2), arr2)
}

继承,实现,组合(extend&interface&abstract)

  • java

java 有继承extend和实现interface 之分,一个类只能单继承或者多实现,但不管是被继承还是被实现,他们的类型还是有差异的
(访问类型也是有差异的)

public class ExtendIntfTest {


}

 interface EntityA{
    void doSth01();
    // private doSth02();
}
class EntityB{
    public void doSth01(){

    }
}

public abstract class EntityC {

    public void doSth01(){
        // TODO
    }

    public void doSth02(){
        // TODO
    }
}
  • go

go更偏向于过程,只给出了组合作为继承的一种实现,而且是通过结构体嵌套实现的,不说了还是看代码吧:

package main

import (
	"fmt"
)

type Base struct {
}

func (b *Base) ShowA() {
	fmt.Println("showA")
}
func (b *Base) ShowB() {
	fmt.Println("showB")
}

type Derived struct {
	Base
}

func (d *Derived) ShowB() {
	fmt.Println("Derived showB")
}

func main() {
	// 当 Derived 的结构体中包含Base时也就相当于继承了Base 的 ShowA() 方法
	d := Derived{}
	d.ShowA()
	d.ShowB()
}

包引入及管理(import、dep、module、maven)

 go的包引入与java比较相似,均是通过在java文件或者go文件首行定义包名称以被引入,不过使用的细节上还有有丢丢
 差异的,比如在go内如果有多个引入 则使用 import()来包含,同时还可以对引入做忽略(不够准确,与init相关)和别名处理
 同时对于包(module模块)的管理在go 1.11之前多用dep,而在go 1.11及之后则引入来go module,个人觉得有点儿像git,对于多个
 工程的管理更加的方便了。

 java中如果存在同包内多个子包引入则在包尾使用*,同一package内引入不用声明引入,对于包的管理多用maven(以及gradle),但对于较老的
 工程也有手动导入的方式。

打包运行(run build)

 go的打包只有官方标准的,每一个安装了go语言的机器都内置了go的一些列命令,包含 打包、构建、运行、测试、拉取依赖等等,不过
 虽然方便但也有不足之处,比如`go run`命令没有提供进程守护,需要第三方实现;再比如 `go package` 的包比较大不利于发布,
 一般使用upx命令缩减包的大小。
 java的打包有官方和非官方两种,官方只定义了jar包的打包的规范,对于工程管理却没有提供任何工具;而非官方的以maven为主(还有gradle),不仅仅
 可以管理依赖和工程结构等等~=,比官方好用多了~;而运行主要将打包后的文件扔进容器即可,同时容器提供了进程守护等功能,容器以tomcat、
 jetty、webLogic、ws为主,均为非官方。
原文地址:https://www.cnblogs.com/funnyzpc/p/10990703.html