
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 18, 2024
In this tutorial, we’ll take a look at how to use the open keyword to signal that a class is open for extension in Scala 3. Then, we’ll focus on its intended use for concrete classes and its motivation.
We’ll first see how the open keyword behaves, then we’ll dive into the motivations for it, and finally, we’ll compare it with the sealed keyword, which is also available in Scala 2.
The open modifier on a class signals that the class is open for extension. Abstract classes and traits are, by default, considered open in Scala 3. Hence, this modifier only makes sense in concrete classes.
open is a “soft” modifier, meaning that it is treated as a modifier only if it is in a precise position, not otherwise. This means that we can define variables or values named open without worrying about the compiler complaining.
open introduces a new possibility for programmers. In Scala 2, we could extend any concrete class unless it was marked as final. Scala 3’s approach is safer, as programmers are now forced to think whether it’s safe for a (concrete) class to be extended and, if it is, they have to state that explicitly.
Let’s see how to use open in practice:
open class Album:
val tracks: List[String] = ???
class DeluxeEdition extends Album:
override val tracks: List[String] = ???
val premiumCode: String = ???
In the example above, Album is a concrete class that is safe to extend. Then, DeluxeEdition inherits from it, modifying the list of tracks and adding a new field, premiumCode. Album has the open modified in its declaration, and that’s why DeluxeEdition can extend it. Also, Album and DeluxeEdition can be defined in two different files.
It’s good practice to document the ways other programmers can extend an open class. For example, this includes the methods that subclasses may override.
Classes that are not marked with open can still be extended but only if at least one of the following conditions is true:
In our example above, if Album were not open, then we could extend it either by defining DeluxeEdition in the same file or by importing the adhocExtensions flag. If we didn’t do any of that and yet had DeluxeEdition inherit from Album, the compiler would show us a warning when compiling the extending class (DeluxeEdition). Still, this is just a warning and won’t cause compilation to fail (unless we configure the compiler differently):
-- Feature Warning: DeluxeEdition.scala:3:30 ----
|class DeluxeEdition extends Album
| ^
|Unless class Album is declared 'open', its extension
| in a separate file should be enabled
|by adding the import clause 'import scala.language.adhocExtensions'
|or by setting the compiler option -language:adhocExtensions.
Obviously, an open class cannot be final or sealed. For example, marking the class Album above as both open and final will produce an error at compile-time: illegal combination of modifiers: `final` and `open` for: class Album.
The documentation advises against using adhocExtension, even though there might be some cases where it’s useful.
For example, let’s consider test doubles. Suppose we have a concrete class that uses some external dependencies (database connections, for example). In this case, when unit-testing that class, we want to cut off the database. So, instead of mocking the class, we might want to subclass it, modifying some methods to run a stubbed implementation.
A well-established alternative to test doubles is dependency injection, but that’s not always possible, for example, because we’re working with a concrete class we didn’t write and/or we cannot modify.
Another legitimate use of adhocExtension is for temporary bug fixes. In this case, assume we’re using a library, and we ran into a bug in one of the classes. We can report it and wait for a fix, but we don’t how long that’s going to take. In the meantime, we can just import adhocExtension, subclass the buggy class, put in a temporary patch, and keep working on our code.
For those reasons, Scala 3 allows adhocExtension but only if we import the feature flag explicitly.
Classes that are neither open nor abstract are similar to sealed classes. As a matter of fact, there is no difference if we define a sub-class in the same file. However, sealed does not allow for extending classes to be defined in different files. open, on the other hand, allows that or shows a warning, depending on the import of adhocExtensions.
open is a new modifier in Scala 3, and it involves breaking changes. Hence, its introduction will be gradual. As a matter of fact, to allow cross-compilation between Scala 2.13 and Scala 3, the compiler will show the warning for the missing import of adhocExtensions only if we set another compiler flag: -source future. From Scala 3 on, the compiler will show the warning by default.
When we are writing a class and thinking of its extensibility, we have three possible choices:
In this article, we saw how to use the open keyword to signal that a class is open for extension in Scala 3. We discussed its motivations, saw how to use it in practice, and compared it with sealed.