Java 是一种混合性质的编程语言,它既具有解释性,也具有编译性的特点。这种混合性质是通过 Java 编译器和 Java 虚拟机(JVM)实现的。
在白皮书中,Java被冠以以下特性:
白皮书:https://www.oracle.com/java/technologies/language-environment.html
特性解释:https://horstmann.com/corejava/java-an-overview/7Gosling.pdf
JDK(Java Development Kit,Java开发工具包)
JRE(Java Runtime Environment,Java运行时环境)
AOT(Ahead-of-Time Compilation)编译技术是一种将Java源代码或字节码提前编译成本地机器码的技术。与传统的JIT(Just-In-Time)编译方式不同,AOT编译技术可以在应用程序部署或运行之前,将Java代码转换为目标计算机体系结构和操作系统的本地机器码,从而直接由处理器执行,避免了JVM的解释和翻译过程,避免了JIT预热等各方面的开销,比如Oracle JDK 9就引入了实验性的AOT特性,并且增加了新的jaotc工具。
AOT编译技术通过将Java代码预先编译成本地机器码,可以显著提高应用程序的启动时间和性能。开发者可以使用工具如GraalVM来进行AOT编译,将Java代码编译成可执行文件,然后直接在目标计算机上运行,无需先启动JVM。
AOT编译技术的主要优势在于提高应用程序的性能和响应速度。通过绕过JVM的解释和翻译过程,直接执行本地机器码,可以减少性能开销和启动延迟,为用户提供更快速、更流畅的应用体验。
尽管AOT编译技术可以带来显著的性能提升,但并不是所有类型的Java应用程序都适合使用。一些特定的Java特性和库可能不支持AOT编译,因此在使用AOT编译时需要进行仔细的测试和验证。
通过如javac等(前端)编译器,由.java代码源文件生成.class字节码文件
字节码到机器码 把经常运行的代码作为"热点代码"编译成与本地平台相关的机器码。HotSpot虚拟机内置两种JIT编译模式:C1、C2。
C1的编译速度比C2快,C2会做一些激进的优化,编译耗时较长
HotSpot虚拟机有三种运行模式:混合模式、解释模式、编译模式。
try catch finally,不管是否发生异常finally代码块都会被执行,哪怕try catch中有return,除非是System.exit(0),一般finally块中可以存放回收资源的代码,比如释放数据库连接,释放IO对象,清空集合,设置大对象为Null,以减少对大对象的强引用,从而提升垃圾回收的效率。
垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。那么finalize()究竟是做什么的呢?它最主要的用途是回收特殊渠道申请的内存。Java 程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI(Java Native Interface)调用non-Java程序(C 或C++),finalize()的工作就是回收这部分的内存。
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持4种不同的访问权限
| 访问级别 | 访问控制修饰符 | 同类 | 同包 | 子类 | 不同的包 |
|---|---|---|---|---|---|
| 公开 | public | √ | √ | √ | √ |
| 受保护 | protected | √ | √ | √ | - |
| 默认 | 没有访问控制修饰符 | √ | √ | - | - |
| 私有 | private | √ | - | - | - |
String底层使用一个字符数组来维护的。成员变量可以知道String类的值是final类型的,不能被改变的,所以只要一个值改变就会生成一个新的String类型对象,存储String数据也不一定从数组的第0个元素开始的,而是从offset所指的元素开始。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。
使用 “+” 进行字符串拼接会产生大量的临时对象的问题在 JDK9 中得到了解决。在 JDK9 当中,字符串相加 “+” 改为了用动态方法 makeConcatWithConstants() 来实现,而不是大量的 StringBuilder 了.
可以转,得处理异常Integer.parseInt(s) 主要为NumberFormatException:
parseInt()返回的是基本类型int,而valueOf()返回的是包装类Integer。Integer是可以使用对象方法的,而int类型就不能和Object类型进行互相转换。
运行时常量池(Runtime Constant Pool)是虚拟机规范中是方法区的一部分,在加载类和结构到虚拟机后,就会创建对应的运行时常量池;而字符串常量池是这个过程中常量字符串的存放位置。所以从这个角度,字符串常量池属于虚拟机规范中的方法区,它是一个逻辑上的概念;而堆区,永久代以及元空间是实际的存放位置。
不同的虚拟机对虚拟机的规范(比如方法区)是不一样的,只有 HotSpot 才有永久代的概念。
HotSpot也是发展的,由于一些问题在新窗口打开的存在,HotSpot考虑逐渐去永久代,对于不同版本的JDK,实际的存储位置是有差异的,具体看如下表格:
| JDK版本 | 是否有永久代,字符串常量池放在哪里? | 方法区逻辑上规范,由哪些实际的部分实现的? |
|---|---|---|
| jdk1.6及之前 | 有永久代,运行时常量池(包括字符串常量池),静态变量存放在永久代上 | 这个时期方法区在Hotspot中是由永久代来实现的,以至于这个时期说方法区就是指永久代 |
| jdk1.7 | 有永久代,但已经逐步“去永久代",字符串常量池、静态变量移除,保存在堆中 | 这个时期方法区在Hotspot中由永久代(类型信息、字段、方法、常量)和堆(字符串常量池、静态变量)共同实现 |
| jdk1.8及之后 | 取消永久代,类型信息、字段、方法、但字符常量保存在本地内存的元空间,串常量池、静态变量仍在堆中 | 这个时期方法区在Hotspot中由本地内存的元空间(类型信息、字段、方法、常量)和堆(字符串常量池、静态变量)共同实现 |
在比较基本数据类型时是比较它们的值,而在比较对象时是比较对象的引用地址。
equals本身也是调用了==,只不过String重写了equals方法从而做到比较字符串内容。
示例一:int a=59; Integer b=new Integer(59);
ab为true,因为比较的是基本类型,Integer自动拆箱。
示例二:Integer a=59; Integer b=59;
a==b为true,在数值在 [-128, 127]常量池范围内时,相同的值会被缓存并共享相同的引用。
java.lang.Object,所有类的根(父类),所有对象都实现了Object的方法
protected方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出CloneNotSupportedException 异常,深拷贝也需要实现 Cloneable,同时其成员变量为引用类型的也需要实现 Cloneable,然后重写 clone 方法。
public class CloneExample implements Cloneable {
private int a;
private int b;
@Override
protected CloneExample clone() throws CloneNotSupportedException {
return super.clone();
}
}
clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
该方法和垃圾回收有关,判断对象是否可以被回收的最后一步就是判断是否重写了该方法,执行后它并不会马上回收并释放资源。因为垃圾回收的运行时机是由垃圾回收器自身的策略和算法决定的,而不是我们所控制的。
当对象的 finalize 方法被调用时,它被标记为"可回收",但是具体的垃圾回收时机是不确定的。
返回当前对象的哈希码,用于哈希查找,重写了 equals 方法一般都要重写 hashCode 方法,这个方法在一些具有哈希功能的 Collection 中用到。
obj1.equals(obj2)==true可以推出 obj1.hashCode()==obj2.hashCode() ,但是反过来不一定满足。不过为了提高效率,应该尽量使上面两个条件接近等价。
配合synchronized使用,wait 会使当前线程释放对象的锁,并进入等待状态,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait() 方法一直等待,直到被唤醒(notify,notifyAll)或被中断(interrupt,中断会抛出中断异常),wait(long timeout)是超时间隔。
配合 synchronized 使用,该方法唤醒在该对象上等待队列中的某个线程(同步队列中的线程是给抢占 CPU 的线程,等待队列中的线程指的是等待唤醒的线程)。
配合 synchronized 使用,该方法唤醒在该对象上等待队列中的所有线程。
返回对象运行时类,toString()返回字符串表示。
在 JDK 1.6 和 JDK 1.7 中,java.util.Random 类的随机数生成算法基于线性同余生成器(Linear Congruential Generator,LCG)。这种算法使用一个种子值来生成伪随机数序列。每次调用 next() 方法时,都会生成下一个伪随机数;
从 JDK 1.8 开始,Random 类的实现进行了优化,采用了 Marsaglia’s xorshift 算法,xorshift 算法结合了当前线程的随机数种子和几个常量值来生成新的随机数。这个算法的实现旨在提供更高效的随机数生成,并改进了性能和随机性。
面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它具有三大特征:
多态(Polymorphism)是面向对象编程中的一个重要概念,它允许不同类型的对象对同一个消息做出不同的响应。简单来说,多态指的是对象能够根据具体的类型来执行相应的方法或操作。
具体来说,多态有两个要点:
方法的重载(Overloading)和重写(Overriding)虽然都和多态有关,但它们并不完全等同于多态。
多态的原理
| 区别 | 重载 | 重写 |
|---|---|---|
| 概念 | 方法名称相同,参数类型和个数不同 | 方法名称,参数类型和个数以及返回值类型都相同 |
| 权限 | 没有权限限制 | 子类覆写的方法不能拥有比父类更严格的权限控制 |
| 范围 | 发生在一个类中 | 发生在有继承关系的类中 |
为了满足里式替换原则,重写有以下两个限制
| 区别 | 抽象类 | 接口 |
|---|---|---|
| 定义 | abstract class 抽象类名称{} | interface 接口名称{} |
| 组成 | 成员变量,方法(普通方法,静态方法),构造块,初始化块,内部类 | 全局常量,普通方法,抽象方法,static方法 |
| 权限 | 各种权限 | public |
| 子类 | 子类通过extends继承一个抽象类 | 子类通过implements实现一个或多个接口 |
| 关系 | 抽象类可以实现若干个接口 | 接口不允许继承抽象类,但允许继承多个父接口 |
| 使用 | 抽象类或接口必须定义子类 | 当抽象类和接口都可以使用的情况下优先要考虑接口,因为接口可以避免子类的单继承 |
接口和抽象类很像,它们都具有如下特征。
下面具体分析二者的差别:
接口作为系统与外界交互的窗口,接口体现的是一种规范。 对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式来提供);对于接口的调用者而言,接口规定了调用者可以使用哪些服务,以及如何调用这些服务(就是如何来调用方法)。当在一个程序中使用接口时,接口是多个模块间的耦合标准:当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。
从某种程度上来看,接口类似于整个系统的“ 总纲”,它制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口被改变,对整个系统甚至其他系统的影响将是辐射式的,导致系统中大部分类都需要改写。
抽象类则不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。 抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(那些已经提供实现的方法),但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可能有几种不同方式。
除此之外,接口和抽象类在用法上也存在如下差别。
抽象类和抽象方法是面向对象编程中的概念,它们与普通类和方法有一些区别:
算法的四大特性是指:有穷性、确定性、输入、输出和可行性。
基本类型:byte short int long float double boolean char
区别:
List<Integer> list = new ArrayList<>();)在Java中,有八种基本的数据类型,它们可以分为四个类别:整数类型、浮点类型和字符类型、布尔类型。
大部分包装类型都实现了缓存池,在自动装箱时,如果基本类型在缓存池内,则不会创建新的对象,而是从缓存池中直接获取,包装类型比较要用equals方法。
自动装箱(Autoboxing):
自动拆箱(Unboxing):
底层原理:装箱使用了 Integer.valueOf()、拆箱使用了 Integer.intValue()。
new Integer(123) 与 Integer.valueOf(123) 的区别在于
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
基本类型对应的缓冲池:
为了解决ClassCastException的问题,在进行对象的向下转型时,永远都可能存在安全隐患,而Java希望通过泛型慢慢解决。
避免出现ClassCastException的最好做法是可以直接回避掉对象的强制转换。
Point<Integer>point=new Point<>()”后面的数据类型可以省略在“”这个通配符的基础上实际还提供有两类小的通配符:
Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。
List<String>[] list11 = new ArrayList<String>[10]; //编译错误,非法创建
List<String>[] list12 = new ArrayList<?>[10]; //编译错误,需要强转类型
List<String>[] list13 = (List<String>[]) new ArrayList<?>[10]; //OK,但是会有警告
List<?>[] list14 = new ArrayList<String>[10]; //编译错误,非法创建
List<?>[] list15 = new ArrayList<?>[10]; //OK
List<String>[] list6 = new ArrayList[10]; //OK,但是会有警告
public class GenericsDemo30{
public static void main(String args[]){
Integer i[] = fun1(1,2,3,4,5,6) ; // 返回泛型数组
fun2(i) ;
}
public static <T> T[] fun1(T...arg){ // 接收可变参数
return arg ; // 返回泛型数组
}
public static <T> void fun2(T param[]){ // 输出
System.out.print("接收泛型数组:") ;
for(T t:param){
System.out.print(t + "、") ;
}
}
}
public ArrayWithTypeToken(Class<T> type, int size) {
array = (T[]) Array.newInstance(type, size);
}
正确示例:
我们在使用到泛型数组的场景下应该尽量使用列表集合替换,此外也可以通过使用 java.lang.reflect.Array.newInstance(Class<T> componentType, int length) 方法来创建一个具有指定类型和维度的数组
public class ArrayWithTypeToken<T> {
private T[] array;
public ArrayWithTypeToken(Class<T> type, int size) {
array = (T[]) Array.newInstance(type, size);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
public T[] create() {
return array;
}
}
//...
ArrayWithTypeToken<Integer> arrayToken = new ArrayWithTypeToken<Integer>(Integer.class, 100);
Integer[] array = arrayToken.create();
类型擦除会造成多态的冲突,而JVM解决方法就是桥接方法
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
class DateInter extends Pair<Date> {
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
}
// 类型擦除后,父类的的泛型类型全部变为了原始类型Object,父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。
本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。
JVM采用了一个特殊的方法,来完成这项功能,那就是桥接方法。
子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
在普通的类继承中也是普遍存在的重写,这就是协变。
根据代码块的位置和定义关键字的不同分为:普通代码块,构造块,静态块,同步代码块
静态代码块会优先于构造块执行,无论实例化多少个新对象,静态块只执行一次,主要目的是为了类中静态属性的初始化
静态代码块必须考虑在主类中定义的形式;静态代码块优先于主方法执行。
当程序出现异常时,Java会抛出一个异常对象。Java中的异常可以分为两类Error与Exception,两者都继承自java.lang.Throwable:
在Java中,异常处理通常包括try-catch语句和throw语句。try-catch语句可以捕获异常并进行处理,而throw语句可以手动抛出异常。
可查异常(编译器要求必须处置的异常):
不可查异常(编译器不要求强制处置的异常),包括运行时异常(RuntimeException与其子类)和错误(Error):
提到JVM处理异常的机制,就需要提及Exception Table,以下称为异常表。
//javap -c Main
public static void simpleTryCatch();
Code:
0: invokestatic #3 // Method testNPE:()V
3: goto 11
6: astore_0
7: aload_0
8: invokevirtual #5 // Method java/lang/Exception.printStackTrace:()V
11: return
Exception table:
from to target type
0 3 6 Class java/lang/Exception
异常表中包含了一个或多个异常处理者(Exception Handler)的信息,这些信息包含如下
当一个异常发生时
ClassNotFoundException:
NoClassDefFoundError:
try-with-resources 是 Java 7 引入的一种语法,用于自动管理资源,如文件、网络连接等,确保这些资源在使用后能够被正确关闭。这个语法简化了资源的管理,并减少了资源泄露的风险。
try (ResourceType resource = new ResourceType()) {
// 使用资源
} catch (Exception e) {
// 处理异常
}
在 Java 7 及其更高版本中,可以在一个 catch 块中捕获多个异常,这种特性称为“多重捕获”(Multiple Catch)。这种语法简化了异常处理代码,特别是在多个 catch 块中执行相同的处理逻辑时。
语法:在 catch 语句中使用管道符 | 来分隔多个异常类型。所有列出的异常类型都将被捕获,并且可以在一个 catch 块中进行统一处理。
注意:不能在同一个 catch 块中捕获子类和父类异常。例如,不能同时捕获 IOException 和 Exception,因为 IOException 是 Exception 的子类。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
java.lang.relfect。Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java 语言的反射机制。
方法有三种:
(1) 使用 Class 类的forName静态方法:
public static Class<?> forName(String className)
比如在 JDBC 开发中常用此方法加载数据库驱动:
Class.forName(driver);
(2)直接获取某一个对象的 class,比如:
Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;
(3)调用某个对象的 getClass() 方法,比如:
StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();
一般地,我们用 instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class 对象的 isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法:
public native boolean isInstance(Object obj);
通过反射来生成对象主要有两种方式。
使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> c = String.class;
Object str = c.newInstance();
先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
获取某个Class对象的方法集合,主要有以下几个方法:
getDeclaredMethods 方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
public Method[] getDeclaredMethods() throws SecurityException
getMethods 方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。
public Method[] getMethods() throws SecurityException
getMethod 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
public Method getMethod(String name, Class<?>... parameterTypes)
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:
public T newInstance(Object ... initargs)
此方法可以根据传入的参数来调用对应的Constructor创建对象实例。
主要是这几个方法,在此不再赘述:
getFileds:访问公有的成员变量
getDeclaredFields:所有已声明的成员变量,但不能得到其父类的成员变量
getFileds 和 getDeclaredFields 方法用法同上(参照 Method)。
当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。invoke 方法的原型为:
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference。下面我们看一看利用反射创建数组的例子:
public static void testArray() throws ClassNotFoundException {
Class<?> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls,25);
//往数组里添加内容
Array.set(array,0,"hello");
Array.set(array,1,"Java");
Array.set(array,2,"fuck");
Array.set(array,3,"Scala");
Array.set(array,4,"Clojure");
//获取某一项的内容
System.out.println(Array.get(array,3));
}
由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),
logger.info("根据类名: \t" + User.class);、logger.info("根据对象: \t" + new User().getClass());、logger.info("根据全限定类名:\t" + Class.forName("com.test.User"));logger.info("获取全限定类名:\t" + userClass.getName());、logger.info("获取类名:\t" + userClass.getSimpleName());、logger.info("实例化:\t" + userClass.newInstance());定义在类内部的静态类,就是静态内部类。
public class Out {
private static int a;
private int b;
public static class Inner {
public void print() {
System.out.println(a);
} } }
Out.Inner inner =new Out.Inner(); inner.print();定义在类内部的非静态类,就是成员内部类。成员内部类不能定义静态方法和变量(final 修饰的除外)。这是因为成员内部类是非静态的,类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的。
定义在方法中的类,就是局部类。如果一个类只在某个方法中使用,则可以考虑使用局部类。
匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class 关键字,这是因为匿名内部类是直接使用new 来生成一个对象的引用。
序列化(Serialization)和反序列化(Deserialization)是将对象转换为字节流以便存储或传输,并且在需要时将字节流重新转换为对象的过程。
序列化:是将对象的状态信息转换为可以存储或传输的形式的过程(字节流)。
对于不想进行序列化的变量(敏感信息、临时变量等),使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复(会被设为默认值)。transient 只能修饰变量,不能修饰类和方法。
transient 关键字的应用场景包括:
举例来说,如果一个用户类包含了密码字段,为了安全起见可以将密码字段标记为 transient,这样在序列化过程中不会被持久化到文件中,提高了安全性。
Java中的序列化是指将对象转换为字节流的过程,以便可以将其保存到文件、数据库中,或者通过网络传输。序列化可以让对象的状态在不同的 JVM(Java虚拟机)之间进行传输和共享,同时也可以实现对象的持久化存储。Java序列化的作用包括:
注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要的作用有以下四方面:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethodAnnotation {
public String title() default "";
public String description() default "";
}
boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。注意:此方法会忽略注解对应的注解容器<T extends Annotation> T getAnnotation(Class<T> annotationClass):返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。Annotation[] getAnnotations():返回该程序元素上存在的所有注解,若没有注解,返回长度为0的数组。<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass):返回该程序元素上存在的、指定类型的注解数组。没有注解对应类型的注解时,返回长度为0的数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。getAnnotationsByType方法与 getAnnotation的区别在于,getAnnotationsByType会检测注解对应的重复注解容器。若程序元素为类,当前类上找不到注解,且该注解为可继承的,则会去父类上检测对应的注解。<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass):返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释。如果没有注释直接存在于此元素上,则返回null<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass):返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解及注解对应的重复注解容器。与此接口中的其他方法不同,该方法将忽略继承的注解。如果没有注释直接存在于此元素上,则返回长度为零的一个数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。这些新特性使 Java 语言更加现代化、功能丰富,并且更适合于面向对象编程和函数式编程。它们提高了代码的可读性、可维护性和性能。
JDK动态代理(JDK Dynamic Proxy)是Java语言提供的一种动态代理技术,它允许在运行时动态地创建代理对象并将方法调用转发给真实对象。JDK动态代理主要使用了Java的反射机制来实现。
在使用JDK动态代理之前,需要定义一个接口,该接口包含代理对象需要实现的方法。然后,通过Java提供的Proxy类和InvocationHandler接口,可以动态地生成一个代理类,该代理类实现了指定接口,并且将方法调用转发给一个InvocationHandler对象。
以下是使用JDK动态代理的基本步骤:
代理类重写invoke方法进行增强:
public class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method: " + method.getName());
return result;
}
使用 Proxy 类的 newProxyInstance() 方法创建代理对象:
public class Main {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
LogInvocationHandler handler = new LogInvocationHandler(userService);
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
handler);
proxy.save("Alice");
}
}
SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦。
在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式为:key=value1,value2,value3
// 从所有的jar包中找到META-INF/spring.factories文件
// 然后从文件中解析出key=factoryClass类名称的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 取得资源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 遍历所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根据资源文件URL解析properties文件,得到对应的一组@Configuration类
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 组装数据,并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
public static void listAllFiles(String path) {
File file = new File(path);
if (!file.exists()) {
System.out.println("文件不存在");
}
if (file.isDirectory()) {
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
listAllFiles(files[i].getPath());
}
} else {
System.out.println(file);
}
}
public static void copyAllFiles(String srcPath, String desPath) throws IOException {
File srcfile = new File(srcPath);
File desFile = new File(desPath);
if (!srcfile.exists()) {
System.out.println("文件不存在");
}
if (srcfile.isDirectory()) {
File[] files = srcfile.listFiles();
for (int i = 0; i < files.length; i++) {
String des = desPath + File.separator + files[i].getPath().replace(srcPath, "");
copyAllFiles(files[i].getPath(), des);
}
} else {
copyFile(srcfile, desFile);
}
}
public static void copyFile(File srcFile, File desFile) throws IOException {
if (!desFile.getParentFile().exists()) {
desFile.getParentFile().mkdirs();
}
FileInputStream in = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(desFile);
byte[] buf = new byte[1024];
int len = 0;
while ((in.read(buf)) != -1) {
out.write(buf, 0, len);
}
in.close();
out.close();
}
与Java有关的专业术语如表2-1所示。
术语名 缩写 解释
Java Development Kit
(Java开发工具包) JDK 编写Java程序的程序员使用的软件
Java Runtime Environment
(Java运行时环境) JRE 运行Java程序的用户使用的软件
Server JRE(服务器JRE) - 在服务器上运行Java程序的软件
Standard Edition
(标准版) SE 用于桌面或简单服务器应用的Java平台
Enterprise Edition
(企业版) EE 用于复杂服务器应用的Java平台
Micro Edition(微型版) ME 用于小型设备的Java平台
Java FX - 用于图形化用户界面的一个备选工具包,在Java11之前的某些Java SE发布版本中提供
OpenJDK - JavaSE的一个免费开源实现
Java 2 J2 用于描述1998~2006之间的Java版本
Software Development Kit
(软件开发工具包) SDK 用于描述1998~2006年之间的JDK
Update u Oracle公司的术语,表示Java 8之前的bug修正版本
NetBeans - Oracle公司的集成开发环境
mkdir 目录名 在主目录创建一个目录cd 路径 进入目录jar xvf *.zip 解压zip文件javac *.java 将.java文件编译成class文件java * 运行.class文件JVM Process Status,可以显示指定系统内所有正在运行的HotSpot虚拟机进程,包括进程ID,进程启动的路径及启动参数等。参数选项有:
usage: jps [-help]
1. -q:只显示进程号;
2. -m:显示main method的参数;
3. -l:用于传输主函数的完整路径;
4. -v:显示jvm的参数;
5. -V:可用于调整JVM参数,输出通过flag文件传递到JVM中的参数(.hotspotrc文件或-XX:Flags=所指定的文件)。
此外,我们也可以使用命令ps -ef|grep "java"查看进程的相关信息。
JVM Statistics Monitoring Tool,可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,是用于监视虚拟机运行时状态信息的命令。参数选项有:
Usage: jstat -help|-options
1. option可以从下列选项中选择:(如 jstat -gc <pid>)
2. -class:显示类加载的相关信息;
3. -compiler:显示JIT编译的相关信息;
4. -printcompilation:输出JIT编译的方法信息;
5. -gc:显示gc的堆信息;
6. -gccapacity:显示各个代的容量以及使用情况;
7. -gcmetacapacity:显示元空间的大小;
8. -gcnew:显示新生代的相关信息;
9. -gcnewcapacity:显示新生代大小和使用情况;
10. -gcold:显示老年代和永久代的信息;
11. -gcoldcapacity:显示老年代的大小;
12. -gcutil:显示垃圾收集信息;
13. -gccause:显示垃圾回收的相关信息,同时显示最后一次或当前正在发生的垃圾回收的诱因;
14. -t:用于显示系统运行的时间;
15. -h<lines>:在输出多少行之后输出一次表头信息;
16. vmid:Virtual Machine ID,即进程ID,pid;
17. interval:用于控制执行的间隔时间,单位为毫秒/次;
18. count:用于指定输出多少次记录,缺省则会一直打印。
Stack Trace for java ,显示虚拟机的线程快照。参数选项有:
Usage:
1. <pid>:进程ID;<executable> <core>:产生的core dump文件;[server_id@]<remote server IP or hostname>:远程的ip或者hostname,server-id标记服务的唯一性id;
2. -l:长列表,打印关于锁的附加信息,如:属于 java.util.concurrent 的 ownable synchronizers 列表;
3. -F:当进程挂起,执行jstack <pid> 命令没有任何输出后,将强制转储堆内的线程信息;
4. -m:混合模式下,打印 java 和 native c/c++ 框架的所有栈信息;
通常,执行 jstack -l <pid>就可以查看堆栈信息了,如果控制台查看麻烦,可以输出到文件中查看并进行后续的分析,如:
jstack -l 6666 >abc.log
注意:执行jstack -l <pid>命令时会打印锁的相关信息,也会触发gc,建议使用的时候更多时候使用 jstack <pid>命令。
Configuration Info for java ,用于实时查看和调整虚拟机运行参数。参数选项有:
Usage:
option可以从下列选项中选择:
1. -flag <name>:用于输出对应名称的参数;
2. -flag [+|-]name:开启或者关闭对应名称的参数;
3. -flag <name>=<value>:设定对应名称的参数;
4. -flags:输出全部的参数;
5. -sysprops:输出系统属性;
6. <no option>:输出全部的参数和系统属性;
7. <pid>:进程ID;<executable> <core>:产生的core dump文件;[server_id@]<remote server IP or hostname>:远程的ip或者hostname,server-id标记服务的唯一性id。
jinfo命令可以用来查看正在运行的 java 应用程序的扩展参数,如系统属性、JVM参数等;也可以动态的修改正在运行的 一些JVM参数;系统崩溃时更可以从core文件里面知道崩溃的Java应用程序的配置信息。实用场景举例,以6666进程ID为例:
# 查看进程的系统属性、JVM参数(如:检查PrintGCDetails参数是否开启)
注意:jinfo命令虽然可以在java程序运行时动态地修改虚拟机参数,但并不是所有的参数都支持动态修改!
option可以从下列选项中选择:
1. -heap:用于输出heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况;
2. -histo[:live]:用于输出每个class的实例数、内存占用、类全名等相关信息(VM的内部类名字开头会加上前缀*,带上live参数只统计存活的对象数量);
3. -clstats:用于输出类加载器和jvm heap长久层的信息,包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量等;
4. -finalizerinfo:用于输出正等候回收的对象的信息;
5. -dump:<dump-options>:使用hprof二进制形式,输出jvm的heap内容到文件(带上live参数只统计存活的对象数量);
6. -F:表示强制,当pid无响应的时候会使用使用-dump或者-histo参数,且该模式下live参数无效;
7. -J<flag>:传递参数给jmap启动的JVM。
jmap命令不仅能生成dump文件,还可以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。实用场景举例,以6666进程ID为例:
# 查看对象数最多的对象,并按降序排序输出
但使用需要注意以下危险事项:
JVM Heap Dump Browaser ,用于离线分析heapdump文件,会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析的结果,包括对象的数量,大小等,支持对象查询语言。参数选项有:
Usage: jhat [-stack <bool>] [-refs <bool>] [-port <port>]
1. -J<flag>:在JVM启动时传入参数,如执行 jhat -J-Xmx512m <pid>命令,则指定运行 jhat 的Java虚拟机使用的最大堆内存为512MB(传入多个JVM参数的话,加入-Jxxxxxx);
2. -stack false|true:关闭跟踪对象分配调用堆栈,默认true,如果分配位置信息在堆转储中不可用时则必须设置false;
3. -refs false|true:关闭对象引用跟踪,默认true,返回的指针是指向其他特定对象的对象,如:反向链接或输入引用,会统计/计算堆中的所有对象;
4. -port <port>:设置 jhat HTTP server 的端口号,默认7000;
5. -version:启动后只显示版本信息就退出。
jhat命令是JVM性能调优的利器之一,用于分析heapdump文件。在执行jhat <file>命令后,在浏览器访问http://localhost:7000就可以看到分析结果了,如果熟悉OQL的话,可以通过查询语句查找自己想要的分析结果。
注意:有时候dump出来的堆很大,在启动时会报堆空间不足的错误,可以使用 -J-Xmx512m解决!
一个多功能的工具,可以用它来导出堆、查看Java进程、导出线程信息、执行GC、还可以进行采样分析(jmc 工具的飞行记录器)。参数选项有:
Usage: jcmd <pid | main class> <command ...|PerfCounter.print|-f file>
1. -l:列出机器上的JVM进程信息;
2. f:从文件读取或执行命令;
3. -h:查看帮助文档。
jcmd作为一个多功能工具,常见用法如下:
# 查看Java进程,等同于jsp命令、jinfo -flags <pid>命令
java GUI监视工具,用来监控VM,并可监控远程的VM。参数选项有:
Usage: unpack200 [-opt... | --option=value]... x.pack[.gz] y.jar
注意:pack200并且unpack200已被弃用,并可能在将来的JDK版本中删除,这里只需要了解即可。
这些命令,了解即可:
Java编译器,将Java源代码转换成字节码文件,即.class文件。举例:
// 指定生成的字节码文件放置于当前目录下,为编译指定编码,防止中文乱码
需要了解更多的参数选项,CMD 运行 javac 命令查看。
Java解释器,执行.class文件。举例:
# 注意执行该 HelloWorld.class 文件时,需要使用该类的全限定名,
需要了解更多的参数选项,CMD 运行 javac 命令查看。
Java反汇编器,反编译 .class文件。举例:
# 对字节码文件反编译,并输出附加信息
javac、java、javap命令实战,输出信息如下:
反编译可以清晰的看到:String 的 + 号的作用,在这里会通过StringBuilder的append()进行追加字符串。javap有助于我们分析底层的实现过程
多用途的存档及压缩工具,java应用程序,可将多个文件合并为单个JAR归档文件。参数选项有:
用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
jar命令带上合适的参数,可实现对 .class文件的打包,压缩与解压缩等功能。打包的优点在于统一管理、易扩展、可移植、减少磁盘占用及下载时间、安全性等。另外,使用 jar 命令将多个文件归档打包,通常支持生成三种格式:.jar、.war、.ear。
常用场景:
根据Java源代码及其说明语句生成的HTML文档。参数选项有:
用法: javadoc [options] [packagenames] [sourcefiles] [@files]