JAVA静态方法是否可以被继承?

结论:java中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐藏.

原因:
1. 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为”隐藏”。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在”隐藏”的这种情况。
2. 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:”重写”后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
3. 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。

public class A { //父类  
    public static String staticStr = "A静态属性";  
    public String nonStaticStr = "A非静态属性";  
    public static void staticMethod(){  
        System.out.println("A静态方法");  
    }  
    public void nonStaticMethod(){  
        System.out.println("A非静态方法");  
    }  
}  
public class B extends A{//子类B  
    public static String staticStr = "B改写后的静态属性";  
    public String nonStaticStr = "B改写后的非静态属性";  
    public static void staticMethod(){  
        System.out.println("B改写后的静态方法");  
    }  
}  
public class C extends A{//子类C继承A中的所有属性和方法  

}  
public class StaticExtendsTest {  

    public static void main(String[] args) {  
        C c = new C();  
        System.out.println(c.nonStaticStr);  
        System.out.println(c.staticStr);  
        c.staticMethod();
        //输出的结果都是父类中的非静态属性、静态属性和静态方法,
        //推出静态属性和静态方法可以被继承  

        System.out.println("-------------------------------");  

        A c1 = new C();  
        System.out.println(c1.nonStaticStr);  
        System.out.println(c1.staticStr);  
        c1.staticMethod();
        //结果同上,输出的结果都是父类中的非静态属性、静态属性和静态方法,
        // 推出静态属性和静态方法可以被继承  

        System.out.println("-------------------------------");  
        B b = new B();  
        System.out.println(b.nonStaticStr);  
        System.out.println(b.staticStr);  
        b.staticMethod();  

        System.out.println("-------------------------------");  
        A b1 = new B();  
        System.out.println(b1.nonStaticStr);  
        System.out.println(b1.staticStr);  
        b1.staticMethod();
        //结果都是父类的静态方法,说明静态方法不可以被重写,不能实现多态  
    }  

}  

一种新的移动APP保持登陆的实现机制介绍

移动APP的特点

移动APP和网页登陆不同的一点就是,App不需要用户每次使用都登陆,增加了易用性, 本文介绍一下App保持登陆的是实现机制

目前常见的机制:

一、使用传统的会话机制session

把网页的机制照搬过来,利用传统网页的记住登陆机制. 用户输入正确的用户名和密码后,创建登陆会话,同时生成一个记住登陆token保持在服务器端,同时发个客户端. 客户端每次启动时,通过记录登陆token新建会话,后续使用便采取session机制. 服务器端的可用Memcache 或 Redis 存储会话.

回味一下这个机制,其中的记住登陆token,也可定个长的有效期,比如30天, 记住登陆token类似Oauth 2.0 的 Refresh token, Session机制里的Session Id 类似Access token. 只不过,Session机制里的Session Id 持续使用时,会自动延期.

这个机制的好处是充分利用现有知识,简单易用,没有太多新名词概念 不足之处是不便于分布式认证,还有Session机制对性能有一小点影响, 同时不符合Restful API无状态的设计精神.

二、使用一个有效期很长的Token 机制

用户正确登陆后,生成一个有效期很长的Token(比如半年),保存在服务器端,同时发给客户端, 客户端的每次请求就以这个Token验证身份. 采用https 传输加密, Token中途不会被获取, 而保存在本地的Token其他程序也访问不了. 对应普通应用而言,这个方案也是可以的.

三、使用一个长期的Refresh Token 和 短期的Access Token.

对于方案二, 如果手机硬件本身被黑客获取过, 长期Token可能被盗,有潜在的风险. 考虑到这一点, Oauth 2.0 标准推荐采用Refresh Token和Access Token. Refresh Token 有效期很长, Access Token 有效期很短. 用户登陆后,同时获得Refresh Token 和 Access Token,平时就用 Access Token, Access Token 过期后就用Refresh Token 获取新的Access Token.

这个方案使用很广泛,包括微信公众平台开发 也使用这个机制 但细细一想, 这个机制并不比方案二(使用一个长期的token)安全, 黑客如果能够获取Access Token,获取Refresh Token也不难,采用两个token 仅仅是给黑客增加点小麻烦. 一旦黑客获取了获取Refresh Token, 就可反复的刷新的Access Token

本文介绍一种新的机制

Token以旧换新的机制

这个机制只使用一个短期的Token,比如1天. 用户登陆后, 这个Token发给客户端, 用户每次请求就使用这个Token认证身份, Token过期后凭此token换取新的Token,一个过期的Token只能换取一个新的Token,这是关键. 如果Token被盗, 黑客要持续使用也需持续的换取新的Token, 服务器一旦发现,一个旧Token多次试图换取新Token,表示有异常. 这时强制用户再次登陆. Token旧换新,不一定等过期了才换,应用启动时就可旧换新,这个视具体情况而定.

这个Token的有效期,针对不同的应用可以调整. 以设计招商银行的app为例: 1, 采用https 加密,确保传输安全. 2,Token的有效期设为15分钟,Token每15分钟,以旧换新换取新的Token. 正常情况下,这个以旧换新对用户不可见,一但两人试图以旧换新,两人都阻止,需要再次登陆. 3,对于修改密码和转账支付这样的关键操作,要求用户输入密码. 这样就万无一失了.

重复一下,设计安全机制时,一定要使用https, 没有https, 多数安全设计都是无用功

附: Token 简介

Token 中文的翻译就是令牌, 识别身份的依据. 通常token有两种:

一、不含内容的token

这种token这是一个唯一的hash值, 要知道这个token是谁,要到一个中心服务器查询. 在中心服务器,用户数据可能储存于文件或是数据库或是Redis等. 在session 机制的Cookie里 有一个session id, 本质上也是一个这类token.

二、包含内容的token

这种token, 就像一个身份证,包含公开的用户信息, 通过签名机制确保token无法伪造. 最常见的这类token 就是: Json web token (JWT) 这种token好处是不用到中心服务器查询,对于分布式系统很有用, 比如用户登陆后,要看视频,要下载文件. 而视频,文件资源都需验证用户身份,视频,文件资源在不同的服务器,甚至由不同的公司提供,这时可分布式验证的JWT就很有用. 这种可分布式验证的Token通常发行了就不能注销,只能等其自行过期失效. 这时为了保证安全性,使用短期JWT,再加上述的token以旧换新,就很有效.

本文所说的Token旧换新机制,对上述两种token均适用. Token 就是一个字符串信息,就算复制一万份,彼此也毫无差别, 有了以旧换新的机制,Token就有点像实物了, 已经换过了自然不能再换,不管有多少份,只能有一个换取新的Token 当两个人先后拿着同一个token 来换新,我们不能判断到底哪个合法,哪个非法,好吧,两人都再次登陆确认身份吧.

转载 https://my.oschina.net/u/3532467/blog/978953

Android中View的Draw

View中:

  1. Draw the background 绘制背景
  2. If necessary, save the canvas’ layers to prepare for fading. 如有必要,颜色渐变淡之前保存画布层(即锁定原有的画布内容)
  3. Draw view’s content. 绘制view的内容
  4. Draw children. 绘制子view
  5. If necessary,draw the fading edges and restore layers. 如有必要,绘制颜色渐变淡的边框,并恢复画布(即画布改变的内容附加到原有内容上)
  6. Draw decorations (scrollbars for instance). 绘制装饰,比如滚动条
public void draw(Canvas canvas){
    ...
    if(!dirtyOpaque){
        drawBackground(canvas);//背景绘制
    }
    // skip step 2 & 5 if possible (common case) 通常情况跳过第2和第5步
    ...
    if(!dirtyQpaque) onDraw(canvas); // 调用onDraw
    dispatchDraw(canvas); // 绘制子view
    onDrawScrollBars(canvas); // 绘制滚动条
    ...
}

protected void dispatchDraw(Canvas canvas){// 空实现}

protected void onDraw(Canvas canvas){// 空实现}

ViewGroup中:

protected void dispatchDraw(Canvas canvas){
    ...
    drawChild(...); // 绘制子View
    ...
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime){
    return child.draw(canvas, this, drawingTime);
}

说明:

  1. 自定义一个View时,重写onDraw。
    调用view.invalidate(), 会触发onDraw和computeScroll(). 前提是该View被附加在当前窗口
    view.postInvalidate();//是在非UI线程上调用的

  2. 自定义一个ViewGroup,重写onDraw。
    onDraw可能不会被调用,原因是需要先设置一个背景(颜色或图)
    表示这个group有东西需要绘制了,才会触发draw,之后是onDraw。
    因此,一般直接重写dispatchDraw来绘制viewGroup

  3. 自定义一个ViewGroup
    dispatchDraw会调用drawChild。

Android Studio Mac上常用快捷键

⌃:control
⇧:shift
⌥:alt(option)
⌘:cmd(command)

重构(提取为方法):
option+command+M

全局查找:
shift+command+F

搜索并替换:
command+R

大小写切换:
shift+command+U

导包:
option+Enter

全局找文件:
double shift

编译并运行
control+R

删除行:
command+X

全屏:
control+command+F

跳转至第几行:
cmd + L

引入重写父类的方法:
ctr + O

引入接口或抽象类方法的实现:
ctr + I

切换文件:
ctr + tab

局部代码块展开/收缩:
cmd + + / cmd + –

全部代码块展开/收缩:
sft + cmd + + / sft + cmd + –

类名/方法名/变量名 重命名操作:
sft + fn + F6

方法重构,方法抽离:
opt + cmd + M

抽离成方法参数:
opt + cmd + P

抽离为局部变量:
opt + cmd + V

抽离为成员变量:
opt + cmd + F

Android Studio代码行数统计插件Statistics

Android Studio 是没有提提供统计代码全部行数的功能的,但是对于开发者来说,这个功能确实必备的,Statistic统计代码行数非常方便,也很详细。

首先肯定是将插件下载下来,下载地址:https://plugins.jetbrains.com/plugin/4509

也可以直接从插件中搜索

首次安装在AS的View→Tool Windows→Statistic,选择之后会在AS的左下角出现statistic按钮。

一开始里面的内容是空白的,我们点击Refresh,如果还是空白,就点击Settings来添加需要统计行数的项目。

Android Stduio 统计代码行数

android studio统计项目的代码行数的步骤如下:
1. 在弹出Find in Path的框中的Text to find输入\n,接着勾选Regular expression(正则表达式),Context选择anywhere,
Scope根据你想要统计的范围进行选择,File mask选择*.java。(在这里统计项目的Java的代码行数)

2. 下图的Useages in generated code是自动生成的代码,上面那个就是项目的代码行数。

View.setWillNotDraw()方法的使用

这个函数更多被在ViewGroup上调用。

View

自定义View中如果重写了onDraw()即自定义了绘制,那么就应该在构造函数中调用view的setWillNotDraw(false),设置该flag标志。其实默认该标志就是false。

ViewGroup

ViewGroup默认情况下,出于性能考虑,会被设置成WILL_NOT_DROW,这样,ondraw就不会被执行了。

如果我们想重写一个viewgroup的ondraw方法,有两种方法:

  1. 构造函数中,给viewgroup设置一个颜色。
  2. 构造函数中,调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。

在viewgroup初始化的时候,它调用了一个私有方法:initViewGroup,它里面会有一句setFlags(WILLL_NOT_DRAW,DRAW_MASK);相当于调用了setWillNotDraw(true)
所以说,对于ViewGroup,他就认为是透明的了,如果我们想要重写onDraw,就要调用setWillNotDraw(false)。

试分析-ShimmerFrameLayout

数据结构

public class ShimmerFrameLayout extends FrameLayout{
    // enum specity(特殊) the shape(形状) of the highlight mask applied to the contained view
    // 内容控件高光蒙版效果的枚举
    public enum MaskShape{
        LINEAR, // 线性效果
        RADIAL  // 辐射效果
    }
    // enum controlling the angle of the highlight mask animation
    // 控制高光蒙版动画的角度
    public enum MaskAngel{
        CW_0, // left to right      水平向右划过
        CW_90, // top to bottom     垂直向下落
        CW_180, // right to left    水平向左划过
        CW_270, // bottom to top    垂直向上冲
    }
    // struct storing(存储) various(各种) mask related(相关) parameters, which are used to construct the mask bitmap
    // 存放各种蒙版参数的类结构,这些参数是用于创建蒙版位图
    private static class Mask{
        public MaskAngle angle; // 动画角度
        public float tilt;      // 动画角度偏移
        public float dropoff;   // 掉落??
        public int fixedWidth;  // 固定宽度
        public int fixedHeight; // 固定高度
        public float intensity; // 强度
        public float relativeWidth; // 相对宽度
        public float relativeHeight; // 相对高度
        public MaskShape shape; // 动画形状
    }
    // struct for storing the mask translation animation values
    // 存放蒙版移动动画的起始点信息
    private staic class MaskTranslation{
        public int fromX;
        public int fromY;
        public int toX;
        public int toY;
    }
}

流程步骤

  1. 在构造方法中初始化参数
  2. 启动动画(自动启动或主动调用)
  3. 在动画中刷新参数,并调用invalidate(),触发逻辑
  4. 在dispatchDraw中,使用一个Bitmap结合子控件内容绘制

构造方法

    setWillNotDraw(false);
    mAlphaPaint = new Paint();
    mMaskPaint = new Paint();
    // 初始化
    useDefaults();

    if (attrs != null) {
        // 读取属性
    }

setWillNotDraw

动画

/**
   * Start the shimmer animation. If the 'auto start' property is set, this method is called automatically when the
   * layout is attached to the current window. Calling this method has no effect if the animation is already playing.
   * 如果设置为自动开启动画(auto start),这个方法当绑定到窗口之后就会自动调用
   */
  public void startShimmerAnimation() {
    if (mAnimationStarted) {
      return;
    }
    Animator animator = getShimmerAnimation();
    animator.start();
    mAnimationStarted = true;
  }

  /**
   * Stop the shimmer animation. Calling this method has no effect if the animation hasn't been started yet.
   */
  public void stopShimmerAnimation() {
    if (mAnimator != null) {
      mAnimator.end();
      mAnimator.removeAllUpdateListeners();
      mAnimator.cancel();
    }
    mAnimator = null;
    mAnimationStarted = false;
  }

  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    if (mGlobalLayoutListener == null) {
      mGlobalLayoutListener = getLayoutListener();
    }
    // 绑定到窗口后,等子视图都摆放好后,就会尝试启动动画
    getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
  }

  private ViewTreeObserver.OnGlobalLayoutListener getLayoutListener() {
    return new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        boolean animationStarted = mAnimationStarted;
        resetAll();
        if (mAutoStart || animationStarted) {
          startShimmerAnimation();
        }
      }
    };
  }

  @Override
  protected void onDetachedFromWindow() {
    stopShimmerAnimation();
    if (mGlobalLayoutListener != null) {
      getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
      mGlobalLayoutListener = null;
    }
    super.onDetachedFromWindow();
  }

动画和绘制

动画改变数据

  // Get the shimmer Animator object, which is responsible(负责的) for driving the highlight mask animation.
  private Animator getShimmerAnimation() {
    if (mAnimator != null) {
      return mAnimator;
    }
    int width = getWidth();
    int height = getHeight();
    switch (mMask.shape) {
      default:
      case LINEAR:
        switch (mMask.angle) {
          default: case CW_0: mMaskTranslation.set(-width, 0, width, 0); break;
          case CW_90: mMaskTranslation.set(0, -height, 0, height); break;
          case CW_180: mMaskTranslation.set(width, 0, -width, 0); break;
          case CW_270: mMaskTranslation.set(0, height, 0, -height); break;
        }
    }
    mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f + (float) mRepeatDelay / mDuration);
    mAnimator.setDuration(mDuration + mRepeatDelay);
    mAnimator.setRepeatCount(mRepeatCount);
    mAnimator.setRepeatMode(mRepeatMode);
    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        float value = Math.max(0.0f, Math.min(1.0f, (Float) animation.getAnimatedValue()));
        setMaskOffsetX((int) (mMaskTranslation.fromX * (1 - value) + mMaskTranslation.toX * value));
        setMaskOffsetY((int) (mMaskTranslation.fromY * (1 - value) + mMaskTranslation.toY * value));
      }
    });
    return mAnimator;
  }

  /**
   * Translate the mask offset horizontally. Used by the animator.
   *
   * @param maskOffsetX Horizontal translation offset of the mask
   */
  private void setMaskOffsetX(int maskOffsetX) {
    if (mMaskOffsetX == maskOffsetX) {
      return;
    }
    mMaskOffsetX = maskOffsetX;
    invalidate(); // 使其重绘
  }

  /**
   * Translate the mask offset vertically. Used by the animator.
   *
   * @param maskOffsetY Vertical translation offset of the mask
   */
  private void setMaskOffsetY(int maskOffsetY) {
    if (mMaskOffsetY == maskOffsetY) {
      return;
    }
    mMaskOffsetY = maskOffsetY;
    invalidate(); // 使其重绘
  }

在dispatchDraw处理绘制工作

@Override
  protected void dispatchDraw(Canvas canvas) {
    if (!mAnimationStarted || getWidth() <= 0 || getHeight() <= 0) {
      super.dispatchDraw(canvas);
      return;
    }
    dispatchDrawUsingBitmap(canvas);
  }

  /**
   * Draws and masks the children using a Bitmap.
   *
   * @param canvas Canvas that the masked children will end up being drawn to.
   */
  private boolean dispatchDrawUsingBitmap(Canvas canvas) {
    Bitmap unmaskBitmap = tryObtainRenderUnmaskBitmap();
    Bitmap maskBitmap = tryObtainRenderMaskBitmap();
    if (unmaskBitmap == null || maskBitmap == null) {
      return false;
    }
    // First draw a desaturated version
    drawUnmasked(new Canvas(unmaskBitmap));
    canvas.drawBitmap(unmaskBitmap, 0, 0, mAlphaPaint);

    // Then draw the masked version
    drawMasked(new Canvas(maskBitmap));
    canvas.drawBitmap(maskBitmap, 0, 0, null);

    return true;
  }

最主要的是对Bitmap的处理

/**
   * Draws and masks the children using a Bitmap.
   *
   * @param canvas Canvas that the masked children will end up being drawn to.
   */
  private boolean dispatchDrawUsingBitmap(Canvas canvas) {
    Bitmap unmaskBitmap = tryObtainRenderUnmaskBitmap();    // 获取一个Bitmap,
    Bitmap maskBitmap = tryObtainRenderMaskBitmap();        // 获取一个Bitmap,
    if (unmaskBitmap == null || maskBitmap == null) {
      return false;
    }
    // First draw a desaturated version
    drawUnmasked(new Canvas(unmaskBitmap));     // 将子控件绘制到临时的画布上
    canvas.drawBitmap(unmaskBitmap, 0, 0, mAlphaPaint);// 将子控件绘制到屏幕上

    // Then draw the masked version
    drawMasked(new Canvas(maskBitmap));
    canvas.drawBitmap(maskBitmap, 0, 0, null);

    return true;
  }
  // 试图获取一个Bitmap
  private Bitmap tryObtainRenderUnmaskBitmap() {
    if (mRenderUnmaskBitmap == null) {
      mRenderUnmaskBitmap = tryCreateRenderBitmap();
    }
    return mRenderUnmaskBitmap;
  }

  // 试图获取一个Bitmap
  private Bitmap tryObtainRenderMaskBitmap() {
    if (mRenderMaskBitmap == null) {
      mRenderMaskBitmap = tryCreateRenderBitmap();
    }
    return mRenderMaskBitmap;
  }

  // 其实就是创建一个和自己一样大的Bitmap
  private Bitmap tryCreateRenderBitmap() {
    int width = getWidth();
    int height = getHeight();
    try {
      return createBitmapAndGcIfNecessary(width, height);
    } catch (OutOfMemoryError e) {
      // 打印log
      String logMessage = "ShimmerFrameLayout failed to create working bitmap";
      StringBuilder logMessageStringBuilder = new StringBuilder(logMessage);
      logMessageStringBuilder.append(" (width = ");
      logMessageStringBuilder.append(width);
      logMessageStringBuilder.append(", height = ");
      logMessageStringBuilder.append(height);
      logMessageStringBuilder.append(")\n\n");
      for (StackTraceElement stackTraceElement :
          Thread.currentThread().getStackTrace()) {
        logMessageStringBuilder.append(stackTraceElement.toString());
        logMessageStringBuilder.append("\n");
      }
      logMessage = logMessageStringBuilder.toString();
      Log.d(TAG, logMessage);
    }
    return null;
  }

  // Draws the children without any mask.
  // 将子控件绘制到画布上
  private void drawUnmasked(Canvas renderCanvas) {
    renderCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
    super.dispatchDraw(renderCanvas);
  }

  // Draws the children and masks them on the given Canvas.
  private void drawMasked(Canvas renderCanvas) {
    Bitmap maskBitmap = getMaskBitmap();
    if (maskBitmap == null) {
      return;
    }

    renderCanvas.clipRect(
        mMaskOffsetX,
        mMaskOffsetY,
        mMaskOffsetX + maskBitmap.getWidth(),
        mMaskOffsetY + maskBitmap.getHeight());
    renderCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
    super.dispatchDraw(renderCanvas);

    renderCanvas.drawBitmap(maskBitmap, mMaskOffsetX, mMaskOffsetY, mMaskPaint);
  }

  // Return the mask bitmap, creating it if necessary.
  private Bitmap getMaskBitmap() {
    if (mMaskBitmap != null) {
      return mMaskBitmap;
    }

    int width = mMask.maskWidth(getWidth());
    int height = mMask.maskHeight(getHeight());

    mMaskBitmap = createBitmapAndGcIfNecessary(width, height);
    Canvas canvas = new Canvas(mMaskBitmap);
    Shader gradient;
    switch (mMask.shape) {
      default:
      case LINEAR: {
        int x1, y1;
        int x2, y2;
        switch (mMask.angle) {
          default:
          case CW_0:
            x1 = 0;
            y1 = 0;
            x2 = width;
            y2 = 0;
            break;
          case CW_90:
            x1 = 0;
            y1 = 0;
            x2 = 0;
            y2 = height;
            break;
          case CW_180:
            x1 = width;
            y1 = 0;
            x2 = 0;
            y2 = 0;
            break;
          case CW_270:
            x1 = 0;
            y1 = height;
            x2 = 0;
            y2 = 0;
            break;
        }
        gradient =
            new LinearGradient(
                x1, y1,
                x2, y2,
                mMask.getGradientColors(),
                mMask.getGradientPositions(),
                Shader.TileMode.REPEAT);
        break;
      }
      case RADIAL: {
        int x = width / 2;
        int y = height / 2;
        gradient =
            new RadialGradient(
                x,
                y,
                (float) (Math.max(width, height) / Math.sqrt(2)),
                mMask.getGradientColors(),
                mMask.getGradientPositions(),
                Shader.TileMode.REPEAT);
        break;
      }
    }
    canvas.rotate(mMask.tilt, width / 2, height / 2);
    Paint paint = new Paint();
    paint.setShader(gradient);
    // We need to increase the rect size to account for the tilt
    int padding = (int) (Math.sqrt(2) * Math.max(width, height)) / 2;
    canvas.drawRect(-padding, -padding, width + padding, height + padding, paint);

    return mMaskBitmap;
  }

A little lesson learn from Java

A post about good books, language design and JIT compilation, in which one bug turns into another and than back……

Recently I started looking through an excellent book “Java™ Puzzlers”, where Joshua Bloch and Neal Gafter provide a list of Java’s “Traps, Pitfalls, and Corner Cases”, i.e. programs that make you think they do what they really don’t. My idea is to see how many of the puzzlers are ruled out or fixed by Kotlin. I’ve looked through the first 24 items, and 15 of them are fixed in Kotlin, which is over 60%.

Some of the puzzlers can’t be fixed without severe implications on compatibility with the rest of the world. For example, most of the tricky things about IEEE-745 floating-point numbers, But some other ones, though not fixed in Kotlin yet, may be fixed. One particular example is Puzzier 26 “In the Loop”:

/**
 * Bloch, Joshua; Gofter, Neal (2005-06-24).
 * Java™ Puzzlers: Traps, Pitfalls, and Corner Cases (p. 57).
 * Pearson Education (USA). Kindle Edition.
 */
public class InTheLoop{
    public static final int END = Integer.MAX_VALUE;
    public static final int START = END - 100;

    public static void main(String[] args){
        int count = 0;
        for(int i = START; i <= END; i++){
            count++;
        }

        System.out.println(count);
    }
}

Don’t read further until you figure what this program prints.

This program prints nothing and simply loops forever, because variable ‘i’ is of type int, and ANY int is less or equal than Integer.MAX_INT.

Now, if I write this in Kotlin:

val end = Integer.MAX_VALUE
val start = end - 100
var count = 0
for(i in start..end){
    count++
}

println(count)

It does Not loop. And prints “101”, which is the size fo the range of iteration……

This is the point where you think: “Didn’t he say that this puzzler is not yet fixed by Kotlin?” Yes, I did.

This Kotlin program SHOULD loop forever. And it does not. Sigh. I have already opened the “New issue” dialog in our tracker when I got too curious and looked at the code our compiler emits. You know what? I found nothing bad there. Written in Java (I am your honest decompiler today), it would look like this:

int end = Integer.MAX_VALUE;
int start = end - 100;
int count = 0;
for(int i = start; i <= end; i++){
    count++;
}

System.out.println("count = " + count);

And this TERMINATES and prints “101”. That’s where I really got puzzled.

After some experimentation, I discovered that making variable ‘end’ final makes the program loop forever. “It must be JIT,” — I though, and was right: when run with “java -Xint”, this code loops forever, and so does the Kotlin code.

How come? Well, I run a 64-bit JVM. Most likely, JIT optimizer makes the loop variable 64-bit, because registers are this big, or something like this, and it doese not overflow, but just becomes Integer.MAX_VALUE + 1.

Sigh. I closed our “New issue” dialog, and opened the HotSpot’s one…… (Some technicalities prevent me from finishing the reporting process right now, but I will do it on Monday).

Now, what lesson can we learn from this? I don’t think I can learn much from hitting a JIT bug. Bugs happen — that’s the lesson here, I think.

But what about the initial puzzler? I teach Pascal at high school, and one thing I really like about this laguage is that a for loop ALWAYS TERMINATES there. We cannot have the same in Kotlin, because in general for uses an iterator that may have arbitrary logic in it. But what we can do is guarantee that iteration over a range of numbers always terminates.

BTW, if a range was just a list of numbers, the loops would terminate, right? So it IS a bug in the Kotlin compiler, after all.

DSLs in Kotlin: Part 1. What’s in the toolbox + Builders

If you have a very nice API, it is the fashion nowadays to call it an internal DSL, because the code that uses such an API reads almost like a language inside your language of choise. Fluent interfaces serve as one of the most popular examples.

Many modern languages provide some advanced means for creating internal DSLs, and Kotlin is no exception here. In this post I will briefly list the features that are useful for this purpose.

Let’s start with extension functions. We all are familiar with Java’s utility classes, like java.util.Collections and like. Such classes are simply containers for a bunch of static methods, which are intended to be used with such and such classes. So we end up writing code like this:

Collection.sort(list);
int index = Collections.binarySearch(list, x);

and this does not look very pretty. Static imports make it prettier, but they don’t solve an important problem of discoverability: we all navigate through APIs with IDE’s code completion capability:

And wouldn’t it be cool to discover those utility functions the same way? So we have extension functions that are called in the form “a.foo()” even if foo() is not a member of the class of a. For example, those utility functions from Collections could be defined as extension functions, and be called like this:

list.sort();
val index = list.binarySearch(x);

These are still statically dispatched utility functions, i.e. the bytecode emitted by the compiler is the same as in Java, but the syntax is better, and code completion works. Note that, unlike members, extension functions cannot be overridden in subclasses, i.e. some special implementation of List could not override sort() to be more efficient.

To define an extension function, we just put a receiver type in front of its name:

fun <T : Comparable<T>> List<T>.sort(){
    Collections.sort(this);
}

Note that I can use a ‘this’ reference that represents my receiver object. See more here.

Now, what do extension functions give us, DSL creators? First of all you can turn any interface into a fluent one. For example, let’s create a new buffered reader with a given charset:

val reader = FileInputStream("mytext.txt").buffered().reader("utf-8")

is it a special class I wrote to be able to get this? No. It’s only two functions:

fun InputStream.buffered() = BufferedInputStream(this);
fun InputStream.reader(charset : String) = InputStreamReader(this, charset)

Then, they play very well together with operator overloading: in Kotlin, most operators, such as plus, minus and so on, are compiled by convention to named function calls. For example, when I say “a + b”, Kotlin reads “a.plus(b)” (see more in our docs). This means that that by adding an extension function named “plus” to my type I can have a binary ‘+’ working on it. For example, I could make my own ‘+’ for list concatenation:

fun List.plus(other : List) : List{
    val result = ArrayList(this)
    result.addAll(other)
    return result
}

And call it like this:

val l1 = list(1, 2, 3)
val l2 = list(4, 5, 6)
val l3 = l1 + l2 // a new list of length 6 is created

And there’s more: since indexation is compiled to calls of get() and set() functions, we can have pretty sublists (or “slices”) that look like this:

val sublist = list[a..b]

By defining an extension function get() on a list:

fun <T> List<T>.get(range : IntRange<Int>) : List<T> 
    = subList(range.start, range.end)

Infix function calls add more on top of that, because you can say, for example

it hasPrivilege WRITE

instead of

it.hasPrivilege(WRITE)

And, of course< you get a whole lot of fun with higher-order functions and function literals (i.e. “closures”). For example, check this out:

lock (myLock){
  // Do something
}

Is this a built-in construct, like Java’s synchronized section? No, it’s a function call. It uses a very handy convention: you can pass the last function literal outside the parentheses you put around your argument list. So this call is the same as “lock(myLock, {……})”, but looks prettier.

More about this example can be found here.

There’s one other nice convention that makes something very close to LINQ possible:

users
    .filter{ it hasPrivilege WRITE }
    .map{ it => it.fulName }
    .orderBy{ lastName }

The convention is: If a function with only one parameter is expected, the parameter declaration may be omitted, and the default name ‘it’ will be used. I.e. “filter {it.foo()}” is the same as “filter {it => it.foo()}”.

And finally, if we put all this (and just a tiny little bit more) together, we can get something really nice. Look at this code:

html {
    head {
        title { + "XML encoding with Kotlin" }
    }

    body {
      h1 { + "XML encoding with Kotlin" }
      p { + "this format is now type-safe" }

      /* an element with attributes and text content */
      a(href = "http://jetbrains.com/kotlin") { + "Kotlin" }
    }
}

Is it Groovy? No, it’s Kotlin, and unlike Groovy, it’s statically typed. Yes, we can do builders like Groovy, but better. I added a detailed explanation of this example to our wiki; you find it here.

Multiple Inheritance Part 2: Possible directions

In the previous post in this series(系列) we discussed the disadvantages of the inheritance(继承) model we initially planned for Kotlin. Today we will talk about alternative designs.

Note that these posts are intended to provoke(激起) a discussion, so that we can benefit(有益于) from your feedback and come up with a better design.

What’s out there

The previous post concluded with the following (incomplete) list of solutions to the problem of multiple inheritance available in other languages:

  • Java and C# have classes and interfaces, i.e. multiple interface inheritance and single implementation inheritance;
  • Scala has classes and traits(特性) that may implement methods and even have state, but their constructors can not have parameters;
  • Some other languages, like Fortress, do not allow state in traits;
  • <Your favorite language here>

We all know that Java’s approach is rock-solid(坚如磐石), but imposes severe limitations on code reuse, so we would like to relax these limitations, but without getting ourselves into trouble. “First degree” of relaxing the limitations would be stateless traits (like in Fortress, and in [1]): no state in traits, no implicit overrides. Or we can trade inheritance of traits off for state and get mixins (like in Ruby). Relaxing the limitations even more we get to Scala’s traits that have state but no parameters for constructors, and one trait may override functions of another. Then we get to CZ’s classes with requires (as presented in [2]). The next step, I guess, would already be unrestricted multiple inheritance, like in C++.

We will skip a thorough analysis of each of these solutions, and just make a remark about state.

State. One important consideration is whether to allow multiple inheritance of state in this or that form. On the one hand, it seems to be very useful, but on the other hand, it imposes problems. One problem was discussed in the previous post under the name of Problem 2:

the implementation of Left assumes it’s initialized with 3, but it may call bar() that is implemented in Right and assumes everything is initialized with 4. This may cause some inconsistent behavior.

Another problem is that having state in a unit of inheritance (a class or trait or mixin) implies having a constructor there. and constructors may have side effects, and it’s important that those come in a predictable order.

Problem 2 is rather elegantly fixed by the Scala’s approach of having no parameters in the trait constructors. Unfortunately, the problem of constructor side-effects still stands: changing inheritance relations between traits (e.g. done by a library-writer) may reorder side-effects of their constructors upon creating a subclass instance (see this comment below). And this problem seems to be inevitable no matte what approach to multiple inheritance of state we choose (I wish someone could prove me wrong here!).

All that said, I’ll explain a design we are currently considering. As mentioned above, the purpose of this text is to start a discussion, so your feedback is very welcome.

The Kotlin way(Attempt #2)

First, I would like to note that at this point, we prefer conservative solutions, so that we could naturally extend them later if the set of features they provide is not enough.

In short, the current design can be described as follows:

  • Stateless traits: no properties with backing fields, no constructors,
  • that can “require” classes to be present in the set of supertypes of a concrete class that uses the trait,
  • with no automatic resolution for overriding conflicts: if a class inherits two implementations of something, it must override this something and provide its own implementation (i.e., choose from the inherited ones, or write its own from scratch, or mix the two).

So, we refrain from having multiple inheritance of state for now. I think it’s OK if we try it this way and consider relaxing the limitations later, if there’s a real demand for that.

Syntax. Now, let’s render this in some syntax. First question here is “Should we still call those stateless guys classes, or have a special term?” They differ from classes by imposing some limitations, and for now it is that they don’t have any state. If there are only classes, the user will fall into the following situation:

  • Nothing tells me that this class is special, so
  • I add a piece of state, and
  • the compiler complains about having no constructor, so
  • I simply add a constructor, and
  • get errors from some other classes telling me that I broke someone’s supertype lists, so
  • it takes some time before I track down the real cause of the error, which is no good.

It would be a lot better if I knew that this class bears some restrictions in the first place, so I wouldn’t make any changes blindly, and if I make a bad change, the compiler would know that I have violated a local restriction, and would complain appropriately. So, it’s better to have traits differ syntactically from unrestricted classes. So, let’s have a keyword trait in font of the declaration, rather than class.

So, we have classes (state and all, but single inheritance) and traits (no state, no constructor, but multiple inheritance). Traits can declare (“require”, is CZ terms) one superclass, but not initialize it:

open class MyClass(){
    fun foo(){……}
}

trait MyTrait : MyClass {// MyClass is not initialized
    fun bar() {
      foo()// calls foo() from MyClass
    }
}

class Child : MyClass(), MyTrait{// MyClass is initialized

}

class ChildErr : MyTrait{// ERROR: MyClass must be a supertype

}

This allows traits to use members of a base class without interfering with the initialization logic.

One other syntactic issue is whether we should have a single homogenous supertype list (like in the example above) or something like Java’s “extends” and “implements” clause, or even Scala’s “extends Class with Trait1 with Trait2 with Trait3” syntax. The idea of making things explicit speaks for some syntactic separation of a class in the supertype list, for it is privileged in some way, i.e. having something like Java’s “extends” and “implements”, at least. On the other hand, we all know this annoying case in Java, when I turn an interface into an abstract class, and have to change all those subclasses that used to implement the interface, and now must extend the class. The change that could be syntactically local becomes non-local. This is why we’re inclined to have a homogenous supertype list, as in the example above.

Using traits

Now, we prohibit state in traits. It certainly is a significant limitation, but I would like to point out what it is not.

You CAN have properties in your traits. The limitation is that those properties can not have backing fields or initializers, but properties themselves may appear in traits:

trait Trait {
    val property : Int // abstract

    fun foo(){
      print(property)
    }
}

class C() : Trait{
    override val property : Int = 239
}

Our trait declares an abstract property, and the class overrides it with a stateful one. Now, the trait can use the property, and by late binding of calls, if we call foo() on an object of C, we get 239 printed.

You CAN access state in your traits. The previous example shows how you can do it sort of indirectly, by making subclasses override a property you define, but there is another way. Remember that a trait may declare (require) a superclasee:

open class A(x : Int){
    val y = x * 2
}

trait B : A {
    fun foo(){
        print(y)
    }
}

class C() : A(239), B{}

In this example, we have a base class A, that defines a concrete property y and initializes it. The trait B extends this class, but dose not pass a constructor parameter in, because traits have no initialization code at all, Note that B has access to the property y defined in A. Now, class C extends A and initializes it with 239, and extends B. Extending B is OK because B requires A, and we extend A, all right.

Now, what happens when we call foo() on an instance of C? It prints 478 (239*2), because the value of y is obtained from this instance, and the constructor of C has written 239 there.

Now, let’s look at the last example about traits:

How to resolve overriding conflicts. When we declare many types in out supertype list, it may appear that we inherit more than one implementation of the same method. For example:

trait A {
  fun foo() { print("A") }
  fun bar()
}

trait B {
  fun foo() { print("B") }
  fun bar() { print("bar") }
}

class C() : A {
  override fun bar() { print("bar") }
}

class D() : A, B {
  override fun foo(){
    super<A>.foo()
    super<B>.foo()
  }
}

Traits A and B both declare functions foo() and bar(). Both of them implement foo(), but only B implements bar() (bar() is not marked abstract in A, because this is the default for traits, if the function has no body). Now, if we derive a concrete class C from A, we, obviously, have to override bar(), because we have inherited only one implementation of it. But we have inherited two implementations of foo(), so the compiler does not know, which one to choose, and forces us to override foo() and say what we want explicitly.

I think, it’s enough for today. Your comments are very welcome, as usual.

Multiple Inheritance Part 1: Problems with the existing design

I’m back from my vacation, and it’s time to get to one one the biggest issues pointed out in the feedback we received during conference presentations and in the comments to the docs. I’m talking about inheritance.

I plan to write a series of posts on this topic. These posts are intended to provoke a discussion, so that we can benefit from your feedback and come up with a better design.

This is the first post in the series, and I discuss the design we presented in July 2011. It features the following approach to inheritance:

  • there were no interfaces, only classes;
  • each class could have multiple superclasses;
  • if some non-abstract member (property or method) was inherited from two of the supertypes, the compiler required the user to override it and specify manually what code to run.

(For more details, see our wiki as of July 20th 2011.)

This is, basically, the infamous multiple inheritance story, and we remember from the C++ times that it is sort of bad. Let’s look closer.

It’s all about initialization

Let’s a look at the following example:

abstract class Base(x : Int){……}

open class Left(x : Int) : Base(x) {……}
open class Right(x : Int) : Base(x) {……}

class Child : Left(3), Right(4) {……}

So, we have a diamond: Base at the top, Left and Right on the sides, and Child at the bottom. One thing looks suspicious here: Child initializes its superclasses passing different numbers two them: 3 to Left and 4 to right. Now, they in turn, initialize Base with those numbers…… What is Base initialized with?

Actually, there are two “instances” of Base created: one, initialized with 3, is hidden inside Left(3), and another, initialized with 4 — inside Right(4). I.e. it works like non-virtual inheritance in C++. (On the Java platform, we implemented it by delegation, which is invisible for the user.)

Now, what happens when you call a function that is defined in Base? For example, let’s say that Base defines two abstract functions:

abstract class Base(x : Int){
fun foo()
fun bar()
}

Now,let Left override foo() and Right override Bar:

open class Left(x : Int) : Base(x){
override fun foo() { print(x) }
}

open class Right(x : Int) : Base(x){
override fun bar() { print(x) }
}

In this case Child inherits two declarations of foo() and two declarations bar(), but at the same time it inherits only one implementation for each of these functions, so it’s OK, the behavior is determined. So, when we say

val c = Child(0)
c.foo()
c.bar()

The output is

4

Because foo() was called for Left, and bar() was called for Right.

If Child inherited more than one implementation of, say, foo(), the compiler would have complained until we override foo() in Child and specify the behavior explicitly. So, we are guaranteed to have no ambiguity when calling functions of Child.

So far, so good, but there still is something wrong with this approach……

Problem 1: the constructor for Base is called twice whenever we create an instance of Child. It’s bad because if it has side-effects, they are duplicated, and the author of the Child class may not know about it, because someone change the inheritance graph turning it into a diamond that was not there before.

Problem 2: the implementation of Left assumes it’s initialized with 3, but it may call bar() that is implemented in Right and assumes everything is initialized with 4. This may cause some inconsistent behavior.

Problem 3: being implemented by delegation, deep hierarchies will degrade performance by having long delegation chains.

(Im)Possible ways of fixing it

Now, how can we fix our design? C++ copes with Problem 1 and 3 by having virtual inheritance. On the Java platform and with separate compilation in mind, I do not think we can get rid of delegation when a class inherits state from two sources, so the Problem 3 stands for us anyway. And having two flavors of inheritance is no good, as we learned from c++……

Virtual inheritance does not fix Problem 2: being initialized differently, parts of the inherited implementation may make inconsistent assumptions about the overall state of the object. This problem seems intractable in the general case, but let’s be accurate and make sure it really is.

We could try to guarantee that everything is initialized consistently. In the general case, when we pass arbitrary expressions to Left and Right, there’s no way to be sure they yield same results, even if they are textually the same. Then, we could impose some constraints here. For example: only allow to pass compile-time constants or immutable variables to superclass constructors. In this case the compiler could examine the whole class hierarchy and make sure every base class is initialized consistently. There is a problem, though: if one of the superclasses change its initialization logic even slightly, subclasses may become inconsistent, so this will be a big evolution problem, for example, for libraries.

And, of course, it would be too restrictive to impose those constraints on all classes. So we end up with two flavors of classes……

Well, it seems that “there are only classes(i.e. no interfaces or alike)” approach did not work out. Now, it’s time to consider other approaches.

What’s out there

Different languages manage multiple inheritance differently, and I summarize some of the approaches here.

  • Java and C# have classes and interfaces, i.e. multiple interface inheritance and single implementation inheritance;
  • Scala has classes and traits that may implement methods and even have state, but their constructors can not have parameters;
  • Some other languages, like Fortress, do not allow state in traits;
  • <Your favorite language here>

In the next post of this series we will discuss the options in detail.

And now it’s time for your comments. They are very welcome.

The Kotlin issue tracker is now public

Following the tradition of other JetBrains projects, we’ve opened up the issue tracker for Kotlin to the public. In the issue tracker, you can see some of our thinking and things we’re working on, and you can also file issues asking for new features in the language or changes in the current design. We hope that the tracker will let us keep the discussion more structured than the comments in the blog and on Confluence pages.

Why JetBrains needs Kotlin

The question of motivation(动机) is one of the first asked when someone learns that someone else is working on a new programming language. Kotlin documentation offers a fairly(详细) detailed overview of why the language exists. Still, we would like to make it clearer what exactly(究竟) JetBrains expects to gain from the whole endeavor(努力). We’re obviously(明显的) in it for the long run, and yes, we realize(认识到) it will take years to reach(达到) our goals. And here’s why we are willing to make this investment(投资).
[collapse title=”翻译”] 当人们得知另一些人在研究一个新编程语言时,第一个被问到的问题就是动机。在Kotlin的文档中,对这种语言为什么存在提供了非常详细概述。不过,我们想弄清楚的是JetBrains究竟想通过整个努力得到什么。显然我们需要一个长时间的努力,是的,我们也意识到要达到我们的目标可能需要很多年。这就是我们为什么要进行这样的投资。
[/collapse]

First and foremost(最重要的), it’s about our own productivity(生产力). Although we’ve developed support for several JVM-targeted programming languages, we are still writing all of our IntelliJ-based IDEs almost entirely(全部地) in Java. The IntelliJ build system is based on Groovy and Gant, some Groovy is also used for tests, there is some JRuby code in RubyMine, and that’s it. We want to become more productive by switching to a more expressive(表现力) language. At the same time(同时), we cannot accept compromises(妥协,折中方法) in terms(条款,地位) of either Java interoperability(互用性) (the new language is going to be introduced gradually(逐步地,渐渐地), and needs to interoperate smoothly(平稳的,平滑的) with the existing code base) or compilation(编辑) speed (our code base takes long enough to compile with javac, and we cannot afford(提供) making it any slower).
[collapse title=”翻译”] 首先也是最重要的,就是我们的生产力。虽然我已经开发了几种针对JVM的编程语言,而且我们仍然几乎完全使用Java写我们的基于IntelliJ的所有IDE。IntelliJ编译系统是基于Groovy和Gant,Groovy也被用于测试,在RubyMine里有一些JRuby代码,就是这样。我们想切换到一种更具表现力的语言来提高效率。同时,我们不能在与Java的交互方面(新语言在逐渐的被引入的过程中,和现有的基础代码的交互需要平稳)和编译速度方面(我们的基础代码使用javac编译时需要很长时间,我们不能让它再慢了)进行妥协.
注:in terms of 在……方面
[/collapse]

The next thing is also fairly straightforward: we expect Kotlin to drive the sales of IntelliJ IDEA. We’re wording on a new language, but we do not plan to replace the entire ecosystem of libraries that have been built for the JVM. So you’re likely to keep using Spring and Hibernate, or other similar frameworks, in your projects built with Kotlin. And while the development tools for Kotlin itself are going to be free and open-source, the support for the enterprise development frameworks and tools will remain part of IntelliJ IDEA Ultimate, the commercial version of the IDE. And of course the framework support will be fully integrated with Kotlin.

The final point is less obvious but still important: new programming languages is a topic that many people really enjoy talking about, and the first days that have passed since we’ve unveiled Kotlin prove that. We see that people who are already familiar with JetBrains trust the company to be able to do a good job with this project. Thus, we believe that this trust and the increasing community awareness of JetBrains will not only drive the company’s business, but will attract even more people to our approach to building development tolls, and let them Develop with Pleasure.

And we’d like to reiterate that our work on Kotlin does not in any way affect our investment into other development tools, and in particular the Scala plugin. If you’re already happy with Scala and have no need for another new language, we’ll continue to do our best providing you with first-class Scala development tooling.

Hello World

Today at the JVM Language Summit, JetBrains is unveiling the new project we’ve been working on for almost a year now. The project is Kotlin, a new statically typed programming language for the JVM.

With Kotlin, we’re building upon the many years of experience creating development tools for different languages, and hoping to provide a language which is productive enough for today’s environment and at the same time simple enough for the ordinary programmer to learn.

Right now Kotlin is under active development and now here near mature enought to be used by anyone outside of the development team. What you can do today is read the language documentation and leave feedback on the design of the language — what features you like, which ones are missing, what’s confusing and so on.

One thing to note: since we’re a development tools company, we’re building first-class IDE support for Kotlin in parallel with the language itself. And as soon as the language reaches its beta stage (currently planned for the end of 2011), we’ll release both the complier and the development tools as open-source under the Apache 2 license.

There’s still a huge amount of work ahead of us, and we’re excited to hear what you guys think about our latest eneavor. So let the discussions begin!

为什么说Volley适合数据量小,通信频繁的网络操作

为什么说Volley适合数据量小,通信频繁的网络操作

前言

网络编程对于客户端来说是一块及其重要的地方,使用高效的网络请求框架将为你的系统产生很大的影响。而Volley作为谷歌的一个开源项目,炙手可热。有很多中小型公司的安卓移动客户端的网络程序都是基于volley的。
Volley的优点很多,光可扩展性这一条优点就值得我们称赞。但是我想针对的是在 Google I/O 2013 大会上发布Volley的时候的一句话:a burst or emission of many things or a large amount at once(爆炸性的事件在很短的时间内发生),意思就是:Volley特别适合数据量小,通信量大的客户端。同为网络请求框架,为什么Volley会有这样的特点?接下来,我就用我的理解来解释一下。
ByteArrayPool
ByteArrayPool产生背景

根据类名,知道这是一个字节数组缓存池。没错,就是用来缓存 网络请求获得的数据。
当网络请求得到返回数据以后,我们需要在内存开辟出一块区域来存放我们得到的网络数据,不论是json还是图片,都会存在于内存的某一块区域,然后拿到UI显示,然而客户端请求是相当频繁的操作,想一下我们平时使用知乎等一些客户端,几乎每一个操作都要进行网络请求(虽然知乎大部分是WebView)。那么问题来了:这么频繁的数据请求,获得数据以后我们先要在堆内存开辟存储空间,然后显示,等到时机成熟,GC回收这块区域,如此往复,那么GC的负担就相当的重,然而Android客户端处理能力有限,频繁GC对客户端的性能有直接影响。我们能不能减少GC和内存的分配呢?我想这个问题就是这个类的产生背景。
实现原理(怎么实现缓存从而减少GC)

在ByteArrayPool中维护了两个List集合。
属性名 作用 类型
mBuffersByLastUse 按照最近使用对byte[]排序 LinkedList
mBuffersBySize 按照byte[]大小对byte[]排序 ArrayList

通过上述两个属性的作用可以分析出它们是ByteArrayPool类对byte[]的管理。
从缓冲区取空间

当请求数据返回以后,我们不是急于在内存开辟空间,而是从ByteArrayPool中取出一块已经分配的内存区域。此时会调用ByteArrayPool的getBuf(int)方法,来得到一块参数大小的区域,源码如下:

 public synchronized byte[] getBuf(int len) {
for (int i = 0; i < mBuffersBySize.size(); i++) {
byte[] buf = mBuffersBySize.get(i);
if (buf.length >= len) {
mCurrentSize -= buf.length;
mBuffersBySize.remove(i);
mBuffersByLastUse.remove(buf);
return buf;
}
}
return new byte[len];
}

方法的第2行代码,遍历mBuffersBySize,找到最适合len大小的byte[]。第6 ~8行更新缓存池中数据的大小,并从两个数组中去除分配出去的byte[]。如果在缓存池中没有要求的byte[],此时会从内存分配一跨区域返回。
此方法主要的功能: 不必每次存数据都要进行内存分配,而是先查找缓冲池中有无适合的内存区域,如果有,直接拿来用,从而减少内存分配的次数。
其实这个方法有改进空间:由于在类中有一个mSizeLimit属性,表示此缓冲区的最大值。我们可以在方法体的第一行判断 len与mSizeLimit的大小,如否 len>mSizeLimit,直接进入到最后一句运行,否则,循环。修改后的方法如下:

public synchronized byte[] getBuf(int len) {
//有的条件不需循环
if(len<=mSizeLimit)
{
for (int i = 0; i < mBuffersBySize.size(); i++) {
byte[] buf = mBuffersBySize.get(i);
if (buf.length >= len) {
mCurrentSize -= buf.length;
mBuffersBySize.remove(i);
mBuffersByLastUse.remove(buf);
return buf;
}
}
}
return new byte[len];
}

将空间返回给缓存池

如果只是拿数据,缓存区的只会越来越小,我们还需要向缓冲区中加入存储空间。这个时候涉及到一个方法:returnBuf(byte[])。

 public synchronized void returnBuf(byte[] buf) {
if (buf == null || buf.length > mSizeLimit) {
return;
}
mBuffersByLastUse.add(buf);
int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
if (pos < 0) {
pos = -pos - 1;
}
mBuffersBySize.add(pos, buf);
mCurrentSize += buf.length;
trim();
}

方法首先检查 要插入的数据大小有没有超出边界,如果没有,利用二分法找到插入位置,将数据插入到上述的两个集合,完成排序。然后更新缓冲池的大小,以方便从缓冲区中取存储空间。

结语

ByteArrayPool利用getBuf和returnBuf以及mBuffersByLastUse和mBuffersBySize完成字节数组的缓存。当需要使内存区域的时候,先从已经分配的区域中获得以减少内存分配次数。当空间用完以后,在将数据返回给此缓冲区。这样,就会减少内存区域堆内存的波动和减少GC的回收,让CPU把更多的性能留给页面的渲染,提高性能。通过这个类发现,谷歌对技术的细节十分考究。