目录
栈、堆、方法区、本地方法栈、寄存器
方法区:字节码文件加载时进入的内存
栈:方法运行时所进入的内存,变量也在这里
堆:new出来的东西会在这块内存中开辟空间并产生地址
方法调用流程内存解析
在一次程序执行的过程中,创建了多个对象,该类的字节码文件加载一次。每new一次,都会在堆内存中开辟一个独立的空间。成员方法的引用地址指向同一片内存空间。
成员变量和局部变量的区别
-
编写位置不同
-
成员变量 : 方法外
-
局部变量 : 方法中
-
-
初始化值不同
-
成员变量 : 有默认初始化值
-
局部变量 : 没有默认初始化值, 使用之前必须完成赋值.
-
-
内存位置不同
-
成员变量 : 堆内存
-
局部变量 : 栈内存 -- 栈内存的所属方法当中
-
-
生命周期不同
-
成员变量 : 随着对象的创建而存在, 随着对象的消失而消失
-
局部变量 : 随着方法的调用而存在, 随着方法的调用结束而消失.
-
-
作用域
-
都在自己所在的大括号中.
-
权限修饰符的作用范围
-
private : 在同一个类中
-
默认的 : 在同一个类中, 在同一个包下进行访问
-
protected : 在同一类中,在同一包中,在不同包中被protected修饰类的子类
-
public : 可以在任意位置访问
二维数组的动态初始化面试题
int[][] arr=new int[2][3];
int[] arr1={11,22,33};
int[] arr2={44,55,66,77};
arr[0] =arr1;
arr[1]=arr2;
以上内容不会报错,原因在于:二维数组保存的是多个一维数组的地址值,arr1赋给arr[0]其实是arr1的地址赋给了arr[0].具体内存图如下:
对象的内存机制
子类继承父类构造器的特点
子类中所有的构造器默认都会先访问父类中的无参构造器,再执行自己。原因是子类在初始化时,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据
怎样调用父类构造器
子类构造器的第一行语句默认是:super(),不写也存在
方法重写的使用注意事项
1、private方法不能被重写
2、static方法不能被重写
3、子类重写方法权限必须大于或等于父类中方法的权限
final修饰符
修饰该方法时,方法不能被重写,修饰类时,不能被继承,修饰变量时,该变量成为一个常量,不能被修改。
注:final修饰变量时,如果变量时基本数据类型,值不能被修改,如果是引用数据类型,内容可以修改,地址不能修改
例如
final int[] arr = {11,22,33};
arr[0] = 100;
System.out.println(arr[0]);
static修饰符的特点
1、随着类的加载而加载,类的加载时机是字节码文件运行时进入方法区的过程,优先于对象存在。
2、被同一个类多个对象共享
静态成员变量内存图
main方法进栈后,在执行第一句代码时,会将User的字节码文件加载到方法区,然后为User的静态变量划分一个专属于User静态成员的变量区。
多态特点
多态:在继承/实现关系中,不同子类对象展现各自的行为,这种现象叫多态
前提:1、要有继承
2、要有方法的重写
3、父类的引用指向子类的对象
注:成员变量没有多态,弊端:不能调用子类特有的方法。
如何解决无法调用子类特有的方法?强制类型转换
自动类型提升
父类名 对象名=new 子类名();//小的赋给大的
Person p=new Student();
强制类型转换
子类名 对象=(子类名)父类型变量;
Student s=(Student)p;
问题:当遇到传入的参数与强制类型转换需要的参数不一致时,会出现类型转换异常ClassCastException
原因:父类型变量记录的真实类型和要转换的类型不一致。
解决:判断真实类型 instanceof p instanceof Student
ElectricAlarmDoor实现了AlarmDoor接口中的抽象方法
main{
showElectricDoor(new ElectricAlarmDoor());
}
public static void showElectricDoor(AlarmDoor alarmDoor){
alarmDoor.BaoJing();
}
匿名内部类和Lambda表达式
内部类分类:
- 成员内部类(了解)
- 静态内部类(了解)
- 局部内部类(了解)
- 匿名内部类(重点)
成员内部类格式:外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
静态内部类格式:static修饰的成员内部类 外部类名.内部类名 对象名=new 外部类名.内部类名();
局部内部类格式:只能在方法中创建类,只能在方法中创建对象。
匿名内部类
前提:要有一个接口或类
new 类名或者接口名(){
// 重写方法
}
举例:
public interface Inter {
void show();
}
new Inter() {
@Override
public void show() {
System.out.println("匿名内部类");
}
};
匿名内部类的本质:是一个继承了该类的子类【对象】,或者是一个实现了该接口的实现类【对象】
使用场景
通常作为方法的参数传递
Thread t=new Thread()//构造器中参数有个能传入Runable接口,但接口不能实例化,所以需要使用匿名内部类
Thread t=new Thread(new Runable(){
public void run(){
}
})
Lambda表达式
前提:有一个接口,且接口中只有一个抽象方法
基本格式:
(形式参数) -> {代码}
(形式参数): 和接口中抽象方法中的参数保持一致
举例:1、无参数无返回值
public interface Addable {
int add(int x,int y);
}
public class AddableDemo {
public static void main(String[] args) {
useAddable(new Addable() {
@Override
public void add(int x,int y) {
return x + y;
}
});
useAddable((int x,int y) -> {
return x + y;
});
}
public static void useAddable(Addable a) {
int sum = a.add(10, 20);
System.out.println(sum);
}
}
lambda省略格式
-
省略规则
-
基本格式: (参数) -> {}
-
-
参数
-
没有参数: 小括号不能省略
-
() -> {System.out.println("黑马程序员");}
-
-
有一个参数: 小括号和数据类型都可以省略
-
(String s) -> {System.out.println(s);}
-
s -> {System.out.println(s);}
-
-
多个参数: 数据类型可以省略,参数类型要么都省略要么都留着
-
(int a,int b) -> {return a + b;}
-
(a,b) -> {return a + b;}
-
-
-
语句体{}
-
如果{} 中只有一条语句,可以省略大括号,分号,return
-
(int a,int b) -> {return a + b;}
-
(a,b) -> a + b
-
-
BigDecimal类
传统的double运算会出现运算结果不准确的结果
System.out.println(0.1+0.2);
方式一:
BigDecimal b1 = new BigDecimal("0.55555");
BigDecimal b2 = new BigDecimal("0.54353455");
b1.add(b2)
方式二:
调用BigDecimal提供的静态方法valueof
BigDecimal b3 = BigDecimal.valueOf(1.0234739749823);
BigDecimal b4 = BigDecimal.valueOf(1.0234738749823);
b3.add(b4)
常用方法
public BigDecimal add(另一个BigDecimal对象) | 加法 |
---|---|
public BigDecimal subtract (另一个BigDecimal对象) | 减法 |
public BigDecimal divide (另一个BigDecimal对象) | 除法 |
public BigDecimal multiply (另一个BigDecimal对象) | 乘法 |
public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式) | 除法(如果除不尽会报错)参数二: 精确到小数点后几位参数三: 舍入模式向上取整: RoundingMode.UP向下取整: RoundingMode.DOWN四舍五入: RoundingMode.HALF_UP |
---|---|
public double doubleValue() | 返回double类型结果 |
当进行除法时会出现报错的问题
System.out.println(b3.add(b4));
解决办法
b3.divide(b4,2, RoundingMode.HALF_DOWN)
举例
-
需求: double[] arr = {0.1,0.2,0.3}; 求和
public class Demo {
public static void main(String[] args) {
double[] arr = {0.1,0.2,0.3};
// 求和
// 1.定义求和变量
BigDecimal sum = BigDecimal.ZERO;
// 2.遍历数组
for (int i = 0; i < arr.length; i++) { // i 0 1 2
// 累加求和
// 第1轮 0 和 0.1 结果赋值给了sum(0.1)
// 第2轮 0.1 和 0.2 结果赋值给了sum(0.3)
// 第3轮 0.3 和 0.3 结果赋值给了sum(0.6)
sum = sum.add(BigDecimal.valueOf(arr[i]));
}
System.out.println(sum);
}
}
Object类
-
所有类都直接或者间接的继承自Object类
为什么打印对象名输出的是地址值?
println()底层源码会调用String类的valueof静态方法,valueof静态方法返回三目运算符的boolean值,会调用父类的toString方法,toString方法返回全类名和地址值。如果不想输出地址值可以重写toString方法。
同样的Object的equals方法底层也是比较的地址值,想要比较内容需要重写equals方法。
Objects
-
Objects工具类,提供了操作对象的方法
方法名 | 说明 |
---|---|
public static Boolean isNull(对象) | 判断对象是否为空 |
public static Boolean nonNull(对象) | 判断对象是否不为空 |
public static boolean equals(对象1,对象2) | 比较两个对象是否相同,避免空指针异常 |
注:Objects的equals方法可以避免空指针异常,底层做了判断,如果为空直接返回。
StringJonier
-
JDK8出现的API,可变的、操作字符串容器
-
拼接字符串的时候,不仅高效,而且代码的编写更加的方便。
构造方法 | 说明 |
---|---|
public StringJoiner (间隔符号) | 创建一个StringJoiner对象,指定拼接时的间隔符号 |
public StringJoiner (间隔符号,开始符号,结束符号) | 创建一个StringJoiner对象,指定拼接时的间隔符号、开始符号、结束符号 |
方法名 | 说明 |
---|---|
public StringJoiner add (添加的内容) | 添加数据,并返回对象本身参数可以是String、StringBuilder等类型 |
public int length() | 返回长度 ( 字符的个数) |
public String toString() | 返回一个字符串(该字符串就是拼接之后的结果) |
举例:
StringJoiner stringJoiner = new StringJoiner("-");
String s="123456789";
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; i++) {
stringJoiner.add(chars[i]+"");
}
System.out.println(stringJoiner);
包装类
自动装箱与自动拆箱
-
自动装箱: 基本数据类型可以自动转换为包装类型。
-
自动拆箱: 包装类型可以自动转换为基本数据类型。
自动装箱与自动拆箱原理
-
自动装箱: 底层自动调用了Integer.valueOf方法
-
自动拆箱: 底层自动调用了intValue方法
基本数据类型-> 字符串数据类型
public static String toString(基本数据类型) |
---|
public String toString() |
字符串类型转换成数值本身对应的数据类型
public static int parseInt(String s) |
---|
public static Integer valueOf(String s) |
Integer源码片段
public final class Integer{
private final int value;
public Integer(int value) {
// 装箱,给成员变量赋值
this.value = value;
}
public int intValue() {
// 拆箱,获取成员变量的值
return value;
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
}
Integer面试题
public class Demo {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
System.out.println(i1 == i2); // true
System.out.println("===========");
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i3 == i4); // false
}
}
总结:数值范围不同所返回的结果也不同,自动装箱,底层自动调用valueOf方法,数据在-128~127之间直接使用缓存(已经存在的数组)的数据,数据不在-128到127之间:创建新的Integer对象