面试题整理-Java部分(一)

作者: 慕涵盛华 | 来源:发表于2019-01-07 16:14 被阅读5次

    一.Java部分(一)

    1.String、StringBuffer、StringBuilder区别

    String:字符串常量;StringBuffer:字符串变量 (线程安全);StringBuilder:字符串变量。
    String类中使用字符数组保存字符串,因为有“final”修饰符,所以string对象是不可变的。private final char value[];
    StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,可知这两种对象都是可变的。char[] value;
    String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String

    • 1.每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。
    • 2.从sub操、concat还是replace方法的源码看出以上操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
    String str1="aaa";
    String str2="aaa";
    System.out.println(str1==str2);//true 可以看出str1跟str2是指向同一个对象 ,“aaa”存在常量池中
    
    String str3=new String("aaa");
    String str4=new String("aaa");
    System.out.println(str3==str4);//false 可以看出用new的方式是生成不同的对象 ,存放在堆中
    
    String s1="helloworld";
    String s2="hello"+"world";
    System.out.println(s1==s2); //true 可以看出s1跟s2是指向同一个对象 ,"helloworld”是字符串常量,它在编译期就被确定了;而"hello”和"world”也都是字符串常量
    //当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中"helloworld”的一个引用。
    
    String s0="helloworld"; 
    String s1=new String("helloworld"); 
    String s2="hello" + new String("world"); 
    System.out.println( s0==s1 ); //false  
    System.out.println( s0==s2 ); //false 
    System.out.println( s1==s2 ); //false
    //用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间
    
    String str1="abc";   
    String str2="def";   
    String str3=str1+str2;
    System.out.println(str3=="abcdef"); //false
    //JVM对String str="abc"对象放在常量池中是在编译时做的,而String str3=str1+str2是在运行时刻才能知道的。new对象也是在运行时才做的。
    //而这段代码总共创建了5个对象,字符串池中两个、堆中三个。+运算符会在堆中建立来两个String对象,这两个对象的值分别是"abc"和"def",
    //也就是说从字符串池中复制这两个值,然后在堆中创建两个对象,然后再建立对象str3,然后将"abcdef"的堆地址赋给str3。
    

    总的来说就是:字面量"+"拼接是在编译期间进行的,拼接后的字符串存放在字符串池中;而字符串引用的"+"拼接运算实在运行时进行的,新创建的字符串存放在堆中。关注两个方面:1.存放的位置(堆还是常量池)2.什么时期确定(编译时期还是运行时期)

    2.int与integer的区别

    1.int是java的一种基本数据类型,Integer是int的包装类。
    2.Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
    3.Integer的默认值是null,int的默认值是0

    关于Integer和int的比较:

    //由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的
    Integer i = new Integer(100);
    Integer j = new Integer(100);
    System.out.print(i == j); //false
    
    //因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较
    Integer i = new Integer(100);
    int j = 100;
    System.out.print(i == j); //true
    
    //因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同
    Integer i = new Integer(100);
    Integer j = 100;
    System.out.print(i == j); //false
    
    //对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true
    //如果两个变量的值不在此区间,则比较结果为false
    Integer i = 100;
    Integer j = 100;
    System.out.print(i == j); //true
    Integer i = 128;
    Integer j = 128;
    System.out.print(i == j); //false
    
    java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100);,而java API中对Integer类型的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);
    }
    java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了,否则new新的对象。
    

    3.java中==和equals和hashCode的区别

    基本数据类型的==比较的值相等;类的==比较的内存的地址,即是否是同一个对象,在不覆盖equals的情况下,默认比较内存地址,原实现也为 == 。
    equal 是对比两个两个对象是否是等价关系。等价不同于相等。它在不同的类中有不同的规则,equal在不同的类中有不同的重载。如String等重写了equals方法,比较字符串内容相同。
    hashCode也是Object类的一个方法。返回一个离散的int型整数。在集合类操作中使用,为了提高查询速度。

    • 如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。(两个equals为true的实例必须返回相同的hashCode。)
    • 如果两个对象不equals,他们的hashcode有可能相等。
    • 如果两个对象hashcode相等,他们不一定equals。(hashCode相同的两个实例,equals方法不一定会返回true)
    • 如果两个对象hashcode不相等,他们一定不equals。

    4.谈谈对java多态的理解

    多态:不同的对象对同一种行为具有不同的表现。
    前提条件:1.继承,2.重写,3.父类引用指向子类实例(上转型)
    所谓多态就是:指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,让程序可以选择多个运行状态,这就是多态性。
    指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。

    5.什么是内部类?内部类的作用

    定义:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。

    分类:

    • 1.成员内部类:定义在成员位置

    成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
    当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。

    • 2.局部内部类

    局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

    • 3.匿名内部类

    匿名内部类就是没有名字的内部类,在声明的时候直接创建对象实例。(因为没有名字)隐含实现一个接口或继承一个类。

    • 4.静态内部类

    指被声明为static的内部类,他可以不依赖内部类而实例,而通常的内部类需要实例化外部类,从而实例化。静态内部类不可以有与外部类有相同的类名。不能访问外部类的普通成员变量,但是可以访问静态成员变量和静态方法(包括私有类型)

    内部类的共性:

    • 1.内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号
    • 2.外部类不能直接访问内部类的的成员,但可以通过内部类对象来访问。

    作用:

    • 1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,
    • 2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
    • 3.方便编写事件驱动程序
    • 4.方便编写线程代码

    6.抽象类和接口区别

    • 语法层面上的区别:

      • 1.抽象类中可以有非抽象方法,接口中都是抽象方法。(java8中新增default方法,带有具体的实现)
      • 2.抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
      • 3.接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
      • 4.一个类只能继承一个抽象类,而一个类却可以实现多个接口。
    • 设计层面上的区别:

      • 1.抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
      • 2.设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。

    什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

    意义或者作用:把一些具有相同属性和方法的组件进行抽象,这样更有利于代码和程序的维护。

    7.Serializable 和Parcelable 的区别

    Serializable: Java 序列化接口(空接口或者标记接口) 在硬盘上读写 读写过程中有大量临时变量的生成,Serializable的本质是使用了反射,序列化的过程比较慢
    Parcelable Android: 序列化接口 效率高 使用麻烦 在内存中读写(AS有相关插件 一键生成所需方法) 本质是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的类型,这样就实现了传递对象的功能了。

    如果是仅仅在内存中使用,比如activity、service之间进行对象的传递,强烈推荐使用Parcelable,因为Parcelable比Serializable性能高很多;如果是持久化操作,推荐Serializable,虽然Serializable效率比较低,但是还是要选择它,因为在外界有变化的情况下,Parcelable不能很好的保存数据的持续性。

    把对象转换为字节序列的过程称为对象的序列化。

    serialVersionUID的详细工作机制是这样的:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能会发生变化,这时候就无法正常的反序列化。所以一般来说,我们应该手动去指定serialVersionUID的值,这样即使类发生变化了也能反序列化成功。如果不指定的话,默认根据当前类的结构去生成对应的hash值作为serialVersionUID的值。

    8.string转换成integer的方式及原理

    //Integer.parseInt(String str)方法
    public static int parseInt(String s) throws NumberFormatException {
            //内部默认调用parseInt(String s, int radix)基数设置为10
            return parseInt(s,10);
    }
    
    public static int digit(int codePoint, int radix) {
            //基数必须再最大和最小基数之间
            if (radix < MIN_RADIX || radix > MAX_RADIX) {
                return -1;
            }
            
            if (codePoint < 128) {
                // Optimized for ASCII
                int result = -1;
                //字符在0-9字符之间
                if ('0' <= codePoint && codePoint <= '9') {
                    result = codePoint - '0';
                }
                //字符在a-z之间
                else if ('a' <= codePoint && codePoint <= 'z') {
                    result = 10 + (codePoint - 'a');
                }
                //字符在A-Z之间
                else if ('A' <= codePoint && codePoint <= 'Z') {
                    result = 10 + (codePoint - 'A');
                }
                //通过判断result和基数大小,输出对应值
                //通过我们parseInt对应的基数值为10,
                //所以,只能在第一个判断(字符在0-9字符之间)
                //中得到result值 否则后续程序会抛出异常
                return result < radix ? result : -1;
            }
            return digitImpl(codePoint, radix);
     }
    

    1.parseInt(String s)--内部调用parseInt(s,10)(默认为10进制)
    2.正常判断null,进制范围,length等
    3.判断第一个字符是否是符号位
    4.循环遍历确定每个字符的十进制值(对单个char进行数值计算Character.digit(char ch, int radix) ,该方法返回的一定是十进制数值)
    5.通过*= 和-= 进行计算拼接
    6.判断是否为负值 返回结果。

    相关文章

      网友评论

        本文标题:面试题整理-Java部分(一)

        本文链接:https://www.haomeiwen.com/subject/uawyrqtx.html