前言
本章介绍Java的基础知识,包括异常、反射、注解等。
一、Java异常
1.1. 概念
当某个方法不能按照正常逻辑继续执行时,可以通过另一种路径退出方法。在这种情况下回抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。
1.2. 异常分类
Throwable是Java语言中所有错误或异常的超类,下一层分为Error和Exception。
- Error
Error类是指Java运行时系统的内部错误或者资源耗尽错误。应用程序不会抛出该类对象。如果出现这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。 - Exception
Exception分为两个分支,运行时异常RuntimeException和受检异常CheckedException。 - RuntimeException:非受检异常,如NullPointException、ClassCastException。它是哪些可能在JVM正常运行期间抛出的异常的超类。如果出现了RuntimeException,那么一定是程序员的错误。
- CheckedException:受检异常,如IOException、SQLException。一般是外部错误,这种异常都发生在编译阶段,Java编译器会强制去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行try catch处理。
1.3. 异常的处理方式
异常的处理方式分为两种,直接捕获处理和抛出异常。
- 捕获处理(try-catch)
直接在可能抛出异常的代码块,用try-catch包裹,并在catch中对此异常进行处理。
File file = new File("a.txt");
try {
InputStream in = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
- 抛出异常
抛出异常是指在当前方法不捕获处理,而是抛出异常给上层调用者处理。抛出异常的方式有以下两种:
throw关键字:throw new FileNotFoundException();
throws关键字:public void testCallable() throws ExecutionException, InterruptedException
1.4. Throw和Throws的区别
Throw | Throws |
---|---|
用在函数内,后面跟的是异常对象 | 用在函数声明,后面跟的是异常类 |
抛出具体的问题对象,执行到throw时,功能已经结束返回,所以不要在该语句下面定义其他语句,因为执行不到 | 用来声明异常,让调用者知道该功能可能会出现问题,可以给出预先的处理方法 |
执行该代码一定会抛出某种异常 | 表示出现异常的一种可能性,不一定会发生这些异常 |
都是消极处理异常的方式,只是抛出或可能抛出,但是不会由函数去处理异常,真正处理异常由函数的上层调用处理 | 同左 |
二、Java反射
2.1. 动态语言
动态语言,是指程序在运行时可以改变其结构,新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的JavaScript就是动态语言,除此之外Rubby、Python等也属于动态语言,而C、C++则不属于动态语言。从反射角度说JAVA属于半动态语言。
2.2. 反射机制概念
在Java中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
2.3. 反射的应用场合
编译时类型和运行时类型
在Java程序中许多对象在运行时都会出现两种类型,编译时类型和运行时类型。编译时的类型由声明对象时实用的类型来决定,运行时的类型由实际赋值给对象的类型决定。
编译时类型无法获取具体方法
程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为Object,但是程序有需要调用该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了。
2.4. Java反射API
反射API用来生成JVM中的类,接口或者对象的信息
- Class类:反射的核心类,可以获取类的属性,方法等信息。
- Field类:Java.lang.reflec包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
- Method类:Java.lang.reflec包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
- Constructor类:Java.lang.reflec包中的类,表示类的构造方法。
2.5. 反射使用步骤
- 获取想要操作的类的Class对象,它是反射的核心,通过Class对象我们可以任意调用类的方法。
- 调用Class类中的方法,即就是反射的使用阶段。
- 使用反射API来操作这些信息。
2.6. 获取class对象常用方法
- 调用某个对象的getClass()方法
Person person = new Person();
Class clazz = person.getClass();
- 调用某个类型的class属性来获取该类对应的Class对象
Class clazz = Person.class;
- 使用Class类中的forName()静态方法(最安全、性能最好)
Class clazz = Class.forName("类的全路径");
2.7. 创建对象的常用方法
- Class对象的newInstance()
使用Class对象的newInstance方法来创建该类对象时,要求该Class对象对应的类有默认的空构造器。
Class clazz = Class.forName("reflection.Person");
Person p = (Person) clazz.newInstance();
- 调用Constructor对象的newInstance()
先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建Class对象对应类的实例,通过这种方法可以选定构造方法创建实例。
Class clazz = Class.forName("reflection.Person");
Constructor c = clazz.getDeclaredConstructor(String.class, String.class, int.class);
Person p = (Person) c.newInstance("张三","男",20);
三、Java注解
3.1. 概念
Annotation(注解)是Java提供的一种对元程序关联信息和与数据的途径和方法。Annotation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的Annotation对象,然后通过该Annotation对象来获取注解中的元数据信息。
3.2. 元注解
元注解的作用是负责注解其他注解。
- @Target
说明了Annotation所修饰的对象范围;Annotation可被用于packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标 - @Retention
Retention定义了改Annotation的声明周期。表示需要在什么级别保存注解信息,用于描述注解的声明周期,取值(RetentionPoicy)由:
SOURCE:在源文件中有效
CLASS:在class文件中有效
RUNTIME:在运行时有效 - @Documented
用于描述其他类型的annotation应该被作为标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。 - @Inherited
该注解是一个标记注解,阐述了某个被标注的类型是可以被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
3.3. 注解处理器
如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitInfo {
int id() default -1;
String name() default "";
String address() default "";
}
public class Apple {
@FruitInfo(id = 1, name = "苹果", address = "菜市场")
private String appleProvider;
public String getAppleProvider() {
return appleProvider;
}
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
}
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(FruitInfo.class)) {
FruitInfo fruitInfo = field.getAnnotation(FruitInfo.class);
System.out.println("水果名称:" + fruitInfo.name() + ",水果售卖地址:" + fruitInfo.address());
}
}
}
public static void main(String[] args) {
Apple apple = new Apple();
getFruitInfo(apple.getClass());
}
}
四、Java内部类
Java类中不仅可以定义变量和方法,还可以定义类,这样定义在类内部的类就被称为内部类。根据定义的方式不同,内部类分为静态内部类、成员内部类、局部内部类、匿名内部类。
4.1. 静态内部类
定义在类内部的静态类,就是静态内部类。
public class Out {
private static int a;
public static class Inner {
public void print() {
System.out.println(a);
}
}
}
- 静态内部类可以访问外部类所有的静态变量和方法,即使是private的也一样。
- 静态内部类和一般类一致,可以定义静态变量、方法、构造方法等。
- 其他类使用静态内部类需要使用“外部类.静态内部类”方法,如下所示:
Out.Inner inner = new Out.Inner();
inner.print();
- Java集合类HashMap内部就有一个静态内部类Entry。Entry是HashMap存放元素的抽象,HashMap内部维护Entry数组用了存放元素,但是Entry对使用者是透明的。像这种和外部类关系密切的,且不依赖外部类实例的,都可以使用静态内部类。
4.2. 成员内部类
定义在类内部的非静态类,就是成员内部类。成员内部类不能定义静态方法和变量(final 修饰的除外)。这是因为成员内部类是非静态的,类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的。
public class Out {
private static int a;
public class Inner {
public void print() {
System.out.println(a);
}
}
}
4.3. 局部内部类
定义在方法中的类,就是局部类。如果一个类只在某个方法中使用,则可以考虑使用局部类。
public class Out {
private static int a;
public void test() {
final int d=1;
class Inner {
public void print() {
System.out.println(a);
}
}
}
}
4.4. 匿名内部类
匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。
public abstract class Bird {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract int fly();
}
public class BirdTest {
public void test(Bird bird) {
System.out.println(bird.getName() + "能够飞" + bird.fly() + "米");
}
public static void main(String[] args) {
BirdTest birdTest = new BirdTest();
birdTest.test(new Bird() {
@Override
public String getName() {
return "大雁";
}
@Override
public int fly() {
return 10000;
}
});
}
}
五、Java泛型
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定作为一个参数。
5.1. 泛型方法
你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
public static <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.println(element);
}
}
5.2. 泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
public class Box<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
5.3. 类型通配符
类型通配符一般是使用?代替具体的类型参数,例如List<?>在逻辑上是List,List等所有List<具体类型实参>的父类。
5.4. 类型擦除
Java中的泛型基本上都是编译器这个层次来实现的。在生成的Java字节代码中不是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如代码中定义的List和List等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。
类型擦除的基本过程也比较简答,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界。把代码中的类型参数都替换成具体的类。
六、Java序列化
6.1. 保持(持久化)对象及其状态到内存或者磁盘
Java平台允许我们在内存中创建可复用得Java对象,但一般情况下,只有当JVM处于运行时,这些对象才有可能存在,即这些对象得生命周期不会比JVM得生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定对象,并在将来重新读取被保存得对象。Java对象序列化就能够帮助我们实现该功能。
6.2. 序列化对象以字节数组保持-静态成员不保存
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意的是,对象序列化保存的是对象的“状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
6.3. 序列化用户远程对象传输
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供一个标准机制,该API简单易用。
6.4. Serializable实现序列化
在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。
6.5. ObjectOutputStream和ObjectInputStream对对象进行序列化和反序列化
通过ObjectOutputStream和ObjectInputStream对对象进行序列化和反序列化
6.6. writeObject和readObject自定义序列化策略
在类中增加writeObject和readObject方法可以实现自定义序列化策略。
6.7. 序列化ID
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID是否一致。
6.8. 序列化并不保存静态变量
序列化子父类说明
要想将父类对象也序列化,就需要让父类也实现Serializable接口。
Transient关键字阻止该变量被序列化到文件中
1、在变量声明前加上Transient关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值设为初始值,如int型是0,对象型是null。
2、服务端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
七、Java复制
将一个对象的引用复制给另外一个对象,一共有三种方式。第一种方式是直接赋值,第二种方式是浅拷贝,第三种是深拷贝。所以大家都知道,这三种概念实际上都是为了拷贝对象。
7.1. 直接赋值
直接赋值。在Java中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是说a1和a2指向的是同一个对象的地址。因此,当a1变化时,a2里面的成员变量也会跟着变化。
7.2. 浅复制
创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果自动是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。
class Resume implement Cloneable {
public Object clone() {
try {
return (Resume) super.clone();
} catch (Exception e) {
return null;
}
}
}
7.3. 深复制
深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。
class Student implement Cloneable {
String name;
int age;
Professor p;
Student(String name, int age, Professor p) {
this.name = name;
this.age = age;
this.p = p;
}
public Object clone() {
Student o = null;
try {
o = (Student) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
o.p = (Professor) p.clone;
return o;
}
}
7.4. 序列化
在Java语音里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,在从流里读出来,便可以重建对象。
总结
以上就是Java基础知识。