Java封神之路:Java面试备战(十)

6、IO流

6.1 什么是序列化,如何实现序列化

序列化是一种处理对象流的机制,所谓对象流就是对对象的内容进行流化。可以对流化后的对象传输于网络之间,序列化是为了解决对象流进行读写操作时所引发的问题。

序列化实现:将需要被序列化的类实现Serializable接口

7、网络编程

8、异常处理

8.1 Java中如何自定义异常

继承Exception类

//或者继承RuntimeException(运行时异常) 
public class MyException extends Exception {
    
     
 
  private static final long serialVersionUID = 1L; 
 
  // 提供无参数的构造方法
  public MyException() {
    
     
  } 
 
  // 提供一个有参数的构造方法,可自动生成
  public MyException(String message) {
    
     
    super(message);// 把参数传递给Throwable的带String参数的构造方法 
  } 
 
} 

8.2 Java当中两种异常的类型是什么?他们有什么区别

Java中两种异常,分别是受检查的异常和不受检查的异常,也被称为编译型异常和运行时异常

受检查的异常(编译异常):这种异常需要使用throws语句在方法上或者类上声明,或者是使用try…catch进行捕获。一般像IDEA这样智能的编译器可以直接检测出来

不受检查的异常(运行时异常):程序运行后发生的异常,例如数组下标越界,OOM等

8.3 Java中Exception和Error有什么区别

Exception和Error都是Throwable的子类。

Exception是程序在正常运行中,可以预料的意外情况,并且能够进行相应的处理

Error导致程序处于非正常、不可恢复的状态

8.4 throw和throws有什么区别

throw:

1、作用在方法上,表示抛出具体异常,由方法体内的语句处理

2、具体向外抛出的动作,所以它抛出的是一个异常实体类。若执行了throw一定是抛出了某种异常

throws:

1、作用在方法的声明上,表示如果抛出异常,则有该方法的调用者来进行异常处理

2、主要是声明这个方法会抛出某种类型的异常,让使用者知道异常类型

3、出现异常是一种可能性,并不一定会真的发生

9、类和对象

9.1 类和对象的关系

类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。

类和对象好比图纸和实物的关系,模具和铸件的关系。

9.2 面向过程和面向对象的区别

两者都是软件开发思想,先有面向过程,后有面向对象。在大型项目中,针对面向过程的不足推出了面向对象开发思想

比喻

面向过程是蛋炒饭,面向对象是盖浇饭。盖浇饭的好处就是“菜”“饭”分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是“可维护性”比较好,“饭” 和“菜”的耦合度比较低。

区别

编程思路不同: 面向过程以实现功能的函数开发为主,而面向对象要首先抽象出类、属性及其方法,然后通过实例化类、执行方法来完成功能。

封装性:都具有封装性,但是面向过程是封装的是功能,而面向对象封装的是数据和功能。

面向对象具有继承性和多态性,而面向过程没有继承性和多态性,所以面向对象优势是明显。

9.3 面向对象的特征有哪些方面?请用生活中的例子来描述

面向对象的三大特征:封装、继承、多态。

抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么

封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口

生活中看电视,液晶电视非常薄,但里面的构造却十分复杂,作为用户,我们只需要知道如何使用就好了

继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段

多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对A系统来说都是透明的(就像电动剃须刀是A系统,它的供电系统是B系统,B系统可以使用电池供电或者用交流电,甚至还有可能是太阳能,A系统只会通过B类对象调用供电的方法,但并不知道供电系统的底层实现是什么,究竟通过何种方式获得了动力)。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

9.4 静态内部类和内部类有什么区别

静态内部类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。

静态内部类可以有静态成员(方法,属性),而非静态内部类则不能有静态成员(方法,属性)。

非静态内部类能够访问外部类的静态和非静态成员。静态内部类不能访问外部类的非静态成员,只能访问外部类的静态成员。

实例化方式不同:

  1. 静态内部类:不依赖于外部类的实例,直接实例化内部类对象

  2. 非静态内部类:通过外部类的对象实例生成内部类对象

9.5 类加载的顺序与多态调用

public class ClassLoadTest {
    
    
    public static void main(String[] args) {
    
    
        A a=new B();
        System.out.println(a.x);

    }
}

class A{
    
    
     int x=5;

    static {
    
    
        System.out.println("static A");
    }

    {
    
    
        System.out.println("AAAAAAAAAAAAAAA");
    }

    public A(){
    
    
        System.out.println("A的构造函数");
    }

}

class B extends A{
    
    
     int x=10;

    static {
    
    
        System.out.println("static B");
    }

    {
    
    
        System.out.println("BBBBBBBBBBBB");
    }

    public B(){
    
    
        System.out.println("B的构造函数");
    }

}

9.6 知道字节码吗?字节码有哪些?Integer x=5,int y=5,比较x==y都经过了哪些步骤

1.字节码的运行是在栈中进行的。

2.通过JDK中的编译器,用javac(java Compiler)命令编译源代码为字节码文件(.class),给JVM使用; 然后通过classLoader类加载器加载字节码; java源码 --> 编译成.class字节码 --> CL加载字节码 --> 字节码校验器 --> 字节码解释器 --> 硬件

3.字节码指令有哪些: monitorenter指令、monitorexit指令:在字节码对应位置加锁解锁;
iadd/ ladd/ fadd/ dadd指令:int/long/float/double的加法,弹出栈顶两个数字,求和之后压入栈顶;sub:减法
load指令: 将局部变量加载到栈; store指令: 把栈中的数据保存到局部变量表
new指令:创建对象; dup指令:复制顶部操作数的栈值

4.字节码增强技术在spring AOP、ORM框架、热部署中的应用。 // todo

字节码角度讲int y=5单纯压入int类型变量,赋值为5,而Integer x=5则会调用Integer.valueOf()的方法。 x==y会使用到if_icmp指令比较。

9.7 讲讲类加载机制,都有哪些类加载器,这些类加载器都加载哪些文件

当我们的Java代码编译完成后,会生成对应的 class 文件。接着我们运行java Demo命令的时候,我们其实是启动了JVM 虚拟机执行 class 字节码文件的内容。而 JVM 虚拟机执行 class 字节码的过程可以分为七个阶段:加载、验证、准备、解析、初始化、使用、卸载。

类加载器的分类

  • 第一个:启动类/引导类:Bootstrap ClassLoader
    这个类加载器使用C/C++语言实现的,嵌套在JVM内部,java程序无法直接操作这个类。
    它用来加载Java核心类库,如:JAVA_HOME/jre/lib/rt.jar、resources.jar、sun.boot.class.path路径下的包,用于提供jvm运行所需的包。

并不是继承自java.lang.ClassLoader,它没有父类加载器

它加载扩展类加载器和应用程序类加载器,并成为他们的父类加载器

出于安全考虑,启动类只加载包名为:java、javax、sun开头的类

  • 第二个:扩展类加载器:Extension ClassLoader
    Java语言编写,由sun.misc.Launcher$ExtClassLoader实现,我们可以用Java程序操作这个加载器
    派生继承自java.lang.ClassLoader,父类加载器为启动类加载器

从系统属性:java.ext.dirs目录中加载类库,或者从JDK安装目录:jre/lib/ext目录下加载类库。我们就可以将我们自己的包放在以上目录下,就会自动加载进来了。

  • 第三个:应用程序类加载器:Application Classloader
    Java语言编写,由sun.misc.Launcher$AppClassLoader实现。
    派生继承自java.lang.ClassLoader,父类加载器为启动类加载器

它负责加载环境变量classpath或者系统属性java.class.path指定路径下的类库

它是程序中默认的类加载器,我们Java程序中的类,都是由它加载完成的。

我们可以通过ClassLoader#getSystemClassLoader()获取并操作这个加载器

  • 第四个:自定义加载器
    一般情况下,以上3种加载器能满足我们日常的开发工作,不满足时,我们还可以自定义加载器
    比如用网络加载Java类,为了保证传输中的安全性,采用了加密操作,那么以上3种加载器就无法加载这个类,这时候就需要自定义加载器

9.8 手写类加载的Demo

要想实现自己的类加载器,只需要继承ClassLoader类即可。而我们要打破双亲委派规则,那么我们就必须要重写loadClass方法,因为默认情况下loadClass方法是遵循双亲委派的规则的。

public class CustomClassLoader extends ClassLoader{

    private static final String CLASS_FILE_SUFFIX = ".class";

    //AppClassLoader的父类加载器
    private ClassLoader extClassLoader;

    public CustomClassLoader(){
        ClassLoader j = String.class.getClassLoader();
        if (j == null) {
            j = getSystemClassLoader();
            while (j.getParent() != null) {
                j = j.getParent();
            }
        }
        this.extClassLoader = j ;
    }

    protected Class<?> loadClass(String name, boolean resolve){

        Class cls = null;
        cls = findLoadedClass(name);
        if (cls != null){
            return cls;
        }
        //获取ExtClassLoader
        ClassLoader extClassLoader = getExtClassLoader() ;
        //确保自定义的类不会覆盖Java的核心类
        try {
            cls = extClassLoader.loadClass(name);
            if (cls != null){
                return cls;
            }
        }catch (ClassNotFoundException e ){

        }
        cls = findClass(name);
        return cls;
    }

    @Override
    public Class<?> findClass(String name) {
        byte[] bt = loadClassData(name);
        return defineClass(name, bt, 0, bt.length);
    }

    private byte[] loadClassData(String className) {
        // 读取Class文件呢
        InputStream is = getClass().getClassLoader().getResourceAsStream(className.replace(".", "/")+CLASS_FILE_SUFFIX);
        ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
        // 写入byteStream
        int len =0;
        try {
            while((len=is.read())!=-1){
                byteSt.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 转换为数组
        return byteSt.toByteArray();
    }

    public ClassLoader getExtClassLoader(){
        return extClassLoader;
    }
}


9.9 对象在java中是怎么存储的

在Java中,所有的对象都被动态地分配在堆上。这与C++不同,C++的对象要么分配在栈中,要么分配在堆上。在C++中,我们用new()来分配对象,这个对象就会被分配到堆上,如果不是全局的或者静态的,那么就会分配到栈上。

在Java中,只有我们在申明一个类型变量的时候,只创建一个引用(内存不会为对象分配)。为了给一个对象分配内存,我们必须用new()。所以对象总是分配在堆上。

9.10 对象头信息里面有哪些东西

由于Java面向对象的思想,在JVM中需要大量存储对象,存储时为了实现一些额外的功能,需要在对象中添加一些标记字段用于增强对象功能,这些标记字段组成了对象头。

1.1.普通对象

|--------------------------------------------------------------|
|                     Object Header (64 bits)                  |
|------------------------------------|-------------------------|
|        Mark Word (32 bits)         |    Klass Word (32 bits) |
|------------------------------------|-------------------------|

1.2.数组对象

|---------------------------------------------------------------------------------|
|                                 Object Header (96 bits)                         |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |    Klass Word(32bits) |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|

猜你喜欢

转载自blog.csdn.net/weixin_54707168/article/details/113975540
今日推荐