详解java常见的核心基础知识
能不能给我简单介绍一下java
Java是1995年由sun公司推出的一门高级语言,该语言具备如下特点:
- 简单易学,相较于C语言和C++,没有指针的概念,所以操作和使用是会相对容易一些。
- 平台无关性,即Java程序可以通过Java虚拟机在不同硬件不同操作系统上运行,而无需进行任何修改。
- 面向对象:面向对象是一种程序设计技术,以木匠工作为例,使用面向对象方式实现的木匠的工作关注重点永远是制作椅子,其次才是工具。而面向过程则优先关注制作工具。与C++不同的是,Java不支持多继承,取而代之的是更加简单的接口的概念。
- 编译与解释并存:与C++,Rust,Go不同的是,java源代码运行时需要先编译称为字节码文件(.class),然后再通过解释器翻译成机器码运行。
- 可靠性:Java通过早期检测以及运行时检测消除了容易出错的情况。以及与C++不同的是,操作数组、字符串方式采用的是指针模型避免了重写内存或者损坏数据的问题。
- 安全性:Java适用于网络/分布式环境,为了达到这个目标,Java在防病毒,防篡改做出很大的努力。
- 支持网络编程,使用非常方便,也有不错的性能。
- 支持多线程编程。
Java和C++主要区别有哪些?能不能给我说说优缺点
Java属于半编译、半解释型语言,通过编译器javac生成JVM可识别的字节码(.class)之后,由JVM将其解释成计算机可执行的机器码,所以执行效率相对低,依赖解释器,但是跨平台较好。基于这种情况JVM在后端编译阶段会将那些频繁执行的热点代码(hot spot code)的字节码直接生成机器码直接执行,由此避免频繁编译+解释的步骤:
而C++则是编译型语言,代码通过编译后就可以直接生成机器码直接在服务器上运行,执行速度块、效率高、因为代码是直接服务于针对性的操作系统,所以为了兼容性不得不在逻辑上进行特殊处理,所以跨平台相对差一些。
除此之外java和C++还有以下几个区别:
- 多继承:Java支持单继承类,多继承接口,而C++支持多继承。
- 内存管理方面:java有JVM自动管理、而C++需要手动管理。
- 参数传递方面:Java仅仅支持值传递,而C++支持引用、指针、值传递。
- 系统资源控制方面:Java相关方法依赖于JVM底层实现没有指针等机制操作内存空间,不够底层,所以相对C++会弱一些。
Java中有了基本类型为什么还需要包装类
java开发是面向对象的,很多特定的操作都需要以对象的维度运行,最典型的就是集合的泛型:
List<String> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
而且基本类型作为成员变量时,如果没有做任何初始化则取对应类型的零值,这种情况下会导致某些业务场景的错误,例如我们的某个int变量要求必传,设置为基本类型时,假如前端没有传值,那么这个值就会得到0而不是空,这种场景就很可能导致业务上的错误:
public class Main {
private int a;
public static void main(String[] args) {
System.out.println("a="+new Main().a);//a=0
}
}
Java的三种技术架构是什么
Java有多种技术架构,但以下是其中三种常见的:
- Java SE(Java Standard Edition):Java标准版是Java平台的基础版本,用于开发和部署桌面、嵌入式和服务器应用程序。它包含了Java编程语言、Java虚拟机(JVM)、Java类库等核心组件。Java SE提供了广泛的功能和API,可以用于开发各种类型的应用程序,从简单的命令行工具到复杂的图形用户界面(GUI)应用。
- Java EE(Java Enterprise Edition):Java企业版是用于开发和部署企业级应用程序的Java平台。它建立在Java SE的基础上,提供了一组扩展和技术,用于构建分布式、可扩展和安全的企业应用。Java EE包括诸如Servlet、JavaServer Pages(JSP)、Enterprise JavaBeans(EJB)、Java Persistence API(JPA)等技术,还提供了支持事务管理、安全性、消息传递、Web服务等企业级功能的各种规范和API。
- Java ME(Java Micro Edition):Java微版是专门设计用于嵌入式设备和移动设备的Java平台。它针对资源受限的环境,如智能手机、个人数字助理(PDA)、嵌入式系统等,提供了一个轻量级的Java运行时环境。Java ME包括了一组精简的Java类库和API,使开发人员能够构建适用于小型设备的应用程序,如移动游戏、手机应用等。
这些Java技术架构可以根据应用程序的需求和目标进行选择和使用。Java SE适用于通用的应用程序开发,Java EE适用于构建大型企业级应用,而Java ME适用于嵌入式和移动设备的开发。
JVM简介
Java虚拟机(Java Virtual Machine,JVM)是Java平台重要组成部分,它是一个在计算机上运行Java字节码的虚拟计算机,它负责解释并执行编译后的Java字节码,并将其转换为底层操作系统能够理解的机器码。
JDK和JRE分别是什么
JDK(Java Development Kit)是Java开发工具包,包含了JRE所有的东西,所以作为开发人员,只需要安装JDK即可
JRE(Java Runtime Environment)是Java运行环境,包含运行所需要的类库以及JVM。你可能认为如果仅仅要运行Java程序,安装JRE即,但是某些web程序例如需要将JSP 转换为 Java servlet就需要jdk编译了,所以保守起见,无论运行还是开发,我们都建议在操作系统上安装jdk。
什么字节码文件,字节码文件的优势是什么
首先我们了解一下从编写Java代码到Java代码被执行的过程:
- 开发人员编写java代码。
- 通过javac编译生成字节码即.class结尾的文件。
- 通过解释器翻译计算机可以理解的机器码,需要注意的是JIT解释器通过运行时编译,会将某些字节码对应的机器码保存下来,这就是HotSpot 的Lazy Evaluation原则,将那些热点代码保存起来,执行次数越多速度越快(这里参照了CPU分支预测器的工作原理)
- CPU收到机器码执行该指令。
JDK9的AOT技术以及为什么不全部使用 AOT 呢?
JDK9的AOT(Ahead-of-Time)编译技术使得Java字节码可直接编译为本地机器码,相比于传统JIT编译来说在运行时将字节码转为机器码,这种做法带来了两个好处:
- 启动时间快,因为AOT的提前编译,使得运行时避免了一些编译的开销,所以启动的效率得以提高。
- 执行性能优化,因为AOT编译是在运行前针对Java程序进行全局优化,而不是仅仅针对运行时的热点代码,所以程序执行性能相比JIT更高。
同样的,它也带来如下几个缺点:
- Java是一门动态语言,需要动态加载类、反射、动态代码生成等特性,这些特性无法在编译器静态决定,需要运行时才能进行决策的,所以使用AOT这些特性就无法支持了。
- Java应用程序需要长期的运行,可能需要动态加载和卸载类,或者在运行时进行动态的优化,AOT的静态编译就无法满足这些特性。
- Java生态系统包含大量的第三方框架,如果使用AOT可能使得很多特性都无法支持,最典型就是Spring框架的AOP。
Java 有哪几种基本数据类型?float 和 double 的区别是?
对应的基本类型和默认0值如下:
基本类型 位数 字节 默认值
int 32 4 0
short 16 2 0
long 64 8 0L
byte 8 1 0
char 16 2 'uo000'
float 32 4 0f
double 64 8 0d
boolean 1 false
所以float为4个字节,double为8字节。
Java有那些引用类型
除了8种基本类型以外,其余类型都是引用类型即变量所存储的都是数据的引指向堆中某块内存,对应的引用类型有:
- 类类型即用 class关键字修饰
- 接口 类型interface
- 数组类型arr[]
自动类型转换和强制类型转换
精度小的赋值给精度大的Java会进行自动转换,反之就需要我们进行强制转换了。
精度自小向大有两种情况,一种是字符和数字的转换,char赋值给int类型会自动转换为int。因为char占两个字节,存放都是0到 65535以内的整数,所以赋值给4个字节的int就会发生自动类型转换。
char c = 'a';
int num = c;
反之int转char就需要强制转换了:
int num=1;
char c=(char)num;
另一种情况大家就比较熟悉了,即精度小的转为精度大的发生自动类型转换,反之就是强制类型转换,如下图所示:
什么是自动装箱和自动拆箱
将基本数据类型赋值给包装类就是装箱,如下所示:
Integer i = 10; //装箱
这一点,我们可以通过查看字节码得以印证,可以看到底层就是通过valueOf实现装箱。
public static main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
BIPUSH 10
//valueOf 完成类型装箱
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 1
L1
LINENUMBER 6 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
LOCALVARIABLE i Ljava/lang/Integer; L1 L2 1
MAXSTACK = 1
MAXLOCALS = 2
}
Integer i = 10;
int num = i; //拆箱
查看字节码,可以看到底层是通过Integer.intValue实现拆箱:
// access flags 0x9
public static main([Ljava/lang/String;)V
L1
LINENUMBER 6 L1
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ISTORE 2
L2
LINENUMBER 7 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE i Ljava/lang/Integer; L1 L3 1
LOCALVARIABLE num I L2 L3 2
MAXSTACK = 1
MAXLOCALS = 3
}
&和&&的区别是什么
前者进行逻辑与时不会因为左边的false发生短路即表达式左右两边都会执行,而后者会发生短路,即左边得到false就不执行右边的逻辑:
具体我们可以查看下面这个例子,&运算不会发生短路,所以第一个func()结果返回false,第二个func()还是会被执行。
public static void main(String[] args) {
boolean b1 = func() & func();
}
private static boolean func() {
System.out.println("调用了func");
return false;
}
从输出结果我们可以得以印证:
调用了func
调用了func
同理我们再看看&&运算,因为第一次返回了false,所以&&运算符后面的func()就不会被执行。
public static void main(String[] args) {
boolean b1 = func() && func();
}
private static boolean func() {
System.out.println("调用了func");
return false;
}
输出结果如下:
调用了func
switch 是否能作用在 byte/long/String 上?
到JDK7开始byte、String都可以都可以用在switch上,但是long还是不行的。
break、continue、return的区别
- break:结束当前循环体
- continue:跳过本次循环
- return 结束当前循环直接返回结果
最有效的计算2乘8是什么方式?
用左移运算符即 2<<3 ,这种写法在JDK的数据结构中经常可以见到,例如Arrays中的binarySearch0二分搜索,它获取中间索引时除2就是用右移运算符号。
private static int binarySearch0(long[] a, int fromIndex, int toIndex,
long key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
//右移运算实现除2操作
int mid = (low + high) >>> 1;
long midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
为什么不能用浮点数表示金额?
浮点数只有8个字节,作为高精度计算的金融结算场景可能存在偏差,一般情况下针对高精度的计算,我们更推荐使用BigDecimal,因为其底层针对精度和数值进行相应的处理不会造成丢失。
BigDecimal用equals进行比较可以吗?为什么?
BigDecimal重写的equals方法比较的是两个值的精度的大小是否一致:
@Override
public boolean equals(Object x) {
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
//先比较精度
if (scale != xDec.scale)
return false;
//再比较数值
long s = this.intCompact;
long xs = xDec.intCompact;
if (s != INFLATED) {
if (xs == INFLATED)
xs = compactValFor(xDec.intVal);
return xs == s;
} else if (xs != INFLATED)
return xs == compactValFor(this.intVal);
return this.inflated().equals(xDec.inflated());
假设我们BigDecimal声明的分别是1.0和1.00,最终的结果就可能返回false,所以一般情况下,BigDecimal进行数值比较时我们建议使用compareTo
BigDecimal(double)和BigDecimal(String)有什么区别?
因为double只有8个字节,对于高精度的小数可能存在进度丢失问题,对应String类型因为字符串字面量的关系所以对应的小数就可以通过BigDecimal完成正确的转换。
为什么对Java中的负数取绝对值结果不一定是正数
我们都知道integer是4个字节,即取值范围是-2的31次方(-2147483648),2的31次方减一(2147483647),当-2147483648取绝对值时就会得到2147483648超过integer最大值导致越界得到负数。
Lambda表达式是如何实现的
本质上Lambda就是语法糖,编译阶段编译器会将lambda表达式进行解糖转换成对应的方法进行解析。
public static void main(String[] args) {
List<String> list = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.filter(i -> i % 2 == 0)
.map(String::valueOf)
.collect(Collectors.toList());
System.out.println(list);
}
对应的我们给出反编译后的结果,可以看到这些表达式最后都会转为Predicate.test、Function.apply等调用:
83: aastore
84: invokestatic #4 // InterfaceMethod java/util/stream/Stream.of:([Ljava/lang/Object;)Ljava/util/stream/Stream;
87: invokedynamic #5, 0 // InvokeDynamic #0:test:()Ljava/util/function/Predicate;
92: invokeinterface #6, 2 // InterfaceMethod java/util/stream/Stream.filter:(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
97: invokedynamic #7, 0 // InvokeDynamic #1:apply:()Ljava/util/function/Function;
102: invokeinterface #8, 2 // InterfaceMethod java/util/stream/Stream.map:(Ljava/util/function/Function;)Ljava/util/stream/Stream;
107: invokestatic #9 // Method java/util/stream/Collectors.toList:()Ljava/util/stream/Collector;
110: invokeinterface #10, 2 // InterfaceMethod java/util/stream/Stream.collect:(Ljava/util/stream/Collector;)Ljava/lang/Object;
115: checkcast #11 // class java/util/List
finally中代码一定会执行吗
不会,原因如下:
- 在执行finally前调用System.exit(0);。
- try-catch块中存在死循环。
- 虚拟机崩溃或者执行过程中服务器宕机。
- finally代码块由守护线程执行,主线程执行完时,守护线程可能会直接推出,不执行finally。
你觉得Java中的枚举有什么用处?
- 语义化好,天生自带属性,拓展强。
- 天生就是个单例,可以直接保证线程安全,便于使用单例模式。
什么是AIO、BIO和NIO
- BIO:同步阻塞IO,线程发起请求后一直阻塞直到读或者写结束,传统的socket编程就是BIO适用于那些连接数较少的服务端。
- NIO:同步非阻塞,线程发起IO请求后不阻塞立即返回,常用于解决c10k问题,这其中Netty就是最经典的NIO框架。
- AIO:异步非阻塞,线程发起IO请求后不阻塞返回,IO操作有结果后会回调通知。
UUID是什么?可以保证全局唯一?
能,UUID即全局唯一标识,是指在一台机器上生成的id编码,由于UUID依赖MAC地址、时间戳、随机数等信息,所以UUID具有极高的唯一性,几乎是不可能重复的。
Arrays.sort是使用什么排序算法实现的
查看底层源码可知排序算法使用的是双路快排法,一种针对数组进行递归分而治之再合并的O(nlogN)级别的排序算法:
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
为什么Java中的main方法必须是public static void修饰
- 设置public让JVM可调用。
- 声明状态避免实例化后调用
- 调用后无需任何返回值,所以返回类型设置为void。
BigDecimal和Long哪个更适合标识金额
BigDecimal无论是使用还是进行高精度计算时都比Long类型准确。
怎么修改一个类中的private修饰的String参数的值
- 反射
- 用set方法
Stream的并行流一定比串行流更快吗
不一定,针对一些计算密集型任务。它需要时刻活跃于CPU核心才能工作,如果使用并行流以为去提升并发度,这其中上下文切换和结果聚合的开销导致的最终性能,可能还不如单线程串行运算。
小结
自此我们将java基础的核心知识进行了简单的总结,后续笔者才会针对这些题目进行进一步的迭代,希望对你有帮助。