Java2游戏编程读书笔记(4-1)

 
第4章   Java API 为你服务:常用Java 类
Java.lang包中含有Java语言的基本类,其中包括系统工具,原始数据类型的封闭类,数学工具和线程管理。
Object类是所有Java类的基类。这使得Java成为一种单一类型语言,也就是说,所有已定义的类型都从一个共同的祖先派生。不论是否有显式声明,所有的类都从Object类派生。
public class AquaMonster
public class AquaMonster extends Object
这两个声明语义上是一样的。它们都从Object类继承功能,这样,在整个继承体系中都是“是一个(is-a)”的关系。
没有人会关心is-a关系扩展到整个体系结构,但要想写出通用的,灵活的代码时就必须注意这个问题。
接下来讨论Object类必须提供的一些方法。
1.clone 方法
它所作的是创建调用它的对象的一个深拷贝,并且把这个拷贝返回到主程序中。clone方法原型如下:
protected Object clone() throws CloneNotSupportedException
clone方法执行与类复制构造器相似的任务,但是它成为了创建一个对象的原样拷贝的首选方法。在这部分最后谈到Cloneable接口时,我们会研究clone方法的内部工作原理。
2.equals 方法
equals方法根据被发送的对象是否与调用的对象逻辑“等于”而返回一个true或false值。它的原型如下:
public boolean equals(Object obj)
Java的“==”运算符只确定两个非原始类型是否占有相同的内存地址。它从语义上确定两个对象的引用是否相同。equals方法可以帮助确定两个对象是否逻辑上等价。
假设你正在设计一个枪战游戏,其中包括不同类型的坦克,同组的坦克有相同的颜色和大小,颜色和大小都用int值来表达,并且不想让坦克伤害到同组内的另外的坦克。因此,为了让任何两个坦克逻辑上等价,必须让它们有相同的颜色和大小。我们可能会像下面一样实现Tank类的equals方法:
//判断两个Tank对象是否在逻辑上相同
public boolean equals(Object other){
       Tank temp;
 
       //如果对象指向同一个对象,那么它们相同
       if(this==other){
              return true;
       }
 
       //如果另一个对象为空,或不是Tank类的一个实例,那么两个对象肯定不相等
       if(other==null||!(other instanceof Tank)){
              return false;
       }
 
       //把所传入的Object强制转换为Tank
       temp=(Tank)other;
       //根据颜色和大小来判断
       return (getColor()==temp.getColor()&&getSize()==temp.getSize());
}//equals
 
主程序代码能够很容易地确定两个Tank对象是否可以互相袭击
//假设tank1和tank2指向两个有效的Tank对象
if(!tank1.equals(tanks)){
       //允许这两个坦克开火
}
所写的每个equals方法都应该在确定两个对象是否相等之前通过几个琐碎的拒绝。如果两个对象占有相同的内存地址,那么显然它们相等。如果被发送的对象是null或者不是Tank类的实例,那么它们肯定不相等。最后,如果引入的对象没有被拒绝,那么在确定它们是否等价之前把它转换成有效的Tank对象。
3.hashCode 方法
哈希(Hash)码是惟一的识别一个对象的代码。在希望通过把对象值映射成惟一的“关键字”值来组织数据时,哈希码经常起着很大作用。Java如下定义了Object类的hashCode方法:
public int hashCode()
Java API对设计一个自定义hashCode方法有如下的建议:
q        方法必须是一致的。只要对象没有在几次调用之间修改,对于它的每次调用hashCode方法应该得到完全相同的整型结果,这样可以维护一个哈希值对应一个对象的原则。
q        等价的对象产生相等的哈希代码。如果两个对象逻辑相等,那么它们应当返回相同的哈希代码值。
q        非等价的对象可能产生相等的哈希代码。并不是所有不相等对象必须产生唯一的哈希代码值,但是越是能够使不同的对象产生唯一的哈希值,程序就拥有越好的性能。
4.toString方法
这个方法主要用于创建并返回一个String来表示一个对象的当前状态。对象的String表现形式对于一般用途的单步调试和错误处理很有意义。
Java 2 文档建议每个类都覆盖这个方法。如果不选择覆盖这个方法,Object toString方法返回对象的类名和哈希码的连接字符串。
1.length 方法
2.charAt 方法
3.endsWith 和startsWith 方法
String类的endsWith方法有一些极好的应用,其中特别的一个是决定给定的文件名是否有效。
If(filename.endsWith(“.txt”)){
       //打开并处理该文件
}else{
       //无效的格式:报告错误信息
}
4.substring 方法
5.toLowerCase 和toUpperCase 方法
6.trim 方法
7.valueof 方法
下面演示了如何使用valueOf方法来把原始类型转换成String对象:
int value=54;
double pi=Math.PI;
String str=String.valueOf(value)+String.valueOf(pi);
System.out.println(str);
1.insert 方法
2.delete 和deleteCharAt 方法
3.append 方法
虽然可能不会太多地使用这个方法,但是要注意String串连在Java中的工作方式也是很有趣的。Java编译器用StringBuffer的append方法来实现对于字符串的二进制的“+”运算,例如:
String s=new StringBuffer().append(“card”).append(“inal”).toString();
与下面等价
String s=”card”+”inal”;
 
4.1.4         封装类
封装类不仅可以用数据结构来使用,它们还提供了许多转换方法和工具:
//把十进制转化为二进制、八进制和十六进制
int decimalValue=516;
System.out.println(Integer.toBinaryString(decimalValue));
System.out.println(Integer.toOctalString(decimalValue));
System.out.println(Integer.toHexString(decimalValue));
4.1.5         java.lang.Math
可以使用它的静态方法来执行许多不同的数学运算,而无须初始化它。
4.1.6         java.lang.System
这是一个final的,不能实例化的类,它含有几个有用的方法和工具,其中大部分会在稍后看到。
1.println 方法
2.arraycopy 方法
原型如下:
public static void arraycopy(Object src,int src_position,Object dst,int dst_position,int length);
下面是一个例子
//建立我们的初始源数组和目标数组
String[] src={“1”,”2”,”3”, ”4”, ”5”, ”6”, ”7”, ”8”, ”9”, ”A”};
String[]dst=new int[10];
//从源数组的下标3复制5个成员到目标数组的第四个下标开始
System.arraycopy(src,3,dst,4,5);
 
//把dst数组打印成序列
System.out.println(Arrays.asList(dst));
3.currentTimeMills 方法
在希望以规则的间隔更新对象或者只是想知道当前时间时用途非常大。currentTimeMills方法返回当前时间的毫秒数。
4.exit 方法
4.1.7         java.lang.Cloneable
它是一个public接口,当我们希望重写Object的clone方法时会在类中实现它。Cloneable接口中没有方法,但是实现它则表明一个类可以实现clone方法。clone方法创建调用对象的一个完全拷贝,并把它返回到主程序中。
还要记住Java对象都是引用值,也就是说,它们由它们当前的内存地址引用,所以对对象使用赋值运算只能把它们赋值给相同的内存地址。例如,思考一个虚构的Alien类,如果这样:
Alien a1=new Alien(50,150);
Alien a2=a1;
现在,假设想改变a2的位置,如下:
a2.moveTo(300,300);
如果用户希望对同一对象有两个引用,这样极好。但是如果需要同一对象的两个独立的拷贝(游戏中经常用到的“分身”,这个是无法直接实例化得到的,必须“克隆”),可以在改变其中一个的时候不会影响到另外一个时该怎么办?答案就是创建a1的一个克隆并把它赋给a2。
要使用clone方法可用,我们需要做的只是下面这样写:
public class Alien implement Cloneable
然后,再这样:
//克隆一个Alien;如果克隆不成功抛出错误
public Object clone(){
       try
       {
              return(super.clone());
       }catch(Exception e){
              //这一步应该不会发生,因为已经声明为Cloneable
              throw new InternalError();
       }
}
现在可以通过调用它的clone方法创建a1的一个拷贝:
a2=(Alien)a1.clone();
相对于复制构造器创建对象的拷贝,使用Java的clone方法是首选。
提示: 如果一个类包含另一个类的实例,那么也应该提供它自己的 clone 方法,这样可以对这个对象进行完全复制。否则,会制造出所谓的浅复制 (shallow copy) ,而且数据很可能会很快被破坏掉。
4.1.8         java.lang.Thread
线程是Java很重要的一个方面,尤其是开始研究图形用户界面的应用程序后。Java提供了一种比C或C++等语言简单很多的方法来实现线程。
当今大多数操作系统都允许并发执行多于一个的程序,每个并发执行的程序被称为一个进程(呆在内存里的程序就叫进程)。只要内存足够,可以运行尽量多的进程。
与进程不同,线程是程序内的一个执行实例(其实有什么分别呢?我倒没有觉出来,只是范围的不同罢了,线程就是进程的最小单位嘛。也就是说,进程可以由多个线程来组成,最少也要有一个线程,也就是多任务的程序,游戏程序里大量是这样的,如,即时战略游戏程序需要一边放着音乐,一边指挥着敌人的部队,还要响应用户的鼠标操作,而这一切必须是同时进行的,程序对线程的管理其实非常类似于操作系统对进程的管理)。
线程被加到应用程序中后,处理器时间通过有限的时间片给予那些单独的线程。
1. 一个例子
//计算并打印Fibonacci数列的Thread
class Fibonacci extends Thread{
       //在更新前等待的最短时间
       private static final long WAIT_TIME=500;
      
       //数列中的前一个数
       private long previous;
      
       //数列中的当前数
       private long current;
      
       //这个数列的标记
       private char ident;
      
       //构造一个新的Fibonacci数列
       public Fibonacci(char id){
              ident=id;
             
              previous=1;
              current=1;
       }
      
       private void printNext(){
              System.out.print("("+ident+") "+current+", ");
       }
      
       //计算数列中的下一个数,它是数列中前两个数的和
       private void update(){
              long temp=current;
              current=previous+current;
              previous=temp;
       }
      
       //线程的执行代码,这个方法是从Thread类继承而来的
       public void run(){
              //打印数列中的前两个数字
              printNext();
              printNext();
             
              //得到这个线程的引用
              Thread t=Thread.currentThread();
             
              //循环,直到线程激活
              while(t==this){
                     update();
                    
                     //只要当前的数列值超过了最大的long型数值,它就会截断为负数
                     //只要当前值为负数就终止这个线程的执行
                     if(current<0L){
                            System.out.println("/n已达长整型值极限!线程消毁...");
                           
                            t=null;
                            return;
                     }
                    
                     //打印下一个Fibonacci数
                     printNext();   
             
                     //暂停一会
                     try{
                            Thread.sleep(WAIT_TIME);
                     }catch(InterruptedException e){
                     }
              }
       }//run
}
 
public class ThreadTest{
       public static void main(String[] args){
              //启动几个Fibonacci线程
              new Fibonacci('A').start();
              new Fibonacci('B').start();
              new Fibonacci('C').start();
       }
}
2.Runnable 接口
此外,线程也可以通过实现Runnable接口创建,这比通过继承Thread类创建线程要好。这种方法允许你给予类从主程序开启线程的能力,而又不会被限制从Thread类继承。可以通过传递一个新Thread对象来启动一个Runnable对象。
关于线程的讨论会带给我们一个有趣的要点:如果两个线程同时试图访问并使用一个内存地址或值时会发生什么呢?就是术语“脏读”(dirty read)和“丢失更新”(loat update)所描述的情况。
怎么防止线程之间互相扰乱(即前面所说的脏读)呢?答案就是对象的同步。在类的方法或者代码块中使用synchronized关键字,能够给对象“加锁”。当一个对象被锁住时,其他的线程在代码块或方法结束之前不能访问它。我们会在本书后面研究更多关于同步的问题。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默然说话

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值