1. Overview
In this tutorial, we’ll discuss how to create a byte array in Kotlin. We’ll see how the byte is represented in Kotlin. Additionally, we’ll show how to create the byte array using signed and unsigned bytes.
2. Bytes Representation in Kotlin
Firstly, let’s show how the byte is represented in Kotlin. Above all, the byte is represented by a Byte type. Additionally, the Byte type contains a signed value. It means that one bit is reserved for the information if a value is positive or negative. As a consequence, it can store a value between -128 and 127. Additionally, Kotlin supports an unsigned byte. The Ubyte type is provided as an experimental feature. It can store values between zero and 225.
3. Create a Byte Array Using Signed Byte
Let’s now have a look at how to create a byte array. Kotlin provides a built-in method byteArrayOf. Moreover, it takes as an argument multiple values of the Byte type.
Now, let’s show it in a simple example:
@Test
fun `create a byte array using signed byte`() {
val byteArray = byteArrayOf(0x48, 101, 108, 108, 111)
val string = String(byteArray)
assertThat(string).isEqualTo("Hello")
}
An argument is a Byte object, which represents an 8-bit signed integer. We can use hexadecimal values or integer values as arguments. Moreover, the values must be in the range of -128 and 127. Thanks to that, we can create an array of negative and positive values:
@Test
fun `create a byte array with negative values`() {
val byteArray = byteArrayOf(Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE)
assertThat(byteArray)
.hasSize(5)
.containsExactly(-128,-1, 0, 1, 127)
}
Consequently, when we provide a value outside of the range, it causes a compilation error: “The integer literal does not conform to the expected type Byte“.
4. Create a Byte Array Using Unsigned Byte
After that, let’s have a look at how to create a byte array using an unsigned byte. The Kotlin language provides an inline class UByteArray. The whole class is an experimental feature. It provides a method ubyteArrayOf, which creates a collection of Ubyte objects. Moreover, the method is an experimental feature as well. We should mark the usage with @OptIn(ExperimentalUnsignedTypes::class) or @ExperimentalUnsignedTypes to avoid compilation warnings*.*
Let’s create an example:
@Test
@OptIn(ExperimentalUnsignedTypes::class)
fun `create a byte array using unsigned byte`(){
val uByteArray = ubyteArrayOf(UByte.MIN_VALUE, 130U, 131u, UByte.MAX_VALUE)
val intValues = uByteArray.map { it.toInt() }
assertThat(intValues)
.hasSize(4)
.containsExactly(0, 130, 131, 255)
}
As shown above, we use U or u suffixes to declare unsigned values. Additionally, we can use only positive values from zero to 255 range. In case we would provide a value outside of the range, it causes a compilation error: “The integer literal does not conform to the expected type UByte“.
5. Appending Bytes to ByteArray
When working with byte-level data, sometimes we might want to extend an existing ByteArray and apply an operation on the combined bytes. In this section, we’ll learn how to append bytes to ByteArray in Kotlin.
5.1. Using the + Operator
Let’s start by defining two instances of ByteArray class, namely byteArray and bytesToAdd:
val byteArray = byteArrayOf(0b00000001, 0b00000010, 0b00000011)
val bytesToAdd = byteArrayOf(0b00000100, 0b00000101)
Now, let’s see how conveniently we can use the + operator to append the bytes from bytesToAdd to byteArray:
val concatenatedArray = byteArray + bytesToAdd
Continuing with this, let’s verify that the concatenatedArray contains all the bytes in the correct sequence:
Assertions.assertEquals(byteArray.size + bytesToAdd.size, concatenatedArray.size)
Assertions.assertEquals(0b00000001, concatenatedArray[0])
Assertions.assertEquals(0b00000010, concatenatedArray[1])
Assertions.assertEquals(0b00000011, concatenatedArray[2])
Assertions.assertEquals(0b00000100, concatenatedArray[3])
Assertions.assertEquals(0b00000101, concatenatedArray[4])
Perfect! We’ve concatenated bytes from two ByteArray instances successfully.
Furthermore, we can use the same approach to concatenate a single Byte to an existing ByteArray:
val byteToAdd = 0b00000100.toByte()
val concatenatedArray = byteArray + byteToAdd
Likewise, we can even concatenate a collection of bytes to a ByteArray object:
val bytesToAddList = listOf(0b00000100.toByte(), 0b00000101.toByte())
val concatenatedArray = byteArray + bytesToAddList
Great! We’re off to a good start since we solved multiple scenarios using the + operator quite conveniently.
5.2. Using copyInto() Method
Alternatively, we can use the copyInto() method to copy the bytes from multiple ByteArray objects into a target ByteArray object:
fun ByteArray.copyInto(
destination: ByteArray,
destinationOffset: Int = 0,
startIndex: Int = 0,
endIndex: Int = size
): ByteArray
So, let’s go ahead and copy bytes from the BytesToAdd and bytesToAdd arrays to the concatenatedArray ByteArray:
val concatenatedArray = ByteArray(byteArray.size + bytesToAdd.size)
byteArray.copyInto(concatenatedArray)
bytesToAdd.copyInto(concatenatedArray, destinationOffset = byteArray.size)
We must note that we don’t need to specify the destinationOffset argument for the first copy operation, as the default offset is 0. However, we need to explicitly specify the destinationOffset for the subsequent copy operations to ensure we don’t override existing bytes.
5.3. Using Interoperable Java Code
Since Kotlin is fully interoperable with Java, we can use Java classes and libraries to solve our use case.
First, let’s see how to use the System.arraycopy() method to append bytes into a new ByteArray destination:
val concatenatedArray = ByteArray(byteArray.size + bytesToAdd.size)
System.arraycopy(byteArray, 0, concatenatedArray, 0, byteArray.size)
System.arraycopy(bytesToAdd, 0, concatenatedArray, byteArray.size, bytesToAdd.size)
Next, let’s explore how to use the ByteArrayOutputStream class to solve our use case:
val outputStream = ByteArrayOutputStream()
outputStream.write(byteArray)
outputStream.write(bytesToAdd)
val concatenatedArray = outputStream.toByteArray()
We must note that we did two write operations to an instance of the ByteArrayOutputStream class, one from each ByteArray object. Then, we used the toByteArray() method to get a new ByteArray object. Further, it’s essential to realize that the toByteArray() method uses the Arrays.copyOf() method internally.
Moving on, let’s also learn how to use the ByteBuffer class for appending bytes from ByteArray objects:
val buffer = ByteBuffer.allocate(byteArray.size + bytesToAdd.size)
buffer.put(byteArray)
buffer.put(bytesToAdd)
val concatenatedArray = buffer.array()
In this approach, we must allocate enough space in the buffer to hold all the transferred bytes.
Lastly, let’s extend this approach to solving an interesting scenario where we want to append bytes from an Int object to a ByteArray:
val valueToAdd = 256
val buffer = ByteBuffer.allocate(byteArray.size + Integer.BYTES)
buffer.put(byteArray)
buffer.putInt(valueToAdd)
val concatenatedArray = buffer.array()
We can observe that we’ve used the Integer.BYTES value for allocating sufficient space in the buffer object. Additionally, we used the putInt() method to write all bytes from the Int object into the buffer. Eventually, we got the ByteArray object using the array() method.
6. Conclusion
In this article, we discussed byte array creation. We described byte representation in Kotlin. We saw how to create the byte array using signed and unsigned bytes. Finally, we learned multiple ways to append bytes to a ByteArray.
As always, the source code of the examples is available over on GitHub.