动态代理是基于什么原理
代理一般分为静态代理
和 动态代理
,它们都是代理模式的一种应用,静态代理指的是在程序运行前已经编译好,程序知道由谁来执行代理方法。
而动态代理只有在程序运行期间才能确定,相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。可以说动态代理是基于 反射
实现的。通过反射我们可以直接操作类或者对象,比如获取类的定义,获取声明的属性和方法,调用方法,在运行时可以修改类的定义。
动态代理是一种在运行时构建代理、动态处理方法调用的机制。动态代理的实现方式有很多,Java 提供的代理被称为 JDK 动态代理
,JDK 动态代理是基于类的继承。
int 和 Integer 的区别
int 和 Integer 区别可就太多了
- int 是 Java 中的基本数据类型,int 代表的是
整型
,一个 int 占 4 字节,也就是 32 位,int 的初始值是默认值是 0 ,int 在 Java 内存模型中被分配在栈中,int 没有方法。 - Integer 是 Java 中的基本数据类型的包装类,Integer 是一个对象,Integer 可以进行方法调用,Integer 的默认值是 null,Integer 在 Java 内存模型中被分配在堆中。int 和 Integer 在计算时可以进行相互转换,int -> Integer 的过程称为
装箱
,Integer -> int 的过程称为拆箱
,Integer 还有 IntegerCache ,会自动缓存 -128 - 127 中的值
Java 提供了哪些 I/O 方式
Java I/O 方式有很多种,传统的 I/O 也称为 BIO
,主要流有如下几种
Java I/O 包的实现比较简单,但是容易出现性能瓶颈,传统的 I/O 是基于同步阻塞的。
JDK 1.4 之后提供了 NIO
,也就是位于 java.nio
包下,提供了基于 channel、Selector、Buffer的抽象,可以构建多路复用、同步非阻塞 I/O 程序。
JDK 1.7 之后对 NIO 进行了进一步改进,引入了 异步非阻塞
的方式,也被称为 AIO(Asynchronous IO)
。可以用生活中的例子来说明:项目经理交给手下员工去改一个 bug,那么项目经理不会一直等待员工解决 bug,他肯定在员工解决 bug 的期间给其他手下分配 bug 或者做其他事情,员工解决完 bug 之后再告诉项目经理 bug 解决完了。
谈谈你知道的设计模式
一张思维导图镇场
比如全局唯一性可以用 单例模式
。
可以使用 策略模式
优化过多的 if...else...
制定标准用 模版模式
接手其他人的锅,但不想改原来的类用 适配器模式
使用 组合
而不是继承
使用 装饰器
可以制作加糖、加奶酪的咖啡
代理
可以用于任何中间商......
Comparator 和 Comparable 有什么不同
- Comparable 更像是自然排序
- Comparator 更像是定制排序
同时存在时采用 Comparator(定制排序)的规则进行比较。
对于一些普通的数据类型(比如 String, Integer, Double…),它们默认实现了Comparable 接口,实现了 compareTo 方法,我们可以直接使用。
而对于一些自定义类,它们可能在不同情况下需要实现不同的比较策略,我们可以新创建 Comparator 接口,然后使用特定的 Comparator 实现进行比较。
Object 类中一般都有哪些方法
Object 类是所有对象的父类,它里面包含一些所有对象都能够使用的方法
- hashCode():用于计算对象的哈希码
- equals():用于对象之间比较值是否相等
- toString(): 用于把对象转换成为字符串
- clone(): 用于对象之间的拷贝
- wait(): 用于实现对象之间的等待
- notify(): 用于通知对象释放资源
- notifyAll(): 用于通知所有对象释放资源
- finalize(): 用于告知垃圾回收器进行垃圾回收
- getClass(): 用于获得对象类
反射的基本原理,反射创建类实例的三种方式是什么
反射机制就是使 Java 程序在运行时具有自省(introspect)
的能力,通过反射我们可以直接操作类和对象,比如获取某个类的定义,获取类的属性和方法,构造方法等。
创建类实例的三种方式是
- 对象实例.getClass();
- 通过 Class.forName() 创建
- 对象实例.newInstance() 方法创建
强引用、若引用、虚引用和幻象引用的区别
我们说的不同的引用类型其实都是逻辑上的,而对于虚拟机来说,主要体现的是对象的不同的可达性(reachable)
状态和对垃圾收集(garbage collector)
的影响。
可以通过下面的流程来对对象的生命周期做一个总结
对象被创建并初始化,对象在运行时被使用,然后离开对象的作用域,对象会变成不可达并会被垃圾收集器回收。图中用红色标明的区域表示对象处于强可达阶段。
JDK1.2 介绍了 java.lang.ref
包,对象的生命周期有四个阶段:强可达(Strongly Reachable)
、软可达(Soft Reachable)
、弱可达(Weak Reachable)
、 幻象可达(Phantom Reachable)
。
如果只讨论符合垃圾回收条件的对象,那么只有三种:软可达、弱可达和幻象可达。
- 软可达:软可达就是我们只能通过软引用才能访问的状态,软可达的对象是由
SoftReference
引用的对象,并且没有强引用的对象。软引用是用来描述一些还有用但是非必须的对象。垃圾收集器会尽可能长时间的保留软引用的对象,但是会在发生OutOfMemoryError
之前,回收软引用的对象。如果回收完软引用的对象,内存还是不够分配的话,就会直接抛出 OutOfMemoryError。 - 弱可达:弱可达的对象是
WeakReference
引用的对象。垃圾收集器可以随时收集弱引用的对象,不会尝试保留软引用的对象。 - 幻象可达:幻象可达是由
PhantomReference
引用的对象,幻象可达就是没有强、软、弱引用进行关联,并且已经被 finalize 过了,只有幻象引用指向这个对象的时候。
除此之外,还有强可达和不可达的两种可达性判断条件
- 强可达:就是一个对象刚被创建、初始化、使用中的对象都是处于强可达的状态
不可达(unreachable)
:处于不可达的对象就意味着对象可以被清除了。
下面是一个不同可达性状态的转换图
判断可达性条件,也是 JVM 垃圾收集器决定如何处理对象的一部分考虑因素。
所有的对象可达性引用都是 java.lang.ref.Reference
的子类,它里面有一个get()
方法,返回引用对象。如果已通过程序或垃圾收集器清除了此引用对象,则此方法返回 null 。也就是说,除了幻象引用外,软引用和弱引用都是可以得到对象的。而且这些对象可以人为拯救
,变为强引用,例如把 this 关键字赋值给对象,只要重新和引用链上的任意一个对象建立关联即可。
final、finally 和 finalize() 的区别
这三者可以说是没有任何关联之处,我们上面谈到了,final 可以用来修饰类、变量和方法,可以参考上面 final 的那道面试题。
finally 是一个关键字,它经常和 try 块一起使用,用于异常处理。使用 try...finally 的代码块种,finally 部分的代码一定会被执行,所以我们经常在 finally 方法中用于资源的关闭操作。
JDK1.7 中,推荐使用 try-with-resources
优雅的关闭资源,它直接使用 try(){} 进行资源的关闭即可,就不用写 finally 关键字了。
finalize 是 Object 对象中的一个方法,用于对象的回收方法,这个方法我们一般不推荐使用,finalize 是和垃圾回收关联在一起的,在 Java 9 中,将 finalize 标记为了 deprecated
, 如果没有特别原因,不要实现 finalize 方法,也不要指望他来进行垃圾回收。
内部类有哪些分类,分别解释一下
在 Java 中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。
内部类的分类一般主要有四种
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类
静态内部类
就是定义在类内部的静态类,静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;
成员内部类
就是定义在类内部,成员位置上的非静态类,就是成员内部类。成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。
定义在方法中的内部类,就是局部内部类
。定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。
匿名内部类
就是没有名字的内部类,除了没有名字,匿名内部类还有以下特点:
- 匿名内部类必须继承一个抽象类或者实现一个接口
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
说出几种常用的异常
- NullPointerException: 空指针异常
- NoSuchMethodException:找不到方法
- IllegalArgumentException:不合法的参数异常
- IndexOutOfBoundException: 数组下标越界异常
- IOException:由于文件未找到、未打开或者I/O操作不能进行而引起异常
- ClassNotFoundException :找不到文件所抛出的异常
- NumberFormatException:字符的UTF代码数据格式有错引起异常;
- InterruptedException:线程中断抛出的异常
静态绑定和动态绑定的区别
一个Java 程序要经过编写、编译、运行三个步骤,其中编写代码不在我们讨论的范围之内,那么我们的重点自然就放在了编译
和 运行
这两个阶段,由于编译和运行阶段过程相当繁琐,下面就我的理解来进行解释:
Java 程序从源文件创建到程序运行要经过两大步骤:
1、编译时期是由编译器将源文件编译成字节码的过程
2、字节码文件由Java虚拟机解释执行
绑定
绑定就是一个方法的调用与调用这个方法的类连接在一起的过程被称为绑定。
绑定主要分为两种:
静态绑定 和 动态绑定
绑定的其他叫法
静态绑定 == 前期绑定 == 编译时绑定
动态绑定 == 后期绑定 == 运行时绑定
为了方便区分:下面统一称呼为静态绑定和动态绑定
静态绑定
在程序运行前,也就是编译时期 JVM 就能够确定方法由谁调用,这种机制称为静态绑定
识别静态绑定的三个关键字以及各自的理解
如果一个方法由 private、static、final 任意一个关键字所修饰,那么这个方法是前期绑定的
构造方法也是前期绑定
private:private 关键字是私有的意思,如果被 private 修饰的方法是无法由本类之外的其他类所调用的,也就是本类所特有的方法,所以也就由编译器识别此方法是属于哪个类的
public class Person { private String talk; private String canTalk(){ return talk; } } class Animal{ public static void main(String[] args) { Person p = new Person(); // private 修饰的方法是Person类独有的,所以Animal类无法访问(动物本来就不能说话) // p.canTalk(); } }
final:final 修饰的方法不能被重写,但是可以由子类进行调用,如果将方法声明为 final 可以有效的关闭动态绑定
public class Fruit { private String fruitName; final String eatingFruit(String name){ System.out.println("eating " + name); return fruitName; } } class Apple extends Fruit{ // 不能重写final方法,eatingFruit方法只属于Fruit类,Apple类无法调用 // String eatingFruit(String name){ // super.eatingFruit(name); // } String eatingApple(String name){ return super.eatingFruit(name); } }
static:static 修饰的方法比较特殊,不用通过 new 出某个类来调用,由类名.变量名
直接调用该方法,这个就很关键了,new 很关键,也可以认为是开启多态的导火索,而由类名.变量名直接调用的话,此时的类名是确定的,并不会产生多态,如下代码:
public class SuperClass { public static void sayHello(){ System.out.println("由 superClass 说你好"); } } public class SubClass extends SuperClass{ public static void sayHello(){ System.out.println("由 SubClass 说你好"); } public static void main(String[] args) { SuperClass.sayHello(); SubClass.sayHello(); } }
SubClass 继承 SuperClass 后,在
是无法重写 sayHello 方法的,也就是说 sayHello() 方法是对子类隐藏的,但是你可以编写自己的 sayHello() 方法,也就是子类 SubClass 的sayHello() 方法,由此可见,方法由 static 关键词所修饰,也是编译时绑定
动态绑定
在运行时根据具体对象的类型进行绑定
除了由 private、final、static 所修饰的方法和构造方法外,JVM 在运行期间决定方法由哪个对象调用的过程称为动态绑定
如果把编译、运行看成一条时间线的话,在运行前必须要进行程序的编译过程,那么在编译期进行的绑定是前期绑定,在程序运行了,发生的绑定就是后期绑定
public class Father { void drinkMilk(){ System.out.println("父亲喜欢喝牛奶"); } } public class Son extends Father{ @Override void drinkMilk() { System.out.println("儿子喜欢喝牛奶"); } public static void main(String[] args) { Father son = new Son(); son.drinkMilk(); } }
Son 类继承 Father 类,并重写了父类的 dringMilk() 方法,在输出结果得出的是儿子喜欢喝牛奶。那么上面的绑定方式是什么呢?
上面的绑定方式称之为动态绑定
,因为在你编写 Father son = new Son() 的时候,编译器并不知道 son 对象真正引用的是谁,在程序运行时期才知道,这个 son 是一个 Father 类的对象,但是却指向了 Son 的引用,这种概念称之为多态,那么我们就能够整理出来多态的三个原则:
- 继承
- 重写
- 父类对象指向子类引用
也就是说,在 Father son = new Son() ,触发了动态绑定机制。
动态绑定的过程
- 虚拟机提取对象的实际类型的方法表;
- 虚拟机搜索方法签名;
- 调用方法。
动态绑定和静态绑定的特点
静态绑定
静态绑定在编译时期触发,那么它的主要特点是
1、编译期触发,能够提早知道代码错误
2、提高程序运行效率
动态绑定
1、使用动态绑定的前提条件能够提高代码的可用性,使代码更加灵活。
2、多态是设计模式的基础,能够降低耦合性。