Earlier in this chapter, WE introduced Scala’s associated objects and associated classes, as well as the abstract classes and attributes used to design the overall architecture of a project.

Scala has special classes designed to meet specific needs, such as inner classes (which also touch on the concept of “path-dependent types”), enumerated classes, sealed classes, and subsequent implicit classes for implicit conversions (with implicit modifications); Template classes for pattern matching (using case modifier)…… In this chapter, we will focus on the first three types. The last two classes, which deal with implicit conversions and pattern matching, will be covered in related chapters later.

The inner class

Java inner class

There are five types of content inside Java classes: properties, methods, inner classes, constructors, and code blocks.

There is another complete class structure that can be nested inside a Java class: this class is called an inner class. Classes that nest this class are called external classes.

One of the great features of Java inner classes is that they have direct access to the private properties of external classes, which represent a has-A relationship between classes rather than an IS-A relationship (which is implemented by inheritance).

Java inner classes are bound to an instance of an outer class, and this is the same for Scala.

The type of Java inner class

Java inner classes can be divided into four types:

  • Member inner class, not usedstaticAn instance of an inner class is bound to an instance of an outer class and cannot access the static content of the outer class.
  • Static inner class, usedstaticAn instance of an inner class is independent of an instance of an outer class and can access the static content of the outer class.
  • A local inner class that is defined for use within a method and is not externally accessible.
  • Anonymous inner classes, which before Java 8 were often used to implement interfaces or abstract classes once, have been optimized for Lambda expressionsSAMDeclaration of.

For example, listing a Block of Java code to represent Outer, Inner, and staticInner:

public class Outer {

    // Member inner class, no static modifier.
    Member inner classes cannot define static methods.
    class Inner{
        public void innerTest(Inner inner){
            System.out.println("print inner Test."); }}// Static inner class with static modifier.
    Static inner classes can define static methods.
    // Static inner classes cannot access non-static members of external classes.
    static class staticInner{
        public static void staticInnerTest(staticInner staticInner){
            System.out.println("print static inner Test."); }}}Copy the code

The three types of construction methods are as follows:

// Build a normal class
Outer outer = new Outer();

// Build an inner class of a normal class
// You need to hold an instance reference to Other before you can use it, because the inner class of the member is bound to an instance of the outer class.
Outer.Inner inner = outer.new Inner(a);

// Build a static inner class of a normal class that doesn't need to hold an instance reference to Outer first, just by the class name. A static inner class constructor can be constructed.
Outer.staticInner staticInner =new Outer.staticInner();

Outer.staticInner.staticInnerTest(staticInner);
Copy the code

When compiled, the three classes will eventually be split into three.class files: Outer$Inner. Class, Outer$StaticInner. Class, and Outer.

The meaning of creating inner classes

  1. The inner class has free access to the outer classprivateMembers.
  2. It can be used for lazy loading: for example, an inner class object is created only when the inner content is used.
  3. Describes classes and between classeshas-aRelationship.

Create inner classes in Scala

The static keyword does not exist in Scala, so static inner classes in Scala are declared in associated objects.

class Outer{
  //Scala's member inner classes are stored in companion classes
  class InstanceInnerClass{}}object Outer{
   //Scala's static inner classes are stored in associated objects
  class StaticInnerClass{}}Copy the code

Create a member inner class

Scala enforces the binding of a member’s inner class to an external class instance. It does not support writing like this:

new Outer().new InstanceInnerClass(a)Copy the code

When an instance of an inner class is created, references to its outer class must have explicit variable names, and anonymous instances of the outer class cannot be created through the new keyword or returned by other functions.

val outer  : Outer = new Outer
val instanceInner: outer.InstanceInnerClass = new outer.InstanceInnerClass
Copy the code

Note that instanceInner is of type outer.InstanceInnerClass. It makes it more explicit: ** The inner class instance does not belong to Outer, but to an explicit instance of Outer named **. Here is a representation of Java’s member Inner class, which is of type Outer.Inner, not Outer.Inner.

Outer.Inner inner2 = new Outer().new Inner(a);
Copy the code

The following Scala code blocks are given below:

val outerClazz1 = new OuterClazz
val innerClazz1 : outerClazz1.InnerClazz= new outerClazz1.InnerClazz

val outerClazz2 = new OuterClazz
val innerClazz2 : outerClazz2.InnerClazz= new outerClazz2.InnerClazz

if(innerClazz1.getClass == innerClazz2.getClass){
    println("outClazz1.InnerClass == outer2Clazz2.InnerClass")}Copy the code

InnerClazz1, the type of innerClazz2 is semantically different. Because one of their types is outerClazz1 and the other is outerClazz2. In fact, this type is: path-dependent-type.

However, executing the getClass code for comparison shows that the innerClazz1.getClass and innerClazz2.getClass are essentially the same: they both belong to the OuterClazz$InnerClazz type.

We’ll cover type projections later to eliminate the distinction between “same suffix, different prefix” path-dependent types.

Create a static inner class

In Scala, the process of creating a static inner class is not that different from Java:

val staticInnerClass : Outer.StaticInnerClass = new Outer.StaticInnerClass
Copy the code

Here is how Java is declared:

Outer.staticInner staticInner = new Outer.staticInner();
Copy the code

Permission control for two inner classes

Member inner classes and static inner classes are allowed to be modified to be private using the private keyword, but once we do, we cannot create instances outside of the outer class. The following function that attempts to pass a private member inner class as a return value will fail compilation:

class Outers {

  private class Inners{
    val value : Int = 1000
  }

  // It attempts to "escape" private inner classes from the original domain.
  def returnInners = new Inners
}
Copy the code

Private class XXX escapes its defining scope as part of type XXX The same is true for static inner classes.

Application of inner classes

Member inner classes access outer classesThe instanceA member of the

Scala’s access rules are the same as Java’s: an inner class can arbitrarily read all members of an external class instance (including private members), but the reverse is not true.

class Outer {
  
  def this(aValue1 : Int, aValue2  :Int) {this(a)this.OuterValue1 = aValue1
    this.OuterValue2 = aValue2
  }

  var OuterValue1: Int =  _
  private var OuterValue2 : Int = _

  class InstanceInnerClass {

    // The inner class can directly access the members of the outer class
    def readValue1 : Int = OuterValue1
    def readValue2 : Int = OuterValue2}}Copy the code

This is also commonly used in Scala to represent the external class instance to which it is bound:

class Outer {
  / /...
  class InstanceInnerClass {
    // The inner class can directly access the members of the outer class
    //Outer. This indicates that the inner class accesses the value of the member attribute of the Outer class instance to which it is bound.
    def readValue1 : Int = Outer.this.OuterValue1
    def readValue2 : Int = Outer.this.OuterValue2}}Copy the code

You can also give the “bound external class instance” an alias instead of Outer. This. It is written as follows:

class Outer {
 // The alias declaration must be placed in the first line of the external class.
  outerInstance=>
  class InstanceInnerClass {
    // The inner class can directly access the members of the outer class
    def readValue1 : Int = outerInst.OuterValue1
    def readValue2 : Int = outerInst.OuterValue2
  }
    
  / /...
}
Copy the code

The outerInstance generation here refers to each instance of type Outer that is bound. Notice that you need to place the declaration of the alias on the first line in the body of the external class structure.

Types of projection

Declare an Outer class Outer as follows:

class Outer {

  class InstanceInnerClass {
    
    val innerValue: Int = 100

    def readInnerInst(instanceInnerClass: InstanceInnerClass) :Int = instanceInnerClass.innerValue
  }
  
}
Copy the code

The inner class defines a method, readInnerInst, that reads the innerValue of the inner class and then attempts to call the innerValue in the main function. To illustrate, we create an inner class instance that binds two different external class instances:

val outer1 = new Outer
val outer2 = new Outer

val inner = new outer1.InstanceInnerClass
val inner2 = new outer2.InstanceInnerClass
Copy the code

In the main program, try calling the readInnerInst method of the inner instance and passing inner and inner2 as arguments, respectively:

//available.
inner.readInnerInst(inner)
//Oop!
inner.readInnerInst(inner2)
Copy the code

The first line of code, when the argument is inner, the program will run normally. Because this method is invoked inner belongs to outer1 inner class instances, so the method of parameters is also bound to outer1. InstanceInnerClass, and incoming inner is this type. For the second line of code, the compiler is quoted the wrong: because inner2 belongs to outer2. InstanceInnerClass, rather than outer1. InstanceInnerClass. Scala is more semantically qualified than Java: they are path-dependent types with different “prefixes.”

The type projection mechanism ensures that the parameter types of inner classes are no longer constrained by whether the bound external class instance is consistent (or path consistent). We make the following changes to the type of the instanceInnerClass parameter:

// External class # inner class
def readInnerInst(instanceInnerClass: Outer#InstanceInnerClass) :Int = instanceInnerClass.innerValue
Copy the code

This Outer#Inner declaration means that the Inner class parameter instanceInnerClass is independent of the instance of the Outer class to which it belongs (or is allowed to be any instance of type Outer), thus eliminating compilation errors suggested by the compiler.

Internal class actual combat demonstration

Why is Scala so obsessed with binding instances of inner and outer classes together? Here is an example to illustrate:

Suppose that there are two platforms Bili Bili and YouTube, and users on each platform are not communicating with each other, and the number of users on each platform is counted separately. Users on one platform can send and receive messages, but they can also push messages to users on other platforms in the forward mode. The following code implementation of the Platform class and its inner class User is given:

package Inner

object Platform {
  // Count the total number of users on the "whole network" (all platforms).
  var totalUsers: Int = 0
}

// The main constructor needs to pass in the name of the platform, such as "Douyu", "huya"...
class Platform(val platName: String) {

  // To avoid confusion, the alias plat is used to refer to each different Platform instance.
  plat =>
  class User(private var name: String) {

    // When a new user is registered, the number of plAT users is +1.
    plat.users += 1

    // The total number of users increases by 1.
    Platform.totalUsers += 1

    // Get the user name.
    def getName: String = name

    // Send messages to users on the same platform.
    def sendMsg(user: User, msg: String) :Unit = {
      println(s"send to ${user.getName}:$msg")}// Forward messages to users on different platforms. This is where the type projection comes in.
    def forwardMsg(user: Platform#User,msg : String) : Unit ={
      println(s"forward to ${user.getName}:$msg")}}// It is used to count the user traffic of each platform.
  var users: Int = 0
}
Copy the code

We use inner classes to express the HAS-A relationship between User and Platform to distinguish “which User on which Platform”. Create two platforms in the main function, and then create users in each platform, try to call message sending, message forwarding functions:

package Inner

object Net {

  def main(args: Array[String) :Unit = {

    // Two platforms.
    val CNPlatform  = new Platform("BiliBili")
    val USPlatform = new Platform("YouTuBe")

    // These users are BiliBili users.
    val CNUser1 = new CNPlatform.User("Xiao Mei")
    val CNUser2 = new CNPlatform.User("Lei Jun")


    // These are overseas youtubers.
    val USUser = new USPlatform.User("Tim")
    val USUser1 = new USPlatform.User("Tim")

    println(S "BiliBili number of people online:${CNPlatform.users}")

    println(s"YouTuBe online users : ${USPlatform.users}")

    println(s"Platform.totalUsers = ${Platform.totalUsers}")

    // Users on the same platform send messages.
    CNUser1.sendMsg(CNUser2."Hello!)

    // Users on different platforms forward messages.
    USUser1.forwardMsg(CNUser1."Hello!")}}Copy the code

summary

Because the inner class is bound to the outer class instance, we can express “different users on different platforms” in a more visual way: some users belong to bilibili.user, while others belong to youtube.user. Scala makes internal class parameters more semantically constrained, but also allows type projections to provide the requirement that users from different platforms can also access each other.

Enumeration class

One sentence sums up what enumerations do: label.

Type a keyword

Scala allows aliases for data types to describe a data type in terms that are easier to understand, similar to the C language typedef. For example, alias the Int data type:

// Rename the Int data type to Age.
type Age = Int

val studentAge : Age = 20

println(s"student's age = ${studentAge}")
Copy the code

Why do you do that? For example, our current business needs to process some data segments: name, profession, gender.

val major : String = "CS"
val name : String  = "Wang Fang"
val gender : String = "Male"
Copy the code

All of this data is of type String, and here you can alias it using the type keyword to make the program more readable.

type Major = String
type Name = String
type Gender = String

val major: Major = "CS"
val name: Name = "Wang Fang"
val gender: Gender = "Male"
Copy the code

We rename the String type with type to a semantic “label” to make it easier to separate the meanings of variables more clearly.

Declare enumerated classes in Scala

Now, if you’re going to use a set of labels for “man” and “woman”, some of you might think of 0 and 1 as two ints. This scheme works, but the code is poorly readable: it’s hard for others to intuitively guess what 0,1 means without looking through the source code.

//bad example
val gender : Int = 0
if(gender==0) println("It's a girl") else println("It's a boy")
Copy the code

In addition, if someone deliberately enters a value other than 0,1, the code logically fails. However, from the compiler’s point of view, gender is just a plain Int that should be accepted as any integer value.

We want the compiler to help check the type of each tag, rather than artificially handling matches through a lot of if or match statements. Therefore, enumeration classes are perfect for this kind of “labeling” work. How do you use enumerated classes in Scala?

Scala Enumeration types need to inherit the Enumeration implementation. Enumeration has a member Value. Value has two attributes inside: id of type Int and name of type String. We use these two attributes to distinguish different tags (values) under the same category.

Value provides the apply constructor, so we can quickly set a label with Value(ID,name).

Create a class Genders and insert two tags under the class: FEMALE, MALE to represent “FEMALE” and “MALE”. Instead of using a single attribute to represent multiple tags with different assignments and then using the if mechanism to detect them, we use one member variable to represent one tag.

object Genders extends Enumeration {
    val MALE : Value = Value(0."male")
    val female : Value = Value(1."female")}Copy the code

So we help insert the concept of “MALE” intuitively through Genders.MALE. When we insert its value, we insert the name MALE on the screen. (Depends on how you construct the Value)

Sometimes we don’t want their data types to be represented “ambiguously” with Value, where the type keyword comes in handy:

object Genders extends Enumeration {
    type Gender = Value
    // Use aliases to make the semantics of data types more explicit:
    val MALE : Gender = Value(0."male")
    val female : Gender = Value(1."female")
    
    //Enumeration provides values to hold labels. The name of each label is concatenated into a complete string using the mkString method.
    override def toString() :String = this.values.mkString(",")}Copy the code

The alias given by type applies only to its scope. If you want to use an alias used in another class, you need to import it using import.

// An external program wants to use an alias in Genders. But it's still essentially a Value class within Enumeration.
import Enu.Genders.Gender
Copy the code

Seal type

The Scala sealed class uses the sealed keyword. As its name suggests, a sealed class encapsulates a class within a package, allowing calls to be made outside the package (depending on whether you declare the class as public, not sealed), but only allowing extensions to the class to be declared within the package.

//-------------package1----------------//
// It is sealed, so it is a sealed class.
sealed class sealedObj

// Subclasses of the sealed class can be declared under the same package.
class subObj extends sealedObj 

//-------------package2----------------//
In other packages, it is not allowed to declare subclasses derived from sealedObj, even if package1.sealedobj is introduced.
class subObj extends package1.sealedObj
Copy the code