面试题
一、Java
1.运行机制
1)Java跨平台原理
Java实现跨平台是JVM(Java虚拟机)起的作用。如果是C/C++的编译方式,一旦换了一个平台,那么就需要重新编译一份对应的可执行代码,但是Java则不同,编译好了一份Java字节码,换到不同的平台上时,并不需要重新编译,前提是这些平台上都安装了相应平台的JVM,JVM不是跨平台的。
2)垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
垃圾回收器通常是作为一个单独的低级别的线程运行,在不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。
垃圾回收器不可以马上回收内存,程序员可以手动执行System.gc(),通知GC运行, 但是Java语言规范并不保证GC一定会执行。
3)垃圾回收机制
①分代复制垃圾回收
不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。 在Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如Http请求中的 Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。 但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较 短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。如果每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,并且生命周期长的对象依旧存在,因此引入分代回收,把不同生命周期的对象 放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。
虚拟机中的共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)和持久代(Permanent Generation)。由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。
② 标记垃圾回收
在使用标记清除算法时,未引用对象并不会被立即回收.取而代之的做法是,垃圾对象将一直累计到内存耗尽为止.当内存耗尽时,程序将会被挂起,垃圾回收开始执行.当所有的未引用对象被清理完毕时,程序才会继续执行。
标记清除算法由两个阶段组成:
标记阶段,标记所有的可访问对象。
收集阶段,垃圾收集算法扫描堆并回收所有的未标记对象。
③增量垃圾回收
简单地说,它的存在是为了解决标记清除的长停顿问题。增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。
4)JVM加载class文件的原理机制?
答:JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。
类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:
1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
2)如果类中存在初始化语句,就依次执行这些初始化语句。
类的加载是由类加载器完成的,类加载器包括:
Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
5)Java内存
堆(heap)与栈(stack)
在java中,Main函数就是栈的起始点,也是程序的起始点。程序要运行总是有一个起点的(程序执行的入口)。
概括:
1.栈是运行时的单位 , 而堆是存储的单元。
2.栈解决程序的运行问题,即程序如何执行,或者说如何处理数据,
堆解决的是数据存储的问题,即数据怎么放,放在哪儿。
在java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。
而堆则是所有线程共享的。
疑问一:为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗?
1. 从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。
这种隔离、模块化的思想在软件设计的方方面面都有体现。
2.堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。
好处: a.提供了一种有效的数据交互方式(如:共享内存)
b.堆中的共享常量和缓存可以被所有栈访问,节省了空间。
3. 栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。
由于栈只能向上增长,因此就会限制住栈存储内容的能力,
而堆不同,堆中的对象是可以根据需要动态增长的,
因此栈和堆的拆分使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。
4. 面向对象就是堆和栈的完美结合。
其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。
但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。
当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;
而对象的行为(方法),就是运行逻辑,放在栈中。
我们在编写对象的时候,其实就是编写了数据结构,也编写了处理数据的逻辑。不得不承认,面向对象的设计,确实很美。
疑问二: 堆中存什么?栈中存什么?
1. 栈存储的信息都是跟当前线程(或程序)相关的信息。(局部变量、程序运行状态、方法、方法返回值)等,
栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是
在栈中,一个对象只对应了一个4byte的引用(堆栈分离的好处)。
2. 堆只负责存储对象信息。
疑问三: 为什么不把基本类型放堆中呢?
1. 其占用的空间一般是1~8个字节—需要空间比较少,
2.而且因为是基本类型,所以不会出现动态增长的情况—长度固定,因此栈中存储就够了,如果把它存在堆中是没有什么意义的(还会浪费空间)。
疑问四: java中的参数传递是传值呢?还是传引用?
对象传递是引用值传递,原始类型数据传递是值传递
实际上这个传入函数的值是对象引用的拷贝,即传递的是引用的地址值,所以还是按值传递
堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈。
而堆是为栈进行数据存储服务的,堆就是一块共享的内存。
不过,正是因为堆和栈的分离的思想,才使得java的垃圾回收成为可能。
java中,栈的大小通过-Xss来设置,当栈中存储的数据比较多时,需要适当调大这个值,否则会出现 java.lang.StackOverflowError异常。
6)Dd
7)D
2.基础语法
1)一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?
可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致。
一个文件中可以只有非public类,如果只有一个非public类,此类可以跟文件名不同
2)&和&&的区别。
&和&&都可以用作逻辑与的运算符,&&为短路与,&不是短路与。
另外&可以做为整数的位运算符
例1:对于if(str != null&& !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。
例2:If(x33 &++y>0) y会增长,If(x33 && ++y>0)不会增长
3)java中int数据占几个字节
4)Integer与int的区别
int是java提供的8种原始数据类型之一,另外Java为每个原始类型提供了封装类,Integer是java为int提供的封装类。int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况。
如在业务中表示year,如果用int,在没有赋值时,int类型会给一个默认值0,这样就不知道这个year=0,是手工还是默认给的值,而定义成Integer则在没有赋值时是null,推荐使用封装类型。
表示钱: BigDecimal或Integer(表示分)
5)请设计一个一百亿的计算器
如果只是大整数运算,使用BigInteger就可以
如果有浮点数据参与去处,需要使用BigDecimal进行运算
Java中基本类型的浮点数运算是不精确的,需要使用BigDecimal运算,尤其是金融、会计方向的软件
6)char型变量中能不能存贮一个中文汉字?
char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中可以存储汉字。
7)short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
对于short s1 = 1; s1 = s1 + 1;由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器会提示错误,需要强制转换类型。
对于short s1 = 1; s1 += 1;由于+=是java语言规定的运算符,Java编译器会对它进行特殊处理,因此可以正确编译。
8)""和equals方法有什么区别?
操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用操作符。
如果一个变量指向的数据是对象类型的,那么这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存,此时,变量obj所对应的内存中存储的数值就是对象占用的那块内存的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用操作符进行比较。
equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。例如,对于下面的代码:
String a=new String(“foo”);
String b=new String(“foo”);
两条new语句创建了两个对象,然后用a,b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值是不相同的,所以,表达式ab将返回false,而这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true。
在实际开发中,我们经常要比较传递进行来的字符串内容是否等,例如,String input =…;input.equals(“quit”),如果一个类没有自己定义equals方法,那么它将继承Object类的equals方法,Object类的equals方法的实现代码如下:
boolean equals(Object o){
return thiso;
}
这说明,如果一个类没有自己定义equals方法,它默认的equals方法(从Object类继承的)就是使用操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用equals和使用会得到同样的结果,如果比较的是两个独立的对象则总返回false。如果你编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么你必须覆盖equals方法,由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的。
当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
hashcode这个方法是用来鉴定2个对象是否相等的。hashcode方法一般用户不会去调用,比如在hashmap中,由于key是不可以重复的,他在判断key是不是重复的时候就判断了hashcode这个方法,而且也用到了equals方法。这里不可以重复是说equals和hashcode只要有一个不等就可以了。所以简单来讲,hashcode相当于是一个对象的编码。我们一般在覆盖equals的同时也要覆盖hashcode,让他们的逻辑一致。
9)作用域public,private,protected,以及不写时的区别?
修饰符 当前类 同 包 子 类 其他包
public √ √ √ √
protected √ √ √ ×
default √ √ × ×
private √ × × ×
类的成员不写访问修饰时默认为default。默认对于同一个包中的其他类相当于公开(public),对于不是同一个包中的其他类相当于私有(private)。
受保护(protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。
Java中,外部类的修饰符只能是public或默认,类的成员(包括内部类)的修饰符可以是以上四种。
10)Java 常用包
java.lang–语言包:Java语言的基础类,包括Object类、Thread类、String、Math、System、Runtime、Class、Exception、Process等,是Java的核心类库
java.util–实用工具包:Scanner、Date、Calendar、LinkedList、Hashtable、Stack、TreeSet等;
java.NET–网络功能包:URL、Socket、ServerSocket等;
java.sql–数据库连接包:实现JDBC的类库;
java.io–输入输出包:提供与流相关的各种包;
11)Java常用接口
Comparable ,Collection,Set, List, Map, Runnable Iterable Iterator 等
12)D
13)Dd
14)d
3.面向对象
1)你对面向对象思想的理解?
面向对象编程(Object-Oriented Programming)简称OOP技术,是开发计算机应用程序的一种新方法、新思想。过去的面向过程编程中常常会导致所有的代码都包含在几个模块中,使程序难以阅读和维护,在做一些修改时常常牵一动百,使以后的开发和维护难以为继。而使用OOP技术,使用许多代码模块,每个模块都只提供特定的功能,它们是彼此独立的,可以增加代码重用的几率,更加有利于软件的开发、维护和升级。另外OOP的三大核心特性:继承、封装、多态的特性,使得在面对象编上能够设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低,所以这一编程思想是目前一种应用最为普遍的软件设计思想。
封装
封装是保证软件部件具有优良的模块性的基础,封装的目标就是要实现软件部件的“高内聚、低耦合”,防止程序相互依赖性而带来的变动影响。面向对象的封装就是把描述一个对象的属性和行为的代码封装在一个“模块”中,也就是一个类中,属性用变量定义,行为用方法进行定义,方法可以直接访问同一个对象中的属性。
继承
在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并可以加入若干新的内容,或修改原来的方法使之更适合特殊的需要,这就是继承。继承是子类自动共享父类数据和方法的机制,这是类之间的一种关系,提高了软件的可重用性和可扩展性。
多态
多态指的同一个对象,在程序不同时刻的多种运行状态。举例:水(气态,液态,固态)。
所谓多态,指的就是父类引用指向子类对象,调用方法时会调用子类的实现而不是父类的实现。多态的实现的关键在于“动态绑定”。
为使多态能运行,存在着继承或者实现关系
可以将子类的对象赋给父类/接口,以多态的形式来传递参数,增强了参数类型的灵活性。即父类(接口)引用指向子类(实现)对象
多态的好处和弊端:
好处:多态的存在提高了程序的扩展性和后期可维护性
弊端:虽然可以预先使用,但是只能访问父类中已有的功能,运行的是后期子类的功能内容。不能预先使用子类中定义的特有功能。
多态应用场景:当接口已经确定,但同一个接口在不同环境需要不同实现的时候。如:工厂模式
工厂方法模式(Factory Mehtod Pattern):定义一个用于创建对象的接口,让子类决定哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又称为工厂模式,又称为虚拟构造器模式或多态工厂模式。工厂方法模式是一种类创建型模式。
2)object中定义了哪些方法?
protected Object clone()创建并返回此对象的一个副本。
boolean equals(Object obj)指示其他某个对象是否与此对象“相等”。
protected void finalize()当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
Class<?> getClass()返回此 Object 的运行时类。
int hashCode()返回该对象的哈希码值。
void notify()唤醒在此对象监视器上等待的单个线程。
void notifyAll()唤醒在此对象监视器上等待的所有线程。
String toString()返回该对象的字符串表示。
void wait()在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void wait(long timeout)在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
void wait(long timeout, int nanos)在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。
3)Java对象初始化顺序
分为两种,一种是本类的初始化,一种是含有父类的初始化顺序。
本类的初始化顺序是:静态变量、静态初始化块、变量、初始化块、构造函数
继承类的初始化顺序是:父类静态变量、父类静态初始化块、子类静态变量、子类静态初始块、父类变量、父类初始化块、父类构造函数、子类变量、子类初始化块、子类构造函数。
【分析】
static{
System.out.println(“静态块”);
}
{
System.out.println(“初始化模块”); }
public ClassName() {
System.out.println(“构造方法”);
}
说明:
原则上回答全面的话,应该是完整的说出带有继承的这种类的初始化过程,下面有个步骤可以参考:
1.装载程序的时候首先找到的是它的基(父)类,如果有多层基(父)类则会一级一级的往上找最后找到根基(父)类。
2.执行根基础(父)类中的static初始化,再执行下一个衍生类中的static,依此类推,一直保持这个顺序。
3.此时类已经装载完毕,开始创建对象,所有的基本数据类型都会设成它们的默认值,对象句柄设为null
4.调用基础(父)类的构造方法,基础(父)类的构建采用与衍生类构造方法完全相同的处理过程。
5.构造方法初始完之后,进行变量的初始化。
6.执行构造方法中剩余的部分。
4)方法的覆盖/重写Overload和Override的区别?Overload的方法是否 可以改变返回值的类型?
Overload是重载,Override是覆盖,也就是重写。
重载Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。
重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,因为子类可以解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。是否可以改变返回值类型,在重载的定义中,与方法是什么类型返回值无关
5)abstract class(抽象类)和interface(接口)有什么区别?
含有abstract修饰符的class即为抽象类,abstract类不能创建实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。
接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。
接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(抽象方法),而没有变量和方法的实现。
抽象类:有抽象方法和非抽象方法
接口:所有方法都是抽象的
一个接口可以实现多个父接口,将实现的各个接口用,隔开
接口的特点
A:是对外暴露的规则
B:是功能的扩展
C:接口的出现降低耦合性。
耦合(类与类之间的关系)
内聚(类完成功能的能力)
编程规范:高内聚低耦合
D:接口可以多实现。如:CPU和主板、笔记本的USB插口、插座
6)abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?
abstract的method不可以是static的,因为抽象的方法是要被子类实现的,而static与子类扯不上关系!
native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与abstract混用。
synchronized和abstract合用的问题不能共用,abstract方法只能存在于抽象类或接口中,它不能直接产生对象,而默认synchronized方法对当前对象加锁,没有对象是不能加锁。
另外synchronized不能被继承,子类继承时,需要另加修改符。
7)static关键字
静态初始化块:static{ } : 使用static来修饰的自由块只在类加载的时候执行一次,通常用于初始化静态变量。
A:它只执行一次,它比main还先执行。
B:执行顺序是静态代码块–构造方法
static关键字声明属性(类属性):可以被该类的所有实例对象所共享
static关键字声明方法:可以在不创建对象的情况下,直接使用类名.方法名()的形式调用
静态方法只能访问静态成员
static属性,方法消耗大,要慎用。
静态的特点:随着类的加载而加载
优先于对象存在
对所有对象共享
可以被类名直接调用
8)是否可以从一个static方法内部发出对非static方法的调用?
不可以。因为非static方法(实例方法)是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方法调用,而static方法调用时不需要创建对象,可以直接调用。也就是说,当一个static方法被调用时,可能还没有创建任何实例对象,如果从一个static方法中发出对非static方法的调用,那个非static方法是关联到哪个对象上的呢?这个逻辑无法成立,所以,一个static方法内部发出对非static方法的调用。
9)静态变量和成员/实例变量的区别
A:调用方式
静态变量也称为类变量,可以直接通过类名调用。也可以通过对象名调用。 这个变量属于类。
成员变量也称为实例变量,只能通过对象名调用。这个变量属于对象。
B:存储位置
静态变量存储在方法区栈中的静态区。
成员变量存储在堆内存。
C:生命周期
静态变量随着类的加载而存在,随着类的消失而消失。生命周期长。
成员变量随着对象的创建而存在,随着对象的消失而消失。
D:与对象的相关性
静态变量是所有对象共享的数据。
成员变量是每个对象所特有的数据。
静态方法可以调用其它的静态方法,但是不能调用非静态方法,这个好比Java中的类变量与实例变量的关系。类变量是被所有类成员共享,而实例变量只被该实例共享
10)单态(Singleton)设计模式
在实际开发中常用到,比如数据库的链接。使用Singleton模式可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收。
单态设计模式创建一个类,应满足:
*为了避免其他程序建立该类对象,先禁止其他程序建立该类对象,即将构造函数私有化
*为了其他程序访问到该类对象,须在本类中创建一个该类私有对象
*为了方便其他程序访问到该类对象,可对外提供一个公共访问方式
Runtime类就是单例设计模式。
单例设计模式的两种方式
A:饿汉式 当类加载的时候,就创建对象。
class Single
{
private Single(){}//将构造函数私有化,不让别的类建立该类对象
private static final Single s=new Single();//自己建立一个对象
public static Single getInstance()//提供一个公共访问方式
{
return s;
}
}
B:懒汉式 当使用的使用,才去创建对象。
class Single
{
private Single(){}
private static Single s;
public static Single getInstance()
{
if(snull)
s=new Single();
return s;
}
} 饿汉式和懒汉式的区别:
饿汉式是类一加载进内存就创建好了对象;
懒汉式则是类才加载进内存的时候,对象还没有存在,只有调用了 getInstance()方法时,
对象才开始创建。
**
懒汉式是延迟加载,如果多个线程同时操作懒汉式时就有可能出现线 程安全问题,解决线程安全问题
可以加同步来解决。但是加了同步之后,每一次都要比较锁,效率就 变慢了,所以可以加双重判断来提高程序效率。
如将上述懒汉式的Instance函数改成同步:
public static Single getInstance()
{
if(snull)
{
synchronized(Single.class)
{
if(s==null)
s=new Single();
}
}
return s;
}
注:开发常用饿汉式,因为饿汉式简单安全。懒汉式多线程的时候易发生问题
11)final关键字
final类:不可被继承,如java.lang.Math就是一个 final类,不可被继承。
final变量:在初始化后不可改变变量值,用于常量定义。如果final变量是引用变量,则不可以改变它的引用对象,但可以改变对象的属性。final修饰的变量是一个常量。只能被赋值一次
final方法:不可被重写
定义常量:
//static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变。
public static final double pi=3.14;
12)内部类
定义在一个类内部的类(inner class)
内部类的对象能访问它所处类的私有数据
内部类能隐藏起来不为同一个包中其他类访问
当内部类定义在外部类的成员位置,而且非私有,则可以在其他外部类中直接建立内部类对象
格式:外部类名.内部类名 变量名 = new 外部类对象.内部类对象
如:Outer.Inner in = new Outer().new Inner()
13)匿名内部类
(1)前提:继承一个类或者实现一个接口
(2)格式:
new 父类名或者接口名()
{
重写父类方法或者实现接口中的方法。
也可以自定义其他方法。
};
(3)什么时候定义匿名内部类?
匿名内部类只是为了简化书写,匿名内部类有局限,通常定义匿 名内部类时,该类方法不超过3个
(4)匿名内部类的好处和弊端:
好处:简化代码书写
弊端:
不能直接调用自己的特有方法
不能执行强转换动作
如果该类里面方法较多,不允许使用匿名内部类
14)String是最基本的数据类型吗?
基本数据类型包括byte、int、char、long、float、double、boolean和short。
String是引用数据类型。
java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer/StringBuilder类
15)String类方法(不可改变)
Java 中的 String 不可变是因为 Java 的设计者认为字符串使用非常频繁,将字符串设置为不可变可以允许多个客户端之间共享相同的字符串。
java中字符串的处理专门有String类提供的很多API可以很方便的对字符串进行各种操作,如截取、查询、转换成其它类型。特别是在结合正则表达式可以灵活的对字符串进行筛选。
获取方法
int length() 获取字符串的长度
char charAt(int index) 获取特定位置的字符 (角标越界)
int indexOf(String str) 获取特定字符的位置(overload)
int lastIndexOf(int ch) 获取最后一个字符的位置
判断方法
boolean endsWith(String str) 是否以指定字符结束
boolean isEmpty()是否长度为0 如:“” null V1.6
boolean contains(CharSequences) 是否包含指定序列 应用:搜索
boolean equals(Object anObject) 是否相等
boolean equalsIgnoreCase(String anotherString) 忽略大小写是否相等
转换方法
String(char[] value) 将字符数组转换为字符串
String(char[] value, int offset, int count)
Static String valueOf(char[] data)
static String valueOf(char[] data, int offset, int count)
char[] toCharArray() 将字符串转换为字符数组
转换方法
String replace(char oldChar, char newChar) 替换
String[] split(String regex) 切割
String substring(int beginIndex)
String substring(int beginIndex, int endIndex)截取字串
String toUpperCase() 转大写
String toLowerCase() 转小写
16)StringBuffer与StringBuilder的区别
这两个类都实现了CharSequence接口。
-
类型不同,因为不是一个类,也没有继承关系,做参数时不能共用
-
String对象是不可变对象,不能修改值。StringBuffer是可变对象,能修改值。
-
拼接字符串时,String会产生新对象,而StringBuffer只是增加新字符,不产生新对象,因此效率高。
-
String覆盖了equals方法和hashCode方法,而StringBuffer没有覆盖equals方法和hashCode方法,所以,将StringBuffer对象存储进Java集合类中时会出现问题。
17)用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
引用变量不能重新赋值,但是引用指向的对象的内容可以变化
例1:final StringBuffer a=new StringBuffer(“immutable”);
a=new StringBuffer("");
有编译错
例2:
final StringBuffer a=new StringBuffer(“immutable”);
a.append(“123”);
正确
18)Dd
19)D
20)D
21)d
4.异常处理
1)Error、Exception区别
Error类和Exception类的父类都是throwable类,他们的区别是:
Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
2)Java中的异常处理机制的简单原理和应用。
异常是指java程序运行时(非编译)所发生的非正常情况或错误。
Java使用面向对象的方式来处理异常,它把程序中发生的每个异常也都分别封装到一个对象中,该对象中包含有异常的信息。
Java可以自定义异常类,所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception。
1、Error表示应用程序本身无法克服和恢复的一种严重问题,程序只有退的份了,例如说内存溢出和线程死锁等系统问题。
2、Exception表示程序还能够克服和恢复的问题,其中又分为运行时异常和检查异常,运行时异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉。
例如,数组越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);检查异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。
Java为运行时异常和检查异常提供了不同的解决方案,编译器强制检查异常必须try…catch处理或用throws声明继续抛给上层调用方法处理,所以检查异常也称为checked异常,而运行异常可以处理也可以不处理,所以编译器不强制用try…catch处理或用throws声明,所以运行异常也称为Runtime异常。
3)常见异常
ArithmeticException(算术异常)
ClassCastException (类转换异常)
IllegalArgumentException (非法参数异常)
IndexOutOfBoundsException (下标越界异常)
NullPointerException (空指针异常)
4)Java的异常有哪几种,有什么区别?
两大类,一般异常和运行时异常。一般异常,这些异常是在定义方法时声明抛出的,这些异常必需用try catch抛出,或throws处理,如果不处理程序将编译失败。比如:IOException、FileNotFoundException、SQLException等。
运行时异常是程序运行时可能报出的异常。可以用try catch抓取,也可以不做任何处理。例如:NullPointerException异常是一种比较常见的运行时异常。
5)try-catch-finally (不要在循环内写try…catch)
try{
// 可能会抛出特定异常的代码段
}catch(MyExceptionType myException){
// 如果myException 被抛出,则执行这段代码
}catch(Exception otherException){
//如果另外的异常otherException被抛出,则执行这段代码
} [finally{ //用于资源回收
//无条件执行的语句
}]
6)throw 和 和 throws 有什么区别?
throw 关键字用来在程序中明确的抛出异常,相反,throws 语句用来表明方法不能处理的异常。每一个方法都必须要指定哪些异常不能处理,所以方法的调用者才能够确保处理可能发生的异常,多个异常是用逗号分隔的。
7)final,finally,finalize区别
final用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM不保证此方法总被调用
8)a
9)d
10)d
5.集合
1)集合类型可以归纳为三种
Iterable
->Collection
->List
->ArrayList
->LinkedList
->Vector
->Stack->Set ->HashSet ->TreeSet
Map
->Hashtable
->HashMap
->LinkedHashMap
Collections,不属于集合,是集合类的工具类
Arrays,不属于集合类,是数据对象的工具类
- 列表(List)集合区分元素的顺序,且允许包含重复元素。
Collection:顶层接口
|—>List:列表,元素是有序的(元素带角标索引),可以有重复元素,可以有null元素。
|—>ArrayList:底层的数据结构是数组数据结构,特点是查询速度快(因为带角标),但是增删速度稍慢,因为当元素多时,增删一个元素则所有元素的角标都得改变,线程不同步。默认长度是10,当超过长度时,按50%延长集合长度。
|—>LinkedList:底层数据结构式链表数据结构(即后面一个元素记录前一个),
特点:查询速度慢,因为每个元素只知道前面一个元素,但增删速度快,因为元素再多,增删一个只要让其前后的元素重新相连即可,线程是不同步的。|—>Vector:底层数据结构是数组数据结构.特点是查询和增删速度都很慢。默认长度是10,当超过长度时,按100%延长集合长度。线程同步。
一般情况下,使用哪种List接口下的实现类呢?
如果要求增删快,考虑使用LinkedList
如果要求查询快,考虑使用ArrayList
如果要求线程安全,考虑使用Vector。
2)集(Set):Set集合中不区分元素的顺序,不允许出现重复元素。
|—>HashSet:底层数据结构是哈希表、存取速度快、元素唯一、线程不同步。|—>TreeSet:底层数据结构式二叉树。可以对Set集合中的元素进行排序。元素有序、线程不同步。
3)映射(Map):顶层接口,该集合存储的是键值对,而且键是唯一的,Map和Set很像,Set集合底层就是使用了Map集合。Map集合没有迭代器,要取出元素必须先将Map集合转换成Set集合才能遍历元素
|—>HashTable: 底层是哈希表数据结构;不可以使用null键和null值;用作键的对象必须实现hashCode和equals方法来保证键的唯一性。线程同步效率低|—>HashMap:底层是哈希表数据结构;允许使用null键和null值;线程不同步,效率高;
|—>TreeMap:底层是二叉树结构;允许使用null键和null值;线程不同步;
2)HashMap的工作原理是什么?
Java 中的 HashMap 是以键值对(key-value)的形式存储元素的。HaspMap的key可以为null
HashMap 需要一个 hash 函数,它使用 hashCode()和 equals()方法来向集合添加和检索元素。
当调用 put()方法的时候,HashMap 会计算 key 的 hash 值,然后把键值对存储在集合中合适的索引上。如果 key已经存在了,value 会被更新成新值。HashMap 的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。当put的时候大于等于容量的0.75时,会进行扩容。
多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合
3)HashMap,TreeMap,HashTable的区别?
HashTable不允许<键,值>有空值,HashMap允许<键,值>有空值。
HashTable线程同步,但是HashMap非线程同步。
HashTable中hash数组的默认大小是11,增加方式的old*2+1,
HashMap中hash数组的默认大小是16,增长方式一定是2的指数倍。
TreeMap能够把它保存的记录根据键排序,默认是按升序排序。
HashTable使用Enumeration,HashMap使用Iterator。
4)数组(Array) 和列表(ArrayList) 有什么区别?什么时候应该使用 Array 而不是 ArrayList ?
下面列出了 Array 和 ArrayList 的不同点:
Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
Array 大小是固定的,ArrayList 的大小是动态变化的。
ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢
5)ArrayList和Vector的区别
- 线程同步,Vector线程安全,ArrayList线程不安全
- 效率问题,Vector效率低,ArrayList效率高
- 增长数量,Vector以1.5倍增长,ArrayList以2倍增长
6)HashSet和TreeSet有什么区别?
HashSet是由一个 hash 表来实现的,因此,它的元素是无序的。add(),remove(),contains() 方法的时间复杂度是 O(1)。
另一方面,TreeSet 是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是 O(logn)。
7)Collections和Collection的区别
Collection是个java.util下的接口,它是各种集合结构的父接口,定义了集合对象的 基本操作方法。Collections是个java.util下的工具类,它包含有各种有关集合操作的静态方法,主要是针对集合类的一个帮助类或者叫包装类,它提供一系列对各种集合的搜索,排序,线程安全化等操作方法。
8)Comparable和Comparator接口是干什么的?列出它们的区别。
Java 提供了只包含一个 compareTo()方法的 Comparable 接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。
Java 提供了包含 compare()和 equals()两个方法的 Comparator 接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和 comparator 相等。只有当输入参数也是一个 comparator 并且输入参数和当前 comparator 的排序结果是相同的时候,这个方法才返回 true。
9)集合中那些类是线程安全类,哪 些是不安全的,哪 些是支持排序的类
线程安全类:Vector、Hashtable、Stack。
线程不安全的类:ArrayList、Linkedlist、HashSet、TreeSet、HashMap、TreeMap
支持排序的类有HashSet、LinkedHashSet、TreeSet等(Set接口下的实现都支持排序)
此题主要考查集合框架的知识。在集合框架中Collection接口为集合的根类型,提供集合操作的常用API方法,该接口下派生出两个子接口,一个是不支持排序的List接口,一个是有自身排序的Set接口,所以回答排序与不排序分别从两接口的实现中在作答。线程安全上来说,Vector类比同属于List接口的ArrayList要早,是一个线程安全的类,在JDK1.2以后才推出一个异步的ArrayList类,比Vector类效率高。同理Stack继承自Vector也线程安全的类,另外在在Map接口的实现在Hashtable也是个线程安全的类。
10)D
11)D
12)d
6.IO
1)Java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?
字节流,字符流两种类型流。
字节流继承于InputStream、OutputStream
字符流继承于Reader、Writer。
其它与IO操作相关的类都是派生至上述4个抽象类。
如字节相关的:FileInputStream、FileOutputStream类;
字符相关的:BufferedReader、BufferedWriter类
2)什么是序列化,如何实现序列化?请解释Serializable接口的作用。
我们有时候将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,例如,要将java对象存储到硬盘或者传送给网络上的其他计算机, 这个过程我们可以自己写代码去把一个java对象变成某个格式的字节流再传输,但是jre本身就提供了这种支持,我们可以调用OutputStream的writeObject方法来做,如果要让java帮我们做,要被传输的对象必须实现serializable接口,这样javac编译时就会进行特殊处理,编译的类才可以被writeObject方法操作,这就是所谓的序列化。
需要被序列化的类必须实现Serializable接口,该接口是一个标记接口,其中没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的。
例如,在web开发中,如果对象要经过分布式系统进行网络传输或通过rmi等远 程调用 ,这就需要在网络上传输对象,被传输的对象就必须实现Serializable接口。
3)java里面的io跟nio有什么区别
1)Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。
2)Java IO的各种流是阻塞的。而Java NIO的非阻塞模式
传统的阻塞式IO,每个连接必须要开一个线程来处理,并且没处理完线 程不能退出。
非阻塞式IO,由于基于反应器模式,用于事件多路分离和分派的体系结 构模式,所以可以利用线程池来处理。事件来了就处理,处理完了就把线 程归还。而传统阻塞方式不能使用线程池来处理,假设当前有10000个连 接,非阻塞方式可能用1000个线程的线程池就搞定了,而传统阻塞方式 就需要开10000个来处理。如果连接数较多将会出现资源不足的情况。非 阻塞的核心优势就在这里。
为什么会这样,下面就对他们做进一步细致具体的分析:
首先,我们来分析传统阻塞式IO的瓶颈在哪里。在连接数不多的情况下, 传统IO编写容易方便使用。但是随着连接数的增多,问题传统IO就不行 了。因为前面说过,传统IO处理每个连接都要消耗一个线程,而程序的 效率当线程数不多时是随着线程数的增加而增加,但是到一定的数量之后, 是随着线程数的增加而减少。这里我们得出结论,传统阻塞式IO的瓶颈 在于不能处理过多的连接。
然后,非阻塞式IO的出现的目的就是为了解决这个瓶颈。而非阻塞式IO 是怎么实现的呢?非阻塞IO处理连接的线程数和连接数没有联系,也就 是说处理 10000个连接非阻塞IO不需要10000个线程,你可以用1000 个也可以用2000个线程来处理。因为非阻塞IO处理连接是异步的。当某 个连接发送请求到服务器,服务器把这个连接请求当作一个请求"事件", 并把这个"事件"分配给相应的函数处理。我们可以把这个处理函数放到线 程中去执行,执行完就把线程归还。这样一个线程就可以异步的处理多个 事件。而阻塞式IO的线程的大部分时间都浪费在等待请求上了。
3)选择器上,Java IO无选择器,而NIO有选择器,Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。
4)D
5)D
6)D
7)d
7.线程
1)创建线程方式
实现多线程可以通过继承Thread类和实现Runnable接口。
(1)继承Thread
定义一个类继承Thread类
复写Thread类中的public void run()方法,将线程的任务代码封装到run 方法中
直接创建Thread的子类对象,创建线程
调用start()方法,开启线程(调用线程的任务run方法)
另外可以通过Thread的getName()获取线程的名称。
(2)实现Runnable接口;
定义一个类,实现Runnable接口;
覆盖接口的public void run()的方法,将线程的任务代码封装到run方法中;
创建Runnable接口的子类对象
将Runnabl接口的子类对象作为参数传递给Thread类的构造函数,创建 Thread类对象
(原因:线程的任务都封装在Runnable接口子类对象的run方法中。
所以要在线程对象创建时就必须明确要运行的任务)。
调用start()方法,启动线程。
两种方法区别:
(1)实现Runnable接口避免了单继承的局限性
(2)继承Thread类线程代码存放在Thread子类的run方法中
实现Runnable接口线程代码存放在接口的子类的run方法中;
在定义线程时,建议使用实现Runnable接口,因为几乎所有多线程都 可以使用这种方式实现
推荐使用线程池
2)启动一个线程是用run()还是start()?
启动一个线程是调用start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。
3)进程和线程的区别是什么?
进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。
线程又叫做轻量级进程。
4)并发编程的3个概念:原子性、可见性、有序性
并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
原子性:一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行。
可见性:当多个线程同时访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:程序执行的顺序按照代码的先后顺序执行。
对于单线程,在执行代码时jvm会进行指令重排序,处理器为了提高效率,可以对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证保存最终执行结果和代码顺序执行的结果是一致的。
Java语言对原子性、可见性、有序性的保证
1、原子性
在java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断,要么执行,要么不执行。
X=10; //原子性(简单的读取、将数字赋值给变量)
Y = x; //变量之间的相互赋值,不是原子操作
X++; //对变量进行计算操作
X = x+1;
语句2实际包括两个操作,它先要去读取x的值,再将y值写入,两个操作分开是原子性的。合在一起就不是原子性的。
语句3、4:x++ x=x+1包括3个操作:读取x的值,x+1,将x写入
注:可以通过 synchronized和Lock实现原子性。因为synchronized和Lock能够保证任一时刻只有一个线程访问该代码块。
2、可见性
Java提供了volatile关键字保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值立即被其他的线程看到,即修改的值立即更新到主存中,当其他线程需要读取时,它会去内存中读取新值。
Synchronized和Lock也可以保证可见性,因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中,
3、有序性
在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
5)线程生命周期
线程的生命周期:一个线程从它创建到启动,然后运行,直到最后执行完的整个过程。
新建状态:即创建一个新的线程对象,注意新创建的线程对象如果没有调 用start()方法将永远得不到运行。
就绪状态:当新的线程对象调用start()方法时,就进入了就绪状态,进入就 绪状态的线程不一定立即就开始运行。
运行状态:进入运行状态的线程,会由CPU处理其线程体中的代码。
阻塞状态:运行状态的线程有可能出现意外情况而中断运行,比如进行IO 操作,内存的读写,等待键盘输入数据(注意不是出错,出错将提前终止 线程)而进入阻塞状态。当阻塞条件解除后,线程会恢复运行。但其不是 立即进入运行状态,而是进入就绪状态。
终止状态:当线程中run()方法语句执行完后进入终止状态。
6)sleep()和wait()区别
sleep()方法是Thread类中方法,而wait()方法是Object类中的方法。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是 他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态,在 调用sleep()方法的过程中,