Java基础(面试)

作者:加菲猫 2018-06-17 166 0

JDK 中常用的包有哪些?

java.lang、java.util、java.iojava.net、java.sql。

JDK、JRE、JVM

JDK:Java 开发工具包,提供编译、调试和运行一个 Java 程序所需要的所有工具。

JRE:Java 运行时环境,是 JVM 的实施实现,提供了运行 Java 程序的平台。包含了 JVM,但是不包含编译和调试之类的工具。

JVM:Java 虚拟机,提供了内存管理、垃圾回收和安全机制。运行一个程序时,JVM 负责将字节码转换为特定的机器代码。

JDK 用于开发,JRE 用于运行,JDK 和 JRE 都包含 JVM,JVM 是 Java 语言的核心,并且具有平台独立性。

基本数据类型

类型 位数 字节数
short 2 16
int 4 32
long 8 64
float 4 32
double 8 64
char 2 16

在 64 位的 JVM 中,int 的长度是多少?

int 的长度是一个固定值,32,与平台无关。

int 和 Integer 的区别

Integer 是 int 的包装类型。int 是基本类型,直接存储数值。Integer 是引用类型,引用指向对象。

int 和 Integer 谁占用的内存多?

Integer 会占用更多的内存。因为 Integer 是一个对象,要存储对象的元数据。int 是一个原始类型的数据,所以占用的空间更少。

String、StringBuffer、StringBuilder

  • String:字符串常量,final 修饰。

  • StringBuffer:字符串变量,是线程安全的。

  • StringBuilder:字符串变量,不是线程安全的。

String 和 StringBuffer 的区别

String 和 StringBuffer 的主要区别是性能。

  • String:是不可变的,修改时会产生一个新的对象,并指向新的对象。所以不要对 String 做多次拼接操作,不然会产生很多临时对象,使 GC 开始工作,影响性能。

  • StringBuffer:是可变的,直接在对象上进行操作,而不是产生新的对象。所以需要大量拼接时,应使用 StringBuffer。

JVM 会对 String 的拼接做出优化。例如:String s = "a" + "b"; 会直接优化成 String s = "ab"; 就不存在拼接过程了。

StringBuffer 和 StringBuilder 的区别

  • StringBuffer:线程安全的可变字符串,内部是可变数组实现的。

  • StringBuilder:JDK 1.5 中新增的,功能和 StringBuffer 类似,但是非线程安全。在没有多线程的问题时,使用 StringBuilder 会取得更好的性能。

什么是编译器常量?使用它会有什么风险?

公共静态不可变(public static final)变量,就是编译器常量。这些变量在编译时会被替换,因为编译器知道它们的值,并且在运行时不能改变。

  • 风险:使用了一个内部的或者第三方的公有编译时常量,之后这个值被修改了(替换了一个新的 jar 包),但是客户端还在使用旧的值。

  • 解决办法:更新依赖的 jar 包时,要重新编译程序。

使用什么类型表示价格?

如果不是特别关心性能和内存的话,使用 BigDecimal,否则使用预定义精度的 double。

如何将 byte 转为 String?

使用 String 接收 byte[] 参数的构造器,需要注意编码。

int 类型可以强转为 byte 类型吗?会产生什么问题?

可以。但是 int 是 32 位的,byte 是 8 位的。强制转换时,int 的高 24 位会被丢弃。byte 类型的范围是 -128 到 128。

值传递和引用传递

值传递:传递了对象的一个副本,即使副本被改变,也不会影响源对象。

引用传递:传递的不是实际的对象,而是对象的引用,对引用对象的改变,会映射到所有对象上。

常见的集合有哪些?

Map 接口 和 Collection 接口是所有集合的父接口。

Collection 接口的子接口:Set 接口和List接口。

Map 接口的实现类:HashMap、TreeMap、Hashtable、ConcurrentHashMap、Properties 等。

Set 接口的实现类:HashSet、TreeSet、LinkedHashSet 等。

List 接口的实现类:ArrayList、LinkedList、Stack、Vector 等。

List、Set、Map 的初始容量和负载因子

比较项目 ArrayList Vector HashSet HashMap
初始容量 10 10 16 16
负载因子 0.5 1 0.75 0.75
扩容增量 0.5 倍 + 1 1 倍 1 倍 1 倍
一次扩容后的长度 16 20 32 32

List 和 Set 的区别

List 和 Set 都实现了 Collection 接口。

List 允许对象重复,是一个有序容器。(场景:常用索引访问)

比较项目 ArrayList LinkedList
底层数据结构 数组 双向循环链表
插入 / 删除效率
读取效率
是否支持随机访问
使用下标访问的时间复杂度 O(1) O(n)

Set 不允许对象重复,是一个无序容器。(场景:确保元素不重复)

  • HashSet:底层是 HashMap。保存的值作为 key,先判断 hashcode,再对 key 做 equals,来保证元素的唯一性。

  • TreeSet:底层是红黑树。存入的对象需要实现 Compareable 接口,重写排序规则,来保证元素的唯一性和有序性。

Array 和 ArrayList 的区别

比较项目 Array ArrayList
存储类型 基本类型和对象 对象
长度 指定大小 固定大小,默认为 10个元素

如何打印数组内容?

Arrays.toString(),如果数组中嵌套了数组,则使用 Arrays.deepToString()。

数组在内存中如何分配内存?

值类型的数组,每个数组成员是一个引用,指向栈上的空间。

引用类型的数组,每个数组成员是一个引用,指向堆上的空间。

遍历 ArrayList 时如何移除元素?

使用 ArrayList 的 remove():要从后往前遍历。

使用 Iterator 的 remove():

Iterator itr = list.iterator();while(itr.hasNext()) {    if(...) {
        itr.remove();
    }
}

poll() 和 remove() 的区别

poll() 相当于先 get() 再 remove(),失败时返回空。remove() 失败时抛出异常。

如何实现集合排序?

使用有序集合(TreeSet、TreeMap)。也可以使用有序的集合(List),然后通过 Collections.sort() 来排序。

Arrays.sort()、Collections.sort() 的实现原理

Arrays.sort():快速排序算法,性能为 n × log(n)。

Collections.sort():合并排序算法,性能为 n × log(n)。将指定列表转储到一个数组中,再对数组进行排序,这避免了由于试图在原地对列表进行排序而产生的 n² × log(n)性能。

HashMap 的原理

put():根据 key 的哈希值计算位置。哈希值有可能重复,所以每个位置都是一个链表,采用头插法,因为后插入的数据被查找的可能性较大。

get():根据 key 的哈希值计算位置,然后去这个位置的链表里查找。

计算位置:index = hashCode(key) & (length-1)

与运算:1 & 1 = 1、1 & 0 = 0、0 & 1 = 0、0 & 0 = 0(1 总是让着 0 的)

HashMap 的初始长度是 16,每次扩容的长度必须是 2 的幂。因为 “16” 和 “2的幂” 减一之后的二进制为 1111,所以计算结果完全取决于 key 的二进制后四位,能尽量降低重复率,使元素均匀分布。

HashMap 在 JDK 1.8 中做了哪些优化?是如何优化的?

JDK 1.7:使用一个 Entry 数组来存储数据。根据 key 的哈希值计算位置,如果计算结果相同,那么这些 key 会被定位到 Entry 数组的同一个格子里,形成一个链表。put() / get() 的时间复杂度是 O(n)。

JDK 1.8:使用一个 Node 数组来存储数据,可能是数组结构,也可能是红黑树结构。根据 key 的哈希值计算位置,如果计算结果相同,那么这些 key 会被定位到 Node 数组的同一个格子里,同一个格子里的 key 不超过 8 个时,用链表存储,超过 8 个时,链表会被转化为红黑树。put() / get() 的时间复杂度是 O(logN)。HashMap 中存储的对象只有在实现了 Comparable 接口后,才能提高读写效率。

Comparable 和 Comparator 的区别

Comparable:如果需要自定义比较类型,需要修改源代码。

Comparator:如果需要自定义比较类型,不需要修改源代码,自定义一个比较器,实现其中的比较方法就可以了。

HashMap 的扩容过程

触发条件:HashMap.size() ≥ Capacity(HashMap 的当前长度) × LoadFactor(HashMap 的负载因子,默认为 0.75)

  • 第一步:创建一个新的 Entry 数组,长度是原数组的 2 倍。

  • 第二步:ReHash,遍历原数组,把所有 entry 重新 hash 到新数组中。

HashMap 为什么不是线程安全的?如何确保 HashMap 的线程安全?

造成线程不安全有以下 2 种情况:

  • 在插入新数据时,多线程 hash 后的结果相同,位置也就指向了数组中相同下标的链表中。假如多个线程同时插入,就可能导致有些值被覆盖。

  • 在 HashMap 扩容时,会把所有值重新 hash,插入到扩容后的 “数组 + 链表” 结构中。可能会出现环状链表,在读取 next 节点时,永不为空,导致 CPU 使用率达到 100%。

使用 ConcurrentHashMap 可以确保 HashMap 的线程安全,其中有 “2的幂” 个 segment,每个 segment 都是一个 HashMap,相当于一个二级哈希表,与数据库的水平拆分有些相似。ConcurrentHashMap 采用了锁分段技术,每个 segment 都有一把锁,在保证了线程安全的同时降低了锁的粒度,segment 之间不会互相影响。

是否在同一个 segment 中 操作 是否可并发
不同 写 + 写
相同 写 + 读
相同 写 + 写

ConcurrentHashMap 的原理

put():根据 key 的哈希值计算位置,定位到这个位置的 segment 对象。获得锁。再次通过哈希值定位到 segment 中的具体位置。插入或覆盖 HashEntry 对象。释放锁。

get():根据 key 的哈希值计算位置,定位到这个位置的 segment 对象。再次通过哈希值定位到 segment 中的具体位置,去这个位置的链表里查找。

size():遍历所有的 segment,把 segment 的元素数量累加起来。再把 segment 的修改数量累加起来,判断总修改数是否大于上一次的总修改数,如果大于,重新计算,重试次数 + 1。如果重试次数超过阀值,则对每个 segment 加锁,重新统计,统计完成后释放锁。

在 JDK 1.8 中,不再使用 segment 分离锁,而是使用 CAS 乐观锁,来实现同步问题,底层还是“数组 + 链表 + 红黑树”的结构。

HashMap 与 Hashtable 的区别

HashMap 没有不是线程安全的,允许使用 null 作为 key。Hashtable 使用了 synchronized 关键字,是线程安全的,不允许使用 null 作为 key。

ConcurrentHashMap 与 Hashtable 的区别

ConcurrentHashMap 结合了 HashMap 和 Hashtable 的优势,Hashtable 每次同步执行时都要锁住整个结构,ConcurrentHashMap 锁的方式是细粒度的,只锁住当前的 segment。

WeakMap 和 HashMap 的区别

WeakMap 的功能与 HashMap 相似,但是使用弱引用作为 key,当 key 对象没有任何引用时,key / value 将会被回收。

LinkedHashMap 和 PriorityQueue 的区别

PriorityQueue:是一个优先级队列,最高或最低优先级的元素始终保持在队列的头部。

LinkedHashMap:支持插入顺序(可以按插入顺序遍历元素)和访问顺序(最近访问的元素可以排在尾部)。

什么是 Fail-Fast?原理是什么?

Fail-Fast 是快速失败机制,集合的一种错误检测机制。当 线程-1 遍历集合时,线程-2 修改了集合的结构(添加或删除元素),迭代器可能会抛出 ConcurrentModificationException 异常。

Fail-Fast 是在操作迭代器时产生的,因为迭代器在调用 next()、remove() 时调用了 checkForComodification(),该方法是检测 modCount 与 expectedModCount 是否相等的,若不相等就抛出异常。expectedModCount 是在迭代器中定义的,不会改变,modCount 是在 AbstractList 中定义的全局变量,ArrayList 中的 add()、remove()、clear() 方法都会改变它的值。

Fail-Fast 的解决办法

方法一:便利过程中所有涉及到改变 modCount 的方法都加上 synchronized,或者直接使用 Collections.synchronizedList。(不推荐,增删造成的同步锁可能会阻塞遍历操作)

方法二:使用 CopyOnWriteArrayList,它的 add()、remove()、clear() 都会复制原来的数组,在新的数组上进行操作,开销较大。(推荐)使用场景如下:

  • 不能或不想进行同步遍历,但又需要从并发线程中排除冲突。

  • 遍历的操作数量远远大于修改数组结构的操作数量。

Fail-Fast 和 Fail-Safe 的区别

比较项目 Fail-Fast Fail-Safe
使用它的类 java.util 包中的集合类 java.util.concurrent 包中的集合类
正在遍历的集合结构被改变时 可能抛出 ConcurrentModificationException 异常 不会抛出异常

final、finally、finalize

final:修饰符。

  • 修饰的类不能被继承。

  • 修饰的方法不能被重写。JVM 会尝试将这些方法内联,来提高运行效率。

  • 修饰的变量不能被修改。如果是引用,那么引用不可变,值可变。如果是常量,在编译阶段会存入常量池。

基于编译器对 final 的重排序规则,以下步骤之间不能重排序:

  • 第一步:在构造函数内对一个 final 域的写入。第二步:把这个被构造的对象赋值给一个引用变量。

  • 第一步:初次读一个包含 final 域的对象的引用。第二步:初次读这个 final 域。

finally:语句块。在异常处理中执行清除工作。无论 try 中是否有异常,finally 中的处理一定会被执行。

finalize:方法名。在垃圾收集器删除对象前调用,做必要的清理工作,也给对象一个复活的机会。

强引用、软引用、弱引用、虚引用

强引用:只要引用存在,垃圾收集器永远不会回收。

软引用:非必需引用,内存溢出之前回收。用于实现类似缓存的功能,在内存足够的情况下,直接通过软引用取值,无需从繁忙的真实来源查询数据,可提升速度。当内存不足时,会自动删除这部分缓存数据,再从真实的来源查询数据。

弱引用:第二次垃圾回收时回收。用于监控对象是否已经被垃圾收集器标记,可以通过 isEnQueued() 返回标记状态。

虚引用:垃圾回收时回收。无法通过引用取得对象的值,用于检测对象是否已经从内存值删除。

面向对象的三个特征

封装、继承、多态。

多态的好处

不同的对象调用同一函数时,可以做出不同的处理。

  • 可替换性:可以替换已存在的代码。

  • 可扩充性:增加新的子类时,不会影响已存在的类。

  • 接口性:可以对公共方法实现自己的处理过程。

  • 灵活性。

  • 简化性。

如何实现多态?

接口实现。

继承父类,重写方法。

同一个类中方法的重载。

JVM 是如何实现多态的?

动态绑定技术,执行期间判断引用对象的实际类型,根据实际类型调用对应的方法。

接口的意义

规范、扩展、回调。

抽象类的意义

为子类提供一个公共的类型。

封装子类中重复出定义的内容。

定义抽象方法,子类虽然有不同的实现,但定义是一样的。

接口和抽象类的区别

接口中的方法必须是抽象方法。抽象类中可以没有抽象方法。

接口中只有常量,没有变量,必须是 static final 的,必须被初始化。抽象类中可以有普通的成员变量。

接口可以继承多个父接口。抽象类只能单继承。

JDK 1.8 中的接口会有 default 方法,默认方法可以被实现。

如何选择接口和抽象类?

第一选择是接口,只有在必须要有方法定义和成员变量的时候,才选择抽象类。因为可以实现多个接口,而只能继承一个抽象类,所以使用接口更灵活。

父类中的静态方法能否被子类重写?

不能。重写只适用于实例方法,不能用于静态方法。子类中含有跟父类相同签名的静态方法,叫做隐藏。

什么是不可变对象?

对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer 等包装类。

静态变量和实例变量的区别

静态变量:存储在方法区,属于类所有。

实例变量:存储在堆,引用存储在栈。

能否创建一个包含可变对象的不可变对象?

可以。不要共享可变对象的引用就可以了,如果需要变化,就返回原对象的一个拷贝。如对象中包含一个 Date 对象的引用。

Java 创建对象的几种方式

new、反射、clone、序列化。

前两种都需要显示的调用构造方法。第一种耦合度最高,松耦合的第一步就是要减少 new 的使用。

switch 能否使用 String 作为参数?

JDK 1.7 以后可以使用。以前只能使用 byte、char、short、int 及它们的封装类,还有 Enum。

switch 能否作用在 byte、long 上?

byte 可以,long 不行。

String s1 = "ab"; String s2 = "a" + "b"; String s3 = "a"; String s4 = "b"; String s5 = s3 + s4; s5 == s2 返回什么?

返回 false。在编译过程中,s2 被直接优化成 "ab",并被放置在常量池中,s5 则被放置在堆,相当于 s5 = new String("ab");

String 中的 intern 方法

intern() 首先从常量池中查找是否存在该常量值,如果不存在,就创建,存在则直接返回。

String s1 = "aa";String s2 = s1.intern();System.out.print(s1 == s2);

结果:true。

Object 中有哪些公共方法?

equals()、clone()、getClass()、notify()、notifyAll()、wait()、toString()。

为什么要有不同的引用类型?

控制对象被回收的时机,不同的引用类型是对 GC 回收时机不可控的妥协。

利用软引用和弱引用解决 OOM 的问题:用 HashMap 存储图片路径和图片对象的软引用之间的映射关系,内存不足时,JVM 会自动回收这些缓存图片对象所占用的空间。

利用软引用实现对象的高速缓存:比如我们创建一个 Student 类,每次查询一个学生的信息,都需要重新构建一个实例,由于生命周期较短,会引起多次 GC,影响性能。使用软引用和 HashMap 的结合可以构建高速缓存。

== 和 equals() 的区别、equals() 和 hashcode() 的区别

== 是运算符,用于比较 2 个变量的值是否相等。

equals() 是 Object 类中的一个方法,用于比较 2 个对象是否相等。

hashcode() 是 Object 类中的一个方法,返回一个哈希值。

  • 如果 2 个对象的 equals() 返回 true,那么 hashcode() 也会返回相同的值。

  • 如果 2 个对象的 equals() 返回 false,那么 hashcode() 有可能会返回相同的值。

可以在 hashcode 中使用随机数字吗?

不可以,因为同一对象的哈希值必须是固定的。

3 * 0.1 == 0.3 返回什么?

false,因为有些浮点数不能完全精确的表示出来。

a = a + b 和 a += b 有什么区别?

+= 会进行隐式的类型转换,a += b 的结果会强制转变成与 a 一致。a = a + b 不会自动转换类型,结果类型与 a 不符时,可能会报错。

short s1 = 1; s1 = s1 + 1; 是否有错?怎么改?

有错,short 类型在进行运算时会自动提升成 int 类型,再赋值给 short 类型就会报错了。改成 s1 += 1 就可以了。

short s1 = 1; s1 += 1; 是否有错?怎么改?

没错,+= 会把结果强制转换成 s1 的类型。

& 和 && 的区别

& 是位操作,不具有短路特性(第一个结果已经为 false,还要计算第二个表达式)。如果 a 对象为空,if(a!=null & a.equals("")) 会报错(空指针异常)。

&& 是逻辑运算符,具有短路特性(第一个结果为 false 时,不计算第二个表达式)。

一个 .java 文件里可以有几个类?

只能有一个 public 公共类,可以有多个 default 修饰的类。

如何退出多层嵌套循环?

在外层循环添加标识符,使用标号和 break。

内部类的作用

内部类可以有多个实例,每个实例都有自己的状态信息,可以让多个内部类以不同的方式实现同一接口,或继承同一个类。创建内部类对象的时刻不依赖于外部类对象的创建,它就像一个独立的实体,提供了更好的封装(除了该外围类,其它类都不能访问)。

clone() 是哪个类的方法?

java.lang.Cloneable 是一个标示性的接口,不包含任何方法,clone() 方法在 Object 类中。clone() 是一个本地方法。

深拷贝和浅拷贝的区别

浅拷贝:被复制对象的所有变量都是原来的值,所有对其它对象的引用也都是指向原理的对象。仅拷贝这个对象,不拷贝这个对象中引用到的对象。

深拷贝:被复制对象的所有变量都是原来的值,所有对其它对象的引用都指向被复制过的新对象。不仅拷贝这个对象,还拷贝了这个对象中引用到的对象。

static 有哪些用法?

静态块:用于初始化操作。

静态内部类:比如 main 方法。

静态导包:import static java.lang.Math.*; 不需要类名,直接使用包中的方法。

静态变量、静态方法。

Java 反射

在不能直接 new 对象的时候,可以使用反射机制来操纵一个类的属性、方法、构造器。

新建 Student 类

package reflect;public class Student {    private Integer id;    private String name;    private String address;    public Student() {
    }    public Student(Integer id, String name, String address) {        super();        this.id = id;        this.name = name;        this.address = address;
    }    public Integer getId() {        return id;
    }    public void setId(Integer id) {        this.id = id;
    }    public String getName() {        return name;
    }    public void setName(String name) {        this.name = name;
    }    public String getAddress() {        return address;
    }    public void setAddress(String address) {        this.address = address;
    }
}

获取类的三种方式

Class<?> stuClass = Class.forName("reflect.Student");Class<?> stuClass = obj.getClass();Class<?> stuClass = Student.class;

获取构造器

// 获取全部构造器Constructor[] constructors = stuClass.getDeclareConstructors();// 根据参数获取构造器,参数是构造器的参数类型数组Constructor constructor = stuClass.getDeclareConstructor(new Class[]{Integer.class, String.class, String.class});// 创建对象,参数是构造器的参数值数组Object student = constructor.newInstance(new Object[]{1, "张三", "火星"});

获取属性

// 获取全部属性Field[] myFields = stuClass.getDeclareFields();// 根据名称获取属性Field myField = stuClass.getDeclareField("name");// 给属性赋值,第一个参数是对象,第二个参数是属性值,方法是私有的时,要先设置 accessible 为truemyField.setAccessible(true);
myField.set(student, "李四");

获取方法

// 获取全部方法Method[] myMethods = stuClass.getDeclareMethods();// 根据名称及参数获得方法,第一个参数是方法名,第二个参数是方法的参数类型数组Method myMethod = stuClass.getDeclareMethod("setName", new Class[]{String.class});// 调用方法,第一个参数是对象,第二个参数是方法的参数值数组myMethod.invoke(student, new Object[]{"王五"});

Cloneable 接口的实现原理

package reflect;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class CopyObj {    public static void main(String[] args) throws Exception {
        Student student = new Student(1, "张三", "地球");        Object newStudent = copy(student);
    }    public static Object copy(Object obj) throws Exception {
        Class<?> objClass = obj.getClass();        // 获得构造器,创建对象
        Constructor<?> constructor = objClass.getConstructor(new Class[] {});        Object newObj = constructor.newInstance(new Object[] {});        // 获得属性,遍历拷贝
        Field[] fields = objClass.getDeclaredFields();        for (Field field : fields) {            String fieldName = field.getName();            // 获得 get 方法,调用 get 方法,获得对象的属性值
            String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            Method getMethod = objClass.getDeclaredMethod(getMethodName, new Class[] {});            Object getResult = getMethod.invoke(obj, new Object[] {});            // 获得 set 方法,调用 set 方法,给新对象的属性赋值
            String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            Method setMethod = objClass.getDeclaredMethod(setMethodName, new Class[] { getMethod.getReturnType() });
            setMethod.invoke(newObj, new Object[] { getResult });
        }        return newObj;
    }
}

动态代理

装饰接口中的方法,在处理业务的前后加入统一操作。

新建一个读书接口,其中有一个阅读方法

package proxy;public interface ReadBook {    public void read();
}

新建一个读书类,实现读书接口

package proxy;public class ReadBookImpl implements ReadBook {    @Override
    public void read() {
        System.out.println("read book");
    }
}

新建一个阅读代理

package proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;
public class ReadProxy implements InvocationHandler {
    // 被代理的真实角色
    private Object obj;
    public ReadProxy(Object obj) {
        super();
        this.obj = obj;
    }
    /**
     * jdk 的动态代理,被代理的对象必须实现接口
     * 第二个参数 method 是被代理对象的接口方法
     * 第三个参数 args 是被代理对象的接口方法的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println("开始");        System.out.println("-----参数-----");        if (args != null) {            for (Object arg : args) {                System.out.println(arg);
            }
        }        System.out.println("-----参数-----");        Object invoke = method.invoke(obj, args);        System.out.println("结束");        return invoke;
    }
}

代理读书对象

package proxy;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;public class Client {    public static void main(String[] args) {        // 创建被代理的接口实现类对象
        ReadBookImpl readBook = new ReadBookImpl();        // 创建代理对象,第一个参数是被代理的对象的类加载器,第二个参数是被代理的对象的类的所有接口,第三个参数是代理类的对象
        ReadBook readBookProxy = (ReadBook) Proxy.newProxyInstance(readBook.getClass().getClassLoader(),
                readBook.getClass().getInterfaces(), new ReadProxy(readBook));
        readBookProxy.read();
        System.out.println("----------");        // 因为 ArrayList 实现了 List 接口,所以 list 对象也可以被代理
        List list = new ArrayList();        List listProxy = (List) Proxy.newProxyInstance(list.getClass().getClassLoader(),                list.getClass().getInterfaces(), new ReadProxy(list));
        listProxy.add("abc");
    }
}

异常分类以及处理机制

运行时异常:NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)。可以不用 try-catch 捕获。

编译异常:IOException、SQLException、自定义的 Exception 异常。必须用 try-catch 捕获。

处理机制:抛出异常,捕捉异常。

  • 当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。

  • 在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着 Java 程序的终止。

发表评论