概念
Java的特点
- 平台无关性:一次编译,处处运行
- 面向对象:封装、继承、多态
- 内存管理:Java有自己的垃圾回收机制,不需要手动管理内存。
Java为什么是跨平台的?
JVM不同平台有不同的版本。
我们编写的Java源码,编译后会生成.class文件(字节码文件)。虚拟机就是将字节码文件翻译成指定平台下的机器码运行的。只要在不同平台上安装对应的JVM,就可以运行字节码文件。
编译的结果不是生成机器码,而是生成字节码,字节码不能直接运行,需要通过JVM翻译成机器码才能运行,不同平台下的机器码不同,但是字节码却是一样的。
JVM、JDK、JRE的关系?
JVM:Java虚拟机,负责将字节码编译成机器码并执行程序(解释自己的指令集并映射到本地CPU指令集和OS的系统调用)
JRE:Java运行时的环境,是Java程序所需的最小环境,包含JVM和一组类库,用来支持程序的运行。(JRE不包含开发工具,只提供Java程序所需的运行环境)
JDK:Java开发工具包,包含了JVM、编译器(javac)等开发工具。JDK提供了开发、编译、调试和运行Java程序所需的全部工具和环境
Java的解释性和编译性
- 编译性:Java源码先被编译成字节码,JIT会把编译后的机器码保存起来,以备下次使用
- 解释性:JVM中的一个方法调用计数器,当累计技术大于一定值的时候,就是用JIT进行编译生成机器码文件,否则就是解释器进行解释执行。(字节码也是通过解释器进行解释运行的)
数据类型
八种基本的数据类型
- 数值类型:long(8)、int(4)、short(2)、byte(1)、double(8)、float(4)
- 字符类型:char(2)
- 布尔类型:boolean(1)
数据类型转换的方式
- 自动类型转换:int转long、float转double
- 强制类型转换:long转int、double转float
为什么用BigDecimal不用double?
double会出现精度丢失的问题,double执行的是二进制的浮点运算,二进制有些情况下不能准确的表示一个小数。
Decimal是精确计算的,一般涉及到金钱的计算都会使用Decimal
在创建BigDecimal对象时,需要使用字符串作为参数,而不是直接使用浮点运算作为数值,以避免浮点数精度丢失。
装箱和拆箱
装箱是基本数据类型转成对应的包装类
拆箱是包装类转成对应的基本数据类型
Integer i = 10; // 装箱
int n = i; // 拆箱
自动装箱的问题
Integer sum = 0; for(int i = 0; i < 10; ++i) { sum += i; // 一直发生装箱、拆箱 }
基本数据类型和引用数据类型不能直接转换,必须使用包装类来实现。
int类型转成String类型:int类型先转成Integer类型;Integer类型转成String类型
Integer的缓存
Integer类的内部实现了一个静态缓存池,用来存储特定范围内(默认:-128~127)的整数值对应的Integer对象。
当创建一个在指定范围内的整数对象时,并不会每次都生成新的对象,而是复用缓存中的现有对象,会直接从内存中取出,不需要新建一个对象
面向对象
怎么理解面向对象?
面向对象是一种编程范式,将现实世界中的事物抽象为对象。面向对象的设计思想以对象为中心,通过对象之间的交互来完成程序的功能。
- 封装:将对象的数据和方法结合在一起,对外隐藏对象内部的实现细节,只通过对象提供的接口与外界交互
- 继承:使得子类自动共享父类数据结构和方法的机制,是代码复用的重要手段,通过继承可以实现建立类和类之间的层次关系
- 多态:允许不同类的对象对同一个消息做出不同的响应。(编译时多态、运行时多态)
- 方法重载:同一个类可以有多个同名的方法(通过参数列表来区分)
- 方法重写:子类可以提供对父类同名方法的具体实现
- 接口的使用:多个类可以实现同一个接口,并通过接口类型的引用来调用这些类的方法
- 向上转型和向下转型:
- 向上转型:使用父类的引用指向子类对象
- 向下转型:将父类引用转化成子类类型(需要确认引用实际指向的对象类型)
面向对象的设计原则
- 单一职责原则:一个类应该只有一个引起他变化的原因
- 开放封闭原则:软件实体应该对扩展开放,对修改封闭
- 里氏替换原则:子类对象应该能够替换所有的父类对象
- 接口隔离原则:客户端不应该依赖那些它不需要的接口
- 依赖倒置原则:高层模块不应该依赖底层模块
- 最少知识原则:一个对象应该对其他对象有最少的了解
抽象类和接口的区别
- 抽象类用来描述类的共同行为和特性,可以有成员变量、构造方法、具体方法;接口用来定义行为规范,可以多实现,只有常量和抽象方法
- 一个类可以实现(implements)多个接口,但是只能继承(extends)一个类
- 接口中只能有定义,不能有方法的实现;抽象类中可以有定义和实现
- 接口成员变量默认为public static final,成员方法默认为public abstract;抽象类中成员变量默认default,可以在子类中被重新定义,也可以被重新赋值,抽象类方法被abstract修饰,不能被private、static、synchronized、native修饰,必须以分号结尾。
- 抽象类可以包含实例变量和静态遍历;接口只能包含常量
new子类对象的加载顺序?
- 父类静态成员变量、静态代码块
- 子类静态成员变量、静态代码块
- 父类构造方法
- 子类构造方法
浅拷贝和深拷贝
- 浅拷贝:只创建一个新的对象,将原对象的字段值放入新对象中,两个对象指向的是同一个引用对象
- 深拷贝:递归复制对象内部所有引用类型的字段,生成一个全新的对象以及其内部的所有对象,实现方式如下:
- 实现Cloneable接口并重写clone()方法
- 将对象序列化为字节流,再从字节流反序列化为对象
- 手动赋值
对象
Java创建对象的方式
-
使用new关键字
-
使用Class类的newInstance()方法【反射】
MyClass obj = (MyClass)Class.forName("com.example.MyClass").newInstance();
- 使用Constructor的newInstance()方法【反射】
Constructor<MyClass> constructor = MyClass.class.getConstructor();
MyClass obj = constructor.newInstance();
- 使用clone方法:如果类实现了Cloneable接口,可以使用clone()方法复制对象
- 使用反序列化:将对象序列化到文件中,再使用反序列化创建对象
new出的对象什么时候回收?
通过new创建出的对象,是由gc负责回收的,再程序运行过程中自动进行的,它会周期性检查不再被引用的对象,将其回收释放内存。gc主要是根据一些算法来决定的,主要有:
- 可达计数器:某个对象的引用计数器为0,表示该对象可以被回收
- 可达分析算法:从GC Root出发,一层层找所引用的对象,如果被找到就是存活对象,其他不可达的对象就是垃圾对象,垃圾对象就会被回收
- 终结器(Finalizer):如果对象重写了finalize()方法,gc会在回收该对象之前调用finalize()方法,对象可以在finalize()方法中进行一些清理操作。【不推荐】
反射
什么是反射
在运行过程中,任意一个类,都能够知道这个类的所有属性和方法,任意一个对象,都能调用它的任意一个方法和属性。
- 允许在运行时获取类的完整结构信息
- 可以使用反射API动态创建对象实例
- 可以在运行时动态调用对象的方法
- 允许在运行时访问和修改对象的字段值(包括私有的)
注解
Java注解的原理
注解本质是继承了Annotation的特殊接口,具体实现类是Java运行时生成的动态代理类。
通过反射获取注解,其实返回的是Java运行时生成的动态代理对象,通过代理对象调用自定义注解的的方法,最后会调用invoke方法。
注解的作用域
- 类级别作用域:描述类的注解,放在类上,用来指定类的一些属性
- 方法级别作用域:描述方法的注解,放在方法定义上,用来指定方法的一些属性
- 字段级别作用域:用来描述字段的注解,放在字段上,用来指定字段的一些属性
Object
== 与 equals的区别?
- 功能不同:==是操作符,用于比较两个变量或对象的引用地址是否相同;equals是一个方法,用于比较两个对象的内容是否相同,他是Object类中的一个方法,子类可以重写这个方法。
- 比较对象不同:==适用于基本数据类型(比较值是否相同)和对象引用(比较引用地址是否相同);equals适合于对象内容的比较,对于基本数据类型不能直接用equals,但是可以包装成对应的包装类对象,再使用equals比较
- 默认行为不同:equals对于未重写equals方法的子类来说,equals方法的行为与==相同,只有在类里重写了equals方法后,才会根据重写的方法逻辑进行比较
- 性能不同:==速度比较快,equals速度比较慢
String、StringBuffer、StringBuilder的区别?
- String是不可变的字符串,因为他的不可变性,在拼接字符串的时候就会产生很多无用的中间对象(线程安全)
- StringBuffer就是为了解决String在拼接字符串的时候产生大量中间对象而提供的类。它是可变的,是线程安全的。
- StringBuilder和StringBuffer本质上没什么区别,但是它是线程不安全的,减少了开销。
速度:String < StringBuffer < StringBuilder
适用场景:
- String:操作少量数据
- StringBuffer:多线程操作大量数据
- StringBuilder:单线程操作大量数据
序列化
怎么把一个对象从一个jvm转移到另一个jvm
- 使用序列化和反序列化:将一个对象序列化为字节流,并将其发送到另一个JVM,在另一个JVM进行反序列化恢复对象。
- 使用消息队列
- 使用远程方法调用(RPC):在分布式系统中调用远程JVM上对象的方法
- 使用共享数据库或缓存:将对象存储在共享数据库或共享缓存中,让不同的JVM可以访问这些共享数据。
I/O
Java怎么实现网络IO高并发编程
传统的BIO:如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读取到的数据,如果BIO想要并发处理多个客户端的IO,只能使用多线程模式,一个线程专门处理一个客户端io,随着客户端越来越多,所需要的线程也会增多,会消耗系统的性能。
Java NIO:同步非阻塞的I/O模型,基于IO多路复用,可以用一个线程处理多个客户端IO
- BIO:传统的java.io包,基于流模型实现,在读写动作完成之前,线程会阻塞。代码比较直观,但是IO的效率和扩展性差
- NIO:java.nio包,提供了Channel、Selector、Buffer等抽象,同步非阻塞的IO程序,同时提供了更接近操作系统底层高性能的数据操作方式
- AIO:是NIO的升级版,提供了异步非阻塞的IO,应用操作后会直接返回,不会阻塞在那里,当后台处理完成后,操作系统会通知响应的线程进行后续的操作。
NIO是怎么实现的?
NIO:同步非阻塞的IO模型。
-
同步:线程不断轮询IO事件是否就绪
-
非阻塞:线程在等待IO的时候可以同时做其他任务
同步的核心是Selector,Selector代替线程本身的IO实践,避免了阻塞同时减少了不必要的线程消耗
非阻塞的核心是通道和缓冲区,当IO事件就绪时,可以通过写到缓冲区保证IO的成功,而无需线程阻塞式等待
NIO由一个专门的线程处理所有的IO事件,负责并发。
Netty的IO模型式基于非阻塞IO实现的。