java面试题总结

来源:互联网 发布:网络黄金egd裴蕾抓了吗 编辑:程序博客网 时间:2024/05/19 00:10

集合

1、获取字符串中每一个字母出现的次数。比如给定字符串”abcdabdeeadb”,输出结果:”a(3)b(3)c(1)d(3)e(2)”

法一:定义26个统计变量。遍历字符串,得到每一个字符进行判断,对应的统计变量++即可,输出拼接输出结果。但是很明显,造成浪费。
法二:
      1、定义一个Map集合。
      2、把字符串转换为字符数组。
      3、遍历字符数组,得到每一个字符。
      4、把这个字符到Map集合中查找看有没有这个字符存在,如果不存在,就把该字符作为键,1作为值存储;如果存在,就把值++,然后重
      新存储该键和值。
      5、定义一个字符串缓冲区
      6、遍历TreeMap集合,获取每一个键值对元素拼接
      7、把字符串缓冲区转换为字符串输出。

选择使用Map集合的哪一个类型呢?
可以发现输出是按照字母顺序排列的,所以我选择使用TreeMap,这样键会自动排序。

为什么要要用StringBuilder呢?
因为String对象是不可改变的。每次使用 System.String类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。在需要对字符串执行重复修改的情况下,与创建新的 String对象相关的系统开销可能会非常昂贵。如果要修改字符串而不创建新的对象,可以使用System.Text.StringBuilder类。例如,当在一个循环中将许多字符串连接在一起时,使用 StringBuilder类可以提升性能。

public class TestDemo {    public static void main(String[] args) {        Scanner scanner = new Scanner(System.in);        System.out.println("请输入一个字符串:");        String str = scanner.nextLine();        //把字符串转换为字符数组        char[] charStr = str.toCharArray();        TreeMap<Character, Integer> tm = new TreeMap<Character, Integer>();        for(char ch:charStr){            //获取这个字符对应的值。通过返回值看这个字符是否存在            Integer i = tm.get(ch);            //如果不存在,存入集合,值设为1            if(i == null){                tm.put(ch, 1);            }else{                //如果存在,把这个键对应的值加1再存入集合中。                i++;                tm.put(ch, i);            }        }        //定义字符串缓冲区变量        StringBuilder sb = new StringBuilder();        //按照输出格式进行拼接        Set<Character> keySet = tm.keySet();        for(Character key:keySet){            Integer value = tm.get(key);            sb.append(key).append("(").append(value).append(")");        }        //把字符串缓冲区转换为字符串输出        String result = sb.toString();        System.out.println(result);    }}

输出:

请输入一个字符串:asbdakshdkgasa(3)b(1)d(2)g(1)h(1)k(2)s(3)

2、HashMap和Hashtable的区别是什么?
Hashtable:线程安全,效率低。不允许null键和null值。
HashMap:线程不安全,效率高。允许null键和null值。

public class HashtableDemo {    public static void main(String[] args) {        HashMap<String, String> hm = new HashMap<String, String>();        hm.put("lili", "21");        hm.put("xiong", "22");        hm.put(null, null);        hm.put("caicai", "13");        System.out.println(hm);        System.out.println("------");               Hashtable<String, String> ht = new Hashtable<String, String>();        ht.put("lili", "21");        ht.put("xiong", "22");        //ht.put(null, null);           //编译虽然不报错,但是运行就报错        ht.put("caicai", "13");        System.out.println(ht);    }}

输出:

{null=null, lili=21, xiong=22, caicai=13}------{xiong=22, lili=21, caicai=13}

3、List,Set,Map等接口是否都继承自Map接口?
List、Set不是继承自Map接口,他们继承自Collection接口
Map接口本身就是一个顶层接口


4、Collection和Collections的区别是什么?
Collection:是单列集合的顶层接口。有子接口List和Set。
Collections:是针对集合操作的工具类。有对集合进行排序和二分查找的方法。


5、Map集合的四种遍历方式是哪四种?
可以去看我的这篇文章:http://blog.csdn.net/qq_36748278/article/details/77921523


6、TreeSet保证元素的唯一性和有序性的原理是什么?

可以去看我的这篇文章:http://blog.csdn.net/qq_36748278/article/details/77915801


7、HashSet保证元素的唯一性的原理是什么?
可以去看我的这篇文章:http://blog.csdn.net/qq_36748278/article/details/77842660


8、如何给ArrayList对象里面添加字符串?
可以去看我的这篇文章:http://blog.csdn.net/qq_36748278/article/details/76736235


实参与形参

java的基本数据类型是传值调用,对象引用类型是传引用。
当传值调用时,改变的是形参的值,并没有改变实参的值,实参的值可以传递给形参,但是,这个传递是单向的,形参不能传递回实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。

当引用调用时,如果参数是对象,无论对对象做了何种操作,都不会改变实参对象的引用,但是如果改变了对象的内容,就会改变实参对象的内容。

代码1:

    public static void testStringBuffer(StringBuffer sb){          sb.append("java");    }     public static void main(String[] args) {          StringBuffer sb = new StringBuffer("my ");        testStringBuffer(sb);          System.out.println("sb=" + sb.toString());    }  

输出:sb=my java
对于这段代码,我们可以知道sb是个实参,它是通过new一个对象生成的,是一个对象引用,它指向的对象引用是”my “,因此它传递给testStringBuffer()方法中的参数也是一个引用,所以在方法内部对sb指向的引用中的数据进行的操作会反应在sb对象引用上,所以输出的是”sb=my java”


代码2:

public static String anaString(String str){           str = str + "_APPEND";           return str;      }      public static void main(String[] args) {          String s = "TEST";          anaString(s);          System.out.println("result = " + s);          s = anaString(s);          System.out.println("result_1 = " + s);     }  

输出:

result = TESTresult_1 = TEST_APPEND

我们可以知道String是引用类型对象,参数传递的应该是地址值,调用了 testStr() 方法后,虽然在这个方法内部进行了值的拼接,但是由于 s 对象指向的引用没有变,所以 s 还是TEST,但是函数返回的就是进行拼接操作后的结果。


代码3:

public static void main(String [] args){    int a = 1;    int b = 2;    change(a,b);    System.out.println("a :" + a + ", b: "+b );}public static void change(int a ,int  b){    int temp = 0;    temp = a;    a = b;    b = temp;}

输出:a :1, b: 2
这种情况是参数是基本类型的数据,由于传值是单向的,实参的值可以传递给形参,但是形参的值不能传递回实参。所以实参的值还是不会改变。



代码4:

public static void main(String [] args){    ArrayList<String> list2 = new ArrayList<String>();      list2.add("AAAAA");      list2.add("BBBBB");      list2.add("CCCCC");      newList(list2);      System.out.println("sizeC=:"+list2.size());}public static void newList(ArrayList<String> list){      list=new ArrayList<String>();      list.add("DDDDD");      System.out.println("sizeB=:"+list.size());  }  

输出:

sizeB=:1sizeC=:3

因为这个虽然传递了一个引用数据类型的实参,但是在方法中重新new了一个新的对象,新的对象的值只添加了1个,所以sizeB输出的是1,但是对于实参list2来说,它指向的引用是没变的,它还是有3个值的,所以他的大小还是3。
也就是说改变了形参对象的引用,但是它的实参引用还是没有改变。



代码5:

public static void main(String [] args){    int [] a = new int[10];    int [] b = new int[10];    a[0] = 1;    b[0] = 2;    change(a,b);    System.out.println("a: " + a[0] + ",b: " + b[0] );}public static void change(int[] a ,int[] b){    int temp = 0;    temp = a[0];    a[0] = b[0];    b[0] = temp;}

输出:

a: 2,b: 1

接口

Java接口的修饰符可以为()
A private B protected C final D abstract
答案:CD

解析:
(1)接口用于描述系统对外提供的所有服务,因此接口中的成员常量和方法都必须是公开(public)类型的,确保外部使用者能访问它们;
(2)接口仅仅描述系统能做什么,但不指明如何去做,所以接口中的方法都是抽象(abstract)方法;
(3)接口不涉及和任何具体实例相关的细节,因此接口没有构造方法,不能被实例化,没有实例变量,只有静态(static)变量;
(4)接口的中的变量是所有实现类共有的,既然共有,肯定是不变的东西,因为变化的东西也不能够算共有。所以变量是不可变(final)类型,也就是常量了。

接口的方法默认是public abstract;
所以接口的属性默认是public static final 常量,且必须赋初值。


JSP和Servlet

jsp的四大作用域是哪四个?
1、page:当前页面有效
2、request:请求中有效
3、session:在整个会话中有效
4、application:整个应用程序有效
从小到大:page < request < session < application


内部类

1、内部类
题目:要求在A、B、C位置填空分别输出30,20,10

class Outer{    public int num = 10;    class Inner{        public int num = 20;        public void show(){            int num = 30;            System.out.println(A);            System.out.println(B);            System.out.println(C);        }    }}public class Test {    public static void main(String[] args) {        Outer.Inner oi = new Outer().new Inner();        oi.show();    }}

答案:

System.out.println(num);//30System.out.println(this.num);//20System.out.println(new Outer().num);//10System.out.println(Outer.this.num);//10

分析:
1、声明的oi是一个Inner类的对象,也就是说this指代的是Inner类。所以要输出20,也就是Inner类的成员变量,可以通过this.num来获得
2、内部类和外部类没有继承关系。
3、可以通过外部类名限定this对象。


2、局部内部类访问局部变量的注意事项?
局部内部类访问局部变量必须用final修饰。
    因为局部变量会随着方法的调用完毕而消失,但是这个时候,局部对象并没有立马从堆内存中消失,但还要使用那个变量。为了让数据还能继续被使用,就用fianl修饰。加了final修饰后,这个变量就变成了常量。既然是常量,你消失了,虽然变量名字不见了,但我在内存中存储的还是那个数据,还是有数据在使用。
    final存储在堆内存中,堆内存的内容不会立即消失,只有垃圾回收机制回收的时候才会消失。

package org.danni.Demo2;class Outer{    private int num = 10;    //局部内部类访问局部变量必须用final修饰    public void method(){        final int num2 = 20;        class Inner{            public void show(){                System.out.println(num);                System.out.println(num2);            }        }        //被垃圾回收机制回收的时候对象才消失。所以还会要用到num2变量。        //要还能够使用,使用final类型,存储在堆内存中,只有垃圾回收机制回首才消失。这样就可以了。        Inner i =  new Inner();        i.show();    }}public class Test {    public static void main(String[] args) {        Outer o = new Outer();        o.method();         //10 20    }}

3、匿名内部类
补齐代码,要求在控制台输出:”你很漂亮”

interface Inter{    void show();}class Outer{}public class Test {    public static void main(String[] args) {        Outer.method().show();    }}

分析:
1、Outer.method()可以看出method()是Outer类的一个静态方法
2、Outer.method().show()可以看出method()方法有返回值,且返回的是一个对象。
3、由于接口Inter中有一个show方法,所以可以想得到method方法的返回值的类型是一个接口(本质是返回接口的子类实现对象),然后子类对象在调用它的show方法。

class Outer {    public static Inter method() {        return new Inter() {            @Override            public void show() {                System.out.println("你很漂亮");            }        };    }}

基础

1、如果一个类没有构造方法,有哪些情况?
1、成员都是静态的,可以通过类直接调用。比如Math、Arrays、Collections
2、单例设计模式(Runtime)
3、类中有静态方法返回该类的对象。(InetAddress)
public static InetAddress getByName(String host),通过该类调用这个静态方法就会返回这个类的一个对象


2、写出下列程序输出的结果。

public class IntegerDemo {    public static void main(String[] args) {        Integer i1 = new Integer(127);        Integer i2 = new Integer(127);        System.out.println(i1 == i2);               //false        System.out.println(i1.equals(i2));          //true        System.out.println("--------");        Integer i3 = new Integer(128);        Integer i4 = new Integer(128);        System.out.println(i3 == i4);               //false         System.out.println(i3.equals(i4));          //true        System.out.println("--------");        Integer i5 = 128;        Integer i6 = 128;        System.out.println(i5 == i6);               //false        System.out.println(i5.equals(i6));          //true        System.out.println("--------");        Integer i7 = 127;        Integer i8 = 127;        System.out.println(i7 == i8);               //true        System.out.println(i7.equals(i8));          //true    }}

Integer i7 = 127;把一个int类型的变量赋值给一个Integer引用类型的变量,在jdk5之后叫做自动装箱,这个编译器会帮我们完成,通过查看它的编译后的文件我们可以知道他其实经过了这样的操作:Integer i7 = Integer.valueOf(127);

下面我们查看valueOf方法的源码:我们可以发现针对在low=-128和high=127之间的数据创建了一个数据缓冲池。如果数据是该范围内的,就直接返回一个缓冲池中的值,否则就要重新new一个Integer对象。
因此代码中的i5,i6值为128超过了最大值127,因此通过new重新创建了一个空间,因此==操作返回的是false。
而i7,i8值为127,在范围内,就直接在缓冲池中取数据,并没有重新创建对象。所以==返回的是true

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);}private static class IntegerCache {    static final int low = -128;    static final int high;    static final Integer cache[];    static {        // high value may be configured by property        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);            // Maximum array size is Integer.MAX_VALUE            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() {}}

3、变量的值只有在运行的时候才能确定,而常量的值在编译期间就可以确定。

byte b1 = 3;byte b2 = 4;byte b;//b = b1 + b2;              //编译错误b = 3 + 4;                  //常量,先计算,看计算结果是否在byte的范围内,如果存在,就不会报错

1、因为变量相加首先看类型问题,常量相加首先看结果看计算结果是否在赋值的数据类型范围内,如果是则正确,不是则报错。
2、因为b1和b2都是byte类型,他们在运行的时候才能计算它的结果,他们相加之后是int类型,而int类型不能转换为为byte类型(大可以转小,小不可以转大,int是da,byte是小),因此把一个int类型的值赋值给byte类型会报错
3、而3+4,都是常量,在编译的时候就计算好了,计算结果在byte范围内,就可以


4、throw和throws的区别是什么?
throws:
            用在方法声明后面,跟的是异常类名
            可以跟多个异常类名,用逗号隔开
            表示抛出异常,由该方法的调用者来处理
            throws表示出现异常的一种可能性,并不一定会发生这些异常
throw
            用在方法体内,跟的是异常对象名
            只能抛出一个异常对象名
            表示抛出异常,由方法体内的语句处理
            throw则是抛出了异常,执行throw则一定抛出了某种异常


5、final,finally和finalize的区别是什么?
final:可以修饰类、成员变量、成员方法
           修饰类:类不能被继承
           修饰变量:变量是常量
           修饰方法:方法不能被重写
finally:是异常处理的一部分,用于释放资源。一般来说finally控制的代码肯定会执行,特殊情况:在执行到finally之前JVM退出了finally控制的代码就不会执行
finalize:是Object类的一个方法,用于垃圾回收。


6、如果catch里面有return语句,请问finally的代码还会执行吗?如果会,请问是在return前还是return后。?
会。在return前面执行。但是准确的说,是在return中间执行。
通过调试,我们可以发现程序是这么执行的:

public class FinallyDemo {    public static void main(String[] args) {        System.out.println(getInt());                      //1   //9(控制台输出30)    }    public static int getInt(){        int a = 10;                                        //2         try{            System.out.println(a / 0);                     //3(异常了,直接去执行catch)                     a = 20;        }catch(ArithmeticException e){                     //4            a = 30;                                        //5            return a;                                      //6      //8            //程序在执行到这一步的时候,这里不是return a而是return 30,这个返回路径就形成了。但是发现后面有finally,继续执行finally的内容,然后回到以前的返回路径,继续走return 30        }finally{            a = 40;                                        //7(执行完之后,继续回到之前的返回路径)        }        return a;    }}

但是如果也在finally之后加个return呢?这个时候输出的就是40了。

public class FinallyDemo {    public static void main(String[] args) {        System.out.println(getInt());                      //1   //9(控制台输出40)    }    public static int getInt(){        int a = 10;                                        //2         try{            System.out.println(a / 0);                     //3(异常了,直接去执行catch)                                  a = 20;        }catch(ArithmeticException e){                     //4            a = 30;                                        //5            return a;                                      //6        }finally{            a = 40;                                        //7            return a;                                      //8        }    }}

线程

1、同步有几种方式,分别是什么?
两种。同步代码块和同步方法。

2、sleep()和wait()方法的区别是什么?
1、sleep()方法必须指定时间。
      wait()方法可以不指定时间,也可以指定时间。
2、sleep()不释放锁
      wait()释放锁

3、为什么wait()、nitify()、notifyAll()等方法都定义在Object类中?
因为这些方法的对象的调用是依赖于锁对象的。而同步代码块的多对象是任意锁。而Object代码是任意的对象。所以,定义在Object里面。

4、线程的生命周期图
http://blog.csdn.net/qq_36748278/article/details/78144988