In Scala, I can declare a byte array this way
val ipaddr: Array[Byte] = Array(192.toByte, 168.toByte, 1.toByte, 9.toByte)
This is too verbose. Is there a simpler way to declare a Byte array, similar to Java's
byte[] ipaddr = {192, 168, 1, 1};
Note that the following results in an error due to the .
in the String
InetAddress.getByAddress("192.168.1.1".toByte)
I believe the shortest you can do is
val ipaddr = Array[Byte](192.toByte, 168.toByte, 1, 9)
You have to convert 192
and 168
to bytes because they are not valid byte literals as they are outside the range of signed bytes ([-128, 127]).
Note that the same goes for Java, the following gives a compile error:
byte[] ipaddr = {192, 168, 1, 1};
You have to cast 192 and 168 to bytes:
byte[] ipaddr = {(byte)192, (byte)168, 1, 1};
How about Array(192, 168, 1, 1).map(_.toByte)
?
To expand on Chris Martin's answer, if you're feeling lazy and like you don't want to type out Array(...).map(_.toByte)
over and over again, you can always write a variadic function:
def toBytes(xs: Int*) = xs.map(_.toByte).toArray
Now you can declare your byte array just about as concisely as in Java:
val bytes = toBytes(192, 168, 1, 1) // Array[Byte](-64, -88, 1, 1)
By adding methods to StringContext
one can easily define various methods for converting String literals into Byte arrays. For example, we can do this:
val bytes = ip"192.168.1.15"
or this:
val bytes = hexdump"742d 6761 2e00 6f6e 6574 672e 756e 622e"
Notice that it is especially useful for working with byte arrays in hexadecimal notation, because writing out the "0x" prefix in front of every byte can become very annoying very quickly, as can be seen in this example. When using hexadecimal notation as in Array(0xAB, 0xCD, 0xEF).map(_.toByte)
, it's not the call to map
that is awkward, it's the repeated "0x"-prefix that generates all the noise.
Here is a little code snippet that shows how several different ways for byte array creation could be implemented by providing an implicit class
that wraps a StringContext
:
implicit class ByteContext(private val sc: StringContext) {
/** Shortcut to the list of parts passed as separate
* string pieces.
*/
private val parts: List[String] = sc.parts.toList
/** Parses an array of bytes from the input of a `StringContext`.
*
* Applies `preprocess` and `separate` and finally `parseByte`
* to every string part.
* Applies `parseByte` to every vararg and interleaves the
* resulting bytes with the bytes from the string parts.
*
* @param preprocess a string preprocessing step applied to every string part
* @param separate a way to separate a preprocessed string part into substrings for individual bytes
* @param parseByte function used to parse a byte from a string
* @param args varargs passed to the `StringContext`
* @return parsed byte array
*
* Uses a mutable `ListBuffer` internally to accumulate
* the results.
*/
private def parseBytes(
preprocess: String => String,
separate: String => Array[String],
parseByte: String => Byte
)(args: Any*): Array[Byte] = {
import scala.collection.mutable.ListBuffer
val buf = ListBuffer.empty[Byte]
def partToBytes(part: String): Unit = {
val preprocessed = preprocess(part)
if (!preprocessed.isEmpty) {
separate(preprocessed).foreach(s => buf += parseByte(s))
}
}
// parse all arguments, convert them to bytes,
// interleave them with the string-parts
for ((strPart, arg) <- parts.init.zip(args)) {
partToBytes(strPart)
val argAsByte = arg match {
case i: Int => i.toByte
case s: Short => s.toByte
case l: Long => l.toByte
case b: Byte => b
case c: Char => c.toByte
case str: String => parseByte(str)
case sthElse => throw new IllegalArgumentException(
s"Failed to parse byte array, could not convert argument to byte: '$sthElse'"
)
}
buf += argAsByte
}
// add bytes from the last part
partToBytes(parts.last)
buf.toArray
}
/** Parses comma-separated bytes in hexadecimal format (without 0x-prefix),
* e.g. "7F,80,AB,CD".
*/
def hexBytes(args: Any*): Array[Byte] = parseBytes(
s => s.replaceAll("^,", "").replaceAll(",$", ""), // ,AB,CD, -> AB,CD
_.split(","),
s => Integer.parseInt(s, 16).toByte
)(args: _*)
/** Parses decimal unsigned bytes (0-255) separated by periods,
* e.g. "127.0.0.1".
*/
def ip(args: Any*): Array[Byte] = parseBytes(
s => s.replaceAll("^[.]", "").replaceAll("[.]$", ""), // .1.1. -> 1.1
_.split("[.]"),
s => Integer.parseInt(s, 10).toByte
)(args:_*)
/** Parses byte arrays from hexadecimal representation with possible
* spaces, expects each byte to be represented by exactly two characters,
* e.g.
* "742d 6761 2e00 6f6e 6574 672e 756e 622e".
*/
def hexdump(args: Any*): Array[Byte] = parseBytes(
s => s.replaceAll(" ", ""),
_.grouped(2).toArray,
s => Integer.parseInt(s, 16).toByte
)(args: _*)
/** Parses decimal unsigned bytes (0-255) separated by commas,
* e.g. "127.0.0.1".
*/
def decBytes(args: Any*): Array[Byte] = parseBytes(
s => s.replaceAll("^,", "").replaceAll(",$", ""), // ,127, -> 127
_.split(","),
s => Integer.parseInt(s, 10).toByte
)(args:_*)
}
As soon as this class is in the implicit scope, we can use all of the following notations to define byte arrays:
def printBytes(bytes: Array[Byte]) =
println(bytes.map(b => "%02X".format(b)).mkString("[",",","]"))
// bunch of variables to be inserted in the strings
val a: Int = 192
val b: Long = 168L
val c: Byte = 1.toByte
val d: String = "0F"
val e: String = "15"
printBytes(ip"192.168.1.15")
printBytes(ip"192.$b.1.$e")
printBytes(ip"$a.$b.$c.$e")
printBytes(hexBytes"C0,A8,01,0F")
printBytes(hexBytes"C0,$b,$c,0F")
printBytes(hexBytes"$a,$b,$c,0F")
printBytes(decBytes"192,$b,1,15")
printBytes(decBytes"192,168,$c,$e")
printBytes(decBytes"$a,$b,1,$e")
printBytes(hexdump"C0A8 010F")
printBytes(hexdump"$a $b $c $d")
printBytes(hexdump"C0 $b 01 $d")
Note that the string literals can also contain references to variables using the $varargVar
syntax inside of the string. All examples generate the same byte array [C0,A8,01,0F]
.
On performance: all of the above is build around method calls, the literals are not transformed to byte-arrays at compile time.
For your specific example you could simply use InetAddress.getByName
instead:
InetAddress.getByName("192.168.1.1")
In general Didier is right, Byte
s are -128 to 127, so this works:
Array[Byte](1,2,3)
but this doesn't:
Array[Byte](192, 168, 1, 1)
You can use implicit
implicit def int2byte(int: Int) = {
int.toByte
}
And that will convert all Int values in scope in places where byte is required.
split
on a String
could do the trick:
val ipaddr: Array[Byte] =
"192.168.1.1".split('.').map(_.toInt).map(_.toByte)
Breaking this down we have
"192.168.1.1"
.split('.') // Array[String]("192", "168", "1", "1")
.map(_.toInt) // Array[Int](192, 168, 1, 1)
.map(_.toByte) // Array[Byte](-64, -88, 1, 1)