MIT OpenCourseWare
OCW Home Course List About OCW Help with OCW Feedback


Search
» 高级搜索
 课程首页
 教学大纲
 教学日程
 参考读物
 讲义
 复习
 实验
 作业
 考试
 项目
 工具
 相关资源

S4: Java Q&A


这章将列举Java语言经常遇到的问题,焦点将落在纯语法和深层次的概念上。(语法的问题请查看参考文献,深层次概念的问题查看Q&A)如果Java参考文献不能解决你的问题,可以查看这里!另外在提前警告某些错误的时候也会提到它。

感谢实验员帮助编译这章。内容:

    • 静态成员的继承
    • 访问的关键字和怎样使用它们(private、public等)
    • 加上你自己的属性、方法或构造函数
  • 构造函数
    • 当初始化一个类时隐含调用
    • 调用其它的构造函数
  • 方法
    • 调用静态方法
  • 数组
    • 建立和使用数组
    • Array() 填充一个类型数组
  • 字符串
    • 字符串和空结束符
  • Hash
    • Hash表和Hash码
  • Swing
    •  fvwm2/fvwm95下Swing 的使用

静态成员的继承

Q: 静态方法是怎么传递的?
A: 你可以用两种方式来调用静态方法。类名.方法名或对象的引用.方法名来调用方法。你应该一直用类名.方法名调用方法,因为在Java中静态方法在编译时候被调用而动态方法在运行时被调用。例如,假设我们执行下面代码,Foo继承 SuperFoo,他们都实现一个静态方法staticMeth()



SuperFoo myFoo = new Foo();







myFoo.staticMeth();







因为静态方法在编译的时候被调用,在这个实例中SuperFoo的实现staticMeth()将被调用。这是因为在编译时myFoo的类型是SuperFoo。也就是静态方法总是类名.方法名这样调用。
Q: 子类是否继承了父类的静态方法?
A:在Java中有各种方法继承静态成员。子类会继承父类的静态成员。但子类没有自己单独的静态拷贝;父类和它的所有子类共享同一个拷贝。如果你想在同一层的每一个子类中让静态方法能访问单个变量,这就有点困难。你得重载每一个单独的静态方法,在每一个子类中重新定义每一个静态变量,稍微有点否定继承。如果你不这样做,你每次改变静态变量的时候它都要在每一个类中改变。例如下面的代码:



public class Test



{



  public static int _myInt = 1;



  public static int foo()



    {



      return _myInt;



    }



}







public class Test2 extends Test



{



   public static int foo()



   {



    _myInt++;



     return _myInt;



   }



}







public class TestDriver



{



 public static void main(String args[])



    {



     System.out.println(Test.foo());



     System.out.println(Test2.foo());



     System.out.println(Test.foo());



    }



}



produces the following behavior:



sicp-19>rhlee~/6.170% java TestDriver







1







2







2



这是因为父类和它所有子类共享相同的所有静态成员的拷贝。

访问的关键字和怎样使用它们(privatepublic等)

Q: 怎么使用访问关键子? privatepublic 和 protected是什么意思,什么时候该使用和不该使用它们?
A: 访问方式决定了类的构造函数、方法和属性的可见性。

不同关键字的简短描述:

  • public -所有类都能查看它的构造函数、方法和属性。
  • protected -只有子类和同一包中的类可以查看它的构造函数、方法和属性。
  • private - 构造函数、方法和属性对类外部是不可见的。

Java中默认的访问是“包内可见”(默认访问)。这个级别的访问权限仅仅是包中的构造函数、方法和属性。

除非必要的情况,你应该小心使你的实现暴露在外部,在每一层的可见性中应该使用折中的设计方案。

如果你不确信哪种可见度合适请咨询你的助教。

加上你自己的属性、方法或构造函数

Q: 我能给一个类加上自己的属性、方法或构造函数吗?
A:你不能改变说明。因为说明只包含对其他类可见的东西,你可以自由的添加私有的属性、方法或构造函数,但不是公有、受保护或在程序包中可见的。(注意我们在 'protected'实例中有个例外)

构造函数Constructors

当初始化一个类时隐含调用。

Q: 当我编译ElementaryRouteRoute的一个子类)出现了错误。这是什么意思?



cannot resolve symbol



symbol: constructor Route ()







location: class ps2.Route



public ElementaryRoute(GeoSegment gs) {







                             ^



A: 当你在Java中初始化一个子类时,你必须调用一个有参数或无参数的父类的构造函数,或Java隐含的调用父类中无参数的构造函数。如果父类不存在这样的构造函数,编译时就会出错。

记住如果你调用了父类,在子类的构造函数中必须第一个声明。另外,如果你定义了一个类但没有声明构造函数,一个隐含的无参数的构造函数(它什么也不做)将被建立。如果你声明其它构造函数,隐含无参数的构造函数就不存在。

调用其它的构造函数

Q: 当我在我的类中调用其他构造来扩充构造函数时,为什么不起作用?也就是,当我写Foo(),语句Foo(0)为什么不工作?
A: 构造函数不是方法。注意要通过新的关键字来正常调用它们。通过调用其它构造函数来扩充你现有的构造函数是有用的,所以Java提供了一个特殊的语法:this(<参数>) 在上面的实例中,应该写成this(0);。注意调用其它构造函数应该在你的构造函数的第一行。

调用静态方法

Q:我已经import java.lang.Math.。为什么还要Math.random();? 为什么只写random()就不工作?
A:Java中使用静态方法必须要显示类,当许多类有相同的方法名时避免名字空间的问题。(当方法在现在的类或一个祖先类中时除外。)import 这个语句让你不必显示一个类的所有包路径。事实上,在这个例子中所有的java.lang.* 类都是被默认输入的,所以这个语句是无关的。

数组

建立和使用数组

Q:怎样建立和使用数组?
A:在Java中数组都是对象,但是它们也遵循特殊的规则。你可以给数组分配新的关键字和特定大小:



Foo f[] = new Foo[20];



数组的长度存储在一个公共最终区属性(只读)中:




int size = f.length;



不能直接调整数组大小。为了使数组长度增大,必须新建一个特定大小的数组,把旧元素拷贝到新数组里。也可以用 System.arraycopy()拷贝元素。例子:




Foo temp[] = f;          // 'temp' 现在指向旧数组。
f = new Foo[f.length*2]; // 建立一个新数组f,长度是旧数组的两倍。


System.arraycopy(temp,0,f,0,temp.length); // 拷贝旧数组的值。

请查看Java语言说明书关于数组的说明,如果你还是有疑惑的话请咨询实验助理或助教。

toArray() 填充一个类型数组

Q我有一个java.util.List (向量、数组列表、链表等),是一系列Foo 对象。是否有一种简单方法可以不通过单步调试整个数组,就能得到 Foo[] 的方法?
A:有。java.util.List 接口定义了一个带一个参数的面向数组或对象的方法,如果数组足够让列表的元素存储在其中;否则,将分配一个新的相同运行时间类型的数组。因为我们接受了正确运行时间类型的数组,所以直接把返回值传给类型数组是安全的。

例如:




//向量fooVect指向Foo的对象







Foo[] fooList = new Foo[fooVect.size()];



fooList = (Foo[])fooVect.toArray(fooList);







字符串

字符串和空结束符

Q: 我尝试通过插入字符'\0' 来分离字符串,好象没有成功。
A: Java句柄字符串和C/C++的不相同。字符串不是通过一个空字符简单结束的。当使用到字符串时应当参考String 的详细说明。

Hash表

Hash表和Hash码

Q:什么是Hash表,我怎样书写一个好的Hash码?
A:给定一个, 一个Hash表基本可以找到一个 。在Hash表中使用Hash 函数把键值映射到Hash码中来表示一个记录。相同的键值必须映射到相同的Hash代码,使你能够在Hash表中找到相关的记录。因为Hash代码只是内存的偏移量,你可以使用在 O(1) 中的任何关键字来检索数据(这和在一个按关键字排序的列表中进行二进制查找用O(lg(n))时间是相反的。)

(在Java中产生的Hash代码可以是任何整数,但基于Hash表的空间(它是自动维护的),在Hash表中Java将会取Hash码的模,把它映射到一个记录上。)

然而,从键值到Hash码有多对一的映射,所以Hash码并不一定只提供一个记录。另外,如果许多键值映射到一个相同的Hash码,我们必须搜索所有的与那个Hash码相关的键值来找到我们所需要的记录;这将否定我们的常量搜索。 (最坏的情况下,所有的键值都映射到一个相同的Hash码,将产生一个简单表。) 因此,我们想写一个Hash函数在可能的时候防止冲突和不同的键值映射到不同的Hash码。

Hash表的基本宗旨:

  1. 在Hash表中不能有两个有相同键值的记录,如果有,当你给定一个键值将不知道要返回给你哪个记录。 (在Java中插入一个在Hash表中已存在的键值新记录时,将会把旧记录删除。)
  2. 当键值在Hash表中使用的时候,不能改变。否则你将找不到你的记录。 (例如,你把数据对象作为一个键值,一定不能修改它,否则将不再符合存储的Hash码。)
  3. 你的Hash函数必须设计成相同的键值返回相同的Hash码。这样就可以一直找到你的记录。
  4. Hash函数应该设计成尽量使不同的键值返回不同的Hash码。代码冲突越少运行的就越快。

关于好的和差的Hash函数: 键值是个有序对 (x,y)。差的Hash函数总是返回1。好的Hash函数可能返回 x+y。如果那样的话 (1,4) 和 (2,3)返回相同的码。更好的hash函数可能返回 x+1123*y, 这将会减少可能的冲突数。 (我们的努力并不会白费。 在这个问答外还有很多相关的hash函数。.)

在 Java™ 1.3中的Hash表是通过HashMap 这个类来实现的。旧的Hashtable 的类还有,但是速度慢,大多数场合还是用HashMap

请查看Object.hashCode()的说明,解释了hashCode()实现的约束。如果还想进一步了解,请查看Object.equals(Object)的说明,解释了equals()实现的约束。要确保正确的实现equals()或你的类作为hash表的键值不能正确工作。

这只是对Hash表的初步认识。如果想知道的更多,请咨询实验助理。

Swing

关于fvwm2/fvwm95下Swing 的问题

Q:我们的桌面弹球项目的实现在我们的一个小组员那里可以完美的工作,但是对于其他组员却不是这样的,出现这种情况的原因应该是什么?
A: 你们可能需要检查你们正在使用的 windowmanager (窗口管理系统)。我们的教员已经发现 swing 在 fvwm2/fvwm95 下可能发生一些问题,比如一些组件的渲染可能会不正常(或者完全失败),绘制表格出错,以及其它问题。请注意, Athena 的默认 windowmanager 是 mwm ,因此如果你不知道什么是 fvwm ,这些可能对你没有什么用处。如果你在使用 fvwm2/fvwm95,你可以考虑使用别的 windowmanager 来开放桌面弹球项目。Athena 机上还有很多的 windowmanager 可以使用,比如 sawfish或者默认的 mwm ,在使用这些 windowmanager时我们没有发现什么问题。(如果你不知道怎么开始,你使用 6.170zephyr实例或者咨询教员来获得帮助。)

返回顶部




 
MIT Home
Massachusetts Institute of Technology Terms of Use Privacy