黑马程序员技术交流社区

标题: Java中比较少人回答的问题 [打印本页]

作者: Yeauty    时间: 2016-11-5 09:16
标题: Java中比较少人回答的问题
Q:我该何时调用构造函数,何时调用其它方法呢?

最直观的回答就是,在你想new一个对象的时候调用构造函数;这是new这个关键字的用途。而我的回答是:构造函数往往被滥用了,调用它们和它们所做的工作两方面都被滥用了。下面是一些需要考虑的问题:


public Number numberFactory(String str) throws NumberFormatException {
    try {
      long l = Long.parseLong(str);
      if (l >= 0 && l < cachedLongs.length) {
        int i = (int)l;
        if (cachedLongs != null) return cachedLongs;
        else return cachedLongs = new Long(str);
      } else {
        return new Long(l);
      }
    } catch (NumberFormatException e) {
      double d = Double.parseDouble(str);
      return d == 0.0 ? ZERO : d == 1.0 ? ONE : new Double(d);
    }
  }
  private Long[] cachedLongs = new Long[100];
  private Double ZERO = new Double(0.0);
  private Double ONE = new Double(1.0);

可以看出new的功能很有用,但是工厂的回收机制同样很有用。Java之所以仅支持new,是因为这是最简单最有效的方法,并且Java的宗旨也是尽量保持语言自身的简洁。但这并不意味着你自己的类库需要按照这一低标准来约束自己。(而且这并不意味着内置的库也需要这种约束条件,但是很可惜,他们还是这么做了。)

Q:我的代码会在创建对象或在GC开始之前时被杀掉吗?

假设应用程序不得不操纵许多3D几何点。很明显,依Java的风格来做就是去写一个Point类,内含3个double变量x、y、z坐标。但是,为大量点进行申请和回收的确会导致性能上的问题。而你可以自己建立资源池对存储进行管理。你可以在程序运行之初申请一大批Point对象,并将其存入数组中,而不是每次用到时才去申请。得到的数组(封装在一个类中)就像Point的工厂一样,但它是上下文感知的(socially-concious)回收工厂。调用pool.point(x,y,z) 时会返回数组中第一个未被使用的Point对象,将其3个变量设置为指定的值,并把它标记为已使用。而作为一个程序员来讲,当这些对象不再使用时,将它们放回资源池中便成了你的责任。

完成这点的方法有很多。如果你确定所申请的Point对象在使用一段时间之后会被丢弃的话,那最简单的方法就是这样做:利用int pos = pool.mark() 来标识当前资源池的位置。当你用完了之后,可以调用pool.restore(pos) 将原来位置的标志位重置。如果你想同时使用多个Point对象,那从不同的资源池里申请吧。资源池节省了垃圾回收时的开销(如果你有一个好的处理对象回收的模型)但是你仍然躲不开初始化对象时候的开销。你可以选择用“Fortran式”的方法来解决这个问题:用三个数组来存储x、y和z坐标,而不是用Point对象。你可以一个管理一批Point的类,而不必为单个点定义Point类。下面是一个资源池类的例子:

public class PointPool {
  /** Allocate a pool of n Points. **/
  public PointPool(int n) {
    x = new double[n];
    y = new double[n];
    z = new double[n];
    next = 0;
  }
  public double x[], y[], z[];
  /** Initialize the next point, represented as in integer index. **/
  int point(double x1, double y1, double z1) {
    x[next] = x1; y[next] = y1; z[next] = z1;
    return next++;
  }
  /** Initialize the next point, initilized to zeros. **/
  int point() { return point(0.0, 0.0, 0.0); }
  /** Initialize the next point as a copy of a point in some pool. **/
  int point(PointPool pool, int p) {
    return point(pool.x[p], pool.y[p], pool.z[p]);
  }
  public int next;
}

你可以这样使用它:

PointPool pool = new PointPool(1000000);
PointPool results = new PointPool(100);
...
int pos = pool.next;
doComplexCalculation(...);
pool.next = pos;
...
void doComplexCalculation(...) {
  ...
  int p1 = pool.point(x, y, z);
  int p2 = pool.point(p, q, r);
  double diff = pool.x[p1] - pool.x[p2];
  ...
  int p_final = results.point(pool,p1);
  ...
}

用PointPool 的方法申请100万个点花了半秒钟,而用Point类直接申请100万个点的方法需要6秒钟,所以相当于提速了12倍。

把p1,p2和p_final直接当做Point来声明远比当做int来声明好的多吧?在C/C++中,你可以用typedef int Point命令,但是Java不允许这样做。如果你想冒险一下,可以自己设置一下makefile,让文件在Java编译器运行之前先过一遍C语言的预处理器,然后你就可以这样写了:#define Point int.

Q:我在循环中有一个复杂的表达式。为了保证效率,我想让这个计算仅做一次。但是为了可读性,我想让它留在循环里被调用的地方。我该怎么办?

我们假设有这样一个例子,match是一个正则表达式的模式匹配函数,compile将一个字符串编译成一个有限状态机以供match调用:

for(;;) {
  ...
  String str = ...
  match(str, compile("a*b*c*"));
  ...
}

由于Java没有宏定义,随着时间的推移,你也许会需要一些控制,但你的选择很有限。其中一种可行的选择是,使用带有变量初始化的内部接口,这虽然不优雅但是是一种可行的方法。

for(;;) {
  ...
  String str = ...
  interface P1 {FSA f = compile("a*b*c*);}
  match(str, P1.f);
  ...
}

P1.f会在第一次使用P1时进行初始化,并且不会再改变,因为接口中的变量是隐式的static final的。如果你不想这么做,那可以换一种可以提供更多控制选择的语言。在Common Lisp中,字符序列#.表示其紧随在后的表达式会在读(编译)时计算,而不是在运行时。所以你可以这样写:

(loop
  ...
  (match str #.(compile "a*b*c*"))
  ...)




作者: 默默默默    时间: 2016-11-5 11:02
精辟,原先压根没考虑过的呢长知识

作者: yanghao2016    时间: 2016-11-5 11:34
涨知识了,

作者: 695783362    时间: 2016-11-5 12:44
哇哦……又长见识了。

作者: efkllx    时间: 2016-11-5 17:43
。。。有些看不懂啊,有点深奥了





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2