⚠️ 注意:基本数据类型存放在栈中是一个常见的误区! 基本数据类型的存储位置取决于它们的作用域和声明方式。如果它们是局部变量,那么它们会存放在栈中;如果它们是成员变量,那么它们会存放在堆/方法区/元空间中。
在 Java 中,包装类(例如 Integer、Character、Byte、Short、Long 和 Boolean)具有缓存机制,用于提高性能并减少内存使用。两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。以下是有关包装类缓存机制的详细信息:
包装类缓存
1. 缓存机制概述
- 目的:为了避免频繁创建相同值的包装类对象,Java 提供了一个缓存机制,特别是在小范围内的值(例如从 -128 到 127 的
Integer)。 - 效果:当创建的包装类值在缓存范围内时,返回的是已存在的对象引用,而不是新创建一个对象。这样可以节省内存并提高性能。
2. 具体实现
-
Integer类:- Java 中的
Integer类缓存了从 -128 到 127 的整数。 - 例如,
Integer a = 100;和Integer b = 100;将引用同一个对象。
Integer a = 100; Integer b = 100; System.out.println(a == b); // 输出 true - Java 中的
-
Byte、Short和Character:- 这些类也有类似的缓存机制,
Byte缓存范围是 -128 到 127,Short是 -128 到 127,Character是 0 到 127。
- 这些类也有类似的缓存机制,
-
Long类:Long类缓存的范围是 -128 到 127,但由于其范围更大,可能会影响性能。
-
Boolean类:Boolean只缓存两个值:Boolean.TRUE和Boolean.FALSE。
3. 不在缓存范围内的值
- 对于不在缓存范围内的值,Java 会创建新的对象。
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // 输出 false,因为 c 和 d 是不同的对象
考点
Integer i1 = 40; //缓存对象
Integer i2 = new Integer(40);//自己new一个
System.out.println(i1==i2); //
输出false Integer i1=40 自动装箱 。等价于Integer.valueOf(40)
- Integer.valueOf()源码如下
- 🎉对于引用类型 == 比较的是对象的内存地址 对于基本数据类型 则比较的是值
- 🏍对于引用类型(equals没有被重写)equals比较的是地址 对于重写过equals方法的类,比如String 则 比较的是值
- 总结
如果一个类没有重写
equals()方法,那么它将继承Object类中的equals()实现。默认情况下,Object类的equals()方法是基于引用的比较,也就是说,它检查两个对象是否指向同一个内存地址。
Objetc类 equals源码
装箱-拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型;
装箱其实就是调用了 包装类的valueOf()方法,拆箱其实就是调用了 xxxValue()方法。
Integer i = 10等价于Integer i = Integer.valueOf(10)int n = i等价于int n = i.intValue();
变量
成员变量与局部变量的区别
- 语法形式:从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被
public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰。 - 存储方式:从变量在内存中的存储方式来看,如果成员变量是使用
static修饰的,那么这个成员变量是属于类的,如果没有使用static修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。 - 生存时间:从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
- 默认值:从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被
final修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
重载-重写
| 特性 | 重载(Overload) | 重写(Override) |
|---|---|---|
| 定义 | 在同一个类中,方法名相同但参数列表不同(参数类型、个数或顺序)。 | 在子类中,方法名、参数列表和返回类型与父类方法完全相同,重新实现父类方法。 |
| 作用范围 | 同一个类中。 | 子类和父类之间。 |
| 参数列表 | 必须不同(参数类型、个数或顺序)。 | 必须相同。 |
| 返回类型 | 可以不同。 | 必须相同(或为父类方法返回类型的子类型)。 |
| 访问修饰符 | 可以不同。 | 不能比父类方法更严格(例如,父类是protected,子类不能是private)。 |
| 异常抛出 | 可以抛出不同的异常或不抛出异常。 | 不能抛出比父类方法更多的检查异常(可以抛出更少或不抛出)。 |
| 静态方法 | 可以重载静态方法。 | 不能重写静态方法(静态方法属于类,不属于实例)。 |
| 绑定方式 | 编译时多态(静态绑定)。 | 运行时多态(动态绑定)。 |
| 目的 | 提供多个方法实现,处理不同类型或数量的参数。 | 子类重新定义父类方法的行为,实现多态性。 |
| 示例 | ```java | ```java |
| class A { | class A { | |
| void show(int a) { } | void show() { System.out.println("A"); } | |
| void show(String s) { } // 重载 | } | |
| } | class B extends A { | |
| @Override | ||
| void show() { System.out.println("B"); } // 重写 | ||
| } | ||
| ``` |
关键区别总结
- 重载:同一个类中,方法名相同但参数列表不同,用于处理不同类型或数量的参数。
- 重写:子类重新定义父类方法,方法名、参数列表和返回类型必须相同,用于实现多态性。
面向对象
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,涉及到访问修饰符。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以重写父类的方法
在Java中,对象类型和引用类型是两个重要的概念,它们描述了对象和变量之间的关系。以下是它们的详细解释:
1. 对象类型(Object Type)
-
定义:对象类型是指对象实际所属的类或类型。
-
特点:
- 对象类型是在运行时确定的,由
new关键字创建的对象决定。 - 对象类型决定了对象的行为和属性(即对象可以调用哪些方法和访问哪些字段)。
- 对象类型是在运行时确定的,由
-
示例:
Animal myDog = new Dog(); // 对象类型是 Dog在这里,
myDog的实际对象类型是Dog,而不是Animal。
2. 引用类型(Reference Type)
-
定义:引用类型是指变量声明时的类型,用于指向对象的引用。
-
特点:
- 引用类型是在编译时确定的,由变量的声明类型决定。
- 引用类型决定了变量可以访问哪些方法和字段(即编译时的类型检查)。
- 引用类型可以是类、接口、数组等。
-
示例:
Animal myDog = new Dog(); // 引用类型是 Animal在这里,
myDog的引用类型是Animal,因此编译器会认为myDog是一个Animal类型的变量。
多态特点
- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
- 多态不能调用“只在子类存在但在父类不存在”的方法;
- 如果子类重写了父类的方法,真正执行的是子类重写的方法,如果子类没有重写父类的方法,执行的是父类的方法。
深浅拷贝
详见 Java基础常见面试题总结(中) | JavaGuide
1. 浅拷贝(Shallow Copy)
-
定义:浅拷贝会创建一个新对象,并将原对象的所有字段复制到新对象中。如果字段是基本类型,则直接复制值;如果字段是引用类型,则复制引用(即新对象和原对象共享同一个引用类型字段)。
-
特点:
- 新对象和原对象的引用类型字段指向同一个内存地址。
- 修改其中一个对象的引用类型字段会影响另一个对象。
-
实现方式:
- ✔默认的
clone()方法(需要实现Cloneable接口)。 - 通过构造函数或工厂方法复制字段。
代码举例
- ✔默认的
class Address implements Cloneable{
private String name;
public Address(String name) {
this.name = name;
}
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
class Person implements Cloneable {
private Address address;
public Person(Address address) {
this.address = address;
}
public Address getAddress() {
return address;
}
@Override
public Person clone() {
try {
Person person = (Person) super.clone();
return person;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
public class test {
public static void main(String []args){
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone();
System.out.println(person1.getAddress() == person1Copy.getAddress());// true 同一个引用
}
public static void printArray(String name,int... numbers) {
System.out.println("name is "+name);
for (int number : numbers) {
System.out.print(number + " ");
}
}
}
2. 深拷贝(Deep Copy)
-
定义:深拷贝会创建一个新对象,并递归地复制原对象的所有字段。如果字段是引用类型,则会创建该字段的新对象(而不是复制引用),从而确保新对象和原对象完全独立。
-
特点:
- 新对象和原对象的引用类型字段指向不同的内存地址。
- 修改其中一个对象的引用类型字段不会影响另一个对象。
-
实现方式:
- 手动实现
clone()方法,递归调用引用类型字段的clone()方法。 - 🐛使用序列化和反序列化(如
ObjectOutputStream和ObjectInputStream)。 - 使用第三方库(如Apache Commons Lang的
SerializationUtils.clone())。
- 手动实现
字符串常量池
Java 中,使用 String s1 = new String("abc"); 这行代码会创建两个对象:
- 字符串常量对象:
"abc"是一个字符串字面量,它会被存储在字符串常量池中。这个对象是自动创建的,在程序运行时,字符串常量池会确保每个字面量只存在一个实例。 - 字符串实例对象:
new String("abc")会使用new关键字显式地创建一个新的String对象。这个对象是在堆内存中创建的,与字符串常量池中的"abc"是两个不同的对象。