Accessing private member via subclass

M

Mike Schilling

I thought I understood Java's access control rules pretty well, but
this case puzzles me.

public abstract class Super
{
private int i;

void method(Sub s)
{
s.i = 2; // (*)
}
}

public class Sub extends Super
{
}
Consider the starred line. The field "i" is private to Super and is
being accessed by Super, which seems to me to fit within JLS 6.6.1:

if the member or constructor is declared private, then access
is permitted if and only if it occurs within the body of the top
level class (§7.6) that encloses the declaration of the member
or constructor.

However, trying to compile these classes leads to:

Super.java:7: i has private access in Super
s.i = 2;
^
1 error

in both 1.4.2_09 and 1.6.0_06.

Obviously, the error can be removed by changing the line to

((Super)s).i = 2;

And, just as obviously, the error doesn't actually prevent
encapsulation from being broken. For what it's worth, similar (in
fact, almost identical) C# code compiles with no problems.

Any thoughts about this?
 
L

Lothar Kimmeringer

Mike said:
public abstract class Super
{
private int i;

void method(Sub s)
{
s.i = 2; // (*)
}
}

public class Sub extends Super
{
} [...]
However, trying to compile these classes leads to:

Super.java:7: i has private access in Super
s.i = 2;
^
1 error

in both 1.4.2_09 and 1.6.0_06.

Obviously, the error can be removed by changing the line to

((Super)s).i = 2;

And, just as obviously, the error doesn't actually prevent
encapsulation from being broken. For what it's worth, similar (in
fact, almost identical) C# code compiles with no problems.

Any thoughts about this?

Peronally I think the decision to break with a compile-error
is good, because otherwise you migth run into problems if you
decide to declare a member "i" in Sub at a later point of time.


Regards, Lothar
--
Lothar Kimmeringer E-Mail: (e-mail address removed)
PGP-encrypted mails preferred (Key-ID: 0x8BC3CD81)

Always remember: The answer is forty-two, there can only be wrong
questions!
 
A

Alessio Stalla

Mike said:
public abstract class Super
{
    private int i;
    void method(Sub s)
    {
        s.i = 2; // (*)
    }
}
public class Sub extends Super
{
} [...]
However, trying to compile these classes leads to:
Super.java:7: i has private access in Super
        s.i = 2;
         ^
1 error
in both 1.4.2_09 and 1.6.0_06.
Obviously, the error can be removed by changing the line to
    ((Super)s).i = 2;
And, just as obviously, the error doesn't actually prevent
encapsulation from being broken.  For what it's worth, similar (in
fact, almost identical) C# code compiles with no problems.
Any thoughts about this?

Peronally I think the decision to break with a compile-error
is good, because otherwise you migth run into problems if you
decide to declare a member "i" in Sub at a later point of time.

You can run into such problems independently on the access level of
the field: it being public or protected would not change that.

I, too, would have expected that piece of code to compile fine.

Alessio
 
A

Arved Sandstrom

Mike said:
I thought I understood Java's access control rules pretty well, but
this case puzzles me.

public abstract class Super
{
private int i;

void method(Sub s)
{
s.i = 2; // (*)
}
}

public class Sub extends Super
{
}
Consider the starred line. The field "i" is private to Super and is
being accessed by Super, which seems to me to fit within JLS 6.6.1:

if the member or constructor is declared private, then access
is permitted if and only if it occurs within the body of the top
level class (§7.6) that encloses the declaration of the member
or constructor.

However, trying to compile these classes leads to:

Super.java:7: i has private access in Super
s.i = 2;
^
1 error

in both 1.4.2_09 and 1.6.0_06.

Obviously, the error can be removed by changing the line to

((Super)s).i = 2;

And, just as obviously, the error doesn't actually prevent
encapsulation from being broken. For what it's worth, similar (in
fact, almost identical) C# code compiles with no problems.

Any thoughts about this?

I'll assume that this is a "let's explore the edge cases in the
language" test case. :) Having said that, I don't read that section of
the JLS as pertaining to what you are doing here; you might as well have
a method in Super that does a similar thing to an instance of a class
that is not related to Super at all, and you certainly wouldn't expect
that to work either. IOW, using Sub in that method is muddying the
waters. The key point is that you're trying to access a private member
variable and you're not actually in the class that owns it because of
the "s.i" syntax.

I'm a bit surprised that C# would permit this. How identical is "almost
identical"? :)

AHS
 
L

Lew

Yes, it does. Obviously.

Arved said:
I'll assume that this is a "let's explore the edge cases in the
language" test case. :) Having said that, I don't read that section of
the JLS as pertaining to what you are doing here; you might as well have
a method in Super that does a similar thing to an instance of a class
that is not related to Super at all, and you certainly wouldn't expect
that to work either. IOW, using Sub in that method is muddying the
waters. The key point is that you're trying to access a private member
variable and you're not actually in the class that owns it because of
the "s.i" syntax.

I'm a bit surprised that C# would permit this. How identical is "almost
identical"? :)

To clarify, the access rules only permit an instance of the class itself or of
an inner class to access the private members. Otherwise they forbid access to
'private' members even to subclasses. Since 'Sub' is not an inner class, it
does not have access to the private members of 'Super'. So 's.i' is illegal.
 
A

Arved Sandstrom

[ SNIP ]
I think that is just the compiler trying to be helpful and concise at
the same time. The message perhaps should be "Super.java:7: i is not a
member of Sub because it has private access in Super".

Patricia

I agree - the message isn't wrong, it's just a tad too abbreviated. In
any case there is no mystery to this situation, and no need to resort to
the JLS - how can you refer to a member variable of Sub that it doesn't
have?

As to C# permitting this I'll have to try an experiment when I get on to
my Windows box.

AHS
 
M

Mike Schilling

Arved Sandstrom said:
I'll assume that this is a "let's explore the edge cases in the
language" test case. :)

Agreed that it looks like a weird thing to do, but it came up in real
life.
Having said that, I don't read that section of the JLS as pertaining
to what you are doing here; you might as well have a method in Super
that does a similar thing to an instance of a class that is not
related to Super at all, and you certainly wouldn't expect that to
work either.

Of course not. But that would be an entirely different situation.
IOW, using Sub in that method is muddying the waters. The key point
is that you're trying to access a private member variable and you're
not actually in the class that owns it because of the "s.i" syntax.

I'm a bit surprised that C# would permit this. How identical is
"almost identical"? :)

I changed "extends" to ":".
 
M

Mike Schilling

Patricia said:
The rule that makes it illegal is the i is not even a member of Sub,
and membership in Sub is needed to make the s.i notation valid.

"Members are either declared in the type, or inherited because they
are accessible members of a superclass or superinterface which are
neither private nor hidden nor overridden (§8.4.8)."

http://java.sun.com/docs/books/jls/third_edition/html/names.html#6.4.3

That explains it. The analogous statement in C# is:

10.2.1: A class inherits the members of its direct base class.
Inheritance means that a class implicitly contains all members of
its
direct base class, except for the instance constructors,
destructors
and static constructors of the base class.

which explain the difference in behavior.
 
M

markspace

Mike said:
public abstract class Super
{
private int i;

void method(Sub s) // <-- Oops
Any thoughts about this?


I was pretty mystified by your example until I did a second glance at
the line labeled "Oops". Well, obviously if the type of "s" is NOT the
type that holds the private field "i", you can't access "i" through that
type. I think that should be completely intuitive.

Someone else mentioned that Sub could have a public field "i" added at
some point, in which case you'd (likely) have an erroneously behaving
Super. My thought on realizing this was that C# is defective in this
regard and Java is correct.
 
M

Mike Schilling

Arved said:
I agree - the message isn't wrong, it's just a tad too abbreviated.
In
any case there is no mystery to this situation, and no need to
resort
to the JLS - how can you refer to a member variable of Sub that it
doesn't have?

It took the JLS to explain that private members aren't inherited, as
opposed to being inherited but inaccessible, which seems to be the
case in C# (in C++ too, IIRC.)
 
A

Arved Sandstrom

Mike said:
That explains it. The analogous statement in C# is:

10.2.1: A class inherits the members of its direct base class.
Inheritance means that a class implicitly contains all members of
its
direct base class, except for the instance constructors,
destructors
and static constructors of the base class.

which explain the difference in behavior.

I don't want to go off on a C# tangent here, but I'm not satisfied. The
C# 3.5 docs (in various places) say things like:

A derived class has access to the public, protected, internal, and
protected internal members of a base class. Even though a derived class
inherits the private members of a base class, it cannot access those
members. However, all those private members are still present in the
derived class and can do the same work they would do in the base class
itself. For example, suppose that a protected base class method accesses
a private field. That field has to be present in the derived class in
order for the inherited base class method to work properly.

and

Private members are accessible only within the body of the class or the
struct in which they are declared.

and

Nested types in the same body can also access those private members.

and

It is a compile-time error to reference a private member outside the
class or the struct in which it is declared.

Your quoted C# language spec snippet does not in fact gainsay any of
these. I do not see how the nearest C# equivalent of what we have here
in Java would compile.

AHS
 
M

Mike Schilling

Arved said:
I don't want to go off on a C# tangent here, but I'm not satisfied.
The C# 3.5 docs (in various places) say things like:

A derived class has access to the public, protected, internal, and
protected internal members of a base class. Even though a derived
class inherits the private members of a base class,

Unlike in Java, where it doesn't inherit them.
it cannot access
those members. However, all those private members are still present
in the derived class and can do the same work they would do in the
base class itself. For example, suppose that a protected base class
method accesses a private field. That field has to be present in the
derived class in order for the inherited base class method to work
properly.
and

Private members are accessible only within the body of the class or
the struct in which they are declared.

In other words, in "super".
and

Nested types in the same body can also access those private members.

and

It is a compile-time error to reference a private member outside the
class or the struct in which it is declared.

In other words, in "super".
Your quoted C# language spec snippet does not in fact gainsay any of
these.

It seems to me that they all agree that the code should compile
without error.
I do not see how the nearest C# equivalent of what we have here
in Java would compile.

Here they are: try it for yourself.

public class Sub : Super
{
}
public abstract class Super
{
private int i;

internal void method(Sub s)
{
s.i = 2;
}
}
 
A

Arved Sandstrom

Mike said:
Unlike in Java, where it doesn't inherit them.


In other words, in "super".


In other words, in "super".


It seems to me that they all agree that the code should compile
without error.


Here they are: try it for yourself.

public class Sub : Super
{
}
public abstract class Super
{
private int i;

internal void method(Sub s)
{
s.i = 2;
}
}

I didn't doubt you that all this compiles, but I went ahead and played
with this in C# anyhow.

However, despite the fact that this does compile, I'm not so sure that
it should. The main phrase from MS that I keep on coming back to is
"Even though a derived class inherits the private members of a base
class, it cannot access those members." The only way I can reconcile
that statement with the actual compiler behaviour is to assume that MS
considers the above scenario to be the superclass doing the
access...which to me is a bit of a smelly situation.

I'm not going to be exploiting this in my C# coding. I will definitely
strive to avoid it though.

AHS
 
M

Mike Schilling

Arved said:
However, despite the fact that this does compile, I'm not so sure
that
it should. The main phrase from MS that I keep on coming back to is
"Even though a derived class inherits the private members of a base
class, it cannot access those members." The only way I can reconcile
that statement with the actual compiler behaviour is to assume that
MS
considers the above scenario to be the superclass doing the
access...which to me is a bit of a smelly situation.

I'm not going to be exploiting this in my C# coding. I will
definitely
strive to avoid it though.

I doubt it'll come up. I only ran across it because I was doing
something odd.
 
A

Arne Vajhøj

Mike said:
I thought I understood Java's access control rules pretty well, but
this case puzzles me.

public abstract class Super
{
private int i;

void method(Sub s)
{
s.i = 2; // (*)
}
}

public class Sub extends Super
{
}
Any thoughts about this?

Given that Super class should not refer to Sub class,
then this seems to be an intellectual exercise.

Arne
 
L

Lew

Mike said:
It took the JLS to explain that private members aren't inherited, as
opposed to being inherited but inaccessible, which seems to be the
case in C# (in C++ too, IIRC.)

One really fun corner case is an inner class that inherits from its enclosing
class. It does not inherit private members of the enclosing class, but it can
access them.
 
A

Alessio Stalla

I didn't doubt you that all this compiles, but I went ahead and played
with this in C# anyhow.

However, despite the fact that this does compile, I'm not so sure that
it should. The main phrase from MS that I keep on coming back to is
"Even though a derived class inherits the private members of a base
class, it cannot access those members." The only way I can reconcile
that statement with the actual compiler behaviour is to assume that MS
considers the above scenario to be the superclass doing the
access...which to me is a bit of a smelly situation.

Why is it smelly? To me it seems the most natural interpretation, and
I'm surprised about Java's behaviour (mind that I routinely program in
Java and have done very little C#).
Consider a generic expression object.field where object is of type T.
Suppose the expression is textually contained in a method declared in
class S. If you argue that the access to the field is done by
'object', rather than by S, then the visibility of object's type T
would apply, and you would have access to all of T's members, since T
can always "see" them. In other words, there would be no encapsulation
whatsoever for members declared in T.
But things don't work like that in Java; normally it is S that
controls the accessibility of members. So Java's behavior to me
suggests that the Java compiler, when evaluating access rules, acts as
if fields were actually inherited - or, better, copied - in
subclasses, which clearly is not the case.

Alessio
 
A

Arved Sandstrom

[ SNIP ]
Why is it smelly? To me it seems the most natural interpretation, and
I'm surprised about Java's behaviour (mind that I routinely program in
Java and have done very little C#).
Consider a generic expression object.field where object is of type T.
Suppose the expression is textually contained in a method declared in
class S. If you argue that the access to the field is done by
'object', rather than by S, then the visibility of object's type T
would apply, and you would have access to all of T's members, since T
can always "see" them. In other words, there would be no encapsulation
whatsoever for members declared in T.

It might be more accurate to say that I consider the entire example to
be smelly (no reflection on Mike's experimenting here)...there's nothing
good about having a method in a superclass that calls out a subclass.
But it's legal code (in C#, but not in Java) so it's worthy of academic
discussion.

To use your terminology, I expect "i" to be accessible only in S. In
both languages. That is to say, in the body of class T we cannot access
S's private field "i". So far so good. However, and again in both
languages, I don't expect an instance of T to behave like its base class
S just because a base class private member is accessed in the lexical
scope of S. This is not intuitive to me at all. I would expect one to
have to cast the instance to the base class, just like Mike demonstrated
in the case of Java.

I'm certainly arguing that object.field access is done by object type.
That's how it works. Given the "object.field" expression, and object
being of class T, my ability to see "field" outside the body of T
depends on its existence and then its access modifier. In Java's case T
doesn't even have a field "i". In C#'s case it does, but in this
particular situation I don't expect an automatic cast to the base class
S just because we are in the body of S...however, that's what we seem to
get in the case of C#.
But things don't work like that in Java; normally it is S that
controls the accessibility of members. So Java's behavior to me
suggests that the Java compiler, when evaluating access rules, acts as
if fields were actually inherited - or, better, copied - in
subclasses, which clearly is not the case.

Alessio

Well, in Java, public and protected (and in some cases package-private)
members of the base class *are* inherited by the subclass. This is
unambiguous in all documentation. The difference between C# and Java in
this case lies in private members - a Java subclass does not inherit
them at all; a C# subclass does, it just cannot access them.

So in Java the situation (to me) is quite clear - an instance of class
Sub (in this example) does not have a field "i". Period. That's what the
javac message is telling us.

In C# an instance of class Sub does have a field "i". However, the C#
docs tell us that a derived class cannot access private members of the
base class. The example given (a base class protected method accessing a
base class private field, and we obviously want to be have the protected
method work when accessed through the derived class) is a motivation (in
C#) for having those private members present, if not directly
accessible. In Java we simply have it, for the same example, that the
subclass can use such a protected method and things will work.

AHS
 
A

Arved Sandstrom

Michal said:
But it does not have anything to do with 'i' being accessible or not. The
issue would remain if you change 'i' to public in the original example (and
it will compile fine).


IMHO it is better done in C# - more regular.

I like a lot of things about C# - I prefer that language to Java - but I
sure don't like *this*. I don't find it "regular" at all.

The C# documentation makes it clear that private member fields are not
accessible to derived classes. This - to me - does not conflict with the
statement that private fields _are_ accessible in the owning class,
because in the example we are _not_ referring to an unadorned "i", we're
referring to "s.i", where "s" is an instance of a derived class. That
"s.i" notation, again to me, is the nub of the whole matter - we are
accessing through the derived class, and it's not supposed to have
access to that field. Regardless of the fact that that access is
happening lexically inside the body of the base class.
In Java things get strange sometimes due to such irregularities. Look at the
following:

public class Super {

private int i;

<T extends Super> void m(T s) {
s.i = 5;
}

}

This compiles fine in Java - should it?

I believe it should. When using "extends", that reference "s" must
behave as an instance of Super; we can't use or guess at any extended
behaviour of a specific T. For example, if you had a List<? extends
Number>, reading from (accessing) that List gives you a Number.

In this case, being in the body of S, getting or setting a private
member field of S is OK.

AHS
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top