面试题1. 按值传递和按引用传递的区别?
- 值传递:方法调用时,实际参数把它的值的副本传递给对应的形式参数,此时形参接收到的其实只是实参值的一个拷贝,所以在方法内对形参做任何操作都不会影响实参
- 引用传递:当参数是对象的时候,其实传递的对象的地址值,所以实参的地址值传给形参后,在方法内对形参进行操作会直接影响真实内容
- 如果传递的参数是八大基本类型的话,是值传递。如果参数传递的是对象实例、数组或者是接口的话,还是按值传递的,千万不要被外表所迷惑
- 也就是说,如果当你传递的是对象作为参数的话,首先参数会先进行拷贝一份引用执行原本的实例对象,但是一旦这个调用这个方法的结束之后,那么这个拷贝过来的实例对象的引用就会被肖销毁。
- https://blog.csdn.net/u013309870/article/details/75499175
- https://blog.csdn.net/javazejian/article/details/51192130
看下面一个例子仔细思考一下吧
public class CallByValue {
private static User user=null;
private static User stu=null;
/**
* 交换两个对象
* @param x
* @param y
*/
public static void swap(User x,User y){
User temp =x;
x=y;
y=temp;
}
public static void main(String[] args) {
user = new User("user",26);
stu = new User("stu",18);
System.out.println("调用前user的值:"+user.toString());
System.out.println("调用前stu的值:"+stu.toString());
swap(user,stu);
System.out.println("调用后user的值:"+user.toString());
System.out.println("调用后stu的值:"+stu.toString());
}
输出结果:
调用前user的值:User [name=user, age=26]
调用前stu的值:User [name=stu, age=18]
调用后user的值:User [name=user, age=26]
调用后stu的值:User [name=stu, age=18]
总结:
- 在传进来的参数是基本类型的时候,用的是值传递
- 如果传进来的参数类型是对象或者是数组类型的时候,也只是拷贝了引用的值罢了,之所以能修改引用数据是因为它们同时指向了一个对象,但这仍然是按值调用而不是引用调用
面试题2. Java的一个对象到底占用多大内存?以及如何分配对象中属性的内存的?
首先需要知道一个对象的结构:
对象头又包括三部分:MarkWord、元数据指针、数组长度。
- MarkWord:用于存储对象运行时的数据,好比 HashCode、锁状态标志、GC分代年龄等。这部分在 64 位操作系统下占 8 字节,32 位操作系统下占 4 字节。
- 指针:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪一个类的实例。
这部分就涉及到指针压缩的概念,在开启指针压缩的状况下占 4 字节,未开启状况下占 8 字节。 - 数组长度:这部分只有是数组对象才有,若是是非数组对象就没这部分。这部分占 4 字节。
实例数据就不用说了,
- 用于存储对象中的各类类型的字段信息(包括从父类继承来的)。
关于对齐填充
- Java 对象的大小默认是按照 8 字节对齐,也就是说 Java 对象的大小必须是 8 字节的倍数。若是算到最后不够 8 字节的话,那么就会进行对齐填充。
Java 中基础数据类型是在栈上分配还是在堆上分配?
我们继续深究一下,基本数据类占用内存大小是固定的,那具体是在哪分配的呢,是在堆还是栈还是方法区?大家不妨想想看! 要解答这个问题,首先要看这个数据类型在哪里定义的,有以下三种情况。
- 如果在方法体内定义的,这时候就是在栈上分配的
- 如果是类的成员变量,这时候就是在堆上分配的
- 如果是基础类型的话,就严格按照基本类型的占用内存大小进行分配的(默认开启指针压缩的情况下,就算不足4个字节,也会按照4个字节进行填充)
- 如果是引用类型的话,那占用内存的地方就是引用类型的指针(不开启指针压缩的话是占用8个字节,默认JVM是开启指针压缩的就是占用4个字节)
- 如果是类的静态成员变量,在方法区上分配的
面试题3. 深拷贝和浅拷贝的区别,怎么实现深拷贝?
他们的区别就是:
- 当如果要拷贝一个A对象,而A对象中又有一个B对象,那么如果对A拷贝的时候,重新拷贝出来一个A1对象并且重新分配内存地址,但是对于A中的B对象,仅仅只是把A1中拷贝出来的B1对象的引用指向原来的B对象而已,并没有把拷贝的B1对象也重新进行分配一个新的内存地址。这就是浅拷贝。
- 而深拷贝就是在第1的基础上,不仅重新给A1对象分配了新的内存地址,而且还给A1中的B1也重新进行分配了新的内存地址,而不只是仅仅把原本的B的引用给B1。这就是深拷贝。
这里借用一张书中的图片:
那么如果实现深拷贝呢?
- 如果想要深拷贝一个对象,这个对象必须要实现 Cloneable 接口,实现 重写clone()方法,并且在 clone 方法内部,把该对象引用的其他对