Java详细总结(精辟版++)-2_第1页
Java详细总结(精辟版++)-2_第2页
Java详细总结(精辟版++)-2_第3页
Java详细总结(精辟版++)-2_第4页
Java详细总结(精辟版++)-2_第5页
已阅读5页,还剩27页未读 继续免费阅读

付费下载

VIP免费下载

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Java总结--开始于2013-9-709:27:48▉Java概述Java分类:javaSE:主要是桌面程序、控制台程序开发,是java基础javaEE:企业级开发(网站开发和基于b/s模式的编程)javaME:嵌入式开发(手机软件、小家电)第一个Java程序:publicclassJavaTest{ privateStringstr=newString("hellojava."); publicvoidshowMessage(){ System.out.println(str); } publicstaticvoidmain(String[]args){ JavaTesttest=newJavaTest(); test.showMessage(); }}Java可以有多个main函数。每个类中都可以有main函数,也就是说你的代码可以有多个入口,你只需要明白什么时候用哪个类的main函数作为入口即可。但是要注意的是用不同入口启动的两个进程是毫不相关的。学习Java前的心理准备: java他妈的把c/c++剔除的够干净的,在c/c++中易如反掌的事,在java中却要大动干戈。所以在学习java之前,请做好心理准备,java其实是一个残疾人。▉Java关键字、运算符Java保留字一览表:java中goto是保留字,但不是关键字,请记住java中没有goto,但可以实现类似的功能,参考百度。java中没有sizeof,因为java认为c语言中的sizeof主要用于跨平台,而java本身就是跨平台的。java中没有unsigned,也就是说java中的基本数据类型都是有符号的。这点要注意一下。请注意下面的代码:inta=…;intb=…;intc=a–b;以上代码只有在a和b都是无符号的int时才能正确工作。对于java这种没有unsigned类型的语言来说,上述代码就是一个编程错误!因为int不足以容纳两个有符号int的差,当a是很大的正整数,b是很大的负整数时,a-b就会溢出并返回负值。Java运算符: java运算符和c、c++的运算符类似,功能也类似。但也有不同,比如java中有无符号右移运算(逻辑右移):>>>。同时注意:用于String的“+”和“+=”是java中仅有的两个重载过的运算符,java不允许程序猿重载任何的运算符。▉Java数据类型Java是强数据类型的语言,java基本数据类型: boolean:trueorfalse char:16bits,Unicode编码字符 byte:8bits short:16bits int:32bits long:64bits float:32bits,floatvar=32.5f,注意加上“f”,否则带小数点的值都会被java当作double处理。 double:64bits在Java中,主数据(primitive)类型都有对应的被包装过了的类,以第一个字母大写开头,例如:Boolean、Integer、Character、Byte、Short、Long、Float、Double。 主数据类型可以与其对应的对象相互转化,在运算时,Java5.0以后会自动进行转换,所以很方便。在使用ArrayList时,你只能声明对象,例如:ArrayList<Integer>(其实这是泛型),而不能使用ArrayList<int>,但是在使用ArrayList时,可以直接使用int类型。Java字节序: java在任何系统上的字节序都是大端(Big-endian),因为java是跨平台的嘛。网络传输的字节序也是大端。因此java程序和java程序之间进行数据传输,并不会出现什么问题。但是当java遇到字节序是小端(Little-endian)的数据时,就会出现问题。这时我们就需要转换数据的大小端。方法有很多,你可以自己写方法实现大小端的检测和转换,但是作为java程序猿来说,我们应该尽量使用已有的类库或者方法来解决问题,以提高生产率。下面介绍几种java类库中已有的解决办法:使用java.nio.ByteBuffer类中的order方法将字节序改为小端。如:buffer.order(ByteOrder.LITTLE_ENDIAN);使用Integer等基本数据类型对应的类类型中的静态方法将int等数据类型的数据转换为小端字节序的数据。如:Integer.reverseBytes(num);另外,在网络传输时,还需要注意其他问题:java没有unsigned;java数据类型的长度和其他语言的可能不同。同时,IEEE754标准只是规范了float、double的逻辑规范,其存储时还是会有字节序问题,因此在不同系统或者编程语言之间进行传输数据时,也要考虑字节序问题。byte、int的转换: 由于java中所有的基本数据类型都是有符号的,所以当你要把byte类型的数据当作无符号数据看待时,你就得使用特殊的方法将其存放在更大容量的数据类型里边,比如将byte转换之后存放在int中。下面说下byte和int的相互转化:byte转换为int有两种方法:不改变原值,也即转换前后都将其看作有符号数,可使用强制类型转换:inti=(int)b;保留最低字节的个个位不变,则需采用位操作:inti=b&0xff;int转换为byte可以直接使用强制类型转换:byteb=(byte)i;,注意这个操作是直接截取int中最低一个字节,如果int数值大于127且小于256,则转换得到的byte为负值(因为是有符号的,但其二进制位和int的最低字节完全相同),如果int数值大于255,则转换得到的byte就会面目全非,因为截断了。▉字符串String: String是不可变字符串对象,任何修改String的操作,实际上返回的都是一个全新的String对象。String中保存的是Unicode格式的16位字符组成的字符串,String永远是Unicode编码。“+”、“+=”可以用来连接字符串。由于String是不可变的,因此在连续的字符串连接时,会不断的生成中间的String对象,以得到最终的String对象。这样的话,效率是很低的,java编译器为了提高效率,会暗地里为我们做一些优化,它用StringBuilder来处理中间过程的字符串连接,并最终使用toString()方法(从Object继承来的,当你要打印该对象时,你就要覆盖toString()方法)将最终结果转换为String对象。在程序中,比如在循环连接字符串时,为了效率,最好使用StringBuilder来处理中间结果。StringBuilder是可变的,类似的还有StringBuffer,不过StringBuffer是线程安全的,因此开销相对也会大点。byte、char、String的转化:String.getBytes(“GBK”)取得其“GBK”编码格式的字节数组,“GBK”指出将字符串转换为何种编码并保存到字节数组,取得的字节数组的内容就是该字符串在“GBK”编码下的字节数组。String.getChars()将String转为char[]。newString(byte[]bytes,“UTF-8”),将bytes字节数组以“UTF-8”编码格式转换为Unicode,“UTF-8”指出字节数组是以什么格式编码的,并不是最终要转换成的编码。newString(chars)将char类型的数组chars转换为String类型。▉数字的格式化 Java中数字的格式化与C语言的很相似。 将数字以带逗号的形式格式化:String.format(“%,d”,1000000);注意:%和d之间有一个逗号。 Java中日期的格式化:String.format(“%tc”,newDate());其中,%tc表示完整日期,%tr只显示时间,%tA%tB%td周月日 也可以这样写:Stringtime=newSimpleDateFormat("yyyy-MM-dd").format(newDate()); 取得当前日期可以使用Date对象,Date中getTime()、setTime()都是针对GMT时间,toString()则会根据时区将时间转换为相应字符串,但其他关于日期的操作最好使用Calendar对象,具体参见Java手册。▉Java控制语句java中控制语句与c、c++类似,但与C/C++不同的是,java中的while、if等的条件判断,不能直接使用类似于int型的变量(看其是否等于0)来判断,应该用一个判断语句或者直接使用boolean类型的变量。特殊循环语句:循环for(intcell:cells){}中,冒号(:)代表in,cells为数组,每循环一次cells数组的下一个元素都会赋值给cell变量。这是java5.0(Tiger)开始使用的for循环加强版,当然你也可以使用for原版:for(inti=0;i<n;i++){}foreach循环可以用于数组和任何实现了Iterable接口的类(这并不意味着数组肯定也是一个Iterable)。▉类和对象java中的所有东西都包含在类中,在java源文件中可以包括多个类,但是只能有一个public类,而且如果有public类的话,这个文件的名字要和这个类的名字一样。一个类中可以有多个方法,一个方法中可以有多个语句。Java存取权限和存取修饰符: Java有4种存取权限和3种存取修饰符,因为有一个缺省的权限等级(无修饰符)。 4种存取权限:public:...private:...default:只有在同一个包中的默认事物能够存取protected:受保护的部分,运行起来像是default,但也允许不在同一个包中子类继承受保护的部分 3种存取修饰符:public、private、protected对象与引用:java中没有所谓的对象变量,有的只是对象的引用,对象只存在于堆上,并且只能通过其引用进行引用。java创建对象不能通过类似于“Aa;”的形式直接定义,这只是定义了一个对象引用变量,而需要通过类似于“Aa=newA();”的形式在堆上定义一个对象,并将在栈上的对象的引用指向这个对象。c++中对象作为参数,是按照值传递。其实java也是值传递,只不过java中只有对象的引用,而没有所谓的“对象变量”,故而传递的都是对象的引用的值,因此在函数中可以改变引用参数指向的对象。注:java中String类型的对象作为函数参数传递时,也是传递引用的值,而不是所谓的“值传递”,因为String类是不可变的,它没有提供自身修改的函数,每次修改操作都是新生成一个String对象,所以要特殊对待,可以认为是传值。关于这一点,网上实在是有太多的流言蜚语,有很多流言是错误的,请务必注意。那么有没有办法使函数不能够修改引用参数指向的对象呢?答案是传递对象副本的引用。为了使用对象的副本作为函数参数,可以使用对象的clone()方法对其进行浅层复制(具体细节请百度之),所谓的浅层复制即是只复制对象本身,而不对其引用的或者其成员变量引用的对象进行复制,也即不对整个对象网进行复制。对对象网进行复制(深层复制),另有它法,请百度之。对象相等性的判断:“==”或者“!=”只能用来判断对象的引用是否相等,若需判断对象是否相等,则需equals(),但一般你需要覆盖equals(),因为Object中的equals()默认使用对象的地址判断对象是否相等。但基本类型直接使用“==”或者“!=”即可。实例变量与局部变量:实例变量是声明在类内,而不是方法中,实例变量有默认值。局部变量是声明在方法中的,局部变量没有默认值,在使用之前,必须初始化,否则编译器报错。关于初始化的更多细节:java中成员变量可以在定义的后边直接进行初始化,也可以在构造函数中进行初始化。而c++中成员变量不能在定义类时直接初始化,需通过构造函数初始化。final:声明一个类或方法到达继承树的末尾,可以用final,也即禁止该类被继承或方法被覆盖。同时请注意final方法是前期绑定的,将方法声明为final可以有效关闭该方法的动态绑定机制,但多数情况下并不会提升多少效率,因此不要为了提升效率而随意使用final来声明方法。静态的方法:用static声明的方法是静态的方法,静态方法不需要实例化的对象,就可以通过类名来引用(当然也可以通过对象来引用),但静态的方法不可以使用非静态的变量,也不能调用非静态的方法。静态的变量:用static声明的变量是静态变量,静态变量为该类的所有对象共享,注意static不能作用于局部变量,只能作用于成员变量,因此只有成员变量才能是静态变量。 静态变量也会有默认值,就像实例变量一样会被自动初始化一样。静态变量会在该类任何的静态方法被调用之前被初始化,也会在该类任何的对象被创建之前被初始化。你可以像初始化其他成员变量一样,直接在定义处初始化,也可以使用特殊的语法结构进行初始化——静态块,如下: publicclassSpoon{ staticinti; static{ i=100;}}还有一种类似的初始化非静态成员变量的语法——实例初始化块,只是没有static关键字而已。静态的final变量:静态的final变量是常数。其声明形式为:publicstaticfinalvar_name;静态的final变量必须被显式初始化,可以用以下两种方法之一初始化:1、声明时赋值:publicstaticfinaldoublePI=3.1415926;2、在静态初始化程序中:publicstaticfinaldoublePI;static{PI=3.1415926;}Java终极对象:在java中的所有类都是从Object这个类继承出来的,也就是说Object是所有类的父类,它是非抽象的,允许被实例化。你可以显式声明类的继承对象为Object:publicclassAnimalextendsObject{},如果不显式声明的话,java编译器会隐含的进行继承。ArrayList就是通过声明Object对象为方法参数来处理所有的对象的,否则他不可能实现处理所有对象的功能。super与this:通过super可以调用父类中的方法,例如:super.isEmpty()。通过this可以调用本类中的方法,例如:this.fun()。实际上,java会将this作为一个隐含的参数传递给类中的每一个方法。构造函数: 在构造器中调用具有多态行为的函数时,容易引起隐蔽的错误。为此,这里有一条有效的准则:“用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其他方法,尤其是别调用具有多态行为的方法”。可以通过super()调用父类的构造函数,如果子类没有显式的调用父类的构造函数,则编译器会在子类的构造函数第一行加上super()调用,当然这里只能调用其父类的无参数的构造函数。当然你也可以自己写出具有参数的super()函数来调用父类的带参构造函数。可以通过this()从某个构造函数中调用同一个类的另外一个构造函数。this()只能存在于构造函数中,且同super()一样,只能位于第一行语句。因此super()与this()不可兼得。垃圾回收器和对象清理:对于java中的垃圾回收器和对象的清理工作,请记住:垃圾回收器可能会被调用,也可能不会被调用,即使其被调用,它也可能以它想要的任何方式来回收垃圾。因此,对它不要抱过多幻想。当下,除了回收内存以外的其他任何事情(比如:已经打开的文件、网络连接、在屏幕上绘制的图形、甚至可以是外部世界的某个开关),你最好不要依赖于垃圾回收器,也不要依赖于finalize()(finalize()是被垃圾回收器调用的,使程序猿可以在垃圾回收器运行时做出一些重要的动作,比如说回收一些非正常方式分配的内存),最好的办法就是程序猿自己编写好特定的方法调用即可,比如在finally中调用之。▉抽象类、抽象方法抽象类:声明形式为:abstractclassDogextendsAnimal{}抽象方法:抽象方法允许你声明方法的名称、参数以及返回类型,但没有实体,直接以分号结束。例:publicabstractvoideat();抽象方法的意义是就算无法实现出方法的内容,但还是可以定义出一组子型共享的协议。注意:①如果你声明一个抽象方法,那么也必须将该类标记为抽象的,你不能在非抽象类中拥有抽象的方法。②第一个具体子类必须实现所有的抽象方法。▉内部类内部类可以使用外部类所有的方法与变量,就算是私有的,也照样可以使用。当然,这是说内部类的实例可以存取它所属的外部类实例的所有方法与变量。一个内部类被嵌套多少层并不重要,它都能够透明地访问所有它所嵌入的外围类的所有成员。 在外部类中创建内部类的实例,就像普通的类的创建一样,例如:MyInnerinner=newMyInner(); 在外部类以外的程序代码创建内部类的实例,要用特殊的语法:MyOuterouter=newMyOuter();MyOuter.MyInnerinner=outer.newMyInner(); 如果你需要生成对外部类对象的引用,可以使用外部类的名字后边紧跟圆点和this的特殊语法,如:MyOuter.this 使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。也就是说内部类允许以继承多个非接口类型(类或者抽象类)的方式来实现“多重继承”。▉匿名内部类Objectobject=newObject(){ ... }; 以上代码创建了一个继承自Object的匿名内部类的对象。如果初始化匿名内部类的成员变量,可以直接在变量定义的后边初始化,也可以使用类似于构造器的方法初始化——实例初始化块(匿名内部类没有名字,所以也不可能有构造器)。但在初始化时,如果匿名内部类需要用到外部方法中的变量(外部方法的形参或者外部方法的局部变量),那么外部方法中的该变量必须声明为final的。同时注意:匿名内部类既可以继承类,也可以扩展接口,但是不管是什么,一次只能一个啊。▉静态类 静态类包含在外部类中,但用static修饰,在实例化时不需要有外部类的实例。▉重载与覆盖java中,派生类可以随意重载基类中已经拥有的方法,并且使用基类中已经拥有的方法和使用自己重载的方法一样容易,但是如果你想覆盖基类中的方法时最好加上注解:@Override,以防止弄错了,没有覆盖,反而成了重载。注意方法的类型由方法名和参数列表和参数类型组成,方法返回值和方法名后边跟的异常说明不属于方法类型的一部分。▉组合与继承组合:就是在类中定义其他类的引用,并使用之。继承:声明形式为:publicclasschild_nameextendsfather_name{}在面向对象的编程中,要谨慎使用继承,因为继承可能会导致脆基类问题,也即当你更改超类时可能会破坏子类。关于应该使用组合还是使用继承,看看是否需要向上转型,如果需要则考虑使用继承,如果不需要则考虑要不要使用继承。▉多态java中除了static方法(构造器实际上也是static方法,只不过该static声明是隐式的)和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。多态正是基于此机制。同时请注意,对于成员变量并不具有多态性啊。▉接口(interface) 类可以有多个接口,接口的意义在于多态!在C++中允许多重继承,即一个子类可以有多个父类;但在Java中是不允许一个子类有多个父类的,这样也是为了简单化,防止致命方块的复杂性。接口可以用来解决多重继承的问题,却也不引起致命方块的问题。接口解决致命方块的方法很简单:那就是把全部的方法设置为抽象的,就相当于100%的纯抽象类。接口允许创建者确定方法的名字、参数、以及返回类型,但是没有任何方法体,以分号结束。接口也可以包含域,但是这些域隐式的是static和final的。静态类也可以作为接口的一部分,放到接口中的任何类都自动地是public和static的。接口的定义: publicinterfacePet{},定义Pet类为接口。接口的实现: publicclassDogextendsAnimalimplementsPet{},Dog继承自Animal,以Pet为接口。注意:一个实现该接口的具体类必须实现该接口中的所有方法。▉枚举(enum)暂时还没东东呢▉类型信息Java允许运行时识别对象和类的信息,运行时类型信息识别主要有两种方式:RTTI、反射(折射折射你在哪儿)。RTTI: 通过RTTI(Run-TimeTypeInformation,运行时类型信息),程序能够使用基类的引用来检查这些引用所指的对象的实际派生类型。同时注意:RTTI实际上允许我们对特定的类进行特定的处理,这对于有过程化编程经验的程序猿来说,是不是很合口味?但是我们在面向对象的编程中,应该尽量使用多态机制,来进行普适操作,然而使用多态机制要求你对基类拥有控制权。因此对于RTTI和多态要进行合理取舍。Java的RTTI与Class对象密切相关。Class: Class是java中的一个类(java.lang.Class<T>),只不过名字有点特殊罢了。那么这个类抽象了什么?它的实例又表示了什么呢?在一个运行的程序中,会有许多类和接口存在。我们就用Class这个类来表示对这些类和接口的抽象,而Class类的每个实例则代表运行中的一个类。所谓抽象,就是提取这些类的一些共同特征,比如说这些类都有类名,都有对应的hashcode,可以判断类型属于class、interface、enum还是annotation。 需要注意的是,这个特殊的Class类没有公共构造方法。Class对象是在加载类时由Java虚拟机以及通过调用类加载器中的defineClass方法自动构造的,因此不能显式地声明一个Class对象。 虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。 基本的Java类型(boolean、byte、char、short、int、long、float和double)和关键字void也都对应一个Class对象。

每个数组属于被映射为Class对象的一个类,所有具有相同元素类型和维数的数组都共享该Class对象。

一般某个类的Class对象被创建,它就用来创建这个类的所有对象。 获取Class对象的三种方法:调用Object类的getClass()方法来得到Class对象,这也是最常见的产生Class对象的方法。使用Class类的中静态forName()方法获得与字符串对应的Class对象。获取Class类型对象的第三个方法非常简单。如果T是一个Java类型,那么T.class就代表了匹配的类对象。类型转换前的检查: java会在类型转换之前进行严格的检查,确保类型转换的正确性。主要有以下几种形式:传统的强制类型转换,如Bb=(B)a;由RTTI确保类型转换的正确性。代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。使用关键字instanceof,它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。如:if(xinstanceofDog)((Dog)x).bark();对instanceof有比较严格的限制:只可将其与命名类型进行比较,而不能与Class对象所比较。Class类提供了isInstance()方法,可以用来动态的测试对象的类型。反射: RTTI有一个限制:这个类型在编译时必须是已知的,这样才能使用RTTI识别它。但当你获取了一个指向某个并不在你的程序空间中的对象的引用,你该如何使用这样的类呢?这时反射闪亮登场。java.lang.Class类和java.lang.reflect类库一起对反射的概念进行了支持。该类库包含了Field、Method以及Constructor(每个类都实现了Member接口)。这些类型的对象是由jvm在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()、getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何的事情。 重要的是,要认识到反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,jvm只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样)。在用它做其他事情之前必须先加载那个类的Class对象。因此,那个类的.class文件对于jvm来说必须是可获取的:要么在本地机器上,要么可以通过网络取得。所以RTTI和反射之间真正的区别只在于,对RTTI来说,编译器在编译时打开和检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。 通常你不需要直接使用反射工具,但是它们在你需要创建更加动态的代码时会很有用。反射在java中是用来支持其他特性的,例如对象序列化和JavaBean。▉Java中的泛型 泛型意味着更好的类型安全性。不过,基本类型无法作为类型参数,这是java泛型的一个限制,不过在java5.0以后基本类型会和它的包装类型进行自动转换,这个是不是很方便了啊。定义泛型的两种方式:使用定义在类声明或接口声明的类型参数,如下:publicclassArrayList<E>extendsAbstractList<E>implementsAbstractInterface<E>{ publicbooleanadd(Eo){…}}publicinterfaceAbstractInterface<E>{ Efun(Ee);}使用未定义在类声明或接口声明的类型参数,也即泛型方法。对于一个static的方法而言,它无法访问泛型类的类型参数,它只能使用泛型方法。泛型方法定义如下:public<T>voidtakeThing(ArrayList<T>list){…} 或者 publicvoidtakeThing(ArrayList<?>list){…}java泛型由擦除实现: 然而遗憾的是,java中的泛型是个伪泛型。由于历史的原因,为了迁移的兼容性,java泛型由擦除(类型擦除)实现。java泛型只在程序源码中存在,编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除,在编译后的字节码文件中,就已经被替换为原来的原生类型(RawType,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList<Integer>与ArrayList<String>就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖。一般而言,你会发现泛型在类或方法的边界处很有效,而在类或方法的内部,由于泛型类型参数被擦除,泛型变得不大有效了。这意味着:在泛型代码的内部,你无法获得任何有关泛型参数类型的信息,你无法知道确切类型信息,任何在运行时需要知道确切类型信息的操作都将无法进行。如果你确实需要一个确切的类型信息,你可以传入你的类型的Class对象,然后就可以使用Class对象的isInstance()、newInstance()等方法。协变、逆变、上界、下界: 拿ArrayList为例,如果S是T的子类型,那么ArrayList<S>是ArrayList<T>的子类型,就称ArrayList是协变的;相反,如果ArrayList<T>是ArrayList<S>的子类型,那么ArrayList是逆变的;既不是协变又不是逆变的是不变的,不变的又叫严谨的。 java数组是协变的,如果Number是Integer的超类型(事实也是如此),那么Number[]也是Integer[]的超类型,但是协变数组在写入数组的操作时可能会出安全问题,为此,java把每个数组对像在创建时附标一个类型,每当向数组存入一个值,编译器插入一段代码来检查该值的运行时类型是否等于数组的运行时类型。如果不匹配,会抛出一个ArrayStoreException。 虽然java的数组是协变的,但java的泛型是“不变”的,不过在使用类型时可以通过使用extends指定类型边界的上界和super指定类型边界的下界来达到协变逆变的目的。以下是一些关于通配符、extends以及super的规则: <T>与<TextendsObject>等同,而<Integer>则是指定了一个具体的类型;<?>与<?extendsObject>等同,?为通配符;<?extendsT>指定类型的上界,表示参数化类型可能是T或是T的子类;<?superT>指定类型下界,表示参数化类型可能是T或是T的超类,直至Object。 使用extends和super时很容易产生迷惑,以下代码注释掉的部分编译错误:staticclassFood{}staticclassFruitextendsFood{}staticclassAppleextendsFruit{}staticclassRedAppleextendsApple{}List<?extendsFruit>flist=newArrayList<Apple>();//complieerror://flist.add(newApple());//flist.add(newFruit());//flist.add(newObject());flist.add(null);//onlyworkfornull其中,List<?extendsFruit>表示“具有任何从Fruit继承类型的列表”,实际上是将List声明为协变的,但由于编译器无法确定List所持有的是Fruit的哪个具体子类型,所以无法安全的向其中添加对象,但是你可以从中获取元素。相反,你可以向List<?superFruit>类型的List中添加元素,却无法获取元素。实际上,在使用extends(协变)和super(逆变)时,应该遵循PECS法则。PECS法则:PECS指“ProducerExtends,ConsumerSuper”。生产者需要用extends(协变),比如说方法的返回值(作为生产目的),比如说你要从一个集合中取值。消费者需要用super(逆变),比如说方法的参数(作为消费目的),比如说向集合赋值。如果你是想遍历collection,并对每一项元素操作时,此时这个集合时生产者(生产元素),应该使用Collection<?extendsThing>;如果你是想添加元素到collection中去,那么此时集合时消费者(消费元素)应该使用Collection<?superThing>。如果你想要一个集合内的类型是一个范围,并且同时能够取值和赋值,这是做不到的,这时候你只能用固定类型,比如List<T>。我们还可以这样理解:子类生产父类用。比如:函数的参数是消费者,函数参数指定了更加超类的东东,调用该函数就需要生产东东,所以调用的地方就需要更加具体化的子类。但是返回值正好相反,函数返回更加具体化的东东,你需要更加超类的东东来保存之。继承中的协变逆变: c++、java、scala都支持返回值协变,也就是说在继承层次中子类覆盖超类的方法时,可以指定返回值为更具体的类型。c#不支持返回值协变。允许参数逆变的面向对象语言并不多——c++、java、scala和c#都会把它当成一个函数重载。使用类型参数实例的方法或变量: 在泛型代码内部,擦除会进行到类型边界,如果没有指定类型边界的话,类型边界就默认是Object。此时,当你在泛型代码的内部,使用一个类型参数实例的方法或者字段时,由于类型信息被擦除了到了Object,故编译器就会报告错误:说该方法或字段不存在。要想使用类型参数实例的方法或者变量该怎么办呢?就得使用extends或super指定类型边界。这样一来,你就会觉得java泛型的自由性太差,实际上就是如此。使用泛型: 如上所述,定义泛型类库非常之复杂,但使用泛型类库相对简单一些。在使用泛型类创建对象时,必须指定类型参数的值。在使用泛型方法时,却可以像调用普通方法一样,而无需指定类型,因为编译器会进行类型参数推断。何时该使用泛型:何时应该使用泛型?历史导致了java的泛型算不上真正的泛型,java泛型唯一的作用就是编译期类型检查,还有就是适应C++程序员的编程习惯。至于说泛型可以消除强制转换,也只是在掩耳盗铃而已。泛型的其他作用实际上可以用继承、多态等方式替代。java程序员乱用泛型都是装B而已。▉数组 在java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。java会对数组进行边界检查,一旦出现数组访问越界,就会出现运行时错误(异常)。数组也是对象:数组中的元素都是变量,要么是8种基本数据类型中的一种,例:int[]array_name=newint[7];要么是引用变量,例:Dog[]dogs=newDog[7];数组中的变量都是Dog引用,故还需创建Dog对象,使数组指向新创建的Dog对象。基本类型的数组如果不进行显式的初始化,会自动初始化为0,对象数组会被初始化为null。多维数组:在大多数语言里,创建多维数组就像一个方阵,但在Java里,二维数组就是数组的数组,三维数组就是数组的数组的数组。比如:4*2的数组,在java里,是由5个数组连接而成的。比如: int[][]a2d=newint[4][2];上面的代码会让java虚拟机创建出4个元素的数组,这些元素实际上是对2个元素数组的引用变量。对于多维数组的操作,大部分与C语言类似。 另外,多维数组中构成矩阵的每个向量都可以具有任意的长度,这被称之为粗糙数组。Arrays: java.util.Arrays是工具类,该类包含了操作数组的各种静态方法(比如排序和搜索),该类也包含一个静态工厂,允许将数组视为列表。 Arrays.equals():比较两个数组是否相等。此比较是基于内容的。 Arrays.deepEquals():用于多维数组是否相等。 Arrays.fill():用同一个值填充数组,或者是同一个基本类型,或者是对象的同一个引用。 Arrays.sort():对数组进行排序。 Arrays.binarySearch():对已经排序的数组进行二分查找。 Arrays.asList():接收任意的序列或数组作为其参数,将其转换为List容器。 Arrays.copyOf():复制得到新的数组,内部实现是调用System.arraycopy()。System.arraycopy():复制数组,速度比for循环要快很多,它针对所有类型做了重载。基本类型数组和对象数组都可以复制,但如果是对象数组,那么只是复制了对象的引用,而不是对象本身。这也称作浅复制。java.lang.reflect.Array:此类提供了动态创建和访问

Java

数组的一些静态方法。 即使数组的效率很高,但是当你在选择时,应该优先选择使用容器,除非在性能成为问题时,再考虑重构使用数组。▉容器、迭代器容器(Collection): 由于c++中容器之间没有任何的公共基类,容器之间的所有共性通过迭代器实现;但在java中,容器之间的所有共性通过公共基类(可以向上转型)和迭代器共同实现。 如上图,容器主要分为两类:Collection(List、Set、Queue)、Map。另外,还有一些过时了的类:Vector、Stack、Hashtable。 List:顺序的好帮手。是一种知道索引位置的容器,可以有多个元素引用相同的对象。List主要有:ArrayList和LinkedList(LinkedList还添加了可以使其用作栈、队列或双端队列的方法)。 Set:注重独一无二的性质。不会有多个元素引用相同的对象,被认为相等的对象也不可以。Set主要有:HashSet(不关心对象存储顺序)、TreeSet(按照比较结果升序保存对象)、LinkedHashSet(按照被添加的顺序保存对象)。HashSet和LinkedHashSet是通过hashCode()和equals()方法来判断对象是否相等,首先判断hashCode()返回值是否相同,若相同则进一步用equals()判断是否对象相等。因为哈希码是通过哈希函数确定的,因此不相等的对象的哈希码可能会相同。由于Object中的hashCode()方法默认用对象的地址计算哈希码,这会导致两个相等(但不同)的对象的哈希码不一样。因此,在使用HashSet和LinkedHashSet时,我们需要覆盖hashCode()和equals()方法。编写hashCode()的常规协定是:在Java应用程序执行期间,在对同一对象多次调用hashCode方法时,必须一致地返回相同的整数,前提是将对象进行equals比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。如果根据equals(Object)方法,两个对象是相等的,那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果。如果根据equals(java.lang.Object)方法,两个对象不相等,那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。尽管在使用TreeSet时,我们不需要覆盖hashCode()方法,但对于良好的编程风格而言,你应该在覆盖equals()的同时,总是覆盖hashCode(),并保持hashCode()与equals()的一致性。另外,TreeSet中存放的对象必须实现Comparable接口,因为它要维护对象的大小顺序。 Queue:只允许在容器的一端插入对象,并从另一端移除对象。除了并发应用,Queue在javase5中仅有的两个实现是LinkedList和PriorityQueue。 Map:用key来搜索的专家。两个key可以引用相同的对象,但是key不能重复。Map主要有:HashMap(不关心对象的存储顺序)、TreeMap(按照比较结果的升序保存键)、LinkedHashMap(按照插入顺序保存键)。迭代器(Iterator): 迭代器是一种轻量级的对象,它允许程序猿不必关心底层的细节,而很方便的遍历容器中的对象。java中的Iterator只能单向移动,ListIterator可以双向移动,但是后者只能适用于各种List类的访问。Iterator的使用方法是:先调用容器的iterator()方法产生迭代器对象,然后就可以使用迭代器的next()、hasNext()、remove()方法来进行其他操作了啊。 任何实现了Iterable接口的类,也都可以使用foreach语法来进行for循环迭代,功能类似于迭代器。排序: Java有两种方式来提供比较功能。第一种是实现java.lang.Comparable接口,使你的类具有“天生”的比较能力。此接口只有一个compareTo()方法,但是compareTo()方法只能对类按一种方式进行比较,当然也可以使用标记来进行不同的比较,但这不是面向对象的完美做法。此时你可以使用第二种比较方法:创建一个实现了java.util.Comparator接口的单独的类,这个接口有compare()和equals()两个方法,一般,你只要实现compare()方法,不一定要实现equals()方法,除非你要用到它。Collections: 像Arrays一样,java.util.Collections也是一个工具类,它提供了一些静态的操作Collection的方法。Collections.sort():对List进行排序。Collections.fill():用同一个值填充List,或者是同一个基本类型,或者是对象的同一个引用。▉异常处理(exception-handling) 异常是Exception类型的对象,Exception继承自Throwable类。我们总是使用new在堆上创建异常对象。标准异常类都有多个构造器:一个是默认构造器,另一个接受字符串作为参数,还有其他的构造器……。你也可以自定义异常类,当然要从Exception类或者其他已有的异常类继承。 执行期间可能会出现异常的方法必须用throws声明其可能会抛出的异常。例如:publicvoidtakeRisk()throwsBadException{…}方法也可以抛出多个异常,当然要throws一一列出。注意这是在函数名后声明,用throws,若在函数中抛出异常,则需用throw。 当你调用可能会出现异常的方法时,编译器会要求你使用try/catch来处理这块代码。例如:try{…}catch(BadExceptionex){…}。注意:如果你不想处理该异常,那么你可以像踢皮球一样的把它踢给(duck给)调用你的方法的方法,这是通过在你的方法上继续用throws声明其可能会抛出的异常来实现的,而此时在你的方法中不需要再使用try/catch处理异常,当然你也可以处理一下,然后再throw出去(注意此时直接throw异常对象的引用即可,无需再new一个此类型的异常对象)。而这样的duck关系链可能会延续到main()函数,而main()函数还可能继续duck掉,那么这个异常就只能交给java虚拟机来处理了,其结果就是java虚拟机会死给你看⊙﹏⊙! finally块用来存放不管有没有异常都得执行的程序。注意:如果try或者catch块有return指令,finally还是会执行,流程会先跳到finally然后再回到return指令。注意:只带有try和finally的方法,必须声明throws异常,将异常duck给调用者。 因为异常也是对象,所以有多态性,故可以throws和catch父类型的对象。可以用父类处理异常,但是这样做并不是好的,应该为每一个异常编写特定的catch,并且catch有一定的顺序:子类的(更详细的)catch应该在父类的前面,否则子类的(更详细的)catch将不会被执行到,这样的错误是通不过编译器的。 如果还是有其他很多异常,并且单次执行结果可以忽略的话(比如有大量数据要获取,丢弃几个也没关系的情况),可以用try{…}catch(Exceptionex){}来catch所有的异常,catch内不做恢复操作,然后在finally里returnnull或者returnfalse即可。但你要记住这样做是有风险的。 对于RuntimeException(NullPointerException、DivideByZero等等),java虚拟机会自动检测并在合适的时候抛出,它们属于java的标准运行时检测的一部分,因此你无需检查何时去throw这些异常,也无需在方法的后边声明该方法会throw这些异常。实际上,大部分的RuntimeException都是因为程序逻辑的问题,而不是真正的无法预测的执行期间失败状况。编译器也不会对RuntimeException异常是否被处理进行强制检查,而其他类型的异常由编译器强制检查并保证。 对于构造器中发生异常,其处理是很棘手的,一定要多多留心啊。注意事项及其解决办法参见:《Java编程思想》中文第四版P271。 关于异常处理,我们要记住一个名叫“吞食有害”的经验法则:在你不知道合适的处理该异常的方法,或者你根本不想去处理该异常时,java强迫你去处理,这就可能导致你草率的去处理该异常,从而可能会导致很隐晦的bug。因为你把异常给吃了,而你却忘了。此时,我认为至少要做的工作是使用printStackTrace()/getStackTrace()打印出异常栈轨迹,这样很方便我们debug呢。▉JavaI/O系统File类:java.io.File:File这个类代表磁盘上的文件或目录的路径名称,并不是文件的内容。并没有读写文件的方法。File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。 java.io.FilenameFilter:文件名过滤器。可以传给File.list()方法,过滤文件名。I/O流:任何自InputStream或Reader派生而来的类都含有名为read()的基本方法,任何自OutputStream或Writer派生来的类都含有名为write()的基本方法。但是,我们通常不会用到这些方法,它们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。我们很少使用单一的类来创建“流”对象,而是通过“叠合”多个对象来提供所期望的功能。实际上,java中“流”类库让人迷惑的主要原因就在于:创建单一的结果流,却需要创建多个对象。而这跟装饰器模式密切相关。 java.io.InputStream和java.io.OutputStream是用于各种不同输入输出源的输入输出字节流类。 java.io.FilterInputStream和java.io.FilterOutputStream以及它们的子类是可以控制其他InputStream和OutputStream的装饰器。 java.io.Reader和java.io.Writer是提供了兼容Unicode与面向字符的I/O功能的字符流类。老的I/O流(InputStream或OutputStream)继承层次结构仅支持8位字节流,并且不能很好地处理16位的Unicode字符。设计Reader和Writer继承层次结构主要是为了方便处理字符(以便国际化),并不是为了取代老的I/O流。另外,新类库的设计使得它的操作比旧类库更快。 java.io.BufferedReader和java.io.BufferedWriter以及它们的子类是可以控制其他Reader和Writer的装饰器。无论何时我们使用readLine()时,都不应该使用DataInputStream(这会遭到编译器的强烈反对),而应该使用BufferedReader,除了这一点,DataInputStream仍是很好的东东。 java.io.FilterReader和java.io.FilterWriter是所有自定义具体装饰器的父类。 java.io.InputStreamReader和java.io.OutputStreamWriter可以把InputStream和OutputStream分别转换为Reader和Writer。实际上,InputStreamReader是字节到字符的桥梁,相反,OutputStreamWriter是字符到字节的桥梁。 java.io.RandomAccessFile是一个自我独立的类,适用于由大小已知的记录组成的文件,可以使用seek()移动文件指针。注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。可以用于多线程下载或多个线程同时写数据到文件。 JavaI/O流类可以组合出很多种使用方式,以下给出几种组合使用方式: newBufferedInputStream(newFileInputStream(…)):从文件读取字节。 newBufferedReader(newFileReader(…)):从文件读取字符。 newStringReader(newString(…)):从内存读取字符。标准I/O: 在java中,System.in为标准输入,System.out为标准输出,System.err为标准错误输出。System.out和System.err是PrintStream对象,可以直接使用;而System.in则是未经过包装的InputStream对象,在使用之前需要对其进行包装。可以使用如下代码将System.in包装为BufferedReader: BufferedReaderstdin=newBufferedReader(newInputStreamReader(System.in));然后就可以使用BufferedReader.readLine()方法读取用户输入了。另外,System.setIn()、System.setOut()、System.setErr()允许对标准输入、标准输出、标准错误输出进行重定向。新I/O: Java1.4的java.nio.*包中引入了新的I/O类库,其目的在于提高速度。实际上,旧的I/O包已经使用nio重新实现过,以便充分利用这种速度提高,因此,即使我们不显式地用nio编写代码,也能从中受益。速度的提高来自于所使用的结构更接近于操作系统执行I/O的方式:通道(Channel)和缓冲器(Buffer)。通道:FileInputStream、FileOutputStream和RandomAccessFile可以通过调用getChannel()产生文件通道java.nio.channels.FileChannel。Reader和Writer这种字符模式类不能用于产生通道,但是java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Writer。FileChannel.transferTo()和FileChannel.transferFrom()允许我们将一个通道和另一个通道直接相连。FileChannel.size()取得通道所对应文件的大小(字节数)。缓冲器:java.nio.ByteBuffer是唯一直接与通道交互的缓冲器。ByteBuffer是抽象类,可以使用静态方法ByteBuffer.allocate()产生ByteBuffer对象。ByteBuffer使用方法如下: position:缓冲区位置指针。 capacity:缓冲区容量,即分配时指定的大小(字节数)。 limit:代表读取或写入的位置限制。ByteBuffer.order():设置缓冲区字节序。ByteBuffer.wrap():将字节数组包装为字节缓冲并返回,并且修改字节缓冲将会影响字节数组,反之则反之。ByteBuffer.array():返回字节缓冲对应的字节数组,并且修改字节数组将会影响字节缓冲,反之则反之。ByteBuffer.flip():回绕缓冲区,将当前位置设置为limit,然后将位置指针设置为0。ByteBuffer.rewind():将位置指针设置为0,不修改limit。以便可以重新使用缓冲区。ByteBuffer.clear():清除缓冲区,将位置指针设置为0,将limit设置为capacity。内存映射文件:java.nio.MappedByteBuffer是ByteBuffer的子类,MappedByteBuffer允许你将太大而不能够放入内存的文件映射进内存,进而把它当作非常大的字节数组来访问,这可以极大地简化操作文件的代码,并且它可以显著地加快速度。MappedByteBuffer对象通过调用FileChannel.map()产生。对象序列化:Java的对象序列化将那些实现了Serializable接口(该接口仅是一个标记接口,不包含任何方法)的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。你还可以在一种操作系统上对对象进行序列化,而在另一个操作系统上对其进行还原操作,这都没有任何问题。所有基本数据类型的封装器、所有容器类以及其他许多东东,甚至Class对象都可以被序列化。(解)序列化的步骤:要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需调用writeObject()即可将对象序列化,并将其发送给OutputStream(对象序列化是基于字节的,因此要使用InputStream和OutputStream继承层次结构)。要解序列化,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们。解序列化需要类信息:解序列化时,需要有序列化对象对应的Class对象,因此你必须保证JVM能够找到相关的.class文件。 序列化的过程控制:当对象被序列化时,该对象的所有实例变量被序列化,同时,所有被包含或者被引用的对象也会被序列化,依此类推,整个“对象网”都会被序列化,并且这些操作都是自动进行的。同时注意:序列化是全有或者全无的,如果其中有一个变量引用的对象不能被序列化,那么这个序列化就是不可行的。此种现象的解决办法是:如果某实例变量不能或者不应该被序列化,就把它标记为transient(瞬时)的。另外,你也可以通过实现Externalizable接口(继承自Serializable接口)来对序列化过程进行控制,关于它的用法请百度之。序列化的版本控制:可以使用serialVersionUID为对象加上版本ID。使用java的serialver工具获取该对象的UID,示例:serialverclass_name。在类中加入变量staticfinallongserialVersionUID,并将刚才获取的UID赋值给这个变量即可。Preferences: PreferencesAPI用于存储和读取用户的偏好(preferences)以及程序的配置项。Preferences是一个“键-值”集合(类似于映射),它只能用于小的、受限的数据集合——我们只能存储基本类型和字符串,并且每个字符串的存储长度不能超过8KB。 通常,我们可以使用静态的Preferences.userNodeForPackage()、Preferences.systemNodeForPackage()等方法产生Preferences对象。然后就可以使用相应的方法读取和写入配置了。 使用Preferences时,程序运行完并不会出现本地文件。PreferencesAPI利用合适的系统资源去完成这个任务,并且这些资源会随操作系统不同而不同,例如在windows系统里就是注册表。而你却不必担心不同的操作系统是怎么运作的。Java与其他编程语言交换数据:java没有结构体,用class代替,但java的class和c/c++中的struct还是有区别的,比如说:你可以将struct一次性写入文件,结构体字段的顺序在内存中和文件中是一样的,更进一步你可以通过设置字节对齐方式来准确控制字段的内存布局。但你却不能这样操作java中的class字段。如果java需要读取c/c++以上述方式写入的文件(或者是其他既定文件格式,比如bmp图像文件),就需要费很大功夫了。你可以自己写程序慢慢解析,但应该尽量使用已有的类库去解决这个问题。这里推荐两个类库:JavaStruct和Javolution。JavaStruct比较轻量级,不支持联合体,但是联合体的问题,在编程中一般是可以避开的。Javolution比较庞大,对结构体和联合体都有支持。另外,Javolution提供一个高性的Java集合(collection)类库和一些实用的工具类。虽然这个类包只提供非常少的几个集合类,但是这些类就能够代替大部分java.util类。javolution可以让你的应用程序更加快速和更实时。Javolution主要用在对实时性要求很强的应用中,对集合操作的耗时更加可预测。 当然,如果对于进行数据交换的程序双方,你都可以控制的话,那么规范程序交换的数据的格式是再好不过了。比如,你可以使用:json、googleprotocolbuffer、SOAP、ASN.1亦或者是xml。▉Java包、包的引入你必须指明程序代码中使用到的类的完整名称,除非是来自于java.lang这个包中。完整路径的指明有两种方法:其一:使用import,例如:importjava.util.ArrayList;其二:在程序的任何使用到的地方都写全名。例如:java.util.ArrayList<String>str_list=newjava.util.ArrayList<String>();问:使用import会把程序变大吗?编译过程会把包或类包含进去吗?答:你一定是个C语言程序员。import与C的include并不相同。运用import只是帮你省下类前面的包名称而已。程序不会因为使用了import而变大或者变慢。注意:在同一目录的类不需要import引用注意:当你以命令行的方式执行java程序时,有时需要指定类库的路径,这时就需要用到“-classpath”。类路径中的内容可以是:文件的目录(包含不在包里面的类),包的根目录(包含已打包的类,是package,不是jar),包含类的档案文件(比如.zip文件或者.jar文件)。在Unix家族的系统上,类路径的各个项目由冒号“:”分隔,在MSWindows系统上,它们由分号“;”分隔。例如在linux上我们可以这样(注意记得在classpath中加上“.”):java-classpathxxx.jar:.xxx如果你的程序使用到了非标准类库,那么你在发布程序的时候,就要将这些非标准类库一起打包,因为用户那里没有。▉程序的包装和部署程序包装和部署的方式主要有:JAR、javawebstart、远程部署RMI、servlet。这里只讲JAR包装,其他的百度之⊙﹏⊙! -d选项可以指定生成的类文件所在的目录。把类打包成JAR文件:将所有的类都放在classes目录下。在classes目录下创建manifest.txt文件,该文件带有这样一行:Main-Class:MyApp 注意:MyApp为你的包含main()方法的类名,后面没有.class后缀,而且此行后面要换行,否则会出错。执行jar工具来创建JAR文件,命令如下:cdMyProject/classesjar–cvmfmanifest.txtapp.jar*.class执行JAR文件:cdMyProject/classesjava–jarapp.jar把类加入包(package)中:选择包名称在类中加入包指令,如:packagecom.headfirstjava;注意:该语句必须位于源文件的最前面,比import还要靠前。设定相应的目录结构。你必须把类放在包名称对应的目录结构中。你的类必须在headfirstjava目录中,而headfirstjava必须在com目录中。编译生成包:cdMyProject/sourcejavac–d../classescom/headfirstjava/*.java执行程序:cdMyProject/classesjavacom/headfirstjava/mypackage //注意:执行时,必须写类的全名从jar包中读取资源文件: 当你把资源文件连同类文件一同打包进jar文件之后,访问其中的资源文件就不能通过File类进行。这时候你就要借助于类装载器(ClassLoader),因为它知道类的运行信息。具体地,ClassLoader类中的getResource()和getResourceAsStream()可以帮助我们获取资源。但ClassLoader是abstract的,不可能实例化对象,更加不可能通过ClassLoader调用上面两个方法。所以我们真正写代码的时候,是通过Class类中的getResource()和getResourceAsStream()方法,这两个方法会委托ClassLoader中的getResource()和getResourceAsStream()方法。 需要注意的是:getResource()和getResourceAsStream()同样也可以获取未打包进jar包的文件。▉在Java中使用Mysql数据库在Java中执行SQL语句时,特别注意:对于静态的SQL语句,可以使用Statement;但是对于字符串里面可能含有单引号的情况,应该使用PreparedStatement,其会对SQL语句进行预编译、转义,之后再执行。此外PreparedStatement还有很多好处,因此推荐使用PreparedStatement,具体请百度之。▉Java并发编程并发基础: 并发“具有可论证的确定性,但是实际上具有不可确定性”。通过仔细设计和代码审查,编写能够正确工作的并发程序是可能的,但是,在实际情况中,很容易发生的是:所编写的代码在特殊条件下将会工作失败。 并发可以使程序更快的执行。对于多处理器的系统,由于具有天生的并行能力,可并发执行的程序当然会如虎添翼。对于单处理器系统,并发会加快程序的执行,则是由于程序运行中经常会出现的“阻塞”,事实上,单从性能的角度考虑,如果没有任务会阻塞,那么在单处理器系统上使用并发就没有任何意义。 并发可以帮助改进代码设计。某些类型的问题,例如仿真(通常涉及很多交互元素),没有并发的支持是很难解决的。Java中基本的线程机制: Java的线程机制是抢占式的,这表示调度机制会周期性地中断线程,将上下文切换到另一个线程。 定义任务:线程可以用来执行任务,要想定义一个任务,有两种方法:实现Runnable接口,并编写run()方法;继承Thread类,并重写run()方法;run()方法中只是包含了要执行的任务,但是它并无特殊之处,调用它并不会产生任何内在的线程能力。 执行线程的传统方法:创建Thread对象,并调用它的start()方法。如果你是上述第一种方式定义的任务,那么你在创建Thread对象时,需要将Runnable对象作为参数传给Thread对象,因为此时Thread的run()需要调用Runnable的run()。 执行线程的优选方法:JavaSE5的java.util.concurrent包中的执行器(Executor)将为你创建Thread,并为你管理Thread对象,你只需调用execute()方法即可。ExecutorService(具有服务生命周期的Executor,例如可以shutdown():执行后不再接受新的任务,已有任务将继续执行完;也可以shutdownNow():执行后不再接受新的任务,并试图终止已有任务,返回尚未执行任务的列表)。你可以使用Executors的静态方法来创建CachedThreadPool、FixedThreadPool、SingleThreadExecutor,它们都将返回ExecutorService,你也可以在调用它们时,传入实现了ThreadFactory接口的对象,以便能够自定义创建线程。如下: ExecutorServiceexec=Executors.newCachedThreadPool(); for(inti=0;i<5;i++) exec.execute(newRunnableTask()); exec.shutdown(); 从线程执行的任务中获取返回值:run()方法包含执行工作的独立任务,但是它不会返回任何值。如果希望任务在返回时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。此时你需要实现call()方法,并且必须使用ExecutorService.sub

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论