参考
Java基础知识详解:从面向对象到异常处理-CSDN博客
2024年 Java 面试八股文(20w字)_java面试八股文-CSDN博客
基础知识
java概述
什么是java?
java是一种面向对象的编程语言
java特点
- 面向对象(继承,多态,封装);
- 平台无关性,平台无关性的具体表现在于,Java 是“一次编写,到处运行(Write Once,Run any Where)”的语言,因此采用 Java 语言编写的程序具有很好的可移植性,而保证这一点的正是 Java 的虚拟机机制。在引入虚拟机之后,Java 语言在不同的平台上运行不需要重新编译。
- 支持多线程。C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持;
- 编译与解释并存;
JDK、JRE、JVM 三者之间的关系?
- JVM:Java 虚拟机,Java 程序运行在 Java 虚拟机上。针对不同系统的实现(Windows,Linux,macOS)不同的 JVM,因此 Java 语言可以实现跨平台。
- JRE:Java 运⾏时环境。它是运⾏已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,Java 命令和其他的⼀些基础构件。但是,它不能⽤于创建新程序。
- JDK: Java开发工具包,它拥有 JRE 所拥有的⼀切,还有编译器(javac)和⼯具。它能够创建和编译程序。
JDK = JRE + 多种Java开发工具
JRE = JVM + 各种类库
这三者的关系是一层层的嵌套关系,
JDK > JRE > JVM
说说什么是跨平台性?原理是什么
所谓跨平台性,是指 Java 语言编写的程序,一次编译后,可以在多个系统平台上运行。
实现原理:Java 程序是通过 Java 虚拟机在系统平台上运行的,只要该系统可以安装相应的 Java 虚拟机,该系统就可以运行 java 程序。
什么是字节码?采用字节码的好处是什么?
所谓的字节码,就是 Java 程序经过编译之类产生的.class 文件,字节码能够被虚拟机识别,从而实现 Java 程序的跨平台性。
Java 程序从源代码到运行主要有三步:
- 编译:将我们的代码(.java)编译成虚拟机可以识别理解的字节码(.class)
- 解释:虚拟机执行 Java 字节码,将字节码翻译成机器能识别的机器码
- 执行:对应的机器执行二进制机器码
为什么说 Java 语言“编译与解释并存”?
- 编译型语言:在程序执行之前,整个源代码会被编译成机器码或者字节码,生成可执行文件。执 行时直接运行编译后的代码,速度快,但跨平台性较差。
- 解释型语言:在程序执行时,逐行解释执行源代码,不生成独立的可执行文件。通常由解释器动态解释并执行代码,跨平台性好,但执行速度相对较慢
比如,你想读一本外国的小说,你可以找一个翻译人员帮助你翻译,有两种选择方式,
编译:可以先等翻译人员将全本的小说(也就是源码)都翻译成汉语,再去阅读,
解释:可以让翻译人员翻译一段,你在旁边阅读一段,慢慢把书读完。
编译 + 解释就是:先给你一个大概的翻译,再逐步为你详细解释,既有预先的准备,又有灵活的实时翻译。
因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(\*.class
文件),这种字节码必须再经过 JVM,解释成操作系统能识别的机器码,在由操作系统执行。因此,我们可以认为 Java 语言编译与解释并存。
基础语法
java的数据类型
自动类型转换、强制类型转换
- 自动类型转换(隐式转换):当目标类型的范围大于源类型时,Java会自动将源类型转换为目标 类型,不需要显式的类型转换。例如,将 int 转换为 long 、将 float 转换为 double 等。
- 强制类型转换(显式转换):当目标类型的范围小于源类型时,需要使用强制类型转换将源类型 转换为目标类型。这可能导致数据丢失或溢出。例如,将 long 转换为 int 、将 double 转换为 int 等。语法为:目标类型 变量名 = (目标类型) 源类型
什么是自动拆箱/封箱
- 装箱:将基本类型用它们对应的包装类起来;
- 拆箱:将包装类型转换为基本数据类型;
Integer i = 10; //装箱
int n = i; //拆箱
基本类型-引用类型-String类型
int i = 10;
Integer integer = new Integer(i);
String str = integer.toString();
System.out.println(str);
类、对象、实例化
类是对象的模板,定义对象的属性和方法
对像是类的实例化,具有具体的属性和方法
实例化是指根据类创建对象的过程,使用 new
关键字完成。
类名 对象名 = new 类名();
//定义类
public class Dog {String name;int age;public void bark() {System.out.println(name + " 在汪汪叫!");}
}// 在主方法中实例化对象
public class Main {public static void main(String[] args) {Dog myDog = new Dog(); // 实例化对象myDog.name = "旺财";myDog.age = 3;myDog.bark(); // 输出:旺财 在汪汪叫!}
}
- 实例化后,Java 在堆内存分配空间,并调用构造方法初始化对象。
- 实例化的对象可以调用类的方法和访问成员变量。
Integer相比int有什么优点?
int是Java中的原始数据类型,而Integer是int的包装类。
Integer和 int 的区别:
- 基本类型和引用类型:首先,int是一种基本数据类型,而Integer是一种引用类型。基本数据类型 是Java中最基本的数据类型,它们是预定义的,不需要实例化就可以使用。而引用类型则需要通 过实例化对象来使用。这意味着,使用int来存储一个整数时,不需要任何额外的内存分配,而使 用Integer时,必须为对象分配内存。在性能方面,基本数据类型的操作通常比相应的引用类型 快。
- 自动装箱和拆箱:其次,Integer作为int的包装类,它可以实现自动装箱和拆箱。自动装箱是指将 基本类型转化为相应的包装类类型,而自动拆箱则是将包装类类型转化为相应的基本类型。这使 得Java程序员更加方便地进行数据类型转换。例如,当我们需要将int类型的值赋给Integer变量 时,Java可以自动地将int类型转换为Integer类型。同样地,当我们需要将Integer类型的值赋给int 变量时,Java可以自动地将Integer类型转换为int类型。
- 空指针异常:另外,int变量可以直接赋值为0,而Integer变量必须通过实例化对象来赋值。如果 对一个未经初始化的Integer变量进行操作,就会出现空指针异常。这是因为它被赋予了null值, 而null值是无法进行自动拆箱的
break、continue、return 的区别及作用?
break 跳出整个循环,不再执行循环(结束当前的循环体)
continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)
面向对象基础
面向对象与过程
- 面向过程:把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
- 面向对象:会先抽象出对象,然后用对象执行方法的方式解决问题
三大特性
- 继承:一个类继承另一个类的属性和方法,实现代码复用
**关于继承如下 3 点请记住:**
1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法 子类是无法访问,**只是拥有**。
2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3. 子类可以用自己的方式实现父类的方法。
- 多态:同一个行为具有多个不同表现形式或形态的能力
*类型的多态**,变量的编译时类型和运行时类型不一致,如子类对象指向父类引用;
**方法的多态**,子类重写父类方法,父类进行调用表现出子类的行为模式.
- 封装:封装把⼀个对象的属性和方法封装,隐藏内部,仅对外暴漏接口。
重载(overload)和重写(override)的区别?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
访问修饰符 public、private、protected、以及不写(默认)时的区别?
Java 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。可以修饰在类、接口、变量、方法。
private : 在同一类内可见。可以修饰变量、方法。注意:不能修饰类(外部类)
public : 对所有类可见。可以修饰类、接口、变量、方法
protected : 对同一包内的类和所有子类可见。可以修饰变量、方法。注意:不能修饰类(外部类)。
this 关键字有什么作用?
this 是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。
this 的用法在 Java 中大体可以分为 3 种:
- 普通的直接引用,this 相当于是指向当前对象本身
- 形参与成员变量名字重名,用 this 来区分:
public Person(String name,int age){this.name=name;this.age=age;
}
- 引用本类的构造函数
抽象类(abstract class)和接口(interface)有什么区别?
抽象类(abstract class
)和 接口(interface
)都是用于定义类的抽象行为,且都不能直接实例化。不能创建一个抽象类的对象。抽象类 主要是用来定义一个类族的共同行为和属性。
- 接⼝的⽅法默认是 public ,所有⽅法在接⼝中不能有实现(Java 8 开始接⼝⽅法可以有默认实现),⽽抽象类可以有⾮抽象的⽅法。
- 接⼝中除了 static 、 final 变量,不能有其他变量,⽽抽象类中则不⼀定。
- ⼀个类可以实现多个接⼝,但只能实现⼀个抽象类。接⼝⾃⼰本身可以通过 extends 关键字扩展多个接⼝。
- 接⼝⽅法默认修饰符是 public ,抽象⽅法可以有 public 、 protected 和 default 这些修饰符(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!)。
- 从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。
✅ 用抽象类(Abstract Class):
- 需要定义共同的属性和行为,并且希望部分方法有默认实现。
- 需要构造方法来初始化变量。
- 逻辑上属于 “是一种”(
is-a
)关系,比如Dog extends Animal
。
✅ 用接口(Interface):
- 只需要定义行为,不关心内部实现,适用于不同类的行为标准化。
- 需要多继承,因为 Java 只支持单继承,但允许实现多个接口。
- 逻辑上属于 “能做什么”(
can-do
)关系,比如Bird implements Flyable, Swimmable
。
成员变量与局部变量的区别有哪些?
- 从语法形式上看:成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;成员变量可以被 public , private , static 等修饰符所修饰,⽽局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
- 从变量在内存中的存储⽅式来看:如果成员变量是使⽤ static 修饰的,那么这个成员变量是属于类的,如果没有使⽤ static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆内存对象的引⽤或者是指向常量池中的地址。
- 从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建⽽存在,⽽局部变量随着⽅法的调⽤⽽⾃动消失。
- 成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值。
静态变量和实例变量的区别?静态方法、实例方法呢?
静态变量和实例变量的区别?
静态变量: 是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个副本。
实例变量: 必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。
静态⽅法和实例⽅法有何不同?
静态方法:static 修饰的方法,也被称为类方法。在外部调⽤静态⽅法时,可以使⽤"类名.⽅法名"的⽅式,也可以使⽤"对象名.⽅法名"的⽅式。静态方法里不能访问类的非静态成员变量和方法。
实例⽅法:依存于类的实例,需要使用"对象名.⽅法名"的⽅式调用;可以访问类的所有成员变量和方法。
final 关键字有什么作用?
final 表示不可变的意思,可用于修饰类、属性和方法:
- 被 final 修饰的类不可以被继承
- 被 final 修饰的方法不可以被重写
- 被 final 修饰的变量不可变,被 final 修饰的变量必须被显式第指定初始值,还得注意的是,这里的不可变指的是变量的引用不可变,不是引用指向的内容的不可变。
final StringBuilder sb = new StringBuilder("abc");
sb.append("d");
System.out.println(sb); //abcd
final、finally、finalize 的区别?
- final 用于修饰变量、方法和类:final 修饰的类不可被继承;修饰的方法不可被重写;修饰的变量不可变。
- finally 作为异常处理的一部分,它只能在 try/catch 语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下,System.exit (0) 可以阻断 finally 执行。
- finalize 是在 java.lang.Object 里定义的方法,也就是说每一个对象都有这么个方法,这个方法在 gc 启动,该对象被回收的时候被调用。
一个对象的 finalize 方法只会被调用一次,finalize 被调用不一定会立即回收该对象,所以有可能调用 finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用 finalize 了,进而产生问题,因此不推荐使用 finalize 方法。
==和 equals 的区别?
==
: 它的作⽤是判断两个对象的地址是不是相等。即,判断两个对象是不是同⼀个对象(基本数据类型 ==
比较的是值,引⽤数据类型 ==
比较的是内存地址)。
equals()
: equals()
默认 也是比较对象的引用,但许多类(如 String
、Integer
)已经重写了 equals()
,使其比较值是否相同。
hashCode 与 equals
什么是 HashCode?
hashCode() 的作⽤是获取哈希码,也称为散列码;它实际上是返回⼀个 int 整数,定义在 Object 类中, 是一个本地⽅法,这个⽅法通常⽤来将对象的内存地址转换为整数之后返回。
为什么重写equals 时必须重写 hashCode ⽅法?
- 如果两个对象相等,则 hashcode ⼀定也是相同的。
- 两个对象有相同的 hashcode 值,它们也不⼀定是相等的 。
没重写之前equals是比较内存地址的,而hashcode就是返还对象的内存地址。但重写equals是为了比较其值是否相等,而不是内存地址,因此hashcode的返还条件需要更改。
为什么两个对象有相同的 hashcode 值,它们也不⼀定是相等的?
hashCode() 所使⽤的散列算法也许刚好会让多个对象传回相同的散列值。越糟糕的散列算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode )。
Java 是值传递,还是引用传递?
Java 语言是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。
JVM 的内存分为堆和栈,其中栈中存储了基本数据类型和引用数据类型实例的地址,也就是对象地址。
而对象所占的空间是在堆中开辟的,所以传递的时候可以理解为把变量存储的对象地址给传递过去,因此引用类型也是值传递。
深拷贝和浅拷贝?
- 浅拷贝:仅拷贝对象的成员变量的值,而对于引用类型变量指向的堆中的对象不会拷贝。连哥哥对象引用的是同一个引用对象
- 深拷贝:完全拷贝一个对象,拷贝被拷贝对象的成员变量的值,堆中的对象也会拷贝一份。
浅拷贝如何实现呢?
Object 类提供的 clone()方法可以非常简单地实现对象的浅拷贝。
深拷贝如何实现呢?
- 重写克隆方法:重写克隆方法,引用类型变量单独克隆,这里可能会涉及多层递归。
- 序列化:可以先讲原对象序列化,再反序列化成拷贝对象。
Java 创建对象有哪几种方式?
- new 创建新对象
- 通过反射机制
- 采用 clone 机制
- 通过序列化机制
常用类
1) String
String 不是 Java 基本数据类型,是一种特殊的引用类型,不可以被继承(用final修饰)。
String 和 StringBuilder、StringBuffer 的区别?
- String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的生成。
- StringBuffer:跟 String 类似,但是值可以被修改,使用 synchronized 来保证线程安全。
- StringBuilder:StringBuffer 的非线程安全版本,性能上更高一些。
String str1 = new String(“abc”)和 String str2 = “abc” 和 区别?
当字符创常量池没有 “abc”,此时会创建如下两个对象:
- 一个是字符串字面量 “abc” 所对应的、字符串常量池中的实例
- 另一个是通过 new String() 创建并初始化的,内容与"abc"相同的实例,在堆中。
String 不是不可变类吗?字符串拼接是如何实现的?
String 的确是不可变的,“+”的拼接操作,其实是会生成新的对象。
2) Integer
Integer a= 127,Integer b = 127;Integer c= 128,Integer d = 128;相等吗?
答案是 a 和 b 相等,c 和 d 不相等。
- 对于基本数据类型==比较的值
- 对于引用数据类型==比较的是地址
自动装箱的时候会去缓存池里取 Integer 对象,没有取到才会创建新的对象。
如果整型字面量的值在-128 到 127 之间,那么自动装箱时不会 new 新的 Integer 对象,而是直接引用缓存池中的 Integer 对象,超过范围结果是 false
什么是 Integer 缓存?
因为根据实践发现大部分的数据操作都集中在值比较小的范围,因此 Integer 搞了个缓存池,默认范围是 -128 到 127,可以根据通过设置JVM-XX:AutoBoxCacheMax=来修改缓存的最大值,最小值改不了。
String 怎么转成 Integer 的?原理?
String 转成 Integer,主要有两个方法:
Integer.parseInt(String s)
Integer.valueOf(String s)
不管哪一种,最终还是会调用 Integer 类内中的parseInt(String s, int radix)方法。
3) Object
对象比较:
- public native int hashCode() :native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的 HashMap。
- public boolean equals(Object obj):用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写用户比较字符串的值是否相等。
对象拷贝:
- protected native Object clone() throws CloneNotSupportedException:naitive 方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为 true,x.clone().getClass() == x.getClass() 为 true。Object 本身没有实现 Cloneable 接口,所以不重写 clone 方法并且进行调用的话会发生 CloneNotSupportedException 异常。
对象转字符串:
- public String toString():返回类的名字@实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
多线程调度:
- public final native void notify():native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
- public final native void notifyAll():native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
- public final native void wait(long timeout) throws InterruptedException:native 方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 。timeout 是等待时间。
- public final void wait(long timeout, int nanos) throws InterruptedException:多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。所以超时的时间还需要加上 nanos 毫秒。
- public final void wait() throws InterruptedException:跟之前的 2 个 wait 方法一样,只不过该方法一直等待,没有超时时间这个概念
反射:
- public final native Class<?> getClass():native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
垃圾回收:
- protected void finalize() throws Throwable :通知垃圾收集器回收对象。
异常处理
Throwable
是 Java 语言中所有错误或异常的基类。Throwable 又分为Error
和Exception
,其中 Error 是系统内部错误,比如虚拟机异常,是程序无法处理的。Exception
是程序问题导致的异常。
异常的处理方式
- try catch 捕获异常
在 catch 语句块中补货发生的异常,并进行处理。
try {//包含可能会出现异常的代码以及声明异常的方法}catch(Exception e) {//捕获异常并进行处理}finally { }//可选,必执行的代码
}
三道经典异常处理代码题
题目一
public class TryDemo {public static void main(String[] args) {System.out.println(test());}public static int test() {try {return 1;} catch (Exception e) {return 2;} finally {System.out.print("3");}}
}
执行结果:31。
try、catch。finally 的基础用法,在 return 前会先执行 finally 语句块,所以是先输出 finally 里的 3,再输出 return 的 1。
题目 2
public class TryDemo {public static void main(String[] args) {System.out.println(test1());}public static int test1() {try {return 2;} finally {return 3;}}
}
执行结果:3。
try 返回前先执行 finally,结果 finally 里不按套路出牌,直接 return 了,自然也就走不到 try 里面的 return 了。
finally 里面使用 return 仅存在于面试题中,实际开发这么写要挨吊的。
题目 3
ublic class TryDemo {public static void main(String[] args) {System.out.println(test1());}public static int test1() {int i = 0;try {i = 2;return i;} finally {i = 3;}}
}
执行结果:2。
大家可能会以为结果应该是 3,因为在 return 前会执行 finally,而 i 在 finally 中被修改为 3 了,那最终返回 i 不是应该为 3 吗?
但其实,在执行 finally 之前,JVM 会先将 i 的结果暂存起来,然后 finally 执行完毕后,会返回之前暂存的结果,而不是返回 i,所以即使 i 已经被修改为 3,最终返回的还是之前暂存起来的结果 2。
I/O
流按照不同的特点,有很多种划分方式。
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流
IO 流用到了什么设计模式?
其实,Java 的 IO 流体系还用到了一个设计模式——装饰器模式。
既然有了字节流,为什么还要有字符流?
其实字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还比较耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。
所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
序列化
什么是序列化?什么是反序列化?
什么是序列化,序列化就是把 Java 对象转为二进制流,方便存储和传输。
所以反序列化就是把二进制流恢复成对象。
泛型
什么是泛型?
泛型是 Java 编程语言中的一个重要特性,它允许类、接口和方法在定义时使用一个或多个类型参 数,这些类型参数在使用时可以被指定为具体的类型。
- 适用于多种数据类型执行相同的代码
private static int add(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } private static float add(float a, float b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } private static double add(double a, double b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; }
- 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
List<String> list = new ArrayList<String>();
// list中只能放String, 不能放其它类型的元素
注解
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。
Java 注解本质上是一个标记,可以理解成生活中的一个人的一些小装扮,比如戴什么什么帽子,戴什么眼镜。
注解可以标记在类上、方法上、属性上等,标记自身也可以设置一些值,比如帽子颜色是绿色。有了标记之后,我们就可以在编译或者运行阶段去识别这些标记,然后做一些事情,这就是注解的用处。
例如我们常见的 AOP,使用注解作为切点就是运行期注解的应用;比如 lombok,就是注解在编译期的运行。
注解生命周期有三大类,分别是:
- RetentionPolicy.SOURCE:给编译器用的,不会写入 class 文件
- RetentionPolicy.CLASS:会写入 class 文件,在类加载阶段丢弃,也就是运行的时候就没这个信息了
- RetentionPolicy.RUNTIME:会写入 class 文件,永久保存,可以通过反射获取注解信息
- 所以我上文写的是解析的时候,没写具体是解析啥,因为不同的生命周期的解析动作是不同的。
反射
什么是反射?
我们通常都是利用new方式来创建对象实例,这可以说就是一种“正射”,这种方式在编译时候就确定了类型信息。
而如果,我们想在时候动态地获取类信息、创建类实例、调用类方法这时候就要用到反射。
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
反射在你平时写代码或者框架中的应用场景有哪些?
加载数据库驱动
我们的项目底层数据库有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个 时候反射就有用了,假设 com.mikechen.java.myqlConnection, com.mikechen.java.oracleConnection这两个类我们要用。 这时候我们在使用 JDBC 连接数据库时使用 Class.forName()通过反射加载数据库的驱动程序,如果 是mysql则传入mysql的驱动类,而如果是oracle则传入的参数就变成另一个了。
配置文件加载
Spring 框架的 IOC(动态加载管理 Bean),Spring通过配置文件配置各种各样的bean,你需要用到 哪些bean就配哪些,spring容器就会根据你的需求去动态加载,你的程序就能健壮地运行。
反射的原理?
我们都知道 Java 程序的执行分为编译和运行两步,编译之后会生成字节码(.class)文件,JVM 进行类加载的时候,会加载字节码文件,将类型相关的所有信息加载进方法区,反射就是去获取这些信息,然后进行各种操作。
JDK1.8新特性
- 接口默认方法:Java 8 允许我们给接口添加一个非抽象的方法实现,只需要使用 default 关键字修饰即可
- Lambda 表达式和函数式接口:Lambda 表达式本质上是一段匿名内部类,也可以是一段可以传递的代码。Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中),使用 Lambda 表达式使代码更加简洁,但是也不要滥用,否则会有可读性等问题,《Effective Java》作者 Josh Bloch 建议使用 Lambda 表达式最好不要超过 3 行。
- Stream API:用函数式编程方式在集合类上进行复杂操作的工具,配合 Lambda 表达式可以方便的对集合进行处理。
- Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。