1、JDK 和 JRE 有什么区别?
jdk:java development kit
jre:java runtime Environment
jdk是面向开发人员的,是开发工具包,包括开发人员需要用到的一些类。
jre是java运行时环境,包括java虚拟机等,是提供给使用java的人用的
2、== 和 equals 的区别是什么?
在Java中,==
和equals()
方法都是用于比较两个对象的,但它们之间存在一些重要的区别。
-
定义:
==
:这是一个运算符,用于比较两个值是否相等。对于基本数据类型,它比较的是值;对于引用类型,它比较的是引用(即内存地址)是否相同。equals()
:这是一个方法,定义在java.lang.Object
类中,并由许多类(如String
、Integer
等)进行重写以提供特定的比较逻辑。它通常用于比较两个对象的内容是否相等。
-
比较的对象:
==
:对于基本数据类型,它比较的是两个值是否相等;对于引用类型,它比较的是两个引用是否指向同一个对象(即内存地址是否相同)。equals()
:它总是用于比较两个对象的内容是否相等。在Object
类中,默认的equals()
方法实际上与==
运算符对于引用类型的比较行为是相同的,即比较的是引用是否相同。但是,许多类(如String
、Integer
等)都重写了equals()
方法以提供特定的比较逻辑。
-
运行速度:
- 一般来说,
==
运算符的速度比equals()
方法快,因为它只是比较两个值或引用是否相同,而不涉及任何方法调用或复杂的比较逻辑。但是,这种差异在大多数情况下是可以忽略的,除非你正在处理大量的比较操作。
- 一般来说,
-
用途:
==
:适用于基本数据类型的值比较和引用类型的引用比较。equals()
:通常用于比较两个对象的内容是否相等。当你需要比较两个对象的内容是否相同时(而不是它们是否指向同一个对象),你应该使用equals()
方法。但是,请注意,由于equals()
方法是由各个类进行重写的,因此不同的类可能有不同的比较逻辑。在使用equals()
方法之前,你应该先了解该方法的实现方式以及它如何比较对象的内容。
总的来说,==
和equals()
方法都是用于比较两个对象的,但它们的定义、比较的对象、运行速度和用途都有所不同。在选择使用哪个进行比较时,你应该根据你的具体需求来选择。
3、两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
在Java中,hashCode()
和 equals()
方法有特定的约定,但这些约定并不包括“如果两个对象的 hashCode()
相同,则 equals()
也必须为 true
”。以下是这些约定的总结:
- 如果两个对象根据
equals(Object obj)
方法是相等的,那么调用这两个对象中任一对象的hashCode
方法都必须产生相同的整数结果。 - 如果两个对象根据
equals(java.lang.Object)
方法是不相等的,那么调用这两个对象中任一个对象的hashCode
方法不一定要产生不同的整数结果。但是,程序员应该意识到为不相等的对象生成不同整数可能会提高哈希表的性能。
换句话说,hashCode()
方法的主要目标是为不相等的对象生成不同的哈希码,以提高哈希表的性能。但是,相等的对象必须有相同的哈希码。
然而,由于哈希码的空间(通常是 int
类型的大小,即 2^32 或 4294967296 个不同的值)远小于对象可能的空间,因此必然存在多个不同的对象具有相同的哈希码(这种情况称为哈希冲突)。因此,不能仅凭两个对象的哈希码相同就断定它们相等。
4、final 在 java 中有什么作用?
在Java中,final
关键字具有多种用途,主要作用包括:
- 修饰类:当一个类被
final
修饰时,它表示这个类是不能被其他类继承的。也就是说,这个类是一个“最终类”,没有子类。这通常用于设计那些功能已经足够完善、不需要被扩展的类。 - 修饰方法:如果一个方法被
final
修饰,那么这个方法就不能被子类重写(Override)。这可以确保子类不会修改父类中该方法的行为。这在某些情况下是很有用的,比如当我们想要确保某个方法的特定实现不被改变时。 - 修饰变量:
final
修饰的变量就是常量,只能被赋值一次,赋值后值不再改变。这有助于我们创建一些在程序运行期间不会改变的变量,如数学中的常数、物理公式中的常量等。需要注意的是,final
修饰的是变量的引用,而不是引用指向的内容。也就是说,如果final
变量引用的是一个对象,那么这个对象的内容是可以被修改的,但是你不能让final
变量引用另一个对象。
此外,final
关键字还可以用于修饰局部变量,尤其是在函数参数列表中。当使用final
修饰一个形参时,意味着这个形参是一个常量,在函数体内部不能被重新赋值。
总的来说,final
关键字在Java中主要用于确保类的不可继承性、方法的不可重写性以及变量的不可变性,从而增强代码的稳定性和可维护性。
5、JAVA 中的 Math.round(-1.5) 等于多少?
-1
向上取整Math.ceil();
向下取整Math.floor();
四舍五入Math.round(x); //==Math.floor(x+0.5)
6、String 属于基础的数据类型吗?
在Java中,String
不属于基础的数据类型(primitive data types)。Java有8种基础数据类型,它们分别是:
byte
:字节型,用于存储8位整数。short
:短整型,用于存储16位整数。int
:整型,用于存储32位整数。long
:长整型,用于存储64位整数。float
:单精度浮点型,用于存储32位IEEE 754单精度浮点数。double
:双精度浮点型,用于存储64位IEEE 754双精度浮点数。char
:字符型,用于存储Unicode字符。boolean
:布尔型,用于存储true或false。
String
是一个类(class),它属于Java的引用类型(reference types)。它用于存储一系列的字符(即文本),并提供了一系列的方法来操作这些字符。由于String
是对象,所以它在内存中通过引用来被访问和操作。
7、java 中操作字符串都有哪些类?它们之间有什么区别?
在Java中,操作字符串主要有三个类:String
、StringBuffer
和StringBuilder
。它们之间的主要区别如下:
- String:
- 特性:
String
类表示不可变的字符序列。一旦创建了一个String
对象,其内容就不能被修改。任何对字符串的操作(如截取、连接、替换等)都会返回一个新的String
对象,而原始的字符串对象不会受到影响。 - 线程安全:
String
是线程安全的,可以在多线程环境中安全地使用。 - 存储方式:在JDK 8及之前的版本中,
String
底层使用char[]
数组来存储数据;而在JDK 9及之后的版本中,它底层使用byte[]
数组。 - 性能:由于
String
的不可变性,对字符串进行大量操作(如连接)时可能会生成许多新的字符串对象,这可能会影响性能。
- 特性:
- StringBuffer:
- 特性:
StringBuffer
类表示可变的字符序列,与String
的不可变性形成对比。这意味着你可以修改StringBuffer
对象的内容,而不需要每次都创建新的对象。 - 线程安全:
StringBuffer
是线程安全的,适用于多线程环境。它通过同步方法来确保线程安全。 - 存储方式:与
String
类似,在JDK 8及之前的版本中,StringBuffer
底层使用char[]
数组来存储数据;而在JDK 9及之后的版本中,它底层使用byte[]
数组。 - 性能:由于
StringBuffer
是可变的,并且适用于大量字符串操作,因此在某些情况下,它可能比String
更有效率,因为它避免了不必要的对象创建。但是,由于线程安全的需要,它在某些情况下可能会比StringBuilder
慢。
- 特性:
- StringBuilder:
- 特性:
StringBuilder
类也表示可变的字符序列,与String
的不可变性不同。与StringBuffer
类似,你可以修改StringBuilder
对象的内容而不需要每次都创建新的对象。 - 线程安全:与
StringBuffer
不同,StringBuilder
不是线程安全的。这意味着它不能在多线程环境中安全地使用,但这也使得它在单线程环境中通常比StringBuffer
更快。 - 存储方式:与
String
和StringBuffer
类似,在JDK 8及之前的版本中,StringBuilder
底层使用char[]
数组来存储数据;而在JDK 9及之后的版本中,它底层使用byte[]
数组。 - 性能:由于
StringBuilder
是可变的并且不是线程安全的,因此它在单线程环境中通常比StringBuffer
和String
更高效,因为它避免了不必要的同步和对象创建。
- 特性:
8、String str="i"与 String str=new String("i")一样吗?
不一样。他们不是同一个对象
前者如果定义多个变量都为相同值的话,会共用同一个地址,创建的对象应该放在了常量池中;
后者是创建了一个新的对象,放在的是堆内存中。
9、如何将字符串反转?
方法1:使用StringBuilder
或StringBuffer
由于StringBuilder
和StringBuffer
是可变的字符序列,它们提供了reverse()
方法可以直接反转字符串。
public class ReverseString {
public static void main(String[] args) {
String original = "Hello, World!";
StringBuilder sb = new StringBuilder(original);
sb.reverse();
String reversed = sb.toString();
System.out.println(reversed); // 输出 "!dlroW ,olleH"
}
}
方法2:使用双指针
如果不使用StringBuilder
或StringBuffer
,你也可以通过双指针的方式来反转字符串。这种方法需要创建一个新的字符数组,并通过从字符串的两端向中间遍历来交换字符。
public class ReverseString {
public static void main(String[] args) {
String original = "Hello, World!";
char[] chars = original.toCharArray();
int left = 0;
int right = chars.length - 1;
while (left < right) {
char temp = chars[left];
chars[left] = chars[right];
chars[right] = temp;
left++;
right--;
}
String reversed = new String(chars);
System.out.println(reversed); // 输出 "!dlroW ,olleH"
}
}
方法3:使用Java 8的流(Streams)
虽然使用流来反转字符串可能不是最高效的方法,但它展示了Java 8流API的灵活性。注意,这种方法在性能上可能不如前两种方法。
public class ReverseString {
public static void main(String[] args) {
String original = "Hello, World!";
String reversed = IntStream.range(0, original.length())
.mapToObj(original::charAt)
.collect(Collectors.toList())
.stream()
.sorted((a, b) -> original.length() - original.indexOf(b) - original.length() + original.indexOf(a))
.collect(Collectors.joining());
System.out.println(reversed); // 输出 "!dlroW ,olleH"
}
}
10、String 类的常用方法都有那些?
"".toCharArray("");
"".charAt();
"".split();
"".indexOf();
"".equals();
"".contains();
"".length();
"".subString("");
"".replace("","");
11、抽象类必须要有抽象方法吗?
不是。抽象类可以没有抽象方法,但是如果你的一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化,即不能直接构造一个该类的对象。如果一个类中有了一个抽象方法,那么这个类必须声明为抽象类,否则编译通不过。
12、普通类和抽象类有哪些区别?
普通类可以被继承,不能包含抽象方法,不可以被实现。
抽象类也可被继承,但是子类必须要实现父类中的抽象方法;
抽象类中的方法不能包含主体。
抽象类中的方法在扩展性和延伸性要比普通类的更好;
抽象类可以应用多态,普通类不可以。
13、抽象类能使用 final 修饰吗?
在Java中,final
关键字用于表示一个类、方法或变量是不可变的。当 final
修饰一个类时,这个类就不能被继承。而抽象类(abstract
类)的主要目的是为了被其他类继承,并实现其中的抽象方法。
由于 final
类的特性(即不能被继承)与抽象类的特性(即需要被继承以实现其中的抽象方法)是相互矛盾的,因此Java不允许使用 final
修饰抽象类。
如果你尝试使用 final
修饰一个抽象类,编译器会报错。
14、接口和抽象类有什么区别?
接口是要被实现的,抽象类是要被继承;
接口用interface修饰;抽象类使用abstract修饰;
两者均不能被实例化,方法都不包含主体;
一个类只能继承一个抽象类,但是可以实现多个接口。
15、java 中 IO 流分为几种?
字节流:InputStream、OutputStream
字符流:Reader、Writer
字节流是最基本的
- 字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
- 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
读文本的时候用字符流,例如txt文件。读非文本文件的时候用字节流,例如mp3。
16、BIO、NIO、AIO 有什么区别?
BIO:Block IO 同步阻塞式 IO
NIO:Non IO 同步非阻塞 IO
AIO:Asynchronous IO 异步非阻塞IO
BIO是一个连接一个线程。JDK4之前的唯一选择
NIO是一个请求一个线程。JDK4之后开始支持,常见聊天服务器
AIO是一个有效请求一个线程。JDK7之后开始支持,常见相册服务器
17、重写和重载的区别?
重载:必须有不同的参数列表;可以有不同的访问修饰符;可以抛出不同的异常;
重写:参数列表必须要与被重写的相同;返回的类型必须保持一致;修饰符和抛出的异常不能在被重写的方法之外
重写是父类与子类的关系,是垂直关系;重载是同一个类方法中的关系,是水平关系。
18、什么是多态 ?
在Java中,**多态(Polymorphism)**是一种面向对象编程的重要特性,它允许我们以统一的方式处理不同类型的对象。多态意味着“多种形态”,它允许我们将子类对象当作父类对象处理,这样,在执行过程中,多态就会根据传入的实际参数的类型(即对象的实际类型)来调用相应的方法。
Java中的多态主要有两种形式:
- 方法的重载(Overloading):在同一个类中,可以有多个方法具有相同的名称,但参数列表(参数的类型、个数或顺序)不同。这样,在调用方法时,JVM会根据传入的参数列表来确定应该调用哪个方法。这可以看作是编译时的多态。
- 方法的重写(Overriding):在子类中,可以重写父类中的方法(即子类中的方法与父类中的方法具有相同的名称、参数列表和返回类型)。这样,在创建子类对象并调用该方法时,就会执行子类中的方法,而不是父类中的方法。这可以看作是运行时的多态,也是Java中最常用的多态形式。
实现多态的三个必要条件是:
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 父类引用指向子类对象:多态性就是父类类型的引用变量可以指向子类对象,并执行子类重写后的方法