答:封装、继承和多态_(应要多算一个那就是抽象)_java
答:面向过程是一种站在过程的角度思考问题的思想,强调的是功能行为,功能的执行过程,即先干啥,后干啥。程序员
面向过程的设计:最小的程序单元是函数,每一个函数负责完成某一个功能,用以接受输入数据,函数对输入数据进行处理,而后输出结果数据。整个软件系统由一个个的函数组成,其中做为程序入口的函数称之为主函数,主函数依次调用其余函数,普通函数之间能够相互调用,从而实现整个系统功能。web
面向过程最大的问题在于随着系统的膨胀,面向过程将没法应付,最终致使系统的崩溃。为了解决这一种软件危机,咱们提出面向对象思想。面试
面向对象是一种基于面向过程的新的编程思想,是一种站在对象的角度思考问题的思想,咱们把多个功能合理的放到不一样对象里,强调的是具有某些功能的对象。算法
看到知乎上有一句有意思的话:你的程序要完成一个任务,至关于讲一个故事。sql
面向过程:编年体;
面向对象:纪传体。编程而对于复杂的程序/宏大的故事,事实都证实了,面向对象/纪传是更合理的表述方法。数组
扩展阅读:面向过程 VS 面向对象缓存
解析:这是考察一些基本的概念安全
答:Java 运行时环境(JRE-Java Runtime Environment),它包括 Java 虚拟机、Java 核心类库和支持文件,但并不包含开发工具(JDK-Java Development Kit)——编译器、调试器和其余工具。
Java 开发工具包(JDK)是完整的 Java 软件开发包,包含了 JRE,编译器和其余的工具(好比 JavaDoc, Java 调试器),可让开发者开发、编译、执行 Java 应用程序。
- 还有其余的一些名词也能够再看一下:
解析:覆盖和重载是比较重要的基础知识点,而且容易混淆,因此面试中常见。
答:覆盖(Override)是指子类对父类方法的一种重写,只能比父类抛出更少的异常,访问权限不能比父类的小,被覆盖的方法不能是 private 的,不然只是在子类中从新定义了一个新方法。
重载(Overload)表示同一个类中能够有多个名称相同的方法,但这些方法的参数列表各不相同。
面试官: 那么构成重载的条件有哪些?
答:参数类型不一样、参数个数不一样、参数顺序不一样。
面试官: 函数的返回值不一样能够构成重载吗?为何?
答:不能够,由于 Java 中调用函数并不须要强制赋值。举例以下:
以下两个方法:
void f(){} int f(){ return 1; }
只要编译器能够根据语境明确判断出语义,好比在 int x = f();
中,那么的确能够据此区分重载方法。不过, 有时你并不关心方法的返回值,你想要的是方法调用的其余效果 (这常被称为 “为了反作用而调用” ),这时你可能会调用方法而忽略其返回值,因此若是像下面的调用:
f();
此时 Java 如何才能判断调用的是哪个 f()
呢?别人如何理解这种代码呢?因此,根据方法返回值来区分重载方法是行不通的。
答:
面试官:抽象类和接口如何选择?
答:
解析:虽然咱们不太懂C++,可是就是会这么问,尤为是三面(总监级别)面试中。
答:
答:“static” 关键字代表一个成员变量或者是成员方法能够在没有所属的类的实例变量的状况下被访问。
面试官:Java中是否能够覆盖(override)一个 private 或者是 static 的方法?
答:Java 中 static 方法不能被覆盖,由于方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,因此概念上不适用。
Java 中也不能够覆盖 private 的方法,由于 private 修饰的变量和方法只能在当前类中使用,若是是其余的类继承当前类是不能访问到 private 变量或方法的,固然也不能覆盖。
扩展阅读:从新认识java(六) ---- java中的另类:static关键字(附代码块知识)
解析:这类题目,面试官会手写一个例子,让你说出函数执行结果。
答:值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。引用传递通常是对于对象型变量而言的,传递的是该对象地址的一个副本, 并非原对象自己 。
通常认为,Java 内的传递都是值传递.,Java 中实例对象的传递是引用传递,Java 是值传递的!
- 咱们先来看一个例子:
这是一个很经典的例子,咱们但愿在调用了 swap() 方法以后交换 arg1 和 arg2 的值,但事实上并无,为何会这样?
这就是由于 Java 是值传递的,也就是说,咱们在调用一个须要传递参数的函数时,传递给函数的参数并非咱们传递进去的参数自己,而是它的一个副本,咱们改变了数据其实只是改变了副本的数据而已,并不会对原来的参数有任何的改变。
- 再来看一个例子:
咱们本身定义了一个内部类 Person ,该类只有一个 int 类型的 age 属性,而后有 getter/setter ,咱们但愿经过 changeAge() 函数来改变 Person 对象的 age 属性,为何此次成功了呢?
你依然能够理解为,主函数将 person 复制了一份到 changeAge 函数中去,最终仍是只改变了 changeAge 中复制的那一份参数的值,而本来的参数并无改变,但 changeAge 中的那一份和本来的参数指向了同一个内存区域!
答:java.lang、java.util、java.io、java.net、java.sql。
答:JDK 是 Java 开发工具包,是 Java 开发环境的核心组件,并提供编译、调试和运行一个 Java 程序所须要的全部工具,可执行文件和二进制文件,是一个平台特定的软件。
JRE 是 Java 运行时环境,是 JVM 的实施实现,提供了运行 Java 程序的平台。JRE 包含了 JVM,可是不包含 Java 编译器 / 调试器之类的开发工具。
JVM 是 Java 虚拟机,当咱们运行一个程序时,JVM 负责将字节码转换为特定机器代码,JVM 提供了内存管理 / 垃圾回收和安全机制等。
这种独立于硬件和操做系统,正是 Java 程序能够一次编写多处执行的缘由。
区别:
1. JDK 用于开发,JRE 用于运行 Java 程序;
2. JDK 和 JRE 中都包含 JVM;
3. JVM 是 Java 编程语言的核心而且具备平台独立性。
解析:考察的是对源码的熟悉程度
- 看一个例子:
第一个返回true很好理解,就像上面讲的,a和b指向相同的地址。第二个返回false是为何呢?这是由于 Integer 有缓存机制,在 JVM 启动初期就缓存了 -128 到 127 这个区间内的全部数字。
第三个返回false是由于用了new关键字来开辟了新的空间,i和j两个对象分别指向堆区中的两块内存空间。
咱们能够跟踪一下Integer的源码,看看到底怎么回事。在IDEA中,你只须要按住Ctrl而后点击Integer,就会自动进入jar包中对应的类文件。
跟踪到文件的700多行,你会看到这么一段,感兴趣能够仔细读一下,不用去读也没有关系,由于你只须要知道这是 Java 的一个缓存机制。Integer 类的内部类缓存了 -128 到 127 的全部数字。(事实上,Integer类的缓存上限是能够经过修改系统来更改的,了解就好了,没必要去深究。)
// 第一种:直接赋一个字面量 String str1 = "ABCD"; // 第二种:经过构造器建立 String str2 = new String("ABCD");
解析:考察的是对 String 对象和 JVM 内存划分的知识。
答:String str1 = "ABCD";
最多建立一个String对象,最少不建立String对象.若是常量池中,存在”ABCD”,那么str1直接引用,此时不建立String对象.不然,先在常量池先建立”ABCD”内存空间,再引用.
String str2 = new String("ABCD");
最多建立两个String对象,至少建立一个String对象。new关键字绝对会在堆空间建立一块新的内存区域,因此至少建立一个String对象。
咱们来看图理解一下:
String 对象是一个特殊的存在,须要注意的知识点也比较多,这里给一个以前写的 String 详解的文章连接:传送门 其中包含的问题大概有:1)“+” 怎么链接字符串;2)字符串的比较;3)StringBuilder/StringBuffer/String 的区别;
解析:对于这两个的区别,熟悉的表述是:前置++是先将变量的值加 1,而后使用加 1 后的值参与运算,然后置++则是先使用该值参与运算,而后再将该值加 1 .但事实上,前置++和后置++同样,在参与运算以前都会将变量的值加 1
答:实际上,无论是前置 ++,仍是后置 ++,都是先将变量的值加 1,而后才继续计算的。两者之间真正的区别是:前置 ++ 是将变量的值加 1 后,使用增值后的变量进行运算的,然后置 ++ 是首先将变量赋值给一个临时变量,接下来对变量的值加 1,而后使用那个临时变量进行运算。
答:
- 第一种:经过第三个变量
public class Test{ public static void main(String[] args) { int x = 5; int y = 10; swap(x,y); System.out.println(x); System.out.println(y); Value v = new Value(5,10); swap(v); System.out.println(v.x); System.out.println(v.y); } // 无效的交换:形参的改变没法副作用于实参 public static void swap(int x,int y) { int temp = x; x = y; y = temp; } // 有效的交换:经过引用(变量指向一个对象)来修改为员变量 public static void swap(Value value) { int temp = value.x; value.x = value.y; value.y = temp; } } class Value{ int x; int y; public Value(int x,int y) { this.x = x; this.y = y; } }
输出的结果:
5
10
10
5
这有点相似于C/C++语言中的指针,不过相对来讲更加安全。
事实上,其实若是把基础类型int改为对应的包装类的话其实能够更加简单的完成这个操做,不过须要付出更多的内存代价。
第二种:经过经过相加的方式(相同的 Value 类再也不重复展现)
public class Test{ public static void main(String[] args) { Value v1 = new Value(5,10); swap(v1); System.out.println("v1交换以后的结果为:"); System.out.println(v1.x); System.out.println(v1.y); } public static void swap(Value v) { v.x = v.x + v.y; v.y = v.x - v.y; v.x = v.x - v.y; } }
输出的结果:
v1的交换结果:
10
5
核心的算法就是swap方法:
v.x = v.x + v.y; // 把v.x与v.y的和存储在v.x中 v.y = v.x - v.y; // v.x减掉v.y原本的值即为v.x v.x = v.x - v.y; // v.x减掉v.y的值也就是之前x.y的值
这样就能够不经过临时变量,来达到交换两个变量的目的,若是以为上面的方法不太容易理解,咱们也能够用另外一个参数z来表示上述过程:
int z = v.x + v.y; // 把v.x与v.y的和存储在z中 v.y = z - v.y; // z减掉之前的v.y就等于v.x v.x = z - v.y; // z减掉如今的v.y即之前的v.x,即为v.y
但并不推荐这种作法,缘由在于当数值很大的时候,16进制的求和运算可能形成数据的溢出,虽然最后的结果依然会是咱们所指望的那样,但仍然不是十分可取。
- 第三种:经过异或的方式:
位异或运算符(^)有这样的一个性质,就是两个整型的数据x与y,有: (x ^ y ^ y) == x
这说明,若是一个变量x异或另一个变量y两次,结果为x。经过这一点,能够实现交换两个变量的值:
public class Test{ public static void main(String[] args) { Value v1 = new Value(5,10); swap(v1); System.out.println("v1交换以后的结果为:"); System.out.println(v1.x); System.out.println(v1.y); } public static void swap(Value v) { v.x = v.x ^ v.y; v.y = v.x ^ v.y; v.x = v.x ^ v.y; } }
输出的结果:
v1交换以后的结果为:
10
5
跟上面相加的方式过程几乎相似,只不过运算的方式不一样而已。异或的方法比相加更加可取的地方在于,异或不存在数据溢出。
答:不考虑静态成员的初始化,调用一个对象的构造函数时,程序先调用父类的构造函数(能够经过super关键字指定父类的构造函数,不然默认调用无参的构造函数,而且须要在子类的构造函数的第一行调用),以后静态成员变量的初始化函数和静态初始化块则按照在代码当中的顺序执行,成员变量若是没有指定值的话则赋予默认值,即基本数据类型为0或false等,对象则为null;最后调用自身构造函数。
- 咱们能够写一段程序来对初始化顺序进行一个简单的验证:
public class Derive extends Base { private Member m1 = new Member("Member 1"); { System.out.println("Initial Block()"); } public Derive() { System.out.println("Derive()"); } private Member m2 = new Member("Member 2"); private int i = getInt(); private int getInt() { System.out.println("getInt()"); return 2; } public static void main(String[] args) { new Derive(); } } class Base { public Base() { System.out.println("Base()"); } } class Member { public Member(String m) { System.out.println("Member() "+m); } }
程序的输出结果是:
Base()
Member() Member 1
Initial Block()
Member() Member 2
getInt()
Derive()
答:不是。true、false 是布尔类型的字面常量,null 是引用类型的字面常量。
面试官:那 goto 与 const 呢?
答:是。goto 与 const 均是 Java 语言保留的关键字,即没有任何语法应用。
答:exception 和 error都是 Throwable 的子类。exception 用于用户程序能够捕获的异常状况;error 定义了不指望被用户程序捕获的异常。
exception 表示一种设计或设计的问题,也就是说只要程序正常运行,从不会发生的状况;而 error 表示回复不是不可能可是很困难的状况下的一种严重问题,好比内存溢出,不可能期望程序处理这样的状况。
答:throw 关键字用来在程序中明确的抛出异常,相反,throws 语句用来代表方法不能处理的异常。每个方法都必需要指定哪些异常不能处理,因此方法的调用者才可以确保处理可能发生的异常,多个异常是用逗号分隔的。
小结:本节主要阐述了 Java 基础知识,并无涉及到一些高级的特性,这些问题通常难度不大,适当复习下,应该没问题。
集合这方面的考察至关多,这部分是面试中必考的知识点。
答:Map接口和Collection接口是全部集合框架的父接口:
答:
答:在Java8以前,其底层实现是数组+链表实现,Java8使用了数组+链表+红黑树实现。此时你能够简单的在纸上画图分析:
答:ConcurrentHashMap 结合了 HashMap 和 HashTable 两者的优点。HashMap 没有考虑同步,HashTable 考虑了同步的问题。可是 HashTable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。 ConcurrentHashMap 将 hash 表分为 16 个桶(默认值),诸如get,put,remove 等经常使用操做只锁当前须要用到的桶。
面试官:ConcurrentHashMap的具体实现知道吗?
答:
1. 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色;
2. Segment 是一种可重入的锁 ReentrantLock,每一个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先得到对应的 Segment 锁。
答:
1. 经过将 Key 的 hash 值与 length - 1 进行 & 运算,实现了当前 Key 的定位,2 的幂次方能够减小冲突(碰撞)的次数,提升 HashMap 查询效率
2. 若是 length 为 2 的次幂 则 length-1 转化为二进制一定是 11111……的形式,在于 h 的二进制与操做效率会很是的快,并且空间不浪费;若是 length 不是 2 的次幂,好比 length 为 15,则 length - 1 为 14,对应的二进制为 1110,在于 h 与操做,最后一位都为 0 ,而 0001,0011,0101,1001,1011,0111,1101 这几个位置永远都不能存放元素了,空间浪费至关大,更糟的是这种状况中,数组可使用的位置比数组长度小了不少,这意味着进一步增长了碰撞的概率,减慢了查询的效率!这样就会形成空间的浪费。
答:List元素是有序的,能够重复;Set元素是无序的,不能够重复。
答:
1. List
2. Set
HashSet,初始容量为16,加载因子为0.75; 扩容增量:原容量的 1 倍; 如 HashSet的容量为16,一次扩容后容量为32
3. Map
HashMap,初始容量16,加载因子为0.75; 扩容增量:原容量的 1 倍; 如 HashMap的容量为16,一次扩容后容量为32
答:
1. 前者简单,可是若是须要从新定义比较类型时,须要修改源代码。
2. 后者不须要修改源代码,自定义一个比较器,实现自定义的比较方法。 具体解析参考博客:Java集合框架—Set
答:
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操做时,有可能会产生 fail-fast 机制。
例如:假设存在两个线程(线程一、线程2),线程1经过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
缘由:迭代器在遍历时直接访问集合中的内容,而且在遍历过程当中使用一个 modCount 变量。集合在被遍历期间若是内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素以前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;不然抛出异常,终止遍历。
解决办法:
1. 在遍历过程当中,全部涉及到改变modCount值得地方所有加上synchronized。
2. 使用CopyOnWriteArrayList来替换ArrayList
答:
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中的元素位置都是有顺序的,至关于一种动态的数组,咱们之后能够按位置索引来取出某个元素,而且其中的数据是容许重复的,这是与 HashSet 之类的集合的最大不一样处,HashSet 之类的集合不能够按索引号去检索其中的元素,也不容许有重复的元素。
ArrayList 与 Vector 的区别主要包括两个方面:
面试官:那 ArrayList 和 LinkedList 的区别呢?
答:
面试官:Array 和 ArrayList 有什么区别?何时该应 Array 而不是 ArrayList 呢?
答:它们的区别是:
对于基本类型数据,集合使用自动装箱来减小编码工做量。可是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
答:
Vector newVector = new Vector(); for (int i = 0; i < vector.size(); i++) { Object obj = vector.get(i); if (!newVector.contains(obj)) { newVector.add(obj); } }
还有一种简单的方式,利用了 Set 不容许重复元素的特性:
HashSet set = new HashSet(vector);
小结:本小节是 Java 中关于集合的考察,是 Java 岗位面试中必考的知识点,除了应该掌握以上的问题,包括各个集合的底层实现也建议各位同窗阅读,加深理解。
答:有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。有序数组的缺点是插入操做的时间复杂度是O(n),由于值大的元素须要日后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。