java基础面试题

jdk,jre,jvm

编程、编译、运行

编程:java程序员写的.java代码,c程序员写的.c代码,python程序员写的.py代码

编译:机器只认识0011的机器语言,把.java,.c,.py的代码做转化让机器认识的过程

运行:让机器执行编译后的指令

java语言的跨平台是通过虚拟机实现的,java语言不是直接运行在操作系统里面的,而是运行在虚拟机中的。针对于不同的操作系统,安装不同的虚拟机就可以了。

什么是字节码?字节码的好处是什么?

java中的编译器和解释器:

java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个共同的接口。

编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来讲虚拟机代码转换为特定系统的机器码执行。在java中,这种供虚拟机理解的代码叫做字节码,它不面向任何特定的处理器,只面向虚拟机。

每一种平台的解释器是不同的,但是实现的虚拟机是相同的。java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了java的编译与解释并存的特点。

采用字节码的好处:

java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,java程序无需重新编译便可在多种不同的计算机上运行。

JDK JRE

JDK是java的开发工具包

JDK = JVM+核心类库+开发工具+JRE

JRE = JVM+核心类库+运行工具

在这里插入图片描述

GC如何判断对象可以被回收

  • 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。
  • 可达性分析法:从GCRoots开始向下搜索,搜索所走过的路径成为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象

引用计数法,可能会出现A引用了B,B又引用了A,这时候就算他们都不再使用了,但一位内相互引用,计数器=1永远无法被回收

GC Roots的对象有:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

基本语法

注释

单行注释 //

多行注释 /* */

文档注释 /** */

字符型常量和字符串常量

字符型常量是单引号(‘ ’)引起来的一个字符,字符串常量是多引号(“ ”)引起的0个或者若干个字符

字符常量相当于一个整型值(ASCII值),可以参加表达式的运算;字符串常量代表一个地址值(该字符串在内存中存放的位置)。

占内存大小:字符常量只占两个字节,字符串常量占若干个字节。

在这里插入图片描述

String、StringBuffer、StringBuilder

String使用final修饰的,不可变,每次操作都会产生新的String对象

StringBuffer和StringBuilder都是在原对象上操作

StringBuffer是线程安全的,StringBuilder线程不安全

StringBuffer方法是synchronized修饰的

性能:StringBuilder>StringBuffer>String

标识符和关键字的区别

标识符是指用来标识某个实体的一个符号,在不同的应用环境下有不同的含义。在计算机编程语言中,标识符是用户编程时使用的名字,用于给变量、常量、函数、语句块等命名,以建立起名称与使用之间的关系。标识符通常由字母和数字以及其他字符组成。

1、标识符由数字(09)和字母(AZ 和 a~z)、美元符号($)、下划线(_)以及 Unicode 字符集中符号大于 0xC0 的所有符号组合构成(各符号之间没有空格)。

2、标识符的第一个符号为字母、下划线和美元符号,后面可以是任何字母、数字、美元符号或下划线。

一般标识符长度是由机器上的编译系统决定的,一般的限制为8字符

关键字就是在编程语言中被赋予了特点涵义的英文单词,在Java中一共有53个关键字。

自增自减

自增自减运算符优先级高于赋值运算符

后缀自增满足先引用后自增的规律,是因为后缀自增在自增前会将数值存入“操作数栈”,接着完成自增操作,最后操作数栈中自增前的数会被赋值操作引用。这样一来既满足了自增自减运算符优先级高于赋值运算符又满足了后缀自增先引用后自增的规律。

泛型,类型擦除

Java中的泛型是伪泛型,元素在存入集合时,会使用泛型来判断存入的参数,参数存入集合之后,会进行类型擦除,将参数转换成Object类型,从集合中拿出参数时会进行强转,转换成泛型所对应的类型。

==、equals()、hashCode()

==可以用来判断基本数据类型和引用数据类型,当==用来判断基本数据类型时,会比较基本数据类型的值,当==用来判断引用数据类型时,会比较两个引用数据类型的地址值是否相等。

.equals()只能用来比较引用数据类型,当使用.equals()来判断引用数据类型时,会使用重写的equals()方法判断两个引用数据类型是否相等。如果没有重写equals()方法,则会调用Object中的equals()方法判断引用数据类型是否相等

在HashMap、HashTable等数组中,计算存储元素的位置的时候需要调用到hashcode方法来判断插入的位置而不是调用equals方法(节约性能),而一般的类中都是调用equals方法来判断两个元素是否相等,如果只重写了equals方法而不重写hashcode方法,就会导致这个类只可以用equals判断是否相等,但是添加到不可重复集合中的时候无法工作。

hashCode介绍

hashCode()的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()定义在JDK的Object.java中,Java中的任何类都包含有hashCode()函数。散列表存储的是键值对(key-value),它的特点是:能根据”键“快速的检索出对应的”值“。这其中就利用到了散列码(可以快速找到所需要的对象)

为什么要有hashCode

对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有值,如果没有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals()方法来检查两个对象是否真的相同。如果两者相同,HashSet就不会让其假如操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。

  • 如果两个对象相等,则hashcode一定相同
  • 两个对象相等,对两个对象分别调用equals方法都返回true
  • 两个对象有相同的hashcode值,他们也不一定是相等的
  • 因此,equals方法被覆盖过,则hashcode方法也必须被覆盖
  • hashcode()默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

final

最终的

  • 修饰类:表示类不可被继承
  • 修饰方法:表示方法不可被子类覆盖,但是可以重载
  • 修饰变量:表示变量一旦被赋值就不可以更改它的值
  1. 修饰成员变量
  • 如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。
  • 如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值

​ 2.修饰局部变量

系统不会为局部变量进行初始化,局部变量必须由程序员显式初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对变量在赋值),也可以不指定默认值,而在后面的代码中对final变量赋初值(仅一次)。

为什么局部内部类和匿名内部类只能访问局部final变量?

内部类和外部类属于同一个级别,内部类不会因为定义在方法中就随着方法的执行完毕就被销毁。

这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的“copy”。这样就好像延长了局部变量的生命周期。

将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也跟着改变,怎么解决问题呢?

就将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。使得局部变量和内部类内建立的拷贝保持一致。

基本数据类型

基本数据类所占字节数

byte:1 short:2 int:4 long:8 float:4 double:8 char:2 boolean:1

自动装箱和自动拆箱

自动装箱:把一个基本类型的值赋值给一个引用变量,系统可以自动将基本类型包装成引用类型。

自动拆箱:需要一个基本类型的值时,但实际上传入的是包装类的对象,系统会自动把对象“剥”开,得到它的值。

public class{
    public static void main(String[] args){
        Integer i = 10;
        int n = i;
    }
}

反编译后:

public class{
    public static void main(String[] args){
        Integer i = Integer.valueOf(10);
        int n = i.intValue();
    }
}

数据类型的封装类,以及中间的区别

为了对基本数据类型进行更多方便的操作,java就针对每一种基本数据类型提供了对应的类类型,也就是基本数据类型相对应的封装类

方法

方法的几种类型

方法的常见格式:

访问修饰符 返回类型 方法名(参数列表){方法体}

方法的几种类型:

无参无返回值、无参有返回值、带参无返回值、有参有返回值

返回值就是对方法里面的语句进行运行后所得到的返回结果

静态方法的特殊性

静态方法可以直接使用类名+静态方法名调用,不会产生该类的对象。同时null可以转化成为各种类对象,转化之后的类对象同样可以调用静态方法,但是调用实例方法时会出现空指针异常。

实例方法以及静态方法的区别

静态方法属于整个类所有,因此不需要实例化,可以直接调用。实例方法必须先实例化,创建一个对象,才能进行调用。

静态方法只能访问静态成员,不能访问实例成员;而实例方法可以访问静态成员和实例成员。

值传递和引用传递的区别

1、java没有引用传递

2、java传递对象确实传递对象的引用,但是是传递的对象引用的副本

3、java传递的是引用的副本,而不是引用本身,所以引用没有传递,所以这个不叫引用传递。

重载和重写

1、定义不同:重载是定义相同的方法名、参数不同,重写是子类重写父类的方法
2、范围不同:重载是在一个类中,重写是子类与父类之间的
3、多态不同:重载是编译时的多态性,重写是运行时的多态性
4、参数不同:重载的参数个数、参数类型、参数的顺序可以不同,重写父类子方法参数必须相同
5、修饰不同:重载对修饰范围没有要求,重写要求重写方法的修饰范围大于被重写方法的修饰范围

多态是一个类需要表现出多种形态,子类重写父类的方法,使子类具有不同的方法实现

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。

重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法

浅拷贝和深拷贝

基本数据类型直接存储在栈中,引用数据类型在栈中存储引用,真实的数据放在堆内存内。

深拷贝和浅拷贝都是只针对Object和Array这样的引用数据类型的。

浅拷贝只赋值指向某个对象的指针,而不赋值对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟元对象不共享内存,修改新对象不会改到原对象。

面向对象

面向对象和面向过程

面向过程的优缺点:

效率高,面向过程强调代码的胆小精悍,善于结合数据结构来开发高效率的程序。

流程明确,具体步骤清楚,便于节点分析。

缺点是:需要深入的思考,耗费精力,代码重用性低,扩展能力差,维护起来难度比较高,对复杂业务来说,面向过程的模块化难度较高,耦合度也比较高。

面向对象的优缺点:

结构清晰,程序便于模块化,结构化,抽象化,更加符合人类的思维方式;

封装性,将事务高度抽象,从而便于流程中的行为分析,也便于操作和自省;

容易扩展,代码重用率高,可继承,可覆盖;

实现简单,可有效地减少程序的维护工作量,软件开发效率高。

面向对象的缺点:效率低,面向对象在面向过程的基础上高度抽象,和代码底层的直接交互的机会非常少,从而不适合底层开发和游戏甚至多媒体开发;复杂性,对于事务开发而言,事务本身是面向过程的,过度的封装导致事务本身的复杂性变高。

成员变量和局部变量的区别

成员变量是独立于方法外的变量,局部变量是类的方法中的变量。

修饰符:成员变量可以被public,protect,private,static等修饰符修饰,而局部变量不能被控制修饰符及static修饰;两者都可以定义成final型。

成员变量存储在堆,局部变量存储在栈。局部变量的作用域仅限于定义它的方法,在该方法的外部无法访问它。成员变量的作用域在整个类内部都是可见的,所有成员方法都可以使用它。如果访问权限允许,还可以在类的外部使用成员变量。

局部变量的生存周期与方法的执行期相同。当方法执行到定义局部变量的语句时,局部变量被创建;执行到它所在的作用域的最后一条语句时,局部变量被销毁。类的成员变量,如果是实例成员变量,它和对象的生存期相同。而静态成员变量的生存期是整个程序运行期。

成员变量在类加载或实例被创建时,系统自动分配内存空间,并在分配空间后自动为成员变量指定初始化值,初始化值为默认值,基本类型的默认值为0,符合类型的默认值为null。(被final修饰且没有static的必须显式赋值),局部变量在定义后必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。

局部变量可以和成员变量同名,且在使用时,局部变量具有更高的优先级,直接使用同名访问,访问的是局部变量,如需要访问成员变量可以使用this关键字访问。

对象实体和对象引用的区别

对象引用存储在栈内存中,指向堆内存中的对象实体。对象实体不能被直接访问,只能通过对象引用间接访问,多个对象引用可以指向一个对象实体。当没有任何一个对象引用指向对象实体时,对象实体就会被垃圾回收器回收,至于什么时候回收,就要看垃圾处理器的心情了。

临时对象:如

System.out.println("I am Java!");

其中被打印的字符串就是一个临时对象,打印语句执行完成后,没有任何对象引用指向该字符串,该字符串对象就会被回收。

构造方法的作用,特点

构造方法的作用是创建对象的同时对成员变量进行初始化。

  • 构造方法一定与类同名
  • 构造方法无返回值类型
  • 构造方法可以没有(默认一个无参构造方法),也可以有多个构造方法,他们之间构成重载关系
  • 如果定义有参构造函数,则无参构造函数将被自动屏蔽
  • 构造方法不能被集成
  • 构造方法不能手动调用,在创建类实例的时候自动调用构造方法

封装、继承、多态

封装就是把类的属性私有化(private修饰),再通过公有方法(public)进行访问和修改

为什么要封装?

1、追踪变化:可以在set方法中,编写代码来追踪属性的改变记录

2、修改底层实现:在修改属性名时,不会影响外部接口对属性的访问

3、校验数据:可以在set方法中,校验传来的数据是否符合属性值的设定范围,防止无效数据的乱入

继承

如果子类继承了父类,那么子类就可以复用父类的方法和属性,并且可以在此基础上新增方法和属性

多态

字面意思就是一个类可以有多种表现形态,多态主要是用来创建可扩展的应用程序

同一个接口,用不同的实例来执行不同的操作

接口和抽象类的区别

  • 抽象类可以存在普通成员函数,而接口中只能存在publicabstract方法。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的
  • 抽象类只能继承一个,接口可以实现多个

接口的设计目的,是对类的行为进行约束,也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。

抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为,且其中一部分行为的实现方式一致时,可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来。

抽象类是对类本质的抽象,表达的是is a的关系。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。

而接口是对行为的抽象,表达的是like a的关系。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。

List和Set的区别

  • List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator去除所有元素,再逐一遍历,还可以使用get(int index)获取指定下标的元素
  • Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素,再注意遍历各个元素

ArrayList和LinkedList区别

ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList

LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历

遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。

另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexOf对list进行了遍历,当结果为空时会遍历整个列表。

异常

Java中的异常体系

Java中所有异常都来自顶级父类Throwable。

Throwable下有两个字类Exception和Error。

Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。

Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。

RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致编译不通过。

反射

动态代理

程序为什么需要代理?代理长什么样?

代理可以无侵入式的给对象增强其他功能

代理里面就是对象要被代理的方法

java通过什么来保证代理的样子?

通过接口保证,后面的对象和代理需要实现同一个接口

接口中就是被代理的所有方法

如何为java对象创建一个代理对象?

java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:

在这里插入图片描述

package lianxi;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 类的作用:
 * 创建一个代理
 *
 * 形参:
 *  被代理的对象
 *
 * 返回值:
 *  给明星创建的代理
 *
 *  需求:
 *
 *      外面的人想要蔡徐坤唱一首歌
 *      1、获取代理的对象
 *          代理对象 = ProxyUtil.createProxy(大明星的对象);
 *      2、再调用代理的唱歌方法
 *          代理对象.唱歌的方法();
 * */
public class ProxyUtil {
    public static test createProxy(caixukun caixukun){
        test test1 = (test) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(), //参数一:用于指定用哪个类加载器,去加载生成的代理类
                new Class[]{test.class}, //参数二:指定接口用于生成的代理长什么样,也就是有哪些方法
                new InvocationHandler() { //参数三:用来指定生成的代理对象要干什么
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if("sing".equals(method.getName())){
                    System.out.println("准备话筒,收钱");
                }else if("dance".equals(method.getName())){
                    System.out.println("准备场地,收钱");
                }
                //去找大明星开始唱歌或者跳舞
                //代码的表现形式:调用大明星里面唱歌或者跳舞的方法
                return method.invoke(caixukun , args);
            }
        });
        return test1;
    }
}

package lianxi;

public class caixukun implements test{
    @Override
    public String sing() {
        return "sing";
    }

    @Override
    public void dance() {
        System.out.println("dance");
    }
}

package test;

import lianxi.ProxyUtil;
import lianxi.caixukun;
import lianxi.test;

public class Main {
        public static void main(String[] args){
                test proxy = ProxyUtil.createProxy(new caixukun());
                String sing = proxy.sing();
                System.out.println(sing);
                proxy.dance();
        }
}

反射

反射允许对封装类的字段,方法和构造函数的信息进行编程访问

获取class对象的三种方式

  1. Class.forName(“全类名”);
  2. 类名.class
  3. 对象.getClass

在这里插入图片描述

java类加载器

JDK自带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。

BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件

ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和class类

AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件

继承ClassLoader实现自定义类加载器

Class类中用于获取构造方法的方法

Constructor<?>[] getConstructors();//返回所有公共构造方法对象的数组
Constructor<?>[] getDeclaredConstructors();//返回所有构造方法对象的数组
Constructor<?>[] getConstructor(Class<?>...parameterTypes);//返回单个公共构造方法对象
Constructor<?>[] getDeclaredConstructor(Class<?>...parameterTypes);//返回单个构造方法对象

Constructor类中用于创建对象的方法

T newInstance(Object... initargs);//根据指定的构造方法创建对象
setAccessible(boolean flag);//设置为true,表示取消访问检查

Class类中用于获取成员变量的方法

Field[] getFields();//返回所有公共成员变量对象的数组
Field[] getDeclaredFields();//返回所有成员变量对象的数组
Field getField(String name);//返回单个公共成员变量对象
Field getDeclaredField(String name);//返回单个成员变量对象

Field类中用于创建对象的方法

void set(Object obj , Object value);//赋值
Object get(Object obj);//获取值

获取成员方法

Class类中用于获取成员方法的方法

Method[] getMethods();//返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods();//返回所有成员方法对象的数组,不包括继承的
Method getMethod(String name ,Class<?>...parameterTypes);//返回单个公共成员方法对象
Method getDeclaredMethod(String name,Class<?>...parameterTypes);//返回单个成员方法对象

Method类中用于创建对象的方法

Object invoke(Object obj , Object...args);//运行方法
//参数一:用obj对象调用该方法
//参数二:调用方法的传递的参数(如果没有就不写)
//返回值:方法的返回值

Spring

spring是什么?

轻量级的开源的J2EE框架。它是一个容器框架,用来装javabean(java对象),中间层框架(万能胶)可以起一个连接作用,比如把struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简洁

Spring是一个轻量级的控制反转(ioc)和面向切面(AOP)的容器框架

  • 从大小与开销两方面而言Spring都是轻量级的
  • 通过控制反转的技术达到松耦合的目的
  • 提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发
  • 包含并管理应用对象的配置和生命周期,这个意义上是一个容器
  • 将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架

对AOP的理解

当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力,也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。

日志代码往往水平的散布在所有对象层次中,而与他所散布到的对象的核心功能毫无关系。

在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外做一些事情,在某个方法执行之后额外的做一些事情。

谈谈你对IOC的理解

容器概念、控制反转、依赖注入

IOC容器:实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点,@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名是用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map里

这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,通过DI注入

控制反转:

没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点时候,自己必须主动去创建对象B或者是用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

引入IOC容器之后,对象A和对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。

如何实现一个IOC容器

1、配置文件配置包扫描路径

2、递归包扫描获取.class文件

3、反射、确定需要交给IOC管理的类

4、对需要注入的类进行依赖注入

  • 配置文件中指定需要扫描的包路径
  • 定义一些注解,分别表示控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解
  • 从配置文件中获取需要扫描的包的路径,获取到当前路径下的文件信息以及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储
  • 遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这些对象
  • 遍历这个IOC容器,获取每一个类的实例,判断里面是否有依赖其他类的实例,然后进行递归注入

BeanFactory和ApplicationContext有什么区别

ApplicationContext是BeanFactory的子接口

ApplicationContext提供了更完整的功能:

  • 继承MessageSource,因此支持国际化
  • 统一的资源文件访问方式
  • 提供在监听器中注册bean的事件
  • 同时加载多个配置文件
  • 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层
  1. BeanFactory采用的是延迟加载形式注入Bean,即只有在使用某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFactory加载后,直至第一次使用调用getBean方法才会抛出异常。
  2. ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean,确保当你需要的时候,你就不用等待,因为他们已经创建好了。
  3. 相对于基本的BeanFactory,ApplicationContext唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
  4. BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
  5. BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值