Java面试题总结 | Java基础部分(持续更新)

Java基础

一个Java文件里可以有多个类吗(不含内部类)?

  1. 一个java文件里可以有多个类,但最多只能有一个被public修饰的类;
  2. 如果这个java文件中包含public修饰的类,则这个类的名称必须和java文件名一致。

创建对象的方法

  1. 使用 new 关键字(最常用):
    ObjectName obj = new ObjectName();

  2. 使用反射的Class类的newInstance()方法:
    ObjectName obj = ObjectName.class.newInstance();

  3. 使用反射的Constructor类的newInstance()方法:
    ObjectName obj = ObjectName.class.getConstructor.newInstance();

  4. 使用对象克隆clone()方法:
    ObjectName obj = obj.clone();

  5. 使用反序列化(ObjectInputStream)的readObject()方法:
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME))) {

​ ObjectName obj = ois.readObject();}

public class ObjectCreate {
    private static final String FILE_NAME = "employee.obj";
 
    public static void main(String[] args) throws Exception {
        // 使用 new关键字 创建对象
        Employee employee = new Employee();
        employee.setName("张三");
 
        // 使用 Class类的 newInstance()方法
        // Employee employee2 = (Employee) Class.forName("Employee").newInstance();
        Employee employee2 = Employee.class.newInstance();
        employee2.setName("xxx2");
        System.out.println("Class类的newInstance()方法:" + employee2);
 
        // 使用 Constructor类的newInstance()方法
        Employee employee3 = Employee.class.getConstructor().newInstance();
        employee3.setName("xxx3");
        System.out.println("Constructor类的newInstance()方法:" + employee3);
 
        // 使用 clone()方法:类必须实现Cloneable接口,并重写其clone()方法
        Employee employee4 = (Employee) employee.clone();
        // employee4.setName("xxx4");
        System.out.println("对象clone()方法:" + employee4);
 
        // 使用 反序列化ObjectInputStream 的readObject()方法:类必须实现 Serializable接口
        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME))) {
            oos.writeObject(employee);
        }
        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME))) {
            Employee employee5 = (Employee) ois.readObject();
            System.out.println("反序列化:" + employee5);
        }
    }
}

面向对象和面向过程

  • 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。

  • 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。

  • 面向对象相比于面向过程更易扩展、复用

    扫描二维码关注公众号,回复: 16659999 查看本文章
  • 面向对象,它是注重对象的。当解决一个问题的时候,面向对象会把事物抽象成对象的概念,就是说这个问题里面有哪些对象,然后给对象赋一些属性和方法,然后让每个对象去执行自己的方法,问题得到解决。

简述自动装箱拆箱

java每一个基本数据类型都会对应着一个包装数据类型

自动装箱就是将基本数据类型转换为包装数据类型的过程

自动拆箱就是将包装数据类型转换为基本数据类型的过程

Java代码块执行顺序

父类的静态代码块

子类的静态代码块

父类的构造代码块

父类的构造方法

子类的构造代码块

子类的构造方法

普通代码块

class Father{
    static {
        System.out.println("Fatcher class");
    }
    public Father(){
        System.out.println("Father");
    }
}
class Son extends Father{
    static {
        System.out.println("Son class");
    }
    public Son(){
        System.out.println("Son");
    }
}
Fatcher class
Son class
Father
Son

java中的基本数据类型对应的字节数

short - 2个字节

int - 4个字节

long - 8个字节

float - 4个字节

double - 8个字节

char - 2个字节

byte - 1个字节

boolean - 一位

基本类型 位数 字节 默认值 取值范围
byte 8 1 0 -128 ~ 127
short 16 2 0 -32768 ~ 32767
int 32 4 0 -2147483648 ~ 2147483647
long 64 8 0L -9223372036854775808 ~ 9223372036854775807
char 16 2 ‘u0000’ 0 ~ 65535
float 32 4 0f 1.4E-45 ~ 3.4028235E38
double 64 8 0d 4.9E-324 ~ 1.7976931348623157E308
boolean 1 false true、false

包装类型和基本数据类型的场景

    public static void main(String[] args) {
        int num1 = 10;
        int num2 = 12;
        swap(num1,num2);
        System.out.println(num1 + " " + num2);
    }
    public static void swap(int a,int b){
        int temp = a;
        a = b;
        b = temp;
        System.out.println(a);
        System.out.println(b);
    }
12 10
10 12
也就是交换失败
    public static void main(String[] args) {
        int num1 = 10;
        int num2 = 12;
        swap(num1,num2);
        System.out.println(num1 + " " + num2);
    }
    public static void swap(Integer a,Integer b){
        int temp = a;
        a = b;
        b = temp;
        System.out.println(a + " " + b);
    }
也是交换失败的

java中的关键字分类

分类 关键字
访问控制 private protected public
类,方法和变量修饰符 abstract class extends final implements interface native
new static strictfp synchronized transient volatile enum
程序控制 break continue return do while if else
for instanceof switch case default assert
错误处理 try catch throw throws finally
包相关 import package
基本类型 boolean byte char double float int long
short
变量引用 super this void
保留字 goto const

final关键字

final代表不可变的,被final修饰的类不可以被继承,修饰变量不可以被修改,被final修饰的方法不可以被重写

static关键字

static关键字可以修饰类、方法、变量、初始化块、内部类,被static修饰的成员都是类成员,可以只用类名的方式进行访问。

  • 被static修饰的变量,叫做静态变量,只加载一次
  • 静态代码块也是只加载一次,不论创建多个对象都是被加载一次
  • 如果是非静态的内部类,需要通过外部类进行创建,而静态内部类不需要,且不可以访问外部类的非静态成员

字符串比较问题

        String s1 = "abc";
        String s2 = new String("abc");
        String s3 = "a"+"bc";
        System.out.println(s1 == s2);//false
        System.out.println(s1 == s3);//true
        System.out.println(s3 == s2);//false
        System.out.println(s1 == s1.intern());//true
        System.out.println(s1 == s2.intern());//true

多态

是指子类对象可以直接赋值给父类变量,在运行时依然表现出子类的特征,这意味着同一类型的对象在执行同一个方法时,可能表现出多种行为特征。

简述throw与throws的区别

throw一般是用在方法体的内部,由开发者定义当程序语句出现问题后主动抛出一个异常

throws一般用于方法声明上,代表该方法可能会抛出的异常列表。

java的异常

分为运行时异常和编译时异常,编译时异常必须显示的进行处理,运行时异常可以使用throws抛出或者try catch进行捕获

throw和throws的区别

总结下 throw 和throws 关键字的区别

1、写法上 : throw 在方法体内使用,throws 函数名后或者参数列表后方法体前
2、意义 : throw 强调动作,而throws 表示一种倾向、可能但不一定实际发生
3、throws 后面跟的是异常类,可以一个,可以多个,多个用逗号隔开。throw 后跟的是异常对象,或者异常对象的引用。
4、throws 用户抛出异常,当在当前方法中抛出异常后,当前方法执行结束(throws 后,如果有finally语句的话,会执行到finally语句后再结束。)。可以理解成return一样。

异常抛出之后,会跳转到调用这个方法或类的上一层,运行的系统会找到处理此异常的异常处理器,返回对应的错误

try-catch-finally 如何使用?

  • try块 : 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • catch块 : 用于处理 try 捕获到的异常。
  • finally 块 : 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

注意:不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。

捕获异常机制

当catch多个异常的时候,如果前面的catch先捕获了,那么后面的catch将不会捕获这个异常

    public static void main(String[] argßs) throws Exception {
    
    
        testAbove();
    }

    public static void testException() throws Exception  {
    
    
        try {
    
    
            System.out.println(10%0);
        }catch(ArithmeticException r) {
    
    
            System.out.println("Catch function exception" + r.toString());
        }
        catch (Exception e) {
    
    
            throw new Exception("testException()方法出现异常"+e.toString());
        }
    }

    public static void testAbove() throws Exception {
    
    
        try {
    
    
            testException();
            System.out.println("execute test above ");
        }catch(Exception e) {
    
    
            throw new Exception("testAbove()方法执行出现异常"+e.toString());
        }
    }
输出
    被子类捕获
Catch function exceptionjava.lang.ArithmeticException: / by zero
execute test above 

在这里插入图片描述

Java 1.8新特性

  1. 新增lambda表达式
  2. 新增函数式接口
  3. 新增stream api
  4. hashmap和concurrenthashmap实现底层优化
  5. jvm内存布局进行了修正,元数据区取代了永久代

Sting类的常见方法

和长度有关的方法
返回类型      方法名               作用
 int        length()        得到一个字符串的字符个数(一个中文是一个字符,一个英文是一个字符,一个转义字符是一个字符)

和数组有关的方法
返回类型        方法名             作用
byte[]        getBytes()       将一个字符串转换成字节数组
char[]        toCharArray()    将一个字符串转换成字符数组
String[]      split(String)    将一个字符串按照指定内容劈开

和判断有关的方法
返回类型        方法名                         作用
boolean       equals(String)                判断两个字符串的内容是否一模一样
boolean       equalsIgnoreCase(String)      忽略大小写的比较两个字符串的内容是否一模一样
boolean       contains(String)              判断一个字符串里面是否包含指定的内容
boolean       startsWith(String)            判断一个字符串是否以指定的内容开头
boolean       endsWith(String)              判断一个字符串是否以指定的内容结尾

和改变内容有关的方法
    和改变内容有关的方法,都不会直接操作原本的字符串
    而是将符合条件的字符串返回给我们,所以注意接收
返回类型        方法名                         作用
String        toUpperCase()                 将一个字符串全部转换成大写
String        toLowerCase()                 将一个字符串全部转换成小写
String        replace(String,String)        将某个内容全部替换成指定内容
String        replaceAll(String,String)     将某个内容全部替换成指定内容,支持正则
String        repalceFirst(String,String)   将第一次出现的某个内容替换成指定的内容
String        substring(int)                从指定下标开始一直截取到字符串的最后
String        substring(int,int)            从下标x截取到下标y-1对应的元素
String        trim()                        去除一个字符串的前后空格

和位置有关的方法
返回类型        方法名                     作用
char          charAt(int)               得到指定下标位置对应的字符
int           indexOf(String)           得到指定内容第一次出现的下标
int           lastIndexOf(String)       得到指定内容最后一次出现的下标

四种修饰限定符

修饰成员变量和方法:

  • public:可以被任何包下任意类的成员进行访问
  • protected:可以被该类内部的成员访问,可以被同一包下的其他类进行访问,可以被他的子类访问
  • default:可以被该类的内部成员访问,可以被同一包下的其他类进行访问
  • private:可以被该类的内部成员访问

在修饰类的时候:

  • defalut:可以被同一包下的其他类访问
  • public:可以被任意包下的任意类访问

修饰符范围

范围 private default protected public
同一类
同一包中的类
同一包中的类、不同包中的子类
所有

private修饰符

private主要用来修饰变量和方法,一般不会用来修饰类,除非是内部类。

被private修饰的变量和方法,只能在自己对象内部使用,当一个变量或方法被定义为私有变量,则在别的类中用该类实例化的对象将无法直接访问该成员变量和方法。(在自身所在类的内部,依旧可以访问)

如果是继承关系呢?也是不行的,你依然无法通过super来调用被private修饰的方法和变量。

请介绍全局变量和局部变量的区别

Java中的变量分为成员变量和局部变量,它们的区别如下:

成员变量:

  1. 成员变量是在类的范围里定义的变量;
  2. 成员变量有默认初始值;
  3. 未被static修饰的成员变量也叫实例变量,它存储于对象所在的堆内存中,生命周期与对象相同;
  4. 被static修饰的成员变量也叫类变量,它存储于方法区中,生命周期与当前类相同。

局部变量:

  1. 局部变量是在方法里定义的变量;
  2. 局部变量没有默认初始值;
  3. 局部变量存储于栈内存中,作用的范围结束,变量空间会自动的释放。

注意事项

Java中没有真正的全局变量,面试官应该是出于其他语言的习惯说全局变量的,他的本意应该是指成员变量。

构造方法的特点

  • 名字与类名相同。
  • 没有返回值,但不能用 void 声明构造函数。
  • 生成类的对象时自动执行,无需调用。

构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。

为什么要有包装类型

new对象就会在堆上分配空间,如果我们频繁的使用Integer和Byte对象的时候,这样就会频繁的访问堆空间,对垃圾回收机制会造成一定影响,影响了效率,基本数据类型只在栈上创建、使用以及销毁,提高了效率节省了空间

Java是面向对象的编程语言,为了让基本数据类型具有面向对象的特征,对其进行包装,使其具有面向对象的特征,具有属性和方法;并且在某些情况下,方法的参数必须是对象;包装类提供了更强大的方法;方便了其他对象与基本数据类型的转换

内部类

静态内部类(static 修饰类的话只能修饰内部类):静态内部类与非静态

内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向

创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的

创建。2. 它不能使用任何外围类的非 static 成员变量和方法

非静态内部类

外部类.this.成员变量
外部类.this.成员方法

成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。

虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问;

成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下

//第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建
         
//第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();

静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

外部类访问内部类的私有成员

内部类相当于外部类的一个成员,和其它成员处于同一个级别,因此可以在内部类中直接访问外部类的各个成员(包括私有属性)。
需要注意的是在外部类中要想访问内部类的各个成员(这里也包括内部类的私有属性)就必须先实例化内部类,然后才能访问。对于为什么能访问内部类的私有属性,是因为即使内部类的成员是私有的,但是也是在外部类中,和外部类的其它成员是平等的,只不过被内部类囊括是在内部中,因此访问都必须先实例化。

简述内部类及其作用

  • 成员内部类:作为成员对象的内部类。可以访问private及以上外部类的属性和方法。外部类想要访问内部类属性或方法时,必须要创建一个内部类对象,然后通过该对象访问内部类的属性或方法。外部类也可访问private修饰的内部类属性。
  • 局部内部类:存在于方法中的内部类。访问权限类似局部变量,只能访问外部类的final变量。
  • 匿名内部类:只能使用一次,没有类名,只能访问外部类的final变量。
  • 静态内部类:类似类的静态成员变量。

String和StringBuffer有什么区别?

String用于字符串操作,属于不可变类。String对象一旦被创建,其值将不能被改变。而StringBuffer是可变类,当对象创建后,仍然可以对其值进行修改。

说一说重写与重载的区别

重载发生在同一个类中,若多个方法之间方法名相同、参数列表不同,则它们构成重载的关系。重载与方法的返回值以及访问修饰符无关,即重载的方法不能根据返回类型进行区分。

重写发生在父类子类中,若子类方法想要和父类方法构成重写关系,则它的方法名、参数列表必须与父类方法相同。另外,返回值要小于等于父类方法,抛出的异常要小于等于父类方法,访问修饰符则要大于等于父类方法。还有,若父类方法的访问修饰符为private,则子类不能对其重写。

深拷贝和浅拷贝

浅拷贝

是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。

特点:

  1. 如果属性是基本类型,拷贝的就是基本类型的值;除此之外还有 String 对象、Integer,Double。例如 String 类虽然是引用类型,但是是 final 类,同时也有字符串常量池的存在,因此看到的现象也是拷贝出了新的值。

  2. 如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。例如下图中,拷贝出的 stu2 与 stu1 中,其存在的引用类型还是指向同一个地址。

  3. 实现方式:实现对象拷贝的类,需要实现 Cloneable 接口,并覆写 clone()方法。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQxiHLiv-1681012609585)(D:/学习/JAVA/面经/面试题整理版本.assets/image-20220831105407980.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sd8OblC6-1681012609585)(D:/学习/JAVA/面经/面试题整理版本.assets/image-20220831104203682.png)]

深拷贝

在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝

特点:

  1. 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。

  2. 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。例如下图中拷贝出的 stu2 与stu1 中存在的引用类型指向的是不同的区域。

  3. 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法, 进而实现了对象的串行层层拷贝。

  1. 深拷贝相比于浅拷贝速度较慢并且花销较大。

深拷贝实现方式

①让每个引用类型属性内部都重写 clone() 方法:既然引用类型不能实现深拷贝,那么我们将每个引用类型都拆分为基本类型,分别进行浅拷贝。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qdW1HtvQ-1681012609585)(D:/学习/JAVA/面经/面试题整理版本.assets/image-20220831105351133.png)]

②利用序列化:序列化这个对象,再反序列化回来。每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为transient,即将其排除在克隆属性之外。因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都能实现深拷贝.

在这里插入图片描述

【Java深入】深拷贝与浅拷贝详解_白夜行515的博客-CSDN博客

静态代码块/非静态代码块什么时候执行

类加载的时候执行并且只执行一次

非静态代码块随着对象的加载而加载

代码块执行顺序静态代码块——> 构造代码块 ——> 构造函数——> 普通代码块

继承中代码块执行顺序:父类静态块——>子类静态块——>父类代码块——>父类构造器——>子类代码块——>子类构造器

猜你喜欢

转载自blog.csdn.net/qq_43167873/article/details/130040198