java面试题-java基础

来源:互联网 发布:windows 10 QQ登录上限 编辑:程序博客网 时间:2024/06/03 14:05

1. 面向对象的概念

(1) 抽象抽象(Abstract)就是忽略事物中与当前目标无关的非本质特征,更充分地注意与当前目标有关的本质特征。从而找出事物的共性,并把具有共性的事物划为一类,得到一个抽象的概念。

(2) 封装封装Encapsulation)就是把对象的属性和行为结合成一个独立的单位,并尽可能隐蔽对象的内部细节。

(3) 继承:继承Inheritance)是一种联结类与类的层次模型。继承性是指特殊类的对象拥有其一般类的属性和行为。

(4) 多态:多态Polymorphism)是指类中同一函数名对应多个具有相似功能的不同函数,可以使用相同的调用方式来调用这些具有不同功能的同名函数。java的多态有方法重载(overload,方法有相同的名称,但是参数列表不相同)和方法重写(override子类继承父类并重写父类中已有的方法)。重载为编译时的多态性,重写为运行时的多态性。


2.面向对象五个基本原则(SOLID)

1.单一职责原则(SRP:Single responsibility principle)又称单一功能原则。它规定一个类应该只有一个发生变化的原因。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性

2.开闭原则(OCP)是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。对于扩展是开放的,对于修改是关闭的,这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。

3.里氏替换原则(LSP:Liskov Substitution Principle)任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。

4.接口隔离原则(ISP:Interface Segregation Principle)客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。使用多个专门的接口比使用单一的总接口要好。

5.依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

其它原则

1.合成聚合复用原则:优先使用聚合或合成关系复用代码。类与类之间简单的说有三种关系,Is-A关系(继承)、Has-A关系(关联)、Use-A关系(依赖)。其中,关联关系根据其关联的强度又可以划分为关联、聚合和合成。优先使用对象的合成/聚合将有助于保持每个类被封装,并集中在单个任务上。继承是一种强耦合的结构子类随父类改变而改变。

2.迪米特法则(Law of Demeter)又叫作最少知道原则(Least Knowledge Principle)。一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。


3. java数据类型

(1) 基本数据类型primitive type):Java中的基本数据类型只有8个:byteshortintlongfloatdoublecharboolean

(2) 引用数据类型reference type):除了基本数据类型外都为引用数据类型:类(class)、接口(interface)、数组、枚举(enum)。


4.下列的代码是否正确:

float f1 = 3.5;float f2 = 3;short s1 = 1;s1 = s1 + 1;s1 += 1;

java中浮点数默认为double类型,整数默认为int类型,float f1 = 3.5无法通过编译,正确写法为f1=3.5f或者进行强制类型转换。

java自动类型转换规则为:byteshort(char)intlongfloatdouble,就是存储类型小的可以自动转换成大的。int类型可以自动转换成float类型。float f2 = 3没有问题。

short s1 = 1s1 += 1可以正确编译。对short的操作,在-3276832767之间的值编译器会进行隐式的强制类型转换。bytechar类型同理,具体的值为对应包装类的MAX_VALUEMIN_VALUE。

s1+1的结果为int类型,将int类型赋值给short类型需要进去强制类型转换。


5.java包装类,下列代码的运行结果:

Integer i1 = 100;Integer i2 = 100;Integer i3 = new Integer(100);int i4 = 100;System.out.println("i1==i2:" + (i1 == i2));System.out.println("i2==i3:" + (i2 == i3));System.out.println("i3==i4:" + (i3 == i4));

为了能够将基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class)。

Java 1.5引入了自动装箱/拆箱机制,所以 i3==i4 为 true 应该没什么问题。对于i1==i2、i2==i3很容易认为要么都是true要么都是false。

运行的结果为:

i1==i2:true

i2==i3:false

i3==i4:true

以下是对class文件进行反编译后的代码:

Integer i1 = Integer.valueOf(100);Integer i2 = Integer.valueOf(100);Integer i3 = new Integer(100);int i4 = 100;System.out.println("i1==i2:" + (i1 == i2));System.out.println("i2==i3:" + (i2 == i3));System.out.println("i3==i4:" + (i3.intValue() == i4));
可见Integer自动装箱是调用了vlaueOf方法,自动拆箱是调用了intVlaue方法。i1==i2和i2==i3没有进行自动拆箱。Integer是引用类型,引用类型==操作比较的是引用的地址。i1==i2为true,可见i1、i2是同一个对象。查看valueOf方法的代码:

 public static Integer valueOf(int i) {        assert IntegerCache.high >= 127;        if (i >= IntegerCache.low && i <= IntegerCache.high)            return IntegerCache.cache[i + (-IntegerCache.low)];        return new Integer(i);    }

代码中使用了名为IntegerCache类,查看IntegerCache类

private static class IntegerCache {        static final int low = -128;        static final int high;        static final Integer cache[];        static {            int h = 127;            String integerCacheHighPropValue =                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");            if (integerCacheHighPropValue != null) {                int i = parseInt(integerCacheHighPropValue);                i = Math.max(i, 127);                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);            }            high = h;            cache = new Integer[(high - low) + 1];            int j = low;            for(int k = 0; k < cache.length; k++)                cache[k] = new Integer(j++);        }        private IntegerCache() {}    }

IntegerCache是在Integer中的静态内部类,在JVM加载Integer类的时候IntegerCache生成-128到127的Integer对象放入缓存中。

在valueOf中,传入的值为-128到127之间的直接从缓存返回对象,否则new一个。所以i1和i2是同一个对象,i3是不同的对象。

那么System.out.println(i2 >= i3); Integer i5 = 200 ; Integer i6 = 200 ;  System.out.println(i5 == i6); 的结果是什么。

在包装类中,除了Float和Double外都有这样的特性。在引用类型进行值相等比较时必须使用equal。


6.String类,下列代码的运行结果:

String s1 = "hello";String s2 = "hello";String s3 = new String("hello");System.out.println("s1==hello:" + (s1 == "hello"));System.out.println("s1==s2" + (s1 == s2));System.out.println("s2==s3" + (s2 == s3));

String类型不属于包装类,没有自动装箱的功能,代码编译后依然是String s1 = "hello"。引用类型进行==操作比较的是引用地址,s2==s3结果为false,大家都懂的。那么s1、s2、"hello"是不是同一个对象。运行结果为:
s1==hello:true
s1 == s2:true
s2 == s3:false

从结果可以知道s1、s2、"hello"是同一个对象。

在JDK1.6之后,java程序中的字面量(直接写在代码上的,如100、"hello")和常量都会放入常量池中。在创建一个字符串时,JVM首先检查字符串常量池中是否有值相等的字符串,如果有直接返回该字符串的引用地址,没有则创建,然后放到字符串常量池中,并返回新创建的字符串的引用地址。具体可以查找“逃逸分析”技术。

常见问题,下列代码创建了几个对象:

String str = new String("hello");
按照上面的说明,这行代码一共创建了2个对象。查看String的构造方法:

public String(String original) {       this.value = original.value;       this.hash = original.hash;   }
构造方法中输入的是一个String对象,可以按照String的代码创建一个MyString类:
public MyString(String original) {System.out.println(original == "hello");}
输出结果为true,所以在new String("hello")的对象和"hello"是同一个对象。在代码String str = new String("hello");中创建了一个"hello"的对象放入了常量池,另外使用new创建了一个对象str。所以创建了2个对象。


7. java访问修饰符

(1) public(公开):public修饰符修饰的类、属性和方法,任何类(本类、同包的类、子类、其他包的类)均可访问。

(2) protected(受保护):protected修饰符修饰的类、属性和方法,本类、同包的类、子类可访问,其他包的类无法访问。

(3) default(默认):没有修饰符的类、属性和方法为defualt访问级别,设置为default的类、属性和方法,本类、同包的类可访问,子类及其他包的类无法访问。

(4) private(私有):private修饰符修饰的类、属性和方法,只有在本类内部可以访问,其他的类均无法访问。


8.final关键字用法

final关键字可以用来修饰类、方法、变量(包括成员变量和局部变量)和参数。

(1)final修饰类表示该类不能被继承;
(2)final修饰方法表示该方法不能被重写,可以被继承;
(3)final修饰变量表示值一旦给定就无法改变,也就是常量;
(4)final修饰参数表示无法改变该参数的值。
private方法会隐式地被指定为final方法。在早期的Java实现版本中,会将final方法转为内嵌调用,在新的Java版本中,已不需要使用final方法进行这些优化。


9.final、finally、finalize的区别。

final:参考上面 

finally:放在try{}catch{}后面(try{}catch{}finally{})
finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行某些工作。
或者在try{}后面try{}finally{})。finally用来构造一个总是执行代码块程序无论正常执行还是发生异常,只要JVM不关闭finally块中的代码都能执行


10.static关键字用法:

static关键字可以用来修饰方法、成员变量和代码块,还可以修饰内部类。

(1)static修饰的成员变量和方法独立于该类的任何对象,它不依赖类特定的实例,被类的所有实例共享。

(2)static修饰代码块,当类被JVM加载时会执行static代码块中的代码。

(3)JDK1.5中加入了import static(静态导入),意思是导入类里的static方法和static变量,导入后可以直接使用变量名而不需要类名。如:

import static java.lang.System.out;import static java.lang.Integer.MAX_VALUE;

(4)static修饰内部类。如单例模式使用静态内部类SingletonHolder创建实例

public class Singleton {private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}private Singleton() {}public static final Singleton getInstance() {return SingletonHolder.INSTANCE;}}

在JVM加载类的时候才会创建一个Singleton实例。


11.java内部类

内部类(nested classes),面向对象程序设计中,可以在一个类的内部定义另一个类。可分为三种:

(1)在一个类(外部类)中直接定义的内部类。static修饰的为静态内部类,非static的为成员内部类。

(2)在一个方法和代码块中定义的类,局部内部类

(3)没有名字的局部内部类,匿名内部类。

成员内部类

类定义在类中,任何方法外,作为外部类的一个成员存在,与外部类的属性、方法并列可以访问外部类的私有成员或属性。

成员内部类不能定义静态成员。

在外部类的内部可以直接使用inner s=new inner()而在外部类的外部,要生成(new)一个内部类对象,需要首先要建立一个外部类对象的实例,然后再生成一个内部类对象。

Outer o=new Outer();

Outer.Inner in=o.new.Inner();

在内部类中访问实例变量:this.属性。

在内部类访问外部类的实例变量:外部类名.this.属性。

在外部类的外部访问内部类,使用out.inner。

静态内部类

static定义的成员内部类。

静态内部类只能访问外部类的静态成员,可以定义静态成员;

生成(new)一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成:

Outer.Inner in=new Outer.Inner();

而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个顶级类。

局部内部类

局部内部类定义在代码块或方法中,作用范围为定义它的代码块。

局部内部类不能定义静态变量、静态方法和静态类。

局部内部类不能是public,protected,private,static。 

局部内部类可以访问外部类的所有成员和块内的final变量。

public static void main(String[] args) {class T implements Runnable{@Overridepublic void run() {}}       Thread t = new Thread(new T());       t.start();}

匿名内部类

匿名内部类也就是没有名字的内部类,所以只有一个实例通常用来简化代码编写

一个匿名内部类一定是在new的后面,用其隐含实现一个接口或继承一个类。

匿名内部类不能有构造方法。 

因匿名内部类属于局部内部类,所以局部内部类的所有限制都对其生效。 

public static void main(String[] args) {        Runnable r = new Runnable() {            public void run() {                            }        };        Thread t = new Thread(r);        t.start();    }

内部类的优点

每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。

在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。

创建内部类对象的时刻并不依赖于外围类对象的创建。

内部类并没有令人迷惑的“is-a”关系(继承关系),他就是一个独立的实体。

内部类提供了更好的封装,除了该外围类,其他类都不能访问。

--摘自《Think in java》


12.java对象的初始化顺序

(1)类被JVM加载的时候:父类static变量→父类static代码块子类static变量子类static代码块

(2)类被实例化的时候:父类非static变量父类非static代码块父类构造方法子类非static变量子类非static代码块子类构造方法。

没有相应块的直接跳过,有多个相同块的从上往下执行。


13.switch可以接受哪些数据类型

switch可以接受能自动转型为int类型的数据,就是byte、short、char、int。在JDK1.5引入了枚举类型,switch可以接收enum类型和能自动转型为int类型的包装类。在JDK1.7后,switch可以传入字符串(String)类型。


14.两个对象值相同(x.equals(y) == true),hash code是不是一定相同

Java对于eqauls方法和hashCode方法是这样规定的:(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;(2)如果两个对象的hashCode相同,它们并不一定相同。

equals方法必须满足自反性(x.equals(x)必须返回true)、对称性(x.equals(y)返回true时,y.equals(x)也必须返回true)、传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true)和一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值),而且对于任何非null值的引用x,x.equals(null)必须返回false。


15.下列代码的运行结果是什么,方法调用传递的参数是属于值传递还是引用传递。

public static void main(String[] args) {String s1 = "s1";String s2 = "s2";swap(s1, s2);System.out.println("s1:" + s1);System.out.println("s2:" + s2);}public static void swap(String str1, String str2) {String temp = str1;str1 = str2;str2 = temp;}

java将对象作为实参传递到方法中,在方法中可以改变对象的属性,但是java方法的调用只支持参数的值传递。当一个对象实例当作参数传递到方法中时,相当于实参赋值给了形参,相当于String str1 = s1;。两个参数的值都是实例引用的地址,都可以操作对象实例,但两个是保存在栈中不同的参数,相互之间没有影响。运行结果为:

s1:s1
s2:s2

如果是引用传递,方法中的操作会改变实参。值传递中,方法操作的结果不会影响到实参。如C++的引用传递将改变实参:

int main(){string s1 = "s1";string s2 = "s2";swap(s1,s2);cout<< "s1:"<<s1<<endl;cout<< "s2:"<<s2<<endl;}void swap(string &s1,string &s2){string temp = s1;s1 = s2;s2 = temp;}输出结果:s1:s2s2:s1

16. String和StringBuilder、StringBuffer的区别

String是只读字符串,String引用的字符串内容是不能被改变的。而StringBuffer和StringBuilder是可变的字符序列。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于StringBuffer是线程同步的,StringBuilder不是线程同步的,因此StringBuilder的效率也比StringBuffer要高。

 

17.抽象类(abstract class)和接口(interface)有什么异同

抽象类和接口都不能够实例化。抽象类中可以定义构造方法、抽象方法和具体方法。而接口中不能定义构造方法并且方法全部是抽象方法。抽象类中的成员可以是private、default、protected、public,而接口中的成员只能是public的。抽象类中可以定义成员变量,而接口中只能定义常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。


18. Error和Exception有什么区别? 

Error类和Exception类的父类都是throwable类Error一般是指与虚拟机相关的问题,如系统崩溃,内存空间不足等。对于这类错误导致的应用程序中断仅靠程序本身无法恢复和预防。

Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常;程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用try。。。catch捕获或者用throws字句声明抛出,否则无法编译通过。


19.下列代码的运行结果是什么,在try{return}finally{}块中是先执行return还是先执行finally块

public String fun() {try {…………return "success";} finally {…………return "true";}}

首先在finally中执行reutrn是不好的行为。上面代码的执行顺序是 return "success";→finally块→return "true";

最后的返回值是true。如果代码块中存在finally,try中的return语句不会马上返回,而是记录下返回值然后执行finally块,finally执行完后再进行返回。如果在finally中修改了返回值,就会返回修改后的值。

所以,在try{return}finally{}块中先执行return,再执行finally块,最后再返回值。


20.下面代码的运行结果。(出自《Java编程思想》)

class Annoyance extends Exception {}class Sneeze extends Annoyance {}public class Main {public static void main(String[] args) throws Exception {try {try {throw new Sneeze();} catch (Annoyance a) {System.out.println("Caught Annoyance");throw a;}} catch (Sneeze s) {System.out.println("Caught Sneeze");return;} finally {System.out.println("Hello World!");}}}

在异常捕获中,父类型的异常可以捕获子类型的异常,所以运行结果为:
Caught Annoyance
Caught Sneeze
Hello World!

21.Java中如何实现序列化,有什么意义? 

序列化就是一种用来处理对象流的机制,将对象状态转换为可保持或传输的格式的过程。简单点的说法就是将对象转换成二进制。可以对流化后的对象进行传输或持久化。

实现序列化需要让类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的。进行序列化时使用输出流来构造对象输出流通过writeObject(Object)方法将对象写出。反序列化则可以用输入流建立对象输入流,然后通过readObject方法从流中读取对象。

序列化除了能够实现对象的传输和持久化之外,还能够用于对象的深度克隆。