Java 其他特性
1 关于JDK、JRE、JVM和Java编译器及Java解释器
JDK是Java的开发工具包(SDK),提供了开发环境(编译器javac等工具,用于将java文件编译为class文件)以及运行环境(JVM和Runtime辅助包,用于解析class文件使其得到运行)
JRE是Java运行环境,包含JVM和核心类库(的class文件)与支持文件(只要装了JRE就可以运行Java文件)
JVM:一种能够运行Java字节码(Java bytecode)的虚拟机。
字节码:字节码是已经经过编译,但与特定机器码无关,需要解释器转译后才能成为机器码的中间代码。
JVM:JVM有自己完善的硬件架构,如处理器、堆栈(Stack)、寄存器等,还具有相应的指令系统(字节码就是一种指令格式)。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM是Java平台无关的基础。JVM负责运行字节码:JVM把每一条要执行的字节码交给解释器,翻译成对应的机器码,然后由解释器执行。JVM解释执行字节码文件就是JVM操作Java解释器进行解释执行字节码文件的过程。
Java编译器:将Java源文件(.java文件)编译成字节码文件(.class文件,是特殊的二进制文件,二进制字节码文件),这种字节码就是JVM的“机器语言”。javac.exe可以简单看成是Java编译器。
Java解释器:是JVM的一部分。Java解释器用来解释执行Java编译器编译后的程序。java.exe可以简单看成是Java解释器。
即时编译(Just-in-time compilation: JIT):又叫实时编译、及时编译。是指一种在运行时期把字节码编译成原生机器码的技术,一句一句翻译源代码,但是会将翻译过的代码缓存起来以降低性能耗损。这项技术是被用来改善虚拟机的性能的。
JIT编译器是JRE的一部分。原本的Java程序都是要经过解释执行的,其执行速度肯定比可执行的二进制字节码程序慢。为了提高执行速度,引入了JIT。在运行时,JIT会把翻译过来的机器码保存起来,以备下次使用。而如果JIT对每条字节码都进行编译,则会负担过重,所以,JIT只会对经常执行的字节码进行编译,如循环,高频度使用的方法等。它会以整个方法为单位,一次性将整个方法的字节码编译为本地机器码,然后直接运行编译后的机器码。
2 异常
异常处理可以使得程序处理费预期场景,并且继续正常的处理
在程序运行时,JVM如果检测出一个不可能执行的操作,就会出现运行时错误。Java中运行时错误会作为异常抛出。
异常就是一种对象,表示阻止正常进行程序执行的错误或情况。如果异常没有被处理,那么程序会非正常终止。
异常是方法抛出的,方法的调用者可以捕获以及处理该异常,Java可以让一个方法抛出一个异常,该异常可以被调用者捕获和处理
如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方***立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。
异常分类
Throwable 是 Java 语言中所有错误或异常的超类。下一层分为 Error 和 Exception
Error(错误)
Error 类是指Java运行时系统的内部错误和资源耗尽错误。是由JVM抛出的,应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。
Exception(RuntimeException、CheckedException)
RuntimeException(运行时异常)
如 : NullPointerException 、ClassCastException ; RuntimeException。是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 描述的是程序设计的错误。
CheckedException(必检异常)
Error和RuntimeException统称为免检异常。Exception下的其他的都是必检异常(和 RuntimeExceptio同级)如 I/O错误导致的 IOException、QLException。
必检的意思是编译器会强制程序员检查,并通过try-catch块来进行处理,或者在方法头进行声明;而免检异常一般都是设计上的逻辑错误,Java不要求捕获声明免检异常
CheckedException一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,该类异常一般包括几个方面:
试图在文件尾部读取数据
试图打开一个错误格式的 URL
试图根据给定的字符串查找 class 对象,而这个字符串表示的类并不存在
Java的异常处理模型基于三种操作:声明一个异常、抛出一个异常、捕获一个异常
(1)声明异常(方法头)
每个方法都要声明它可能抛出的必检异常的类型
在方法头中使用关键字 thows表明要抛出的异常种类(可以多个)
public void method() throws Exception1,Exception2,...
(2) 抛出异常(方法体)
检测到异常的程序可以创建一个合适的异常类型的实例并抛出它,要在方法体中使用thow对可能有异常的地方进行判断和抛出
thow new IllegalArgumentException("错误信息的描述字符串");//隐式创建异常的实例 一般来说,异常类有两个构造方法,一个是无参的,一个是可带描述信息的,该参数称为异常消息,可以通过getMessage()来获取异常的异常消息
(3)捕获异常(方法调用时(可以是一个方法的定义中调用了另一个方法))
使用try-catch块来捕获和处理异常,finally子句用来放置必然要执行的语句
try{
statements
}catch(Exception1 ex1){
handdler for ex1
}catch(Exception2 ex2){
handdler for ex2
}
···
finally{
···
} 如果在执行try块的过程中没有出现异常,则跳过catch子句
*如果try块中的某条语句抛出一个异常,Java就会跳过剩余语句,然后开始查找处理这个异常的catch块,之后执行try-catch后的语句。处理这个异常的代码称作 异常处理器 *
当某个调用的方法抛出一个异常,从当前的方法开始,沿着方法调用链,按照异常的反向传播方向去寻找这个异常的处理器(某个catch块),如果异常没有在当前方法被捕获,就被传给该方法的调用者,这个过程一直重复(不断向上寻找),直到异常被捕获或者被传给main方法(如果最终没有捕获就会终止程序)
任何情况情况下,finally中的代码都会执行(即便之前有return也会执行,finally是在本层的最后执行的)
从一个通用父类可以派生出各种异常类,如果一个catch块可以捕获一个父类的异常对象,它就可以捕获那个父类的所有子类的异常对象
如果方法声明了一个必检异常(在方法头),那调用它的时候就必须加上try-catch块(在调用者这一层进行处理),或者在调用者的方法上声明同类异常(不在这一层处理,继续向上传导该异常)
void p1(){//在调用者这一层进行处理
try{
p2();
}
catch (IOException ex){
···
}
}
void p1() throws IOException{//不在这一层处理,继续向上传导该异常
p2();
} 对于捕获多个异常使用同样代码处理,可以用JDK7的 多捕获特征 来写(使用 |来分隔多个异常)
catch (Exception1 | Exception2 | ··· | Exceptionk ex){
···
} 从异常中获取信息
使用Throwable类中的方法
getMessage()获取描述该异常的信息
toString()返回三个字符串连接 异常类名 :getMessage()
printStackTrace() 打印toString()+getStackTrace
getStackTrace() 返回该异常堆对象相关堆栈信息
注意!使用异常需要 1 新建异常对象 2 从调用栈返回 3 沿方法调用链来传播异常以便找到异常处理器 这意味着花更长的时间和资源,所以要谨慎使用异常
重新抛出异常:如果在这一层处理不了这个异常,允许在catch中重新thow已经捕获异常,以便给本层后面的catch块或者上一层调用来处理
链式异常:同原始异常一起抛出一个新异常 比如thow new Exception("新异常的信息", 上一层原始异常ex)
创建自定义异常:可以通过派生 Exception类来定义一个自定义异常类
文本I/O:File类 绝对/相对路径 I/O方法(Scanner PrintWriter 类)try-with-resources自动关闭资源
Scanner&PrintWriter:
3 Java反射
(1)反射概述
动态语言,是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的 JavaScript 就是动态语言,除此之外 Ruby,Python 等也属于动态语言,而 C、C++则不属于动态语言。从反射角度说 JAVA 属于半动态语言。
在 Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。
反射机制可以用来:
1 在运行时分析类的能力 。
2 在运行时查看对象。例如,编写一个 toString 方法供所有类使用。
3 实现通用的数组操作代码。
4 利用 Method 对象, 这个对象很像c++中的函数指针。
(2)Java反射API
反射 API 用来生成 JVM 中的类、接口或则对象的信息。
1. Class 类:反射的核心类,可以获取类的属性,方法等信息。(Java.lang.Class)
(以下都是Java.lang.reflec 包中的类)
2. Field 类 (域):表示类的成员变量,可以用来获取和设置类之中的属性值。
3. Method 类 (方法): 表示类的方法,它可以用来获取类中的方法信息或者执行方法。
4. Constructor 类 (构造器): 表示类的构造方法。
(3)反射使用步骤
获取想要操作的类的 Class 对象(3种方法),通过 Class 对象我们可以任意调用类的方法。
调用 Class 类中的方法(获取反射API对象),这是反射的使用阶段。
使用反射 API(Field Method Constructor的方法)来操作这些信息。
(4)Class类的使用
在程序运行期间, Java 运行时系统始终为所有的对象维护一个被称为运行时的类型标识。 这个信息跟踪着每个对象所属的类。 虚拟机利用运行时类型信息选择相应的方法执行。保存这些信息的类被称为Class。 Object 类中的 getClass( ) 方法将会返回一个 Class 类型的实例。
一个 Class 对象将表示一个特定类的属性。
(4.1) 获取Class对象
E e = new E();
String s = e.getClass().getName();//得到某个实例对应类的类名
//获得Class类的3种方法
Class cl = e.getClass();//通过调用类的实例的getClass()方法获取
Class cl = E.class;//通过调用类的class属性来获取
Class cl = Class.forName("类的全路径(包名+类名)");//(最安全|性能最好)这是个静态方法,可以根据类名来得到对应的类的Class类的实例。注意!无论何时使用这个方法,都应该提供一个异常处理器 注意!一个 Class 对象实际上表示的是一个类型, 而这个类型未必一定是一种类。 例如, int 不是类, 但 int.class 是一个 Class 类型的对象。
Class类实际上是一个泛型类。例如,Employee.class的类型是Class<Employee>。但其实在大多数实际问题 中, 可以忽略类型参数, 而使用原始的Class 类
注意!鉴于历史原 getName 方法在应用于数组类型的时候会返回一个很奇怪的名字: Double[]class.getName()返回“ [Ljava.lang.Double;’’
int[].class.getName()返回“ [I”,
虚拟机为每个类型管理一个 Class 对象。(所以对于同一个类来说,其Class实例时唯一的,所有引用都指向一个地方) 因此, 可以利用 = 运算符实现两个类对象比较 的操作。
可以让启动变快的方法:首先, 显示一个启动画面; 然后,通过调用 Class.forName 手工地加载其他的类。(避免调用main方法加载所有的类花费大量时间)
(4.2)通过Class类型创建对应类的实例的两种方法
1.Class 对象的 newInstance()
使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求该 Class 对象对应的类有默认的空构造器。(首先获得该类的Class对象,然后创建实例)
比如将 forName 与 newlnstance 配合起来使用, 可以根据存储在字符串中的类名创建一个对象
e.getClass().newlnstance(); String s = "java.util.Random"; Object m = Class.forName(s).newlnstance();//注意使用时要类型转换
2.调用 Constructor 对象的 newInstance()
先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。
//获取 Person 类的 Class 对象
Class clazz = Class.forName("reflection.Person");
//使用.newInstane 方法创建对象
Person p = (Person) clazz.newInstance();
//获取构造方法并创建对象(构造方法参数是对应类的Class对象)
Constructor c = clazz.getDeclaredConstructor(String.class,String.class,int.class);
//创建对象并设置属性
Person p1 = (Person) c.newInstance("李四","男",20); (4.3)调用Class的方法(得到对应的反射API实例)
Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的 public域、方法和构造器数组,其中包括超类的公有成员。
Class 类的 getDeclareFields、getDeclareMethods 和 getDeclaredConstructors 方法将分别返回类中声明的全部域、方法和构造器, 其中包括私有和受保护成员,但不包括超类的成员。
(5)反射API的使用(Field Method Constructor)(用于检查类的结构)
这三个类都有一个叫做 getName 的方法,用来返回项目的名称。
Method类有一 个 getType 方法,用来返回描述域所属类型的 Class 对象。
Method 和 Constructor 类有能够报告参数类型的方法, Method 类还有一个可以报告返回类型的方法。
这3个类还有一个叫 做 getModifiers 的方法,它将返回一个整型数值,用不同的位开关描述 public 和 static 这样 的修饰符使用状况。
另外, 还可以利用 java.lang.reflect 包中的 Modifier 类的静态方法分析 getModifiers 返回的整型数值。 例如, 可以使用 Modifier 类中的 isPublic、 isPrivate 或 isFinal 判断方法或构造器是否是 public、 private 或 final。 我们需要做的全部工作就是调用 Modifier 类的相应方法, 并对返回的整型数值进行分析, 另外,还可以利用 Modifier.toString 方法将修饰符打印出来。
(5.1) 运行时分析反射对象(查看修改数据域内容 Field类)
在编写程序时, 如果知道想要査看的域名 和类型, 查看指定的域是一件很容易的事情。而利用反射机制可以查看在编译时还不清楚的对象域。
(a)得到Field对象
使用getDeclaredFields方法来获得所有成员的一个Field对象数组,然后在进行查找
使用Class类的getField方法根据表示域名的字符串,返回一个 Field对象(要使用这个必须加上try-catch块)
(b)查看和修改Field对象
查看和修改对象域的关键方法是 Field 类中的 get和set方法。 如果 f 是一个 Field 类型的对象(例如,通过 getDeclaredFields 得到的对象) obj 是某个包含 f 域的类的对象, f.get(obj) 将返回一个对象,其值为 obj 域的当前值。
当然,可以获得就可以设置 调用 f.set(obj value) 可以将 obj 对象的 f 域设置成新值
Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
Class cl = harry.getClass();// 拿到了Employee的Class
Field f = cl.getDeclaredField("name")://拿到了该类的指定成员的Filed对象
Object v = f.get(harry);//使用该Field实例去用get方法获取某一个类的实例的指定成员的内容,并返回一个新的变量实例(注意是Object类型 要转换)
// the value of the name field of the harry object, i .e., the String object "Harry Hacker" 实际上,这段代码存在一个问题。由于 name 是一个私有域, 所以 get 方法将会抛出一个 IllegalAccessException。 只有利用 get 方法才能得到可访问域的值。 除非拥有访问权限, 否则 Java 安全机制只允许査看任意对象有哪些域, 而不允许读取它们的值。(必须要加上try-catch块)
Field类使用还有很多内容,比如 对于基本类型获取,get方法可以自动打包,setAccessible 方法,编写通用 toString 方法**
(5.2) 调用任意方法(Method类)
在 C 和 C++ 中, 可以从函数指针执行任意函数。从表面上看, Java 没有提供方法指针, 即将一个方法的存储地址传给另外一个方法, 以便第二个方法能够随后调用它。事实上, Java 的设计者曾说过: 方法指针是很危险的, 并且常常会带来隐患。 他们认为 Java 提供的 接口 (interface )是一种更好的解决方案。 然而,反射机制允许你调用任意方法。
(a)得到Method对象
可以通过调用 getDeclareMethods 方法, 然后对返回的 Method 对象数组进行查找, 直到发现想要的方法为止。
也可以通过调用 Class 类中的 getMethod 方法得到想要的方法。因为有可能存在若干个相同名字的方法,所以还必须提供想要的方法的参数类型。(必须要加上try-catch块)
Method getMethod(String name, Class... parameterTypes)
Method ml = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("raiseSalary", double.class); (b)调用Method对象中的方法
在 Method 类中有一个 invoke 方法, 它允许调用包装在当前 Method 对象中的方法。
invoke方法的签名是 Object invoke(Object obj, Object... args)
第一个参数是隐式参数, 其余的对象提供了显式参数
对于静态方法, 第一个参数可以被忽略, 即可以将它设置为 null
假设用 ml 代表 Employee 类的 getName 方法(是一个Method对象),下面这条语句显示了如何调用这个方法:
String n = (String) ml.invoke(harry);
invoke 的参数和返回值必须是 Object 类型的。这就意味着必须进行多次的类型转换。 这样做将会使编译器错过检查代码的机会。 因此,等到测试阶段才会发现这些错误,找 到并改正它们将会更加困难。不仅如此,使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些。
有鉴于此, 建议仅在必要的时候才使用 Method 对象,而最好使用接口以及 Java SE 8 中 的 lambda 表达式
建议 Java 开发者不要使用 Method 对象的回调功能。 使用接口进行回调会使得代码的执行速度更快, 更易于维护。(?不明白这是什么意思)
