第1章 Java基础

1.1 关于 JVM JDK 和 JRE 详细通俗的解答

  • JVM: 是一种运行Java字节码的虚拟机,它针对不同的操作系统(Windows、Linux、macOS)有特定的实现,目的是让相同的字节码在不同的操作系统下面,得到相同的结果。
  • 字节码:它是一种.class文件,是一种只有JVM可以理解的代码,不面向其他任何处理器,只面向JVM虚拟机。
  • 字节码的好处:字节码使得Java程序拥有良好的移植性,解决了传统解释型语言执行效率低的问题,无需重新编译程序就可在多种不同的操作系统下面直接运行。
  • Java程序从源代码到执行过程:
  • JDK:是功能齐全的Java SDK,它拥有JRE所有的功能,还有编译器(javac)和工具(javadoc),它可以创建和编译程序。
  • JRE:是java运行时的环境,它不能创建程序。
    如果只是为了运行程序,那么只需装JRE即可,如果需要编程那么需要装JDK。但是有时候不在计算机进行编程也需要装JDK,因为有些其他软件需要JDK环境的支持(比如:Neo4j,在使用Neo4j图数据库前,需要预装JDK环境, 因为Neo4j是用java编写的。使用JSP来部署Web应用程序也需要JDK,因为应用服务程序服务器会将JSP转换为Java Servlet,需要使用JDK来编译Servlet)。

1.2 Java和C++的区别?

  • Java和C++都是面向对象的语言,都支持继承、封装和多态。
  • Java不提供指针来访问内存,使得程序内存更加安全。
  • Java类继承是单继承,C++支持多继承。Java可以通过接口来实现多继承。
  • Java有自动内存管理机制,C++需要程序员手动释放无用的内存。

1.3 什么是 Java 程序主类,应⽤程序和⼩程序的主类有何不同?

  • 一个程序中可以有多个类,但是只能有一个主类,主类是java程序执行的入口。
  • 在java应用程序中,主类是指包含main()方法的类,在java小程序中,主类是指继承自JApplet或Applet的子类。
  • 在java应用程序中,主类不一定要求是public类,在java小程序中,主类必须是public类。

1.4 字符常量和字符串常量的区别?

  • 字符常量是用单引号引起的一个字符,字符串常量是双引号引起的若干个字符。
  • 字符常量相当于一个整型值,可以参与表达式运算;字符串常量代表一个地址值。
  • 字符常量占2个内存,字符串常量占若干个字节。

1.5 构造器 Constructor 是否可被重写?重写和重载的区别?

构造器不可以被重写,但是可以被重载。

1.6 重写和重载的区别?

  • 重载:发生在编译期,在同一个类中,可以对任意的方法重载,不只局限于对构造器进行重载。方法名必须相同,必须修改参数列表(个数、类型、顺序),返回值类型、访问修饰符可以不同。
  • 重写:发生在运行期,在子类中,是子类对父类的允许访问的方法的实现过程进行重新编写。对构造函数不可以重写。要求方法名、返回值类型、参数列表必须相同,而且子类的访问修饰符范围要大于父类。(外表样子不可以改变,但是内部逻辑可以改变)。

1.7 String 为什么是不可变的?

String类中使用final关键字来修饰字符数组来保存字符串(private final char value[]),在Java 9以后,改用byte[]数组来存储字符串(private final byte value[])

1.8 String StringBuffer 和 StringBuilder 的区别是什么?

  • 是否可变性:String由于是字符串常量,所以是不可变的。StringBuffer和StringBuilder是继承自AbstractStringBuiler类(也是用char[] value 来保存字符串,但是没有用final来修饰),所以StringBuffer和StringBuilder是可变的。
  • 线程安全性:由于String是字符串常量,所以它是线程安全的。StringBuffer对方法或对调用的方法加了同步锁,所以StringBuffer是线程安全的。但是StringBuilder没有对方法加同步锁,所以StringBuilder是线程不安全的。
  • 性能方面:需要对String进行修改的时候,每次都要生成一个新的String对象,然后将指针指向新的String对象。但是StringBuffer进行修改的时候,都是对自身进行修改,不是生成新的对象并且对其进行改变。相同情况下,使用StringBuilder仅能提升大约10%左右的性能,但是要冒着线程不安全的风险。

1.9 自动装箱与自动拆箱?

  • 自动装箱:将基本类型用它们对应的引用类型包装起来,装箱过程是通过调用包装器的valueOf方法实现的。(int->Integer)
  • 自动拆箱:将包装类型转换成基本数据类型,拆箱过程是通过调用包装器的 xxxValue方法实现的。(Integer->int)
Integer i = 10;  //装箱 int n = i; //拆箱
public class Main {
   
    public static void main(String[] args) {
            
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
        System.out.println(i1==i2, i3==i4);
    }
}
输出:true, false
原因:
public static Integer valueOf(int i) {
   
	if(i >= -128 && i <= IntegerCache.high)
		return IntegerCache.cache[i + 128];
else
        return new Integer(i);
}

在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。

上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象。

1.10 在⼀个静态方法内调用⼀个非静态成员为什么是非法的?

因为调用静态方法中的方法可以不通过对象来调用,所以在静态方法里,不能调用非静态变量,也不可以访问非静态变量成员。

1.11 在 Java 中定义⼀个不做事且没有参数的构造方法的作用?

因为Java程序在执行子类的构造方法之前,如果子类没有通过super()方法来调用父类特定的构造方法,那么会直接调用父类中无参构造方法。这时候,如果父类只定义了有参构造函数,没有定义无参构造函数,那么就会报错。

1.12 接口和抽象类的区别?

  • 接口的默认修饰符是public,抽象方法的修饰符可以有public、protected和default(不能有private,因为抽象方法就是为了被重写)。
  • 接口中不能存在实现方法,抽象类里面可以有非抽象方法。
  • 一个类可以实现多个接口(java通过接口实现多继承),一个类智能实现一个抽象类。接口本身可以通过extends来扩展多个接口。

1.13 成员变量与局部变量的区别?

成员变量 局部变量
在类中的位置不同 在类中方法外面 在方法或者参数列表中
在内存中的位置不同 在堆中(方法区中的静态区) 在栈中
生命周期不同 随着对象的创建而存在,随着对象的消失而消失 随着方法的调用或者代码块的执行而存在,随着方法的调用完毕或者代码块的执行完毕而消失
初始值 有默认初始值 没有默认初始值,使用之前需要赋值,否则编译器会报错

1.14 什么是方法的返回值?返回值在类的方法的作用?

  • 方法的返回值是调用一个方法后执行的结果。
  • 作用:接收本方法的结果,便于其他操作。

1.15 类构造方法的作用?如果一个类没有声明构造方法,那么可以执行吗?

  • 作用:对类对象进行初始化。
  • 可以执行,因为会有默认不带参的构造方法。

1.16 构造方法有哪些特性?

  • 名字和类名相同。
  • 没有返回值,但是也不能用void声明。
  • 当生成类对象的时候,会自动执行,无需调用。

1.17 静态方法和实例方法的区别?

  • 外部调用静态方法的时候:类名.方法名 or 对象名.方法名。外部调用实例方法:对象名.方法名【调用静态方法无需创建对象
  • 静态方法只能访问静态成员变量 or 静态方法。实例方法还可以访问实例变量和实例方法。

1.18 对象的相等or对象的引用相等有何区别?

  • 对象相等比较的是内存中的内容是否相等。
  • 对象的引用相等比较的是指向的内存地址是否相同。

1.19 在调用子类构造方法之前,为什么要先调用父类没有参数的构造方法?

给子类进行初始化。

1.20 ==和equals()的区别?

  • ==:判断两个对象的地址是否相等。
    • 如果是基本数据类型,那么比较值是否相同;
    • 如果是引用数据类型,那么比较内存地址是否相同。
  • equals():也是判断两个对象是否相同。(一般情况下,都进行覆盖,也就是比较内容)
    • 类没有覆盖equals(),等价于“==”。
    • 类覆盖equals(),若内容相同,返回true。
public class test {
       
	public static void main(String[] args) {
           
		String a = new String("ab"); // a是一个引用 
		String b = new String("ab"); // b是另一个引用,内容相同 
		String aa = "ab"; // 放在常量池中
		String bb = "ab"; // 从常量池中查找 
		if (aa == bb) // true 
			System.out.println("aaWXbb");        
		if (a == b) // false,不是同一个对象 
			System.out.println("aWXb");        
		if (a.equals(b)) // true 比较内容 
			System.out.println("aEQb");        
		if (42 == 42.0) {
    // true 
			System.out.println("true");        
		}    
	} 
}

1.21 java中为什么只有值传递?

为什么只有值传递

1.22 java中关于final关键字总结?

  • 如果final用在变量前面,那么这个变量一旦被初始化就不能被修改;如果final用在引用类型前面,那么这个对象一旦被初始化就不能被指向另外一个对象;如果final用在类前面,那么该类不能被继承,从而所有方法都被隐式指定为final方法;
  • 类中所有private方法都被隐式地指定为final;
  • 使用final的好处: 锁定类中的方法,防止类被继承修改类中的方法。

1.23 关于序列化问题?(有待理解)

1. 什么是序列化和反序列化?
序列化:将对象转换成字节序列的过程。
反序列化:将字节序列转换成对象的过程。
2. 怎么序列化?
需要被序列化的类必须实现Serializable接口(implements Serializable)。
3. 为什么序列化?
用于对象的持久化、远程通信和跨进程访问。
4. 序列化会遇到什么问题,怎么解决?

  • transient修饰的属性,不会被序列化。
  • 静态static属性也不会被序列化。
  • 实现Serializable接口的时候,一定要给serialVersionUID赋值。
  • 当属性是对象的时候,对象也有实现序列化接口。

5. transient作用:阻止那些被transient关键字修饰的变量被序列化,当对象被反序列化时,可以不被持久化和恢复。只能修饰变量,不能修饰方法和类。

1.24 深拷贝和浅拷贝的区别?(有待理解)

  • 浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递。clone()方法可以实现浅拷贝。
  • 深拷贝:对基本数据类型进行值传递,对引用数据类型创建一个新的对象,并且复制内容。

1.25 Java中关于IO流?(有待整理)