目录
一、基础
1、java中操作字符串都有哪些类?它们之间有什么区别?
2、JDK和JRE有什么区别?
3、continue、break和return的区别是什么?/终止for循环的方式
4、== 和 equals 的区别是什么?
5、面向对象三大特征
6、重写和重载的区别
7、final、finally、finalize三者的区别?
8、throw和throws的区别
9、接口和抽象类有什么共同点和区别?
10、为什么要有接口和继承?
11、抽象方法能不能加final?
12、Java为什么被称为平台无关性语言?
13、什么是反射?
14、反射机制有什么优缺点?
15、反射的运用场景
16、JDK中哪些东西用了反射?
17、利用反射获取 Class 对象的四种方式
18、动态代理是什么?有哪些应用?
19、怎么实现动态代理?
20、动态代理的实现方式有哪些?
21、JDK 动态代理和 CGLIB 动态代理对比
22、什么是静态代理?
23、静态代理和动态代理的对比
24、什么是泛型?有什么作用?
25、泛型的使用方式有哪几种?
二、异常
1、Exception 和 Error 有什么区别?
2、Checked Exception 和 Unchecked Exception 有什么区别?
3、Throwable 类常用方法有哪些?
4、try-catch-finally 中哪个部分可以省略?
5、如果 catch 中 return 了,finally 还会执行吗?
6、什么情形下,finally代码块不会执行?
7、常见的异常类有哪些?
8、throw和throws的区别?
9、final、finally、finalize有什么区别?
三、I/O
1、什么是序列化?什么是反序列化?
2、Java 序列化中如果有些字段不想进行序列化,怎么办?
3、Java如何实现序列化?
4、实际开发中有哪些用到序列化和反序列化的场景?
5、获取用键盘输入常用的两种方法
6、Java 中 IO 流分为几种?
7、既然有了字节流,为什么还要有字符流?
8、Java中的IO 方式/BIO和NIO区别
9、IO 多路复用模型
10、io输入与输出的分别相对于谁来说
操作字符串的类有:String、StringBuffer、StringBuilder。
String和StringBuffer、StringBuilder的区别在于String声明的是不可变的对象,每次操作都会生成新的String对象,然后将指针指向新的String对象,而StringBuffer、StringBuilder可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用String。
StringBuffer和StringBuilder最大的区别在于,StringBuffer是线程安全的,而StringBuilder是非线程安全的,但StringBuilder的性能高于StringBuffer,所以在单线程环境下推荐使用StringBuilder,多线程环境下推荐使用StringBuffer。
JDK:Java开发工具包,提供了Java的开发环境和运行环境。
JRE:Java运行环境,为Java的运行提供了所需环境。
JDK包含了JRE,同时还包含了编译Java源码的编译器javac,还包含了很多Java程序调试和分析的工具。如果只需要运行Java程序,只安装JRE就可以了,如果需要编写Java程序,需要安装JDK。
continue:指跳出当前的这一次循环,继续下一次循环。
break:指跳出整个循环体,继续执行循环下面的语句。
return:用于跳出所在方法,结束该方法的运行。return一般有两种用法:
return;:直接使用return结束方法执行,用于没有返回值函数的方法
returnvalue;:return一个特定值,用于有返回值函数的方法
==:对于基本类型和引用类型 == 的作用效果是不同的
基本类型:比较的是值是否相同
引用类型:比较的是引用是否相同
equals
equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
封装:封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
继承:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态:表示一个对象具有多种状态,具体表现为父类的引用指向子类的对象
从概念上来看
重载:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。
从规则上来看
重载:
重载可以有不同的访问修饰符
重载能够抛出不同的异常
重载一定要有不同的参数列表
重写:
重写访问修饰符的限制一定要大于被重写方法的访问修饰符
重写的参数列表要完全和被重写的方法相同
重写返回的类型要和被重写的方法的返回类型相同
重写方法一定不可以抛出新的检查异常或者是比被重写方法申明更加宽泛的检查型异常
从类的属关系上来看
重写:重写是垂直关系,是子类和父类之间的关系
重载:重载是水平关系,是同一个类中方法之间的关系
final可以修饰类,变量,方法,修饰的类不能被继承,修饰的变量不能重新赋值,修饰的方法不能被重写。
finally用于抛异常,finally代码块内语句无论是否发生异常,都会执行finally,常用于一些流的关闭。
finalize方法用于垃圾回收。一般情况下不需要我们实现finalize。
throw 表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
throws 属于异常处理的一种方式,声明在方法的声明处。
共同点:
都不能被实例化。
都可以包含抽象方法。
都可以有默认实现的方法(Java 8 可以用 default 关键在接口中定义默认方法)。
区别:
抽象类的子类使用 extends 来继承;接口用 implements 来实现。
抽象类可以有构造函数;接口不能有。
接口主要用于对类的行为进行约束,实现某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
一个类只能继承一个类,但是可以实现多个接口。
接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
继承减少了代码的冗余,提高了代码的复用性,便于功能的扩展,但是继承只能单继承,有局限性,而接口可以实现多个接口。
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。
Java 虚拟机实现平台无关性,JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。
Java反射机制主要提供了以下功能:
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的方法
优点 : 可以让代码更加灵活、为各种框架提供开箱即用的功能提供了便利
缺点 :虽然在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
动态代理
注解
在JDK中,主要有以下类来实现Java反射机制,这些类都位于java.lang.reflect包中:
Class类:代表一个类。
Field 类:代表类的成员变量(成员变量也称为类的属性)。
Method类:代表类的方法。
Modifier类:代表修饰符。
lConstructor 类:代表类的构造方法。
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
知道具体类的情况下可以使用类名.Class的方式
通过 Class.forName()传入类的全路径获取
通过对象实例instance.getClass()获取
通过类加载器xxxClassLoader.loadClass()传入类路径获取
动态代理:
当想要给实现了某个接口的类中的方法,加一些额外的处理,比如说加日志,加事务等,可以给这个类创建一个代理,就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的,具有解耦意义,灵活,扩展性强。
动态代理的应用:
Spring的AOP
加事务
加权限
加日志
首先定义一个接口,还要定义一个InvocationHandler处理类,将实现接口的类的对象传递给这个处理类。
再定义一个工具类Proxy(习惯性将其称为代理类,因为调用他的newInstance()可以产生代理对象,其实他只是一个产生代理对象的工具类)。利用InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。
JDK 动态代理
在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。
Proxy 类中使用频率最高的方法是:newProxyInstance(),这个方法主要用来生成一个代理对象。
要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。
你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。
JDK 动态代理类使用步骤
定义一个接口及其实现类
自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
通过 Proxy.newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h) 方法创建代理对象;
invoke() 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke() 方法,然后 invoke() 方法代替我们去调用了被代理对象的原生方法。
CGLIB 动态代理
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,可以用 CGLIB 动态代理机制来避免。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。
需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。
CGLIB 动态代理类使用步骤
定义一个类
自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
通过 Enhancer 类的 create()创建代理类;
JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
静态代理中,我们对目标对象的每个方法的增强都是手动完成的,不灵活且麻烦,比如接口一旦新增加方法,目标对象和代理对象都要进行修改,需要对每个目标类都单独写一个代理类。
静态代理实现步骤:
定义一个接口及其实现类
创建一个代理类同样实现这个接口
将目标对象注入代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情
灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。
编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 ArrayList
并且,原生 List 返回类型是 Object ,需要手动转换类型才能使用,使用泛型后编译器自动转换。
泛型类
泛型接口
泛型方法
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:
Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
Error :Error 属于程序无法处理的错误 ,不建议通过catch捕获 。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
Checked Exception
即受检查异常,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundException 、SQLException...。
Unchecked Exception
即不受检查异常,Java 代码在编译过程中 ,即使不处理不受检查异常也可以正常通过编译。
RuntimeException 及其子类都统称为非受检查异常,常见的有:
NullPointerException(空指针异常)
IllegalArgumentException(非法数据异常,比如方法入参类型错误)
NumberFormatException(数字格式化异常,IllegalArgumentException的子类)
ArrayIndexOutOfBoundsException(数组越界异常)
ClassCastException(类型转换异常)
ArithmeticException(算术运算异常)
SecurityException (安全异常,比如权限不够)
UnsupportedOperationException(不支持的操作异常,比如重复创建同一用户)
String getMessage(): 返回异常发生时的简要描述
String toString(): 返回异常发生时的详细信息
String getLocalizedMessage(): 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
void printStackTrace(): 在控制台上打印 Throwable 对象封装的异常信息
以下三种情况都是可以的:
try-catch
try-finally
try-catch-finally
可以省略catch或者finally,catch和finally不可以同时省略。
会。
finally的作用是,无论出现什么状况,finally里的代码一定会被执行。
如果在catch中return了,会在return之前,先执行finally代码块。
如果finally代码块中含有return语句,会覆盖其他地方的return。
对于基本数据类型的数据,在finally块中改变return的值对返回值没有影响,而对引用数据类型的数据会有影响。
finally 之前虚拟机被终止运行
程序所在的线程死亡
关闭 CPU
没有进入try代码块
System.exit()强制退出程序
守护线程被终止
NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
SQLException:提供关于数据库访问错误或其他错误信息的异常。
IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。
IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。
IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。
NoSuchMethodException:无法找到某一特定方法时,抛出该异常。
SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。
UnsupportedOperationException:当不支持请求的操作时,抛出该异常。
RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。
throw表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
throws属于异常处理的一种方式,声明在方法的声明处。
final可以修饰类,变量,方法,修饰的类不能被继承,修饰的变量不能重新赋值,修饰的方法不能被重写。
finally用于抛异常,finally代码块内语句无论是否发生异常,都会执行finally,常用于一些流的关闭。
finalize方法用于垃圾回收。一般情况下不需要我们实现finalize。
序列化:将数据结构或对象转换成二进制字节流的过程
反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
对于不想进行序列化的变量,使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
关于 transient 还有几点注意:
transient 只能修饰变量,不能修饰类和方法。
transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0。
static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。
实现Serializable接口
实现Externalizable接口
对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
将对象存储到文件中的时候需要进行序列化,将对象从文件中读取出来需要进行反序列化。
将对象存储到缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化。
方法 1:通过 Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
方法 2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流。
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
回答:字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是非常耗时,并且,如果不知道编码类型很容易出现乱码问题。所以, I/O 流就提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
BIO(Blocking I/O)
同步阻塞式IO,是传统IO。
同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间,特点是模式简单使用方便,但是并发处理能力低。
NIO(Non-blocking/New I/O)
同步非阻塞IO,是传统IO的升级。
NIO有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)。
它是支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。
NIO 的选择器 ( Selector ) 也被称为多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。
AIO(Asynchronous I/O)
是NIO的升级,也叫NIO2,实现了异步非堵塞IO。
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。
IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。
相对于程序来说