文章目录
- 1.Java中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节?
- 2.String、StringBuffer和StringBuilder的区别是什么?String为什么是不可变的?
- 3.String s1 = new String(“abc”); 这段代码创建了几个字符串对象?
- 4. == 与 equals ?hashCode 与 equals ?
- 5.包装类型的缓存机制是什么?
- 6.自动装箱与拆箱是什么? 原理是什么?
- 7.深拷贝和浅拷贝的区别?什么是引用拷贝?
- 8.Java注解的作用?
- 9.Exception 和 Error 有什么区别?
- 10.Java泛型是什么?什么是类型擦除?说一下常用的通配符?
- 11.内部类了解吗?匿名内部类了解吗?
- 12.Java反射是什么,有什么优缺点,怎么理解?
- 13.BIO,NIO,AIO有什么区别?
1.Java中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节?
Java中有8种基本数据类型
byte 8位 1字节 包装类:Byte 默认值0 -128~127
short 16位 2字节 包装类:Short 默认值0 -32768(-2^15)~ 32767(2^15-1)
int 32位 4字节 包装类:Integer 默认值0 -2147483648 ~ 2147483647
long 64位 8字节 包装类:Long 默认值0L -9223372036854775808(-2^63) ~ 9223372036854775807(2^63 -1)
char 16位 2字节 包装类:Character 默认值'u0000' 0 ~ 65535(2^16-1)
float 32位 4字节 包装类:Float 0f 1.4E-45 ~ 3.4928235E38
double 64位 8字节 包装类:Double 0d 4.9E-324 ~ 1.7976931348623157E308
boolean 1位 包装类:Boolean false true、false
2.String、StringBuffer和StringBuilder的区别是什么?String为什么是不可变的?
区别
可变性
- String:不可变。
- StringBuilder 和 StringBuffer 都继承自 AbstractStringBuilder类,类中用 char[] 字符数组来保存字符串,没有使用 final 和 private 关键字,所以这两个可变。
线程安全性
- String 中的对象不可变,可以看作是常量,线程安全。
- StringBuffer 线程不安全,没有对方法进行加同步锁。
- StringBuilder 线程安全,对方法加了同步锁或者对调用的方法加了同步锁,所以线程安全。
性能
- 每次操作 String 都会生成新 String 对象,并将指针指向新的 String对象
- StringBuffer 和 StringBuilder 每次都会对对象本身操作,StringBuilder 比 StringBuffer 性能提升 10% ~ 15% 左右。
使用场景
- 操作少量数据:String
- 单线程操作大量数据:StringBuilder
- 多线程操作大量数据:StringBuffer
3.String s1 = new String(“abc”); 这段代码创建了几个字符串对象?
- 两个;如果字符串常量池中不存在字符串对象“abc” 的引用,那么它会在堆上创建2个字符串对象,其中一个字符串对象的引用会背保存在字符串常量池中。
- 一个;如果字符串常量池中已存在字符串对象“abc” 的引用,则只会在堆中创建1个字符串对象“abc”。
4. == 与 equals ?hashCode 与 equals ?
== 对于基本类型和引用类型的作用效果不同。
- 对于基本类型和引用类型,==比较的是值。
- 对于引用数据类型来说,==比较的是对象的内存地址。
因为Java中只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals() 方法存在于 Object 类中,而 Object 类是所有类的直接与间接父类,因此所有的类都有 equals() 方法。 Object 类 equals() 方法
public boolean equals(Object obj) {
return (this == obj);
}
equals() 方法存在两种使用情况
- 类没有重写 equals() 方法: 通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object 类 equals() 方法。
- 类重写了 equals() 方法: 一般我们都重写 equals() 方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回true,认为这连个对象相等。
常见案例
String a = new String("ab"); // a 为一个引用
String b = new String("ab); // b 为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa == bb); // true
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
System.out.println(42 == 42.0); // true
String 中 equals 方法是被重写过的,因为 Object 里比较的是内存地址,String 里比较的是值。以下是 String 类中的 equals() 方法源码。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
hashCode() 有什么用?
hashCode() 的作用是获取海马(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。 hashCode() 定义在JDK的 Object 类中,也就以为着 Java 中任何类都包含有 hashCode() 函数。 总结:
- 如果两个对象的 hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
- 如果两个对象的 hashCode 值相等并且 equals() 方法也返回 true ,我们才认为这两个对象相等。
- 如果两个对象的 hashCode 值不相等,我们可以直接认为这两个对象不相等。
为什么重写 equals() 必须重写 hashCode?
- equals 方法判断两个对象相等,那 hashCode 也要相等。
- 两个对象有相同的 hashCode ,他们也不一定是相等的。
5.包装类型的缓存机制是什么?
- Byte、Short、Integer、Long 这4种包装类型默认创建了数值[-128,127]的相应类型的缓存数据。
- Character 创建了数值在[0,127]范围的缓存数据。
- Boolean 直接返回 True or False。
6.自动装箱与拆箱是什么? 原理是什么?
- 装箱:将基本类型用它们对应的引用类型包装起来。
- 拆箱:将包装类型转换为基本数据类型。
Integer i = 10; // 装箱
int n = i; // 拆箱
- Integer i = 10 等价于 Integer i = Integer.valueOf(10);
- int n = i 等价于 int n = i.initValue();
- 在实际应用中,应避免频繁拆装箱操作。
7.深拷贝和浅拷贝的区别?什么是引用拷贝?
- 浅拷贝:在堆上创建一个新的对象,如果原对象内部属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说浅拷贝对象和源对象公用一个内部对象。
- 深拷贝:完全复制整个对象,包括这个对象所包含的内部对象。
浅拷贝实例代码,实现 Cloneable 接口,并重写 clone() 方法。 clone() 方法实现直接调用父类 Object 的 clone() 方法。
public class Address implements Cloneable{
private String name;
// 省略构造函数、Getter&Setter方法
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
public class Person implements Cloneable {
private Address address;
// 省略构造函数、Getter&Setter方法
@Override
public Person clone() {
try {
Person person = (Person) super.clone();
return person;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
深拷贝示例代码
@Override
public Person clone() {
try {
Person person = (Person) super.clone();
person.setAddress(person.getAddress().clone());
return person;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
- 什么是引用拷贝?:引用拷贝就是两个不同的引用指向同一个对象。
- 总结:
8.Java注解的作用?
Annotataion(注解) 从Java5开始引入的新特性,可以看做是一种特殊的注释,用与修饰类、方法、变量等,提供某些信息供程序在编译或者运行时使用。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
public interface Override extends Annotation{
}
9.Exception 和 Error 有什么区别?
- Exception:程序本身可以处理的异常,可以通过catch进行捕捉。
- Error:Error属于程序无法处理的错误,例如OOM,NCF等等。 Exception又分为 Checked Exception 和 Unchecked Exception,一个是编译期,如果受检查异常没有被catch或者throws,则没办法通过编译。另一种则是编译时不处理。
10.Java泛型是什么?什么是类型擦除?说一下常用的通配符?
- Java泛型是 JDK5 引用的一个新特性,使用泛型可以增强代码的可读性及稳定性。编译器可以对泛型参数进行检测,并且通过泛型参数可以传入指定的对象类型。
- 类型擦除:Java的泛型是伪泛型,因为在Java编译期间,所有的泛型信息都会被擦除掉
List<Integer> list = new ArrayList<>();
list.add(12);
// 编译期间报错
list.add("abc");
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add",Object.class);
// 运行期间通过
add.invoke(list,"abc");
System.out.println(list);
- 通配符:常见的通配符有?和 T。
- T 可以用于声明变量或常量,而 ? 不行。
- T 一般用于声明泛型类或方法,通配符 ? 一般用于泛型方法的调用代码和形参。
- T 在编译期会背擦除为限定类型或 Object,通配符用于捕获具体类型。
11.内部类了解吗?匿名内部类了解吗?
在Java中,内部类是指定义在另一个类内部的类。内部类可以访问其外部类的成员变量和方法,包括私有成员,因为它们在同一作用域内。内部类可以分为成员内部类、静态内部类、局部内部类和匿名内部类。
1.成员内部类:定义在外部类的内部,不使用static修饰。它可以访问外部类的所有成员,包括私有成员。
class OuterClass {
private int x;
class InnerClass {
void display() {
System.out.println("Value of x: " + x);
}
}
}
2.静态内部类:使用static修饰的内部类。它不持有对外部类对象的引用,因此无法访问外部类的非静态成员,但可以访问外部类的静态成员。
class OuterClass {
static class StaticInnerClass {
void display() {
System.out.println("Inside static inner class");
}
}
}
3.局部内部类:定义在方法或作用域内部的类。局部内部类只在声明它的方法或作用域中可见
class OuterClass {
void method() {
class LocalInnerClass {
void display() {
System.out.println("Inside local inner class");
}
}
LocalInnerClass inner = new LocalInnerClass();
inner.display();
}
}
4.匿名内部类:没有名字的内部类。通常用于创建实现某个接口或继承某个类的对象。
interface MyInterface {
void display();
}
class OuterClass {
void method() {
MyInterface inner = new MyInterface() {
@Override
public void display() {
System.out.println("Inside anonymous inner class");
}
};
inner.display();
}
}
匿名内部类的创建通常在需要实例化接口或抽象类的对象时使用,且仅使用一次,因此不需要命名。
12.Java反射是什么,有什么优缺点,怎么理解?
Java反射是指在运行时动态地获取类的信息(例如类的属性、方法、构造函数等),并且可以动态调用类的方法、创建对象,以及修改类的属性值。通过反射机制,可以在运行时获取程序中任意一个类的信息并操作它们,而无需在编译时确定这些信息。
优点:
- 动态性:反射机制允许在运行时动态地获取和使用类的信息,使得程序具有更大的灵活性和可扩展性。
- 泛化:通过反射可以编写通用的代码,对于不同的类都能适用,而不需要针对特定的类进行硬编码。
- 解耦:反射使得类与类之间的依赖关系降低,提高了代码的灵活性和可维护性。
缺点:
- 性能开销:由于反射涉及到动态的类加载、方法查找等操作,因此在性能上会有一定的开销,相比直接调用方法或创建对象,反射效率较低。
- 类型安全性:由于反射允许绕过编译时的类型检查,因此可能导致类型安全性问题,如调用不存在的方法或访问私有属性等,这可能会导致程序运行时的异常。
- 可读性和维护性:反射使得代码更加复杂和难以理解,因为它在编译时无法提供类型信息,导致代码可读性和维护性降低。
理解反射机制
反射机制在Java中可以通过java.lang.reflect包中的类来实现,例如Class、Method、Field等。通过这些类,可以获取类的信息,调用方法、设置属性等。理解反射机制的关键在于理解其灵活性和动态性,它使得在编译时无法确定类的情况下,依然能够进行类的操作,这为很多框架和工具的实现提供了可能。但同时,也要注意反射的性能开销和潜在的类型安全问题,合理使用反射才能发挥其优点并尽量避免其缺点。
13.BIO,NIO,AIO有什么区别?
BIO(Blocking I/O)、NIO(Non-blocking I/O)、AIO(Asynchronous I/O)是Java中用于处理I/O操作的三种不同的模型。
BIO(Blocking I/O):
- 在BIO模型中,I/O操作是阻塞的。也就是说,当一个线程执行一个I/O操作时,它会被阻塞,直到操作完成或者出现错误。
- 通常情况下,每个I/O操作都会对应一个线程,如果有大量的I/O操作需要处理,就需要创建大量的线程,而线程创建和销毁的开销很大,因此BIO模型在处理大量连接时效率较低。
NIO(Non-blocking I/O):
- NIO模型是一种非阻塞的I/O模型。在NIO中,一个线程可以同时处理多个连接,不需要为每个连接创建一个线程。
- NIO通过Selector(选择器)实现多路复用,Selector可以监听多个Channel,当某个Channel上的I/O事件发生时,就会通知对应的线程进行处理,从而实现了多个连接的复用。
AIO(Asynchronous I/O):
- AIO模型是一种异步I/O模型。在AIO中,I/O操作不会阻塞线程,而是在操作完成后通过回调的方式通知应用程序。
- AIO是在NIO的基础上进一步封装的,更加方便和高效地处理I/O操作。