1. 程式人生 > >20190608_淺談go&java差異(三)

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為主,均為非官方。