文章目录
- 4.Java基础重要知识(面试题)
- 4.1基本数据类型
- 4.1.1 八种基本数据类型
- 4.1.2 基本类型和包装类型的区别
- 4.1.3包装类型的缓存机制
- 4.1.4自动装箱和拆箱?原理?
- (1)定义
- (2)原理
- 4.2变量
- 4.2.1成员变量与局部变量的区别
- 4.2.2静态变量的作用
- 4.3面向对象基础
- 4.3.1面向对象三大特征
- (1)封装
- (2)继承
- (3)多态
- 4.3.2辨别接口和抽象类的异同
- (1)共同点
- (2)不同点
- 4.3.3辨别深拷贝、浅拷贝、引用拷贝
- (1)浅拷贝
- (2)深拷贝
- (3)引用拷贝
- 4.4 Object类
- 4.4.1==和equals的区别
- (1)==
- (2)equals
- 4.5 String
- 4.5.1 String、StringBuffer、StringBuilder的区别
- 4.5.2 String为什么是不可变的?
4.Java基础重要知识(面试题)
4.1基本数据类型
4.1.1 八种基本数据类型
Java 中有 8 种基本数据类型,分别为:
- 6 种数字类型:
- 4 种整数型:
byte
、short
、int
、long
- 2 种浮点型:
float
、double
- 4 种整数型:
- 1 种字符类型:
char
- 1 种布尔型:
boolean
。
这 8 种基本数据类型的默认值以及所占空间的大小如下:
基本类型 | 位数 | 字节 | 默认值 | 取值范围 |
---|---|---|---|---|
byte | 8 | 1 | 0 | -128 ~ 127 |
short | 16 | 2 | 0 | -32768(-2^15) ~ 32767(2^15 - 1) |
int | 32 | 4 | 0 | -2147483648 ~ 2147483647 |
long | 64 | 8 | 0L | -9223372036854775808(-2^63) ~ 9223372036854775807(2^63 -1) |
float | 32 | 4 | 0f | 1.4E-45 ~ 3.4028235E38 |
double | 64 | 8 | 0d | 4.9E-324 ~ 1.7976931348623157E308 |
char | 16 | 2 | ‘u0000’ | 0 ~ 65535(2^16 - 1) |
boolean | 1 | false | true、false |
4.1.2 基本类型和包装类型的区别
- 用途:
- 除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量
- 包装类型可用于泛型,而基本类型不可以。
- 存储方式:
- 基本数据类型的 局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被
static
修饰 )存放在 Java 虚拟机的堆中。 - 包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
- 基本数据类型的 局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被
- 占用空间:
- 相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。
- 默认值:
- 成员变量包装类型不赋值就是
null
- 基本类型有默认值且不是
null
。
- 成员变量包装类型不赋值就是
- 比较方式:
- 对于基本数据类型来说,
==
比较的是值。 - 对于包装数据类型来说,
==
比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用equals()
方法。
- 对于基本数据类型来说,
- 注意注意:基本数据类型存放在栈中是一个常见的误区! 基本数据类型的存储位置取决于它们的作用域和声明方式。如果它们是局部变量,那么它们会存放在栈中;如果它们是成员变量,那么它们会存放在堆中
public class Test {// 成员变量,存放在堆中int a = 10;// 被 static 修饰,也存放在堆中,但属于类,不属于对象// JDK1.7 静态变量从永久代移动了 Java 堆中static int b = 20;public void method() {// 局部变量,存放在栈中int c = 30;static int d = 40; // 编译错误,不能在方法中使用 static 修饰局部变量}
}
4.1.3包装类型的缓存机制
Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据Character
创建了数值在 [0,127] 范围的缓存数据Boolean
直接返回True
orFalse
。- 如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
- 举例一:
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 trueFloat i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// 输出 falseDouble i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false
- 举例二:注意新建包装类型对象
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);//Integer i1=40 这一行代码会发生装箱,也就是说这行代码等价于 Integer i1=Integer.valueOf(40) 。因此,i1 直接使用的是缓存中的对象。而Integer i2 = new Integer(40) 会直接创建新的对象
//结果为fasle
4.1.4自动装箱和拆箱?原理?
(1)定义
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型;
(2)原理
- 举例
Integer i = 10; //装箱
int n = i; //拆箱
- 字节码文件:
L1LINENUMBER 8 L1ALOAD 0BIPUSH 10INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;L2LINENUMBER 9 L2ALOAD 0ALOAD 0GETFIELD AutoBoxTest.i : Ljava/lang/Integer;INVOKEVIRTUAL java/lang/Integer.intValue ()IPUTFIELD AutoBoxTest.n : IRETURN
- 装箱其实就是调用了 ==包装类的
valueOf()
==方法 - 拆箱其实就是调用了
xxxValue()
方法。
Integer i = 10
等价于Integer i = Integer.valueOf(10)
int n = i
等价于int n = i.intValue()
;
4.2变量
4.2.1成员变量与局部变量的区别
-
语法形式:
- 从语法形式上看,成员变量是属于类的,成员变量可以被
public
,private
,static
等修饰符所修饰 - 局部变量是在代码块或方法中定义的变量或是方法的参数;,局部变量不能被访问控制修饰符及
static
所修饰; - 成员变量和局部变量都能被
final
所修饰。
- 从语法形式上看,成员变量是属于类的,成员变量可以被
-
存储方式:从变量在内存中的存储方式来看
- 如果成员变量是使用
static
修饰的,那么这个成员变量是属于类的 - 如果没有使用
static
修饰,这个成员变量是属于实例的。 - 而对象存在于堆内存 ,局部变量则存在于 栈内存。
- 如果成员变量是使用
-
生存时间:从变量在内存中的生存时间上看
- 成员变量是对象的一部分,它随着对象的创建而存在
- 局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
-
默认值:从变量是否有默认值来看
- 成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被
final
修饰的成员变量也必须显式地赋值) - 而局部变量则不会自动赋值。
- 成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被
4.2.2静态变量的作用
- 静态变量也就是被
static
关键字修饰的变量。它可以被类的所有实例共享,无论一个类创建了多少个对象,它们都共享同一份静态变量。 - 静态变量只会被分配一次内存,即使创建多个对象,这样可以节省内存。
- 静态变量是通过类名来访问的,例如
StaticVariableExample.staticVar
(如果被private
关键字修饰就无法这样访问了)。
public class StaticVariableExample {// 静态变量public static int staticVar = 0;
}
通常情况下,静态变量会被 final
关键字修饰成为常量。
public class ConstantVariableExample {// 常量public static final int constantVar = 0;
}
4.3面向对象基础
4.3.1面向对象三大特征
封装、继承、多态
(1)封装
- 封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
- 举例:我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。
(2)继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能
核心点:
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是 无法访问,只是拥有。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法
(3)多态
多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。
多态的特点:
- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
- 多态不能调用“只在子类存在但在父类不存在”的方法;
- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
4.3.2辨别接口和抽象类的异同
(1)共同点
- 都不能被实例化。
- 都可以包含抽象方法。
- 都可以有默认实现的方法(Java 8 可以用
default
关键字在接口中定义默认方法)
(2)不同点
-
单继承、多实现
-
成员变量修饰符不同、赋值不同:
- 接口中的成员变量只能是 public static final 类型 的,不能被修改且必须有初始值
- 抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值
-
用途不同:
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。
- 抽象类主要用于代码复用,强调的是所属关系。
4.3.3辨别深拷贝、浅拷贝、引用拷贝
- 浅拷贝:
- 浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点)
- 如果原对象内部的属性是 引用类型 的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
- 深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
(1)浅拷贝
举例说明浅拷贝,实现Cloneable接口,并重写clone()方法,直接调用父类Obejct的clone()方法
public class Address implements Cloneable{private String name;// 省略构造函数、Getter&Setter方法@Overridepublic Address clone() {try {return (Address) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError();}}
}public class Person implements Cloneable {private Address address;// 省略构造函数、Getter&Setter方法@Overridepublic 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
对象。
(2)深拷贝
这里我们简单对 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
对象已经是不同的了。
(3)引用拷贝
简单来说,引用拷贝就是两个不同的引用指向同一个对象。
以下图来描述浅拷贝、深拷贝、引用拷贝:
4.4 Object类
4.4.1==和equals的区别
==是运算符,equals是方法
(1)==
-
若比较的对象是基本数据类型,则比较数值是否相等
-
若比较的对象是引用数据类型,则比较的是对象的内存地址是否相等
-
不管是比较基本数据类型,还是引用数据类型的变量,其比较的都是值,只是引用类型变量存的值是对象的地址。引用类型对象变量其实是一个引用,它们的值是指向对象所在的内存地址。
(2)equals
equals方法比较的是对象的内容是否相同
equals()方法存在于Object类中,而Object类是所有类的父类。在Object类中定义了equals方法:
public boolean equals(Object obj) {return (this == obj);
}
-
如果类未重写equals方法
调用equals时,会调用Object中的equals方法(实际使用的也是 == 操作符)
-
如果类重写了equals方法
调用equals时,会调用该类自己的equals方法(一般是比较对象的内容是否相同)。比如:
- String:比较字符串内容是否相同;
- Integer:比较对应的基本数据类型int的值是否相同。
4.5 String
4.5.1 String、StringBuffer、StringBuilder的区别
三者的区别可以从可变性、线程安全性、性能三个角度去分析:
-
可变性:
-
String
是不可变的 -
StringBuilder
与StringBuffer
都继承自AbstractStringBuilder
类,在AbstractStringBuilder
中也是使用字符数组保存字符串,不过没有使用final
和private
关键字修饰,最关键的是这个AbstractStringBuilder
类还提供了很多修改字符串的方法比如append
方法abstract class AbstractStringBuilder implements Appendable, CharSequence {char[] value;public AbstractStringBuilder append(String str) {if (str == null)return appendNull();int len = str.length();ensureCapacityInternal(count + len);str.getChars(0, len, value, count);count += len;return this;}//... }
-
-
线程安全性:
String
中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder
是StringBuilder
与StringBuffer
的公共父类,定义了一些字符串的基本操作,如expandCapacity
、append
、、insert
、indexOf
等公共方法。StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的。
-
性能:
- 每次对
String
类型进行改变的时候,都会生成一个新的String
对象,然后将指针指向新的String
对 StringBuffer
每次都会对StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用。- 相同情况下使用
StringBuilder
相比使用StringBuffer
仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
- 每次对
- 总结:
- 操作少量的数据: 适用
String
- 单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
- 操作少量的数据: 适用
4.5.2 String为什么是不可变的?
String
类中使用 private 和 final 两个关键字修饰字符数组(作用:保存字符串)- 被
final
关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象 - 但是当数组存放的对象是引用类型时,这个数组保存的字符串的内容是可变的(虽然不能再指向其他对象,但是可以修改里面对象的数据)
- 真正不可变的原因:
- 保存字符串的数组被
final
修饰且为私有的,并且String
类没有提供/暴露修改这个字符串的方法。 String
类被final
修饰导致其不能被继承,进而避免了子类破坏String
不可变。
- 保存字符串的数组被
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {private final char value[];//...
}