public在一个文件中只能有一个,能够是一个类class或者一个接口interface >一旦建立一个引用,就但愿它能与一个新的对象相关联: String s = "hello"; String s = new String("hello"); s:遥控器(引用) “hello”:电视机(对象) 数据存储在: 寄存器:最快的存储区,在处理器内部 堆栈:RAM(随机访问存储器),栈底在上栈顶在下,指针向下移动->分配新的内存 堆:一种通用的内存池(RAM),用于存放全部Java对象,当 new 一个对象,代码执行到的时候自动在堆里进行存储分配 常量存储:一般直接存放在程序代码内部 非RAM存储:若是数据彻底存活与程序以外,那它能够不受程序的任何控制,在程序没有运行时也能够存在。例:流对象,持久化对象 记住: new 将对象存储在“堆”中 基本类型:new不是颇有效,不用new建立,非引用的“自动”变量,直接存储“值”于栈内 Java 基本数据类型占用存储空间大小不受机器限制 全部数值类型都有正负号,没有无符号数 byte 1 char 2 short 2 int 4 long 8 float 4 double 8 boolean 所占的存储空间没有明确指定,仅定义为可以取字面值 true 和 false 高精度数字: BigInteger 支持任意精度的整数 BigDecimal 支持任何精度的定点数 引用的空值:null Java 的垃圾回收器存在,使得咱们不须要在用完某一对象后进行手动销毁(释放内存) 类中定义的成员是基本类型会自动初始化为 0 boolean值初始化false 方法中定义的基本变量要手动进行初始化,否则会报:变量没有初始化的 错误 int x; System.out.println(x);//报错:x没有初始化 字符串的length()方法返回的是字符串中字符个数 定义包名(指定名字空间):使用反转的域名,所有小写 import用于导入类库 执行 new 来建立对象时,数据才存储空间才被分配,其方法才供外界调用 static设计的目的: 1、只想为某特定域分配单一存储空间,而不去考虑究竟要建立多少对象,甚至根本不建立对象 2、但愿某个方法不与包含它的类的任何对象关联在一块儿,没有建立对象也能够使用此方法 记住:static是类成员 调用非类方法(没有static修饰)必须使用此类的某一对象 javadoc的使用(注解): 第一行: //:文件路径/文件名 @author @version @classname @description 用于方法文档: @param 参数列表中的标识符 @return 返回值含义 @throws 在别处定义的异常的无歧义名字 @deprecated //表示方法过期,可能在后续版本中废弃 最后一行: ///:~ Java 编码风格:驼峰风格 类名所有单词首字母大写 方法、字段、对象引用名、变量名 第一个单词首字母小写,其余单词首字母大写 操做符中一元减号起到转变数据的符号的做用: -a 就是a的相反数 与、或、非 逻辑运算符 只适用于布尔值 不会像C同样适用于数值 !1 是非法的 java中增长了无符号右移操做符 >>> 空位所有补0 数字的二进制表示形式称为:有符号的二进制补码 int x = 1, y = 2, z = 3; 是合法的 char byte short 在使用算术运算符中数据类型被提高为int 获得的结果是int类型 foreach语句中迭代变量仅在for存活 多个foreach语句内部的迭代变量彻底能够相同 for(int i: iArray){ System.out.println(i); } 在Java里须要使用标签的惟一理由就是由于循环嵌套存在,并且想从多层嵌套中break或continue break label; continue label;
构造器(constructor) 调用构造器是编译器的责任 构造器没有返回值,这与返回值为void彻底不一样的 java中,“初始化”和“建立”捆绑在一块儿,二者不能分离 方法重载:在一个类内能够定义多个含有不一样参数类型列表的同名的方法来实现方法的重载 构造器是典型的例子 默认构造器在类内没有明确指定一个构造器时编译器会自动添加,若是本身提供了构造器编译器就不会在自做多情 this 表示对“调用方法的那个对象”的引用 方法内部调用同一类的另外一个方法,没必要使用 this 直接调用便可 同一类内成员相互使用时,没必要加this 只有当要明确指出对当前对象的引用时才使用this this能够用来调用一个构造器,理解同与super 在构造器中调用构造器 除了构造器,其余地方都不能够调用构造器 static 方法就是没有 this 的方法 static内部不能直接调用非静态方法,反之能够 在代码中出现大量的static方法,就该从新考虑本身的设计了 清理:终极处理和垃圾回收 1、对象可能不被垃圾回收 2、垃圾回收并不等于“析构” 3、垃圾回收只与内存有关 当垃圾回收时调用finalize()方法,finalize()方法不是进行普通的清理工做的合适场所 记住:不管是“垃圾回收”仍是“终结”,都不保证必定会发生。若是Java虚拟机(JVM)并无面临内存耗尽的情形 它是不会浪费时间去执行垃圾回收以恢复内存的 垃圾回收器对于提升对象的建立速度具备明显的效果 “自适应的、分代的、中止-复制、标记-清扫”式垃圾回收器 成员初始化: 类内成员变量初始化默认值为 0 (引用是null) 记住:构造器进行的类成员变量初始化没法阻止自动初始化的进行,它在构造器被调用以前发生 在类的内部,变量定义的前后顺序决定了初始化的顺序,但全部的初始化都优先与构造器内的初始化 静态(static)初始化仅执行一次,静态初始化只在class对象首次加载的时候进行一次 显示的静态初始化: public class Spoon{ static int i; //静态初始化块 static{ i = 1; } } 数组初始化 int[] intArray = {1, 2, 3, }; int[] intArray = new int[]{1, 2, 3, }; int[] intArray = new int[3];//默认初始化为0 可变参数列表 public static void main(String[] args){} public void print(Object... args){} //其中Object能够为具体的类型 args仍旧是一个数组 调用print: print(1, 2, "hello"); print((Object[])new Integer[]{1, 2, 3}); 对一个类的实例直接打印:获得 实例名@地址 枚举类型 enum public enum Months{ JANUARY, FEBRUARY, } 能够与switch完美契合 enum类有 toString()、ordinal()(获取某个特定enmu常量的声明顺序)、static values()(存贮所有enum值的一个数组) for(Months month: Months.values){ System.out.println(month + "," + month.ordinal); }
包 -> 库单元 |package| |... |-package| |... |.java java可运行程序是一组可打包并压缩为一个Java文档文件(Jar包)的.class文件 机器上要配置环境变量 CLASSPATH java解析器能够将报名 com.package1.xlc 中的 . 替换为 / 来构成一个路径名称 对于jar包,路径要包括jar包的具体名 : com/package1/xlc/myjar.jar 导入类内静态方法: import static com.package1.xlc.MyClass.*; 各访问修饰词: 默认:包访问权限 private:你没法访问 protected:继承访问权限 public:接口访问权限 class Sundae{ private Sundae(){} static Sundae makeASundae(){ return new Sundae(); } } 这个类是没法被继承的,并且没法经过 new Sundae();来建立一个对象 能够使用 Sundae sundae = Sundae.makeASundae(); 类访问权限只有两种选择: 接口访问权限(public) 包访问权限(默认、不加修饰) 应用实例: >没法直接建立类的对象、不能用于继承 class Class1{ private Class1(){} public static Class1 makeAClass1(){ return new Class1(); } } >没法直接建立类的对象、只能建立一个实例 class Class2{ private Class2(){} private static Class2 class2 = new Class2();//定义一个Class2类型的私有静态成员并初始化为Class2的一个实例 public static Class2 returnAClass2(){ return class2; } } 记住:相同目录下的全部不具备明确package声明的文件,都被视做是该目录下默认包的一部分
每个非基本类型的对象都有一个 toString() 方法,能够在类内重写它来知足本身的需求 java的OOP:当建立一个类时,老是在继承,除非已经明确指出要从其余类中继承,不然就是在隐式地从java的标准根类 Object 进行继承 在多个类中能够都含有 main() 方法,当命令行 java 类名 此类的main会被调用 一个有用的规则:为了继承,通常的规则是将全部的数据成员都指定为private,全部的方法都指定为public super.fatherMethod() 调用父类的方法 子类的对象在 new 的时候 构建过程是从基类“向外”扩散的,基类在导出类构造器能够访问它以前,已经完成初始化 class Father{ Father(int i){ System.out.println("Father Constructor"); } } class Son extends Father{ Son(int i){ //没有默认的基类构造器,或者想调用一个带参数的基类构造器,必须用关键字 super 显示的调用,要放在子类构造器的首行 super(i); System.out.println("Son Constructor"); } } 代理:将一个成员对象置于所要构造的类中 public class AObject{ private String name; private AObject aobject = new AObject(); public AObject(String name){ this.name = name; } public void aMethod(int i){ aobject.aMethod(i); } } 确保正确清理:首先,执行类的全部特定的清理工做,其顺序同生成顺序相反;而后调用基类的清理方法 is a 是继承 has a 是组合 向上转型:子类实例转型为父类实例 (较专用类型转较通用类型) 经常使用的: 直接将数据和方法包装进一个类,并使用该类的对象 运用组合技术使用现有类来开发新的类 继承技术其实不太经常使用,到底该不应用继承的一个问题:须要重新类向基类进行向上转型么? final: 命名,使用全大写形式,多个单词采用下划线 一个永不改变的编译时常量 一个在运行时被初始化的值,可是不但愿它被改变 final修饰的基本类型值没法改变,修饰一个引用则这个引用的值没法改变,可是指向的对象可能会变 static final 只占据一段不能改变的存储空间(强调一个、不变) final变量必须在使用前进行初始化,此时出现空白final 一个类中的final域能够作到根据对象而有所不一样,却又保持其恒定不变的特性: class Aaa{ private final int i; //每次实例化的时候均可以指定i的值,一旦指定后就没法在修改 Aaa(int i){ this.i = i; } } final参数: void f(final int i){ i++;//错误 i不能够被改变 } void g(final int i){ System.out.println(i);//能够取值 } final方法:确保在继承中使用方法行为保持不变,而且不会被覆盖 继承的时候,“覆盖”只有在某方法是基类的 接口 的一部分时才会出现,private不是基类的接口的一部分 基类中有一个private方法aMethod,导出类继承与此基类而且也有一个aMethod的方法,这里只是新建立一个方法,不存在Override问题 private 修饰的做用:隐藏 finale修饰一个类:目的在于对该类的设计永不须要作任何改动,或者出于安全的考虑,不但愿它有子类(不能被继承) 继承与初始化: 在一个类上运行Java,第一件事就是试图访问此类的 main() 方法 加载器开始启动并找出此类的编译代码(在名为 .class文件内) 在对其加载的过程当中,碰见 extends 关键字,找出其基类,继续加载基类,基类还有基类以此类推 接下来,根基类中的static开始初始化,而后是其导出类的static,以此类推 如今对象能够被建立了, 首先对象中全部基本类型初始化为0,引用初始化为null 而后基类的构造器被调用,导出类构造器和基类构造器同样,以相同的顺序经历相同的过程 构造器完成以后,实例变量按其次序被初始化,最后构造器的其他部分被执行
java中除了static方法和final方法(static方法属于final方法)是前期绑定以外,其余全部方法都是后期绑定 一种工厂: public class Factory{ private Random rand = new Random(10); public Father next(){ switch(rand.nextInt(3)){ default: case 0: return new Son1(); case 1: return new Son2(); case 3: return new Son3(); } } } 多态的体现: Father的三个导出类Son一、Son二、Son3都重写了Father的 aMethod()方法 Father[] fathers = new Father[]{new Son1(), new Son2(), new Son3(),}; for(Father father: fathers){ father.aMathod(); } 因为动态绑定,能够成功的调用各种实例本身的aMethod()方法 不想让导出类覆盖的方法,指定为private: public class PrivateOverride{ private void f(){ System.out.println("private f()"); } public static void main(String[] args){ PrivateOverride po = new Derived(); po.f(); } } public class Derived extends PrivateOverride{ public void f(){ System.out.println("public f()"); } } 输出结果是:private f() //基类中的f()方法没法被覆盖,向上转型后调用po的f()方法是基类的 基类中已经有的成员,导出类再次定义则不会覆盖,它们各自有本身的存储空间导出类中要得到基类对象的属性应显示的使用 super.value 构造器是static方法,static的声明是隐式的 初始化顺序: 0、在其余任何事物发生以前,将分配给对象的存储空间初始化成二进制的 0 1、调用基类的构造器,反复递归,首先构造这种层次结构的根,而后是下一层导出类,直到最底层的导出类 2、按声明顺序调用成员的初始化方法 3、调用导出类构造器的主体 若是要手动进行清理工做,则销毁顺序与初始化顺序相反,导出类先进行清理而后才是基类,觉得导出类的清理工做可能须要基类中的某些方法 编写构造器的一条有效准则:用尽量简单的方法使对象进入正常状态;若是能够的话,避免调用其余方法 一条通用准则:用继承表达行为间的差别,用字段表达状态上的变化 class Actor{ public void act(){} } //经过继承表达 act 的不一样 class HappyActor extends Actor{ public void act(){ System.out.println("HappyActor"); } } class SadActor extends Actor{ public void act(){ System.out.println("SadActor"); } } //使用组合使本身状态发生变化 class Stage{ private Actor actor = new HappyActor(); //状态发生变化 public void change(){ actor = new SadActor(); } public void performPlay(){ actor.act(); } } 抽象类: 关键字 abstract 抽象方法:仅声明,没有方法体 public abstract void abMethod(); 包含抽象方法的类叫作抽象类: abstract class AbClass{ private i; public abstract void abMethod(); public String method(){ return "hello world"; } } 若是从一个抽象类继承,并想建立该新类的对象,那么就必须为基类中 全部 抽象方法提供方法定义,若是不这样则导出类也是抽象类
关键词 interface 能够认为是一种特殊的类,可是其支持多引用(implements) 像类同样,能够在定义时 interface前加public(可是仅限在与其同名的文件内),不加public则此接口只具备报访问权限 接口内也能够包含域,可是这些域隐式的是static和final的 能够显示的声明接口内的方法为public 若是不这样,它们也是public的 interface Interface{ int value = 1;//static && final void method1(); String returnString(Stirng str); } 一个类能够实现多接口:必需要实现两个接口的全部方法,若是不则应将Class定义为abstract class Class implements Interface1, Interface2{ } 向上转型时,只可以使用转型的那个接口内还有的方法 接口可继承: 实现扩展接口 interface Interface2 implements Interface1, Interface{ void method(); } 适配: interface Interface{ Object method(Object value);//此方法接收任何类型,并支持返回任何类型 } 接口能够嵌套在一个类或接口中 设计中,恰当的原则应该是优先选择类而不是接口,从类开始,若是接口的必需性变得很是明确,那么就进行 重构 工厂型设计的一个例子: interface Car{ void setname(String str); } interface CarFactory{ Car getCar(); } class Car1 implements Car{ private String name; public void setname(String name){ this.name = name; } } class Car2 implements Car{ private String name; public void setname(String name) { this.name = name; } } class Car1Factory implements CarFactory{ public Car getCar() { return (new Car1()); } } class Car2Factory implements CarFactory{ public Car getCar() { return (new Car2()); } }
成员内部类: 当生成一个内部类的对象时,此对象与制造它的外围对象之间有一种联系,它不须要任何特殊条件就能够访问其外围对象的全部成员,如同它拥有外部的成员同样 在内部类中生成对外部类对象的引用以及经过外部类对象建立内部类的对象: public class Outer{ void f(){ System.out.println("outer.f()"); } public class Inner{ public Outer outer(){ //在内部类中生成对外部类对象的引用 return Outer.this; } } public static void main(String[] args){ //经过外部类对象建立内部类的对象 Outer.Inner outerInner = new Outer().new Inner(); outerInner.outer.f(); } } 记住:成员内部类对象的建立依赖与外部类的 对象 ,没有外部类 对象 以前是不可能建立内部类 对象 的 成员内部类是private的,相同外部类内的非private成员内部类是没法访问的,只有外部类有其访问权限 局部内部类: 定义在某方法内部的类 / 定义在方法内的某做用域内 有效范围限制在这个域内 匿名内部类: 建立一个而继承于基类的匿名类的对象(涵盖的信息:它是一个子类,建立其对象时进行向上转型) 帮助理解:看起来彷佛是你正要建立一个对象,可是而后你却说:“等一等,我想在这里插入一个类的定义” interface Interface{} public class Class{ public Interface itf(){ return new Interface(){ private int i = 0; public int value(){ return i; } };//分号用来标记表达式的结束 } } ? 若是定义一个匿名内部类,并但愿它使用一个其外部定义的对象,编译器会要求其参数引用是 final 的(实验中并无) 匿名内部类没有构造器:由于它没有名字! <<经过实例初始化可达到为匿名内部类建立一个构造器的效果>> 匿名内部类能够扩展接口和类,可是不能二者兼容,对于接口也只能实现一个 嵌套类: 声明为static的内部类是嵌套类,这样的内部类对象与其外部类对象之间没有联系 嵌套类意味着: 要建立嵌套类的对象,并不须要其外围类的对象 不能从嵌套类的对象中访问非静态类的外围类的对象 普通内部类不能有static数据和static字段,也不能包含嵌套类,可是嵌套类能够包含全部这些东西 接口内部能够定义类:默认是 public 和 static 的 不懂:闭包 回调 一个内部类无论嵌套多少层,均可以透明地访问全部它嵌套入的外围类的全部成员: class MNA{ private void f(){} class A{ private void g(){} public class B{ void h(){ g(); f(); } } } } 内部类的继承: class WithInner{ class Inner{} } public class InheritInner extends WithInner.Inner{ //当要生成一个构造器时,必须使用这样的语法 InheritInner(WithInner wi){ wi.super(); //!!!!!!!!!!! } public static void main(String... args){ WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } } 内部类能够被覆盖么: 当继承了某个外围类的时候,内部类并无发生什么特别神奇的变化,这两个内部类是彻底独立的两个实体,各自在本身的命名空间内 外围类继承某一类后,其内部类能够明确指定继承于被继承的类内的一个类,此时能够覆盖内部类对方法 局部内部类与匿名内部类的选择: 若是须要一个已命名的构造器,或者须要重载构造器,就须要使用局部内部类,此时匿名内部类是没法实现的(没有名字),它只能用于实例初始化 还有就是一个局部内部类能够建立多个其对象,匿名内部类只会建立一个 内部类标识符: 每个类都会产生一个.class文件,其中包含了如何建立该类型的对象的所有信息 内部类class文件命名:外围类名字+$+内部类名字.class 若是是匿名内部类,则产生一个数字做为其标识符
Java容器类类库的用途是“保存对象”,划分的两个概念: Collection Map 泛型:一个例子 List<String> list = new ArrayList<String>();//指定list的存储类型为String,实例对象后向上转型为List接口,此时list可用的方法都是List接口中含有的 添加一组数据: Arrays.asList() 接受一个数组,或一个用逗号分割的元素列表,转化为List对象,可是其底层是数组,不能调整大小 Arrays.asList(1, 2, 3, 4); Integer[] intArray = {1, 2, 3, 4}; Arrays.asList(intArray); Collections.addAll() 接受一个Collection对象,以及一个数组或是用逗号分割的列表 Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1, 2, 3));//Collection的构造器接受一个Collection用于本身的初始化 Collections.addAll(collection, intArray); Collections.addAll(collection, 1, 2, 3); collection.addAll(Arrays.asList(intArray));//此方法只能接受一个Collection对象做为参数 建立一个空的Collection,而后使用对象的addAll()方法运行速度快 数组转List的时候:Arrays.<MyType>asList(new MyType(1), new MyType(2)); 容器的打印很规范: List:[hello, world] Set:[hello, world] Map:{nihao=hello, shijie=world} Set: HashSet:元素存储顺序没有意义,很快的查找速度 TreeSet:按照比较结果的升序保存对象 LinkedHashSet:按照被添加的顺序保存对象 Map: HashMap:保存顺序不是插入顺序,基于key的某种算法存储,很快的查找速度 TreeMap:按照比较结果的升序保存键(key) LinkedHashMap:按照插入顺序保存键,并保留了HashMap的查询速度 List: ArrayList 易于随机访问元素,可是在LIst的中间插入和移除元素时较慢 LinkedList 在List中间进行插入和移除代价会较低,提供了优化的顺序访问,可是随机访问会相对比较慢 一堆方法,用时查阅--224页 二者都是按照插入顺序保存元素,LinkedList包含更多的操做、 迭代器:Iterator 只能向前移动 使用iterator()方法返此容器的迭代器:Iterator<MyType> it = myArrayList.iterator(); 经过hasNext()方法检查是否还有元素:it.hasNext() 经过next取出当前值,并将指针向后移动:it.next() 经过remove删除容器中元素: it.next(); it.remove(); 迭代器统一了对容器的访问方式: public void display(Iterator<Object> it){ if(it.hasNext()){ System.out.println(it.next());//取出当前值后指针自动后移 } } ListIterator: 只适用于各类List类的访问,能够双向移动 建立List的专属迭代器:ListIterator<MyType> it = myArrayList.listIterator();//从0开始 ListIterator<MyType> it = myArrayList.listIterator(3);//指定从index=3开始 next仍然适用于取出当前值,并指针前移:it.next() 当前指针位置:it.nextIndex() 当前指针前一个位置的索引:it.previousIndex() 使用set修改当前指针位置的元素:it.set(new MyType()) LinkedList: 它添加了做用于栈、队列和双端队列的方法 取出第一个元素但不移除: getFirst();//List为空抛出异常 element();//与getFirst彻底同样 peek();//List为空返回null 取出第一个元素并移除: removeFirst();//List为空抛出异常 remove();//与removeFirst彻底同样 poll();//List为空返回null 添加一个元素: addFirst();//在表头添加 add();//在表尾添加 addLast();//表尾添加 offer();//表尾添加 移除并返回最后一个元素: removeLast();//List为空抛出异常? 栈(Stack):后进先出 采用LinkedList构建栈 public class Stack<T>{//重点:使用泛型,<T>告诉编译器这是一个参数化类型 构建此类的对象时能够采用泛型指定类型 private LinkedList<T> storage = new LinkedList<T>(); //增 public void push(T v){ storage.addFirst(v); } //查 public T peek(){ storage.getFirst(); } //删 public T pop(){ storage.removeFirst(); } //判空 public boolean empty(){ return storage.empty(); } //转串 public String toString(){ return storage.toString(); } } Set: HashSet Set最常被使用的是测试归属性:contains()方法 查找是Set中最重要的操做就是查找:基于对象的值 实际上Set就是Collection,只是行为不一样 HashSet内存储的元素没有顺序:出于速度考虑使用了散列 TreeSet将元素存储在红-黑树数据结构中,而HashSet使用的是散列函数,LinkedHashSet也使用了散列,可是看起来它使用了链表来维护元素的插入顺序 TreeSet能够根据对象的值在其插入的时候进行排序 Map: <key, value> 查看是否包含某个key或value:containsKey() containsValue() Map的value能够其余容器:例如Map<ClassName, List<MyType>> Map能够返回他的键的Set:myMap.keySet() myMap.values()//返回值类型是Collection 以前没见过:List<? extends MyType> //此处的泛型是全部继承与MyType的类 Queue: 队列常被看成一种可靠的将对象从程序的某一个区域传输到另一个区域的途径 彩蛋:char的修饰类 Character PriorityQueue: 默认最小值拥有最高的优先级 能够在构造队列的时候调用含参构造器指定Comparator设定元素优先级 PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>(3, Collections.reverseOrder()) Collection 和 Iterator: 实现Collection能够轻松使用foreach进行遍历 生成Iterator是将队列与消费队列的方法链接在一块儿的耦合度最小的方式,并与实现Collection相比,它在序列类上施加的约束也少的多 ?? Iterable接口: 该接口包含一个可以产生Iterator的iterator()方法(匿名内部类实现) 而且Iterable接口被foreach用来在序列中移动,任何实现了Iterable的类,均可以将他用于foreach语句中 大量的类都是Iterable类型,主要包括有Collection类(除Map) foreach能够用于数组,可是数组不能自动包装成Iterable:直接做为Iterable类型传入会报异常 <T> void test(Iterable<T> ib){//指定泛型的方法 要在方法返回以前加 <T> for(T t: ib){ System.out.println(t); } } String[] strings = {"A", "B"}; test(strings);//!!!!!!!!!!失败 new 出来的容器会从新给其要存储的元素分配内存,若是将一个List做为参数传入则就是生成了一个副本,对其操做不会影响以前的内容 要进行大量随机访问就使用ArrayList,若是要常常从表中间插入或删除元素则使用LinkedList 各类Queue和Stack的行为由LinkedList提供支持 各类不一样的容器类的方法:246页
当抛出异常后,有几件事会随之发生。首先,同Java中其余对象的建立同样,将使用new在堆上建立异常对象。而后,当前的执行路径(它不能继续下去了) 被终止,而且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。 这个恰当的地方就是异常处理程序,它的任务就是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去 抛出一个异常: throw new NullPointerException(); 全部标准异常类都有两个构造器:一个是默认构造器,一个是接受字符串做为参数来把相关信息放入异常对象的构造器 异常类型的根类:Throwable 捕获异常: 监控区域:一段可能产生异常的代码,而且后面跟着处理这些异常的代码 若是在方法内部抛出了异常(或者在方法内部调用的其余方法抛出了异常),这个方法将在抛出异常的过程当中结束 try{}catch(Type1 id1){} 终止:出现异常直接中止执行 恢复:出现异常,尝试修复在继续执行 一种方式是try放在while循环里 自定义异常继承于与其意思相近的异常(很难找),能够继承于Exception 输出错误信息到控制台能够采用System.err.println();将错误发送给标准错误流 自定义异常并建立本身的构造器: class MyException extends Exception{//必定要继承于基类异常 如Exception MyException(){} MyException(String msg){ super(msg);//直接调用父类构造器 } } 在捕获异常后打印栈轨迹: try{ }catch(Exception e){ //能够指定将信息发送的输出流,好比System.out标准输出流 默认是发送到标准错误流System.err e.printStackTrace(); } ???使用java.util.logging工具将输出记录到日志 Throwable类中的getMessage()方法能够得到异常的详细信息,全部异常也都会有此方法 捕获全部异常: 使用异常类型的基类Exception catch(Exception e){ System.out.println("Caught an exception"); } 能够调用其从Throwable继承来的: //获取详细信息 String getMessage(); String getLocalizedMessage(); String toString(); //打印栈轨迹 void printStackTrace(); void printStackTrace(PrintStream); void printStackTrace(java.io.PrintStream); Throwable fillInStackTrace(); 用于在Throwable对象的内部记录栈帧的当前状态 ---->更新异常 Object类中的 getClass() 能够返回一个表示此对象类型的对象: 再 getName() getSimpleName() printStackTrace()方法所提供的信息能够经过getStackTrace()方法直接访问: try{ throw new Exception(); }catch(Exception e){ for(StackTraceElement ste: e.getStackTrace()){ //实际上还能够打印整个StackTraceElement 包含其余附件的信息 System.out.println(ste.getMethodName()); } } 一个被抛出的异常被捕获后,printStackTrace()方法显示的还是原来异常抛出点的 调用栈 信息,而并不是从新抛出点的信息 可是能够在捕获后调用异常的fillInStackTrace()方法,会将当前的调用栈信息填入原来那个异常对象,此行代码成了异常的新发地 在捕获到一个异常后能够加以包装再次抛出或处理,或从新抛出一个新的异常,而前一个异常对象因为是new在堆上建立的,垃圾回收器会自动把它们清理掉 异常链: 在捕获一个异常后抛出另外一个异常,而且但愿把原始异常的信息保存下来 Trowable的子类在构造器中均可以接受一个 cause 对象做为参数,cause表示原始异常 只有三个基本异常提供了带 cause 参数的构造器:Error、Exception、RuntimeException 其余的异常须要使用 initCause()方法而不是构造器: MyException myException = new MyException(); myException.initCause(new NullPointerException());//建立新异常并将旧异常包装进来 catch(RuntimeException e){ throw new Exception(e);//自带接受cause的构造器 } Throwable类被用来表示任何能够做为异常被抛出的类: Error:表示编译时和系统错误 Exception:能够被抛出的基本类型 特例异常:RuntimeException 它们会自动被Java虚拟机抛出,因此没必要在异常说明中把它们列出来: void test1() extends Exception{//注意此处必定要有异常说明 throw new Exception(); } void test2(){//此处不须要异常说明,其输出被报告给System.err throw new NullPointerException(); } 记住:只能在代码中忽略RuntimeException类型的异常,其余类型异常的处理都是由编译器强制实施的,缘由:RuntimeException表明的是编程错误 使用finally进行清理:无论try内的代码抛不抛出异常,finally块的代码都会执行 记住:finally子句不管如何都会被执行 何时使用finally:当要把除内存以外的资源恢复到它们的初始状态时,就会使用finally,例如:关闭已经打开的文件或网络链接等等 在return中使用finally: public class Return{ public static void f(int i){ try{ if(i == 1){return;} }finally{//此处的代码仍然会被执行 System.out.println("print in finally"); } } public static void main(String[] args){ f(1);//此时仍然会输出 print in finally } } 异常丢失: try{ throw new Exception("an exception"); }finally{ //抛出的异常没有被处理和显示,就好像没有出现异常同样 return; } 异常的限制:须要记住的几点 1、基类方法声明异常,派生类重写此方法能够不声明异常或只声明基类的异常 2、基类方法不声明异常,派生类重写的方法不可声明异常 3、接口不能添加异常对于基类中已经存在的方法:接口与基类含有相同方法 4、构造器能够声明新的异常,但派生类的构造器必定要声明基类构造器的异常 5、处理某一类对象,编译器就会强制要求捕获这个类所抛出的异常 6、对于某派生类将其向上转型,编译器就会要求捕获基类的异常 7、派生类中的方法能够声明基类方法声明的异常的派生异常 8、派生类构造器不能捕获基类构造器抛出的异常 总结:基类的能力强于接口,因为存在向上转型,基类声明的异常派生类能够不声明,但不能增长声明新的异常(包括接口中的) 在建立须要清理的对象以后,当即进入try-finally语句块 try{ //须要执行的任务 }finally{ //后续的清理工做 } 匹配异常:catch会按照代码的书写顺序进行匹配捕获,捕获到当即处理后续再也不继续查找 捕获的某个异常能够经过 getCause()方法得到异常信息而且能够直接做为异常被再次抛出: catch(RuntimeException re){ throw re.getCause(); } 技巧:在为了简化程序时,能够将异常包装成RuntimeException,由于此异常是不须要异常说明的
String对象是不可变的,每个看起来会修改String值的方法,实际上都是建立了一个全新的String对象来包含修改后的字符串内容 每当把String对象做为方法的参数被传入时,都会复制一份引用 对于方法而言,参数是为该方法提供信息的,而并非想让该方法改变本身的 String对象经过 += 拼接的本质: 编译器自动引用java.lang.StringBuilder类,经过现有的String对象值生成Stringbuilder对象,而后在调用append()方法将字符串拼接,再调用toString()方法获得新的String对象 无心识的递归: public class InfiniteRecursion{ public String toString(){ return "InfiniteRecursion address:" + this + "\n"; //此处会将尝试将this转为String,那就要调用toString方法造成了递归调用 } public static void main(String[] args){ List<InfiniteRecursion> v = new ArrayList<InfiniteRecursion>(); for(int i = 0; i < 10; ++i){ v.add(new InfiniteRecursion); } System.out.println(v); } } 正确方式:调用Object.toString()方法:使用 super.toString()//由于它确定是一个Object类 String类的方法都会返回一个新的String对象,若是内容没有发生改变则只是返回指向原对象的引用 格式化输出://参数的格式书写与C彻底同样,包括宽度、左对齐仍是右对齐以及精度等等 System.out.format(); System.out.printf(); Formatter类://java.util.Formatter public class PrintFormat{ private Formatter f = new Formatter(System.out);//参数告诉它最终结果向哪输出,经常使用PrintStream、OutputStream、File public void printF(){ f.format("%-15s %5s %10.2f\n", "Tom", "", 3.14);//此时调用printF方法就会将格式化字符串输出在System.out } } String.format()方法:String类的static方法,接受与Formatter.format()一样的参数,返回一个格式化后的String对象 Scanner类:控制太输入 Scanner stdin = new Scanner(System.in);//接受任何类型的输入对象 示例具备全部类型(除char)对应的next方法,以及hasNext方法用来判断下一个输入分词是否是所需的类型 Scanner有一个假设,在输入结束时会抛出IOException,因此Scanner会把IOException吞掉,能够经过ioException()方法找到最近发生异常(这应该不会用到) Scanner能够经过useDelimiter()方法指定定界符(就是用啥切分),默认是空白字符: Scanner scanner = new Scanner("12, 13, 14, 15, 16"); scanner.useDelimiter("\\s*,\\s*");//指定逗号(包括逗号先后任意的空白字符)做为定界符
Java如何让咱们在运行时识别对象和类的信息? 一、传统的RTTI,它假定咱们在编译时就已经知道了全部的类型(Run-Time Type Information) 在运行时,识别一个对象的类型 2、“反射”机制,它容许咱们在运行时发现和使用类的信息 “多态”是面向对象编程的基本目标 Class对象: 每一个类都有一个Class对象(被保存在.class文件) 类型信息如何表示:这项工做是由称为Class对象的[特殊对象]完成的,它包含了与类有关的信息 事实上,Class对象就是用来建立类的全部的“常规”对象的 !!若是很差理解能够类比Object类,也就是会有一个Class类它就存在那里,当建立一个类时就会获得此类的Class对象并保存在相应的.class文件中 重点:全部的类都是在对其第一次使用时,动态加载到JVM的。 当程序建立第一个对类的静态成员的引用时,就会加载这个类。 构造器也是类的静态方法,即便在构造器以前并无使用static关键字 使用new操做符建立类的新对象也会被看成对类的静态成员的引用(含义:new时候也会加载类到JVM) Java程序在它开始运行以前并不是被彻底加载,其各个部分是在必须时才加载的 类加载器首先检查这个类的Class对象是否已经加载,若是没有,默认的类加载器就会根据类名查找.class文件; 在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,而且不包含不良Java代码 一旦某个类的Class对象被载入内存,它就被用来建立这个类的全部对象 静态块: class Test{ static {System.out.println("hello");}//没有方法体 } Class类的众多方法: forName("package.ClassName");//取得Class对象的引用,传进去的是一个具体的类名,获得一个其相应的Class对象的引用 若是已经有ClassName类的对象,还能够使用Object的方法getClass()获取一个Class对象的引用 getName();//产生全限定的类名(包括包名) getSimpleName();//产生不包含包名的类名 getCanonical();//产生全限定的类名(包括包名) isInterface();//判断这个Class对象是否表示某个接口,接口.isInterface();才会返回true getInterface();//返回Class对象,它们表示某个Class对象中所包含的接口 getSuperclass();//查找某Class对象的直接基类,返回的是基类的Class对象 newInstance();//不知道确切的类型,仍旧建立那种类型的实例对象,能够用一个Object的引用来接收 使用此方法来建立实例的类必须带有[默认的构造器] isInstance();//接受一个实例对象,判断其是不是此类的一个实例 类字面常量: 另外一种方法来生成对Class对象的引用 能够应用于普通的类,也能够应用于接口、数组以及基本数据类型 基本数据类型的包装类,还存在一个TYPE字段(一个引用),指向基本数据类型的Class对象:char.class(建议使用) <=> Character.TYPE 为了使用类要作的准备工做: 1、加载 由类加载器执行 2、连接 验证类中大的字节码,为静态域分配存储空间,若是必须则将解析这个类建立的对其余类的全部引用 3、初始化 若是该类具备超类,则对其初始化,执行静态初始化器和静态初始化块 仅使用.class语法来得到对类的引用不会引起初始化 Class.forName()会当即进行初始化 泛化的Class引用: 不指定类型,则理解相似于Object Class<Integer> intClass = Integer.class; //!intClass = Double.class; 错误 通配符 ---> ?:表示“任何事物” Class<? extends Number> bounded = int.class; //指定类型为全部继承与Number类的类 //还有这种:Class<? super sonClass> up = son.class.getSuperclass(); 转型语法: class Building{} class House extends Building{} public class ClassCasts{ public static void main(String[] args){ Building b = new House(); Class<House> houseType = House.class; House h = houseType.cast(b); // 等价于 h = (House)b; } } instanceof: if(x instanceof Dog){//判断对象x是否为Dog类的一个实例 ((Dog)x).bark(); } “曲线救国”:简介调用初始化块 private static void init(){ System.out.println(); } static{init();} instanceof与Class的等价性: instanceof和isInstance()生成的结果彻底同样,它们保持了类型的概念,指:你是这个类么,或者你是这个类的派生类么 比较用 equals() 和 == ,它们比较的就是实际的Class对象,不会考虑继承->要么是确切的类型,要么不是 反射: java.lang.reflect类库:Method、Constructor类等 RTTI与反射的真正区别在于,对于RTTI来讲,编译器在编译时打开和检查.class文件;对于反射,.class文件在编译时是不可得到的,是在运行时打开和检查.class文件 类方法提取器: 能够提取出某个类的方法以及其从基类继承来的方法 getMethods();//提取方法 getConstructors();//提取构造器 ???动态代理 337页 空对象: public interface Null{}//建立一个标记接口 class Person{ public final String first; public final String last; public final String address; public Person(String first, String last, String address){ this.first = first; this.last = last; this.address = address; } public String toString(){ return "Person: " + first + " " + last + " " + address; } //***********内部的空类用于生成空对象*********** public static class NullPerson extends Person implements Null{ private NullPerson{ super("None", "None", "None"); } public String toString[(){ return "NullPerson"; } } //空对象 public static final Person NULL = new NullPerson(); } 经过反射能够到达并调用那些非公共访问权限的方法以及访问私有的域: 例子 class WithPrivateFinalField{ private int i = 1; private final String s = "I am totally safe";//在使用反射尝试修改其值时是安全的,但实际上不会发生任何修改 private String s2 = "Am I safe?"; } public class ModifyingPrivateFields{ public static void mian(String[] args){ WithPrivateFinalField pf = new WithPrivateFinalField(); Field f = pf.getClass().getDeclaredField("i"); //获取私有的域 f.setAccessible(true); //设定此域能够被到达 System.out.println("f.getInt(pf): " + f.getInt(pf));//会将i的值打印 f.setInt(pf, 47); //修改i的值为47 ...等等 } }
泛型的主要目的之一就是用来指定 容器 要持有什么类型的对象,并且由编译器来保证类型的正确性 Java泛型的核心概念:告诉编译器想使用什么类型,而后编译器帮你处理一切细节 元组类库(tuple): 将多个返回值(可不一样类型)包装成一个元组,做为return的值 public class Tuple<A, B>{ public final A a;//这里虽然是public,可是因为有final的修饰,仍能够保证其被任意读取而不能被修改 public final B b; public Tuple(A a, B b){ this.a = a; this.b = b; } } //还能够在其基础上建立更多元素的元组继承与这个基类 一个堆栈类: public class LinkedStack<T> { //Node类也有本身的类型 public static class Node<U>{ U item; Node<U> next; Node(){ item = null; next = null; } Node(U item, Node<U> next){ this.item = item; this.next = next; } boolean end() { return (item == null && next == null); } } private Node<T> top = new Node<T>(); public void push(T item) { top = new Node<T>(item, top); } public T pop() { T result = top.item; if(!top.end()) { top = top.next; } return result; } public static void main(String[] args) { LinkedStack<String> lss = new LinkedStack<String>(); for(String str: "hello world !".split(" ")) { lss.push(str); } String s; while((s = lss.pop()) != null) { System.out.println(s); } } } 泛型接口: public interface Generator<T>{} Java泛型的局限:基本类型没法做为类型参数 泛型方法:将泛型参数列表置于返回值前 public class GenericMethods{ //泛型方法,指定参数类型,啥都行 public <T> void f(T x){ System.out.println(x.getClass.getName()); } public static void main(String[] args){ GenericMethods gm = new GenericMethods(); gm.f(""); gm.f(1); gm.f(1.0); gm.f('c'); gm.f(gm); } } 泛型方法不受其类是不是泛型类的影响,单独存在 使用泛型类时,必须在建立对象的时候指定类型参数的值,而使用泛型方法的时候,一般没必要指明参数类型,由于编译器会为咱们找出具体的类型 杠杆利用类型参数推断 public class New{ public static <K, V> Map<K, V> map(){ return new HashMap<K, V>(); } public static void main(String[] args){ Map<String, List<String>> sls = New.map();//类型参数推断 } } 类型推断只对赋值操做有效,其余时候并不起做用:将方法调用结果看成参数传递给另外一个方法则不会进行推断 public class LimitsOfInference{ static void f(Map<String, List<String>> map){} public static void main(String[] args){ f(New.map);//!!!!出错,不会进行自动推断 } } 显示的类型说明(不多使用): 上例中的调用 f(New.<String, List<String>>map());//在点和方法名之间添加 擦除的神秘之处: public class Test{ public static void main(String[] args){ Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2); } }//输出:true 在泛型代码内部,没法得到任何有关泛型参数类型的信息 Java泛型使用擦除来实现,当使用泛型时任何具体的类型信息都被擦除,惟一知道的就是在使用一个对象,List<String>和List<Integer>在运行时其实是相同类型 指定泛型类边界: class Test<T extends MyClass>{//已知Myclass类含有f()方法 此处指定边界 private T obj; public Test(T x){ obj = x; } public void test(){ obj.f(); }//在这里才能够放心的调用f()方法 } 重点:即便擦除在方法或类内部移除了有关实际类型的信息,!!!编译器仍旧能够确保在方法或类中使用的类型的内部一致性 public class GenericHolder<T>{ private T obj; public void set(T obj){ this.obj = obj; } public T get(){ return obj; } public static void main(String[] args){ GenericHolder<String> holder = new GenericHolder<String>(); holder.set("hello world");//编译器执行传入参数检查 String s = holder.get(); //字节码中含有对get()返回的值进行转型 } } 在泛型中的全部动做都发生在边界处---对传递进来的值进行额外的 编译器检查,并插入对传递出去的值的转型 擦除的补偿----引入类标签: class ClassAsFactory<T>{ T x; public ClassAsFactory(Class<T> kind){//指定类标签 try{ x = kind.newInstance(); }catch(Exception e){ throw new RuntimeException(e); } } } 泛型数组: array = (T[])new Object[size];//泛型数组的强制类型转换 Warning:unchecked cast 补偿仍是使用类型标签: Class<T> 边界:重用 extends 关键字 class Test<T extends Class & Interface1 & Interface2>{}//类在前,接口在后,类只能有一个,接口能够有不少 能够在继承的每一个层次添加边界限制 interface HasColor{} class HolsItem<T>{ T item; HolsItem(){ this.item = item; } T getItem(){ return item; } } class Colored<T extends HasColor> extends HoldItem<T>{//继承了HostItem的持有对象的能力,并要求其参数与HasColor一致 Colored(T item){ super(item); } } 通配符: //条件 class Fruit{} class Apple extends Fruit{} class Jonathan extends Apple{} class Orange extends Fruit{} //例子 Fruit[] fruit = new Apple[10]; fruit[0] = new Apple();//没毛病 fruit[1] = new Jonathan();//继承于Apple,没毛病 fruit[2] = new Fruit();//ArrayStoreException fruit[3] = new Orange();//ArrayStoreException 解释:编译器认为是合法的,可是运行时的数组机制知道它处理的是Apple[],所以会在向数组中放置异构类型时抛出异常 //编译时错误,不相容类型 List<Fruit> flist = new ArrayList<Apple>();//Apple的List在类型上不等价于Fruit的List 这根本不是向上转型 List<? extends Fruit> flist = new ArrayList<Apple>();//一旦执行这种类型的向上转型,将丢掉向其中传递任何对象(set)的能力,甚至是Object 逆变: 超类型通配符,能够声明通配符是由某个特定类的任何基类来界定的 <? super MyClass> <? super T> 无界通配符: <?> 它是在声明:我是想用Java的泛型来编写这段代码,我这里并非要原生类型,可是在当前这种状况下,泛型参数能够持有任何类型 <?>表示一种特定的类型,可是我如今还不知道它是啥,原生类型则是任何Object类型 一个原则: <? extends MyClass> 指明的对象至少是MyClass,能够使用get返回值并转型为MyClass(来自Myclass的两个不一样的东西不具备兼容性,无法存) <? super MyClass> 指明的对象是MyClass的某个基类,能够使用set将MyClass存储(根据多态必定能够操做MyClass,取出来的类型有不少种,因此就无法取) 泛型使用时常见问题: 任何基本类型都不能做为类型参数:new ArrayList<int>();//int是不被容许的 使用包装类 转型和警告:使用带有泛型类型参数的转型或 instanceof 不会有任何效果(因为存在擦除) 泛型方法的重载: public class UseList<W, T>{ void f(List<T> v){}//因为擦除会致使产生相同的类型签名 void f(List<W> v)()//出现这中状况就使用具备明显区别的方法名 } 基类劫持接口: public class ComparableClass implements Comparable<ComparableClass>{ public int compareTo(ComparableClass arg){ return 0; } } class ExtendedClass extends ComparableClass implements Comparable<ExtendedClass>{}//错误,基类实现的是ComparableClass对象的比较,这里没法指定新的对象 自限定类型: 古怪的循环泛型(DRG):类至关古怪的出如今它本身的基类中 理解:我正在建立一个新类,它继承自一个泛型类型,这个泛型类型接受个人类的名字做为其参数 class GenericType<T>{} public class MyType extends GenericType<Mytype>{} 自限定: class SelfBounded<T extends SelfBounded<T>>{} class A extends SelfBounded<A>{}//基类中指定的类型要是继承于基类的类型 class D{} class E extends SelfBounded<D>{}//这样是不能够的 若是不使用自限定,将重载参数类型,若是使用了自限定,只能得到 某个方法 的一个版本,它将接受确切的参数类型(使用自限定类型就肯定): 自限定类型的价值在于它能够产生 协变参数类型 --- 方法参数类型会随子类而变化 interface SelfBoundSetter<T extends SelfBoundSetter<T>>{ void set(T arg); } interface Setter extends SelfBoundSetter<Setter>{} public class SelfBoundingAndCovariantArguments{ void testA(Setter s1, Setter s2, SelfBoundSetter sbs){ s1.set(s2); //s1.set(sbs); //Error 自限定类型 } } 泛型异常---目前不会用吧 410页 Java混型|--与接口混合 |--使用装饰器模式 |--与动态代理混合 泛型的普遍应用:持有器(持有对象) 潜在类型机制:Java缺少潜在类型机制 对其的补偿: 反射:经过Class对象尝试获取某对象的方法 问题:将全部的类型检查都转移到了运行时,所以在许多状况下是咱们所不但愿的 ???函数对象:就是某种程度上行为像函数的对象---与普通方法不一样,它们能够传递出去,而且还能够拥有在多个调用之间持久化的状态 论点:使用泛型类型机制的最吸引人的地方,就是在使用容器类的地方,这些类包括各类List、各类Set、各类Map等等 当将一个对象放置到容器中时,这个对象就会被向上转型为Object,所以将会丢失类型信息 当将一个对象从容器中取回,用它去执行某些操做时,必须将其向下转型回正确的类型
数组与其余种类的容器之间的区别有三方面:效率、类型、保存基本类型的能力 数组是一种效率最高的存储和随机访问对象引用序列的方式 使用数组和容器时,若是越界到会获得一个 表示程序员错误的 RuntimeException Java返回数组(引用):无需考虑像C或C++那样内存回收或内存泄漏问题(数据存在堆内的缘由?) 无需担忧要为数组负责,只要须要它就会一直存在,使用完后垃圾回收器会清理掉它 Arrays.deepToString()方法:将多维数组转换为多个String Arrays.deepToString(new int[][]{{1, 2, 3}, {4, 5, 6}});//输出[[1, 2, 3], [4, 5, 6]] 数组中构成矩阵的每一个向量均可以具备任意长度---粗糙数组: int[][] is = new int[][]{ {1, 2, 3}, {1, 2}, {5, 6, 7, 8} }; 数组必须知道它们所持有的确切类型,以强制保证类型安全 数组与泛型不能很好的结合,不能实例化具备参数化类型的数组: !!!编译器不容许实例化泛型数组 T[] t = new T[10];是不容许的 Peel<Banana>[] pells = new Peel<Banana>[10];//错误 能够参数化数组自己的类型: class Class<T>{ public T[] f(T[] arg){ return arg; //doSomething }// } 还能够建立数组引用: List<String>[] ls; List[] la = new List[10]; ls = (List<String>[])la;//"Unchecked" warning //数组是协变类型 List<String>[] 也是一个 Object[] Object[] objects = ls; //此时就能够存入其余类型 objects[1] = new List<Integer>(); 数组统一初始化:Arrays.fill();方法 int[] is = new int[6]; Arrays.fill(is, 2); Array.fill(is, 2, 5, 3);//index为二、三、4的位置填充替换为3 System.out.println(Arrays.toString(is));//[2, 2, 3, 3, 3, 2] 数据生成器:Generator Arrays使用功能:一套用于数组的static方法 equals(); ----比较两个数据是否相等(deepEquals()用于多维数组) 基于内容的比较 Object.equals() fill(); ----填充数组 sort(); ----对数组排序 默认从小到大 须要排序的对象必须实现Comparable接口(重写compareTo(MyType another)方法)sort须要将参数的类型转变为Comparable Arrays.sort(array, Collections.reverseOrder());//反序排列 Collections.reverseOrder()产生了一个Comparator 编写本身的Comparator class MyTypeComparator implements Comparator<Mytype>{ public int compare(Mytype o1, MyType o2){ return (o1.i < o2.i ? -1 : (o1.i == o2.i ? 0 : 1)); } } Arrays.sort(array, new MytypeComparator()); 针对基本类型采用“快速排序” 针对对象采用“稳定归并排序” binarySearch(); ----在已经排序的数组中查找元素 也能够使用Comparator: Arrays.binarySearch(array, array[3], String.CASE_INSENSITIVE_ORDER);//字符串忽略大小写 toString(); ----数组转换为String hashCode(); ----产生数组的案列码 asList(); ----接受任意的序列或数组为参数,并将其转变为List容器 数组复制: Java标准类库提供有static方法 System.arraycopy() 用法:System.arraycopy(源数组, 源数组偏移量, 目标数组, 目标数组偏移量, 复制元素的个数) 基本数组以及对象数组均可以复制,对象数组的复制只是复制引用(浅拷贝) 注:System.arraycopy 不会执行自动包装和自动拆包,两个数组必须具备相同的确切类型 总结:Java编程中,优先选用容器而不是数组,只有在已证实性能成为问题(切换到数组会有大的提高)时,才应该将程序重构为使用数组
填充容器: Collections.nCopies()建立传递给构造器的List: List<String> list = new ArrayList<String>(Collections.nCopies(4, new String("hello")));//之间填充4个相同的对象(引用) Collections.fill()只对List有用: Collections.fill(list, new String("world"));//只能替换已经在List中的元素,不能添加新的元素 直接输出一个对象的引用获得:类型@该对象的散列码的无符号十六进制 全部的Collection子类型都有一个接收另外一个Collection对象的构造器,用所接受的Collection对象中的元素来填充新的容器 一种Generator解决方案: interface Generator<T>{ T next(); } public class CollectionData<T> extends ArrayList<T>{ public CollectionData(Generator<T> gen, int num){ for(int i = 0; i < num; ++i){ add(gen.next()); } } //通用便捷方法 传递进来任意实现生成器接口的类型,就能够获得持有此类型的CollectionData public static <T> CollectionData<T> list(Generator<T> gen, int num){ return new CollectionData<T>(gen, num); } } //一个实现生成器的类 class MyClass implements Cenerator<Integer>{ Random rand = new Random(49); public Integer next(){ return rand.nextInt(100); } } //在main中测试 Set<Integer> set = new LinkedHashSet<Integer>(new CollectionData<Integer>(new MyClass(), 3));//使用CollectionData进行填充 set.addAll(CollectionData.list(new MyClass(), 3));//使用便捷方式 static list()方法 再次复习Iterable接口的使用: class MyType implements Iterable<Integer>{ private int size = 9; private int number = 1; @Override //重点 public Iterator<Integer> iterator(){ return new Iterator<Integer>(){ public Integer next(){ return number++; } public boolean hasNext(){ return number < size; } public void remove(){ throw new UnsupportedOperationException(); } } } } 使用Abstract类:建立定制的Collection和Map实现本身需求(目前可能用不到) Collection的功能方法: boolean add(T);//添加元素 ^ boolean addAll(Collection<? extends T>);//添加全部元素 ^ void clear();//清空容器 ^ boolean contains(T);//是否持有参数 Boolean containsAll(Collection<?>);//是否持有全部元素 Iterator<T> iterator();//返回一个Iterator<T>,用于遍历容器 Boolean remove(Object);//移除此元素的一个实例 ^ boolean removeAll(Collection<?>);//移除参数中全部元素 ^ Bollean retainAll(Collection<?>);//只保留参数中的元素 ^ int size();//容器中元素数目 Object[] toArray();//返回持有全部元素的数组 <T> T[] toArray(T[] a);//返回与a相同类型的数组 用法举例:String[] strs = myList.toArray(new String[0]); 注意:这些都是Collection的方法,对于List和Set适用,Map不是继承自Collection没法使用 并且对于List和Set还会有特定的一些方法实现更多的功能 执行各类不一样的添加和移除的方法在Collection接口中都是可选操做,这意味着实现类并不须要为这些方法提供功能定义 ^:标注为可选操做 经常使用的容器类内这些可选操做都获得了具体实现,都支持全部操做 可选操做在运行时可能会抛出 UnsupportedOperationException 未获支持的操做:add() addAll() retainAll() 等 来源于背后由固定尺寸的数据结构支持的容器 ---> Arrays.asList() 基于一个固定大小的数组,仅支持那些不会改变底层数组大小的操做,好比修改 set() 是能够的 经过 Collections.unmodifiableList() 构建的不可修改的容器 因为指定是不可修改,因此都不能够 使用迭代器也能够修改元素: public void iterTest(List<String> aList){ ListIterator<String> it = aList.listIterator(); it.add("47"); it.previous();//在前面添加了一个后把指针移到最前面 it.remove(); it.next();//把当前的元素删除,指针移动到下一个 it.set("47");//修改当前指针位置的元素 } 全部在Set中存储的类都必须具备 boolean equals() 方法 HashSet、LinkedHashSet中存储的类都必须具备 hashCode() 方法(这里看到的是返回 int类型) TreeSet中存储的类都必须实现 Comparable接口 并实现 compareTo() 方法: 注意:不要使用简明的 return (i - i2); 的形式 例如i是很大的正整数 i2是很大的负整数? i-i2可能会溢出返回负值 总结:全部存入Set类必须具备 equals() 方法 全部存入HashSet的类必须还要具备 hashCode() 方法 全部存入任何种类的排序容器中必须实现 Comparable 接口 SortedSet: 实现此接口的类能够调用Comparator comparator() 方法返回当前Set使用的Comparator (返回null表示以天然方式排序) 还有: Object first();//返回容器中第一个元素 Object last();//返回容器中的最后一个元素 SortedSet subSet(fromElement, toElement);//生成Set的子集 左闭右开 SortedSet headSet(toElement);//生成子集,元素小于参数 SortedSet tailSet(fromElement);//生成子集,元素不小于参数 队列: 两个实现:LinkedList PriorityQueue 差别在于排序行为而不是性能 优先级队列 双向队列(没有具体声明 靠LinkedList实现) Map: HashMap//默认Map,速度最快 TreeMap LinkedHashMap WeakHashMap ConcurrentHashMap IdentityHashMap hashCode()方法是Object中的方法,全部Java对象都能产生散列码 默认使用对象的地址计算散列码 map.keySet();//返回Map的键的Set map.values();//返回Mao的值的Collection SortedMap(TreeMap是其惟一实现)的使用跟SortedSet很像,方法什么的也都差很少 为Map建立本身的key: 注意的两个方法 hashCode() equals() //二者都在Object类中都有 不覆盖会使用默认的形式 equals()方法必须知足的5个条件: 1、自反性 对于任意的x x.equals(x)必定返回true 2、对称性 对于任意的x和y 若是y.equals(x)返回true,则x.equals(y)也必定返回true 3、传递性 对于任意x、y、z x.equals(y)返回true y.equals(z)也返回true 则x.equals(z)也返回true 4、一致性 对于任意的x和y,若是对象中用于等价比较的信息没有改变,那么不管调用 x.equals(y) 多少次返回的结果应该保持一致 五、对于任何 不是null的x,x.equals(null)必定返回false 线性查询是最慢的查询方式 为速度而散列: 建立一个固定大小的 LinkeedList数组,每个数组位置称为桶位(bucket) 基于Key计算哈希值做为数组的index,若是put时index位置为null,则建立一个LinkedList 并将引用存储在相应位置的桶位上 为了散列分布均匀,桶的数量一般使用质数 普遍测试结果:使用2的整数次方的散列表速度更快(掩码代替耗时的除法) 散列码没必要是独一无二的,散列码关注的是生成速度 String的特色:若是程序中有多个String对象,都包含相同的字符串序列,那么这些String对象都映射到同一块内存区域 String的hashCode()是基于String的内容生成的(不一样的引用指向赞成内存地址) 本身覆盖hashCode()生成根据须要的哈希值 TreeSet 和 TreeMap都实现了元素的排序: 树的行为是老是保证有序,而且没必要进行特殊的排序。一旦填充了一个TreeMap,就能够调用keySet()方法获取键的Set视图,而后调用toArray()方法来 产生由这些键构成的数组,以后能够使用Arrays.binarySearch()方法在排序的数组中快速查找对象 默认使用状况:ArrayList HashSet HashMap HashMap的性能因子: *容量:桶个数 *初始容量:表在建立时拥有的同位数,HashMap和HashSet都具备指定初始容器容量的构造器 *尺寸:表中当前存储的项数 *负载因子:尺寸/容量(HashMap默认负载因子 0.75 ) 大量的容器实用方法: java.util.Collections 类内部的静态方法(static) 512页 list的排序使用Comparator binarySearch()的时候也要使用相同的Comparator 不可修改容器: Collections.unmodifiable* (*: Collection Set List Map SortedSet SortedMap) 使用时能够事先构造一个private的正常容器,填充好数据(设定为不可修改的必须条件)后做为参数传递给此方法 而后返回不可修改容器的引用 Collection和Map的同步控制: Collections类有办法可以自动同步整个容器 Collections.synchronized*() *一样表明各类容器 //直接将新生成的容器传递给适当的“同步”方法 保证没有任何机会暴露出不一样步的版本 Collection<String> c = Collections.synchronizedCollection(new ArrayList<String>()); 快速报错: Java容器有一种保护机制,可以放置多个进程同时修改同一个容器的内容。 若是在迭代遍历某个容器的过程当中,另外一个进程介入其中,而且插入、删除、或修改此容器内某个对象,那么就会出现问题 快速报错机制(fail-fast)探查容器上的任何处理本身的进程所进行的操做之外的全部变换,一旦发现其余进程修改容器,就会马上抛出ConcurrentModificationException异常 对象是可得到的:指此对象可在程序中的某处找到 此对象的引用在栈内找到(或是存在中间连接 a->b->c) 对象是可得到的则垃圾回收器就不能释放它,由于它仍然为程序所用 若是是不可得到的则垃圾回收器将其回收是安全的 Reference类再研究(WeakHashMap)
File类: 帮助咱们处理文件目录问题 目录列表器 list() 方法 此方法接收空参数 以及一个实现了FilenameFilter接口的类的实例 public interface FilenameFilter{ boolean accept(File dir, String name);//list() 会回调accept方法 } 此处复习:某方法内部建立匿名内容类须要传递进参数,必须是final(编译器强制要求 为了保持应用的的正确性,不被修改) 流:它表明任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象 “流”屏蔽了实际的I/O设备中的处理细节 InputStream: 做用是用来表示那些从不一样数据来源产生输入的类 ByteArrayInputStream//容许将内存的缓冲区看成InputStream 构造器参数为 缓冲区, 字节将从中取出 StringBufferInputStream//将String转换成InputStream FileInputStream//文件 PipedInputStream//管道 SequenceInputStream//将多个InputStream对象转换成单一InputStream FilterInputStream//抽象类,做为“装饰器”的接口 为其余的InputStream类提供有用的功能 OutputStream: 决定输出所要去往的目标 ByteArrayOutputStream FileOutputStream PipedOutputStream FilterOutputStream 咱们几乎每次都是对输入进行缓冲---无论咱们正在链接的是什么I/O设备,I/O类库把无缓冲输入做为特殊状况 FilterInputStream经常使用类型: DataInputStream//能够按照可移植方式从流读取基本数据类型(int, char, long等) BufferedInputStream//防止每次读取时都得进行写操做 DataOutputStream PrintStream BufferedOutputStream InputStream和OutputStream面向字符形式的I/O Reader和Writer提供兼容Unicode和面向字符的I/O功能 InputStreamReader 将InputStream转换为Reader OutputStreamWriter 将OutputStream转换为Writer 总结一点:从面向字节流的InputStream / OutputStream 转换为面向字符的Reader / Writer 使用相应的适配器 *Reader *Writer 自我独立的类:RandomAccessFile//只适用于文件 构造器须要额外参数: "r"//随机读 "rw"//随机读写 getFilePointer()//查找当前所处的文件位置 seek()//在文件内移至新的位置 length()//判断文件最大尺度 I/O流的典型使用方法: 缓冲输入文件: 打开文件用于字符输入 FileInputReader 为了提升速度使用缓冲 将所产生的引用传给一个BufferedReader构造器 BufferedReader也提供 readLine() 方法 到文件末尾返回null BufferedReader in = new BufferedReader(new FileReader(filename)); //BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(filename))); String s; while(s = in.readLine() != null){ System.out.print(s + "\n");//readLine() 会删除掉每一行后面的换行符 } in.close();//不要忘了用完要关闭文件 从内存输入//不经常使用 格式化的内存输入//不经常使用 小窍门:跟内存/缓存有关的 都是Buffer 基本的文件输出: PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(filename)));//PrintWriter提供格式化输出 //BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename))); 写入文件时:PrintWriter提供print相关方法 BufferedWriter提供write方法(还有一个newLine) 特别推荐的快捷方式:直接PrintWriter out = new PrintWriter(filename);//仍旧会使用缓存 存储和恢复数据:格式化读与写 DataInputStream DataOutputStream 含有针对不一样内容的读写操做 如 readDouble() 读写随机访问文件: RandomAccessFile();//构造器接收文件名以及一个打开方式 "r" "rw" 它不支持装饰,因此不能将其与InputStream以及OutputStream子类的任何部分组合起来 文件读写的实用工具: 确保处理文件后关闭文件: try{ PrintWriter out = new PrintWriter(new FileReader(filename)); try{ for(String str: strArrays){ out.println(str); } }finally{ //这里保证了处理完文件后及时关闭文件 out.close(); } }catch(IOException e){ throw new RuntimeException(e); } 读取二进制文件: 使用字节流 BufferedInputStream bf = new BufferedInputStream(new FileInputStream(bFile)); try{ byte[] data = new byte[bf.available()];//bf.available()返回文件占有的byte大小 bf.read(data); return data; }finally{ //再次复习:上面return了 也会执行finally的代码 bf.close(); } 标准I/O: System.out//事先被包装成了printStream System.err//事先被包装成了printStream System.in//没有被包装过的未经加工的InputStream 使用它以前必须对其进行包装 //包装如:BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); 标准I/O重定向://不太清楚怎么用(没有进行实验) 静态方法: System.setIn(); System.setOut(); System.setErr(); 通道与缓冲器:通道(Channel)->包含煤层(数据)的矿藏 缓冲器->用于矿藏与外界进行资源交互的卡车 惟一直接与通道交互的缓冲器是 ByteBuffer ByteBuffer是将数据移进移出通道的惟一方式 视图缓冲器:针对基本类型 好比CharBuffer 内存映射文件:(MappedFile) 容许咱们建立和修改那些由于太大而不能放入内存的文件 FileChannel fc = new RandomAccessFile("temp.tmp", "rw").getChannel(); IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size()).asIntBuffer(); for(int i; i < 10; ++i){ ib.put(i); } fc.close(); 文件加锁: public class FileLocking{ public static void main(String[] args) throws Exception{ FileOutputStream fos = new FileOutputStream("file.txt"); FileLock fl = fos.getChannel().tryLock(); if(fl != null){ System.out.println("Locked File"); TimeUnit.MILLISECONDS.sleep(100); fl.release(); //释放锁 System.out.println("Released File"); } fos.close(); } } 经过对FileChannel调用tryLock()或者lock() 就能够得到整个文件的FileLock 注:SockedChannel、DatagramChannel和ServerSocketChannel不须要加锁,由于他们是从单进程实体继承而来的 tryLock()是非阻塞的,它设法获取锁,可是若是不能得到(当其余进程已经持有相同的所而且不共享时),它将直接从方法调用中返回 lock()是阻塞的,它要阻塞进程直至所能够得到,或者调用lock()的线程中断,或者调用lock()的通道关闭 能够对文件的一部分上锁: tryLock(long position, long size, boolean shared); lock(long position, long size, boolean shared);//加锁区域为 position ~ position+szie shared指定是否为共享锁 对 独占锁 和 共享锁 的支持必须由底层的操做系统提供,若是操做系统不支持共享锁并为每一个请求都建立一个锁,那么它就会使用独占锁 经过 FileLock.isShared() 进行查询锁的类型 压缩//应该不会用到 Zip GZIP JAR文件: 一种压缩的文件格式 JAR文件是跨平台的,因此没必要担忧跨平台的问题 声音和图像文件能够像类文件同样被包含在其中 最实用: jar cf myJarFile.jar *.class //Jar文件包含全部的class文件 571页 对象序列化与反序列化://目的就是将含有信息的对象存入磁盘 并在须要的时候将其恢复 序列化接口 Serializable 实现了此接口的类能够支持序列化 序列化: public class Alien implements Serializable{} public class FreezeAlien{ public static void main(String[] args) throws Exception{ ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("X.file")); Alien quellek = new Alien(); out.writeObject(quellek); } } 反序列化: public class ThawAlien{ public static void main(String[] args) throws Exception{ ObjectInputStream in = new ObjectInputStream( new FileInputStream(new File("..", "X.file"))); Object mystery = in.readObject(); System.out.println(mystery.getClass); } } 注:打开文件和读取mystery对象中的内容都须要Alien的Class对象;若是Java虚拟机找不到Alien.class就会获得ClassNotFoundException 必须保证Java虚拟机可以找到相关的.class文件 序列化的控制: Externalizable 继承了Serializable 并添加了两个方法:writeExternal() readExternal()//这两个方法在序列化与反序列化的过程当中被自动调用来执行一些特殊操做 Externalizable对象在反序列化时 它的构造器必须是public//不然恢复时形成异常 注:Serializable对象的恢复彻底以它的二进制位为基础来构造,不调用构造器 Externalizable对象会调用默认构造器 Externalizable类内的域要在writeExternal() 以及 readExternal() 中进行显示序列化与反序列化 两种对象序的列化的区别:Serializable 所有会自动进行 Externalizable没有任何东西能够自动序列化 transient(瞬时)关键字: 若是使用的是Serializable构建类 可是但愿其中的某一个子类(域)不被序列化存储(如密码)则须要在此域前加 transient关键字 Externalizable的替代方法: 实现Serializable接口的类中定义 writeObject() 以及 readObject()方法 方法必须具备准确的方法签名: private void writeObject(ObjectOutputStream stream) throws IOException; private void readObject(ObjectOutputStream stream) throws IOException, ClassNotFoundException; 注:这两个方法是 ObjectOutputStream 以及 ObjectInputStream 的writeObject() 和 readObject() 方法来调用的//private之因此能够被调用使用的是 反射机制 能够在本身的这两个方法中调用defaultWriteObject() 和 defaultReadObject() 来选择执行默认的write和read 使用“持久性”: 只要将任何对象序列化到单一流中,就能够恢复出与咱们写出时同样的对象网,并无任何意外重复复制出的对象 若是咱们想保存系统状态,最安全的作法就是将其做为“原子”操做进行序列化: 将构成系统状态的全部对象都置于单一容器内,并在一个操做中将该容器直接写出 而后一样只需一次方法调用便可将其恢复 对于类内static值不会被自动序列化,想要序列化static值,必须本身手动去实现 XML: import nu.xom.*; 能够将对象保存为规范格式的.xml文件 具体参考 586页 读取xml文件并解析对象//反序列化 public class People extends ArrayList<Persion>{ public People(String filename) throws Exception{//传入文件名的构造器 Document doc = new Builder().builder(filename);//打开文件 基于xom类库 Elements elements = doc.getRootElement().getChildElements();//获得一个Elements列表 拥有get() 和 size() 方法 for(int i = 0; i < elements.size(); ++i){//size肯定大小 add(new Persion(elements.get(i))); //get获取元素 } } public static void main(String[] args) throws Exception{ People p = new People("People.xml"); System.out.println(p); } } Preferences API: 588页 他只能用于小的、受限的数据集合---咱们只能存储基本类型和字符串(每一个字符串的存储长度不能超过8K) Preferences是一个键-值集合(相似映射),存储在一个节点层次结构中
enum基本特性: 建立enum时,编译器会自动生成一个相关的类,这个类继承于java.lang.Enum 方法values() 返回enum实例的数组 能够用于遍历 ordonal() 返回int值 对应于每个实例的声明时的次序(从0开始) 能够使用 == 比较实例 编译器会自动提供equals() 和 hashCode() 方法 Enum类实现了Comparable接口 因此enum实例具备compareTo() 方法 enum实例上能够调用 getDeclaringClass() 查看它所属的类 Enum类的静态方法valueOf() 根据给定的名字返回相应的enum实例: enum E { ONE, TWO, THREE} Enum.valueOf(E.class, "ONE");//获得ONE对应的enum实例 将 import static 用于enum: 能够将enum实例的标识符带入当前的命名空间,无需在用enum类型来修饰enum实例: class Test{ E em; Test(E em){ this.em = em; } } 不使用静态导入: new Test(E.ONE) 使用: new Test(ONE); 注意:在定义enum的同一个文件中 或者 在默认包中定义enum 没法使用此技巧 enum不能被继承,除了这一点几乎能够将其当作为类,能够在其中添加方法,甚至它能够有main()方法 在enum中定义本身的方法: 注意几点:建立enum实例的时候能够采用括号后传入字符串参数做为描述//字符串做为其构造器的参数 实例后面定义方法时,实例定义结束要加分号 构造器默认是private 一旦enum的定义结束,编译器就不容许咱们再使用其构造器来建立任何实例 也能够像正常的类同样,覆盖已有的方法 switch语句中能够使用enum:正常状况下,须要使用enum类型来修饰一个enum实例,可是在case语句中却不须要 values()的神秘之处: enum E { ONE, TWO } 反编译:java的代码能够调用系统命令 OSExecute.command("javap E"); 获得: final class E extends java.lang.Enum{ public static final E ONE; public static final E TWO; public static final E[] values(); public static E valueOF(java.lang.String); static {};//静态初始化子句 } 解释:编译器将 E 标记为final类,因此没法被继承 values()是由编译器添加的static方法 编译器还添加了valueOf()方法//Enum类的valueOf()须要两个参数 添加的这个须要一个 values()方法是由编译器插入到enum定义中的static方法,向上转型为Enum后values()方法没法访问,能够使用Class的getEnumConstants()方法获得全部enum实例 enum继承于Enum因此没法再继承其余类,可是能够implements接口 使用接口组织枚举: 在一个接口的内部,建立实现该接口的枚举,以此将元素进行分类 public interface Food{ enum Appetizer implements Food{ SALAD, SOUP, SPRING_ROLLS; } enum MainCourse implements Food{ LASAGNE, BURRITO, PAD_THAI; } enum Dessert implements Food{ TIRAMISU, GELATO, BLACK_FOREST_CAKE; } }//全部的枚举实例都是Food类 它们又被进行了分类 EnumSet: EnumSet中的元素必须来自一个enum 使用一个long(64位)值做为比特向量 一个enum实例只需一位bit表示其是否存在 EnumSet能够应用于最多不超过64个元素的enum 当enum超过64个,EnumSet会在必要的时候增长一个long 向其中添加enum实例的顺序并不重要,由于其输出的次序决定于enum实例定义时的次序 各类方法查阅资料使用 EnumMap: EnumMap中的key必须来自一个enum 构建Map: EnumMap<MyEnum, MyClass> em = new EnumMap<MyEnum, MyClass>(MyEnum.class); enum的每个实例做为一个键,老是存在 若是没有为这个键调用put()方法来存入相应的值的话,其对应的值就是null 常量相关方法: public enum ConstantSpecificMethod{ ONE{ void action(){ System.out.println("ONE"); } } TWO{ void action(){ System.out.println("TWO"); } }; abstract void action();//须要定义抽象方法 } 调用:ONE.action(); enum实例不能够做为一个类型来使用: //void f1(ConstantSpecificMethod.ONE instance){} ONE是一个静态实例 不能做为类型 除了实现abstract方法外 还能够覆盖常量相关方法 public enum ConstantSpecificMethod{ ONE, TWO{ void action(){ System.out.println("TWO"); } }; void action(){ ; } } Java只支持单路分发: 若是要执行的操做包含了不止一个类型未知的对象时,那么Java的动态绑定机制只能处理其中一个的类型 因此咱们必须本身来断定其余的类型,从而实现本身的动态绑定行为 使用enum分发:615页代码 不太理解构造器的代码部分 private Outcome的 三个实例如何初始化(初始化为啥呢!!??) 还有使用 常量相关方法 以及 EnumMap 和 数组:核心思路就是构建各类不一样类型的表而已 “表驱动式编程”
注解为咱们在代码中添加信息提供了一种格式化的方法,是咱们在稍后某个时刻很是方便地使用这些数据 Java SE5内置三种注解,定义在java.lang中: @Override 表示当前的方法定义将覆盖超类中的方法 @Deprecated(弃用) 若是程序员使用了注解为Deprecated的元素,编译器会发出警告 @SupressWarnings 关闭不当的编译器警告 每当建立描述符性质的类或接口时,一旦其中包含了重复性的工做,就能够考虑使用注解来简化与自动化该过程 定义注解: 注解定义后,其中能够包含一些元素来表示某些值 分析处理注解时,程序或工具能够利用这些值 没有元素的注解是 标记注解 例: @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test{} 含有元素的注解定义: @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UseCase{ public int id(); //元素定义相似于方法 public String description() default "no description";//设置默认值 若是添加注解时没有指定 则使用默认值 } 用注解: @UseCase(id = 47, description = "whatever you want to write") public void oneMethod(){} 四种元注解 @Target 用来定义咱们本身定义的注解将应用于什么地方(例如是一个方法或者一个域)(ElementType.) @Retention 用来定义该注解在哪个级别可用 源码:OURCE 类:CLASS 运行时:RUNTIME(RetentionPolicy.) @Documented 将注解包含在Javadoc中 @Inherited 容许子类继承父类中的注解 注解元素可用的类型: 基本类型 String Class enum Annotation 以上类型的数组 元素默认值限制: 元素必需要么具备默认值,要么在使用注解时提供元素的值 对于非基本类型的元素,默认值定义不能为null 因此在定义时一般以负数或空字符串来表示某个元素不存在 元素命名为value,使用注解时value是惟一须要赋值的元素则能够直接在括号里写如值 而无需使用键=值的形式 注意:注解不支持继承 基于注解的单元测试: 单元测试 是对类中的每一个方法提供一个或多个测试的一种实践,其目的是为了有规律地测试一个类的各个部分是否具有正确的行为 基于注解的测试框架:@Unit 用的最多的@Test 用@Test标记测试方法 被标记的测试方法不带参数 返回boolean 或 void 例子: @Test boolean assertAndReturn(){ assert 1 == 2: "What a surprise!"; return methodOne().equals("") } 注:使用@Unit进行测试必须定义在某个包中(即必须包括package声明 例如java mywork.mypackage.xlc MyTest) 对于每个单元测试而言,@Unit都会用默认的构造器,为该测试所属的类建立出一个新的实例 并在此新建立的对象上运行测试,而后丢弃该对象,以避免对其余测试产生反作用 若是没有默认浏览器,或者新对象须要复杂的构造过程,能够建立一个 static方法专门负责构造对象 使用 @TestObjectCreate注解标记此方法(必须是static的方法) 测试单元中添加额外的域 使用 @TestProperty注解(还能够用来标记那些只在测试中使用的方法,而他们自己又不是测试方法) 进行清理工做使用 @TestObjectCleanup 标记static方法 经过继承实现测试类 来完成@Unit对泛型的使用: 惟一缺点是 继承使咱们失去了访问被测试的类中的private方法的能力 解决办法:设定为protected 或 使用非private的@TestProperty方法调用private方法 总结:Java SE5仅提供了不多的内置的注解 若是咱们在别处找不到可用的类库,就只能本身建立新的注解以及相应的处理器 借助apt工具,咱们能够同时编译新产生的源文件,以及简化构建过程 mirror API只能提供基本功能--帮助找到Java类定义中的元素 Javassist可以用来操做字节码
用并发解决的问题大致上能够分为 速度 和 设计可管理性 阻塞: 若是程序中的某个任务由于该程序控制范围以外的某些条件(一般是I/O)而致使不能继续执行,那么咱们就说这个任务或线程 阻塞 了 进程: 是运行在它本身的地址空间内的自包容的程序 多任务操做系统能够经过周期性地将CPU从一个进程切换到另外一个进程,来实现同时运行多个进程(程序) 进程会被互相隔离开,所以它们不会彼此干涉 与在多任务操做系统中分叉外部进程不一样,线程机制是在由执行程序表示的 单一进程 中建立任务 Java的线程机制是 抢占式 的,这表示调度机制会周期性地中断线程,将上下文切换到另外一个线程,从而为每个线程都提供时间片 使得每一个线程都会分配到数量合理的时间去驱动它的任务 定义任务: 一种描述任务的方式:由Runnable接口来提供 要想定义任务,只须要实现Runnable接口并编写run()方法,使得该任务能够执行你的命令 public class RunClass implement Runnable{ ... public void run(){ while(...){ ... Thread.yield(); } } } 注:任务的run方法一般总会有某种形式的循环,使得任务一直运行下去直到再也不须要 因此要设定跳出循环的条件(有一种选择是直接从run()返回) 一般run被写成无限循环的形式 静态方法Thread.yield()是对线程调度器的一种建议:我已经执行完最重要的部分了,此刻正是切换给其余任务执行一段时间的大好时机 Thread类: 将Runnable对象转变为工做任务的传统方式是把它提交给一个Thread构造器: public class BasicThreads{ public static void main(String[] args){ Thread t = new Thread(new RunClass());//Thread构造器只须要一个Runnable对象 t.start(); System.out.println("Waiting for RunClass"); } } 注:调用Thread对象的start()方法为该线程执行必要的初始化操做,而后调用Runnable的run()方法 程序会同时运行两个方法,main()和RunClass.run() 使用Executor: 用单个Executor 来建立和管理系统中全部的任务 例子: public class Executor{ ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; ++i){ exec.execute(new RunClass); } exec.shutdown();//防止新任务被被提交给这个Executor } newCachedThreadPool是首选 还有: FixedThreadPool(指定线程数) SingleThreadPool //至关于FixedThreadPool(1) 会序列化全部提交给它的任务 逐个完成 Runnable是执行工做的独立任务,可是不会返回任何值 若是但愿任务在完成时可以返回一个值,则能够实现 Callable接口 Callable是一种具备参数类型的泛型,它的类型参数表示的是从方法call()中返回的值,必须使用ExecutorService.submit()方法调用它 例子: class TaskWithResult implements Callable<String>{ private int id; public TaskWithResult(int id){ this.id = id; } public String call(){ return "result of TaskWithResult " + id; } } public class CallableDemo{ public static void main(String[] args){ ExecutorService exec = Executors.newCachedThreadPool(); ArrayList<Future<String>> results = new ArrayList<Future<String>>(); for(int i = 0; i < 10; ++i){ results.add(exec.submit(new TaskWithResult(i))); } for(Future<String> fs: results){ try{ System.out.println(fs.get()); }catch(InterruptedException e){ System.out.println(e); return; }catch(ExecutionException e){ System.out.println(e); }finally{ exec.shutdown(); } } } } 注:submit()方法会产生Future对象,此处将指定类型的返回值做为参数传递给submit()方法 //能够使用 isDone()方法来查询Future是否已经完成 //直接使用get()方法在Future没有完成时会造成阻塞,直到结果准备就绪 休眠: sleep() 老式的sleep: Thread.sleep();//单位是毫秒 新式的sleep: TimeUnit.MILLISECONDS.sleep();//能够指定sleep()延迟的时间单元,具备更好的可阅读性 对sleep的调用会抛出InterruptedException 异常 异常不能跨线程传播回主线程,因此必须在本地处理全部在任务内部产生的异常 优先级: 线程的优先级将该线程的重要性传递给调度器 调度器倾向于让优先级最高的线程先执行 这并不意味着优先级较低的线程得不到执行(优先级不会致使死锁) 只是优先级较低的线程执行的频率较低 能够使用getPriority()来读取现有线程的优先级 setPriority()来修改线程的优先级 public void run(){ Thread.currentThread().setPriority(priority);//currentThread:当前线程 } 经常使用优先级: MAX_PRIORITY//最大 NORM_PRIORITY//标准 MIN_PRIORITY//最小 让步: Thread.yield()方法 若是知道已经完成了在run()方法的循环的一次迭代过程当中所需的工做,就能够给线程调度机制一个暗示: 你的工做已经作得差很少了,可让别的线程使用CPU了 注意:对于任何重要的控制或在调整应用时,都不能依赖于yield() 后台线程: 是指在程序运行的时候在后台提供一种通用服务的线程,而且这种线程并不属于程序中不可或缺的部分 使用Thread的setDaemon() 方法将线程设置为后台线程: Thread daemon = new Thread(new SimpleDaemon()); daemon.setDaemon(true);//设置为后台线程 daemon.start(); 被设置为后台线程的线程,其派生出来的其余子线程,都是后台线程(不须要显示地设置为后台线程) 后台线程中 不会执行finally子句的状况下就会终止其run()方法 直接继承与Thread类: public class SimpleThread extends Thread{ private int contDown = 5; private static int threadCount = 0; public SimpleThread(){ super(Integer.toString(++threadCount)); start(); } public String toString(){ return "#" + getName() + "(" + countDown + "),"; } public void run(){ while(true){ System.out.println(this); if(--countDown ==0){ return; } } } public static void main(String[] agrs){ for(int i = 0; i < 5; i++){ new SimpleThread(); } } } * 能够定义内部类来将线程代码隐藏在类中 * 还能够在类的内部定义Thread引用: 采用匿名内部类的形式建立Thread子类并向上转型为Thread,并使用定义好的Thread引用指向它 * 也能够将线程建立在方法内,当准备好运行线程时,就能够调用这个方法,而在线程 开始以后,该方法将返回 加入一个线程: 在A线程中调用B的B.join()方法 A线程会被挂起 直达目标线程B结束才恢复 在调用join()时带上一个超时参数(单位能够是秒,毫秒或纳秒) 这样若是目标线程在这段时间到期时尚未结束的话 join()方法总能返回 对join()方法的调用能够被中断 使用 B.interrupt()方法 异常捕获: 在线程中的run()方法内抛出的异常,并不会被外部线程捕获到 专用: Thread.UncaughtExceptionHandler接口 共享受限资源: 对于并发工做,须要某种方式来防止两个任务访问相同的资源,至少在关键阶段不能出现这种问题 采用:序列化访问共享资源 的方案: 在给定时刻只容许一个任务访问共享资源 共享资源通常是以对象形式存在的内存片断,但也能够是文件、输如/输出端口,或者打印机 要控制对共享资源的访问,得先把它包装进一个对象,而后把全部要访问这个资源的方法标记为synchronized synchronized void f(){} 全部对象都自动含有单一的锁(也称为监视器),当在对象上调用任意synchronized方法的时候,此对象都被加锁, 这时该对象上的其余synchronized方法只有等到前一个方法调用完毕并释放了锁以后才能被调用 在并发时,将域设置为private是很是重要的,不然,synchronized关键字就不能防止其余任务直接访问域,这样就会发生冲突 针对每一个类,也有一个锁(做为Class对象的一部分),因此synchronized static方法能够在类的范围内防止对static数据的并发访问 何时使用 同步: 若是你正在写一个变量,它可能接下来将被另外一个线程读取,或者正在读取一个上一次已经被另外一个线程写过的变量,那么你必须使用同步 而且,读写线程都必须用相同的监视器锁同步 重要的一点:每一个访问临界共享资源的方法都必须被同步,不然它们就不会正确的工做 显示的Lock对象: java.util.concurrent.locks中显示的互斥机制 Lock对象必须被显示的建立、锁定、释放 例子: public class MyClass{ private int currentValue = 0; private Lock lock = new ReentrantLock(); public int next(){ /*惯用写法 先加锁 在采用try实现对竞争资源的操做 最后finally语句进行解锁*/ lock.lock(); try{ ++currentValue; Thread.yield(); ++currentValue; return currentValue; }finally{ lock.unlock(); } } } 大致上,使用synchronized关键字时,须要写的代码量更少,而且用户错误出现的可能性也会下降,所以一般只有在解决特殊问题时,才使用显示的Lock对象 原子性与易变性: 原子操做是不能被线程调度机制中断的操做,一旦操做开始就必定会在 切换到其余线程执行 以前 执行完毕 原子性能够应用于除long和double以外的全部基本类型上的“简单操做” 对于读取和写入除long和double以外的基本类型变量这样的操做,能够保证它们会被看成不可分(原子)的操做来操做内存 易变性的关键字---volatile 若是将一个域声明为volatile的,那么只要对这个域产生了写操做,那么全部的读操做均可以看到这个修改 volatile域会当即被写入主存中,读取操做就是发生在主存中 i++这样的操做在java中是非原子性的 基本上,若是一个域可能会被多个任务同时访问,或者这些任务中至少有一个是写入任务,那么就应该将这个域设置为volatile的 读取和写入都是直接针对内存的,而却没有缓存, 可是volatile并不能对递增不是原子性操做这一事实产生影响: public class SerialNumberGenerator{ private static volatile int serialNumber = 0; public static int nextSerialNumber(){ return serialNumber++; //volatile不能阻止此操做是非原子性的 所以 此处是非线程安全的 } } 对基本类型的读取和赋值操做被认为是安全的原子性操做,当对象出于不稳定状态时,仍旧有可能使用原子性操做来访问它们: private int i = 0; public int getValue(){ return i; } public synchronized void iPlusOne(){ i++; i++; } 注:在某个线程中调用iPlusOne() 在另外一个线程中调用getValue() 因为i++是原子性操做 而getValue()方法不是synchronized 致使获取到奇数的中间状态值 原子类: AtomicInteger AtomicLong 等等 Atomic类被设计用来构建java.util.concurrent中的类,所以只有在特殊状况下才在本身的代码中使用它们 临界区: 但愿防止多个线程同时访问方法内部的 部分代码 而不是防止访问整个方法 --- 被分离出来的部分代码段被称为临界区(critical section) 使用sychronized关键字创建,synchronized被用来指定某个对象: synchronized(syncObject){ ...//临界区---同步控制块,进入此段代码前必须获得syncObject对象的锁 } 具体使用案例687页 还能够使用显示的Lock对象来建立临界区://689页的例子 class ExplicitPairManager2 extends PairManager{ private Lock lock = new ReentrantLock(); public void increment(){ Pair temp; lock.lock(); try{ p.incrementX(); p.incrementY(); }finally{ lock.unlock(); } store(temp); } } 在其余对象上同步: synchronized块最合理的使用方式是,使用其方法正在被调用的当前对象: synchronized(this) 这种方式下,若是得到了synchronized块上的锁,那么该对象其余的synchronized方法和临界区就不能被调用了 class DualSynch{ private Object syncObject = new Object(); public synchronized void f(){ for(int i = 0; i < 5; ++i){ System.out.println("f()"); Thread.yield(); } } public void g(){ synchronized(syncObject){ //同步的对象为另外一Object对象 并非this 这样能够保证两个同步是相互独立的 for(int i = 0; i < 5; ++i){ System.out.println("g()"); Thread.yield(); } } } } 线程本地存储://这样每一个线程都会有本身的变量 不会发生冲突 也就不须要进行同步 建立和管理线程本地存储能够由 java.lang.ThreadLocal类来实现 public class ThreadLocalVariableHolder{ private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){ private Random rand = new Random(47); protected synchronized Integer initialValue(){ return rand.nextInt(10000); } };//在一个类内调用使用此类时,每一个线程都会为变量分配本身单独的存储 } 注:ThreadLocal对象一般看成静态域存储 在建立ThreadLocal时,只能经过get()和set()方法来访问该对象的内容 无需使用synchronized 由于ThreadLocal保证不会出现竞争条件 终结任务: cancel()和isCanceled()方法被放到了一个全部任务均可以看到的类中 设置canceled变量人为终止任务: public void run(){ while(!canceled){ ... } } 线程状态: 一个线程能够处于如下四种状态之一: 1 新建(new):当线程被建立时,它只会暂时处于这种状态,此时已经分配了必需的系统资源,并执行了初始化 2 就绪(Runnable):只要调度器把时间片分配给现线程,线程就能够运行 3 阻塞(Blocked):线程可以运行,但有某个条件阻止它的运行。当线程处于阻塞状态时,调度器将忽略线程,不会分配给线程任何CPU时间 4 死亡(Dead):处于死亡或终止状态的线程将再也不是可调度的,而且不再会获得CPU时间,任务已经结束再也不是可运行的 任务死亡的一般方式是从run()方法返回 进入阻塞状态的可能缘由: 1 经过调用sleep()使任务进入休眠状态 2 经过调用wait()使线程挂起 直到线程获得了notify()或notifyAll()消息 (signal()或signalAll()消息) 线程会进入就绪状态 3 任务在等待某个输入/输出完成 4 任务试图在某个对象上调用其同步控制方法,可是对象锁不可用(被另外一个任务获取了锁) 中断: 若是一个线程已经阻塞,或者试图执行一个阻塞操做,那么设置这个线程的中断状态将抛出 InterruptedException Thread.interrupted() 使用Executor来执行操做时,调用Executor的shutdownNow()方法 它将发送一个interrupt()调用给它启动的 全部线程 要中断某个单一任务,则使用ExecutorService的submit()方法 代替execute()方法: submit()方法将返回一个泛型Future<?>,其中有一个未修饰的参数 --- 持有这种Future的关键字在于你能够在其上调用cancel()方法来中断某个特定任务 sleep引发的阻塞可中断 I/O以及synchronized引发的阻塞不可中断 关闭任务在其上发生阻塞的底层资源来中断阻塞: * exec.shutdownNow() * 直接关闭流 * 调用Future的cancel() 一个任务应该可以调用在同一个对象中的其余的synchronized方法,而这个任务已经持有锁了 //699页 中断检查: 将退出考虑全面:在阻塞时调用interrupt()方法抛出InterruptException进行catch处理 非此类状况则须要第二种处理方式来退出 //考虑被阻塞和非阻塞的各类状况 //结合使用try-finally一个典型的惯用法 701页 线程之间的协做: 使用wait() notify() notifyAll() 协做 //三种方法都是Object类的 它们操做的锁也是全部对象的一部分 wait()的调用有两种 1 以毫秒为单位的参数--表示挂起时间 2 没有参数--表示一直挂起--无限等待 在wait()期间对象锁被释放 调用notify() 或 notifyAll()能够是处于wait()的线程唤醒 notify():在众多等待同一个锁的任务中只有一个会被唤醒 notifyAll():唤醒多个处于wait()的任务 注意的是:wait() 和notify() 的使用针对的都是同一个对象 例如一个对象上使用notifyAll() 在另外一个对象上的wait()的任务是不会被唤醒的 生产者与消费者: 一个很好的例子//709页 针对不一样task对象设定不一样的synchronized块(不一样任务不一样的锁) 当wait()被调用后,任务挂起(wait)并等待唤醒信号(notify/notifyAll) 再次强调:任务结束的方式---run()是如何返回的 使用显示的Lock和Condition: 使用Condition和Lock对象,单个Lock将产生一个Condition对象,这个对象被用来管理任务间的通讯 可是Condition对象并不包含任何有关处理状态的信息,须要额外的处理信息 使用的方法:await()来挂起 <-> signal() signalAll()来唤醒 使用的套路: class Car{//被操做的对象类 里面定义必要域 以及 对象上的不一样状态 private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private boolean waxOn = false; public void waxed(){ lock.lock(); try{ waxOn = true; condition.signalAll(); }finally{ lock.unlock(); } } //... } class WaxOn implements Runnable{ /*...*/ }//被操做对象上的具体操做任务 有Runnable编码套路while(!Thread.interrupted()){...} 注:使用Lock和Condition可能会使编码的复杂性变得更高但收益不多 --- Lock和Condition对象只有在更加困难的的多线程问题中才是必需的 生产者-消费者与队列: 解决:wait()和notifyAll()方式一种很是低级的方式解决了任务互操做问题,即每次交互时都握手 使用 同步队列 解决任务协做问题:java.util.concurrent.BlockingQueue接口 LinkedBlockingQueue ArrayBlockingQueue SynchronousQueue 经典例子://715页的土司 没有任何显示的同步(使用Lock对象或者synchronized关键字的同步) 同步由队列(其内部是同步的)和系统的设计隐式地管理 每个被操做的对象在任什么时候刻都只由一个任务在操做 由于队列的阻塞,使得处理过程将被自动地挂起和恢复 任务间使用管道进行输入/输出://piped PipedWriter:容许任务向管道写 PipedReader:容许不一样任务从同一管道中读取 PipedReader的创建必须在构造器中与一个PipedWriter相关联 使用 PipedReader in = new PipedReader(out); //其中out被定义为 PipedWriter out = new PipedWriter(); 注意:BlockingQueue使用起来更加健壮而容易 死锁: 任务循环等待下一个任务释放锁 必须同时知足四个条件才会发生死锁: 1 互斥条件 任务使用的资源中至少有一个是不能共享的 2 至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源 3 资源不能被任务抢占 任务必须把资源释放看成普通事件 4 必须有循环等待 新类库中的构建: java.util.concurrent库引入大量设计用来解决并发问题的的新类 //用到时再仔细研究 CountDownLatch:被用来同步一个或多个任务,强制它们等待由其余任务执行的一组操做完成 CyclicBarrier:适用于:建立一组任务,它们并行地执行工做,而后在进行下一步骤以前等待,直至全部任务都完成(/*赛马*/) DelayQueue;延迟队列 PriorityBlockingQueue:优先级队列 ScheduledExecutor:预约义工具 ScheduledThreadPoolExcutor Semaphore:计数信号量容许n个任务同时访问一个资源 Exchanger:在两个任务之间交换对象的栅栏 典型应用场景:一个任务在建立对象,这些对象的生产代价很高昂,而另外一个任务在消费这些对象 能够使得 有更多的对象在被建立的同时被消耗 当一个任务调用其Exchanger.exchange()方法时,它将阻塞直至对方任务调用它本身的exchange()方法,那时,这两个exchange()方法将所有完成 对象被互换 性能优化: synchronized Lock Atomic 若是涉及多个Atomic对象,就有可能被强制要求放弃这种用法: Atomic对象只有在很是简单的状况下才有用//754页 免锁容器: 免锁容器背后的通用策略:对容器的修改能够与读取操做同时发生,只要读取者只能看到完成修改的结果便可 修改是在容器数据结构的某个部分的一个单独的副本(有时是整个数据结构的副本)上执行的,而且这个副本在修改过程当中是不可视的, 只有当修改完成时,被修改的结构才会自动地与主结构进行交换,以后读取者就能够看到这个修改了 CopyOnWriteArrayList CopyOnWriteArraySet ConcurrentHashMap ConcurrentLinkedDeque ReadWriteLock:对数据结构相对不频繁的写入,可是有多个任务要常常读取这个数据结构的这类状况进行了优化 仅容许一个写入者持有锁,容许多个读取者同时读取 活动对象:每一个对象都维护着它本身的工做器线程和消息队列,而且全部对这种对象的请求都将进入队列排队,任什么时候刻都只能运行其中的一个 有了活动对象://这段话我是没咋明白意思 1 每一个对象均可以拥有本身的工做器线程 2 每一个对象都将维护对它本身的域的所有控制权 3 全部在活动对象之间的通讯都将以在这些对象之间的消息形式发生 4 活动对象之间的全部消息都要排队 总结: 使用多线程的主要缘由: * 要处理不少任务,它们交织在一块儿,应用并发可以更有效地使用计算机(包括在多个CPU上透明地分配任务的能力)//等待输入/输出时使用CPU * 要可以更好地组织代码//仿真 * 要更便于用户使用//长时间下载过程当中监视“中止”按钮是否被按下 多线程的主要缺陷: 1 等待共享资源的时候性能下降 2 须要处理线程的额外CPU花费 3 糟糕的程序设计致使没必要要的复杂度 4 有可能产生一些病态,如 饿死 竞争 死锁 活锁 5 不一样平台致使的不一致 ---------------------------------------------------------- 常见任务写法: public class Task implements Runnable{ //... public void run(){ try{ while(!Thread.interrupted()){ //do something } }catch(InterruptedException e){ //... } } } 使用synchronized方法是最经常使用的方法
--------------------------------------------------------笔记底线------------------------------------------------------------------