Java基础知识和深入理解(针对面试)

本文深入探讨Java基础知识,涵盖面向对象编程思想的历史演变、static关键字的全面解析,以及序列化机制和JVM的基础概念,旨在帮助准备Java面试的读者掌握核心知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java基础知识面试

1. 面向对象的理解[程序设计思想]

计算机的背景介绍

  1. 在早期的计算机主要是实现算术运算的,例如:1+1+2=4等计算问题,所以前期计算机都是面向过程编程的,以实现某种复杂计算功能为主。
  2. 60年代中期,大容量、高速度计算机的出现,使计算机的应用范围迅zhi速扩大,软件开发急剧增长。随着计算机技术的不断提高,计算机被用于解决越来越复杂的问题。面向过程编程的思想不再支持实现该复杂的功能了,因此高级语言开始出现。
  3. 软件系统的规模越来越大,复杂程度越来越高,软件可靠性问题也越来越突出。原来的个人设计、个人使用的方式不再能满足要求,迫切需要改变软件生产方式,提高软件生产率,软件危机开始爆发 。70年代,即使科学家总结出多种开发方法【如:结构化方法】,但依然满足不了市场的需求。
  4. 20世纪60年代挪威计算中心发布的simula语言,首次引入了类的概念和继承机制,该语言的诞生是面向对象发展历史上的第一个里程碑。面向对象程序设计在软件开发领域引起了大的变革,极大地提高了软件开发的效率,为解决软件危机带来了一线生机。因此面向对象编程思想日益成熟,并在80年代[1972年],正式使用了面向对象的这个术语。
  5. 面向对象编程思想的繁荣阶段。从20世纪80年代中期到90年代,是面向对象语言走向繁荣的阶段。其主要表现是大批比较实用的面向对象编程语言的涌现,如 C++。
  6. 当前状况: 面向对象方法几乎覆盖了计算机软件领域的所有分支。例如,已经出现了面向对象的编程语言、面向对象的分析、面向对象的设计、面向对象的测试、面向对象的维护、面向对象的图形用户界面、面向对象的数据库、面向对象的数据结构…

参考网址:https://baike.baidu.com/item/simula/6913723?fr=aladdin

1.1 面向过程[ PO(Procedure Oriented) ]

特点:

  • 面向过程是一种以过程为中心的编程思想。
  • 分析出解决问题的所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候依次调用即可。
    面向过程

1.2 面向对象[ OO(Object Oriented) ]

  • 面向对象,是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

  • 面向对象是一种软件开发方法。面向对象的概念已经超越了程序设计和软件开发,面向对象的应用包括了数据库系统、交互式界面、应用结构、分布式系统、网络管理结构、CAD技术、人工智能等领域。
    面向对象思想

  • 面向对象,是一种对现实世界理解和抽象的方法。
  • 面向对象的思想是,将现实世界中的实体或事物,抽象成对象,分析其属性和行为。
  • 在OOP中,现实世界的所有事物全部都被视为对象,

1.3 总结

面向过程: 是一种以过程为中心的编程思想。分析出解决问题的所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候依次调用即可。

面向对象: 是把构成问题事务分解或抽象成对象,分析其属性和行为,并建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

2. static关键字解析

static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。

1.1 static关键字的用途

static方法:

static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。

class MyObject{
	private static String str1 = "staticProperty" ;
	private String str2 = "property" ;
	public MyObject(){}
	
	public void print1(){
		System.out.println(str1) ;	// 正常
		System.out.println(str2) ;	// 正常
		print2() ;	// 正常
	}
	
	public static void print2(){
		System.out.println(str1) ;	// 正常
		System.out.println(str2) ;	// 静态方法,不能访问 非静态变量
		print1() ;	// 静态方法,不能访问 非静态方法
	}
	
}
static变量:

static变量也称作静态变量。静态变量和非静态变量的区别是:

  • 静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。
  • 非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static代码块:

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

class Person{
    private Date birthDate;
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
    boolean isBornBoomer() {
        Date startDate = Date.valueOf("1946");
        Date endDate = Date.valueOf("1964");
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

1.2 static关键字的误区

1.2.1 static关键字会改变类中成员的访问权限吗?
public class Test{
	public static void main(String[] args){
		System.out.println(Person.name) ;	// 正常使用
		System.out.println(Person.age) ;	// 错误使用
	}
}
class Person{
	public static String name = "张三" ;
	private static int age = 21 ;
}

结论: static关键字并不会改变变量和方法的访问权限。

1.2.2 能通过this访问静态成员变量吗?
public class Main {  
    static int value = 33;
    public static void main(String[] args) throws Exception{
        new Main().printValue();
    }
    private void printValue(){
        int value = 3;
        System.out.println(this.value);	// 程序编译时出错,但运行时正常,显示结果 33
    }
}

总结: this和static的理解

  • this代表当前对象,那么通过new Test()来调用printValue的话,当前对象就是通过new Main()生成的对象。而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是33。在printValue方法内部的value是局部变量,根本不可能与this关联,所以输出结果是33。
  • 静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。

1.3 常见的笔试面试题

(1) 下面这段代码的输出结果是什么?
public class Test extends Base{

    static{
        System.out.println("test【Child】 static");
    }
    {
        System.out.println("Test【Child】 Black");
    }
    public Test(){
        System.out.println("test【Child】 constructor");
    }

    public static void main(String[] args) {
        new Test();
    }
}

class Base{

    static{
        System.out.println("base【parent】 static");
    }

    {
        System.out.println("Base【parent】 Black");
    }

    public Base(){
        System.out.println("base【parent】 constructor");
    }
}

运行结果:

base【parent】 static
test【Child】 static
Base【parent】 Black
base【parent】 constructor
Test【Child】 Black
test【Child】 constructor

结论:

  • 加载顺序:【父】静态代码块>【子】静态代码块>【父】代码块>【父】构造方法>【子】代码块>【子】构造方法
  • 程序的执行开始:
    1. 再执行Test类的main()方法。
    2. 必须先加载Test类【执行main方法执行】
    3. 在加载Test类的时候发现Test类继承自Base类,因此会转去先加载Base类
    4. 在加载Base类的时候,发现有static块,便执行了static块。
    5. 在Base类加载完成之后,便继续加载Test类,然后发现Test类中也有static块,便执行static块
    6. 在加载完所需的类之后,便开始执行main方法。在main方法中执行new Test()的时候会先调用父类的构造器,然后再调用自身的构造器。因此,便出现了上面的输出结果。
(2) 这段代码的输出结果是什么?
/**
 * 描述:<br> 验证 类加载的过程
 *      验证对象:   1. 静态代码【】快
 *                  2. 代码【块】
 *                  3. 构造方法
 * </>
 * @author 周志通
 * @date 2020/7/31 23:07
 **/
public class Test {
    Person person = new Person("3. Test"); // 3 相当于代码块
    static{ // 静态代码块
        System.out.println("1. Test static");  // 1
    }

    public Test() {
        System.out.println("4. Test constructor"); // 4
    }

    public static void main(String[] args) {
        new MyClass();
    }
}

class Person{
    static{
        System.out.println("3.1 Person static"); // 3.1
    }
    public Person(String str) {
        System.out.println(str + " person ");  // 3.2
    }
}


class MyClass extends Test {
    Person person = new Person("5. MyClass");      // 5
    static{
        System.out.println("2. MyClass static");    // 2
    }

    public MyClass() {
        System.out.println("6. MyClass constructor"); // 6
    }
}
	1. Test static
	2. MyClass static
	3.1 Person static
	3. Test person 
	4. Test constructor
	5. MyClass person 
	6. MyClass constructor

总结:

  • 代码的具体执行过程:
    1. 首先加载Test类。
    2. 接着执行new MyClass(),而MyClass类还没有被加载,因此需要加载MyClass类。
    3. 在加载MyClass类的时候,发现MyClass类继承自Test类,但是由于Test类已经被加载了,所以只需要加载MyClass类,那么就会执行MyClass类的中的static块。
    4. 在加载完之后,就通过构造器来生成对象。
    5. 而在生成对象的时候,必须先初始化父类的成员变量,因此会执行Test中的Person person = new Person(),而Person类还没有被加载过,因此会先加载Person类并执行Person类中的static块,接着执行父类的构造器,完成了父类的初始化,然后就来初始化自身了,因此会接着执行MyClass中的Person person = new Person(),最后执行MyClass的构造器。

参考网址1:https://www.cnblogs.com/shanqiang1/p/10786804.html
参考网址2:http://www.cnblogs.com/dolphin0520/p/3799052.html

3. java的序列化机制

3.1 序列化是什么?

  • Java序列化:是指把Java对象数据信息转换为字节序列,并将该对象的信息写入到IO流中的过程,或通过网络传输,成为Java序列化过程。
  • 反序列化: 是指从IO流中的对象,恢复成原来对象的样子。

使用案例:

/**
 * 描述:<br> 初步了解序列化机制和反序列化机制
 * 		过程:	1. User1实现了序列化接口 Serializable
 * 				2. 通过 ObjectOutputStream 对象实现将User1对象的信息序列化,并存入本地磁盘文件中
 * 				3. 最后通过 ObjectInputStream 对象实现将本地磁盘文件的信息,反序列化成 User1 对象中。
 * 		
 * 		结果:	1. 实现序列化接口 Serializable 的类,在写入IO流后,能成功反序列化成相关对象信息;
 * 					而没有实现序列化接口的类,则不能反序列化成相关对象,
 * 					并会出现 java.io.NotSerializableException 相关错误信息
 * </>
 * @author 周志通
 * @date 2020/8/2 21:25
 **/
public class SerialDemo01 {
    public static void main(String[] args) {
        serializeUser1();
        deserializationUser1();
    }
    // 1. 序列化
    private static void serializeUser1(){
        User1 user1 = new User1(1,"张三---English") ;
        try {
            ObjectOutputStream oos =
                    new ObjectOutputStream(new FileOutputStream("D:/java_test/user1.txt")) ;
            oos.writeObject(user1);
            oos.close();
        }catch (Exception e){

        }
    }
    // 2. 反序列化
    private static void deserializationUser1(){
        try {
            File file = new File("D:/java_test/user1.txt") ;
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)) ;
            System.out.println("开始反序列化:");
            User1 user1 =(User1) ois.readObject() ;
            System.out.println("反序列化结果:"+user1);
        }catch (Exception e){
			e.printStackTrack() ;
        }
    }
}
class User1 implements Serializable{
    private static final long serialVersionUID = 1L ;
    private Integer age ;
    private String name ;
	// setter、getter、toString()
}
  • 序列化的意义:序列化机制允许将实现序列化的Java对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。

  • 使用场景:所有可在网络上传输的对象都必须是可序列化的,比如RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的java对象都必须是可序列化的。通常建议:程序创建的每个JavaBean类都实现Serializeable接口。

3.2 序列化实现方式

两种方式:

  • 实现Serializable接口
  • 实现Externalizable接口

注意:

- 'serialVersionUID':验证 版本号【`在JVM底层实现`】。当不同时,会出现异常信息:InvalidCastException
1. Serializable接口
  • Serializable接口是一个标记接口,不用实现任何方法。一旦实现了此接口,该类的对象就是可序列化的。

使用案例

class Person implements Serializable {
	private static final long serialVersionUID = 1L ;
	private String name ;
	private Integer age ;
	// setter、getter
}

1.1 普通序列化

步骤:

  • 步骤一:创建一个ObjectOutputStream 输出流;
  • 步骤二:调用 ObjectOutputStream 对象的writeObject()方法输出序列化对象。
    (详情使用见👆上文对序列化介绍部分的内容)

1.2 反序列化
步骤:

  • 步骤一:创建一个ObjectInputStream 输入流;
  • 步骤二:调用 ObjectInputStream 对象的readObject()方法得到序列化后的对象【需要强制转换】。
    (详情使用见👆上文对序列化介绍部分的内容)

注意:

  • 反序列化不会调用构造方法。反序列的对象是由JVM自己生成的对象,不通过构造方法生成。

1.3 反序列化

====

2. 实现Externalizable接口:强制自定义序列化

使用案例:

public class Person implements Externalizable {
    private static final long serialVersionUID = 1L;
    private transient Long id;
    private String name;
    private transient Integer age;
    private String remarks;
    // setter、getter
    public Person() {
        System.out.println("构造器");
        this.age = 23;
    }
    public Person(Long id, String name, String remarks) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.remarks = remarks;
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(id);
        out.writeObject(name);
        out.writeObject(age);
        out.writeObject(remarks);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        // 要与 writeExternal 写入的顺序一致
        id = (Long) in.readObject();
        name = (String) in.readObject();
        age = (Integer) in.readObject();
        remarks = (String) in.readObject();
    }
}
public class Client {
    private static String filePath = "C:\\Users\\Admin\\Desktop\\test.txt";
    public static void main(String[] args) throws IOException, ClassNotFoundException {
		externalizable();
        antiExternalizable();
    }
    // 2.1 序列化 Externalizable
    private static void externalizable() throws IOException {
        Person person = new Person(1L, "张三", "第一个测试用例");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filePath));
        out.writeObject(person);
        out.close();
    }
    // 2.1 序列化 Externalizable
    private static void antiExternalizable() throws IOException, ClassNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(filePath);
        ObjectInputStream stream = new ObjectInputStream(fileInputStream);
        Person person = (Person) stream.readObject();
        System.out.println(person);
    }
}

总结:

- 1. 关键字:`transient`,在此处没有任何效果。
- 2. 通过无参构造器创建对象,因此 对象中,必须存在一个无参构造
- 3. 'writeExternal''readExternal' 分别实现 序列化操作和反序列化操作。

3.3 深入总结:

1. Java序列化算法
  1. 所有保存到磁盘的对象都有一个序列化编码号
  2. 当程序试图序列化一个对象时,会先检查此对象是否已经序列化过,只有此对象从未(在此虚拟机)被序列化过,才会将此对象序列化为字节序列输出。
  3. 如果此对象已经序列化过,则直接输出编号即可。

2. Java序列化算法潜在的问题

参考网址:Java序列化详解

4. 浅谈Java虚拟机JVM

Java高级面试问题

1. 类 String 相关源码问题

2. Java线程源码解析

3. 深识Java虚拟机JVM

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值