变量
成员变量与局部变量的区别?
-
语法形式:
- 成员变量:成员变量是定义在类中的变量,可以在类的任何方法、构造函数或者块中使用,并且每个类对象都拥有自己的一组成员变量,
- 局部变量:局部变量是定义在方法、构造函数或者代码块中的变量,只在定义它的方法、构造函数或者代码块中可见,超出其作用域范围就无法访问。
- 成员变量可以被public,private,static等修饰符修饰。但是局部变量不能被访问控制修饰符及static修饰,但是他们都可以被final修饰。
-
存储方式:
- 成员变量:成员变量存储在堆内存中,每个类的对象都有自己的一组成员变量,它们随着对象的创建而被分配内存,并且随着对象的销毁而释放内存。
- 局部变量:局部变量存储在栈内存中,它们的生命周期与所在方法的执行周期相同,方法执行结束后局部变量就会被销毁。
-
生存时间:
- 成员变量:成员变量的生存时间与对象的生命周期相关联,当对象被销毁时,其成员变量也会被销毁。
- 局部变量:局部变量的生存时间仅限于声明它的方法、构造函数或代码块的执行期间,在超出其作用域范围后就会被销毁。
-
默认值:
- 成员变量:如果没有显式地初始化成员变量,Java会给成员变量赋予默认值。例如,数值类型的默认值是0,布尔类型的默认值是false,引用类型的默认值是null。
- 局部变量:Java不会为局部变量赋予默认值,必须在使用前显式地初始化局部变量,否则编译器会报错。
静态变量有什么作用?
-
共享数据: 静态变量在内存中只有一份拷贝,被所有该类的对象共享。这意味着,当一个对象修改了静态变量的值,其他对象访问该静态变量时会看到被修改后的值。因此,静态变量可以用来表示类级别的数据,比如记录类的实例数量或者表示全局配置。
-
方便访问: 静态变量可以直接通过类名来访问,不需要创建类的实例。这样可以简化代码,方便访问类级别的数据。
-
在静态方法中使用: 静态方法只能访问静态变量,不能直接访问实例变量。因此,在静态方法中使用静态变量可以避免编译错误,并且可以方便地在静态方法中操作类级别的数据。
-
常量定义: 在Java中,静态变量经常用于定义常量。通过将变量声明为
final
和static
,可以创建一个常量,该常量的值在程序的生命周期内不可修改。
总的来说,静态变量可以用来共享数据、方便访问类级别的数据、在静态方法中使用以及定义常量等场景。但需要注意的是,过度使用静态变量可能会导致程序的耦合性增加,降低代码的可维护性,因此需要谨慎使用。
字符型常量和字符串常量的区别?
-
形式:
- 字符型常量:字符型常量使用单引号括起来,表示单个字符。例如:
'A'
、'b'
、'1'
。 - 字符串常量:字符串常量使用双引号括起来,表示一个或多个字符组成的序列。例如:
"Hello"
、"Java"
、"123"
。
- 字符型常量:字符型常量使用单引号括起来,表示单个字符。例如:
-
含义:
- 字符型常量相当于一个整型值(ASCII值),可以参加表达式运算,字符串常量代表一个地址值(该字符串在内存中存放位置),
-
占内存大小:
字符型常量在内存中占用2个字节的空间,字符串常量占若干个字节
try-catch-finally中,如果catch中return了,finally还会执行嘛?
会
方法
静态方法为什么不能调用非静态成员?
这个需要结合JVM的相关知识,主要原因如下:
1.静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。二非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
2.在类的非静态成员不存在的时候静态方法已经存在了,此时调用在内存中还不存在的非静态成员变量,属于非法操作
虽然静态方法不能直接调用非静态成员,但在静态方法中仍然可以通过创建对象实例来访问非静态成员。另外,如果想要在静态方法中访问非静态成员,也可以将非静态成员改为静态的,使其属于类而不是对象。
静态方法和实例方法有什么不同?
-
调用方式:
- 静态方法: 静态方法通过类名直接调用,不需要创建类的实例。可以使用类名加点运算符的方式调用静态方法,例如:
ClassName.staticMethod()
。 - 实例方法: 实例方法必须通过对象实例来调用,需要先创建类的对象,然后使用对象实例加点运算符的方式调用实例方法,例如:
object.instanceMethod()
。
- 静态方法: 静态方法通过类名直接调用,不需要创建类的实例。可以使用类名加点运算符的方式调用静态方法,例如:
-
访问类成员是否存在限制:
- 静态方法: 静态方法可以直接访问类的静态成员(静态变量和静态方法),因为它们都属于类级别,可以在类加载时被调用。但静态方法不能直接访问非静态成员(实例变量和实例方法),因为它们属于对象级别,需要在对象创建后才能访问。如果要访问非静态成员,需要通过对象实例来访问。
- 实例方法: 实例方法可以直接访问类的静态成员和非静态成员,因为实例方法是通过对象实例来调用的,可以在对象创建后访问类的所有成员。实例方法可以使用
this
关键字来访问当前对象的实例变量和实例方法,也可以直接访问类的静态成员。
重载和重写的区别?
-
定义:
- 重载(Overloading): 重载指的是在同一个类中,可以定义多个方法具有相同的名称但参数列表不同(参数类型、参数个数或参数顺序不同)的情况。重载的方法是在编译时静态绑定的,根据调用时传入的参数类型来决定调用哪个方法。
- 重写(Overriding): 重写指的是子类可以重新定义父类中已有的方法,方法名、参数列表和返回类型必须完全一致。重写的方法是在运行时动态绑定的,根据对象的实际类型来决定调用哪个方法。
-
适用范围:
- 重载: 重载适用于同一个类中的方法,可以提供多种参数类型或个数的方法,提高了代码的灵活性和可读性。
- 重写: 重写适用于父子类之间的方法,子类可以根据需要重新定义父类中的方法,实现对方法行为的定制。
-
绑定方式:
- 重载: 重载是静态绑定的,即在编译时就确定了要调用哪个方法,根据方法的签名(方法名和参数列表)来决定。
- 重写: 重写是动态绑定的,即在运行时根据对象的实际类型来确定要调用的方法,实现了多态性。
-
关注点:
- 重载: 主要关注方法的参数类型、个数和顺序。
- 重写: 主要关注方法的实现,子类的方法要与父类的方法签名完全一致。
什么是可变长参数?
可变长参数(Variable Arguments)是指在方法声明中允许传递数量可变的参数。在Java中,可变长参数允许方法接受零个或多个参数,这些参数被视为一个数组,在方法内部可以像处理数组一样进行操作。
在Java中,可变长参数由省略号 ...
表示,它必须是方法参数列表中的最后一个参数。当调用可变长参数方法时,你可以传递任意数量的参数,甚至可以传递零个参数。编译器会将传递的参数封装成一个数组,然后传递给可变长参数方法。
以下是一个使用可变长参数的示例:
public class Example {
// 可变长参数方法
public static void printNumbers(int... numbers) {
System.out.println("Number of arguments: " + numbers.length);
System.out.print("Arguments: ");
for (int num : numbers) {
System.out.print(num + " ");
}
System.out.println();
}public static void main(String[] args) {
// 调用可变长参数方法
printNumbers(1, 2, 3, 4, 5);
printNumbers(10, 20);
printNumbers(); // 传递零个参数
}
}
数据类型
java中的几种基本数据类型了解吗?
java中有8中基本数据类型,分别为:
6种数字类型:
4种整数型:byte,short,int,long
2种浮点型:float,double
1种字符型:char
1种布尔型:boolean
注意:Java种使用long类型的数据一定要在后面加上L,否则将作为整数解析。
这八种基本类型都有对应的包装类分别为:Byte,Short,Integer,Long,Float,Double,Character,Boolean。
基本类型和包装类型的区别?
1.成员变量包装类型不赋值就是null,而基本类型有默认值且不是null。
2.包装类型可以用于泛型,而基本类型不可以。
3.基本类型的局部变量存放在java虚拟机栈种的局部变量表中,基本数据类型的成员变量(未被static修饰)存放在java虚拟机的堆中。包装类型数据对象类型,我们知道几乎所有对象实例都存放在堆中。
4.相比对象类型,基本数据类型占用的空间非常小。
包装类型的缓存机制了解吗?
Java中基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
Byte,Short,Integer,Long这四种包装类型默认创建了数值[-128,127]的相应类型的缓存数据,
Character创建了数值在[0,127]范围的缓存数据,Boolean直接返回True or False。
Integer的缓存源码:
public final class Integer extends Number implements Comparable<Integer> {
// 定义了缓存的范围,默认为 -128 到 127
private static final int low = -128;
private static final int high;
static {
// 通过系统属性来设置缓存范围的上限
int h = 127;
String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// 将范围扩展到指定的上限
h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
} catch( NumberFormatException nfe) {
// 如果系统属性设置错误,使用默认值
}
}
high = h;
}
// 缓存数组
private static final Integer cache[] = new Integer[(high - low) + 1];
private final int value;
// 构造方法私有化,防止外部创建对象
private Integer(int value) {
this.value = value;
}
// 获取 Integer 对象的静态工厂方法
public static Integer valueOf(int i) {
if (i >= low && i <= high) {
return cache[i + (- low)];
}
return new Integer(i);
}
// 其他方法...
}
Character的缓存源码:
public final class Character implements java.io.Serializable, Comparable<Character> {
private static final long serialVersionUID = 3786198910865385080L;
private final char value;
// 定义缓存数组,缓存范围为 Unicode 编码值为 0 到 127 的字符
private static final char[] valueCache = new char[128];
static {
// 对缓存数组进行初始化
for (int i = 0; i < valueCache.length; i++) {
valueCache[i] = (char)i;
}
}
// 获取 Character 对象的静态工厂方法
public static Character valueOf(char c) {
if (c <= 127) { // 判断是否在缓存范围内
return valueCache[c];
}
return new Character(c);
}
// 其他方法...
}
Boolean的缓存源码:
public final class Boolean implements java.io.Serializable, Comparable<Boolean> {
private static final long serialVersionUID = -3665804199014368530L;
// 定义缓存的 Boolean 对象常量
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
private final boolean value;
// 构造方法私有化,防止外部创建对象
private Boolean(boolean value) {
this.value = value;
}
// 获取 Boolean 对象的静态工厂方法
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
// 其他方法...
}
如果超出对应范围仍然会创建对象,缓存的范围区间的大小只是在性能和资源之间的平衡。
两种浮点类型的包装类Float,Double并没有缓存机制。
自动装箱与拆箱了解嘛?原理是什么?
-
装箱(Boxing): 装箱指的是将基本数据类型转换为对应的包装类对象。例如,将
int
转换为Integer
、double
转换为Double
等。装箱是通过调用包装类的构造方法或者通过调用静态工厂方法实现的。int primitive = 10; Integer wrapper = Integer.valueOf(primitive); // 装箱,将 int 转换为 Integer
-
拆箱(Unboxing): 拆箱指的是将包装类对象转换为对应的基本数据类型。例如,将
Integer
转换为int
、Double
转换为double
等。拆箱是通过调用包装类的xxxValue()
方法实现的,其中xxx
表示基本数据类型。Integer wrapper = Integer.valueOf(20); int primitive = wrapper.intValue(); // 拆箱,将 Integer 转换为 int
自动装箱和拆箱是 Java 5 中引入的语法糖,简化了基本数据类型和包装类之间的转换操作。原理是在编译阶段由编译器自动将基本数据类型和对应的包装类进行转换,从而避免了手动调用构造方法或者方法进行转换的繁琐操作。
例如,在使用时,可以直接将基本数据类型赋值给包装类对象,或者将包装类对象赋值给基本数据类型,编译器会自动插入装箱和拆箱的代码。
// 自动装箱
Integer wrapper = 10;
// 自动拆箱
int primitive = wrapper;
自动装箱和拆箱提高了代码的简洁性和可读性,但在大量数据操作时需要注意性能损耗,因为涉及到频繁的对象创建和销毁。