1

When creating an instance of a subclass but typed into its superclass, the members of the subclass are not accessible, why? What is the usecase for typing into its superclass of the subclass ?

class Fruits{
  val fruit=12
}
class Apple extends Fruits{
  val apple = 90
}
class Orange extends Fruits{
  val orange = 9
}

val aa:Fruits = new Apple
//aa.apple ,This is not accessible, but I have created an instance of Apple, and class Apple contains apple
val ab:Apple = new Apple
ab.apple //Ok
1
  • 3
    Because that is how it is supposed to work. If you have a Fruit you can only use the available methods on a Fruit the fact that you know that it is an Apple is meaningless, the compiler does not know that, so it can not prove that your call to apple is safe. This is basic typing. Commented Dec 25, 2019 at 19:41

3 Answers 3

4

Contrast the static type (at compile-time) versus type at runtime of variable aa in

val aa: Fruit = new Apple
          |           |
     static type  runtime type

The key is to understand the compiler is not aware of types at runtime and only verifies constraints specified by static types. Because you explicitly instructed the compiler with type annotation :Fruit in aa: Fruit the compiler does not know aa is actually an Apple at runtime. Hence aa.apple is a compiler error because Fruit does not have the field apple.

One use-case of inheritance is to enable subtype polymorphism:

trait Shape {
  def area: Double
}

class Circle(val radius: Float) extends Shape {
  override def area: Double = radius * radius * Math.PI
}

class Rectangle(val width: Double, val height: Double) extends Shape {
  override def area: Double = width * height
}

object ShapeArea {
  def area(shape: Shape): Double = shape.area // <=== define method once for all kinds of shape
}

import ShapeArea._
area(new Circle(2))       // res0: Double = 12.566370614359172
area(new Rectangle(2, 3)) // res1: Double = 6.0

As a side-note, in idiomatic functional programming Scala there exists an alternative ad-hoc polymorphism (typeclass) approach:

sealed trait Shape
case class Circle(radius: Float) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape

trait Area[T <: Shape] {
  def area(shape: T): Double
}

object Area {
  def area[T <: Shape](shape: T)(implicit ev: Area[T]): Double = ev.area(shape) // <== define method once for all kinds of shape

  implicit val circleArea: Area[Circle] =
    (circle) => circle.radius * circle.radius * Math.PI

  implicit val rectangleArea: Area[Rectangle] =
    (rectangle) => rectangle.height * rectangle.width
}

import Area._
area(Circle(2))       // res0: Double = 12.566370614359172
area(Rectangle(2, 3)) // res1: Double = 6.0
Sign up to request clarification or add additional context in comments.

Comments

0

This helps to explicitly limit the amount of information accessible/exposed about referred objects; it could be perceived as a form of information hiding. It can be useful to avoid accidentally dependence on extraneous information about objects.

Comments

0

As other answers have already stated that the reason is compiler is not aware that aa is an apple. It thinks aa as a fruit because of static type Fruits provided here val aa:Fruits = new Apple in your code.

One real world use case we have is interaction between BusinessLayer and DataAccessLayer. BusinessLayer has to save some object in a persistent storage and it will call save() method of DataAccessLayer to achieve that. BusinessLayer does not care for the implementation of save(). Its up to DataAccessLayer how it defines the save() method.
In production the save() method will save the object ,for example, in postgres database. But when I am doing some local testing I may not want to set up postgres database. In that case I would like to save object as json in my local filesystem. How can we achieve this ?
We can provide two implementation of DataAccessLayer: PostgresDataAccessLayer and JsonDataAccessLayer. My local test code will call save() method of JsonDataAccessLayer while the production code will call PostgresDataAccessLayer's save() method. Code will look like as below:

trait DataAccessLayer {
  def save(obj: SomeType)
}

class PostgresDataAccessLayer extends DataAccessLayer {

  override def save(obj: SomeType) = {
    //logic to save in postgres DB
  }

}

class JsonDataAccessLayer extends DataAccessLayer {

  override def save(obj: SomeType) = {
    //logic to save as json in local file system
  }

}

class BusinessLayer(val dal: DataAccessLayer) {

  def save(obj: SomeType) ={
    dal.save(obj)
  }

}

//Production code
val bsLayerProd = new BusinessLayer(new PostgresDataAccessLayer())

//Test Code
val bsLayerTest = new BusinessLayer(new JsonDataAccessLayer())

You can pass object of any of the implementation of DataAccessLayer to BusinessLayer while instantiating it. At run time, save() method of underlying subclass will be called.
This kind of interaction , as in between BusinessLayer and DataAccessLayer here, is known dependency inversion i.e. High-level modules should not depend on low-level modules. Both should depend on abstractions.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.