Статьи

Go для программистов на Java: пакеты, функции и переменные

Знакомый синтаксис Go

Благодаря общему наследию в языке программирования C код Go (он же Golang) должен быть достаточно узнаваем для разработчика Java. Вот каноническая программа «Hello world», которую вы можете запустить и изменить через браузер на сайте Go Playground :

1
2
3
4
5
6
7
package main
 
import "fmt"
 
func main() {
    fmt.Println("Hello world")
}

Функции и управляющие структуры начинаются и заканчиваются фигурными скобками. Параметры функций заключены в круглые скобки с пустыми круглыми скобками для функций с нулевыми параметрами. Строго говоря, операторы Go заканчиваются точками с запятой, как и в Java, хотя соглашение заключается в том, чтобы компилятор мог вставлять их неявно. Соглашение об именовании переменных и функций — «camelCase», а не использование подчеркивания (люди из Python склонны ненавидеть это!).

Другие основы Go также очень похожи на своих коллег по Java. Код Go сгруппирован в «пакеты». Функция main (), расположенная в пакете main, является точкой входа в программу Go… как и метод main в приложении Java:

1
2
3
4
5
6
7
8
9
package com.mycompany;
 
public class MyApplication {
 
   public static void main(String args[]) {
      System.out.println("Hello world");
   }
 
}

Пакеты, Переменные и Функции

Однако, конечно, есть различия. Сравните этот расширенный Java-код:

Герцог-голова

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.mycompany;
 
import java.util.Date;
 
public class MyApplication {
 
    private Date currentTime;
 
    public static void main(String args[]) {
        MyApplication instance = new MyApplication();
        String message = instance.sayHello("world");
        System.out.println(message);
        System.out.println("The time is currently: " + instance.currentTime.toString());
    }
 
    public MyApplication() {
        currentTime = new Date();
    }
 
    private String sayHello(String listener) {
        return "Hello, " + listener;
    }
 
}

… для Go аналога:

Суслик-голова

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
 
import (
    "fmt"
    "time"
)
 
var currentTime time.Time
 
func init() {
    currentTime = time.Now()
}
 
func main() {
    message := sayHello("world")
    fmt.Println(message)
    fmt.Println("The time is currently: " + currentTime.String())
}
 
func sayHello(listener string) string {
    return "Hello, " + listener
}

Как видите, объявления переменных и функций Go работают в обратном порядке по сравнению с Java. Как и все функции Go, объявление sayHello здесь начинается с ключевого слова func, а не с возвращаемого типа. Тип возвращаемого значения, в данном случае string, находится в самом конце непосредственно перед открывающей фигурной скобкой. То же самое относится и к параметрам функции. Вместо String listener, объявлением параметра является строка listener.

Переменные объявляются с ключевым словом var, тип которого следует за именем переменной. Однако строка 15 иллюстрирует удобную сокращенную запись. Когда используется оператор двоеточия: =, а не простой знак равенства, ключевое слово var и тип переменной могут быть опущены. Компилятор достаточно умен, чтобы вывести правильный тип из того, что находится справа от оператора.

Область применения и жизненный цикл

Поскольку переменное сообщение объявлено внутри main, его область действия ограничена этой функцией. Однако currentTime объявляется на уровне пакета и поэтому виден всем функциям в пакете. Обратите внимание на отсутствие модификаторов уровня доступа для переменных и функций. Java использует ключевые слова public, private и т. Д. Для управления доступом извне класса или пакета. Go делает это через заглавные буквы. Переменная или функция, начинающаяся с символа нижнего регистра, аналогична закрытой в Java. Если бы переменная currentTime была объявлена ​​следующим образом:

1
2
3
...
var CurrentTime time.Time
...

… Тогда он будет аналогичен общедоступному Java и будет доступен и из других пакетов. То же самое соглашение применяется к функциям, как вы можете видеть в строках 16 и 17. Поскольку он начинается с заглавной буквы «P», функция Println может использоваться вне стандартного пакета fmt.

Наконец, обратите внимание на функцию init () в строке 10. Хотя main () является основной точкой входа для приложения Go, init () — это специальная (необязательная) функция, которая автоматически вызывается раньше всего. Обычно он используется для инициализации переменных или других задач настройки и проверки. На данный момент вы можете думать, что init () примерно сопоставим с конструктором Java, хотя в Go различие между статическими методами и методами экземпляров не совсем применимо. Более подробно о том, как Go сравнивается с объектной ориентацией Java, будет рассказано позже в этой серии статей о типах Go.

Несколько типов возврата?!?

Методы Java могут принимать любое количество входных параметров, но всегда возвращают не более ОДНОГО возвращаемого типа. Если вам нужен метод для возврата более одного отдельного фрагмента информации, вы должны либо создать пользовательский тип для хранения нескольких фрагментов … либо отказаться от и реорганизовать метод, чтобы избежать этого беспорядка.

Одним из сюрпризов функций Go является то, что они довольно часто возвращают несколько значений за один вызов! Рассмотрим пример функции, которая удаляет пробел из строки и возвращает как обрезанную строку, так и количество символов, удаленных в процессе:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package main
 
import (
    "fmt"
    "strings"
)
 
func main() {
    trimmedString, numberOfCharsRemoved := trimWithCount("This is a test   ")
    fmt.Printf("%d characters were trimmed from [%s]\n", numberOfCharsRemoved, trimmedString)
}
 
func trimWithCount(input string) (string, int) {
    trimmedString := strings.Trim(input, " ")
    numberOfCharsRemoved := len(input) - len(trimmedString)
    return trimmedString, numberOfCharsRemoved
}

Вы просто объявляете функцию с разделенным запятыми списком возвращаемых типов, так же, как вы объявляете разделенный запятыми список входных параметров. Оператор возврата должен соответствовать этим типам в правильном порядке. Когда функция вызывается, как здесь, в строке 9, ее результаты должны быть отнесены к разделенному запятыми списку переменных, которые также находятся в правильном порядке.

Как мы увидим позже в этой серии, когда будем говорить об обработке исключений, Go широко использует этот подход для сообщения об ошибках. Если что-то может пойти не так внутри функции, обычно функция возвращает как результат, так и тип ошибки. Вызывающие могут проверить чистый успех вызова функции, проверив, что ошибка равна нулю (т. Е. Равна нулю):

1
2
3
4
5
6
7
...
number, err := strconv.Atoi(someNumericString)
if err != nil {
   // Could not parse this string variable as an integer
   log.Fatal(err)
}
...

Отложенные вызовы функций

Часто у вас есть логика, которую вы хотите гарантировать, что произойдет в конце данного блока, несмотря ни на что. Java поддерживает это с помощью конструкции try-catch-finally. Код внутри блока finally всегда должен выполняться после кода в его блоке try, даже если какое-то исключение запускает блок catch между ними.

Java 7 усилила эту идею с помощью механизма «попробуй с ресурсами». Ресурсы, такие как дескриптор открытого файла или соединение с базой данных, которые объявляются в начале блока try, автоматически закрываются, даже если вы не делаете это вручную внутри блока finally:

Герцог-голова

01
02
03
04
05
06
07
08
09
10
11
...
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
 
    // Do something with the file
 
} catch(Exception e) {
    log.error("An error occurred while processing the file: " + e);
} finally {
    log.debug("The 'finally' block was reached");
}
...

Go имеет аналогичный механизм в отложенных вызовах функций. Когда функция вызывается с ключевым словом defer, то ее выполнение происходит после возврата из функции, в которой она была вызвана.

Суслик-голова

01
02
03
04
05
06
07
08
09
10
11
12
13
...
    file, err := os.Open(filename)
    if err != nil {
        fmt.Println("Could not open file")
        return
    }
    defer file.Close()
 
    // Do something with the file
 
    return
}
...

Здесь вызов file.Close () в строке 7 всегда будет вызываться непосредственно перед возвратом окружающей функции, даже если окружающая функция «паникует» (о ней будет рассказано позже в этой серии статей об обработке исключений). Любая функция может быть отложена таким образом. Ключевые вещи, на которые следует обратить внимание:

  • Несколько отложенных вызовов функций выполняются в порядке LIFO. Если после вызова file.Close () в строке 10 запланирована другая отложенная функция, то эта функция будет вызываться перед file.Close ().
  • Любые параметры, передаваемые в отложенную функцию, оцениваются в точке, где функция запланирована , а не в точке, в которой она фактически выполняется .

Вывод

Синтаксис, пакеты, функции и переменные в Go поразительно похожи на Java, но сильно отличаются друг от друга. В следующей статье серии Go for Java Programmers мы рассмотрим управляющие структуры Go.