分类目录归档:读书笔记

Java里的异常

 

前言:最近都在准备考试复习都木有这么学习新姿势,临时找了以前看书的记录凑出了这个文章,太罪过了><

感觉自己目前写的程序都是编译器在强制自己要做异常处理,对于怎么样编写优秀的异常处理程序还不算很清楚,下面先整理一下Thinking in java里看到的关于异常的知识吧。异常处理的原则是”只有你知道如何处理的情况下才捕获异常”,其中一个重要目标是将错误处理代码和错误发生地点分离。

分类:分为被检查异常(checked)和不被检查异常(unchecked)。这里的检查指的是否进行编译时期的强制检查。

重新抛出对象fillStackTrace的用法;异常出现后异常对象会被压栈,我们可以通过打印栈里的信息,来获取发生异常的函数调用情况,栈底的元素是调用序列里的第一个方法。这里要注意异常重新抛出的一个问题,有时候我们捕获一个异常后我们并不希望在这处理,只是想像上一层再次抛出,比如像catch(Exception e ) {

throw e; // rethrow the exception

}这样重新抛出的话,若用printStackTrace打印栈元素的话,只会显示最原始抛出异常点的信息而不包括重新抛出点的位置,若要把重新抛出异常的信息也要加入栈里,需要调用fillInStackTrace这个方法把信息压入。

finally的注意和特别用法。finally作为try/catch机制里最后进行处理的措施,就算try里遇到了break和return,finally也还是会执行的。

继承对异常的影响。若子类继承了父类的某个方法,子类可以不抛出父类规定的异常。子类要覆写(Override)父类的某个方法,若原方法定义了某个异常,子类的方法里不能再给这个方法抛出更多异常。

切勿“吞食”掉异常。写异常有时候的确很烦,特别遇到有很多异常嵌套的时候,我们为了应付编译器就只写了一个try/catch在那或者简单的throw出去,这个异常就被吞食了,这不是一个好习惯。最次我们也要把异常打印出来提醒,或者我不知道这里遇到异常应该怎么处理,我就抛出一个RuntimeException结束程序把。

Java的RTTI和反射(一)

什么是RTTI?

RTTI的全称是RunTime Type Information的缩写 ,即可以在程序的运行时发现和使用类型信息。在Java里想要在运行时里获得类型的信息有两种方法,一是传统的RTTI即假定我们在编译时已经知道了所有类型,另一种是反射机制,它允许我们在运行时获得类型信息。

为什么需要RTTI?

面向对象编程中的一个基本目的是,让代码只操控一个对基类的引用,这样在添加新类型也能保证不会影响到原来的代码,且增强可读性。比如你有一个Shape类它里面有一个draw()方法,它有三个派生类Circle,Square,Triangle分别覆写了draw方法。

public class Shapes{
    public static void main(String[] args){
        List<Shape> list = Arrays.asList(new Circle,
        new Square,new Triangle());
        for(Shape shape : list)
            shape.draw();
    }
}

但是这样在实际编程的时候经常会有这样的问题,比如我想调用每个子类特有的方法呢?比如Square和Triangle有rotate这个方法,而这个方法Circle没有,这个时候我们就是需要在运行时获得这个shape对象的具体类型信息,来决定是否可以使用某些特定的方法。

Class对象

根据Java的原理,你应该知道编译Java程序的时候,每个你写的类都会编译产生xxx.class文件.如下图:

class

 

这是Java里一个非常强大的特性,Java不像C++这种静态加载语言(即运行前已经加载了所有类型信息),Java则是在开始运行前并非完全加载的,只有在需要对应的类型时才动态加载的。JVM里有一个叫ClassLoader的系统,它会首先检查要用的这个类是否已经被加载,若未加载则会搜索对应的.class文件进行加载。

Java里有Class类,要获得运行时的类型信息,你需要获得它对应的Class对象的应用.

通常有三种方法来获取对应的Class对象引用:

  • 知道类型的具体名字:

Class.forName(String className),这里的className必须是全限定名(也就是相当于文件的据对路径一样,要包含对应的包名才行)。如果用于forName的这个对象没有被加载,就会被自动加载并初始化

  • 有一个具体对象

object.getClass()即可返回对应的Class

  • 类的字面常量

可以用Class myClass = Test.class来直接创建一个Class引用,注意这里和Class.forName()有一个巨大的不同,这里创建的Class引用并不会自动的初始化该对象。并且这种方式创建还有很多好处,这样会使代码更加简洁(因为用Class.forName()可以会抛出ClassNotFoundException,所以它必须写在try/catch里或者抛出异常),而且更安全,因为这样写编译器在编译就可以对其进行检查。

Class类一些常用的方法介绍

  • 获取类型名字

有getName(),getCanonicalName(),getSimpleName()

  • 创建对应类的对象

Class.newInstance()就可以产生对应的对象,注意这里可能会抛出两种异常,一个是IllegalAcessException和InstantiationException,前者主要是可能因为违反了Java里的安全机制如,该类的构筑函数为private。

  • 检查是否是某一类型

有三种方法,一是使用关键字 instanceOf,如可以用 if (circle instanceOf Shape );二是使用Class.isInstance(Object obj)来检查;三是使用xxx.getClass() == xxx.class 来比较检查。在这里一二两种方法完全等价,但是和第三种有巨大不同,一二是相当于问“你是这个类吗,你是这个类的派生类吗”,而方法三就是只最对具体确切的那一个class。

好了,下次再写反射

Singleton模式

简述:构造的这种类型,它只能有一个对象。

这样做的优点是,有时候重复生成多个对象,但其实只需要用一个就够了。重复生成浪费资源。

实现Singleton有以下几种方法:

  1. 让构造函数设为private,在类里建立一个为public static final的对象
    public class  Sprite {
        public static final Sprite INSTANCE = new Sprite();
    
        private Sprite() {……}
    	
    }
  2.  让构造函数设置为private,让类里这个对象的实例private,设置一个public static的方法可以获取这个对象(这里用的就是static factory模式)
    public class  Sprite {
        private final Sprite INSTANCE = new Sprite();
    
        private Sprite() {……}
    
        public static Sprite getInstance() { return INSTANCE; }
    }

以上的两种方法有局限性,就是他们都是通过设置private来达到Singleton化的,而private的构造函数在一些反射方法里可以被特殊的调用。还有在Serializable(序列化)的时候比较麻烦,每当解析一个序列化的时候这个时候会产生一个新的对象,这样就造成我们之前说的唯一一个实例的初衷,这里的解决方案是复写Serialzable的readResolve方法,来继续维护Singleton。

Effect Java的作者提到了,最好的实现Singleton的方法,应该是使用枚举。

public enum Singleton {
    INSTANCE;
    
    // other metho
    public void execute () {
        // Perform operation here 
    }
}

使用的时候只要Singleton.INSTANCE就好,非常的简介优雅。这个方法是利用了Java确保枚举类型在程序里只初始化一次来实现的。

下面再介绍一下,在Android下一种实现Singleton模式的方法。最简单的是通过继承Application实现,这也是利用Android保证了一个app一个Application的实例的特点实现的。下面代码是把Volley里的RequestQueue单例化:

public class MyApplication extends Application{
    private static RequestQueue mRequestQueue;
    public static synchronized MyApplication getInstance(){
        return mInstance;
    }
     public static RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getInstance());
        }
        return mRequestQueue;
    }
}

 

QQ截图20150324172339

控制随机——产生不同概率的事件

本文将对产生不同概率的方法和高斯分布的使用做总结。

在实际情况中我们有时候不光只是要随机,有时候还要不同概率大小情况的下的随机事件,比如发生A事件的几率是30%,B事件是40%,等等.实现它有多种方法,最只觉得想法的就是在一个数组里填充几种元素,通过控制各元素的个数来控制概率,比如我要A事件发生的概率为40%,B事件发生的概率为60%.可以造一个长度为10的boolean数组,里面4个设置为true,另外6个位false,然后随机从里面抽一个出来,true就发生事件A,false就发生事件B。代码如下:

boolean array = new boolean[10];
array[0] = true;
...
array[3] = true;

if (array[random(0,10)]){
    triggerA();
}else{
    triggerB();
}

这个方法的弊端是当有多样元素的填充很麻烦,并且浪费不必要的空间。

另一个办法,这个办法更简单:

比如我想要一个概率为60%的A事件,发生概率为20%的B事件,20%的C事件代码如下:

float p = 0.6;
float rand = random(1); // return a number betwenn 0~1
if( rand < p){
    triggerA();
}else if ( p < rand < 0.8){
    triggerB();
} else {
    triggerC();
}

 

有时候我们需要概率随着一定规律变化,比如从1~100中选择数字,要求实现数字越大选中的概率越大,可以用二次随机的方法(这种方法也叫Monte Carlo method)。

下面的例子返回一个0~1的数,概率大小的变化和数字大小成正比 p = value

float montecarlo(){
    while(true){
        float r1 = random(1);
        float p = r1;
        float r2 = random(1);
        if( r2 < p){
            return r1;
        }
    }
}

可以通过修改上面代码很容易得出概率和数字成正比的随机函数的方法.

高斯分布又叫正态分布(本文后续全部使用正态分布这个叫法),这是生活中出现很多的随机分布现象,比如人的身高,财富情况都是正太分布.下面我介绍一下如何把正态分布用于随机中.

要使用正态分布,首先要明白两个参数,mean 和 deviation:

 正态分布mean是中值,也就是数据最集中的那里.deviation是偏差的意思.也就是和中值偏差的大小.有了这两个值就可以用来对高斯分布进行操纵了,偏差越小意味着数据越集中在中值处,偏差越大意味着数据越分散.那么到底在程序中要怎么用正态分布呢?下面用Java里的Random.nextGaussian()为例,它会返回一个mean为0,deviation为1的随机数字.我们做一个在窗口中大小为600的画布上用正态分布画彩色球小的程序:

import java.util.Random;
Random gen;

int r = 0;
int g = 0;
int b = 0;
float x = 0;
float y = 0;

void setup(){
    // 设置屏幕大小为600
  size(600,600);
  gen = new Random();
}
// 这里是循环执行的部分
void draw(){
  float num = (float) gen.nextGaussian();
  float standardDeviation = 40;  // 偏差设置为80
  float mean = 300;             // 中间值设为300,屏幕的中中心
  
  // 随机产生小球的颜色
  r = int(random(255));
  g = int(random(255));
  b = int(random(255));
  fill(r,g,b);
  
  // 这里就是使用状态分布的地方了 偏差 * 正太分布产生的随机数 + 偏差
  // 产生小球的坐标
  x = standardDeviation * num + mean;
  
  num = (float) gen.nextGaussian();
  y = standardDeviation * num + mean;
  // 画球
  ellipse(x,y,15,15);
  
}

ball1

 

这里是偏差为40时的运行截图,注意到因为偏差比较小小球都集中在屏幕中心,也就是中值附近.

当偏差为80时运行截图为:

ball2

 

注意到偏差较大的时候,求不在是特别集中于中值附近,成较分散状.