This chapter mainly introduces how to implement Scala class inheritance, and the concepts derived from inheritance such as superclass, abstract class, overwrite, supertransition object and so on. In order to understand Scala’s rewriting mechanism, the reader feels it is necessary to review Java’s dynamic and static binding mechanisms.

In addition, with the introduction of inheritance, we begin this chapter by paying extra attention to class control: which members (properties and methods) are allowed to be called externally, and which members are only allowed to be called by subclasses, or used only by this class.

Another important concept was introduced in Java to remedy the shortcomings of single inheritance: the interface. Scala provides a more powerful implementation of traits, which I will introduce in a separate post.

Inheritance and rewriting in Scala

Scala’s inheritance is similar to Java in that it uses extends as a keyword. In Scala, subclasses have access to the public attributes of their parent class, essentially by inheriting the XXX or xxx_eq$methods that their parent class exposes.

Be careful: subclasses can’t access private modified members of their parent class, not because they don’t inherit private members of their parent class, but because the parent class doesn’t provide public access methods.

Rewriting is based on the concept of inheritance extension: modifying methods inherited from a parent class to be used in a more specific business based on actual needs.

In Java, you can use the @Override annotation to check overriding methods (or properties) of a parent class/interface. In Scala, it appears as a keyword, and the override keyword must be explicitly added when overriding a method. If a subclass declares a method (or property) with the same name as the parent class, but does not include this keyword, an error is reported.

class Person{
  protected val age : Int = 100
  def run = "slow"
}

class Athlete extends Person {
  override val age : Int = 100
  override def run ="fast"
}
Copy the code

If you want to explicitly call the same name method/property of the parent class, you can do it with super.{}, as in Java. Another note! Subclasses can only override attributes declared as val in their parent class, because attributes modified by var can be changed at will and overwriting is impossible.

Scala constructs superclasses

In Java, the constructors are horizontal. And the super class constructor is first called either explicitly or implicitly using the super keyword before calling its own constructor. Even for classes that do not inherit, their constructors will always call the super() superclass constructor by default, because Java dictates that all classes inherit from Object.

In Scala, the only way to invoke a superclass constructor is through the primary constructor of a subclass. The secondary constructor cannot call any constructor of the parent class by super.

To initialize the attributes of the parent class, we pass arguments to the parent class’s primary or secondary constructor through the primary constructor of the subclass.

Here is a code demo:

class Person(var age:Int){}

// Assign a value to the parent constructor using arguments in the primary constructor of the subclass.
class Student(age :Int) extends Person(age = age){}
Copy the code

Control permissions in Scala

In Java development, control over class members (including properties and methods) is mostly public or private. Default or protected access is rarely used, especially default. So Scala simply removes the relatively “ambiguous” default modifier.

In addition, the following points should be noted:

  • Scala There is no publicThe keyword. The member is public without any modifiers.
  • ScalaprotectedThe keywords are more strict,It is only available to subclasses, not to other classes in the same package.
  • Scala’s in-class attributes are compiled at the bottom as.classIt’s all about documentsprivate. The compiler uses this to determine whether a property is exposed or not at the Scala levelprivateDecorated to determine whether to provide at the bottomxxx / xxx_eq$Methods.
  • The compiler determines whether a method is declared to be at the Scala levelprivateTo decide which method to declare underneathpublicorprivate
  • The compiler determines whether a property is true at the Scala levelvalVariable to determine whether this attribute is appended at the bottomfinalKeyword modification.
The modifier Visible to subclasses Visible externally
The default Square root Square root
protected Square root x
private x x

There are aboutprotectedKeyword confusion

If we look at the bytecode files using JD-GUI, we can see that protected properties in Scala look no different in a.class file than public properties. It seems that Martin has done something to the compiler itself that prevents us from accessing protected properties even in a single package.

class Clazz(  protected val value : Int)
//---------------- main program -------------------//
val clazz = new Clazz(10)
//IDEA does prompt that clazz.value can be called, but an error will be reported once it is written.
println(clazz.value)
Copy the code

Be aware, however, that IDEA may give bad code prompts when writing code, allowing us to access protected members in an unrelated class (this may be a bug in IDEA).

Attribute to rewrite

Before delving into Scala’s property overrides, it’s worth reviewing Java’s dynamic binding mechanism. Since properties in Java are statically bound (or in terms of property hiding), property overrides don’t make any sense from a Java perspective.

I mentioned binding mechanisms in another article: Sideshow: Java’s dynamic and static binding mechanism

Scala’s Override keyword can be used to override attributes of a parent class. But there are two conditions for property overrides:

  1. This property isDo not rewrite. usingvalModification.
  2. This property is at least readable to subclasses (that is, public, or protected).
class Person{  
 protected val age:Int = 20
}

class Student(InAge :Int) extends Person{
  The Int argument passed overrides the age keyword of the parent class.
  override val age : Int = InAge
  
}
Copy the code

Property overwriting is just a trick in Scala

Scala’s property overrides are still essentially method overrides, because Scala’s property access essentially calls its exposed XXX (get) and xxx_eq$(set) methods.

Why can’t we override var with val?

As we mentioned earlier, subclasses cannot override the var attribute of their parent class using the val attribute. Suppose the following code is reasonable:

class Person {
  var salary: Int = 100
}

class Athlete extends Person {
  override val salary: Int = 500
}
Copy the code

At underlying compilation time, the parent class has two methods: salary (equivalent to Get) and salary_eq$(equivalent to Set), while subclasses can only override the SALARY method. When salary is assigned, the parent class’s salary_eq$method is called (because subclasses don’t have overrides of that method), and the parent class’s salary value is overridden. Because the method is dynamically bound, the program preferentially selects the salary method of the subclass. Obviously, the salary attribute we read and write is not the same.

A situation where properties and methods overwrite each other

In Scala, the line between properties and methods becomes slightly blurred. Here are the changes to the above code:

class Person {def salary: Int = 100}
Copy the code

Attention! Salary here is a method modified by def. It is a very simple get function (declared without parentheses () and belonging to parameterless method, a parameterless function). Since the salary method above does not have any additional code blocks (or side effects) inside it, it is exactly equivalent to the following declaration:

val salary : Int = 100  
Copy the code

So much so that we can override the salary method in subsequent subclasses with a salary attribute of the same name (remember to add the override keyword). In a similar way, methods can override attributes in their parent class that are decorated with val.

class Teacher(override val salary : Int) extends Person
Copy the code

Bring the perspective back to the Person class. Either way, the caller of a Scala program can get the property value simply by using.salary:

val person = new Person()
person.salary
Copy the code

Therefore, we can also say that in Scala, attributes can be defined either as member variables or as no-parameter functions of type GET. What’s the point of this? I’ll cover the unified access principle in more detail in a later section on Advanced Scala functions.

Var can override the var property of the superclass abstraction

This raises the question: what are abstract attributes in Scala? We mentioned earlier in Scala’s datatypes that member attributes of a class must be assigned a value, or assigned a default value using null or _.

Properties with uninitialized values are considered abstract unless the class itself is abstract (which we’ll get to in a moment).

// If there are abstract attributes, the class must also be declared abstract.
abstract class Person{
  // Values that are not initialized are treated as abstract attributes.
  var value : Salary
}
Copy the code

When the bytecode file is compiled, its XXX and xxx_eq$methods also correspond to abstract methods. Note: Abstract member attributes must be modified by var. A property decorated with val adds final keyword protection underneath, making it impossible to override.

// Ignore some irrelevant code
public abstract class Person
{
  public abstract int salary(a);

  public abstract void salary_$eq(int paramInt);
}
Copy the code

If we declare a Student class that inherits from the Person class, the compiler says we must override the abstract salary property (because we essentially override the abstract salary and salary_eq$methods, unless Student is also declared as an abstract class, Passing the abstract method to the next subclass that inherits from Student.

class Student extends Person{

  override var salary: Int = 10000
  
}
Copy the code

It is not so much a rewriting of the parent class’s abstract property of salary, but rather the implementation of the parent class left behind the abstract method. So in this case, no error is reported even if the override keyword is not written.

Scala properties are dynamically bound

With all the evidence that Scala’s attributes are fundamentally different from Java’s, it’s time to talk more deeply. First, from the perspective of Java code, start with a simple example:

public class BindingStaticInJava { private static class Father{ // 1. public int i = 20; // 2. Since the property is statically bound, the I called is comment 1. That property of. public int getI() { return i; } } private static class Son extends Father{ public int i = 10; } public static void main(String[] args) { Father o = new Son(); // 3. See section 2 of comment for the code called. System.out.println(o.getI()); }}Copy the code

The logic of this code needs no further elaboration. It is clear that the getI() method returns the value of I defined in Father. The final return value is 20. The following example combines dynamic and static binding:

public class BindingDynamicInJava { private static class Father{ public int i = 20; public int getI() { return i; } } private static class Son extends Father{ // 1. public int i = 10; // 2. Since the property is statically bound, the I called is comment 1. That property of. public int getI(){ return i; } } public static void main(String[] args) { Father o = new Son(); // 3. Since the method is dynamically bound, see comment 2 for the code called. Part. System.out.println(o.getI()); }}Copy the code

In contrast to the previous example, Son, a subclass of this code, has its own overridden getI() method. Because O is an uptransition object, the main program locates this method through dynamic binding. So, who does the I in this method refer to? Due to the mechanism of static binding, Java determines at compile time that the I here is the I defined internally by Son. Therefore, the final run result of this code is 10.

Now let’s replicate the logic implemented in the BindingStaticInJava class from a Scala perspective:

object BindingStaticInScala { sealed class Father{ val i : Int = 20 // 1. // def returnI: Int = getI(); // In Scala's underlying logic, I is a method, so the JVM calls it dynamically. // See section 2 of note. def returnI : Int = i } sealed class Son extends Father { // 2. The property declared by the subclass is actually a rewrite of the parent getI() method. // This also results in the JVM eventually being dynamically booted to this point "when returning the attribute value of I" and returning 10 instead of 20. override val i: Int = 10 } def main(args: Array[String]): Unit = { val o : Father = new Son // 3. See comment 1 for the code called. Part println(o.returni)}}Copy the code

Remember, in our Java experiment, we learned that this logic should eventually return 20. But in the Scala version, the result is 10, and the program calls the returnI method in Father, but it obviously ends up going to Son and returning the value of I.

It’s also quite simple to explain why this happens: Scala properties are essentially equivalent to Java methods, and the JVM’s handling of method calls is always dynamic. So, if we think of I as a method, then everything makes sense. The following Java code is the equivalent of its Scala version:

Public class HowScalaBinds {private static class Father{// Equivalent to Scala's val I. // Immutable attributes do not provide set methods. public int getI() {return 20; } public int returnI(){// Note that this is still a dynamic call. return getI(); }} Private static class Son extends Father {// The JVM finally locates this through dynamic calls, which is why Scala programs end up returning 10 instead of 20. public int getI() {return 10; } } public static void main(String[] args) { Father o = new Son(); System.out.println(o.returnI()); }}Copy the code

An abstract class

The value of an abstract class lies more in the design, giving an abstract specification at a high level and handing over the implementation to subclasses.

In Scala, the abstract keyword is used only to mark a class, indicating that the class has abstract properties or methods. In Scala, if you want to represent an abstract method or property, you simply do this:

  • wrongvarAttribute assignment.
  • A structure that does not declare member methods.

Therefore, there is no need to use the abstract modifier before properties or methods to declare them abstract.

abstract class Person {
  // Abstract attributes
  var salary: Int
  // Abstract methods
  def action : Any
}
Copy the code

Some details of the abstract class

Abstract properties and methods are collectively referred to as abstract members for the sake of description.

  1. As in Java, abstract classes cannot be instantiated unless all of their abstract members are implemented using anonymous classes.
  2. Abstract classes can have concrete members, but they always have abstract members. Even if there is no abstract member, the compiler does not report an error…… But then why should we define it as an abstract class?
  3. If a class inherits from an abstract class, it must implement all the abstract members, or declare itself as an abstract class and throw the rest of the abstract members to the next subclass that inherits from that class.
  4. Abstract membersUnusable privateorfinalKeyword modifiers, because they run counter to the idea of inheritance and rewriting.
  5. According to clause 4, we can’t use itvalTo modify abstract properties.
  6. You may not use it when implementing abstract membersoverrideKeyword modification, becauseThis is an implementation in itself, not a rewrite.

Implement abstract classes using anonymous subclasses

Scala’s anonymous classes are similar to Java. The declaration needs to be followed by a code block to implement all the abstract members. Here’s an example, such as implementing an abstract class Person declared above:

val person : Person = new Person {
    
    override def action: Any ={
		println("Hello!")}override var salary: Int = 1000
}
Copy the code

Scala type checking and conversion

When examining the specific type of a transition object, we need a mechanism to detect the actual type of an object. Let’s introduce Scala’s conversions in comparison to Java code:

IsInstanceOf classOF [T], [T], asInstanceOf [T].

This paper Java Style Scala Style
Reflect the class name by class String.class; classOf[String]
Determines whether an instance belongs to a class "java" instanceof String; "Scala".isInstanceOf[String]
Cast to a class (Apple)new Fruit(); person.asInstanceOf[Athlete]
View the class name of an instance apple.getClass().getName() person.getClass.getName

Student: Does T’s ClassName have to be T?

This assertion fails in the following cases. That is:

  • PIs an interface, andSThis interface is implemented.
  • PIs an (abstract) class, whileSInherits the class.

When a reference to P refers to an instance of S (the upper transition object), the getClass() method is executed and the resulting type is S.

P parent = new S();
// The output className is S, not P.
System.out.println(parent.getClass().getName());
Copy the code

A good example is the Collection interface implementation in Java:

List list = new LinkedList();
// Print a List or LinkedList?
System.out.println(list.getClass().getName());
Copy the code

So when we want to know the real type of an upper transition object, we often use isInstanceOf[T], getClass(), etc.

Implement parameter polymorphism in Scala based on type conversions

We want to implement the following features:

  • Now both Student and Teacher inherit from the Person class.
  • The Student haslearnMethod, Teacher hasteachMethods.
  • There is a functionactionThe parameters received are limited to subclasses of Person.
  • Now set the following functionality: If the argument passed is the Student class, execute itlearnMethods; Vice executiveteachMethods.

The program diagram is as follows:

To complete the case, we need to figure out the actual type of the upcast object passed to the action function, and then execute different methods based on our judgment.

One thing to note: casting an object using the asInstanceOf[T] method returns a new reference, leaving the original object of the same type.

First, three simple classes are given: Student, Teacher and Person.

class Person {
  def behave = "taking action"
}

class Teacher extends Person {
  def teach = "Teaching Scala"
}

class Student extends Person {
  def learn = "Learning Scala"
}
Copy the code

Define an action function within the main method: feedback the corresponding behavior based on the actual type of the parameter p.

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

  // The passed reference p may be Teacher, Student, or any other class that inherits Person.
  def action(p: Person) :String = {
    // With pattern matching in hand, we can implement this logic in a more elegant way.
    if (p.isInstanceOf[Teacher]) {
      p.asInstanceOf[Teacher].teach
    } else if (p.isInstanceOf[Student]) {
      p.asInstanceOf[Student].learn
    } else {
      p.behave
    }
  }

  val teacher: Teacher = new Teacher
  val student: Student = new Student

  printf(action(teacher))

}
Copy the code

In the subsequent pattern matching, we will use a more efficient method to solve this problem.