目录
为什么Java解释和编译都有
八种基本的数据类型
数据类型转换方式
为什么用bigDecimal 不用double
装箱和拆箱
自动装箱的弊端
包装类作用
Integer相比int
integer的缓存
面向对象
多态体现
抽象类和接口的区别
抽象类能加final修饰吗
抽象类可以被实例化吗
静态变量和静态方法
非静态内部类和静态内部类的区别
非静态内部类可以直接访问外部方法
加载顺序
深拷贝和浅拷贝的区别
实现深拷贝的三种方法
泛型
创建对象
New出的对象什么时候回收?
反射?
阻止反射修改私有变量
反射应用场景
Java注解的原理
注解的作用域
Java异常编辑
异常处理
抛出异常为什么不用throws?
== 与 equals 有什么区别
StringBuffer和StringBuild区别是什么?
stream的API
Stream流的并行API
completableFuture怎么用
把一个对象从一个jvm转移到另一个jvm
序列化和反序列化让你自己实现你会怎么做
对象转为二进制字节流
volatile和sychronized如何实现单例模式
代理模式和适配器模式有什么区别?
BIO、NIO、AIO区别是什么
NIO是怎么实现
哪个框架用到NIO
实现native方法
为什么Java解释和编译都有
编译性:
- Java源代码首先被编译成字节码,JIT 会把编译过的机器码保存起来,以备下次使用。
解释性:
- JVM中一个方法调用计数器,当累计计数大于一定值的时候,就使用JIT进行编译生成机器码文件。否则就是用解释器进行解释执行,字节码也是经过解释器进行解释运行的。
八种基本的数据类型
基本数据类型共有8种,可以分为三类:
- 数值型:整数类型(byte、short、int、long)和浮点类型(float、double)
- 字符型:char
- 布尔型:boolean
数据类型转换方式
- 自动类型转换(隐式转换):当目标类型的范围大于源类型时,Java会自动将源类型转换为目标类型,不需要显式的类型转换。例如,将转换为、将转换为等。
- 强制类型转换(显式转换):当目标类型的范围小于源类型时,需要使用强制类型转换将源类型转换为目标类型。这可能导致数据丢失或溢出。例如,将转换为、将转换为等。语法为:目标类型 变量名 = (目标类型) 源类型。
- 字符串转换:Java提供了将字符串表示的数据转换为其他类型数据的方法。例如,将字符串转换为整型,可以使用方法;将字符串转换为浮点型,可以使用方法等。
- 数值之间的转换:Java提供了一些数值类型之间的转换方法,如将整型转换为字符型、将字符型转换为整型等。这些转换方式可以通过类型的包装类来实现,例如类、类等提供了相应的转换方法。
为什么用bigDecimal 不用double
在创建对象时,应该使用字符串作为参数,而不是直接使用浮点数值,以避免浮点数精度丢失
装箱和拆箱
装箱(Boxing)和拆箱(Unboxing)是将基本数据类型和对应的包装类之间进行转换的过程。
自动装箱主要发生在两种情况,一种是赋值时,另一种是在方法调用的时候。
自动装箱的弊端
在一个循环中进行自动装箱操作的情况,可能会创建多余的对象,影响程序的性能。
包装类作用
可以把属性也就是数据跟处理这些数据的方法结合在一起
泛型只能使用引用类型,而不能使用基本类型
基本类型和引用类型不能直接进行转换,必须使用包装类来实现
集合中只能存储对象,而不能存储基本数据类型
Integer相比int
使用int来存储一个整数时,不需要任何额外的内存分配,而使用Integer时,必须为对象分配内存。在性能方面,基本数据类型的操作通常比相应的引用类型快。
包装类方便地进行数据类型转换
int变量可以直接赋值为0,而Integer变量必须通过实例化对象来赋值
integer的缓存
Java的Integer类内部实现了一个静态缓存池,用于存储-128至127内的整数值对应的Integer对象。
面向对象
封装是指将对象的属性和行为结合在一起,对外隐藏对象的内部细节,仅通过对象提供的接口与外界交互。
继承是一种可以使得子类自动共享父类数据结构和方法的机制
多态是指允许不同类的对象对同一消息作出响应
多态体现
方法重载是指同一类中可以有多个同名方法,它们具有不同的参数列表(参数类型、数量或顺序不同)
方法重写是指子类能够提供对父类中同名方法的具体实现。方法名、参数列表必须与父类中的方法一致
体现在接口的使用上,多个类可以实现同一个接口,并且用接口类型的引用来调用这些类的方法
可以使用父类类型的引用指向子类对象,这是向上转型。通过这种方式,可以在运行时期采用不同的子类实现。向下转型是将父类引用转回其子类类型,但在执行前需要确认引用实际指向的对象类型以避免
抽象类和接口的区别
实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。
接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,静态方法也是在 Java 8 中引入的,私有方法是在 Java 9 中引入的,而抽象类可以有定义与实现,
接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰
抽象类能加final修饰吗
Java中的抽象类是用来被继承的,而final修饰符用于禁止类被继承或方法被重写
抽象类可以被实例化吗
抽象类可以有构造器,这些构造器在子类实例化时会被调用,以便进行必要的初始化工作。然而,这个过程并不是直接实例化抽象类,而是创建了子类的实例,间接地使用了抽象类的构造器。
静态变量和静态方法
- 共享性:所有该类的实例共享同一个静态变量。
- 初始化:静态变量在类被加载时初始化,只会对其进行一次分配内存。
- 访问方式:静态变量可以直接通过类名访问,也可以通过实例访问,但推荐使用类名。
- 无实例依赖:静态方法可以在没有创建类实例的情况下调用。
- 访问静态成员:静态方法可以直接调用其他静态变量和静态方法,但不能直接访问非静态成员。
- 多态性:静态方法不支持重写(Override),但可以被隐藏(Hide)。
非静态内部类和静态内部类的区别
- 非静态内部类依赖于外部类的实例,而静态内部类不依赖于外部类的实例。
- 非静态内部类可以访问外部类的实例变量和方法,而静态内部类只能访问外部类的静态成员。
- 非静态内部类不能定义静态成员,而静态内部类可以定义静态成员。
- 非静态内部类在外部类实例化后才能实例化,而静态内部类可以独立实例化。
- 非静态内部类可以访问外部类的私有成员,而静态内部类不能直接访问外部类的私有成员,需要通过实例化外部类来访问。
非静态内部类可以直接访问外部方法
因为编译器在生成字节码时会为非静态内部类维护一个指向外部类实例的引用。编译器会在生成非静态内部类的构造方法时,将外部类实例作为参数传入
加载顺序
- 父类静态成员变量、静态代码块(如果有)
- 子类静态成员变量、静态代码块(如果有)
- 父类构造方法(实例化对象时)
- 子类构造方法(实例化对象时)
深拷贝和浅拷贝的区别
浅拷贝是指只复制对象本身和其内部的值类型字段,但不会复制对象内部的引用类型字段。
深拷贝是指在复制对象的同时,将对象内部的所有引用类型字段的内容也复制一份
实现深拷贝的三种方法
实现 Cloneable 接口并重写 clone() 方法,在 cl one() 方法中,通过递归克隆引用类型字段来实现深拷贝。
通过将对象序列化为字节流,再从字节流反序列化为对象来实现深拷贝。
手动递归复制对象及其引用类型字段。
泛型
允许类、接口和方法在定义时使用一个或多个类型参数,这些类型参数在使用时可以被指定为具体的类型。
创建对象
使用new关键字:通过new关键字直接调用类的构造方法来创建对象。
使用Class类的newInstance()方法:通过反射机制,可以使用Class类的newInstance()方法创建对象。
使用Constructor类的newInstance()方法:同样是通过反射机制,可以使用Constructor类的newInstance()方法创建对象。
使用clone()方法:如果类实现了Cloneable接口,可以使用clone()方法复制对象。
使用反序列化:通过将对象序列化到文件或流中,然后再进行反序列化来创建对象。
New出的对象什么时候回收?
- 引用计数法:某个对象的引用计数为0时,表示该对象不再被引用,可以被回收。
- 可达性分析算法:从根对象(如方法区中的类静态属性、方法中的局部变量等)出发,通过对象之间的引用链进行遍历,如果存在一条引用链到达某个对象,则说明该对象是可达的,反之不可达,不可达的对象将被回收。
- 终结器(Finalizer):如果对象重写了方法,垃圾回收器会在回收该对象之前调用方法,对象可以在方法中进行一些清理操作。然而,终结器机制的使用不被推荐,因为它的执行时间是不确定的,可能会导致不可预测的性能问题
反射?
在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
阻止反射修改私有变量
使用安全管理器
System.setSecurityManager(new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
if (perm instanceof ReflectPermission) {
throw new SecurityException("反射操作被禁止");
}
}
});
设置字段不可访问
通过将字段的 方法设置为 来阻止对私有变量的修改
反射应用场景
加载数据库驱动
配置文件加载
- 将程序中所有XML或properties配置文件加载入内存
- Java类里面解析xml或者properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息
- 使用反射机制,根据这个字符串获得某个类的Class实例
- 动态配置实例的属性
Java注解的原理
继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。
通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
注解的作用域
类级别作用域:用于描述类的注解,通常放置在类定义的上面,可以用来指定类的一些属性
方法级别作用域:用于描述方法的注解
字段级别作用域:用于描述字段的注解
Java异常
- Error(错误):表示运行时环境的错误。错误是程序无法处理的严重问题,如系统崩溃、虚拟机错误、动态链接失败等。通常,程序不应该尝试捕获这类错误。例如,OutOfMemoryError、StackOverflowError等。
- Exception(异常):表示程序本身可以处理的异常条件。异常分为两大类:
- 非运行时异常:这类异常在编译时期就必须被捕获或者声明抛出。它们通常是外部错误,如文件不存在(FileNotFoundException)、类未找到(ClassNotFoundException)等。非运行时异常强制程序员处理这些可能出现的问题,增强了程序的健壮性。
- 运行时异常:运行时异常由程序错误导致,如空指针访问(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。运行时异常是不需要在编译时强制捕获或声明的。
异常处理
使用try-catch语句块来捕获和处理异常
throw语句:根据需要在代码中使用throw语句主动抛出特定类型的异常。
throws关键字:用于在方法声明中声明可能抛出的异常类型。
抛出异常为什么不用throws?
如果异常是未检查异常或者在方法内部被捕获和处理了,那么就不需要使用throws。
== 与 equals 有什么区别
- 操作符:用于比较两个引用是否指向同一个对象,判断两个对象的内存地址是否相同。
- 方法:用于比较两个对象的内容是否相等。
StringBuffer和StringBuild区别是什么?
String 是 Java 中基础且重要的类,被声明为 final class,是不可变字符串。
StringBuffer 就是为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类。它提供了 append 和 add 方法,它的本质是一个线程安全的可修改的字符序列。
stream的API
从一个列表中筛选出所有长度大于3的字符串,并收集到一个新的列表中。
计算一个数字列表中所有元素的总和。
Stream流的并行API
并行流(ParallelStream)就是将源数据分为多个子流对象进行多线程操作,然后将处理的结果再汇总为一个流对象,底层是使用通用的 fork/join 池来实现,即将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果
对CPU密集型的任务来说,并行流使用ForkJoinPool线程池,为每个CPU分配一个任务,这是非常有效率的
completableFuture怎么用
- Future用于表示异步计算的结果,只能通过阻塞或者轮询的方式获取结果,而且不支持设置回调方法,Java 8之前若要设置回调一般会使用guava的ListenableFuture,回调的引入又会导致臭名昭著的回调地狱
- CompletableFuture对Future进行了扩展,可以通过设置回调的方式处理计算结果,同时也支持组合操作,支持进一步的编排,同时一定程度解决了回调地狱的问题。
CompletableFuture的实现更为简洁,可读性更好。
CompletableFuture实现了两个接口:Future、CompletionStage。
把一个对象从一个jvm转移到另一个jvm
- 使用序列化和反序列化:将对象序列化为字节流,并将其发送到另一个 JVM,然后在另一个 JVM 中反序列化字节流恢复对象。这可以通过 Java 的 ObjectOutputStream 和 ObjectInputStream 来实现。
- 使用消息传递机制:利用消息传递机制,比如使用消息队列(如 RabbitMQ、Kafka)或者通过网络套接字进行通信,将对象从一个 JVM 发送到另一个。这需要自定义协议来序列化对象并在另一个 JVM 中反序列化。
- 使用远程方法调用(RPC):可以使用远程方法调用框架,如 gRPC,来实现对象在不同 JVM 之间的传输。远程方法调用可以让你在分布式系统中调用远程 JVM 上的对象的方法。
- 使用共享数据库或缓存:将对象存储在共享数据库(如 MySQL、PostgreSQL)或共享缓存(如 Redis)中,让不同的 JVM 可以访问这些共享数据。这种方法适用于需要共享数据但不需要直接传输对象的场景。
序列化和反序列化让你自己实现你会怎么做
会考虑用主流序列化框架,比如FastJson、Protobuf来替代Java 序列化。如果追求性能的话,Protobuf 序列化框架会比较合适,Protobuf 的这种数据存储格式,不仅压缩存储数据的效果好, 在编码和解码的性能方面也很高效。
Java 默认的序列化虽然实现方便,但却存在安全漏洞、不跨语言以及性能差等缺陷。
对象转为二进制字节流
- 让类实现Serializable接口
- 创建输出流并写入对象
- 创建输入流并读取对象
volatile和sychronized如何实现单例模式
正确的双重检查锁定模式需要需要使用 volatile。volatile主要包含两个功能。
- 保证可见性。使用 volatile 定义的变量,将会保证对所有线程的可见性。
- 禁止指令重排序优化。
代理模式和适配器模式有什么区别?
目的不同:代理模式主要关注控制对对象的访问,而适配器模java基础语法速记式则用于接口转换,
结构不同:代理模式一般包含抽象主题、真实主题和代理三个角色,适配器模式包含目标接口、适配器和被适配者三个角色。
应用场景不同:代理模式常用于添加额外功能或控制对对象的访问,适配器模式常用于让不兼容的接口协同工作。
BIO、NIO、AIO区别是什么
- BIO(blocking IO):就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。优点是代码比较简单、直观;缺点是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
- NIO(non-blocking IO) :Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
- AIO(Asynchronous IO) :是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非阻塞的 IO 操作方式,异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
NIO是怎么实现
同步是指线程不断轮询IO事件是否就绪,非阻塞是指线程在等待IO的时候,可以同时做其他任务。
同步的核心就Selector(I/O多路复用),Selector代替了线程本身轮询IO事件。非阻塞的核心就是通道和缓冲区,当IO事件就绪时,可以通过通道写到缓冲区。线程之间通过wait,notify通信,减少线程切换。
传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
哪个框架用到NIO
Netty 的 I/O 模型是基于非阻塞 I/O 实现的,底层依赖的是 NIO 框架的多路复用器 Selector。采用 epoll 模式后,只需要一个线程负责 Selector 的轮询。当有数据处于就绪状态后,需要一个事件分发器(Event Dispather),它负责将读写事件分发给对应的读写事件处理器(Event Handler)。事件分发器有两种设计模式:Reactor 和 Proactor,Reactor 采用同步 I/O, Proactor 采用异步 I/O。
Reactor 实现相对简单,适合处理耗时短的场景
Proactor 性能更高,但是实现逻辑非常复杂,适合图片或视频流分析服务器,目前主流的事件驱动模型还是依赖 select 或 epoll 来实现。
实现native方法
生成JNI头文件、使用 命令(在 JDK 8 及之前)或 选项(在 JDK 9 及之后)生成 JNI 头文件。
编写本地代码、使用C/C++编写本地方法的实现
编译本地代码、将C/C++代码编译成动态链接库
加载本地库、使用System.loadLibrary()方法来加载你编译好的本地库,
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/25169.html