Java面试常见知识点总结(三)
21.volatile关键字:
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
(1) 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
(2) 禁止进行指令重排序。
volatile只提供了保证访问该变量时,每次都是从内存中读取最新值,并不会使用寄存器缓存该值—每次都会从内存中读取。
而对该变量的修改,volatile并不提供原子性的保证。
由于及时更新,很可能导致另一线程访问最新变量值,无法跳出循环的情况, 多线程下计数器必须使用锁保护。
22.super和this关键字:
(1) 调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
(2) super()和this()类似, 区别是: super从子类中调用父类的构造方法,this()在同一类内调用其它方法。
(3) super()和this()均需放在构造方法内第一行。
(4) 尽管可以用this调用一个构造器,但却不能调用两个。
(5) this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
(6) this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量, static方法,static语句块。
(7) 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
23.OuterClass类:
1) 为什么使用内部类?
使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,使用内部类还能够为我们带来如下特性:
(1) 内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独。
(2) 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
(3) 创建内部类对象的时刻并不依赖于外围类对象的创建。
(4) 内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
(5) 内部类提供了更好的封装,除了该外围类,其他类都不能访问。
2) 内部类分类:
(一) 成员内部类:
(1) Inner 类定义在 Outer 类的内部,相当于 Outer 类的一个成员变量的位置,Inner 类可以使用任意访问控制符,如 public 、 protected 、 private 等。
(2) Inner 类中定义的 show() 方法可以直接访问 Outer 类中的数据,而不受访问控制符的影响,如直接访问 Outer 类中的私有属性age。
(3) 定义了成员内部类后,必须使用外部类对象来创建内部类对象,而不能直接去 new 一个内部类对象,即:内部类 对象名 = 外部类对象.new 内部类( );
(4) 编译上面的程序后,会发现产生了两个.class 文件: Outer.class,Outer$Inner.class{}。
(5) 成员内部类中不能存在任何static 的变量和方法, 可以定义常量:
● 因为非静态内部类是要依赖于外部类的实例,而静态变量和方法是不依赖于对象的,仅与类相关, 简而言之:在加载静态域时,根本没有外部类,所在在非静态内部类中不能定义静态域或方法,编译不通过; 非静态内部类的作用域是实例级别。
● 常量是在编译器就确定的, 放到所谓的常量池了。
★ 外部类是不能直接使用内部类的成员和方法的,可先创建内部类的对象,然后通过内部类的对象来访问其成员变量和方法;
★ 如果外部类和内部类具有相同的成员变量或方法,内部类默认访问自己的成员变量或方法,如果要访问外部类的成员变量,可以使用 this 关键字,如:Outer.this.name。
(二) 静态内部类: 是 static 修饰的内部类。
(1) 静态内部类不能直接访问外部类的非静态成员,但可以通过 new 外部类().成员 的方式访问
(2) 如果外部类的静态成员与内部类的成员名称相同,可通过“类名.静态成员”访问外部类的静态成员;如果外部类的静态成员与内部类的成员名称不相同,则可通过“成员名”直接调用外部类的静态成员。
(3) 创建静态内部类的对象时,不需要外部类的对象,可以直接创建 内部类 对象名 = new 内部类()。
(三) 方法内部类:访问仅限于方法内或者该作用域内。
(1) 局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。
(2) 只能访问方法中定义的 final 类型的局部变量, 因为: 当方法被调用运行完毕之后,局部变量就已消亡了。但内部类对象可能还存在, 直到没有被引用时才会消亡。此时就会出现一种情况,就是内部类要访问一个不存在的局部变量; 使用final修饰符不仅会保持对象的引用不会改变,而且编译器还会持续维护这个对象在回调方法中的生命周期. 局部内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数; 防止被篡改数据,而导致内部类得到的值不一致。在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的,毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变,这是难以理解和不可接受的,所以为了保持参数的一致性,就规定使用 final来避免形参的不改变。
(四) 匿名内部类:
(1) 匿名内部类是直接使用 new 来生成一个对象的引用;
(2) 对于匿名内部类的使用它是存在一个缺陷的,就是它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,该类的定义会立即消失,所以匿名内部类是不能够被重复使用;
(3) 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口;
(4) 匿名内部类中是不能定义构造函数的,匿名内部类中不能存在任何的静态成员变量和静态方法;
(5) 匿名内部类中不能存在任何的静态成员变量和静态方法,匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法;
(6) 匿名内部类初始化:使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。
24.Spring的事务传播特性:
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
25.Servlet的生命周期:
Servlet的生命周期分为5个阶段:加载、创建、初始化、处理客户请求、卸载。
(1) 加载:容器通过类加载器使用servlet类对应的文件加载servlet。
(2) 创建:通过调用servlet构造函数创建一个servlet对象。
(3) 初始化:调用init方法初始化。
(4) 处理客户请求:每当有一个客户请求,容器会创建一个线程来处理客户请求。
(5) 卸载:调用destroy方法让servlet自己释放其占用的资源。
26.Java反射机制:
Java反射机制概念:Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
Java反射机制主要提供了以下功能:
(1) 在运行时判断任意一个对象所属的类;
(2) 在运行时构造任意一个类的对象;
(3) 在运行时判断任意一个类所具有的成员变量和方法;
(4) 在运行时调用任意一个对象的方法;
(5) 生成动态代理。
27.Struts:
Struts的工作流程: 在web应用启动时就会加载初始化ActionServlet, ActionServlet从struts-config.xml文件中读取配置信息, 把它们存放到各种配置对象, 当ActionServlet接收到一个客户请求时, 将执行如下流程.
(1) 检索和用户请求匹配的ActionMapping实例, 如果不存在, 就返回请求路径无效信息;
(2) 如果ActionForm实例不存在, 就创建一个ActionForm对象, 把客户提交的表单数据保存到ActionForm对象中;
(3) 根据配置信息决定是否需要表单验证. 如果需要验证, 就调用ActionForm的validate()方法;
(4) 如果ActionForm的validate()方法返回或返回一个不包含ActionMessage的ActuibErrors对象, 就表示表单验证成功;
(5) ActionServlet根据ActionMapping所包含的映射信息决定将请求转发给哪个Action, 如果相应的Action实例不存在, 就先创建这个实例, 然后调用Action的execute()方法;
(6) Action的execute()方法返回一个ActionForward对象, ActionServlet在把客户请求转发给ActionForward对象指向的JSP组件;
(7) ActionForward对象指向JSP组件生成动态网页, 返回给客户;
为什么要用Struts?
JSP、Servlet、JavaBean技术的出现给我们构建强大的企业应用系统提供了可能。但用这些技术构建的系统非常的繁乱,所以在此之上,我们需要一个规则、一个把这些技术组织起来的规则,这就是框架,Struts便应运而生。 基于Struts开发的应用由3类组件构成:控制器组件、模型组件、视图组件(MVC)。
28.堆(heap)栈(stack)的区别?
1) JAVA的JVM的内存:
堆区:
(1) 存储的全部是对象, 每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令) .
(2) jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身.
栈区:
(1) 每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中.
(2) 每个栈中的数据(原始类型和对象引用)都是私有的, 其他栈不能访问.
(3) 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令).
2) 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆, 程序员不能直接地设置栈或堆。
3) 栈的优势是, 存取速度比堆要快, 仅次于直接位于CPU中的寄存器。但缺点是, 存在栈中的数据大小与生存期必须是确定的, 缺乏灵活性。另外, 栈数据可以共享。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
4) 在C语言里堆(heap)和栈(stack)里的区别:
heap:是由malloc之类函数分配的空间所在地。地址是由低向高增长的。
stack:是自动分配变量,以及函数调用的时候所使用的一些空间。地址是由高向低减少。
(1) 管理方式不同:栈(stack)由编译器管理;堆(heap)由程序员管理。
(2) 空间大小不同:win32中,堆(heap)可达4G;VC中栈默认1M(可以修改)。
(3) 碎片问题:堆(heap)易产生;栈(stack)不会。
(4) 生长方向不同:堆(heap)生长方向是向上的,也就是向着内存增加的方向;栈(stack)相反。
(5) 分配方式不同:堆(heap)是动态的,没有静态的堆;栈(stack)有两种:动态和静态。
(6) 分配效率不同:栈(stack),系统提供底层支持,有专门的寄存器存放栈地址,效率高;堆(heap),由库函数提供支持,效率底。