Scala supports both immutable and mutable collections. Immutable collections are primarily used for secure concurrent access by multiple threads and are therefore more efficient to read. For both sets, Scala provides two different packages:

  • Immutable collection: scala. Collection. Immutable
  • Mutable collections: Scala.collection.mutable

Regardless of the set, their top-level properties (or abstract classes) continue this structure:

For efficiency reasons, Scala defaults to immutable collections, but that doesn’t stop you from using mutable collections for some necessary tasks.

There are three major categories of collections in Scala: sequences Seq, sets Set, and maps. All sets are extended from the Iterable property, that is, they are traversable using a for loop. Scala provides a very, very wide range of methods for each collection, and this article will cover only the basic ones.

Below is the Scala 2.11.12 version of the official API description document: scala.collection

1. Define immutable

Immutable collections in Scala mean that the memory size of the collection itself is not allowed to change dynamically, and can be likened to Java arrays, which are always sized at creation time. However, the elements inside the set can be changed.

Variable set refers to the size of the memory space occupied by this set can change dynamically. Another example: ArrayList in Java can dynamically change the length of the collection through add or remove.

ArrayList<String> strings = new ArrayList<>();
strings.add("Welcome back");
System.out.println("hashCode:"+strings.hashCode());

strings.add("Java");
System.out.println("hashCode:"+strings.hashCode());
Copy the code

On the surface, an ArrayList is a mutable collection; however, its internal implementation is done with an immutable array. When the length of the strings list changes as we add new elements to our ArrayList, we actually move the elements from the shorter array into a new, longer array, because it can’t directly expand the space in the original array.

To sum up, every time the remove method is called on an ArrayList, the hashcode it references changes.

2. Scala collection system

Here is a family tree of immutable collections:

Seq can be subdivided into indexed sequence and linear sequence. Generally speaking, the access speed of indexed sequence is faster than that of linear sequence, but linear sequence has more application scenarios, such as queue and stack are very common data structures.

Both sets and maps provide an ordered Set called SortedXXX. In Java, a List belongs to a Set; in Scala, it belongs to the linear sequence part of Seq.

A String is grouped under an indexed sequence: it can essentially be considered a collection of types Char[], and it can be indexed to print the characters at the corresponding position.

Here is a family tree of mutable collections:

The contents of mutable sets are obviously more complex than immutable sets, mainly because there are more buffers. In practical development we often use ArrayBuffer and ListBuffer.

If thread-safe content is involved, the collection prefixed with Synchronized is used.

3. The Scala arrays

If you divide arrays in Scala from a mutable, immutable perspective, you can roughly divide them into Array (immutable) and ArrayBuffer (mutable) Array types.

3.1 Creating an Array of fixed length

Scala uses a generic form to indicate the type of elements in an array, parentheses () to specify the index position (Scala’s square brackets [] indicate generics), and subscripts start at 0. During initialization, the space is filled with either 0 or NULL, depending on the type of element in the array.

// Brackets indicate generics
// The parentheses indicate the length of the array
val ints: Array[Int] = new Array[Int] (10)

// Take the element at position 0
val i: Int = ints(0)
Copy the code

Scala can declare an array element of type Any, which means that the array can contain elements of Any type.

//Scala's data elements are not necessarily of the same type.
val anys: Array[Any] = new Array[Any] (4)
anys(0) = "Hello World"
anys(1) = 24.00d
anys(2) = new Student

// Print the type of each element and set the guard to avoid null pointer exceptions.
for(i <- anys ifi! =null) println(i.getClass)
Copy the code

You can also choose to put elements directly into the array when you create it. The following code block calls the apply method of Array, so the new keyword is omitted. The compiler will automatically infer the array type during compilation based on the types of elements put in:

val array = Array(1.2.3."Hello")
// Output type :class [ljava.lang. Object; equivalent to Object[] in Java.
println(array.getClass)

val array2 = Array(1.2.3.4)
// Output type :class [I; equivalent to Java Int[].
println(array2.getClass)
Copy the code

If all the elements are of the same class, the array is of the same type as the elements of that class. If the elements are not of the same class, the array type is Any. However, once the array has a more specific element type, passing in elements of any other type is an error.

val ints = Array(1.2.4.5.6)
println(ints.getClass)

// Ints is determined to be Array[Int] at compile time.
ints(0) = 1  //ok
ints(1) = "Hello" //Oops!
Copy the code

Arrays of the same type can be merged using the ++ symbol.

/ / ints = Array (1, 2, 3, 4, 5)
val ints: Array[Int] = Array(1.2.3) + +Array(4.5)
Copy the code

Arrays. indices can be used to obtain the subscript value from 0 to arrays.length-1 if the traditional array subscript method is used to iterate through the arrays.

val ints = Array(1.2.3.4.5)
// THE IDE recommends using seq.indices for traversal.
// Equivalent to:
//for(index <- 0 until ints.size )
//for(index <- 0 to ints.size -1 )
//indices is a function that returns Range 0 until length.
for(index <- ints.indices)
{
	println(index)
	println(s"position$index:${ints(index)}")}Copy the code

Indices is actually a wrapped function:

  /** Produces the range of all indices of this sequence. * * @return a `Range` value from `0` to one less than the length  of this $coll. */
  def indices: Range = 0 until length
Copy the code

3.2 creating a variable-length ArrayBuffer

ArrayBuffer is located in the scala. Collection. Compared to the previous fixed-length Array, ArrayBuffers can also be extended (append) or removed (remove). You can do this by specifying no initial element in parentheses, so the default length is 0.

import scala.collection.mutable.ArrayBuffer
val arrs = new ArrayBuffer[Int] ()//ArrayBuffer provides the apply method, so you can omit the new keyword.
val arrs1 = ArrayBuffer[Int] ()Copy the code

We try to change the length of the ArrayBuffer and see if the hashcode changes:

val arrs = new ArrayBuffer[Int]()

arrs.append(5.6.7)
println(arrs.hashCode())

arrs.append(2.3.4)
println(arrs.hashCode())
Copy the code

The two lines of Hashcode printed in the console are not the same. This shows that the underlying dynamic scaling of arrayBuffers is similar to that of arrayLists mentioned earlier.

3.2.1 Why Can ArrayBuffers decorated with Val be dynamically expanded?

In the above code, why are arRs decorated by val allowed to be referenced? I read the source code of ArrayBuffer:

class ArrayBuffer[A] (override protected val initialSize: Int)
  extends AbstractBuffer[A]
     with Buffer[A]
     with GenericTraversableTemplate[A.ArrayBuffer]
     with BufferLike[A.ArrayBuffer[A]]
     with IndexedSeqOptimized[A.ArrayBuffer[A]]
     with Builder[A.ArrayBuffer[A]]
     with ResizableArray[A]
     with CustomParallelizable[A.ParArray[A]]
     with Serializable
Copy the code

Arraybuffers inherit quite a few characteristics. One attribute to note is ResizableArray[A], which is named to represent the extensible array attribute. There is an important member of this trait that is modified by var:

protected var array: Array[AnyRef] = new Array[AnyRef](math.max(initialSize, 1))
Copy the code

As you can see, ArrayBuffer is actually wrapped in a layer: all the dynamic expansion/reduction operations replace references to array members. The val modifier only limits the reference to an ArrayBuffer from being changed. When it is dynamically expanded, what is actually changed is its internal var modified array member. However, if you try to modify a reference to an ArrayBuffer, your program is bound to report an error.

val arrs = new ArrayBuffer[Int] ()// This val does not allow modification of the arrs itself. The internal array is allowed, however, because it is a member of the VAR decoration.
arrs = new ArrayBuffer[Int] (4)
Copy the code

3.3 Symbol operations of ArrayBuffer

As I mentioned in the Implicit Conversions section, Scala is keen to use simple mnemonics to help us write more elegant code. As you’re sure to know, these symbols aren’t some arcane Scala syntactic feature, we’re just calling functions that use mnemonics as identifiers.

The following operators operate on elements with ArrayBuffer.

symbol role
++ Is used to mergeArrayBufferorArray
+ = Add elements that match the type to thisArrayBufferIn the.
+ + = To anotherArrayorArrayBufferAdds the element inside to the originalArrayBufferInside.
- = Deletes a specified element. If the element does not exist, the operation will not report an error and will not cause a change.
--= Delete and the other oneArrayorArrayBufferElements that repeat inside.

3.4 Conversion of variable length array and fixed length array

The fixed-length Array provides a toBuffer method to convert to a variable-length Array Buffer (which is a trait).

The variable-length ArrayBuffer and toArray methods convert to a fixed-length Array.

Note that arrays and ArrayBuffers are not exactly converted to each other, but rather the following “triangle” :

The toBuffer method provided by Array actually derives from the IndexedSeqLike attribute. The return type declared by this method is Buffer, and the source code tells us that the real return value is a transformation object on ArrayBuffer that implements the Buffer property, which we can check with the getClass method.

If you want to convert an Array to an ArrayBuffer, you can use asInstanceOf[ArrayBuffer[T]] to cast the toBuffer. It covers higher-order types, or simply the case of generics nested within generics, thanks to Scala’s more powerful type system. Here is an example of this code:

val array : Array[Int] = Array[Int] (1.2.3.4)
val arrayBuffer : ArrayBuffer[Int] = ArrayBuffer[Int] (5.6.7.8)

/ / toBuffer method returns is a scala collections. Mutable. The Buffer type.
val toBuffer: Buffer[Int] = array.toBuffer[Int]

// You can cast to get a more concrete ArrayBuffer than a Buffer.
// val toBuffer: ArrayBuffer[Int] = array.toBuffer[Int].asInstanceOf[ArrayBuffer[Int]]

// Checking the toBuffer also shows that it is actually an uptransition object of type ArrayBuffer.
println(toBuffer.getClass)

// The array itself has not changed. The output will get [I, because it's essentially a Java Int[] array.
println(array.getClass)

//ArrayBuffer[T] can be converted toArray [T] by toArray method.
val toArray: Array[Int] = arrayBuffer.toArray

// The arrayBuffer itself does not change. Output will be scala. Collection. The mutable. ArrayBuffer.
println(arrayBuffer.getClass)
Copy the code

3.5 Creating multidimensional arrays in Scala

Scala specifies the dimension and element types of an immutable multidimensional array in the following way, whose generic type is a higher-order type.

val array: Array[Array[Int]] = Array.ofDim[Int] (2.3)
Copy the code

This line of code generates an array of two rows and three columns. That is, two subarrays are generated, each containing three elements. When assigning and accessing an n-dimensional array, we need n parentheses to “position” :

// array(1) returns the first row of array.
array(1) (1) =2
Copy the code

Iterating over multi-dimensional arrays is also a little more complicated. Here are the implementations of the for-each and subscript iterating styles:

//for each style traversal mode
for{
  line<-array2d
  e<-line
}{
  print(e)
}

//indices Index subscript
for(x <- array2d.indices){
  for (y<- array2d(x).indices){
    println(s"position:{$x.$y} : value =${array2d(x)(y)}")}}Copy the code

3.6 Collection conversion between Scala and Java

To ensure compatibility between data types when writing mixed Java and Scala projects, Scala provides tools to convert collections between Scala and Java. The implementation is exactly the implicit transformation mentioned in the previous chapter, so we can skip over the low-level details.

3.6.1 ArrayBuffer (Scala) -> List (Java)

Scala provides an implicit conversion function that converts an ArrayBuffer (which belongs to Scala) to a List (which belongs to Java).

//JavaConversions also provides many other implicit conversion functions.
import scala.collection.JavaConversions.bufferAsJavaList
Copy the code

Then you can use Java. Util. The List [T] type variable to accept a scala. Collections. Mutable. ArrayBuffer assignment of an object. Here is a complete code example:

val scalaObjectsBuffer: ArrayBuffer[scalaObject] =
ArrayBuffer[scalaObject](new scalaObject(1), new scalaObject(2))

//JavaConversions also provides many other implicit conversion functions.
import scala.collection.JavaConversions.bufferAsJavaList
import java.util

// Use the imported implicit conversion function to convert The Scala ArrayBuffer -> Java List.
val javaObjectsList: util.List[scalaObject] = scalaObjectsBuffer

//Java style Iterator Iterator traversal.
val iterator: util.Iterator[scalaObject] = javaObjectsList.iterator()
while (iterator.hasNext) {
	println(s"scalaObject id:${iterator.next().oid}")}//--------scalaObject is a simple class -------------//
class scalaObject(val oid: Int) {}
Copy the code

3.6.2 List (Java) -> Scala (ArrayBuffer)

It’s the same thing in reverse. This time implement it in Java code, and make sure the Java code uses the SDK.

/ / import scala. Collection. JavaConversions associated objects.
import scala.collection.JavaConversions;
import scala.collection.mutable.Buffer;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class Java2Scala {

    public static void main(String[] args) {
  		// Create a List in Java.
        List<javaObject> javaObjects = new LinkedList<>();
        Collections.addAll(javaObjects, new javaObject(1), new javaObject(2));

        //Java has no concept of implicit functions. In this case, functions are called explicitly.Buffer<javaObject> scalaObjectBuffer = JavaConversions.asScalaBuffer(javaObjects); System.out.println(scalaObjectBuffer); }}@SuppressWarnings("all")
class javaObject {
    public int jid;

    public javaObject(int jid) {
        this.jid = jid;
    }

    @Override
    public String toString(a) {
        return "javaObject:[id = " + jid + "]"; }}Copy the code

4. The Scala tuples

4.1 Under what circumstances is it needed

The existence of Tuples in Scala makes streaming business, which involves a lot of intermediate processes, very flexible and convenient. Is a simple container for holding complex data types. So why introduce tuples when you have OOP concepts?

Using Java Web development as an example, for each discrete query result, we should write another Bean type to host it, such as:

public class Result {
    private Integer count;
    private String studentName;
    private Integer disqualification;
    // The long Get Set method
    // Long constructor methods
}
Copy the code

Obviously, this Result has no purpose other than to serve as a data container for the results of a particular query. As the number of intermediate processes in the business increases, the number of such simple Result classes will increase, and the application may be in a “class explosion” situation. If you don’t want to design a class for every intermediate result, is it possible to use a Map instead of a Bean and store the data?

// Since the type of value contains Integer and String, the type of value can only be Object.
LinkedHashMap<String,Object> hashMap  = new LinkedHashMap<>();

hashMap.put("count".1);
hashMap.put("String"."Mr. Lee");
hashMap.put("disqualification"."1");
// This causes all types to become objects.
Object string = hashMap.get("String");
Copy the code

It’s possible in theory, but it’s not elegant. Since query results tend to be mixed with multiple data types, this means that you can only use the top-level Object to hold its value and cast it to get the expected data type.

The existence of a Tuple simplifies this problem to some extent. For small, scattered pieces of data, we don’t need to build a huge Bean, nor do we need to use a Map:

// Use a Tuple to load these loose data directly into a container.
//Tuple3 indicates that this is a container with three data loads.
new Tuple3<>(21."Mr. Lee".34);
Copy the code

But tuples can also cause problems. Because a Tuple is a loose data organization structure, if you want to find a certain data in a Tuple, you can only find it by the index number. When a structure combines too much data, it’s easy to forget (or misremember) the exact meaning of each element at each location. So in this case, we still need to set up beans to hold the data.

4.2 Creating a Scala Tuple

A Scala Tuple can hold up to 22 elements, which is more than enough.

To efficiently manipulate tuples, Scala classifiers tuples according to the number of elements they contain: for example, a Tuple with only one element is of type Tuple1, and a Tuple with two elements is of type Tuple2…… And so on.

The element type for each corresponding position within the TupleX (X means from 1 to 22, the same below) is specified by generics. For example, a Tuple3 type declaration:

val tuple3 : Tuple3[String.Int.Double]  = Tuple3("scala".1.1.00d)
Copy the code

It represents the creation of a Tuple container where the first location holds a String element and the second location holds an Int element…… Note that when you load elements, you have to pay attention to the mapping of positions.

Syntactic Sugar could be used. Syntactic sugar could be used. Here’s a syntactic sugar: the Tuple is represented directly in parentheses, and the actual Tuple type is inferred by the compiler.

val tuple : (String.Int.Double) = Tuple3("scala".1.1.00d)

// A more concise declaration:
val tuple_  = ("scala"."java".200)
Copy the code

4.3 Aside: Lazy person function provided by IntelliJ

However, when faced with tuples that wrap multiple elements, it is still cumbersome to manually supplement a full variable assignment statement.

// Try adding a full assignment using the TAB key in IntelliJ
Tuple3("scala".1.1.00d).var
Copy the code

For those of you using IntelliJ, here’s a trick: Type.var after a statement with a return value and hit Tab. IntelliJ automatically converts it into a single line of assignment statements, providing optional variable names based on the return value. In addition, you can also choose:

  • Declare the local variable asvarType.
  • Specifies the full type of the variable.

IntelliJ also offers a plethora of shortcuts. For more details, check out my other nugget blog post on common Shortcuts for the Windows 10 && IntelliJ Development Environment

4.4 accessTupleXThe elements within the

TupleX accesses the elements in the corresponding index position in the form of._index. The value of index ranges from 1 to X. As an extra reminder, this is not the index of the array 0, the index of the first position is 1. For example, to access the second element of a Tuple:

val tuple = Tuple3("scala".1.1.00d)

// _x means to access the element at the x position, so 1 is printed.
// Or write it as a postfix expression: tuple _2
println(tuple._2)
Copy the code

If it is customary to start at the 0 subscript position, this can be accessed using productElement(index).

 println(tuple.productElement(0))
Copy the code

The author thinks that using _index way is more intuitive, the second method can understand.

4.5 traversalTupleXThe elements within the

Traversing the inner elements of a Tuple relies on an iterator, productIterator.

// Do not write the following form directly:
//for(e <- tuple)
for (e<-tuple.productIterator){
  println(e)
}
Copy the code

5. Scala List

List in Java is an interface, the real implementation is ArrayList, LinkedList, etc. Scala, on the other hand, provides lists that can directly store data.

Note that List in Scala is immutable and belongs to the sequence Seq. If you want to use mutable list types, you need to introduce another data type: ListBuffer.

The List defined in scala. Predef package, so there is no need to explicitly introduced scala. Collections. Immutable. The List. The following shows how to create a simple collection List, including creating a List without any elements.

// Simply create a collection
val list : List[Int] = List(1.2.3.4.5)
// print list to display list (1,2,3,4,5)
print(list)

// Create an empty set. Nil is a separate class that inherits from List[Nothing].
val nil: Nil.type = Nil
// Print Nil will print List()
println(nil)
Copy the code

It is also possible to place more than one data type in a List, but this requires declaring the generic type of the List as Any. List elements are accessed in the same way as Array, using parentheses (index).

// Access the third element of the list, subscript starting from 0.
print(list(2))
Copy the code

5.1 Append List elements

5.1.1 Insert first and append a single element to the end

Since the List itself is immutable, the append operation does not change the original List, but returns a new List. Scala’s List provides operation symbols for appending, such as inserting header +: and trailing appending :+ :

// Append an element to the list using the + function, with list written first.
val addTohead: List[Int] = list :+ 0

// Append an element to the list using the +: function, followed by the list.
val addToTail: List[Int] = 6 +: list
Copy the code

5.1.2 Splicing elements

In addition to header and tail inserts, the :: symbol is used to concatenate multiple scattered elements into a List.

val tail = List(3.4.5)

/ / ints = List (1, 2, 3, 4, 5)
val ints: List[Int] = 1: :2 :: tail
Copy the code

The :: symbol operates from right to left, so make sure that the rightmost part of the expression is of the List type. If there is no List that can be used as a container, we can use Nil instead to load the concatenated elements into a new, empty List:

/ / ints = List (1, 2, 3)
val ints: List[Int] = 1: :2: :3: :Nil
Copy the code

Note that using :: loads elements into the List as a whole. If we intend to concatenate multiple sublists into one long List with the :: symbol, we may have unexpected results:

val list_1: List[Int] = List(1.2.3)
val list_2: List[Double] = List(1.0.2.0.3.0)
val list_3: List[Any] = List(obj(1), obj(2), obj(3))

val multi_list: List[Any] = list_1 :: list_2 :: list_3

print(s"list_3's size = ${multi_list.size}")
Copy the code

Based on the sequence of operations from right to left, list_3, the rightmost list_3, is used as a container, and then list_2 and list_1 are successively loaded into list_3 as a whole.

In other words: Multi_list now has five elements (two List and three Any elements) instead of six.

5.1.3 Splicing multiple sublists

This might not be what we expected: the addAll method, similar to that for Java Collections, is what we want to implement. In this case, we need to use the ::: operator instead of :: to indicate that all elements in a List are loaded, not the List itself.

::: Is equivalent to a flat operation.

val multi_list: List[Any] = list_1 ::: list_2 ::: list_3

print(s"list_3's size = ${multi_list.size}")
Copy the code

Of course, :: and :: can be mixed, always from right to left.

val multi_list: List[Any] = list_1 ::: list_2 :: list_3 :: Nil

print(s"list_3's size = ${multi_list.size}")

for (index <- multi_list.indices) {println(multi_list(index))}
Copy the code

Note that whether you use :: or :: to concatenate a List, make sure that the rightmost element is an array (which can be Nil), otherwise you will get a syntax error.

5.2 Use a variable length ListBuffer

ListBuffer is a variable-length list that belongs to a Seq sequence. It differs from the immutable List in that all add and delete operations are performed on the original List, rather than returning a new List.

// Construct an empty object using the new call constructor.
val ints = new ListBuffer[Int]()
ints.append(1)

// Use the static apply method to build.
val ints1: ListBuffer[Int] = ListBuffer[Int] (1.2.3.4)
ints1.append(2)
Copy the code

ListBuffer provides a method to append elements: append, or the more concise symbol +=.

// If only one element is added, += is used.
ints1 += 2

// The append method supports the variable parameter Any*, so it is used to append multiple elements at once.
ints1.append(3.4.5.6)
Copy the code

Use ++= to append all other elements ** from the ListBuffer.

// Build with the new call constructor, which is equivalent to List
      
        List = new LinkedList<>();
      
val ints = new ListBuffer[Int]()
ints.append(1.2.3.4)

// Use the static apply method to build.
val ints1: ListBuffer[Int] = ListBuffer[Int] (5.6.7.8)

ints1 ++= ints

// The console shows that Ints1 has 8 elements instead of 5.
println(ints1.size)
Copy the code

We can also use the ++= symbol alone to divide it into ++ and = to assign another new ListBuffer:

// Assign the appended array to the new ListBuffer.
val lnts2: ListBuffer[Int] = ints1 ++ ints
Copy the code

The ListBuffer uses subscripts to find and delete elements at the corresponding location, using the remove method.

5.3 Common symbol Operations for List and ListBuffer

symbol Apply to the List Apply ListBuffer role
: + yes yes Add a new element after the list. Instead of changing the original list, the new list is returned.
+ : yes yes Add a new element at the top of the list. Instead of changing the original list, the new list is returned.
: : yes no In front of theListThe whole load into a rightmostListIn the middle.
: : : yes no In front of theListEach element inside is loaded into a rightmostListIn the middle.
+ = no yes Add a new element to this list.
+ + = no yes To anotherListBufferIs added to the list in turn.
++ no yes with+ + =The difference is that it doesn’t change the originalListBufferInstead, it returns a new oneListBuffer.

6. Scala Queue

Scala, like Java, already gives a straightforward implementation of Queue. It is essentially an ordered list and can be implemented at the bottom using an array or a linked list.

Queue is obviously on a first-in, first-out basis. Scala provides both a mutable implementation and an immutable implementation for queues. For an immutable queue, an operation returns a new queue; For variable queues, in-queue and out-queue operations are performed in the original queue.

Here we use the variable in the collection Queue, it mainly introduces the scala. Collection. The mutable. The function of the Queue. To prevent confusion, you can prefix it to explicitly state whether a mutable or immutable queue is being used.

// Implement the apply method
mutable.Queue[Any] (1.2.3.4.5.6.7)

//new calls the constructor to implement
new mutable.Queue[Any] ()Copy the code

6.1 Trying to add a new element to Queue

Mutable queues support functions similar to those described in ListBuffer using += and ++= operations, which are presented directly here in code with a few points attached to the comments.

+= can be used to add a single element.
queue += 8

//+= will add the entire List as one element to the queue. However, your queue may not support putting the List in its entirety, because that's probably not what you want.
// If you want to add each element in the ListBuffer, you should use the ++= method.
queue += ListBuffer[String] ("a"."b"."c")

// The difference between ++= and += is that it appends all the elements in the queue in order, rather than adding the list itself.
queue ++= ListBuffer[Int] (8.9.10)
Copy the code

6.2 Queue Entry and exit

Queue also provides an enqueue method to Queue in, and a dequeue method to Queue out. Mutable queues will be changed from the original Queue, and immutable queues will return a new Queue.

// Implement the apply method
val queue: mutable.Queue[Any] = mutable.Queue[Any] (1.2.3.4.5.6.7)

// Append elements by operator
queue += 8

// Check the queue header
println(s"queue head : ${queue.head}")

// Pop the first element
val first: Any = queue.dequeue()
print(s"popped element: $first")

// Look at the queue header again
println(s"queue head : ${queue.head}")
Copy the code

In addition, you can view the contents of the elements at the head of the queue with queue.head, or at the end of the queue with queue.last (neither of which pops up).

Queue also provides a unique method: tail. So the name means, is to return to the tail. In a queue, anything except the first head element is called the tail (rather than the last element). This method pops up the first element and returns the remaining elements in a new queue. More generally, enqueue is the “head” and tail is the “torso” after the “head” is removed.

// Look at the elements in the original queue header.
print(ints.head)

// The tail method returns the new queue after removing the original queue from the head element.
val newInts : mutable.Queue[Int] = ints.tail

// Print the newly generated queue.
println(newInts)

// The original queue is unchanged.
println(ints)
Copy the code

The return value of the tail method is also a queue, so you can cascade the method so that it pops up multiple elements at once.

val ints: mutable.Queue[Int] = mutable.Queue[Int] (1.2.3.4.5)

// The tail method can be called cascaded because its return value is also a queue.
val newInts : mutable.Queue[Int] = ints.tail.tail.tail.tail

// Remove the four header elements, leaving the queue with only five.
print(newInts)
Copy the code

Using subscript access to access elements directly over FIFO principles is perfectly fine because Scala divides queues into linear sequences. But since you are using queues, you should try to use them on a queue first-in, first-out basis.

//get the 2th element of the queue?
println(ints(1))   //OK. queue is indeed a sequence.
Copy the code

7. Scala Map

In Java, a HashMap is a hash table, the internal constructs are unordered, and the key cannot be repeated, otherwise it will be overwritten.

Scala’s maps are mutable and immutable. A mutable Map is unordered, while an immutable Map is ordered. By default, immutable maps are used. If you want to use mutable maps, you need to add a prefix to distinguish them. The Map and related methods described below do not distinguish between mutable and immutable.

Map internally uses the type Tuple2 to represent key-value pairs, and Scala provides implicit conversions to wrap them in a “nicer” form:

// Since the k-v pair is essentially a tuple, it can also be written as parentheses.
// Note that the arrow form is "->", not "=>".
val mapSample = mutable.Map("Kotlin" -> "2020", ("Go"."2019"), "Python" -> "2018"."Java" -> "2017")
Copy the code

We try to print the mapSample variable directly and run this line of code over and over again. We can see that the key-value pairs are not printed in the same order as they were put in:

print(mapSample)
//----------Console-----------------//
//Map(Kotlin -> 2020, Go -> 2019, Java -> 2017, Python -> 2018)
//Process finished with exit code 0
Copy the code

Map is an unordered collection that is not strictly indexed like Seq.

7.1 Extracting a K-V Pair from a Map

7.1.1 Fetching the Value in the Map based on the key

Use parentheses to set the value of Map. The arguments in parentheses are the key values corresponding to the value that you are looking for.

val map = mutable.Map("scala"->"2019"."go"->"2018")

// You don't need to call the get method as Java does. You can also fetch the value directly.
// Of course, Scala also provides the traditional get method, with some differences from Java, which we'll discuss later.
print(map("scala"))

// If the program cannot find the key, will it return null as Java does?
print(map("scala~"))
Copy the code

The important thing to note here is that in Java, a value is returned if an existing key can be found, and a null is returned otherwise. In Scala, treatment is different: if the program can’t find the key value, will throw a Java NoSuchElementException.

/** * This method checks if the map passed in has a key-value pair with key "scala". * @ param StringMap need to pass in a Map [String, String] Map. * @ throws Java. Util. NoSuchElementException mechanism of scala and Java is different, if can't find the key, An exception is thrown. * @return returns true if no exception occurred. * /
@throws(classOf[NoSuchElementException])
def getScala(StringMap : mutable.Map[String.String) :Boolean= {StringMap("scala")
  true
}
Copy the code

7.1.2 Using the Contains method to check whether the key exists

Obviously, throwing an exception without any judgment mechanism is too risky. To avoid this problem, we can first use the contains method, which checks for the existence of the key before we pull it out. Here’s the improved getScala internal logic:

// It determines whether the key exists.
if (StringMap.contains("scala")) {
    // If it exists, remove it.
	StringMap("scala")
	true
} else false
Copy the code

7.1.3 Using the GET method to avoid Exceptions

So far we’ve been fetching values directly in the form of a map of keys. Scala Map also provides the same method as get(key), but it is different from Java Map’s get method (at least in Java version 8 and before).

The Scala Map get method returns a more secure Option[T] type. Option[T] itself spawns two subclasses: Some[T] and None. When the get method finds the key, it returns the corresponding value wrapped in the Some[T] class, where T is the return type of value; Otherwise, return a type of None[Nothing], indicating that no matching key was found.

Let’s take advantage of this feature and make another optimization of the previous getScala method:

 def getScala(StringMap : mutable.Map[String.String) :Boolean = StringMap.get("String").isDefined
Copy the code

7.1.4 The concise getOrElse method

Recall that The Optional[T] class was introduced in Java 8: its orElse method effectively avoids the null pointer exception that has plagued Java programmers for so long. The getOrElse() method provided by The Scala Map works in much the same way: returns value if a matching key is found, or a default value otherwise.

def getScala(StringMap: mutable.Map[String.String) :String = {
  StringMap.getOrElse("map"."None.")}Copy the code

7.1.5 How To Select These Value Modes

  1. If you can be sure that the key must exist in the map, you should use the most concise onemap(key)Form.
  2. If you want to take a different business logic based on whether the key exists in the map, you shouldmap.contains(key)Method judgment.
  3. If you just want to know whether a certain key-value pair exists, usemap.get(<key>).isDefinedTo judge.
  4. If you want to provide a default value when a key cannot be found, usemap.getOrElseMethods.

7.2 Map changes, additions and deletions

The add and remove operations described in this section are different for mutable and immutable maps.

7.2.1 Adding a Key

The += symbol is commonly used in mutable maps to add a k-V key-value pair to the original Map. If the key already exists, then this statement becomes a modification operation: it assigns a new value to the key without reporting an error.

Note that the += symbol actually expects a sequence of tuples, and if you were to add key-value pairs as tuples, you would need to nest another parenthesis in the outermost layer. If you want to add only a single element, the -> method is recommended here.

val map = mutable.Map("scala" -> "2019"."go" -> "2018")

// Append a key-value pair to the Map using the += symbol.
map += "Kotlin" -> "2017"
// Alternatively, append key-value pairs as tuples. Note that two levels of parentheses are nested to indicate that you are putting in a set containing tuples.
// map += (("Kotlin"->"2017"))

println(map.getOrElse("Kotlin"."()"))

// If a duplicate key is added, does the program report an error?
map += "scala" -> "2020"
println(map.getOrElse("scala"."()"))
Copy the code

Immutable Map Generally adds a new key-value pair with the + sign and returns a new immutable Map. An immutable Map can be operated with the += symbol, but this requires that the Map variable be modified by var, since this is equivalent to overwriting the reference to the original immutable Map with the reference to the result of the operation.

var map = Map("scala"->"2.11"."java"->"8")
map += "python" -> "3.8"
Copy the code

7.2.2 Deleting a Key

-= Indicates that the key pair of a specified key is deleted from a Map. If the key itself does not exist, nothing will happen and no error will be reported.

val map = mutable.Map("scala" -> "2019"."go" -> "2018")

// Delete the key-value pairs stored in the map by -= "key".
map -= "go"
println(map.getOrElse("go"."deleted"))

// If you are trying to delete a key that does not exist, will the program report an error?
map -= "Java"
Copy the code

For immutable maps, you can also use – or -= to delete key pairs. The same considerations apply to adding keys.

7.2.3 Modifying a Key

A simple assignment statement can be used to change the corresponding value of the key. If the key does not already exist, the statement becomes an add operation: it puts Tuple2(“scala”,”2020″) into the Map without an error.

Note that only mutable maps support modifying k-V pairs in this way.

val map = mutable.Map("scala" -> "2019"."go" -> "2018")

// Change the original key = Scala value from 2019 to 2020.
map("scala") = "2020"

// Try to print the scala value.
println(map.getOrElse("scala"."()"))

// What if we assign a value to a nonexistent key?
map("Java") = "2014"

// Will there be an error?
println(map.getOrElse("Java"."()"))
Copy the code

7.3 Map Traversal

In Java, traversing a Map is cumbersome: you need to get the Map’s KeySet and then iterate through it using an iterator. Scala provides a more concise and efficient way of traversing: a single for loop is needed to traverse a Map tuple.

The for loop may be written differently depending on your needs. Bonus tip: Keys and values can be used to retrieve all key values of the Map, or all value values.

// If we want to use both key and value during the traversal:
for ((k, v) <- map) {
	println(s"key:$k,value=$v")
}

println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --")
// If you want to use only keys during the traversal:
for (k <- map.keys) {
	println(s"key:$k")
}

println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --")
// If only value is used during the traversal:
var count = 1
for (v <- map.values) {
	println(s"$count element value=$v")
	count += 1
}

println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --")
// If we want to treat this key-value pair as Tuple2 during the traversal:
for ( tuple <- map) {
	println(s"key:${tuple._1},value=${tuple._2}")}Copy the code

8. Scala Set

A Set, “Set,” is a Set of non-repeating elements that does not guarantee their order. In Scala, sets are also mutable and immutable. By default, sets are immutable.

// Create an immutable Set.
val immutableInts = Set(1.2.3.4)

// Create a mutable Set.
val mutableInts = mutable.Set(1.2.3.4.5)
Copy the code

8.1 Adding elements to a Set

For immutable sets, the + symbol is supported to return a new Set after the addition operation is performed, leaving the original Set unchanged.

 val ints: immutable.Set[Int] = immutableInts + 5
Copy the code

For mutable sets, you can also use += or add to add elements to the original Set. Note that no duplicate elements are added to a Set.

// Add an element 5 to the collection.
mutableInts += 5

// You can also add an element to the collection using the add method.
// You can also declare mutableInts add 6
mutableInts.add(6)
Copy the code

If you use the += method on an immutable Set, it must be a variable modified by var, because the operation overrides the previous reference with a reference to the result, similar to adding and removing elements in the Map section.

var ints3: immutable.Set[Int] = Set(1.2.3)
ints3 += 4
Copy the code

8.2 Removing elements from a Set

For mutable sets, you can use -= or remove to remove an element from the Set. When there are no elements in the Set to delete, nothing happens, no error is reported.

// Delete an element from the set. If no element is changed, nothing happens.
mutableInts -= 5

// You can also remove elements from a collection by using the remove method.
mutableInts remove 5
Copy the code

Immutable sets can also use the – or -= symbol to remove elements.

8.3 Common methods of Set

Set is more of a mathematical Set. The common method is: intersection of two sets INTERSECT or the union of two sets ++.

Both immutable sets and mutable sets support this class of methods, and even the two can perform Set operations in between, always returning a new Set result. However, the Set is mutable or immutable, depending on whether the Set that calls this operation is mutable or immutable.

val setA = mutable.Set(1.2.3.4)
val setB = mutable.Set(1.2.5.7)

// Return A new mutable Set: it is the intersection of A and B.
val intersection: mutable.Set[Int] = setA.intersect(setB)

Return A new mutable Set, which is the union of A and B.
val union: mutable.Set[Int] = setA ++ setB

println("A ∩ b ="+intersection.mkString(","))
println("A ∪ b ="+union.mkString(","))
Copy the code

Set provides the Max method to extract the maximum value and min method to extract the minimum value. After understanding the Scala.math.ordering [T] attribute, which acts like Java’s Comparable

interface, you can customize the concepts of “maximum” and “minimum” for a class. For basic data types, we can directly extract the maximum and minimum values.

// After understanding the scala.math.ordering [T] attribute, you can implement the maximum and minimum values returned by the custom class.
// Return the maximum value of the Set
println(setA.min)

// Return the minimum value in the Set
println(setA.max)
Copy the code

9. More convenient mkString method

Scala collections provide an mkString method that allows you to quickly get a string of information about elements in any type of collection. Here’s a use case:

//mkString needs to pass a character (string) as the separator.

// the screen will print: 1,2,3,4,5,6,7
println(ints.mkString(","))
// The screen will print: 1-2-3-4-5-6-7
println(ints.mkString("-"))
Copy the code

The basic logic is to call the toString method of the inner element and concatenate it with delimiters.