一、基本概念
1、什么是Java?
是一门面向对象的编程语言,由 Sun公司(后被Oracle收购)于1995年发布。不仅吸收了 C++ 语言的各种优点,还摒弃了 C++ 里难以理解的多继承、指针等概念。它是一种通用的、高性能的、跨平台的编程语言,广泛应用于软件开发领域。具有简单易学、面向对象、跨平台、安全可靠等特点。
2、Java和C++的区别
虽然,Java和C++都是面向对象的语言,都支持封装、继承和多态,但是,它们还是有挺多不相同的地方:
- Java不提供指针来直接访问内存,程序内存更加安全
- Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
- Java是一种解释型语言(实际上Java既是解释型也是编译型语言),程序在运行时由Java虚拟机进行解释和执行,而C++是一种编译型语言,程序需要先编译成机器码,然后才能执行。
- C++同时支持方法重载和操作符重载,但是Java只支持方法重载(操作符重载增加了复杂性,这与Java最初的设计思想不符)。
- ...
3、Java语言的特点
- 简单易学;
- 面向对象(封装,继承,多态);
- 平台无关性(JVM实现);
- 支持多线程(C++语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而Java语言却提供了多线程支持);
- 可靠性、安全性(使用引用取代指针、拥有一套异常处理机制、强制类型转换需要符合一定规则、字节码传输使用了加密机制、运行环境提供保障机制、JVM有垃圾回收机制...);
- 支持网络编程并且很方便(Java 语言诞生本身就是为简化网络编程设计的,因此Java 语言不仅支持网络编程而且很方便);
- 编译与解释并存(Java程序要经过先编译,后解释两个步骤)
4、Java SE/EE/ME 的区别
-
Java SE(标准版,是EE、ME的基础),它包含了Java的核心类库和工具,用于支持Java的基本功能,如面向对象编程、垃圾回收、线程、网络编程等。Java SE主要用于开发桌面应用程序、命令行工具和基本的服务器应用程序等。
-
Java EE(企业版),可以被看作是一个技术平台,用于开发和部署大型企业级应用程序。主要包括servlet、JSP、JavaBean、JDBC、Web Service等技术。Java EE 相对于Java SE多了以下一些内容:
- 分布式应用开发:Java EE 提供了大量的 API 和工具,用于支持分布式系统的开发,例如 EJB、JMS、JTA 等等。这些工具和 API 可以从底层提供异步通信、事务、远程调用等支持。
- Web 应用程序开发:除了支持 Servlet 和 JSP 相关的 API 外,Java EE 还提供了 JSF、JSTL等来简化 Web 开发的 API。
- 数据库访问和 ORM:Java EE 提供了 JPA技术用于数据库访问和对象和关系的映射,使得更方便容易地进行数据库操作。
- 安全和身份验证
- Java Connector Architecture(JCA)
-
Java ME(微型版),主要用于嵌入式设备和小型设备的开发。它提供了一系列标准 API 和轻量级组件,旨在为资源受限的设备提供编程支持和应用程序开发。相对于Java SE,Java ME包含了以下内容:
- 针对嵌入式环境的类库
- 针对移动设备的类库
- 部署和管理工具
说得更简单点:
- Java SE 是做电脑上运行的软件。
- Java EE 是用来做网站的(我们常见的JSP技术)
- Java ME 是做手机软件的
5、Java是如何实现跨平台的?
JVM是关键。
在程序运行前,Java源代码(.java)需要经过编译器编译成字节码(.class)。...运行时,JVM负责将字节码翻译成特定平台下的机器码并运行,也就是说,只要在不同的平台上安装对应的JVM,就可以运行字节码文件。
同一份Java源代码在不同的平台上运行,它不需要做任何的改变,并且只需要编译一次。而编译好的字节码,是通过JVM这个中间的“桥梁”实现跨平台的,JVM是与平台相关的软件,它能将统一的字节码翻译成该平台的机器码。
注意事项:
- 编译的结果是生成字节码、不是机器码,字节码不能直接运行,必须通过JVM翻译成机器码才能运行;
- 跨平台的是Java程序、而不是JVM,JVM是用C/C++开发的软件,不同平台下需要安装不同版本的JVM。
- 编译期:编译器把源代码翻译成机器码;运行期:将可执行文件交给操作系统去执行
- 解释器可直接将源代码翻译成机器语言并执行(注意!!!)
6、JDK、JRE、JVM三者关系
- JRE(Java运行环境):JVM + Java核心类库 + Java命令 + 其他构件
- JDK(Java开发工具包):JRE + 编译器(javac) + (调试/分析)工具(javadoc、jdb、JConsole等)
- JRE不能用于创建新程序,JDK能创建和编译程序
JDK与JRE有什么区别?
- JDK:Java的标准开发工具包(普通用户只需要安装 JRE 来运行 Java 程序。而程序开发者必须安装 JDK 来编译、调试程序),它提供了编译、运行Java程序所需的各种工具和资源。是整个Java的核心。
- JRE:提供了Java运行所需的环境,用于解释执行 Java 的字节码文件。JRE 是 Java 运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器),只是针对于【使用】Java 程序的用户
JDK包含了JRE。如果只运行Java程序,安装JRE即可。要编写Java程序需安装JDK.
7、怎么运行编译一个Java文件【新】
使用cmd命令的方法(确保计算机安装了jdk并配置了相关环境):(另外可以直接用IDEA或Eclipse)
- 使用javac命令来编译.java文件:
javac Test.java - 使用java命令来运行:
java Test
8、为什么说Java语言“编译与解释并存”?
- 编译型:将源代码一次性翻译成可被该平台执行的机器码,执行速度快,开发效率低(如:C、C++、Pascal、Object-C以及Swift)
- 解释型:一句一句的将代码解释为机器代码后再执行,执行速度慢,开发效率高(如:JavaScript、Python、Erlang、PHP、Perl、Ruby)
这是因为Java 语言既具有编译型语言的特征,也具有解释型语言的特征。因为Java程序要经过先编译,后解释两个步骤,由Java编写的程序需要先经过编译步骤,生成字节码(.class文件),这种字节码必须由Java解释器来解释执行。
9、为什么Java语言不支持多重继承?
Java 不支持多重继承的原因实际上是为了避免其引起的冲突和不确定性问题,这可能会使多重继承变得难以使用和维护。以下是一些原因:
- 多个父类的命名冲突:多重继承将使多个父类的属性和方法可能具有相同的命名,这将会在语言级别上不可避免的产生命名冲突,使代码阅读、维护和理解变得困难。
- 避免实现复杂性:如果Java允许多重继承,那么就必须设计实现来处理类与多个父类的交集,这将使语言本身变得复杂,增加了理解和管理代码实现子类的难度,因此单一继承更容易实现和管理。
- 转型困难:如果一个类同时继承了多个父类,那么在进行对象强制类型转换时,可能会发生转换失败的情况。这是因为转换后的对象可能具有多个独立的部分,而这些部分彼此之间可能存在着不兼容的数据类型,从而导致转换失败。
接口(interface)可以实现多重继承的优点。Java接口的引入使得多态性变得更加容易管理,同时在接口中定义的抽象方法的实现可以自由发挥,你可以在多个接口之间实现不同的行为,这比直接实现多个父类的方法更具灵活性并且没有多重继承可能带来的上述问题。同时,Java 也提供了其他一些机制,例如内部类等,来帮助程序员更好地组织和管理代码。
10、java常用的工具类
1、集合工具类
1.1 java.util.Collections
Collections.sort(list);//升序
Collections.reverse(list);//降序
Collections.max(list);//获取最大值
Collections.min(list);//获取最小值
Collections.emptyList();//空集合
Collections.binarySearch(list, 3);//二分查找
Collections.unmodifiableList(list);//转换成不可修改集合
1.2 org.springframework.util.CollectionUtils
1.3 org.apache.commons.collections.CollectionUtils
CollectionUtils.isEmpty();//集合判空
CollectionUtils.union(list, list2);//获取并集
CollectionUtils.intersection(list, list2);//获取交集
CollectionUtils.disjunction(list, list2);//获取交集的补集
CollectionUtils.subtract(list, list2);//获取差集
CollectionUtils.select:根据条件筛选集合元素
CollectionUtils.transform:根据指定方法处理集合元素,类似List的map()
CollectionUtils.filter:过滤元素,雷瑟List的filter()
CollectionUtils.find:基本和select一样
CollectionUtils.collect:和transform 差不多一样,但是返回新数组
CollectionUtils.forAllDo:调用每个元素的指定方法
CollectionUtils.isEqualCollection:判断两个集合是否一致
1.4 org.apache.commons.lang.ArrayUtils
contains:是否包含某字符串
addAll:添加整个数组
clone:克隆一个数组
isEmpty:是否空数组
add:向数组添加元素
subarray:截取数组
indexOf:查找某个元素的下标
isEquals:比较数组是否相等
toObject:基础类型数据数组转换为对应的Object数组
2、文件流工具类
2.1 org.apache.commons.io.IOUtils
2.2 org.apache.commons.io.FileUtils
2.3 org.apache.commons.io.FilenameUtils
3、字符串工具类
4、对象属性及反射工具类 ...
11、一些命令工具
- javac.exe是编译功能javaCompiler
- java.exe是执行程序,用于执行编译好的.class文件
- javadoc.exe用来制作java文档
- jdb.exe是java的调试器
- javaprof.exe是剖析工具
二、基本语法
1、标识符和关键词的区别
标识符就是一个名字;关键字是被赋予特殊含义的标识符。
2、break 、continue 、return 作用?
- break 跳出总上一层循环,不再执行循环(结束当前的循环体)
- continue 跳出本次循环,继续执行下次循环(结束正在执行的循环,进入下一个循环条件)
- return 程序返回,不再执行下面的代码(结束当前的方法,直接返回)
追问:两层循环内层break会咋样?
...,会导致内层循环立即结束,而外层循环会继续执行下一个迭代。在内层循环结束后,外层循环会继续执行其下一个迭代,而不会受到影响。
例如,以下是一个两层嵌套循环的示例代码:
for i in range(3):
for j in range(3):
if j == 1:
break
print(i, j)
当j等于1时,内层循环的break语句会被执行,导致内层循环立即结束。因此,输出结果将为:
0 0
0 1
1 0
1 1
2 0
可以看到,当j等于1时,内层循环结束,而外层循环继续执行下一个迭代。因此,在j等于1之后的迭代中,外层循环仍然会输出0、1和2这三个值。
3、什么是字节码?采用字节码的好处是?
字节码是一种介于源代码和机器码之间的中间代码。编译器将Java源代码翻译成Java字节码。Java字节码是一种与平台无关的二进制代码,它不面向任何特定的处理器,只面向虚拟机。
采用字节码的好处是:
Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
4、字节序定义以及Java属于哪种字节序(字节、阿里、美团高频)
字节序(Byte Order)是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。在计算机中是以字节为单位,每个地址对应一个字节,一个字节8bit。
通常有小端和大端两种方式。
- 小端:低位字节存放在内存的低地址端,高位字节存放在内存的高地址端。
- 大端:高位字节存放在内存的低地址端,低位字节存放在内存的高地址端。
Java语言的字节序是大端。
5、简述Java中Class对象
Java中对象可以分为实例对象和Class对象,每一个类都有一个Class对象,其包含了与该类有关的信息。
获取Class对象方法:
- 使用对象的getClass()方法。实例对象.getClass(0)
- 使用类的getClass()方法。类名.class
- 使用类的forName()方法。Class.forName(“类的全限定名")
- 使用反射。可以使用Java的反射API来获取一个类的Class对象。例如:Class<?> clazz = Class.forName("com.example.MyClass");
6、数组去重有几种方法?
以下是常见的几种方法:
- 使用Set集合:只需要将数组转换为Set,然后再把Set转换回数组即可去重。
- 使用双重循环:遍历数组中的每个元素,再遍历一次数组,判断当前元素是否与之前的元素重复,如果没有重复则添加到新数组中。
- 使用Stream API:将数组转换为流,利用Stream的distinct()方法移除重复元素,再将结果转换为数组。例如:
int[] arr = {1, 2, 3, 3, 4};
int[] uniqueArr = Arrays.stream(arr).distinct().toArray();
System.out.println(Arrays.toString(uniqueArr)); // [1, 2, 3, 4]
- 使用HashMap:遍历数组,将每个元素作为键存入HashMap中,如果元素已经存在于HashMap中,则说明元素重复,可以被跳过。最后,将HashMap的键集合转换为数组即可得到去重后的数组。
其中,使用Set和Stream API是最为简单和高效的方法,建议优先使用。
7、Java 中对集合对象或者数组对象排序有几种实现方式?
常用的实现方式有以下几种:
- Arrays.sort()方法:用于对数组对象进行排序。该方法使用快速排序算法,时间复杂度为O(n log n)。
- Collections.sort()方法:用于对集合对象进行排序。该方法默认使用归并排序算法,时间复杂度为O(n log n)。
- Comparable接口:该接口定义了对自身进行比较的方法,实现该接口的对象可以通过Collections.sort()方法或Arrays.sort()方法进行排序。该方式适用于对单一属性进行排序的情况。
- Comparator接口:该接口定义了对两个对象进行比较的方法,可以通过Collections.sort()方法或Arrays.sort()方法进行排序。该方式适用于对多个属性进行排序的情况或自定义比较规则的情况。
- Lambda表达式:Java 8引入的Lambda表达式可以方便地实现Comparator接口,减少了代码量和实现复杂度。
以上几种实现方式都可以对集合对象或者数组对象进行排序,根据具体场景和需求选择合适的方式进行排序。比如,当需要根据多个属性进行排序时,可以使用Comparator接口实现自定义的比较规则。当排序操作频繁进行时,可以选择快速排序算法的Arrays.sort()方法。
追问:Comparable 和 Comparator 的区别
Comparable和Comparator都是Java用于对集合对象进行排序的接口,它们的区别在于:
-
Comparable是在类内部实现的接口,用于排序操作时对象自身的默认排序规则,实现该接口后,可以使用Arrays.sort()方法或Collections.sort()方法进行排序。而Comparator是在类外部实现的接口,用于对多个属性进行排序或者实现自定义的排序规则。
-
Comparable接口只有一个compareTo()方法,它比较当前对象和另一个对象的大小,并根据比较结果返回正数、负数或零。而Comparator接口有两种实现方法,一种是compare()方法,直接比较两个对象的大小,另一种是使用Lambda表达式实现的方法。
-
Comparable接口实现后,它所属的类的所有对象都可以默认按照该接口指定的规则排序。而Comparator接口在进行排序时,可以动态地指定所使用的比较器,从而实现不同的排序规则。
综上所述,Comparable接口适用于需要默认排序规则的情况,而Comparator接口适用于多种排序规则的情况。在实际使用中,根据具体需求选择合适的接口和实现方式进行排序操作。
8、数组和链表的区别
- 存储方式:
- 数组是一种 静态的 数据结构,它存储在连续的内存空间中,因此可以通过索引直接访问任何元素。
- 链表是一种 动态的 数据结构,它由多个节点组成,每个节点包含数据和指向下一个节点的引用,因此需要通过遍历来访问元素。
- 内存分配方式:
- 数组在创建时需要指定大小,因此会一次性分配内存空间。
- 链表不需要预先分配内存空间,可以随时添加或删除节点。
- 插入和删除操作:
- 在数组中插入或删除元素需要进行数据移动,时间复杂度为O(n),因为需要移动所有后面的元素。
- 链表中插入或删除元素只需要修改节点之间的引用,时间复杂度为O(1)。
- 存储容量:
- 数组的存储容量是固定的,不能动态扩展。
- 链表的存储容量可以动态扩展,可以根据需要随时添加或删除节点。
- 查找操作:
- 在数组中查找元素可以通过索引直接访问,时间复杂度为O(1)。
- 链表中查找元素需要遍历整个链表,时间复杂度为O(n)。
9、数组中插入时空间不足会怎样?
在Java中,当你试图在数组中插入元素而空间不足时,会抛出一个ArrayIndexOutBoundException(数组越界异常),因为数组的大小是固定的。如果你需要动态调整数组的大小,可以使用Java中的ArrayList类,它会自动为你调整数组的大小。
10、数字与字符串相加得到什么结果?【笔试】
在遇到String类型之前,int间使用“+”还是表示数值的相加,但是遇到第一个String后,后面就都是按照String类型来,变成字符串的拼接。
System.out.println(1+"10"+3+"2");
得到:11032
System.out.println(1+2+"10"+3+"2");
得到:31032
三、基本数据类型
1、Java基本数据类型有哪些?
Java数据类型(基本数据类型 + 引用数据类型);基本数据类型(数值型、字符型、布尔型),引用数据类型(类、接口、数组)
| 基本数据类型 | 字节数 | 位数 | 默认值 | 取值范围 |
|---|---|---|---|---|
| byte | 1 | 8 | 0 | -128 ~ 127(-2^7~2^7-1) |
| short | 2 | 0 | -2^15~2^15-1 | |
| int | 4 | 0 | -2^31~2^31-1 | |
| long | 8 | 0L | −2^63~2^63−1 | |
| char | 2 | 'u0000' | 0~2^16−1 | |
| float | 4 | 0f | -2^127~2^127-1(不准) | |
| double | 8 | 0d | −2^1023~2^1023-1(不准) | |
| boolean | 1 | false | true、false |
注意:
- char:单引号,String:双引号。
- 这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean
- 在Java规范中,没明确指出 boolean大小。
基本类型与引用类型两者区别:
- 存储位置
- 基本类型:在方法中定义的非全局基本数据类型变量的具体内容是储存在栈中的。
- 引用类型:引用类型变量具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址。
- 传递方式:
- 基本类型:在方法中定义的非全局基本数据类型变量,调用方法时作为参数时按数值传递的。
- 引用类型:引用数据类型变量,调用方法时作为参数是按引用传递的,传递的是引用的副本。
2、值传递和引用传递
- 值传递是将实际参数的值复制一份,传递给函数的形式参数,函数中对形式参数的修改不会影响到实际参数的值。
- 引用传递是将实际参数的地址或引用传递给函数的形式参数,函数中对形式参数的修改会影响到实际参数的值。
在Java中,基本数据类型的参数传递采用值传递,而对象参数的传递采用引用传递。简而言之,值传递和引用传递的区别在于传递的是实际参数的值还是地址(或引用),对形参的修改是否会影响实参。
3、Java是按值传递还是按引用调用?
- 按值调用表示方法接收的是调用者提供的值。
- 按引用调用表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。
Java是「按值调用」。当传的是基本类型时,传的是值的拷贝,对拷贝变量的修改不影响原变量;当传的是引用类型时,传的是引用地址的拷贝,但是拷贝的地址和真实地址指向的都是同一个真实数据,因此可以修改原变量中的值😓
在 Java 中,方法参数共有两种类型:
- 基本数据类型:一个方法【不能】修改一个基本数据类型的参数
- 引用数据类型:一个方法【可以】改变一个引用数据类型的参数
补充:栈内存中存放局部变量(基本数据类型和对象引用),而堆内存用于存放对象(实体)。
4、深拷贝和浅拷贝了解吗?什么是引用拷贝
-
浅拷贝: 浅拷贝是指仅仅拷贝对象的引用而不是对象本身,也就是说,原对象和拷贝对象会共享同一个引用,任何一方的变化都会影响到另一方。在Java中,可以使用clone()方法实现浅拷贝。
-
深拷贝: 深拷贝是指拷贝对象本身,而不是对象引用,也就是说,原对象和拷贝对象互不影响,它们的属性和数据都是独立的。在Java中,可以通过序列化和反序列化、使用ObjectInputStream和ObjectOutputStream实现深拷贝。
深浅拷贝的区别:
- 浅拷贝只是拷贝对象的引用,两个引用指向同一个对象;而深拷贝是创建一个新的对象,拷贝原始对象中的所有字段和属性,并且创建新对象的内存地址和原始对象不同。
- 浅拷贝对属性是基本数据类型的对象拷贝符合预期,但是对于属性是引用数据类型的对象拷贝,原始对象和拷贝对象会共享同一个引用,任何一方的变化都会影响到另一方;深拷贝不仅能拷贝基本数据类型的属性,还能递归拷贝引用类型的属性,从而真正实现对象拷贝。
- 深拷贝相对于浅拷贝速度较慢并且花销较大。
因此,在Java开发中,需要根据实际需求来选择使用浅拷贝还是深拷贝。如果对象的属性中包含了大量的引用类型数据,那么建议使用深拷贝;如果只是简单的基本数据类型,浅拷贝就足够了。
引用拷贝:引用拷贝就是拷贝引用地址,两个不同的引用指向同一个对象。引用拷贝分为深拷贝和浅拷贝。
5、基本类型和包装类型的区别
- 基本类型是预定义在Java语言中的8种数据类型,而包装类型是为了方便操作基本类型而引入的数据类型。
- 基本类型是值类型,可以直接存储数据值,而包装类型是引用类型,需要使用new关键字实例化对象,才能使用对应的方法。
- 基本类型在内存中占用的空间大小是确定的,而包装类型由于是对象,需要在内存中为对象本身和各种状态(包括标识符和其他属性)分配空间,因此占用的空间比基本类型大。
- 基本类型不能为null,包装类型可以为null。
- 基本类型可以直接进行算术计算,而包装类型需要通过valueOf()等方法转换为基本类型后再进行计算。
- 基本类型存储在栈内存中,而包装类型存储在堆内存中。
为啥要有包装类?
Java是一个面向对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。使用包装类型可以更加灵活地操作数据。
6、包装类型的缓存机制了解么?
Java包装类型的缓冲机制是指,在一定范围内,Java会自动缓存一些常用的包装类型对象,以便于重复利用,提高性能和节约内存。
Java的包装类型缓存范围是在 -128 到 127 之间的整数,包括对应的byte、short、char、int、long、boolean六种包装类型和BigInteger、BigDecimal两个类(JDK6及以前只用于byte、short、char、int、long五种包装类型的缓存)。当在这个范围内创建一个包装类型对象时,Java会先检查对应的缓存中是否已经存在这个值对应的对象,如果已经存在,则直接返回该对象的引用(前提:发生自动装箱的过程),否则会新创建一个对象并存储到缓存中,以供之后的使用。
Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回
TrueorFalse。Float,Double,Boolean 三大包装类并没有缓冲机制。
这种缓冲机制可以通过调用包装类型的 valueOf() 方法来利用,比如:
Integer i1 = Integer.valueOf(10);
Integer i2 = Integer.valueOf(10);
System.out.println(i1 == i2); // true,因为10在缓存范围内,i1和i2指向的是同一个对象
注意,当包装类型的值超过了缓存范围时,Java就不再使用缓存,而是直接创建一个新的对象。因此,如果要比较两个包装类型的值是否相等,建议使用 equals() 方法,而不是 == 操作符。
参考链接:
7、Java中包装类型的创建的方法
- 直接赋值:使用包装类型的构造函数,直接传入对应的基本类型值或字符串进行初始化。(已被JDK废弃,不建议使用,因为自动装箱已经能够很好地实现相同的功能)
Integer i1 = new Integer(10); Integer i2 = new Integer("20"); - 自动装箱:将基本类型自动转换为对应的包装类型。
Integer i3 = 30; - valueOf()方法:该方法是包装类型的静态方法,可以将基本类型值或字符串转化成对应的包装类型对象,底层实现其实就是自动装箱。(使用valueOf()方法创建包装类型对象则会尽可能地适配到缓存中,减少对象的新建操作,并且它具有更高的效率和可读性)
Integer i4 = Integer.valueOf(40); Integer i5 = Integer.valueOf("50");
区别:上述创建方法的主要差异在于构造过程中是否需要发生自动装箱的过程。
8、自动装箱与拆箱了解吗?原理是什么?
- 自动装箱:将基本类型用它们对应的引用类型包装起来;
- 自动拆箱:将包装类型转换为基本数据类型;
自动装箱:当需要将一个基本数据类型赋值给该类型的包装类对象时,Java编译器会自动调用包装类的valueOf()方法进行自动装箱处理。
int i = 10;
Integer j = Integer.valueOf(i); // 自动装箱
自动拆箱:当需要将一个包装类型的对象赋值给一个基本数据类型时,Java编译器会自动调用包装类的xxxValue()方法进行自动拆箱处理。
Integer i = 10;
int j = i.intValue(); // 自动拆箱
9、int和Integer的区别?int a=1, new Integer(1)是否相等?
- int是Java中的基本数据类型,而Integer是包装类,它是int的封装类;
- int占用4个字节的内存,而Integer占用更多的内存空间;
- int的默认值是0,而Integer的默认值(初始值)是null;
- int可以直接参与数学运算,而Integer需要使用intValue()方法进行转换;
- int在内存中直接存储的是数据值,而Integer实际存储的是对象引用,当new一个 Integer 时实际上是生成一个指针指向此对象;
- int可以直接使用"=="进行比较,而Integer需要使用equals()方法进行比较;
当一个int类型的值直接与一个Integer类型的对象作比较时,Java会将Integer对象自动拆箱成int类型,然后再比较两者是否相等。但是当使用Integer对象之间进行比较时,则需要使用equals()方法,因为这是一种安全的比较方式。
int a = 1;
Integer b = new Integer(1);
System.out.println(a == b); // true,自动装箱拆箱后相等
System.out.println(b.equals(a)); // true, 两个对象值相等
在上面的代码中,a和b的值相等,两者本质上是相等的。但是当使用“==”判断时,因为int与Integer进行比较时,Java会自动将Integer对象拆箱成int类型,所以a与b会被自动拆箱并转化为int类型进行比较,所以是相等的。 而当使用equals()方法进行比较时,Java会自动将int类型的a装箱成一个Integer对象,在进行比较,所以也是相等的。
补充:
-
Integer a = new Integer(10); Integer b = new Integer(10); a == b? //false,此时ab指向的是integer对象的地址 -
Intger a = new Integer(10); int b = 10; a == b? //true,java会自动拆箱valueOf方法 //实际上比较的就是两个int变量,只要值相等就相等。 -
Integer a = new Integer(10); Integer b = 10; a == b? //false,a是new生成的Integer变量,指向堆中新建的对象 //b是非new生成的integer变量,指向Java常量池中的对象,两者在内存中地址不同。 -
Integer a = 10; Integer b = 10; a == b? //true,直接声明Integer a = 10时,Java会自动装箱为Integer a = Integer.valueOf(10); //当a的范围在(-128,127)之内时,第一次声明会将放入缓存中,第二次取缓存中已有的对象,而不是重新创建一个Integer对象 //(-128,127)范围内的Integer值可以直接用==判断,但这个区间外的所有数据,都会在堆上产生,不会复用已有对象。 -
Integer a = 128; Integer b = 128; a == b? //false,此时a和b不在(-128,127)内,都会在堆上新产生一个对象。 -
int a = 10; Integer b = 10; a == b? //true,根据integer的自动拆箱。 -
int a = 128; Integer b = 128; a == b? //true,根据integer的自动拆箱。
总结:
- int和int之间,用==比较,为true。基本数据类型没有equals方法。
- int和Integer比较,Integer会自动拆箱,== 和 equals都肯定为true。
- int和new Integer比较,Integer会自动拆箱,调用intValue方法, 所以 == 和 equals都肯定为true
- Integer和Integer进行==比较的时候,在[-128,127]区间的时候,为true。不在这个区间,则为false;进行equals比较的时候,由于Integer的equals方法进行了重写,比较的是内容,所以为true
- Integer和new Integer 进行==比较的话,为false ;进行equals比较的话,为true
- new Integer和new Integer进行==比较的时候,为false ; 进行equals比较,为true。原因是new的时候,会在堆中创建对象,分配的地址不同,==比较的是内存地址,所以肯定不同。
补充:Byte、Short、Integer、Long这几个类的valueOf方法实现类似的。所以在[-128,127]区间内,==比较的时候,值总是相等的(指向的是同一对象),在这个区间外是不等的。而Float和Double则不相等, Boolean的值总是相等的。
10、Integer生成的两个对象可以用“==”直接进行比较吗?
Integer是可以使用==进行比较的也可以用.equals()比较,注意两个Integer的值在-128 ~ 127之内时可以用==比较。两个Integer的值不在-128~127之内时,必须用b.equals(a)进行比较。
11、Integer在拆包过程中有什么要注意?
在integer对象拆箱的过程中,如果装箱时的值是null,会报NullPointerException异常
范围检查:拆包操作时要检查对象是否在基本类型所能表示的范围内,否则可能会导致数值溢出。
注意精度损失:拆包操作时要注意精度损失的问题。例如,将一个大于基本类型所能表示的最大值的Integer对象进行拆包操作,将会得到一个不可预期的结果
12、switch是否能作用在byte上,是否能作用在long上,是否能作用在String上?
switch可以作用在byte、short、char、int类型上,但不能作用在long、float、double以及String类型上(Java7开始可以)。对于String类型可以通过使用字符串常量或枚举类型的方式进行替代。
switch可以作用在以下几种类型上:
- byte、short、char或int基本类型:在case子句中使用这些基本类型的常量值来匹配相应的值。
- 枚举(enum)类型:可以使用枚举类型的常量值来匹配相应的值。
- String类型(从Java 7开始):可以使用字符串类型的常量值来匹配相应的值。
需要注意的是,switch语句中的值必须是常量或不可更改的变量,不能是变量或表达式,否则会导致编译错误。此外,从Java 12版本开始,switch语句增加了对类类型的支持,可以使用类类型的模式匹配相应的值。
13、for each循环
for each循环,也称为增强型for循环。它可以用于遍历数组或集合中的元素,而不需要使用传统的for循环和索引变量来访问元素。
//type:数据类型;variable:变量名;collection:要遍历的集合
for(type variable : collection){
//todo
}
for each循环可以用于哪些类型的集合?——for each循环可以用于任何实现了Iterable接口的集合,包括数组、List、Set、Map等。
for each循环和传统的for循环有什么区别?——for each循环相对于传统的for循环,更加简洁和易读。它不需要使用索引变量来访问集合中的元素,而是直接遍历集合中的每个元素。此外,for each循环不支持修改集合中的元素,因为它只是一个只读的循环结构。
for each循环的性能如何?——for each循环的性能通常比传统的for循环略低,因为它需要在每次循环中调用集合的迭代器方法。但是,这种性能差异通常可以忽略不计,因为它只在大型集合中才会有所影响。
14、能否在foreach循环里调用ArrayList的remove方法,为什么?
不建议在foreach循环里直接调用ArrayList的remove方法。在foreach循环中,迭代器与ArrayList实例绑定,remove方法也会通过迭代器来删除元素。如果在foreach循环过程中直接调用ArrayList的remove方法,会导致迭代器失效,可能会产生一些意外的结果。
例如,以下代码中,使用了foreach循环和ArrayList的remove方法来删除元素:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
for (Integer integer : list) {
if (integer % 2 == 0) {
list.remove(integer);
}
}
上述代码在遍历到第二个元素时会抛出ConcurrentModificationException异常,因为在删除元素时导致了迭代器的失效。如果要删除集合中的元素,应该使用迭代器的remove方法来删除元素,例如:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer % 2 == 0) {
iterator.remove();
}
}
这种方式能够正确地删除集合中的元素,而不会使迭代器失效。如果确实需要在foreach循环中删除元素,可以使用Iterator来遍历集合。
15、a = a + b与a += b的区别?
+= 操作符会进行隐式自动类型转换,将+操作的结果类型强制转换为持有结果的类型。如byte、short或者 int,首先会将它们提升到int类型,然后再执行加法操作。而a = a + b则不会自动进行类型转换。
byte a = 127;
byte b = 127;
b = a + b; // error:cannot convert from int to byte
b += a; // ok
(因为a+b操作会将a、b提升为int类型,所以将int类型赋值给byte就会编译出错)
16、i++与++i
- ++i 先加1,再把结果赋值给左边变量;
- i++ 先把变量i赋值给左边变量,再加1;
- ++i和i++都是分两步完成的。
- n=++i,操进后,n的值发生了改变,其值变成了i+1。
- n=i++,操作后,n的值不发生改变,其值仍然为i。
17、3 * 0.1==0.3将会返回什么?true还是false?
false,因为有些浮点数不能完全精确的表示出来。
四、变量
1、全局变量和局部变量的区别
- 成员变量是在类的范围里定义的;局部变量是在方法里定义的
- 成员变量有默认初始值;局部变量无
- 成员变量可被public,private,static等修饰符修饰;局部变量不能被访问修饰符及static修饰
- 未被static修饰的成员变量也叫实例变量,它存储于对象所在的堆内存中,生命周期与对象相同;被static修饰的成员变量也叫类变量,它存储于方法区中,生命周期与当前类相同。
- 局部变量存储于栈内存中,作用的范围结束,变量空间会自动的释放。
注意事项:Java中没有真正的全局变量,面试官应该是出于其他语言的习惯说全局变量的,他的本意应该是指成员变量。
2、静态变量与实例变量的区别
-
静态变量(类变量)是通过static修饰的成员变量;实例变量是没有static修饰的成员变量。
-
静态变量属于类所有,只要程序加载了类的字节码,静态变量就会被分配空间,就可以直接使用;实例变量是对象的属性,只有实例化对象之后,才会被分配空间,才能使用(即需要new一个才能使用)。
- 静态变量运行时,Java 虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配。
- 实例变量每创建一个实例,Java 虚拟机就会为实例变量分配一次内存。
-
静态变量是所有对象共有,其中一个对象将它值改变,其他对象得到的就是改变后的结果;而实例变量则属对象私有,某一个对象将其值改变,不影响其他对象;
-
静态变量:在类的内部,可以在任何方法内直接访问静态变量。在其他类中,可以通过类名访问该类中的静态变量。
-
实例变量:在类的内部,可以在非静态方法中直接访问实例变量。在本类的静态方法或其他类中则需要通过类的实例对象进行访问。
-
3、字符型常量和字符串常量的区别
- 字符常量单引号引起的1个字符,字符串常量双引号引起的0个或多个字符;
- 字符常量相当于一个整型值(ASCII值),可参加表达式运算,字符串常量代表一个地址值(该字符串在内存中存放位置)。
- 字符常量只占2个字节,字符串常量占若干个字节。