论坛首页 Java企业应用论坛

protected,你真的理解了吗?

浏览 13851 次
精华帖 (4) :: 良好帖 (2) :: 新手帖 (7) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-05-09   最后修改:2010-06-27

Java中的访问控制修饰符有四个级别,但属protected最复杂。如果问大家,protected修饰符作用,大家会说“在子类与同包中可以访问这个成员”,当然在自己类中也是可的,但你真的理解了吗?不信,先看看下面这些实例,看你是否也是这样想的(其中注释掉的是不能编译的,大家最好把这些代码放在eclipse中再看,不然你会晕的^_^):

package pk1.a;
public class Base {
	protected int i = 1;
	protected void protect() {
		System.out.println("Base::protect");
	}
}

package pk1.a;
import pk1.b.Sub;
public class SubSub extends Sub {
	void g() {
		Sub s = new SubSub();
		//!! s.protect();//规则2.c.i
		System.out.println(s.i);//规则2.c.ii
	}
}

package pk1.b;
import pk1.a.Base;
public class Sub extends Base {
	private void prt() {}
	protected void protect() {
		System.out.println("Base::protect");
	}
	void f() {
		//规则2.a
		this.protect();
		this.i = 2;

		//规则2.b
		Base a2 = new Sub();
		//!! a2.protect();
		//!! System.out.println(a2.i);

		//规则1
		Sub b = new Sub();
		b.protect();
		b.i = 1;
		b.prt();
	}
}

package pk1.b;
public class SubSub extends Sub {
	void g() {
		Sub s = new SubSub();
		s.protect();//规则2.c.i
		//!! System.out.println(s.i);//规则2.c.ii
	}
}

package pk1.c;
import pk1.a.Base;
import pk1.b.Sub;
public class SubSub extends Sub {
	void g() {
		this.protect();//规则2.a

		//规则2.b
		Base b = new SubSub();
		//!! b.protect();
		//!! System.out.println(b.i);

		//规则2.b
		Sub s = new SubSub();
		//!! s.protect();
		//!! System.out.println(s.i);

	}
}

 

package pk2.a;
public class Base {
	protected int i = 1;

	protected void protect() {
		System.out.println("Base::protect");
	}
}

package pk2.a;
import pk2.b.Sub;
public class Other {
	void g() {
		//规则3.a
		Base b = new Sub();
		b.protect();
		System.out.println(b.i);

		//规则3.b.ii
		Sub s = new Sub();
		s.protect();
		System.out.println(s.i);
	}
}

package pk2.b;
import pk2.a.Base;
public class Other {
	void g() {
		//规则3.a
		Base b = new Sub();
		//!! b.protect();
		//!! System.out.println(b.i);

		//规则3.b.ii
		Sub s = new Sub();
		//!! s.protect();
		//!! System.out.println(s.i);
	}
}

package pk2.b;
import pk2.a.Base;
public class Sub extends Base {}

 

package pk3.a;
import pk3.b.Sub;
public class Base {
	protected int i = 1;
	protected void protect() {
		System.out.println("Base::protect");
	}
	
	static protected int i_s = 1;
	static protected void protect_s() {
		System.out.println("Static:Base::protect");
	}
	
	void f() {
		//!! Sub.i_s = 2; //规则3.b.i
		Sub.protect_s();//规则3.b.ii
	}
}

package pk3.a;
import pk3.b.Sub;
public class Other {
	void g() {
		Sub s = new Sub();
		//!! s.protect();//规则3.b.i
		System.out.println(s.i);//规则3.b.ii
	}

	void f() {

		//!! Sub.i_s = 2; //规则3.b.i
		Sub.protect_s();//规则3.b.ii

		Base.i_s = 2;//规则3.a
		Base.protect_s();//规则3.a

	}
}

package pk3.b;
import pk3.a.Base;
public class Other {
	void f() {
		Sub.i_s = 2;//规则3.b.i
		//!! Sub.protect1();//规则3.b.ii
		
		//!! Base.i1 = 2;//规则3.a
		//!! Base.protect1();//规则3.a
	}
}

package pk3.b;
import pk3.a.Base;
public class Sub extends Base {
	protected void protect() {
		System.out.println("Base::protect");
	}
	static protected int i_s = 2;

	void f() {
		
		/*
		 * 在子类中可能通过子类类型或父类类型来来访问父类中protected静态
		 * 成员,而不管子类与父类是否在同一包中,或是子类重新定义了这些成员
		 * 
		 * 注,在父类或子类中访问时后面的规则不再适用
		 */
		System.out.println(Sub.i_s);//2
		Sub.protect_s();
	
		System.out.println(Base.i_s);//1
		Base.protect_s();
	}
}

 

如果你看到这里,想法与程序一致的话,说明你理解了,如果不理解,那看看我的理解吧:

 

定义规则前,我这里约定有三个类,一个是Base类,一个是Base类的子类Sub类,一个是Sub类的子类SubSub类,另一个是Other类且与BaseSubSubSub没有继承关系,并假设Base中有protected方法与属性,都叫YYY吧。

 

在理解protected规则:首先要搞清楚什么叫访问?这里在讲到的访问是有二种的:

一、就是在类中通过“XXX x = new XXX(); x.YYY;”的形式来访问(不妨叫此种形式为“外部访问”吧,此种访问形式除了可以应用到自己与子类中外,还可以应用在其他类中访问,其中XXX表示定义的类型,这里可为BaseSubSubSubYYY为方法或属性);

二、就是this.YYY的形式来访问(不妨叫此种形式为“内部访问”吧,不过这种访问形式只能应用在在自己的类或是子类中)。

 

protected方法与属性可访问的地方有三个:

1.         在自己的类Base中:上面的“XXX x = new XXX(); x.YYY;”与“this.YYY”两种访问形式都可以访问的到自己定义的portected方法或属性;

2.         二是子类SubSubSub中,这要分三种访问方式:

        a.         SubSubSub 中的“this.YYY”内部访问形式:在此种方式形式下,不管是否重写或重新定义过父类Baseprotected方法与属性,子类SubSubSub一定可以访问的。

        b.         SubSubSub 中“Base x = new XXX (); x.YYY;”外部访问形式:此种形式就不一定的能访问的到了,这要看父类Base与子类SubSubSub是否在同一包(注意,此时与是否重写或重新定义过这些protedted方法与属性没有关系);

        c.         SubSub 中“Sub x = new XXX (); x.YYY; 外部访问形式:此种访问形式能否访问关键看Sub是否重写或重新定义过Base的属性与方法:

                      i.              如果重写或重新定义过,则看SubSubSub是否在同包中

                      ii.             如果没有,则看BaseSubSub是否在同包中

3.         在其他类Other中:此时只支持外部访问形式,不过到底是要求OtherBase同包还是要求OtherSub同包,则要依你访问方式而定了:

        a.         如果是通过父类引用“Base x = new XXX (); x.YYY;”形式来访问的,则要求OtherBase同包;

        b.         如果是通过子类引用“Sub x = new Sub (); x.YYY;”形式来访问的,情况又会比较复杂了,此时关键是看子类Sub是否重写或重新定义过父类Base中的protected方法与属性:

                      i.              如果重写或重新定义过了,则要求OtherSub同包即可;

                      ii.             如果没有重写或重新定义过了,则要求OtherBase同包即可;

 

 

另外,写到这里我想到了Object中的clone方法,为什么要求具有克隆能力的类要求实现Cloneable接口与clone方法呢:Object.clone()访问修饰符为protected,如果某个类没有重写此方法,则Object中的clone()方法除被自己与子类能调用方法外,其他不管与这个类在同一包还是不同包都是不可见的,因为未重写,还是属于Object中的方法,又Objectjava.lang包中,与我们定义的包又不在java.lang包中,所以不能访问到(这也与你在在程序里定义了Object o = new Object();你还是不能在当前类中调用o.clone();一样),这也恰好符合上面 3.b.ii 这条规则。所以如果要能被不同包中的非子类克隆,则需重写Object.clone()并设置访问权限为public(如果重写后还是protected,则还是只能被同一包访问)。

 

以上这些就是我对protected限制的一种理解,肯定还有遗漏的地方,希望大家给我指出来,我来完善它。如果对你有帮助,请支持一下!

 

刚刚发现的一个小问题,一并附上《protected,这个错了吗?》

 

非常感谢大家,看了一下大家的理解,我上面的理解确实比较复杂,这正是因为把继承访问与非继承访问混在一起了就复杂了。其实这么理解就最简单了,还是以那句话,protected修饰的成员只允许子类与包访问!只不过子类可访问是指继承访问,即在子类的实例中直接访问继承过来的protected成员,而不是在子类中通过创建父类实例的方式来访问;而包访问就是指通过“XXX x = new XXX(); x.YYY;”的方式来访问,即通过实例的引用来访问,能不能访问就是看访问的代码所在的类是否与类XXX 在同一包中。但包访问要注意一点的是:如果XXX中只是继承了父类中的protected成员,而没有重写(方法)或重新定义(属性)这些成员,则能不能访问就要看当前访问的代码所在的类是否与类XXX的父类在同一包;另外如果类XXX重写与重新定义过这些protected成员,则就直接看访问代码所在的类与类XXX是否在同一包即可。

最后要注意的是静态的受保护成员比较特殊,因为protected static 成员即使被子类重写(严格的讲不叫重写)或重新定义后,还是会被继承下来,即在子类会有两份这样的static protected成员,只是他们的所属域(类)不同而已,所以在子类中可以通过父类的类型来访问所属于父的这些成员而不管子类与否与父类在同一包中,这与非静态的是不一样的。

 

 补充:方法能重写,属性能重写吗

   发表时间:2010-05-09  
想不到protected这么复杂,楼主真是细心
0 请登录后投票
   发表时间:2010-05-10  
lz说的不错 至于 "Object.clone()访问修饰符为protected," 我想人家可能就是想叫你把内个方法重写了吧
0 请登录后投票
   发表时间:2010-05-10  
折腾,继续折腾
0 请登录后投票
   发表时间:2010-05-10  
看完代码。。我发现我理解了。。
看完文字。。我发现我晕了,,,麻烦下次举例的时候来个正常字。。。
XXX YYY 然后我 zzz~ 了
0 请登录后投票
   发表时间:2010-05-10  
protected本没有这么复杂,楼主把这个搞复杂了
有一个简单的办法——你把所有new换成一个工厂方法,这样:
      XXX x=Factory.createXXX();
就可以很直观很明显地看出了。
ps:示例代码有错,请lz详查……
0 请登录后投票
   发表时间:2010-05-10  
yssas 写道
protected本没有这么复杂,楼主把这个搞复杂了
有一个简单的办法——你把所有new换成一个工厂方法,这样:
      XXX x=Factory.createXXX();
就可以很直观很明显地看出了。
ps:示例代码有错,请lz详查……


哪儿有错?请指示。
0 请登录后投票
   发表时间:2010-05-10  
eroscxl 写道
看完代码。。我发现我理解了。。
看完文字。。我发现我晕了,,,麻烦下次举例的时候来个正常字。。。
XXX YYY 然后我 zzz~ 了

嗯,提的好,不过知道如何修改,还请指示
0 请登录后投票
   发表时间:2010-05-11  
junJZ_2008 写道
yssas 写道
protected本没有这么复杂,楼主把这个搞复杂了
有一个简单的办法——你把所有new换成一个工厂方法,这样:
      XXX x=Factory.createXXX();
就可以很直观很明显地看出了。
ps:示例代码有错,请lz详查……


哪儿有错?请指示。


是我的结论轻率了,细看之后,发现至少原来认为有错的第一段代码是没有错误的。不过我想总结一个关于字段访问控制的假象算法:

 

字段没有动态绑定且基于隐藏机制。访问字段时,自目标对象对象所声明的类型开始,沿继承体系向上,直到相应字段的定义位置,然后检查访问者权限

 

对于方法的访问,直接视其声明类型就行了。根据Liskov替换原则,子类不可能降低父类的访问设置,这也就是我提出将new换成工厂方法的原因。

 

0 请登录后投票
   发表时间:2010-05-11  
这个关键字 我觉得都可以去掉...
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics