分享
 
 
 

引用,造型,protected和private的困惑

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

或许大家java的多态问题,对上溯,下溯造型有了一定的概念,对protected和private大家想必也很清楚,但是,这几个个结合在一起,往往令人产生很多困惑,在这里,我举一个例子,大家或许会发觉这篇文章对你来说还是很有意义的:

例子一共有两个class. 可能出现困惑的地方我都会在后面一一解释.

A是一个父类,B继承A,并且实现了protectedTest(Object obj)方法.如下面所示:

B.java的源代码:

package cn.org.matrix.test;

import cn.org.matrix.test.A;

/**

* <p>Title: protect, private and upcasting </p>

* <p>Description: email:chris@matrix.org.cn</p>

* <p>Copyright: Matrix Copyright (c) 2003</p>

* <p>Company: Matrix.org.cn</p>

* @author chris

* @version 1.0,who use this example pls remain the declare

*/

public class B extends A

{

protected int protectedb = 0;

protected int protectedab = 0;

protected void protectedTest(Object obj)

{

System.out.println("in B.protectedTest(Object):" + obj);

}

}

A.java的源代码:

package cn.org.matrix.test;

import cn.org.matrix.test.B;

/**

* <p>Title: protect, private and upcasting </p>

* <p>Description: email:chris@matrix.org.cn</p>

* <p>Copyright: Matrix Copyright (c) 2003</p>

* <p>Company: Matrix.org.cn</p>

* @author chris

* @version 1.0,who use this example pls remain the declare

*/

public class A

{

protected int protecteda = 0;

protected int protectedab = 0;

private void privateTest()

{

System.out.println("in A.privateTest()");

}

protected void protectedTest(Object obj)

{

System.out.println("in A.protectedTest(Object):" + obj );

}

protected void protectedTest( String str )

{

System.out.println("in A.protectedTest(String):" + str);

}

public static void main (String[] args)

{

// Test A

A a1 = new A();

a1.privateTest();

// Test B

String helloStr = "Hello";

Object helloObj = helloStr;

B b1 = new B();

A a2 = b1; // 这里发生了什么?困惑1

b1=a1; //编译错误,困惑2

b1. privateTest(); //编译错误,困惑3

b1.protectedTest(helloObj); //输出结果?困惑4

b1.protectedTest(helloStr); //编译错误,困惑5

a2.protectedTest(helloObj); //输出结果? 困惑6

a2.protectedTest(helloStr); //输出结果?困惑7 ?

}

}

下面,我来逐个解释每一处困惑的地方:

困惑1:

这里其实就是子类自动上溯造型到父类A。这里a2其实是指向了一个B类型的对象. 我们通常都可以这样作: A a2=b1, 这样作的意思实际上就是让a2指向了一个类型B的对象—在这里就是b1了.

在java里面,关于跨类引用,有两条规则应该记住:

1. 如果a是类A的一个引用,那么,a可以指向类A的一个实例,或者说指向类A的一个子类.

2. 如果a是接口A的一个引用,那么,a必须指向实现了接口A的一个类的实例.

所以,根据这两个规则,我们就不难理解例子中的A a2 = b1是什么意思了.

困惑2:

A a2 = b1是可以的,但是为什么b1=a1却是不行? 在这里,我们依然可以套用上面的两条规则,我们可以看到,b1是类B的一个引用,a1既不是类B的实例,也不是类B的子类的实例,所以直接b1=a1就出现了编译错误.

如果确实需要进行这样的转化,我们可以这样作:b1=(B)a1; 进行强制转化,也就是下溯造型. 在java里面,上溯造型是自动进行的,但是下溯造型却不是,需要我们自己定义强制进行.

困惑3:

b1. privateTest();编译不通过? 这是很显然的,你可以回顾一下private的定义: 私有域和方法只能被定义该域或方法的类访问. 所以,在这里,b1不能访问A的方法privateTest(),即使b1是A的子类的实例.

请看下面的例子:

public class A

{

private int two(int i) { return i; }

}

class Test extends A {

public static void main(String[] args) {

System.out.println(A.two(3));

}

}

System.out.println(A.two(3));这行编译出错,显然,因为private方法不能在这个类之外被访问。

而protected则不同,我们回顾一下protected的定义: 被保护的域或方法只能被类本身、类的子类和同一 程序包中的类所访问。

下面是一个错误使用protected的例子:

package cn.org.matrix.test;

public class ProtectedTest {

protected void show() {

System.out.println("I am in protected method");

}

}

import cn.org.matrix.test.*;

public class Test {

public static void main (String[] args) {

ProtectedTest obj = new ProtectedTest();

obj.show();

}

}

因为访问权限问题,你会得到”show() has protected access in test.ProtectedTest”的出错信息.

困惑4:

b1.protectedTest(helloObj); 输出的是”in B.protectedTest(Object):…” 这到底是为什么呢? 为什么jvm能够确定是输出B的方法而不是A的方法? 这就和jvm的运行机制有关系了. 我们上面提到了,a1是一个A类型的引用,但是指向了一个B类型的实例. 在这里,如果jvm根据引用的类型-在这里就是A 来定义调用哪个方法的话,那么应该是调用A的protectedTest(helloObj).

然后实际上不是这样的,因为jvm的动态编译能力,jvm会在run-time来决定调用哪一个method,而不是在compile time. 也就是所谓的late-binding(run-time)和early-binding(compile-time).

困惑5:

b1.protectedTest(helloStr); 这里为什么会出现编译错误? 他可以调用类B的protectedTest(Object obj)方法啊,把helloStr上溯造型成一个object就行了啊..或者上溯造型到A然后调用A的protectedTest(helloStr)方法啊.

呵呵,问题的根源就在于此了,既然有两种选择,jvm应该选择那一种?这种不确定性如果交给jvm来动态决定的话,势必带来程序的不确定性..虽然java在其他的一些地方也有类似的情形出现,比如static变量的循环定义造成的不确定性,但是,在这里,jvm还是在编译阶段就解决了这个问题.

所以,我们会在这一步遇到编译错误: “reference to protectedTest is ambiguous; both method protectedTest(java.lang.String) in mytest.A and method protectedTest(java.lang.Object) in mytest.B match at line 46.

在这里,我们遇到的是显式的reference ambiguous错误,但是,有时候,隐式的reference ambiguous却往往是更加的危险.

在这里,我举个例子:

父类的 源代码:

public super

{

private void test(int i, long j);

{

System.out.println(i+”and”+j);

}

}

子类的源代码:

public sub

{

private void test(long j, int i);

{

System.out.println(i+”and”+j);

}

}

子类和父类都用有相同名称的方法test,参数类型不同而已.这种情况下,编译可以被通过.

但是如果你在另外一个类中用到了如下代码:

Sub sb = new Sub();

sb.test(100, 3000);

你就会遇到编译错误,因为没有确定的指出3000的类型,所以造成reference ambiguous的错误了.

困惑6:

a2.protectedTest(helloObj);

输出结果分别是:”in B.protectedTest(Object)..” 经过上面的解释,想必大家都能很清楚的知道为什么会有这两个输出结果了:a2.protectedTest(helloObj);因为jvm的late-binding,所以在run-time的时候,调用了B类的方法,虽然在编译期间a2只是一个父类A的引用类型。

困惑7:

a2.protectedTest(helloStr); 为什么这里会输出” in A.protectedTest(Object)…”。为什么这里不会编译出错?为什么b1. protectedTest(helloStr)会出错而a2. protectedTest(helloStr)会出错?我调用了a2.equals(b1)和a2==b1得到的结果都是true啊?但是为什么这里出这个错误?

在这里,这个问题是最关键的,也是我们放到最后来解答的原因。

首先,回顾一下equals()和==的在java里面的概念,记得有一道scjp的题目:

题目:下面的哪些叙述为真。

A. equals()方法判定引用值是否指向同一对象。

B. == 操作符判定两个分立的对象的内容和类型是否一致。

C. equals()方法只有在两个对象的内容一致时返回true。

D. 类File重写方法equals()在两个分立的对象的内容和类型一致时返回true。

答案是AD,严格来说这个问题的答案是不确定的,因为equals()方法是可以被重载的,但是如果新类没有重写equals(),则该方法在两个变量指向同一对象时返回真. 实际上java也是推荐的是使用equals()方法来判断两个对象的内容是否一样,就像String类的equals()方法所做的那样,判定两个String对象的内容是否相同。而==操作符返回true的唯一条件是两个变量指向同一对象。

在这里,我们不再深入的讨论关于equals()和==的区别和概念。我们只需要知道,在我们的例子里面,无论是equals()和==都是一个含义-就是引用值是否指向同一个对象(因为我们并没有重写equals()).

显然,我们在进行了a2=b1.这一步之后,a2和b1都是指向同一个对象。

既然指向同一个对象,为什么还要区别对待a2和b1?为什么a2就没有编译错误,而b1就要遇到reference ambiguous错误?

我们现看看jvm规范里的一段话:

“The Java Virtual Machine does not require any particular internal

structure for objects. In Sun's current implementation of the Java Virtual Machine, a reference to a class instance is a pointer to a handle that is itself a pair of pointers: one to a table containing the methods of the object and a pointer to the Class object that represents the type of the object, and the other to the memory allocated from the Java heap for the object data.”

实际上就是说:在java虚拟机中,类实例的引用就是指向一个句柄(handle)的指针,这个句柄是一对指针:一个指针指向一张表格,实际上这个表格也有两个指针:一个指针指向一个包含了对象的方法表,另外一个指向类对象;另一个指针指向一块从java堆中为分配出来内存空间。

那么,在a2=b1的时候,到底发生了什么?

实际上,在a2=b1的时候,仍然是存在两个句柄,a2和b1,但是a2和b1拥有同一块数据内存块和不同的函数表。所以在a2.protectedTest(helloStr)的时候,jvm会从a2的函数表里找到protectedTest(String Str)方法,但是b1.protectedTest(helloStr)却会出现编译错误,因为jvm从b1的函数表里找不到,然后就选择自己上溯造型还是参数上溯造型出现了ambiguous。这也是我们对这个问题的精确解释。对这个问题的解释到此结束了,如果大家想对这篇文章相关内容进行讨论,可以到:

http://blog.csdn.net/chensheng913/archive/2004/10/23/148309.aspx发表意见.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有