Part of the Go for Java Programmers series
Before we get into custom types, and Go’s version of object-orientation, let’s cover the simple types that come out of the box. Full details can be found in the (surprisingly readable) language spec, but here are some highlights that are important from a Java programmer’s perspective.
Boolean variables are declared with type bool, and their value must be one of two constants, true or false.
... var isActive bool fmt.Printf("The value of 'isActive' is %t\n", isActive) ...
The output of this program would be:
The value of 'isActive' is false
Notice that Go’s bool, just like Java’s boolean primitive, initializes to false if you don’t supply any value. However, unlike Java, Go does not have the separate concepts of “primitive” boolean and “object” Boolean.
Go does have the concept of “a type” vs. “a pointer to that type”, and we will have to worry about null pointers when we get there (a couple of articles in the future!). For now, rest easy that when you’re not dealing with pointers, Go types operate much like Java primitives. Go initializes them with sane defaults, and in fact the compiler won’t allow you to initialize one with nil (i.e. Go’s version of null).
Characters and Strings
In Java, the String type is a class… there is no concept of string as a primitive type. Once again however, the line of demarcation in Go lies between “types” and “pointers-to-types”. When you initialize a non-pointer type directly, it is similar to working with a Java primitive.
So no more null-check hassles with strings! If you initialize a Go string without assigning a value, then by default it is given the empty-string value (“”), rather than nil.
Speaking of assigning values, Go strings are quite Unicode-friendly. The file format for Go source code is UTF-8, and so any string literals declared in your code are likewise stored as UTF-8. Strictly speaking, a Go string is a series of bytes rather than an array of characters, and so there are edge cases in which a string variable might contain binary data (see this interesting article on the official Go blog for more detail). However, Go strings in practice are almost always UTF-8.
... fmt.Printf("Escaped represention:\t %+q\n", "世界") fmt.Printf("UTF-8 representation:\t %s\n", "\u4e16\u754c") ...
The output of this program would be:
Escaped represention: "\u4e16\u754c" UTF-8 representation: 世界
Characters and substrings
For storing single characters, Go has the data type rune. The rune type is an alias for int32 (covered in the next section below), and is meant for storing one Unicode character (or “code point”). You can extract from a string single runes, or a substring, by using a zero-indexed array syntax. The built-in Go function len(s) gives you the length of a string, to help you avoid the Go equivalent of an ArrayIndexOutOfBoundsException:
... testString := "This is just a test string" fmt.Printf("This string has %d characters\n", len(testString)) fmt.Printf("The 3rd character is: %c\n", testString) lastRuneIndex := len(testString) - 1 fmt.Printf("The last character is: %c\n", testString[lastRuneIndex]) fmt.Printf("The third word in the sentence is: '%s'\n", testString[8:12]) ...
The output of this program would be:
This string has 26 characters The 3rd character is: i The last character is: g The third word in the sentence is: 'just'
The testString[8:12] syntax used to extract a substring will make more sense when we cover arrays and slices in an upcoming article. For now, just know that the number on the left-hand side of the colon is the starting index for the substring, and the number on the right-hand side is the ending index.
Standard Go string functions
In Java, the String class contains numerous methods for manipulating, formatting, and searching for characters within a string. Other core classes contain methods for converting between String and other common data types (e.g. Integer.parseInt(s)).
With Go, string is effectively a built-in primitive type, with no methods. Therefore, the Go standard library contains two packages to fill that gap:
- strings – Search and manipulation functions. Supported operations including splitting and joining, finding characters and substrings, converting to lower-case or upper-case, etc.
- strconv – Functions for converting between string and other data types or output formats.
Types vary between platforms
One of the tricky things about writing in C is that data types can vary from one platform to the next. An int might take up 32 bits on one machine, and 64 bits on another. The Java Virtual Machine, with its “write once, run anywhere” mantra, hides this from the programmer and enforces consistent data types on all platforms.
Go is a partial throwback, combining elements of both. If you need a particular size, then you can declare a specific numeric type (e.g. int32, int64). However, if you merely declare a variable as int, then its size can vary from 32 to 64 bits depending on the platform.
In practice, this doesn’t matter much. Even in the worst-case, the range of a 32-bit int is sufficient for most normal tasks. Also, the ambiguous int is the data type used to index arrays and slices (covered in an upcoming article). So general best practice is to:
- use an int when you need something that’s at least 32 bit, and you don’t care if it’s bigger
- use a more specific type only when you really need a larger or smaller number range
Altogether, these are the integer and floating-point types available in Go, along with the range of possible values for each:
|Go type||Java equivalent||Range|
|int8||byte||-128 to 127|
|int16||short||-32,768 to 32,767|
|int32||int||-32,768 to 32,767|
|int64||long||-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807|
|float32||float||1.40129846432481707e-45 to 3.40282346638528860e+38, positive or negative|
|float64||double||4.94065645841246544e-324d to 1.79769313486231570e+308d, positive or negative|
Go also provides complex64 and complex128 types, for advanced math operations involving imaginary numbers. There is no direct equivalent in the Java library, although you can find similar data types in the Apache Commons Math project.
Notice that while Go has has an ambiguous int type, which might be 32-bit or 64-bits depending on the platform, there is no generic floating-point type. You must specify float32 or float64. Unlike integer types, which differ only in the range of numbers that they can hold, there are subtle differences in the way that 32-bit and 64-bit floats handle precision. This is why Java developers typically use BigDecimal for financial transactions! Speaking of which, Go’s standard library offers equivalent types in its “math/big” package. The Go type Int is similar to Java’s BigInteger, and Go’s Rat (short for “rational”) is similar to Java’s BigDecimal.
Go has both signed and unsigned ints
All of the integer primitives in Java are signed. This means that if Java is your only programming language, you may not even be aware of a difference between signed and unsigned numbers! In essence, a signed integer can be either positive or negative, wheres an unsigned integer can only be zero and up. So with unsigned types, you can get twice the range out the same memory space, so long you don’t need to store any negative values.
|uint8||0 to 255|
|uint16||0 to 65,535|
|uint32||0 to 4,294,967,295|
|uint64||0 to 18,446,744,073,709,551,615|
There are few quirks that you have to look out for with Go’s primitive types, such as the possibility of a string containing binary data, or the fact that integers vary in size between platforms. Overall, however, many things about Go primitives might be a breath of fresh air to you as Java programmer. There is none of Java’s bifurcation between “primitives” and “object wrappers”, and no autoboxing. Moreover, unless you choose to create a pointer, there are no NullPointerException‘s!
In the next article we will look at creating custom types in Go, and how Go’s type system differs from object-orientation in Java.