Java详细总结(精辟版++)

时间:2024.4.20

Java总结

--开始于20##-9-7 09:27:48

▉Java概述

Java分类:

java SE:主要是桌面程序、控制台程序开发,是java基础

java EE:企业级开发(网站开发和基于b/s模式的编程)

java ME:嵌入式开发(手机软件、小家电)

第一个Java程序:

public class JavaTest {

        

         private String str = new String("hello java.");

        

         public void showMessage() {

                   System.out.println(str);

         }

        

    public static void main(String[] args) {

               JavaTest test = new JavaTest();

               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中的基本数据类型都是有符号的。这点要注意一下。请注意下面的代码:

int a = …;

int b = …;

int c = 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:true or false

       char:16bits,Unicode编码字符

       byte:8bits

       short:16bits

       int:32bits

       long:64bits

       float:32bits,float var = 32.5f,注意加上“f”,否则带小数点的值都会被java当作double处理。

       double:64bits

在Java中,主数据(primitive)类型都有对应的被包装过了的类,以第一个字母大写开头,例如:Boolean、Integer、Character、Byte、Short、Long、Float、Double。

       主数据类型可以与其对应的对象相互转化,在运算时,Java 5.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数据类型的长度和其他语言的可能不同。同时,IEEE 754标准只是规范了float、double的逻辑规范,其存储时还是会有字节序问题,因此在不同系统或者编程语言之间进行传输数据时,也要考虑字节序问题。

byte、int的转换:

       由于java中所有的基本数据类型都是有符号的,所以当你要把byte类型的数据当作无符号数据看待时,你就得使用特殊的方法将其存放在更大容量的数据类型里边,比如将byte转换之后存放在int中。下面说下byte和int的相互转化:

byte转换为int有两种方法:

①       不改变原值,也即转换前后都将其看作有符号数,可使用强制类型转换:int i = (int)b;

②       保留最低字节的个个位不变,则需采用位操作:int i = b & 0xff;

int转换为byte可以直接使用强制类型转换:byte b = (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[]。

new String(byte[] bytes, “UTF-8”),将bytes字节数组以“UTF-8”编码格式转换为Unicode,“UTF-8”指出字节数组是以什么格式编码的,并不是最终要转换成的编码。new String(chars)将char类型的数组chars转换为String类型。

▉数字的格式化

       Java中数字的格式化与C语言的很相似。

       将数字以带逗号的形式格式化:String.format(“%,d”, 1000000); 注意:%和d之间有一个逗号。

       Java中日期的格式化:String.format(“%tc”, new Date()); 其中,%tc表示完整日期,%tr只显示时间,%tA %tB %td 周 月 日

       也可以这样写:String time = new SimpleDateFormat("yyyy-MM-dd").format(new Date());

       取得当前日期可以使用Date对象,Date中getTime()、setTime()都是针对GMT时间,toString()则会根据时区将时间转换为相应字符串,但其他关于日期的操作最好使用Calendar对象,具体参见Java手册。

▉Java控制语句

java中控制语句与c、c++类似,但与C/C++不同的是,java中的while、if等的条件判断,不能直接使用类似于int型的变量(看其是否等于0)来判断,应该用一个判断语句或者直接使用boolean类型的变量。

特殊循环语句:

循环for(int cell : cells) {}中,冒号(:)代表in,cells为数组,每循环一次cells数组的下一个元素都会赋值给cell变量。这是java 5.0(Tiger)开始使用的for循环加强版,当然你也可以使用for原版:for(int i=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创建对象不能通过类似于“A a;”的形式直接定义,这只是定义了一个对象引用变量,而需要通过类似于“A a = new A();”的形式在堆上定义一个对象,并将在栈上的对象的引用指向这个对象。

c++中对象作为参数,是按照值传递。其实java也是值传递,只不过java中只有对象的引用,而没有所谓的“对象变量”,故而传递的都是对象的引用的值,因此在函数中可以改变引用参数指向的对象。注:java中String类型的对象作为函数参数传递时,也是传递引用的值,而不是所谓的“值传递”,因为String类是不可变的,它没有提供自身修改的函数,每次修改操作都是新生成一个String对象,所以要特殊对待,可以认为是传值。关于这一点,网上实在是有太多的流言蜚语,有很多流言是错误的,请务必注意。

那么有没有办法使函数不能够修改引用参数指向的对象呢?答案是传递对象副本的引用。为了使用对象的副本作为函数参数,可以使用对象的 clone() 方法对其进行浅层复制(具体细节请百度之),所谓的浅层复制即是只复制对象本身,而不对其引用的或者其成员变量引用的对象进行复制,也即不对整个对象网进行复制。对对象网进行复制(深层复制),另有它法,请百度之。

对象相等性的判断:

“==”或者“!=”只能用来判断对象的引用是否相等,若需判断对象是否相等,则需equals(),但一般你需要覆盖equals(),因为Object中的equals()默认使用对象的地址判断对象是否相等。但基本类型直接使用“==”或者“!=”即可。

实例变量与局部变量:

实例变量是声明在类内,而不是方法中,实例变量有默认值。局部变量是声明在方法中的,局部变量没有默认值,在使用之前,必须初始化,否则编译器报错。

关于初始化的更多细节:java中成员变量可以在定义的后边直接进行初始化,也可以在构造函数中进行初始化。而c++中成员变量不能在定义类时直接初始化,需通过构造函数初始化。

final:

声明一个类或方法到达继承树的末尾,可以用final,也即禁止该类被继承或方法被覆盖。同时请注意final方法是前期绑定的,将方法声明为final可以有效关闭该方法的动态绑定机制,但多数情况下并不会提升多少效率,因此不要为了提升效率而随意使用final来声明方法。

静态的方法:

用static声明的方法是静态的方法,静态方法不需要实例化的对象,就可以通过类名来引用(当然也可以通过对象来引用),但静态的方法不可以使用非静态的变量,也不能调用非静态的方法。

静态的变量:

用static声明的变量是静态变量,静态变量为该类的所有对象共享,注意static不能作用于局部变量,只能作用于成员变量,因此只有成员变量才能是静态变量。

       静态变量也会有默认值,就像实例变量一样会被自动初始化一样。静态变量会在该类任何的静态方法被调用之前被初始化,也会在该类任何的对象被创建之前被初始化。你可以像初始化其他成员变量一样,直接在定义处初始化,也可以使用特殊的语法结构进行初始化——静态块,如下:

              public class Spoon {

                            static int i;

                            static {

                                     i = 100;

}

}

还有一种类似的初始化非静态成员变量的语法——实例初始化块,只是没有static关键字而已。

静态的final变量:

静态的final变量是常数。其声明形式为:public static final var_name;静态的final变量必须被显式初始化,可以用以下两种方法之一初始化:

1、声明时赋值:public static final double PI = 3.1415926;

2、在静态初始化程序中:public static final double PI;  static {PI = 3.1415926;}

Java终极对象:

在java中的所有类都是从Object这个类继承出来的,也就是说Object是所有类的父类,它是非抽象的,允许被实例化。你可以显式声明类的继承对象为Object:public class Animal extends Object {},如果不显式声明的话,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中调用之。

▉抽象类、抽象方法

抽象类:

声明形式为:abstract class Dog extends Animal {}

抽象方法:

抽象方法允许你声明方法的名称、参数以及返回类型,但没有实体,直接以分号结束。例:public abstract void eat(); 抽象方法的意义是就算无法实现出方法的内容,但还是可以定义出一组子型共享的协议。注意:①如果你声明一个抽象方法,那么也必须将该类标记为抽象的,你不能在非抽象类中拥有抽象的方法。②第一个具体子类必须实现所有的抽象方法。

▉内部类

内部类可以使用外部类所有的方法与变量,就算是私有的,也照样可以使用。当然,这是说内部类的实例可以存取它所属的外部类实例的所有方法与变量。一个内部类被嵌套多少层并不重要,它都能够透明地访问所有它所嵌入的外围类的所有成员。

       在外部类中创建内部类的实例,就像普通的类的创建一样,例如:MyInner inner = new MyInner();

       在外部类以外的程序代码创建内部类的实例,要用特殊的语法:MyOuter outer = new MyOuter();  MyOuter.MyInner inner = outer.new MyInner();

       如果你需要生成对外部类对象的引用,可以使用外部类的名字后边紧跟圆点和this的特殊语法,如:MyOuter.this

       使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。也就是说内部类允许以继承多个非接口类型(类或者抽象类)的方式来实现“多重继承”。

▉匿名内部类

Object object = new Object() {

                            ...

                   };

       以上代码创建了一个继承自Object的匿名内部类的对象。如果初始化匿名内部类的成员变量,可以直接在变量定义的后边初始化,也可以使用类似于构造器的方法初始化——实例初始化块(匿名内部类没有名字,所以也不可能有构造器)。但在初始化时,如果匿名内部类需要用到外部方法中的变量(外部方法的形参或者外部方法的局部变量),那么外部方法中的该变量必须声明为final的。同时注意:匿名内部类既可以继承类,也可以扩展接口,但是不管是什么,一次只能一个啊。

▉静态类

       静态类包含在外部类中,但用static修饰,在实例化时不需要有外部类的实例。

▉重载与覆盖

java中,派生类可以随意重载基类中已经拥有的方法,并且使用基类中已经拥有的方法和使用自己重载的方法一样容易,但是如果你想覆盖基类中的方法时最好加上注解:@Override,以防止弄错了,没有覆盖,反而成了重载。

注意方法的类型由方法名和参数列表和参数类型组成,方法返回值和方法名后边跟的异常说明不属于方法类型的一部分。

▉组合与继承

组合:

就是在类中定义其他类的引用,并使用之。

继承:

声明形式为:public class child_name extends father_name {}

在面向对象的编程中,要谨慎使用继承,因为继承可能会导致脆基类问题,也即当你更改超类时可能会破坏子类。关于应该使用组合还是使用继承,看看是否需要向上转型,如果需要则考虑使用继承,如果不需要则考虑要不要使用继承。

▉多态

java中除了static方法(构造器实际上也是static方法,只不过该static声明是隐式的)和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。多态正是基于此机制。同时请注意,对于成员变量并不具有多态性啊。

▉接口(interface)

       类可以有多个接口,接口的意义在于多态!

在C++中允许多重继承,即一个子类可以有多个父类;但在Java中是不允许一个子类有多个父类的,这样也是为了简单化,防止致命方块的复杂性。接口可以用来解决多重继承的问题,却也不引起致命方块的问题。接口解决致命方块的方法很简单:那就是把全部的方法设置为抽象的,就相当于100%的纯抽象类。

接口允许创建者确定方法的名字、参数、以及返回类型,但是没有任何方法体,以分号结束。接口也可以包含域,但是这些域隐式的是static和final的。静态类也可以作为接口的一部分,放到接口中的任何类都自动地是public和static的。

接口的定义:

              public interface Pet {} ,定义Pet类为接口。

接口的实现:

              public class Dog extends Animal implements Pet {} ,Dog继承自Animal,以Pet为接口。注意:一个实现该接口的具体类必须实现该接口中的所有方法。

▉枚举(enum)

暂时还没东东呢

▉类型信息

Java允许运行时识别对象和类的信息,运行时类型信息识别主要有两种方式:RTTI、反射(折射折射你在哪儿)。

RTTI:

       通过RTTI(Run-Time Type Information,运行时类型信息),程序能够使用基类的引用来检查这些引用所指的对象的实际派生类型。同时注意: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会在类型转换之前进行严格的检查,确保类型转换的正确性。主要有以下几种形式:

①       传统的强制类型转换,如B b = (B)a; 由RTTI确保类型转换的正确性。

②       代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。

③       使用关键字instanceof,它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。如:if(x instanceof Dog) ((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泛型的一个限制,不过在java 5.0以后基本类型会和它的包装类型进行自动转换,这个是不是很方便了啊。

定义泛型的两种方式:

1、  使用定义在类声明或接口声明的类型参数,如下:

public class ArrayList<E> extends AbstractList<E> implements AbstractInterface<E> {

         public boolean add(E o) {…}

}

public interface AbstractInterface<E> {

         E fun(E e);

}

2、  使用未定义在类声明或接口声明的类型参数,也即泛型方法。对于一个static的方法而言,它无法访问泛型类的类型参数,它只能使用泛型方法。泛型方法定义如下:

public <T> void takeThing(ArrayList<T> list) {…}

                   或者

                            public void takeThing(ArrayList<?> list) {…}

java泛型由擦除实现:

       然而遗憾的是,java中的泛型是个伪泛型。由于历史的原因,为了迁移的兼容性,java泛型由擦除(类型擦除)实现。java泛型只在程序源码中存在,编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除,在编译后的字节码文件中,就已经被替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的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> 与 <T extends Object> 等同,而 <Integer> 则是指定了一个具体的类型;

<?> 与 <? extends Object> 等同,?为通配符;

<? extends T> 指定类型的上界,表示参数化类型可能是T或是T的子类;

<? super T> 指定类型下界,表示参数化类型可能是T或是T的超类,直至Object。

       使用extends和super时很容易产生迷惑,以下代码注释掉的部分编译错误:

static class Food{}

static class Fruit extends Food{}

static class Apple extends Fruit{}

static class RedApple extends Apple{}

List<? extends Fruit> flist = new ArrayList<Apple>();

// complie error:

// flist.add(new Apple());

// flist.add(new Fruit());

// flist.add(new Object());

flist.add(null); // only work for null

其中,List<? extends Fruit> 表示“具有任何从Fruit继承类型的列表”,实际上是将List声明为协变的,但由于编译器无法确定List所持有的是Fruit的哪个具体子类型,所以无法安全的向其中添加对象,但是你可以从中获取元素。相反,你可以向List<? super Fruit>类型的List中添加元素,却无法获取元素。实际上,在使用extends(协变)和super(逆变)时,应该遵循PECS法则。

PECS法则:

PECS指“Producer Extends,Consumer Super”。生产者需要用extends(协变),比如说方法的返回值(作为生产目的),比如说你要从一个集合中取值。消费者需要用super(逆变),比如说方法的参数(作为消费目的),比如说向集合赋值。

如果你是想遍历collection,并对每一项元素操作时,此时这个集合时生产者(生产元素),应该使用 Collection<? extends Thing>;如果你是想添加元素到collection中去,那么此时集合时消费者(消费元素)应该使用Collection<? super Thing>。如果你想要一个集合内的类型是一个范围,并且同时能够取值和赋值,这是做不到的,这时候你只能用固定类型,比如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 = new int[7];要么是引用变量,例:Dog[] dogs = new Dog[7];数组中的变量都是Dog引用,故还需创建Dog对象,使数组指向新创建的Dog对象。基本类型的数组如果不进行显式的初始化,会自动初始化为0,对象数组会被初始化为null。

多维数组:

在大多数语言里,创建多维数组就像一个方阵,但在Java里,二维数组就是数组的数组,三维数组就是数组的数组的数组。比如:4*2的数组,在java里,是由5个数组连接而成的。比如:

              int[][] a2d = new int[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声明其可能会抛出的异常。例如:public void takeRisk() throws BadException {…} 方法也可以抛出多个异常,当然要throws一一列出。注意这是在函数名后声明,用throws,若在函数中抛出异常,则需用throw。

       当你调用可能会出现异常的方法时,编译器会要求你使用try/catch来处理这块代码。例如:try {…} catch(BadException ex) {…} 。注意:如果你不想处理该异常,那么你可以像踢皮球一样的把它踢给(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(Exception ex){}来catch所有的异常,catch内不做恢复操作,然后在finally里return null或者return false即可。但你要记住这样做是有风险的。

       对于RuntimeException(NullPointerException、DivideByZero等等),java虚拟机会自动检测并在合适的时候抛出,它们属于java的标准运行时检测的一部分,因此你无需检查何时去throw这些异常,也无需在方法的后边声明该方法会throw这些异常。实际上,大部分的RuntimeException都是因为程序逻辑的问题,而不是真正的无法预测的执行期间失败状况。编译器也不会对RuntimeException异常是否被处理进行强制检查,而其他类型的异常由编译器强制检查并保证。

       对于构造器中发生异常,其处理是很棘手的,一定要多多留心啊。注意事项及其解决办法参见:《Java编程思想》中文第四版P271。

       关于异常处理,我们要记住一个名叫“吞食有害”的经验法则:在你不知道合适的处理该异常的方法,或者你根本不想去处理该异常时,java强迫你去处理,这就可能导致你草率的去处理该异常,从而可能会导致很隐晦的bug。因为你把异常给吃了,而你却忘了。此时,我认为至少要做的工作是使用printStackTrace()/getStackTrace()打印出异常栈轨迹,这样很方便我们debug呢。

▉Java I/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()移动文件指针。注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。可以用于多线程下载或多个线程同时写数据到文件。

       Java I/O流类可以组合出很多种使用方式,以下给出几种组合使用方式:

       new BufferedInputStream (new FileInputStream (…)):从文件读取字节。

       new BufferedReader(new FileReader(…)):从文件读取字符。

       new StringReader(new String(…)):从内存读取字符。

标准I/O:

       在java中,System.in为标准输入,System.out为标准输出,System.err为标准错误输出。System.out和System.err是PrintStream对象,可以直接使用;而System.in则是未经过包装的InputStream对象,在使用之前需要对其进行包装。可以使用如下代码将System.in包装为BufferedReader:

              BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));

然后就可以使用BufferedReader.readLine()方法读取用户输入了。另外,System.setIn()、System.setOut()、System.setErr()允许对标准输入、标准输出、标准错误输出进行重定向。

新I/O:

       Java 1.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,示例:serialver class_name。在类中加入变量static final long serialVersionUID,并将刚才获取的UID赋值给这个变量即可。

Preferences:

       Preferences API用于存储和读取用户的偏好(preferences)以及程序的配置项。Preferences是一个“键-值”集合(类似于映射),它只能用于小的、受限的数据集合——我们只能存储基本类型和字符串,并且每个字符串的存储长度不能超过8KB。

       通常,我们可以使用静态的Preferences.userNodeForPackage()、Preferences.systemNodeForPackage()等方法产生Preferences对象。然后就可以使用相应的方法读取和写入配置了。

       使用Preferences时,程序运行完并不会出现本地文件。Preferences API利用合适的系统资源去完成这个任务,并且这些资源会随操作系统不同而不同,例如在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、google protocol buffer 、SOAP、ASN.1亦或者是xml。

▉Java包、包的引入

你必须指明程序代码中使用到的类的完整名称,除非是来自于java.lang这个包中。完整路径的指明有两种方法:

其一:使用import,例如:import java.util.ArrayList;

其二:在程序的任何使用到的地方都写全名。例如:java.util.ArrayList<String> str_list = new java.util.ArrayList<String>();

问:使用import会把程序变大吗?编译过程会把包或类包含进去吗?

答:你一定是个C语言程序员。import与C的include并不相同。运用import只是帮你省下类前面的包名称而已。程序不会因为使用了import而变大或者变慢。

注意:在同一目录的类不需要import 引用

注意:当你以命令行的方式执行java程序时,有时需要指定类库的路径,这时就需要用到“-classpath”。类路径中的内容可以是:文件的目录(包含不在包里面的类),包的根目录(包含已打包的类,是package,不是jar),包含类的档案文件(比如.zip文件或者.jar文件)。在Unix家族的系统上,类路径的各个项目由冒号“:”分隔,在MS Windows系统上,它们由分号“;”分隔。例如在linux上我们可以这样(注意记得在classpath中加上“.”):

java -classpath xxx.jar:. xxx

如果你的程序使用到了非标准类库,那么你在发布程序的时候,就要将这些非标准类库一起打包,因为用户那里没有。

▉程序的包装和部署

程序包装和部署的方式主要有:JAR、java web start、远程部署RMI、servlet。这里只讲JAR包装,其他的百度之⊙﹏⊙!

       -d选项可以指定生成的类文件所在的目录。

把类打包成JAR文件:

1、  将所有的类都放在classes目录下。

2、  在classes目录下创建manifest.txt文件,该文件带有这样一行:

Main-Class: MyApp

       注意:MyApp为你的包含main()方法的类名,后面没有.class后缀,而且此行后面要换行,否则会出错。

3、  执行jar工具来创建JAR文件,命令如下:

cd MyProject/classes

jar –cvmf manifest.txt app.jar *.class

执行JAR文件:

cd MyProject/classes

java –jar app.jar

把类加入包(package)中:

1、  选择包名称

2、  在类中加入包指令,如:package com.headfirstjava; 注意:该语句必须位于源文件的最前面,比import还要靠前。

3、  设定相应的目录结构。你必须把类放在包名称对应的目录结构中。你的类必须在headfirstjava目录中,而headfirstjava必须在com目录中。

4、  编译生成包:

cd MyProject/source

javac –d ../classes com/headfirstjava/*.java

5、  执行程序:

cd MyProject/classes

java com/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()。

       执行线程的优选方法:JavaSE 5的java.util.concurrent包中的执行器(Executor)将为你创建Thread,并为你管理Thread对象,你只需调用execute()方法即可。ExecutorService(具有服务生命周期的Executor,例如可以shutdown():执行后不再接受新的任务,已有任务将继续执行完;也可以shutdownNow():执行后不再接受新的任务,并试图终止已有任务,返回尚未执行任务的列表)。你可以使用Executors的静态方法来创建CachedThreadPool、FixedThreadPool、SingleThreadExecutor,它们都将返回ExecutorService,你也可以在调用它们时,传入实现了ThreadFactory接口的对象,以便能够自定义创建线程。如下:

              ExecutorService exec = Executors.newCachedThreadPool();

                   for(int i=0; i<5; i++)

                            exec.execute(new RunnableTask());

                   exec.shutdown();

       从线程执行的任务中获取返回值:run()方法包含执行工作的独立任务,但是它不会返回任何值。如果希望任务在返回时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。此时你需要实现call()方法,并且必须使用ExecutorService.submit()方法。submit()方法返回Future对象,你可以使用isDone()方法来查询Future是否已经完成,并调用get()方法来获取返回值。

       线程优先级:优先权不会导致死锁,也就是说优先级低的线程也可以得到执行,只不过是执行的频率较低。Thread.getPriority()获取优先级,Thread.setPriority()设置优先级。

       后台线程:后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。当所有的非后台线程结束时,程序也就终止了,同时会立刻杀死所有的后台线程(这意味着后台线程中的finally块将不会被执行)。反过来说,只要有非后台线程还在运行,程序就不会终止,比如main()就是一种非后台线程。要想使线程变为后台线程,必须在线程启动之前调用Thread的setDaemon(true)。可以调用isDaemon()方法来确定一个线程是否为后台线程。一个后台线程创建的任何线程都将默认是后台的。

       捕获线程异常:由于线程的本质特性,使得你不能捕获从线程中逃逸的异常。一旦异常逃出任务的run()方法,它就会向外传播至控制台,除非你采取特殊措施来捕获这种异常。办法百度之。

定时器:

       Java中定时器由java.util.Timer和java.util.TimerTask配合实现。抽象类TimerTask用来定义定时器要执行的任务,你必须实现它的run()方法,Timer类用来创建定时器并执行TimerTask中定义的任务,你必须在调用Timer.schedule()(定时地、定时周期地、延迟地、延迟周期地执行任务,下一次的执行时间点=上一次程序执行完成的时间点+间隔时间,如果指定的时间早于当前时间,则马上执行,但不会把过去的时间算上)或者Timer.scheduleAtFixedRate()(定时周期地、延迟周期地执行任务,下一次的执行时间点=上一次程序开始执行的时间点+间隔时间,如果指定的时间早于当前时间,则会把已经过去的时间也作为周期执行)时,将TimerTask对象作为参数传入才行。另外,Timer和TimerTask都是可以被cancel()的。

       使用它们时需要注意的一些问题:

①       Timer类是线程安全的,多个线程可以共享单个 Timer 对象而无需进行外部同步;

②       每一个Timer会启动一个线程(TimerThread),每一个Timer可以添加多个TimerTask,TimerTask会被放入一个有优先级的队列(TimerQueue)中。在同一个TimerQueue中,如果前一个TimerTask没有执行完,那么下一个TimerTask是不会被执行的;

③       如果TimerTask执行时间过长,会破坏其他TimerTask的定时精确性;

④       如果TimerTask抛出一个未受检查的异常,Timer线程并不捕获,线程将被终止,尚未执行的TimerTask将不会再执行;

⑤       在Java 5.0或者更高的版本中,可以使用ScheduledThreadPoolExecutor代替Timer,至少能避免异常导致线程结束的问题。如果对时间有比较准确的需要,务必使用ScheduledExecutorService处理一个任务。

线程状态转换:

       Java中的线程状态:新建(NEW,线程已创建,但还没有start)、就绪(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING、TIMED_WAITING)、终止(TERMINATED)。

       一些被抛弃的方法:早期java代码中,suspend()和resume()用来阻塞和唤醒线程,但是现在它们被废止了,因为可能会导致死锁。stop()和destroy()也已经被废止了,因为它们都不释放锁,进而也可能会导致死锁。

线程让步(Thread.yield()):Thread中静态的yield()方法使当前线程暂时放弃执行权,但需要注意的是:这只是一种建议,这种建议并不能被保证被采纳。

线程加入(Thread.join()):父线程生成并启动了子线程,而父线程需要用到该子线程的处理结果,这时就需要用到join()方法。在父线程中使用子线程的Thread对象调用了join()方法后,主线程后边的代码需要等到子线程结束才能执行。

线程休眠(Thread.sleep()):使此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep 不会释放对象锁。注意:sleep()方法是一个静态方法,也就是说他只对当前对象有效,例如t是一个线程,通过指定t.sleep()让t对象进入sleep,这样的做法是错误的,它只会是使当前线程被sleep 而不是t线程。

线程等待(Object.wait()):wait 是Object 类的方法,对此对象调用wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。wait,notify和notifyAll只能在同步(synchronized)控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

       线程中断(Thread.interrupt()):interrupt,尽管,其名称似乎在暗示着什么,然而,这种方法并不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。如果线程在对interruptible channel进行I/O操作时阻塞,调用interrupt(),将会收到ClosedByInterruptException异常。而你要做的就是提前预备好处理对应异常的代码。例外的是你不能中断正在试图获取synchronized锁或者试图执行传统I/O操作的线程,但是有补救方法,具体百度之。实际上,interrupt()只是置线程的中断状态位,自己并不中断线程,需要用户自己去监视线程的状态为并做处理,支持线程中断的方法(也就是线程中断后会抛出interruptedException等异常的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常,此时中断状态位会被自动清除。另外,interrupted() 是作用于当前线程,返回当前线程的中断状态,并清除中断状态位;isInterrupted() 是作用于调用该方法的线程对象所对应的线程,返回给定线程的中断状态,不清除中断状态位。

       终止线程:如何合理地终止一个线程?这是一个很复杂的问题,因为涉及到线程资源的释放等等,不然可能会导致死锁等等。终止一个线程,最好的最推荐的方法是:使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量(尤其在冗余操作期间),然后有秩序地中止任务。但是如果线程被阻塞,它便不能核查共享变量,也就不能停止。此时正确的方式是在已经设置共享变量的前提下,调用interrupt(),然后线程就会逃离阻塞状态,它就有机会检查共享变量了。

共享受限资源:

       互斥量(mutex):Java语言的关键字synchronized(同步)可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这个段代码。每个对象都自动含有单一的锁(也称监视器),其所有的synchronized方法共享这同一个锁。一个任务可以多次获得对象的锁,每获得一次锁,锁计数加一,每释放一次锁,锁计数减一,当锁计数为0时,锁被完全释放。针对每个类,也有一个锁(作为类的Class对象的一部分),所以synchronized static方法可以在类的范围内防止对static数据的并发访问。另外,java.util.concurrent.locks包中提供了显式的互斥锁(Lock),Lock对象必须显式地创建、锁定、释放,灵活性更高。

死锁(Deadlock):java对死锁并没有提供语言层面上的支持,能否通过仔细设计程序来避免死锁,这取决于你自己。

       原子性与易变性:原子性与易变性是不同的概念,注意区分。java中,针对除long和double之外的所有基本类型的“简单操作”(读取和写入)都具有原子性。volatile关键字声明的long和double的读写操作也会获得原子性。java中,自增操作不是原子的。同时,在多处理器系统中,针对volatile关键字声明的变量的写操作,其他所有的读操作都可以看到这个修改,即便是使用了处理器缓存。synchronized关键字(同步)也会导致向主存中刷新,因此如果一个域完全由synchronized方法或语句块来保护,那就不必将其设置为volatile的。

       线程安全:线程安全是指一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

▉Java图形界面开发

       Java图形界面开发可以使用AWT、Swing、SWT等,Swing基于AWT和java2D。此处我们使用Swing。使用Swing时,请import javax.swing.*。

Swing库:

       JFrame(框架):大多数的Swing应用都被构建在基础的JFrame内部,JFrame在你使用的任何操作系统中都可以创建视窗应用。

       JDialog(对话框):对话框与框架(JFrame)有一些相似,但它一般是一个临时的窗口,主要用于显示提示信息或接受用户输入。

       JFileChooser(文件对话框):对于“打开文件”对话框,使用showOpenDialog()方法;对于“保存文件”对话框,使用showSaveDialog()方法。

       JPanel(面板):轻量级的容器,可以加入到JFrame中,它自身是个容器,可以把JButton、JTextField等加入到其中。

JLabel(标签):用于短文本字符串或图像或二者的显示区。标签不对输入事件作出反应。因此,它无法获得键盘焦点。

       JButton(按钮):略

       JToggleButton(触发器按钮):具有两个状态的(two-state)的按钮(按下、弹出)。JRadioButton 和 JCheckBox 类是此类的子类。

JRadioButton(单选按钮):略

JCheckBox(复选框):略

JComboBox(组合框、下拉列表):略

JList(列表框):略

       ButtonGroup(按钮组):要想让单选按钮表现出某种“排它”行为,必须把它们加入到一个按钮组中。任何AbstractButton对象都可以加入到按钮组中。

       JTextField(文本框):该组件支持用户输入文本,当然也可以程序设置文本。

       JTextArea(文本域):与JTextField相比,JTextArea可以有多行文本以及更多的功能。

       JScrollPane:JScrollPane允许你方便的组建可滚动的组件。比如和JTextArea配合使用,可以组建可滚动的文本域,使用示例:new JScrollPane(new JTextArea(10,10))。

       JTextPane:该控件的目的是提供即时编辑文本的功能。

       JTabbedPane(页签面板):略

       JOptionPane(消息框):你可以通过调用JOptionPane中静态的showMessageDialog()方法和showConfirmDialog()方法来使用消息对话框和确认对话框。

图标:

可以在JLabel或者任何从AbstractButton继承的组件中使用Icon。要打开一个文件并且得到图形,只需根据图像文件名创建一个ImageIcon对象即可。许多不同的Swing组件的构造器都接受Icon类型的参数,也可以使用setIcon()方法来加入或者改变图标。

       注意以下方法只能显示gif、jpeg、png格式的图像(java 1.7),不能显示bmp格式的图像:

              label.setIcon(new ImageIcon("1.bmp"));

       要想显示bmp格式的图像,可以使用以下方法:

label.setIcon(new ImageIcon(ImageIO.read(new File("1.bmp"))));

       可以使用以下的方法调整图像的大小:

image.setImage(image.getImage().getScaledInstance(width,height,Image.SCALE_DEFAULT));

绘图:

       你可以在任何从JComponent派生来的组件上绘图,也就是它们都可以作为画布。但是,要是你只是想有一个可以直接绘图的平面的话,典型的做法是从JPanel继承,唯一需要覆盖的方法是paintComponent(),在组件必须被重绘的时候调用它(通常你不必为此担心,因为java虚拟机判断需要重绘时,会自动调用该方法,无需你来调用,当然你也可以使用repaint()来让java虚拟机来调用paintComponent()进行重绘。另外注意:validate()也会重绘组件,使它自己或者子组件生效)。当此方法被调用时,Swing将传入一个Graphics对象,然后你就可以使用这个对象绘图了。

工具提示:

       从JComponent派生来的类都具有一个setToolTipText()方法,用来给该组件添加工具提示,也就是鼠标悬停在该组件上边时,显示的提示信息。

边框:

       从JComponent派生来的类都具有一个setBorder()方法,它允许你为任何可视组件设置各种边框。

菜单:

       每个能够持有菜单的组件,包括JApplet、JFrame、JDialog以及它们的子类,都有一个setJMenuBar()方法,它接受一个JMenuBar对象作为参数。你先把JMenu对象添加到JMenuBar中,然后把JMenuItem添加到JMenu中。每个JMenuItem都能有一个相关联的ActionListener,用来捕获菜单项被选中时所触发的事件。

弹出式菜单:

       弹出式菜单使用到JPopupMenu和JMenuItem,你需要设置JMenuItem的监听器,并将其加入到JPopupMenu中,同时也要在想使用弹出式菜单的组件上设置鼠标监听器(可以使用MouseListener或者适配器MouseAdapter等等都可以),以便JPopupMenu可以适时弹出。

托盘:

       给Swing程序添加系统托盘图标需要用到两个主要的类:SystemTray和TrayIcon。SystemTray 通常用于判断系统是否支持托盘图标和添加系统图标功能。TrayIcon 用来创建一个图标,然后需要使用SystemTray将其add进去。示例如下:

if (SystemTray.isSupported()) {

TrayIcon icon = new TrayIcon(Toolkit.getDefaultToolkit().createImage("img.png"));

icon.setImageAutoSize(true);

    SystemTray tray = SystemTray.getSystemTray();

    tray.add(icon);

}

若需添加托盘图标闪烁效果,只需调用TrayIcon.setImage(Image image)动态替换托盘图标即可(创建一个空Image和一个正常Image,定时切换即可)。

Swing组件上显示HTML文本:

       任何能接受文本的组件都可以接受html文本,且能根据html的规则来重新格式化文本。也就是说,可以很容易地在Swing组件上加入漂亮的文本。文本必须以“<html>”标记开始,然后就可以使用普通的html标记了,注意,不会强制要求你添加普通的结束标记。

滑块与进度条:

       JProgressBar为进度条组件,JSlider为滑块组件,把两个组件联系到一起的关键在于让它们共享一个模型,就像这样:jSlider.setModel(jProgressBar.getModel()); 当然也可以使用监听器来控制它们同步,不过这种方法更简单和直接。ProgressMonitor也是监视进度的类,不过它没有模型,因此需要使用监听器方式。注意ProgressMonitor只能向前移动,并且一旦移动到底就会关闭。

选择外观:

       Swing程序默认的外观是跨平台的“金属”外观,不过你可以更改程序的外观,或者将其设置成当前操作系统的外观,或者将其设置成其他外观。将外观设置为当前操作系统的外观很简单,将以下代码加入到任何可视化组件创建之前即可:

try {

UIManager.setLookAndFeel(

UIManager.getSystemLookAndFeelClassName());

} catch (Exception e) {

throw new RuntimeException(e);

}

布局管理器:

       在java中,组件的布局完全基于代码,没有用来控制组件布置的“资源”。并且,组件放置在窗体上的方式不是通过绝对坐标控制,而是由“布局管理器”根据组件加入的顺序决定其位置。布局管理器是面板用来隐式的决定控件在窗体上的位置的工具。使用不同的布局管理器,组件的大小、形状和位置将大不相同。

BorderLayout布局管理器:其具有四个边框区域和一个中央区域的概念,其默认行为是每个组件都会单独的占满整个窗体。JFrame默认使用BorderLayout管理布局。

FlowLayout布局管理器:该布局管理器使得控件可以在窗体上从左到右、从上到下连续均匀分布。所有组件将被压缩到它们的最小尺寸。

GridLayout布局管理器:该管理器允许你构建一个放置组件的表格,在向表格里面添加组件的时候,它们将按照从左到右、从上到下的顺序加入。

GridBagLayout布局管理器:该管理器提供了强大的控制功能,包括精确判断视窗区域如何布局,以及视窗大小变化的时候如何重新放置组件。它也是最复杂的布局管理器,它的目的主要是辅助GUI构造工具自动生成代码。如果你发现你的设计非常复杂,以至于需要使用GridBagLayout,那么你应该使用GUI构造工具来生成这个设计。

绝对定位:我们也可以设置图形组件的绝对位置,步骤是:1、使用setLayout(null)将布局管理器设置为空;2、为每个组件调用setBounds()或者reshape()方法(取决于语言的版本)

,为方法传递以像素坐标为单位的边界矩形的参数。根据你要达到的目的,可以在构造器或者paint()方法中调用这些方法。

捕获事件:

以JButton响应事件为例,为了表明你对按钮按下事件感兴趣,你可以调用JButton的addActionListener()方法,这个方法接收一个实现ActionListener接口的对象作为参数,ActionListener接口只包含一个actionPerformed()方法。所以你要想把事件处理代码和JButton关联,需要在一个类中实现ActionListener接口,然后把这个类的对象通过addActionListener()方法注册给JButton。这样按下按钮的时候就会调用actionPerformed()方法,通常这也称为回调。通常,把ActionListener实现成匿名内部类会更方便,尤其是对每个监听器类只使用一个实例的时候更是如此。

在Swing的事件模型中,组件可以触发事件(XXXEvent),每种事件的类型由不同的类表示,当事件被触发时,它将被一个或者多个监听器(XXXListener)接收,监听器负责处理事件。所谓事件监听器,就是一个实现特定类型的监听器接口的类对象。所有的Swing组件都具有addXXXListener()方法和removeXXXListener()方法,用来为每个组件添加或者移除相应类型的监听器。以下是常用的事件监听器:

ActionListener:该接口用于接收动作事件。

ComponentListener:该接口用于接收组件事件。

ItemListener:该接口用于接收项目事件。

KeyListener:该接口用于接收按键事件。

MouseListener:该接口用于接收鼠标事件。

WindowListener:该接口用于接收窗口事件。

AdjustmentListener:该接口用于接收调整事件。

ContainerListener:该接口用于接收容器事件。

MouseMotionListener:此接口用于接收鼠标移动事件。

FocusListener:该接口用于接收焦点事件。

       Eclipse好用的Swing GUI构造工具(按好用性排序):WindowBuilder(原SWT Designer) > Jigloo > Visual Editor

       最后给出一个java图形界面开发的简单示例程序:

import java.awt.*;

import javax.swing.*;

public class Test {

         public static void main(String[] args) {

                   /* 设置程序外观为当前操作系统的外观 */

                   try {

                            UIManager.setLookAndFeel(

                                               UIManager.getSystemLookAndFeelClassName());

                   } catch (Exception e) {

                            throw new RuntimeException(e);

                   }

                   /* 创建一个框架 */

                   JFrame jFrame = new JFrame("你好,Swing");

                   jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                   jFrame.setSize(600,300);

                   jFrame.setLocation(300, 200);

                   /* 设置框架的布局管理器 */

                   jFrame.setLayout(new FlowLayout());

                   /* 创建一个Icon对象 */

                   Icon icon = new ImageIcon("res/1.gif");

                   /* 创建一个标签,显示图像 */

                   JLabel jLabel = new JLabel(icon);

                   jFrame.add(jLabel);

                   /* 创建一个按钮,带图像和html文本 */

                   JButton jButton = new JButton(

                                     "<html><span style='color: #FF8000'>我是按钮</span>",

                                     icon);

                   jButton.setToolTipText("点我啊");

                   jFrame.add(jButton);

                   /* 创建一个文本框 */

                   JTextField jTextField = new JTextField(10);

                   jTextField.setText("我是文本框");

                   jFrame.add(jTextField);

                   /* 创建一个可滚动的文本域 */

                   JTextArea jTextArea = new JTextArea(10,10);

                   jFrame.add(new JScrollPane(jTextArea));

                   /* 创建一个滑块和一个进度条,并进行关联 */

                   JProgressBar jProgressBar = new JProgressBar();

                   JSlider jSlider = new JSlider();

                   jSlider.setModel(jProgressBar.getModel());

                   jFrame.add(jSlider);

                   jFrame.add(jProgressBar);

                   /* 不要一开始就设置为可见,否则有些控件需要重绘才会显示 */

                   jFrame.setVisible(true);

         }

}

▉Java播放音乐

Swing只是一个GUI框架,不支持播放音乐。要想播放音乐,你可以使用:Applet(java.applet.Applet),JMF(Java Media Framework)或者Java Sound API(javax.sound.sampled.* 和 javax.sound.midi.*)。

不过Applet好像仅支持wav格式的音乐,并且一般是嵌在web页上在浏览器容器中运行的,也就是说Applet一般是用于applet程序。如果你非要在非applet程序中使用Applet也可以,如下:

              AudioClip audio = Applet.newAudioClip(new File("1.wav").toURI().toURL());

                   audio.play();

       JMF可以播放mp3格式的音乐,使用很方便,但你要下载安装JMF类库,怎么用请百度之。

       Java Sound API使用起来则比较复杂了,其中javax.sound.sampled.*用于播放抽样音乐(*.mp3,*.wav),javax.sound.midi.*用于播放midi音乐(*.mid)。

▉Java与其他语言的代码进行交互

       在java中,与其他语言写的代码进行交互,你可以使用JNI,更可以使用比JNI更加强大的JNA。

▉Java执行脚本命令以及调用外部程序

       在Java中要想执行脚本或者调用程序必须通过ProcessBuilder 和 Runtime 类,以上两个类任选一个就行,关于它们的介绍如下:

ProcessBuilder.start() 和 Runtime.exec() 方法都被用来创建一个操作系统进程(执行命令行操作),并返回 Process 子类的一个实例,该实例可用来控制进程状态并获得相关信息。

Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。创建的子进程没有自己的终端或控制台。它的所有标准 IO(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。

每个ProcessBuilder实例管理一个进程属性集。ProcessBuilder的start()方法利用这些属性创建一个新的Process实例。start()方法可以从同一实例重复调用,以利用相同或者相关的属性创建新的子进程。

不同的是:ProcessBuilder.start() 和 Runtime.exec()传递的参数有所不同,Runtime.exec()可接受一个单独的字符串,这个字符串是通过空格来分隔可执行命令程序和参数的;也可以接受字符串数组参数。而ProcessBuilder的构造函数是一个字符串列表或者数组。列表中第一个参数是可执行命令程序,其他的是命令行执行是需要的参数。

通过查看JDK源码可知,Runtime.exec最终是通过调用ProcessBuilder来真正执行操作的。

▉其他常用类库

java.lang.Math:包含常用的数学方法,比如绝对值、随机数等。

▉错误解决

“错误:找不到或无法加载主类”,则可能是classpath环境变量没配置好,关于这一点,请百度之;还有可能是路径不正确,对于路径不正确的,可以在cmd中将工作目录切换到程序目录,然后运行即可。

“在eclipse中可以运行,cmd下却不可以运行”的原因可能是:你定义了包名,但cmd中工作目录不对。解决方法是:假设你定义了一个包名B.C(目录C也要放在B中,不规范可不是好习惯啊),包中有一个主程序文件D.class,而B目录在目录A中,此时你应该在cmd中将目录切换到A,并执行这样的命令来执行你的程序:java B/C/D。

“把eclipse生成的class文件删了之后,不能运行”的原因是:eclipse没有再次编译生成class文件,projectàclean,然后再次运行,OK。

OutOfMemoryException异常:内存不足,可以通过类似于“java –Xmx512m …”的命令设置内存大小。

StackOverflowError异常:栈溢出,可以通过类似于“java –Xss512k …”的命令设置堆栈大小。

更多相关推荐:
Java总结

Java总结Java语言不允许程序员直接控制内存空间的使用。内存空间的分配和回收是有jre负责在后台自动进行,尤其是无用内存空间的回收操作。垃圾回收器不可以被强制执行,但程序员可以通过调用system.gc()…

Java总结

Java总结一.1.Java程序的执行Java先由.Java文件编译生成.class文件,再由.class文件通过不同操作系统的虚拟机进行解释从而执行程序结果。编译命令:javac+文件名.Java解释命令:j…

JAVA总结

1.构造函数:是一种特殊的函数,没有返回值。构造函数的名称与类的名称相同。一般用于初始化成员变量的值。使用super关键字来调用父类的构造函数。2.方法的重载:法名相同但参数的个数或类型不同,一般情况下是在一个…

二级JAVA总结

对长度为n的线性表进行冒泡排序,最坏情况先需要比较的次数为log2n。对长度为n的线性表进行顺序排序,最坏情况先需要比较的次数为n。高内聚低耦合有利于模块的独立性。二叉树属于非线性结构。算法具有可行性、确定性、…

java总结

个人介绍:1.名字可以不用介绍;2.×××大学,学了什么(大致介绍)3.如何进入第一家公司;4.工作年限;5.大致讲下在公司所作的项目;技术方面:1.抽象类与接口的区别;2.版本控制(CVS,VSS)3.常用的…

JAVA总结

在这忙忙碌碌的这段时间里,经过老师的辅导,迅速的将一点没有学的JAVA基础搞定了!有了基础学习还是好,万事开头难这句话说的太对了,学计算机语言我觉得记忆好的方法就是多打代码,课前预习,课堂上认真听讲,把现学的方…

java总结

1.读入数据用Scanner2.向文件写数据用PrintWriter3.this.dispose();这个方法可以真正在内存中移除JFrame从类java.awt.Window继承的方法:dispose()pu…

高手总结java常用API(免费下载)

Java常用API的运用效率及技巧1Java面向对象基本概念2System3StringStringBuffer4数值字符布尔对象与简单类型的操作5ClassClassLoader6JavaIO系统7Java集...

java常见异常总结

算术异常类ArithmeticExecption空指针异常类NullPointerException类型强制转换异常ClassCastException数组负下标异常NegativeArrayException...

Java详细总结(精辟版++)

Java总结开始于20xx97092748Java概述Java分类javaSE主要是桌面程序控制台程序开发是java基础javaEE企业级开发网站开发和基于bs模式的编程javaME嵌入式开发手机软件小家电第一...

java技术小总结

Java语言出现的背景影响及应用前景一背景最近一年多来在Internet上出现的特别吸引人的事件就是Java语言和用Java编写的浏览器HotJava19xx年SUNMicroSystem公司的JameGosl...

Java详细总结(精辟版++)

Java总结开始于20xx97092748Java概述Java分类javaSE主要是桌面程序控制台程序开发是java基础javaEE企业级开发网站开发和基于bs模式的编程javaME嵌入式开发手机软件小家电第一...

java总结(59篇)