java面试题总结

集合

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);
    }
}

输出:

请输入一个字符串:
asbdakshdkgas
a(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 = TEST
result_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=:1
sizeC=: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);//30

System.out.println(this.num);//20

System.out.println(new Outer().num);//10
System.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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值