Android面试之Java 基础篇

11,382 阅读37分钟

本文是Android面试题整理中的一篇,结合右下角目录食用更佳,包括:

  • Java设计思想
  • 抽象类和接口
  • 类和方法
  • 内部类
  • 错误和异常
  • 关键字和运算符
  • 基本类型和常用类
  • 编码
  • 其他未分类等

Java设计思想


0. OOP是什么

面向对象编程(Object Oriented Programming)

1. JDK和JRE

  1. JDK:java development kit:java开发工具包,是开发人员所需要安装的环境(包含JRE)
  2. JRE:java runtime environment:java运行环境,java程序运行所需要安装的环境

2. 面向对象的特征有哪些

面向对象的特征有:抽象封装继承多态

  1. 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
  2. 封装:隐藏对象的实现细节,仅对外公开接口,是针对一个对象来说的
  3. 多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情
  4. 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)

3. java是值传递还是引用传递

java是值传递。可以理解为传入的是一个引用的副本,指向统一地址。当值改变时,原引用和副本指向地址中的值都变了;当副本指向的地址改变,指向新值时,原引用指向的地址没有改变,原值也没有改变。

抽象类和接口


0. 接口的意义

  1. 规范
  2. 扩展
  3. 回掉
  4. java是单继承的

1. 抽象类的意义

  1. 为其他子类提供一个公共的类型
  2. 封装子类中重复定义的内容
  3. 定义抽象方法,子类可以有不同的实现

2. 抽象类和接口有什么不同

  1. 单继承:java中只可以继承一个类,但是可以实现多个接口
  2. 成员变量:接口的成员变量都是public static final 的,抽象类可以有各种类型
  3. 方法:抽象类中可以有方法的具体实现,接口中方法都是抽象的
  4. 扩展://jdk 7 : 只能声明全局常量(public static final)和抽象方法(public abstract) void method1(); // jdk 8 : 声明静态方法 和 默认方法 public static void method2(){ System.out.println("method2"); } default void method3(){ System.out.println("method3"); method4(); } //jdk 9 : 声明私有方法 private void method4(){ System.out.println("私有方法"); }}

3. 接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?

  1. 接口可以继承接口,而且支持多重继承
  2. 抽象类可以实现(implements)接口
  3. 抽象类是否可继承具体类,也可以继承抽象类

4. Java标识符命名规范

0. 规范(强制)

  1. 数字、字母、下划线、$(java中内部类编译后会生成包含$的类名) 组成
  2. 不能以数字开头
  3. 不能和关键字或保留关键字相同

1. 推荐的命名方式(非强制)

  1. 方法:java中通常用小驼峰命名法
  2. 常量:通常用大写字母,不同单词间用“_”分隔开,如MOBILE_NUM
  3. 类名:大驼峰命名法

类和方法


0. 一个".java"源文件中是否可以包含多个类(不是内部类)?有什么限制?

一个".java"文件内可以有多个类,但只能有一个类是公开的

1. 构造器(constructor)是否可被重写(override)

构造器不能被继承,因此不能被重写,但可以被重载

2. 静态变量和成员变量的区别

  1. 静态变量属于类,被多个实例共享,成员变量属于实例
  2. 静态变量储存在方法区,成员变量在堆
  3. 静态变量在类加载时候存在,成员变量在实例加载之后存在
  4. 静态方法可以直接使用静态变量,不能直接使用成员变量

3. Object 中定义了哪些方法

clone/toString/wait/notify/notifyAll/equals/hashcode/finalize/getClass

4. Cloneable 实现原理

  1. Cloneable是一个接口,没有具体方法
  2. clone方法是Object类中方法,会检查当前实例是否实现Cloneable接口,没有实现则抛出异常,实现了就调用native方法进行clone(clone进行的是浅拷贝),源码如下
protected Object clone() throws CloneNotSupportedException {
        if (!(this instanceof Cloneable)) {
            throw new CloneNotSupportedException("Class " + getClass().getName() +
                                                 " doesn't implement Cloneable");
        }
        return internalClone();
    }

5. 两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

  1. 不对。
  2. java 规定,值相同,hashCode一定要相同;hashCode相同,值可能不同
  3. 如果值相同,hashCode不同,就会造成Hashset、HashMap等借助hashCode实现的数据结构出现错乱,相同的值或者key可能出现多次

6. 如何实现对象的克隆

  1. 通过实现Cloneable接口实现clone:这里要注意深拷贝和浅拷贝问题,如果该类内部变量是引用类型的,并且内部变量类没有实现Cloneable接口,那么克隆出来的该变量是浅拷贝的(只是复制了引用,两个引用指向统一实例)
  2. 通过实现Serializable接口,通过对象的序列化和反序列化实现克隆。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class MyUtil {

private MyUtil() {
throw new AssertionError();
}

@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);

ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();

// 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
}
}

7. 谈一谈”==“与”equals()"的区别

  1. == :对于基本类型,比较的是他们的值;对于引用类型,比较的是引用的值,也就是对象实例的地址
  2. equals()方法是Object类中的方法,默认实现是public boolean equals(Object obj) {return (this == obj);};我们可以重写该方法达到我们的目的,例如String重写了该方法要求每个字符都相等。

8. 类中方法调用顺序

指出下面程序的运行结果

class A {

static {
System.out.print("1");
}

public A() {
System.out.print("2");
}
}

class B extends A{

static {
System.out.print("a");
}

public B() {
System.out.print("b");
}
}

public class Hello {

public static void main(String[] args) {
A ab = new B();
ab = new B();
}

}

执行结果:1a2b2b。 创建对象时构造器的调用顺序是: 父类静态初始化块 -> 子类静态初始化块 -> 父类初始化块 ->调用了父类构造器 -> 子类初始化块 -> 调用子类的构造器

9. 重载(Overload)和重写(Override)的区别

重写(Override)和重载(Overload)其实并无联系,可能是因为名称相似,容易引起混淆 > 重写发生在运行时,重载发生在编译期

重写(Override)

重写是针对父类和子类来说的,是在子类中重写父类的方法。

  1. 要求方法名,参数个数和类型必须相同
  2. 返回的数据类型必须与父类相同或者是其子类
  3. 访问修饰符的限制一定要大于父类中该方法的访问修饰符(public>protected>default>private)
  4. 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

重载(Overload)

重载是针对一个类说的,是Java中多态性的一种表现

  1. 要求方法名相同
  2. 必须有不同的参数列表
  3. 可以有不同的返回类型
  4. 可以有不同的修饰符
  5. 可以抛出不同的异常。
扩展:华为的面试题中曾经问过这样一个问题 - "为什么不能根据返回类型来区分重载"
答:因为调用时不能指定类型信息,编译器不知道你要调用哪个函数。例如:
float max(int a, int b);
int max(int a, int b);
当调用max(1, 2);时无法确定调用的是哪个。
参考:https://www.zhihu.com/question/21455159/answer/59874307

10. 阐述静态变量和实例变量的区别。

  1. 静态变量(static 修饰的变量)属于类,被所有类的实例共享,没有实例时也可通过类直接访问
  2. 实例变量:必须通过实例来访问

11. 是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?

不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在调用静态方法时可能对象并没有被初始化

12. 抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?

  1. 抽象方法不能是静态的:静态方法不能被子类重写,抽象方法必须被子类重写,冲突;
  2. 抽象方法不能是native的:本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的
  3. 抽象方法不能用sychronized:synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的

13. Super与this表示什么

Super表示当前类的父类对象;This表示当前类的对象

14. hashcode()和equals()的关系

  1. equals 相等,hashcode一定相等
  2. hashcode相等,equals不一定相等

内部类


1. 内部类的作用

  1. 内部类可以很好的实现隐藏
  2. 内部类拥有外围类的所有元素的访问权限
  3. 可以间接实现多重继承
  4. 可以避免修改接口而实现同一个类中两种同名方法的调用

2. 静态嵌套类(Static Nested Class,或者叫静态内部类)和内部类(Inner Class)的不同

  1. 内部类持有外部类的引用(this),静态内部类不持有
  2. 因为持有外部类的引用,所以new时需要先有外部类的实例,再用外部类实例new内部类实例,举例:new Outer().new Inner();
  3. 扩展:在Android中,因为内部类持用外部类引用,所以容易造成内存泄漏,一般推荐使用静态内部类

3. Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?

可以继承其他类,也可以实现接口

解析:
btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view){

}
})

这里new的就是一个匿名内部类,这个匿名内部类实现了View.OnClickListener接口。所以匿名内部类本身一定会继承或实现一个且仅一个类或者接口。

4. 内部类(非静态内部类)可以引用它的包含类(外部类)的成员吗?有没有什么限制?

  1. 一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。
  2. 应用局部变量,局部变量前要加final修饰

5. 内部类访问局部变量的时候,为什么要加final

  1. 内部类和局部变量生命周期不同(方法结束后局部变量的生命周期就结束了,而内部类只要有引用就不结束,内部类的生命周期>=局部变量)
  2. Java为了解决这一问题,会在编译时在内部类的构造方法里边,将局部变量作为参数传入内部类
  3. 这就造成了局部变量如果改变,内部类不知情的场景,所以要加final,保证引用不可改变

扩展:在java8中,可以不使用final关键字,但是如果我们改变局部变量的引用,编译会发生错误,从而保证了局部变量的引用不变。

6. 为什么内部类会持有外部类的引用?持有的引用是this?还是其它?

内部类虽然和外部类写在同一个文件中, 但是编译完成后, 还是生成各自的class文件,内部类通过this访问外部类的成员。

  1. 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象
  2. 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为内部类中添加的成员变量赋值;
  3. 在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用。

错误和异常


1. java中的异常

  1. 基类是Throwable,Error和Exception继承自Throwable
  2. Error通常是系统抛出来的,也可以catch到,但一般不可恢复,开发是也不做处理
  3. Exception分为受检查异常和不受检查异常,受检查异常会在编译时强制要求我们try/catch

2. throw 和 throws

  1. throw:抛出异常
  2. throws:在方法声明处使用,表示此方法可能抛出的异常,调用此方法处需要处理这些异常。

3. Error和Exception有什么区别?

  1. Error是系统抛出的,不能在运行时捕获,比如内存溢出
  2. Exception 是需要我们捕捉并处理的异常,如类型转换错误等,我们可以通过捕捉异常,使程序发生异常时仍可正常运行

4. 运行时异常与受检异常有何异同?

  1. checked exception:这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行try/catch或者继续上抛
  2. runtime exception:出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了

5. 列出一些你常见的运行时异常

NullPointerException (空指针异常) ClassCastException (类转换异常) IndexOutOfBoundsException (下标越界异常) IllegalArgumentException (非法参数异常)

6. Exception继承相关考题

题目1:
类ExampleA继承Exception,类ExampleB继承ExampleA。有如下代码片断,请问执行此段代码的输出是什么?
try {
throw new ExampleB("b")
} catch(ExampleA e){
System.out.println("ExampleA");
} catch(Exception e){
System.out.println("Exception");
}

解析:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取ExampleA类型异常的catch块能够抓住try块中抛出的ExampleB类型的异常)

题目2:
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}

class Human {

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 AnnoyanceCaught SneezeHello World!

关键字和运算符


1. &和&&的区别;|和||的区别?

  1. &有两种用法:(1)按位与;(2)逻辑与,我们这里说的是逻辑与。
  2. 与运算要求左右两端的布尔值都是true整个表达式的值才是true
  3. &&运算符是短路逻辑与运算,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算
  4. &左右两边的表达式都会计算,我们常用&&,比如if(username != null &&!username.equals("hahaha")){}

2, transient关键字

如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程

3. 修饰符的区别

修饰符一共有四个:private、protected、public和default(也有人管默认叫friendly)

  1. private:私有的,除自己外任何类不能使用
  2. protected:同包可以使用,其他包子类可以使用
  3. public:任何类可以使用
  4. default:同包可以使用,其他包不能使用
修饰符 当前类 同 包 子 类 其他包
public
protected ×
default × ×
private × × ×

4. Java有没有goto?

goto 和 const 是Java中的保留字,在目前版本的Java中没有使用。

5. 在Java中,如何跳出当前的多重嵌套循环

在最外层循环前加一个标记如A,然后用break A;可以跳出多重循环 (应该避免使用带标签的break和continue,因为它不会让你的程序变得更优雅)。

6. switch 是否能作用在byte 上,是否能作用在long 上,是否能作用在String上?

  1. 在Java 5以前,switch(expr)中,expr只能是byte、short、char、int
  2. Java 5开始,Java中引入了枚举类型,expr也可以是enum类型
  3. 从Java 7开始,expr还可以是字符串(String)
  4. long类型不支持

7. static

  1. 可以修饰内部类(静态内部类)
  2. 可以修饰成员变量,该变量属于类,被所有实例共享
  3. 可以修饰方法,该方法属于类,被所有实例共享
  4. 可以修饰代码块(静态代码块),该代码块在第一次被加载时被调用

8. Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?

  1. Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类在Java中,每个异常都是一个对象,它是Throwable类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。
  2. Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;try用来指定一块预防所有异常的程序;catch子句紧跟在try块后面,用来指定你想要捕获的异常的类型;throw语句用来明确地抛出一个异常;throws用来声明一个方法可能抛出的各种异常(当然声明异常时允许无病呻吟);finally为确保一段代码不管发生什么异常状况都要被执行;t
  3. try语句可以嵌套,每当遇到一个try语句,异常的结构就会被放入异常栈中,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这种异常的try语句或者最终将异常抛给JVM。

9. 阐述final、finally、finalize的区别。

这是三个不同的概念,只是因为长得较像而被出成了一道题

final

final是一个修饰符,用来修饰类,变量,方法

  1. final修饰的类不能被继承
  2. final修饰的方法不能被重写
  3. final修饰的成员变量是不可变的,如果成员变量是基本数据类型,初始化之后成员变量的值不能被改变,如果成员变量是引用类型,那么它只能指向初始化时指向的那个对象,不能再指向别的对象,但是对象当中的内容是允许改变的

finally

finally与try,catch一起搭配使用,不论是否catch到异常,finally中的内容都会执行

finalize

finalize是Object类中的方法,垃圾回收器在垃圾回收时会调用该方法,我们可以在子类中重写该方法来做一些清理工作

10. finally 语句一定会执行吗

在极特殊的情况下可能不执行

  1. 调用了System.exit()方法
  2. JVM崩溃了

基本类型和常用类


0. int和Integer有什么区别?

  1. int是基本类型,Integer是int的包装类型
  2. 包装类型可以有一些自己的方法,引入包装类型可以使java更好的面向对象
  3. 每个基本类型都有其包装类:
  • 原始类型: boolean,char,byte,short,int,long,float,double
  • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
扩展1:
java5中引入了自动拆装箱功能,例如在比较时可以自动拆装箱
class AutoUnboxingTest {

    public static void main(String[] args) {
        Integer a = new Integer(3);
        Integer b = 3;                  // 将3自动装箱成Integer类型
        int c = 3;
        System.out.println(a == b);     // false 两个引用没有引用同一对象
        System.out.println(a == c);     // true a自动拆箱成int类型再和c比较
    }
}
扩展2:
一道和装箱有关的面试题
public class Test03 {

    public static void main(String[] args) {
        Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;

        System.out.println(f1 == f2); //true
        System.out.println(f3 == f4); //false
    }
}

分析:自动装箱时,使用的时Integer的valueof方法,当int在-128到127之间时,并不会new一个新的对象,而是直接使用常量池中的Integer
具体分析: 

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    
IntegerCache是Integer的内部类,其代码如下所示:
/**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */

    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) {
                try {
                    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);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
    简单的说,如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,
    所以上面的面试题中f1==f2的结果是true,而f3==f4的结果是false

1. float f=3.4;是否正确?

答:不正确。3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成float f =3.4F;。

2. short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?

  1. 对于short s1 = 1; s1 = s1 + 1;由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型。
  2. short s1 = 1; s1 += 1;可以正确编译,因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换。

3. Java中char 型变量中能不能存贮一个中文汉字,为什么?

Java中 char型变量用来存储Unicode编码的字符,unicode编码字符集中包含了汉字,所以char类型可以储存汉字 char类型占两个字节

4. 数组有没有length()方法?String有没有length()方法?

数组有length属性,String有length()方法

5. String是基本数据类型嘛

  1. 不是。
  2. java中8个基本类型为:byte、short、char、int、float、long、double、boolean。
  3. java中除基本类型外,都是引用类型(枚举是java5以后引入的特殊引用类型)
  4. String类型比较特殊,不可变。但它不是基本类型

6. 是否可以继承String类

String 类是final的,不能被继承

7. String和StringBuilder、StringBuffer的区别

String 是只读字符串,StringBuilder和StringBuffer可以改变,StringBuilder效率高,线程不安全,StringBuffer线程安全。 在拼接String时,使用+编译器会帮我们进行优化,使用StringBuilder进行拼接,这时+和StringBuilder没有多大区别。但当循环中使用+时,我们应该显示的使用StringBuilder,以防止多次调用new StringBuilder,造成不必要的性能浪费。

循环中使用+举例:
String str = "hello,world!";
        String result = "";

        for (int i = 0; i < loopCount; i++) {
            result += str;
        }
这个时候编译器会优化成
String str = "hello,world!";
        String result = "";

        for (int i = 0; i < loopCount; i++) {
            result = new StringBuilder(result).append(str).toString();
        }
多次new StringBuilder造成了性能浪费。
扩展例题
class StringEqualTest {

    public static void main(String[] args) {
        String s1 = "Programming";
        String s2 = new String("Programming");
        String s3 = "Program";
        String s4 = "ming";
        String s5 = "Program" + "ming";
        String s6 = s3 + s4;
        System.out.println(s1 == s2); // false
        System.out.println(s1 == s5); //true
        System.out.println(s1 == s6); //false
        System.out.println(s1 == s6.intern()); //true
        System.out.println(s2 == s2.intern()); //false
    }
}

解析:1. String是引用类型,这里 == 比较的是引用是否相同,即是否指向相同的地址
     2. 在new String对象时,会产生一个新的对象,并不会使用常量池中的字符串
     3. intern会在常量池中寻找该字符串(如果没有责新建),并返回他的地址

8. String s = new String("xyz");创建了几个字符串对象?

两个对象,一个是静态区的"xyz";一个是用new创建在堆上的对象。

9. String 和基本数据类型之间的转换

  1. String 转基本数据类型:调用基本数据类型对应包装类的parseXXX(String)或valueOf(String)方法
  2. 基本数据类型转String:基本数据类型+“”;String.valueof(12)

10. 实现字符串的反转

  1. 方法有很多,可以用StringBuffer/StringBuilder的reverse方法,这里reverse是通过位移实现的
  2. 再举例一种递归方法:
public String reverse(String originString){
if(originString == null || originString.length <= 1)
return originString;
return reverse(originString.subString(1)) + originString.charAt(0);
}

11. String 为什么要设计成不可变的

1. 安全性

  1. 线程安全,不可变天生线程安全
  2. String常被用作HashMap的key,如果可变会引有安全问题,如两个key相同
  3. String常被用作数据库或接口的参数,可变的话也会有安全问题

2. 效率

  1. 通过字符串池可以节省很多空间
  2. 每个String对应一个hashcode,再次使用的话不用重新计算

编码


0. 讲一下Java的编码方式

为什么需要编码

计算机存储信息的最小单元是一个字节即8bit,所以能表示的范围是0~255,这个范围无法保存所有的字符,所以需要一个新的数据结构char来表示这些字符,从char到byte需要编码。

常见的编码方式有以下几种:

  1. ASCII:总共有 128 个,用一个字节的低 7 位表示,031 是控制字符如换行回车删除等;32126 是打印字符,可以通过键盘输入并且能够显示出来。
  2. GBK:码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。
  3. UTF-16:UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。
  4. UTF-8:统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。

Java中需要编码的地方一般都在字符到字节的转换上,这个一般包括磁盘IO和网络IO。

Reader 类是 Java 的 I/O 中读字符的父类,而 InputStream 类是读字节的父类,InputStreamReader 类就是关联字节到字符的桥梁,它负责在 I/O 过程中处理读取字节到字符的转换,而具体字节到字符的解码实现它由 StreamDecoder 去实现,在 StreamDecoder 解码过程中必须由用户指定 Charset 编码格式。

1. Unicode与UTF-8的关系

Unicode是字符集 UTF-8是一种编码方式,达到了对数据流压缩的目的

其他未分类


什么时候用断言(assert)

  1. 断言在软件开发中是一种常用的调试方式
  2. 断言检查通常在开发和测试时开启,发布时关闭
  3. 断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式的值为false,那么系统会报告一个AssertionError。断言的使用如下面的代码所示:assert(a > 0); // throws an AssertionError if a <= 0
  4. Android中不推荐使用断言

Java中的四种引用及应用场景

Java中的引用有四种:强引用,弱引用,软引用,虚引用

  1. 强引用: 通常我们使用new操作符创建一个对象时所返回的引用即为强引用
  2. 弱引用: 若一个对象只能通过弱引用到达,那么它就会被回收(即使内存充足),同样可用于图片缓存中,这时候只要Bitmap不再使用就会被回收
  3. 软引用: 若一个对象只能通过软引用到达,那么这个对象在内存不足时会被回收,可用于图片缓存中,内存不足时系统会自动回收不再使用的Bitmap
  4. 虚引用: 虚引用是Java中最“弱”的引用,通过它甚至无法获取被引用的对象,它存在的唯一作用就是当它指向的对象回收时,它本身会被加入到引用队列中,这样我们可以知道它指向的对象何时被销毁

动态代理和静态代理的区别,动态代理的使用场景

  1. 优点:动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
  2. 缺点:它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。

并发和并行的区别

  1. 并发:(在一个时间段内同时执行多件事)如单核cpu以时间片的方式让多个线程轮循执行,在外界看来他们是同时执行的
  2. 并行:(在一个时刻同时执行多件事)多核cpu每个核运行一个App,他们是真正的同时执行

java中的流类型

  1. 字节流:字节流继承于InputStream、OutputStream,以字节方式读取
  2. 字符流:字符流继承于Reader、Writer,以字符方式读取

字节流和字符流的区别

  1. 字节流操作的基本单元是字节;字符流是Unicode字符
  2. 字节流不使用缓冲区,字符流使用缓冲区
  3. 字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。

IO和NIO区别

  1. IO面向流,NIO面向缓冲区
  2. IO是阻塞的,NIO是非阻塞的
  3. Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道

大文件的复制

利用NewIO的FileChanel

如何在Java中创建Immutable对象

  1. 私有化成员变量
  2. 通过构造方法初始化成员变量
  3. 只提供get方法,不提供set方法
  4. get方法返回的是一个拷贝副本

举例说明同步和异步

  1. java 中同步常指多线程时数据的同步,在多线程中如果数据不是线程安全的,那么有可能会出现很多错误
  2. 异步多指方法调用时,是否等待方法调用完成才继续向下执行,例如通常网络请求就是异步的。

Java中如何实现序列化,有什么意义。

  1. 序列化是将对象的状态信息转换为可以存储或传输的形式的过程。
  2. java将对象序列化成了字节信息
  3. java通过实现Serializable接口实现序列化

for-each与常规for循环的效率对比

  1. for-each 使代码更加简洁优雅
  2. for-each 实际上是通过迭代器(Iterator)实现的

java8 有哪些新特性

支持Lambda 表达式,方法引用,增加了新的时间工具类等

你在项目中哪些地方用到了XML

XML的主要作用有两个方面:数据交换和信息配置。在做数据交换时,XML将数据用标签组装成起来,然后压缩打包加密后通过网络传送给接收者,接收解密与解压缩后再从XML文件中还原相关信息进行处理,XML曾经是异构系统间交换数据的事实标准,但此项功能几乎已经被JSON(JavaScript Object Notation)取而代之。当然,目前很多软件仍然使用XML来存储配置信息,我们在很多项目中通常也会将作为配置信息的硬代码写在XML文件中,Java的很多框架也是这么做的,而且这些框架都选择了dom4j作为处理XML的工具,因为Sun公司的官方API实在不怎么好用

什么是DAO模式

  1. DAO 是 Data Access Object 的缩写
  2. 位于业务逻辑和持久化数据之间
  3. 实现对持久化数据的访问。

简述一下你了解的设计模式

所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。 在GoF的《Design Patterns: Elements of Reusable Object-Oriented Software》中给出了三类(创建型[对类的实例化过程的抽象化]、结构型[描述如何将类或对象结合在一起形成更大的结构]、行为型[对在不同的对象之间划分责任和算法的抽象化])共23种设计模式,包括:Abstract Factory(抽象工厂模式),Builder(建造者模式),Factory Method(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式);Facade(门面模式),Adapter(适配器模式),Bridge(桥梁模式),Composite(合成模式),Decorator(装饰模式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式),Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式),State(状态模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibility(责任链模式)。 面试被问到关于设计模式的知识时,可以拣最常用的作答,例如:

  • 工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
  • 代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache代理、防火墙代理、同步化代理、智能引用代理。
  • 适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。
  • 模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法(多态实现),从而实现不同的业务逻辑。 除此之外,还可以讲讲上面提到的门面模式、桥梁模式、单例模式、装潢模式(Collections工具类和I/O系统中都使用装潢模式)等,反正基本原则就是拣自己最熟悉的、用得最多的作答,以免言多必失。

Comparable和Comparator接口是干什么的

  1. 他们都是接口
  2. Comparable是比较本实例和其他实例的大小
  3. Comparator是比较器,用来输入两个实例后比较两个实例大小

URI和URL

URI 是统一资源标识符,而URL 是统一资源定位符

XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?

  1. XML文档定义分为DTD和Schema两种形式,二者都是对XML语法的约束
  2. 其本质区别在于Schema本身也是一个XML文件,可以被XML解析器解析,而且可以为XML承载的数据定义类型,约束能力较之DTD更强大
  3. 对XML的解析主要有DOM(文档对象模型,Document Object Model)、SAX(Simple API for XML)和StAX(Java 6中引入的新的解析XML的方式,Streaming API for XML),其中DOM处理大型文件时其性能下降的非常厉害,这个问题是由DOM树结构占用的内存较多造成的,而且DOM解析方式必须在解析文件之前把整个文档装入内存,适合对XML的随机访问(典型的用空间换取时间的策略);SAX是事件驱动型的XML解析方式,它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过事件回调代码来处理XML文件,适合对XML的顺序访问;顾名思义,StAX把重点放在流上,实际上StAX与其他解析方式的本质区别就在于应用程序能够把XML作为一个事件流来处理。将XML作为一组事件来处理的想法并不新颖(SAX就是这样做的),但不同之处在于StAX允许应用程序代码把这些事件逐个拉出来,而不用提供在解析器方便时从解析器中接收事件的处理程序。

Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?

Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11. Math.round()得到的值大于等于原来的值。

用最有效率的方法计算2乘以8?

2 << 3(左移n位相当于乘以2的n次方,右移n位相当于除以2的n次方)。

什么是UML

UML(Unified Modeling Language)是统一建模语言。为软件开发提供模型化和可视化支持,方便沟通交流。

18. UML中有哪些常用的图

UML定义了多种图形化的符号来描述软件系统部分或全部的静态结构和动态结构,包括:用例图(use case diagram)、类图(class diagram)、时序图(sequence diagram)、协作图(collaboration diagram)、状态图(statechart diagram)、活动图(activity diagram)、构件图(component diagram)、部署图(deployment diagram)等。在这些图形化符号中,有三种图最为重要,分别是:用例图(用来捕获需求,描述系统的功能,通过该图可以迅速的了解系统的功能模块及其关系)、类图(描述类以及类与类之间的关系,通过该图可以快速了解系统)、时序图(描述执行特定任务时对象之间的交互关系以及执行顺序,通过该图可以了解对象能接收的消息也就是说对象能够向外界提供的服务)。 用例图:

类图:
时序图:

写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数。

import java.io.BufferedReader;
import java.io.FileReader;

public class MyUtil{

private MyUtil(){
throw new AssertError;
}

public static int countWordInFile(String filename,String word){
int counter = 0;
try(FileReader fr = new FileReader(filename){
try(BufferReader br = new BufferReader(fr)){
String line = null;
while((line = br.readLine()) != null){
int index = -1;
while(line.length() >= word.length() && (index = line.indexof(word) >= 0){
counter ++;
line = line.subString(index+word.length());
})
}


}catch(Exception e){
Log.e(e);
}

} catch(Exception e){
Log.e(e);
}
)
}

}

如何用Java代码列出一个目录下所有的文件?

import java.io.File;

class Test12 {

public static void main(String[] args) {
File f = new File("/Users/Hao/Downloads");
for(File temp : f.listFiles()) {
if(temp.isFile()) {
System.out.println(temp.getName());
}
}
}
}



如果需要对文件夹继续展开,代码如下所示:

import java.io.File;

class Test12 {

public static void main(String[] args) {
showDirectory(new File("/Users/Hao/Downloads"));
}

public static void showDirectory(File f) {
_walkDirectory(f, 0);
}

private static void _walkDirectory(File f, int level) {
if(f.isDirectory()) {
for(File temp : f.listFiles()) {
_walkDirectory(temp, level + 1);
}
}
else {
for(int i = 0; i < level - 1; i++) {
System.out.print("\t");
}
System.out.println(f.getName());
}
}
}



在Java 7中可以使用NIO.2的API来做同样的事情,代码如下所示:

class ShowFileTest {

public static void main(String[] args) throws IOException {
Path initPath = Paths.get("/Users/Hao/Downloads");
Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
System.out.println(file.getFileName().toString());
return FileVisitResult.CONTINUE;
}

});
}
}

日期和时间

1. 获取当前年月日,时分秒
Calendar cal = Calendar.getInstance();
System.out.println(cal.get(Calendar.YEAR));
System.out.println(cal.get(Calendar.MONTH));    // 0 - 11
System.out.println(cal.get(Calendar.DATE));
System.out.println(cal.get(Calendar.HOUR_OF_DAY));
System.out.println(cal.get(Calendar.MINUTE));
System.out.println(cal.get(Calendar.SECOND));

2. 取得从1970年1月1日0时0分0秒到现在的毫秒数
Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();
Clock.systemDefaultZone().millis(); // Java 8

3. 如何取得某月的最后一天
Calendar time = Calendar.getInstance();
time.getActualMaximum(Calendar.DAY_OF_MONTH);

4. 如何格式化日期
利用java.text.DataFormat 的子类(如SimpleDateFormat类)中的format(Date)方法可将日期格式化。Java 8中可以用java.time.format.DateTimeFormatter来格式化时间日期,代码如下所示
public static void main(String[] args) {
SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");
Date date1 = new Date();
System.out.println(oldFormatter.format(date1));

// Java 8
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date2 = LocalDate.now();
System.out.println(date2.format(newFormatter));
}

打印昨天的当前时刻

Calender calender = Calender.getInstance();
calender.add(Calender.DATE,-1);
calender.getTime();


在Java 8中,可以用下面的代码实现相同的功能。

import java.time.LocalDateTime;

class YesterdayCurrent {

public static void main(String[] args) {
LocalDateTime today = LocalDateTime.now();
LocalDateTime yesterday = today.minusDays(1);

System.out.println(yesterday);
}
}

用java写一个单例

public class Single{
private static Single  instance = new Single();

private Single(){

}
public static Single getInstance(){
return instance;
}
}

用java写一个冒泡排序

public <T extends Comparable<T>> void sort(T[] list){

boolean swap = true;
T temp;
for(int i = list.length - 1 ; i> 0 && swap; i--){
swap = false;
for (int j = 0;j<i-1;j++){
if (list[j].compareTo(list[j+1]) > 0) {
temp = list[j];
list[j] = list[j+1];
list[j+1] = temp;
swap = true;
}
}
}

}

用Java写一个折半查找

public static <T> int binarySearch(T[] x, T key, Comparator<T> comp) {
int low = 0;
int high = x.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int cmp = comp.compare(x[mid], key);
if (cmp < 0) {
low= mid + 1;
}
else if (cmp > 0) {
high= mid - 1;
}
else {
return mid;
}
}
return -1;
}

参考资料

Java面试题全集(上)

Java线程面试题 Top 50