來自:http://www.jesperdj.com/2016/01/08/scala-access-modifiers-and-qualifiers-in-detail/html
Just like Java and other object-oriented programming languages, Scala has access modifiers to restrict access to members of classes, traits, objects and packages. Scala’s access modifiers are slightly different than Java’s; in this post I explain the difference. Besides modifiers, Scala also has qualifiers which allow more fine-grained access control. This is a feature that doesn’t exist in Java.
java
Java has four access levels:oracle
Modifier | Class | Package | Subclass | World |
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
no modifier | Y | Y | N | N |
private | Y | N | N | N |
Scala has three access levels, and they differ slightly from Java’s levels:app
Modifier | Class | Companion | Subclass | Package | World |
no modifier | Y | Y | Y | Y | Y |
protected | Y | Y | Y | N * | N |
private | Y | Y | N | N * | N |
*: Top-level protected and private members are accessible from inside the package.less
Notable differences between Java and Scala are:ide
In my opinion, Scala’s access levels make more sense than Java’s; the default access level in Java (package-wide access) is hardly ever used in practice, so it’s strange to have this as the default, and the fact that protected members are accessible to unrelated classes in the same package is most of the time not what you want. In Java, it’s not possible to make members accessible only to other members in the class itself and to subclasses, but not other classes in the same package.post
In Java, top-level classes and interfaces can only have one of two access levels: public or the default (package-scope) access level. You cannot make a top-level class or interface private or protected in Java.ui
In Java and also in Scala, when you make a nested class private, it means that the class is accessible only from the enclosing class. The meaning of private for a top-level class in Scala is just an extension of this idea, but now the enclosing scope is the package in which the class is defined instead of an enclosing class.this
So, at first sight, it looks like a private top-level class in Scala is the same as a top-level class in Java with no access modifier; in both cases, the class is accessible only from the package that it is defined in. But this is not entirely correct. In Java, a class with no access modifier is not accessible in a nested package:idea
1
2
3
4
5
6
7
8
9
|
// Java example
package com.jesperdj.example;
// Top-level class with package-level accessibility
class Example {
public void sayHello() {
System.out.println("Hello World");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
// Nested package (under com.jesperdj.example)
package com.jesperdj.example.nested;
// ERROR: class Example is not accessible
import com.jesperdj.example.Example;
public class FromNestedPackage {
public void method() {
// ERROR: class Example is not accessible
new Example().sayHello();
}
}
|
In Scala, a private top-level class is also accessible from nested packages. This makes sense, since the enclosing scope of both the private class and the nested package is the same (it’s the package inside which both are defined), and private means that the class is accessible from anywhere within its enclosing scope.
1
2
3
4
5
6
7
|
// Scala example
package com.jesperdj.example
// Private top-level class
private class Example {
def sayHello(): Unit = println("Hello World")
}
|
1
2
3
4
5
6
7
|
// Nested package (under com.jesperdj.example)
package com.jesperdj.example.nested
class FromNestedPackage {
// OK: class Example is accessible because this is in the same enclosing scope
def method(): Unit = new Example().sayHello()
}
|
This difference comes from the fact that the idea of nested packages do not really exist in Java, even though it seems that way. The Java compiler treats the packages com.jesperdj.example and com.jesperdj.example.nested as two unrelated packages. The fact that the second package looks like it is inside the first package doesn’t mean anything to the Java compiler.
The Scala compiler does have a notion of nested packages, which can also be seen by the fact that we didn’t have to import the classcom.jesperdj.example.Example like we would have to do in Java.
Another difference is that Scala does not allow you to let a private class escape from its enclosing scope. For example, Scala doesn’t allow you to write a public method that returns a private class. This makes sense, since the method can be called from anywhere because it’s public, but its return type is not accessible from everywhere.
1
2
3
4
5
6
7
8
|
// Scala example
package com.jesperdj.example
class ExampleFactory {
// Public method which returns an instance of a private class
// ERROR: private class Example escapes its defining scope
def createExample(): Example = new Example
}
|
The Java compiler doesn’t complain when you write a public method that returns a package-scope class. Instead of complaining about the method definition, it’s going to complain at the point where you try to use the package-scope class.
1
2
3
4
5
6
7
8
9
|
// Java example
package com.jesperdj.example;
public class ExampleFactory {
// OK: Public method which returns an instance of a package-scope class
public Example createExample() {
return new Example();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
// Different package
package com.jesperdj.other;
// ERROR: class Example is not accessible
import com.jesperdj.example.Example;
public class SomewhereElse {
public void method() {
// ERROR: class Example is not accessible
Example example = new ExampleFactory().createExample();
}
}
|
What the Scala compiler does is better; it reports the error closer to the actual source of the error. Java will let you write a library with a method that returns an inaccessible type, and you wouldn’t discover the error until somebody would try to use your library.
Even though Scala allows it, there is not much benefit to make a top-level class protected instead of private – they effectively mean the same thing.
To understand this, think about nested classes. When you make a nested class protected instead of private, it means its accessible not only in its enclosing class, but also in subclasses of its enclosing class. For top-level classes, the enclosing scope is a package instead of a class. Since there is no inheritance between packages (a package cannot extend another package, like a class can extend a superclass), protected doesn’t mean something else than private from the viewpoint of packages.
Scala’s protected and private access modifiers have an interesting feature that give you more fine-grained control. You can add a qualifier after the keywords protected and private, between square brackets. The qualifier can be one of two things: the name of an enclosing scope (package, class, trait or object), or the keyword this. These two kinds of qualifiers have different meanings.
With a qualifier that refers to an enclosing scope, you can widen the accessibility of a private or protected member to that scope, so that it’s not only accessible in the immediate enclosing scope, but in a scope that is one or more levels higher up. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package outside {
package inside {
object Messages {
// Accessible up to package 'inside'
private[inside] val Insiders = "Hello Friends"
// Accessible up to package 'outside'
private[outside] val Outsiders = "Hello People"
}
object InsideGreeter {
def sayHello(): Unit =
// Can access both messages
println(Messages.Insiders + " and " + Messages.Outsiders)
}
}
object OutsideGreeter {
def sayHello(): Unit =
// Can only access the 'Outsiders' message
println(inside.Messages.Outsiders)
}
}
|
Note that the name between the square brackets must be a simple name; it cannot be a fully-qualified name (with parts separated by dots).
Normally, in both Java and Scala, when a member of a class or trait is private or protected, this means that the access is restricted to the class or trait. Access is not restricted to individual instances; an instance of a class can access the same member in other instances of the same class.
In Scala it is possible to restrict access to the same instance only by qualifying the access modifier with [this], making the member object-private or object-protected. This means that an instance of a class or trait can only access the member in the current instance, and not in other instances. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Counter {
// Normal private member variable
private var total = 0
// Object-private member variable
private[this] var lastAdded = 0
def add(n: Int): Unit = {
total += n
lastAdded = n
}
def copyFrom(other: Counter): Unit = {
// OK, private member from other instance is accessible
total = other.total
// ERROR, object-private member from other instance is not accessible
lastAdded = other.lastAdded
}
}
|
The access modifiers in Scala are slightly different than in Java. In my opinion, they are better in Scala, for the following reasons:
In this post I’ve descibed Scala’s access control in detail. It’s possible to go even deeper, by looking at how the Scala compiler implements these features at the JVM-level. I suspect that the class file format that the JVM uses is really geared towards Java’s access control model, and I wonder how Scala manages to implements its own features, which are different and go beyond Java’s model, using what the class file format and the JVM offer. That will be a topic for another day.