面渣逆袭之Java基础篇3
1 包装类型的缓存机制了解么?
Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 TRUE or FALSE。
对于 Integer,可以通过 JVM 参数 -XX:AutoBoxCacheMax=<size> 修改缓存上限,但不能修改下限 -128。实际使用时,并不建议设置过大的值,避免浪费内存,甚至是 OOM。
对于Byte,Short,Long ,Character 没有类似 -XX:AutoBoxCacheMax 参数可以修改,因此缓存范围是固定的,无法通过 JVM 参数调整。Boolean 则直接返回预定义的 TRUE 和 FALSE 实例,没有缓存范围的概念。
Integer 缓存源码:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static {
// high value may be configured by property
int h = 127;
}
}
Character 缓存源码:
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
private static class CharacterCache {
private CharacterCache(){}
static final Character cache[] = new Character[127 + 1];
static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}
Boolean 缓存源码:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 true
Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// 输出 false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false
2 说一下⾯向对象和⾯向过程的区别?
使⽤的时候再⼀个⼀个的⼀次调⽤就可以。
⾯向对象(OOP :⾯向对象,把构成问题的事务分解成各个对象,⽽建⽴对象的⽬的也不是为了完成⼀
个个步骤,⽽是为了描述某个事件在解决整个问题的过程所发⽣的⾏为。 ⽬的是为了写出通⽤的
代码,加强代码的重⽤,屏蔽差异性。
相比较于 POP,OOP 开发的程序一般具有下面这些优点:
- 易维护:由于良好的结构和封装性,OOP 程序通常更容易维护。
- 易复用:通过继承和多态,OOP 设计使得代码更具复用性,方便扩展功能。
- 易扩展:模块化设计使得系统扩展变得更加容易和灵活。
POP 的编程方式通常更为简单和直接,适合处理一些较简单的任务。
⽤⼀个⽐喻:⾯向过程是编年体;⾯向对象是纪传体。面向对象:
public class Circle {
// 定义圆的半径
private double radius;
// 构造函数
public Circle(double radius) {
this.radius = radius;
}
// 计算圆的面积
public double getArea() {
return Math.PI * radius * radius;
}
// 计算圆的周长
public double getPerimeter() {
return 2 * Math.PI * radius;
}
public static void main(String[] args) {
// 创建一个半径为3的圆
Circle circle = new Circle(3.0);
// 输出圆的面积和周长
System.out.println("圆的面积为:" + circle.getArea());
System.out.println("圆的周长为:" + circle.getPerimeter());
}
}
定义了一个 Circle
类来表示圆,该类包含了圆的半径属性和计算面积、周长的方法。
面向过程:
public class Main {
public static void main(String[] args) {
// 定义圆的半径
double radius = 3.0;
// 计算圆的面积和周长
double area = Math.PI * radius * radius;
double perimeter = 2 * Math.PI * radius;
// 输出圆的面积和周长
System.out.println("圆的面积为:" + area);
System.out.println("圆的周长为:" + perimeter);
}
}
直接定义了圆的半径,并使用该半径直接计算出圆的面积和周长。
3 来说说面向对象有哪些特性?
4 创建一个对象用什么运算符?对象实体与对象引用有何不同?
new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。
- 一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);
- 一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
5 对象的相等和引用相等的区别
- 对象的相等一般比较的是内存中存放的内容是否相等。
- 引用相等一般比较的是他们指向的内存地址是否相等。
举一个例子:
String str1 = "xiaoliang";
String str2 = new String("xiaoliang");
String str3 = "xiaoliang";
// 使用 == 比较字符串的引用相等
System.out.println(str1 == str2);
System.out.println(str1 == str3);
// 使用 equals 方法比较字符串的相等
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
大家可以来看看输出结果是什么?
输出:
false
true
true
true
从上面的代码输出结果可以看出:
str1
和str2
不相等,而str1
和str3
相等。这是因为==
运算符比较的是字符串的引用是否相等。str1
、str2
、str3
三者的内容都相等。这是因为equals
方法比较的是字符串的内容,即使这些字符串的对象引用不同,只要它们的内容相等,就认为它们是相等的。
6 构造方法有哪些特点?是否可被 override?
构造方法具有以下特点:
- 名称与类名相同:构造方法的名称必须与类名完全一致。
- 没有返回值:构造方法没有返回类型,且不能使用
void
声明。 - 自动执行:在生成类的对象时,构造方法会自动执行,无需显式调用。
构造方法不能被重写(override),但可以被重载(overload)。因此,一个类中可以有多个构造方法,这些构造方法可以具有不同的参数列表,以提供不同的对象初始化方式。
// 定义父类 Person
class Person {
private String name;
private int age;
// 无参构造方法
public Person() {
this.name = "Unknown";
this.age = 0;
System.out.println("Person 类的无参构造方法被调用");
}
// 带参数的构造方法,构造方法重载
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person 类的带参数构造方法被调用");
}
public void introduce() {
System.out.println("My name is " + name + ", and I'm " + age + " years old.");
}
}
// 定义子类 Student 继承自 Person 类
class Student extends Person {
private String studentId;
// 无参构造方法
public Student() {
super(); // 调用父类的无参构造方法
this.studentId = "Unknown";
System.out.println("Student 类的无参构造方法被调用");
}
// 带参数的构造方法,构造方法重载
public Student(String name, int age, String studentId) {
super(name, age); // 调用父类的带参数构造方法
this.studentId = studentId;
System.out.println("Student 类的带参数构造方法被调用");
}
public void showStudentId() {
System.out.println("My student ID is " + studentId);
}
}
public class Main {
public static void main(String[] args) {
// 创建 Person 对象,调用无参构造方法
Person person1 = new Person();
person1.introduce();
// 创建 Person 对象,调用带参数的构造方法
Person person2 = new Person("Alice", 20);
person2.introduce();
// 创建 Student 对象,调用无参构造方法
Student student1 = new Student();
student1.introduce();
student1.showStudentId();
// 创建 Student 对象,调用带参数的构造方法
Student student2 = new Student("Bob", 22, "123456");
student2.introduce();
student2.showStudentId();
}
}
7 重载(overload)和重写(override)的区别?
8 访问修饰符 public、private、protected、以及不写(默认)时的区别?
可见性 | private | default | protected | public |
---|---|---|---|---|
同一个类中 | ✔ | ✔ | ✔ | ✔ |
同一个包中 | ✖ | ✔ | ✔ | ✔ |
子类中 | ✖ | ✖ | ✔ | ✔ |
全局范围 | ✖ | ✖ | ✖ | ✔ |
9 抽象类(abstract class)和接⼜(interface)有什么区别?
10 深拷贝和浅拷贝区别了解吗?什么是引用拷贝?
关于深拷贝和浅拷贝区别,这里先给结论:
-
浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
-
深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
上面的结论没有完全理解的话也没关系,我们来看一个具体的案例!
浅拷贝
浅拷贝的示例代码如下,我们这里实现了 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();
}
}
}
测试一下:
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone();
// true
System.out.println(person1.getAddress() == person1Copy.getAddress());
从输出结构可以看出, person1
的克隆对象和 person1
使用的仍然是同一个 Address
对象。
深拷贝
简单对 Person
类的 clone()
方法进行修改,连带着要把 Person
对象内部的 Address
对象一起复制。
@Override
public Person clone() {
try {
Person person = (Person) super.clone();
person.setAddress(person.getAddress().clone());
return person;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
测试一下:
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone();
// false
System.out.println(person1.getAddress() == person1Copy.getAddress());
从输出结构就可以看出,显然 person1
的克隆对象和 person1
包含的 Address
对象已经是不同的了。
那什么是引用拷贝呢? 简单来说,引用拷贝就是两个不同的引用指向同一个对象。
综合来看,可以归纳为下面的图: