Skip to content

Qualifiers when overriding Java methods #12349

@kynthus

Description

@kynthus

reproduction steps

using Scala 2.13.5, on Oracle Java SE Development Kit 8u202.

I tried overriding a method in a Java class.

// javapkg/JavaSuper.java

package javapkg;

public class JavaSuper {
    public void fn() { System.out.println("JavaSuper#fn()"); }
}
// pkg/ScalaSub.scala

package pkg

import javapkg.JavaSuper

object Cls {

  class ScalaSub extends JavaSuper {
    override def fn(): Unit = println("ScalaSub#fn()")
  }

}

I changed each other's access modifiers as shown in the table below and verified.
It summarizes whether it can be compiled and whether it is the expected result.

Rules for overriding from Java.
Modifier(JavaSuper) Modifier(ScalaSub) Compile Expected
public none(public) yes
public protected no
public private × yes
public protected[Cls] no
public private[Cls] no
public protected[pkg] no
public private[pkg] no
public protected[this] no
public private[this] no
Java's protected none(public) yes
Java's protected protected no
Java's protected private × yes
Java's protected protected[Cls] no
Java's protected private[Cls] × yes
Java's protected protected[pkg] no
Java's protected private[pkg] × yes
Java's protected protected[this] no
Java's protected private[this] no
package access none(public) yes
package access protected no
package access private × yes
package access protected[Cls] no
package access private[Cls] no
package access protected[pkg] yes
package access private[pkg] yes
package access protected[this] no
package access private[this] no
private none(public) × yes
private protected × yes
private private × yes
private protected[Cls] × yes
private private[Cls] × yes
private protected[pkg] × yes
private private[pkg] × yes
private protected[this] × yes
private private[this] × yes

problem

  1. Subclasses have stricter access privileges.

    Cases
    Modifier(JavaSuper) Modifier(ScalaSub)
    public protected
    public protected[Cls]
    public private[Cls]
    public protected[pkg]
    public private[pkg]
  2. It becomes inaccessible from directly under the package to which the class JavaSuper.

    Cases
    Modifier(JavaSuper) Modifier(ScalaSub)
    Java's protected protected
    Java's protected protected[Cls]
    package access protected
    package access protected[Cls]
    package access private[Cls]
  3. If pkg or its subpackages are unrelated to the javapkg, they will be inaccessible from javapkg.

    Cases
    Modifier(JavaSuper) Modifier(ScalaSub)
    Java's protected protected[pkg]
  4. Access modifiers on the subclass side do not work.

    Cases
    Modifier(JavaSuper) Modifier(ScalaSub)
    public protected[this]
    Java's protected protected[this]
    package access protected[this]

    ※Treated the same as the modifier on the JavaSuper side. (Very confusing!)

  5. Calling via ScalaSub throw java.lang.IllegalAccessError, calling via JavaSuper calls fn() on the JavaSuper side.

    Cases

    As reported in private[this] subverts override accessibility check #11913.

    Modifier(JavaSuper) Modifier(ScalaSub)
    public private[this]
    Java's protected private[this]
    package access private[this]

5. is not mentioned here because the issue already exists.
2. and 3. may be unavoidable due to Java package access specifications, so I'm not going to go into it.

I think problem is 1. and 4..
I didn't expect that public methods could be overridden with stricter modifiers or protected[this] wouldn't work.
Please let me know if these are specifications.

// javapkg/JavaSuper.java

package javapkg;

public class JavaSuper {
    public void fn1() { System.out.println("JavaSuper#fn1()"); }
    public void fn2() { System.out.println("JavaSuper#fn2()"); }
}
// pkg/ScalaSub.scala

package pkg

import javapkg.JavaSuper

object Cls {

  class ScalaSub extends JavaSuper {
    protected       override def fn1(): Unit = println("ScalaSub#fn1()") // 1. OK but strange, access only from subclasses via ScalaSub.
    protected[this] override def fn2(): Unit = println("ScalaSub#fn2()") // 4. This is even more strange, can be accessed anywhere via ScalaSub.
  }

}
import javapkg.JavaSuper
import pkg.Cls.ScalaSub

val j: JavaSuper = new ScalaSub()
val s: ScalaSub  = new ScalaSub()

scala> j.fn1()
ScalaSub#fn1()

scala> j.fn2()
ScalaSub#fn2()

scala> s.fn1() // It's unfamiliar to be inaccessible only from subclasses.
         ^
       error: method fn1 in class ScalaSub cannot be accessed as a member of pkg.Cls.ScalaSub from class $iw
        Access to protected method fn1 not permitted because
        enclosing class $iw is not a subclass of
        class ScalaSub in object Cls where target is defined

scala> s.fn2() // `protected[this]` seems to have been ignored.
ScalaSub#fn2()

Supplement

1. is only occurs if you override a method defined in a Java classes in Scala 2.8.2 or later.

4. is even before Scala 2.8.1, it seems that protected can be overridden with protected[this], allowing access via another instance.
I think the problem became more noticeable as we were able to override public etc, in 2.8.2.

By the way, in Scala 3.0.0-RC1, an error occurred when the JavaSuper side was public and package access, and the result was as expected.
(Java's protected is still suspicious even in Scala 3... But that should be mentioned in Dotty.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions