微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!
10个经典的Java面试题
这里有10个经典的Java面试题,也为大家列出了答案。这是Java开发人员面试经常容易遇到的问题,相信你了解和掌握之后一定会有所提高。让我们一起来看看吧。 1.Java的HashMap是如何工作的? HashMap是一个针对数据结构的键值,每个键都会有相应的值,关键是识别这样的值。 HashMap 基于 hashing 原理,我们通过 put ()和 get ()方法储存和获取对象。当我们将键值对传递给 put ()方法时,它调用键对象的 hashCode ()方法来计算 hashcode,让后找到 bucket 位置来储存值对象。当获取对象时,通过键对象的 equals ()方法找到正确的键值对,然后返回值对象。HashMap 使用 LinkedList 来解决碰撞问题,当发生碰撞了,对象将会储存在 LinkedList 的下一个节点中。 HashMap 在每个 LinkedList 节点中储存键值对对象。 2.什么是快速失败的故障安全迭代器? 快速失败的Java迭代器可能会引发ConcurrentModifcationException在底层集合迭代过程中被修改。故障安全作为发生在实例中的一个副本迭代是不会抛出任何异常的。快速失败的故障安全范例定义了当遭遇故障时系统是如何反应的。例如,用于失败的快速迭代器ArrayList和用于故障安全的迭代器ConcurrentHashMap。 3.Java BlockingQueue是什么? Java BlockingQueue是一个并发集合util包的一部分。BlockingQueue队列是一种支持操作,它等待元素变得可用时来检索,同样等待空间可用时来存储元素。 4.什么时候使用ConcurrentHashMap? 在问题2中我们看到ConcurrentHashMap被作为故障安全迭代器的一个实例,它允许完整的并发检索和更新。当有大量的并发更新时,ConcurrentHashMap此时可以被使用。这非常类似于Hashtable,但ConcurrentHashMap不锁定整个表来提供并发,所以从这点上ConcurrentHashMap的性能似乎更好一些。所以当有大量更新时ConcurrentHashMap应该被使用。 5.哪一个List实现了最快插入? LinkedList和ArrayList是另个不同变量列表的实现。ArrayList的优势在于动态的增长数组,非常适合初始时总长度未知的情况下使用。LinkedList的优势在于在中间位置插入和删除操作,速度是最快的。 LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。 ArrayList实现了可变大小的数组。它允许所有元素,包括null。 每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。 6.Iterator和ListIterator的区别 ●ListIterator有add()方法,可以向List中添加对象,而Iterator不能。●ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。 ●ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。●都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。 7.什么是CopyOnWriteArrayList,它与ArrayList有何不同? CopyOnWriteArrayList是ArrayList的一个线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。相比较于ArrayList它的写操作要慢一些,因为它需要实例的快照。 CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差,但是读操作因为操作的对象和写操作不是同一个对象,读之间也不需要加锁,读和写之间的同步处理只是在写完后通过一个简单的"="将引用指向新的数组对象上来,这个几乎不需要时间,这样读操作就很快很安全,适合在多线程里使用,绝对不会发生ConcurrentModificationException ,因此CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。 8.迭代器和枚举之间的区别 如果面试官问这个问题,那么他的意图一定是让你区分Iterator不同于Enumeration的两个方面: ●Iterator允许移除从底层集合的元素。●Iterator的方法名是标准化的。 9.Hashmap如何同步? 当我们需要一个同步的HashMap时,有两种选择: ●使用Collections.synchronizedMap(..)来同步HashMap。●使用ConcurrentHashMap的 这两个选项之间的首选是使用ConcurrentHashMap,这是因为我们不需要锁定整个对象,以及通过ConcurrentHashMap分区地图来获得锁。 10.IdentityHashMap和HashMap的区别 IdentityHashMap是Map接口的实现。不同于HashMap的,这里采用参考平等。 ●在HashMap中如果两个元素是相等的,则key1.equals(key2) ●在IdentityHashMap中如果两个元素是相等的,则key1 == key2
9个Java初始化和回收的面试题
 1.Java中是如何区分重载方法的? 通过重载方法的参数类型和顺序来进行区分的。 注意:若参数类型和顺序均相同时,不管参数名是否相同,编译器均会报错,提示方法已经被定义。且不能根据返回值类型来区分,如果根据返回值来区分的话,有时程序里调用方法时并不需要返回值,那么程序都无法确定该调用那个重载方法。 2.阅读以下程序,解释其中的错误。public static void testLong(long i) {System.out.println("test long");} public static void testFloat(float i) {System.out.println("test float");}public static void main(String[] args) {testLong(50);testFloat(1.5);}testLong没有问题,因为传递的参数50是int型的,而接收方参数是long型的,小范围可以自动转型为大范围的数据类型;testFloat不会通过编译,因为传递的参数1.5是double类型的,而接收方参数是float类型的,大范围转型为小范围数据类型需要显式转换,即改为testFloat(1.5f)。 3.阅读以下程序,解释其中的错误。public static class A {A(int i) {System.out.println("A(int i)");}}public static void main(String[] args) {A a = new A();}在定义了自定义构造器后,若要使用默认构造器,则需要显式指定默认构造器,否则A a = new A();不能编译通过。 4.阅读以下程序,解释其中的错误。 public static class A {A() {System.out.println("A()");}A(int i) {System.out.println("A(int i)");}A(int i, int j) {A();A(i);System.out.println("A(int i, int j)");}}在一个构造器中调用其它构造器时,需要使用this关键字进行调用,如this();在一个构造器中可调用仅一个其它构造器,并且调用其它构造器的语句需放在调用者(即发出调用行为的构造器)语句块的第一行。 5.阅读以下程序,写出执行结果。public static class A {private int i;private String j;int getI() {return i;}String getJ() {return j;}A(int i) {i = i;}A(String j) {this.j = j;}}public static void main(String[] args) {System.out.println(new A(5).getI());System.out.println(new A("hello").getJ());}执行结果为:  hello 对于i = i;这个语句而言,它并未改变实例变量i的值,且i的默认值为0,因此结果也为0,若需要改变实例变量i的值,需要改为this.i = i; 6.在一个类中,声明了若干个static方法和非static方法,请谈谈声明的static方法是否能访问声明的非static方法,说明理由? static方法不能访问非static方法,因为static方法是属于这个类本身的一个方法,在编译期间就已经确定了;而非static方法是属于这个类的对象的方法,需要在实例化之后才能访问到。若在static方法中访问非static方法,将不能通过编译。 7.static关键字为何不能修饰局部变量? static关键字修饰的变量或方法是属于类的,在编译时就已经确定了;而普通变量或方法是属于该由类生成的对象,需要在实例化后才能确定。因此,若static关键字修饰了方法的局部变量,一方面方法需要在实例化之后才能确定,另一方面static修饰的变量需要在编译时确定,这就会导致矛盾。 8.finalize()有何用途?什么情况下需要调用这个函数? 在需要释放内存的地方调用finalize(),则在下一轮垃圾回收时会回收占用的内存,一般情况下不需要显式调用此函数。 垃圾回收器只能回收那些由new关键字创建的对象所占用的内存,那么有些不是通过这种方式(比如调用C++本地方法)所占用的内存如何回收呢?那么就需要使用finalize()了。由于C++中需要使用free()函数来释放内存,所以Java程序在调用C++时需要调用finalize()方法来释放内存。 9.列出并简要解释几种常见垃圾回收技术。 引用计数:每个对象都包含了一个引用计数器,每被引用一次,计数器都加1,引用者被置为null或者销毁,计数器就减1。垃圾收集器进行轮询,一旦发现计数器的值小于1,就回收该对象占用的内存。 停止复制:在垃圾回收机制运行时,程序需要停止运行,将每个活动的对象由一个堆转移到另一个堆,留下的垃圾会被回收。 标记清除:从堆栈和静态存储区域开始,寻找到活的对象就对其进行标记,所有的标记过程完成后,就对垃圾进行回收。
15个顶级多线程面试题及答案
Java 线程面试问题 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是电子交易发展方面相关的。他们会问面试者很多令人混淆的Java线程问题。面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为候选人中有很多只浮于表面。用于直接面向市场交易的高容量和低延时的电子交易系统在本质上是并发的。下面这些是我在不同时间不同地点喜欢问的Java线程问题。我没有提供答案,但只要可能我会给你线索,有些时候这些线索足够回答问题。现在引用Java5并发包关于并发工具和并发集合的问题正在增多。那些问题中ThreadLocal、Blocking Queue、Counting Semaphore和ConcurrentHashMap比较流行。 15个Java多线程面试题及回答 1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。 2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。 3)在java中wait和sleep方法的不同? 通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。 4)用Java实现阻塞队列。 这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。 5)用Java写代码来解决生产者——消费者问题。 与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。 6)用Java编程一个会导致死锁的程序,你将怎么解决? 这是我最喜欢的Java线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免Java中的死锁来得到关于死锁的更多信息。 7) 什么是原子操作,Java中的原子操作是什么? 非常简单的java线程面试问题,接下来的问题是你需要同步一个原子操作。 8) Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同? 自从Java 5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性、顺序性和一致性。 9) 什么是竞争条件?你怎样发现和解决竞争? 这是一道出现在多线程面试的高级阶段的问题。大多数的面试官会问最近你遇到的竞争条件,以及你是怎么解决的。有些时间他们会写简单的代码,然后让你检测出代码的竞争条件。可以参考我之前发布的关于Java竞争条件的文章。在我看来这是最好的java线程面试问题之一,它可以确切的检测候选者解决竞争条件的经验,or writing code which is free of data race or any other race condition。关于这方面最好的书是《Concurrency practices in Java》。 10) 你将如何使用thread dump?你将如何分析Thread dump? 在UNIX中你可以使用kill -3,然后thread dump将会打印日志,在windows中你可以使用”CTRL+Break”。非常简单和专业的线程面试问题,但是如果他问你怎样分析它,就会很棘手。 11) 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法? 这是另一个非常经典的java多线程面试问题。这也是我刚开始写线程程序时候的困惑。现在这个问题通常在电话面试或者是在初中级Java面试的第一轮被问到。这个问题的回答应该是这样的,当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。阅读我之前写的《start与run方法的区别》这篇文章来获得更多信息。 12) Java中你怎样唤醒一个阻塞的线程? 这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了IO阻塞,我并且不认为有一种方法可以中止线程。如果线程因为调用wait()、sleep()、或者join()方法而导致的阻塞,你可以中断线程,并且通过抛出InterruptedException来唤醒它。我之前写的《How to deal with blocking methods in java》有很多关于处理线程阻塞的信息。 13)在Java中CycliBarriar和CountdownLatch有什么区别? 这个线程问题主要用来检测你是否熟悉JDK5中的并发包。这两个的区别是CyclicBarrier可以重复使用已经通过的障碍,而CountdownLatch不能重复使用。 14) 什么是不可变对象,它对写并发应用有什么帮助? 另一个多线程经典面试问题,并不直接跟线程有关,但间接帮助很多。这个java面试问题可以变的非常棘手,如果他要求你写一个不可变对象,或者问你为什么String是不可变的。 15) 你在多线程环境中遇到的共同的问题是什么?你是怎么解决它的? 多线程和并发程序中常遇到的有Memory-interface、竞争条件、死锁、活锁和饥饿。问题是没有止境的,如果你弄错了,将很难发现和调试。这是大多数基于面试的,而不是基于实际应用的Java线程问题。 补充的其它几个问题: 1) 在java中绿色线程和本地线程区别? 2) 线程与进程的区别? 3) 什么是多线程中的上下文切换? 4)死锁与活锁的区别,死锁与馅饼的区别? 5) Java中用到的线程调度算法是什么? 6) 在Java中什么是线程调度? 7) 在线程中你怎么处理不可捕捉异常? 8) 什么是线程组,为什么在Java中不推荐使用? 9) 为什么使用Executor框架比使用应用创建和管理线程好? 10) 在Java中Executor和Executors的区别? 11) 如何在Windows和Linux上查找哪个线程使用的CPU时间最长?
来自投资银行的20个Java面试题
问题一:在多线程环境中使用HashMap会有什么问题?在什么情况下使用get()方法会产生无限循环? HashMap本身没有什么问题,有没有问题取决于你是如何使用它的。比如,你在一个线程里初始化了一个HashMap然后在多个其他线程里对其进行读取,这肯定没有任何问题。有个例子就是使用HashMap来存储系统配置项。当有多于一个线程对HashMap进行修改操作的时候才会真正产生问题,比如增加、删除、更新键值对的时候。因为put()操作可以造成重新分配存储大小(re-sizeing)的动作,因此有可能造成无限循环的发生,所以这时需要使用Hashtable或者ConcurrentHashMap,而后者更优。 问题二:不重写Bean的hashCode()方法是否会对性能带来影响? 这个问题非常好,每个人可能都会有自己的体会。按照我掌握的知识来说,如果一个计算hash的方法写得不好,直接的影响是,当向HashMap中添加元素的时候会更频繁地造成冲突,因此最终增加了耗时。但是自从Java 8开始,这种影响不再像前几个版本那样显著了,因为当冲突的发生超出了一定的限度之后,链表类的实现将会被替换成二叉树(binary tree)实现,这时你仍可以得到O(logN)的开销,优于链表类的O(n)。 问题三:对于一个不可修改的类,它的每个对象是不是都必须声明成final的? 不尽然,因为你可以通过将成员声明成非final且private,并且不要在除了构造函数的其他地方来修改它。不要为它们提供setter方法,同时不会通过任何函数泄露出对此成员的引用。需要记住的是,把对象声明成final仅仅保证了它不会被重新赋上另外一个值,你仍然可以通过此引用来修改引用对象的属性。这一点是关键,面试官通常喜欢听到你强调这一点。 问题四:String的substring()方法内部是如何实现的? 又一个Java面试的好问题,你应该答出“substring方法通过原字符串创建了一个新的对象”,否则你的回答肯定是不能令人满意的。这个问题也经常被拿来测试应聘者对于substring()可能带来的内存泄漏风险是否有所了解。直到Java 1.7版本之前,substring会保存一份原字符串的字符数组的引用,这意味着,如果你从1GB大小的字符串里截取了5个字符,而这5个字符也会阻止那1GB内存被回收,因为这个引用是强引用。 到了Java 1.7,这个问题被解决了,原字符串的字符数组已经不再被引用,但是这个改变也使得substring()创建字符串的操作更加耗时,以前的开销是O(1),现在最坏情况是O(n)。  问题五:能否写一个单例模式,并且保证实例的唯一性? 这算是Java一个比较核心的问题了,面试官期望你能知道在写单例模式时应该对实例的初始化与否进行双重检查。记住对实例的声明使用Volatile关键字,以保证单例模式是线程安全的。下面是一段示例,展示了如何用一种线程安全的方式实现了单例模式: public class Singleton {     private static volatile Singleton _instance;     /**     * Double checked locking code on Singleton     * @return Singelton instance     */    public static Singleton getInstance() {        if (_instance == null) {            synchronized (Singleton.class) {                if (_instance == null) {                    _instance = new Singleton();                }            }        }        return _instance;    } } 问题六:你在写存储过程或者在Java里调用存储过程的时候如何来处理错误情况? 这是个很棘手的Java面试题,答案也并不固定。我的答案是,写存储过程的时候一旦有操作失败,则一定要返回错误码。但是在调用存储过程的时候出错的话捕捉SQLException却是唯一能做的。 问题七:Executor.submit()和Executor.execute()这两个方法有什么区别? 此问题来自另外一篇文章,《15个最流行的java多线程面试问题》,现在对熟练掌握并发技能的开发者的需求越来越大,因此这个问题也越来越引起大家的重视。答案是:前者返回一个Future对象,可以通过这个对象来获得工作线程执行的结果。 当我们考察异常处理的时候,又会发现另外一个不同。当你使用execute提交的任务抛出异常时,此异常将会交由未捕捉异常处理过程来处理(uncaught exception handler),当你没有显式指定一个异常处理器的话,默认情况下仅仅会通过System.err打印出错误堆栈。当你用submit来提交一个任务的时候,这个任务一旦抛出异常(无论是否是运行时异常),那这个异常是任务返回对象的一部分。对这样一种情形,当你调用Future.get()方法的时候,这个方法会重新抛出这个异常,并且会使用ExecutionException进行包装。 问题八:工厂模式和抽象工厂模式有何不同? 抽象工厂模式提供了多一级的抽象。不同的工厂类都继承了同一个抽象工厂方法,但是却根据工厂的类别创建不同的对象。例如,AutomobileFactory, UserFactory, RoleFactory都继承了AbstractFactory,但是每个工厂类创建自己对应类型的对象。下面是工厂模式和抽象工厂模式对应的UML图。  问题九:什么是单例模式?创建单例对象的时候是将整个方法都标记为synchronized好还是仅仅把创建的的语句标记为synchronized好? 在Java中,单例类是指那些在整个Java程序中只存在一份实例的类,例如java.lang.Runtime就是一个单例类。在Java 4版本及以前创建单例会有些麻烦,但是自从Java 5引入了Enum类型之后,事情就变得简单了。可以去看看我的关于如何使用Enum来创建单例类的文章,同时再看看问题五来看看如何在创建单例类的时候进行双重检查。 问题十:能否写一段用Java 4或5来遍历一个HashMap的代码? 事实上,用Java可以有四种方式来遍历任何一个Map,一种是使用keySet()方法获取所有的键,然后遍历这些键,再依次通过get()方法来获取对应的值。第二种方法可以使用entrySet()来获取键值对的集合,然后使用for each语句来遍历这个集合,遍历的时候获得的每个键值对已经包含了键和值。这种算是一种更优的方式,因为每轮遍历的时候同时获得了key和value,无需再调用get()方法,get()方法在那种如果bucket位置有一个巨大的链表的时候的性能开销是O(n)。第三种方法是获取entrySet之后用iterator依次获取每个键值对。第四种方法是获得key set之后用iterator依次获取每个key,然后再根据key来调用get方法。 问题十一:你在什么时候会重写hashCode()和equals()方法? 当你需要根据业务逻辑来进行相等性判断、而不是根据对象相等性来判断的时候你就需要重写这两个函数了。例如,两个Employee对象相等的依据是它们拥有相同的emp_id,尽管它们有可能是两个不同的Object对象,并且分别在不同的地方被创建。同时,如果你准备把它们当作HashMap中的key来使用的话,你也必须重写这两个方法。现在,作为Java中equals-hashcode的一个约定,当你重写equals的时候必须也重写hashcode,否则你会打破诸如Set, Map等集合赖以正常工作的约定。你可以看看我的另外一篇博文来理解这两个方法之间的微妙区别与联系。 问题十二:如果不重写hashCode方法会有什么问题? 如果不重写equals方法的话,equals和hashCode之间的约定就会被打破:当通过equals方法返回相等的两个对象,他们的hashCode也必须一样。如果不重写hashCode方法的话,即使是使用equals方法返回值为true的两个对象,当它们插入同一个map的时候,因为hashCode返回不同所以仍然会被插入到两个不同的位置。这样就打破了HashMap的本来目的,因为Map本身不允许存进去两个key相同的值。当使用put方法插入一个的时候,HashMap会先计算对象的hashcode,然后根据它来找到存储位置(bucket),然后遍历此存储位置上所有的Map.Entry对象来查看是否与待插入对象相同。如果没有提供hashCode的话,这些就都做不到了。 问题十三:我们要同步整个getInstance()方法,还是只同步getInstance()方法中的关键部分? 答案是:仅仅同步关键部分(Critical Section)。这是因为,如果我们同步整个方法的话,每次有线程调用getInstance()方法的时候都会等待其他线程调用完成才行,即使在此方法中并没有执行对象的创建操作。换句话说,我们只需要同步那些创建对象的代码,而创建对象的代码只会执行一次。一旦对象创建完成之后,根本没有必要再对方法进行同步保护了。事实上,从性能上来说,对方法进行同步保护这种编码方法非常要命,因为它会使性能降低10到20倍。下面是单例模式的UML图。  再补充一下,创建线程安全的单例对象有多种方法,你也可以顺便提一下。 问题十四:HashMap,在调用get()方法的时候equals()和hashCode()方法都起了什么样的作用? 这个问题算是对问题十二的补充,应聘者应该知道的是,一旦你提到了hashCode()方法,人们很可能要问HashMap是如何使用这个函数的。当你向HashMap插入一个key的时候,首先,这个对象的hashCode()方法会被调用,调用结果用来计算将要存储的位置(bucket)。 因为某个位置上可能以链表的方式已经包含了多个Map.Entry对象,所以HashMap会使用equals()方法来将此对象与所有这些Map.Entry所包含的key进行对比,以确定此key对象是否已经存在。 问题十五:在Java中如何避免死锁? 你可以通过打破互相等待的局面来避免死锁。为了达到这一点,你需要在代码中合理地安排获取和释放锁的顺序。如果获得锁的顺序是固定的,并且获得的顺序和释放的顺序刚好相反的话,就不会产生出现死锁的条件了。 问题十六:创建字符串对象的时候,使用字面值和使用new String()构造器这两种方式有什么不同? 当我们使用new String构造器来创建字符串的时候,字符串的值会在堆中创建,而不会加入JVM的字符串池中。相反,使用字面值创建的String对象会被放入堆的PermGen段中。例如: String str=new String(“Test”); 这句代码创建的对象str不会放入字符串池中,我们需要显式调用String.intern()方法来将它放入字符串池中。仅仅当你使用字面值创建字符串时,Java才会自动将它放入字符串池中,比如:String s=”Test”。顺便提一下,这里有个容易被忽视的地方,当我们将参数“Test”传入构造器的时候,这个参数是个字面值,因此它也会在字符串池中保存另外一份。想了解更多关于字面值字符串和字符串对象之间的差别,请看这篇文章。 下图很好地解释了这种差异。  问题十七:什么是不可修改对象(Immutable Object)?你能否写一个例子? 不可修改对象是那些一旦被创建就不能修改的对象。对这种对象的任何改动的后果都是会创建一个新的对象,而不是在原对象本身做修改。例如Java中的String类就是不可修改的。大多数这样的类通常都是final类型的,因为这样可以避免自己被继承继而被覆盖方法,在覆盖的方法里,不可修改的特性就难以得到保证了。你通常也可以通过将类的成员设置成private但是非final的来获
各大公司Java后端开发面试题总结
ThreadLocal(线程变量副本)Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量。采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。ThreadLocal在Spring中发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影。Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。友情链接:深入研究java.lang.ThreadLocal类Java内存模型:Java虚拟机规范中将Java运行时数据分为六种。1.程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。2.Java虚拟机栈:线程私有的,与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。3.本地方法栈:跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。4.Java堆:所有线程共享的一块内存区域,对象实例几乎都在这分配内存。5.方法区:各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。6.运行时常量池:代表运行时每个class文件中的常量表。包括几种常量:编译时的数字常量、方法或者域的引用。友情链接: Java中JVM虚拟机详解“你能不能谈谈,java GC是在什么时候,对什么东西,做了什么事情?”在什么时候:1.新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。2.大对象以及长期存活的对象直接进入老年区。3.当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能地获得老年区的空间。对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象。做什么: 新生代:复制清理; 老年代:标记-清除和标记-压缩算法; 永久代:存放Java中的类和加载类的类加载器本身。GC Roots都有哪些: 1. 虚拟机栈中的引用的对象 2. 方法区中静态属性引用的对象,常量引用的对象 3. 本地方法栈中JNI(即一般说的Native方法)引用的对象。友情链接:Java GC的那些事(上)友情链接:Java GC的那些事(下)友情链接:CMS垃圾收集器介绍Synchronized 与Lock都是可重入锁,同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁。Synchronized是悲观锁机制,独占锁。而Locks.ReentrantLock是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。 ReentrantLock适用场景某个线程在等待一个锁的控制权的这段时间需要中断需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程,锁可以绑定多个条件。具有公平锁功能,每个到来的线程都将排队等候。友情链接: Synchronized关键字、Lock,并解释它们之间的区别StringBuffer是线程安全的,每次操作字符串,String会生成一个新的对象,而StringBuffer不会;StringBuilder是非线程安全的友情链接:String、StringBuffer与StringBuilder之间区别fail-fast:机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件happens-before:如果两个操作之间具有happens-before 关系,那么前一个操作的结果就会对后面一个操作可见。1.程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。2.监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。3.volatile变量规则:对一个volatile域的写,happens- before于任意后续对这个volatile域的读。4.传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。5.线程启动规则:Thread对象的start()方法happens- before于此线程的每一个动作。Volatile和Synchronized四个不同点:1 粒度不同,前者针对变量 ,后者锁对象和类2 syn阻塞,volatile线程不阻塞3 syn保证三大特性,volatile不保证原子性4 syn编译器优化,volatile不优化 volatile具备两种特性:1.保证此变量对所有线程的可见性,指一条线程修改了这个变量的值,新值对于其他线程来说是可见的,但并不是多线程安全的。2.禁止指令重排序优化。Volatile如何保证内存可见性:1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。2.当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。同步:就是一个任务的完成需要依赖另外一个任务,只有等待被依赖的任务完成后,依赖任务才能完成。异步:不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,只要自己任务完成了就算完成了,被依赖的任务是否完成会通知回来。(异步的特点就是通知)。 打电话和发短信来比喻同步和异步操作。阻塞:CPU停下来等一个慢的操作完成以后,才会接着完成其他的工作。非阻塞:非阻塞就是在这个慢的执行时,CPU去做其他工作,等这个慢的完成后,CPU才会接着完成后续的操作。非阻塞会造成线程切换增加,增加CPU的使用时间能不能补偿系统的切换成本需要考虑。友情链接:Java并发编程之volatile关键字解析CAS(Compare And Swap) 无锁算法: CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。友情链接:非阻塞同步算法与CAS(Compare and Swap)无锁算法线程池的作用: 在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。第三:提高线程的可管理性。常用线程池:ExecutorService 是主要的实现类,其中常用的有 Executors.newSingleThreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。友情链接:线程池原理友情链接:线程池原理解析类加载器工作机制:1.装载:将Java二进制代码导入jvm中,生成Class文件。2.连接:a)校验:检查载入Class文件数据的正确性 b)准备:给类的静态变量分配存储空间 c)解析:将符号引用转成直接引用3:初始化:对类的静态变量,静态方法和静态代码块执行初始化工作。双亲委派模型:类加载器收到类加载请求,首先将请求委派给父类加载器完成 用户自定义加载器->应用程序加载器->扩展类加载器->启动类加载器。友情链接:深入理解Java虚拟机笔记—双亲委派模型 友情链接:JVM类加载的那些事友情链接:JVM(1):Java 类的加载机制一致性哈希:Memcahed缓存:数据结构:key,value对使用方法:get,put等方法友情链接:hashcode(),equal()方法深入解析Redis数据结构: String—字符串(key-value 类型)Hash—字典(hashmap) Redis的哈希结构可以使你像在数据库中更新一个属性一样只修改某一项属性值List—列表 实现消息队列Set—集合 利用唯一性Sorted Set—有序集合 可以进行排序 可以实现数据持久化友情链接: Spring + Redis 实现数据的缓存java自动装箱拆箱深入剖析谈谈Java反射机制如何写一个不可变类?索引:B+,B-,全文索引Mysql的索引是一个数据结构,旨在使数据库高效的查找数据。常用的数据结构是B+Tree,每个叶子节点不但存放了索引键的相关信息还增加了指向相邻叶子节点的指针,这样就形成了带有顺序访问指针的B+Tree,做这个优化的目的是提高不同区间访问的性能。什么时候使用索引:经常出现在group by,order by和distinc关键字后面的字段经常与其他表进行连接的表,在连接字段上应该建立索引经常出现在Where子句中的字段经常出现用作查询选择的字段Java学习交流QQ群:589809992 我们一起学Java!友情链接:MySQL:InnoDB存储引擎的B+树索引算法友情链接:MySQL索引背后的数据结构及算法原理Spring IOC (控制反转,依赖注入)Spring支持三种依赖注入方式,分别是属性(Setter方法)注入,构造注入和接口注入。在Spring中,那些组成应用的主体及由Spring IOC容器所管理的对象被称之为Bean。Spring的IOC容器通过反射的机制实例化Bean并建立Bean之间的依赖关系。简单地讲,Bean就是由Spring IOC容器初始化、装配及被管理的对象。获取Bean对象的过程,首先通过Resource加载配置文件并启动IOC容器,然后通过getBean方法获取bean对象,就可以调用他的方法。Spring Bean的作用域:Singleton:Spring IOC容器中只有一个共享的Bean实例,一般都是Singleton作用域。Prototype:每一个请求,会产生一个新的Bean实例。Request:每一次http请求会产生一个新的Bean实例。友情链接: Spring框架IOC容器和AOP解析友情链接:浅谈Spring框架注解的用法分析友情链接:关于Spring的69个面试问答——终极列表代理的共有优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。Java静态代理:代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,代理对象可以在调用目标对象相应方法前后加上其他业务处理逻辑。缺点:一个代理类只能代理一个业务类。如果业务类增加方法时,相应的代理类也要增加方法。Java动态代理:Java动态代理是写一个类实现InvocationHandler接口,重写Invoke方法,在Invoke方法可以进行增强处理的逻辑的编写,这个公共代理类在运行的时候才能明确自己要代理的对象,同时可以实现该被代理
Java后端程序员1年工作经验总结
java后端1年经验和技术总结(1)1.引言毕业已经一年有余,这一年里特别感谢技术管理人员的器重,以及同事的帮忙,学到了不少东西。这一年里走过一些弯路,也碰到一些难题,也受到过做为一名开发却经常为系统维护和发布当救火队员的苦恼。遂决定梳理一下自己所学的东西,为大家分享一下。经过一年意识到以前也有很多认识误区,比如:偏爱收集,经常收集各种资料视频塞满一个个硬盘,然后心满意足的看着容量不行动。不重基础,总觉得很多基础东西不需要再看了,其实不懂的地方很多,计算机程序方面任何一个结果都必有原因,不要只会用不知道原理,那是加工厂出来的。现在ide查看代码那么方便,ctrl+点击就进入了JDK查看实现细节。好高骛远,在计算机基础不牢固的情况下,总想着要做架构,弄分布式,搞大数据之类。不重视性能,只求能实现功能,sql查询是不是可以优化,是否有算法妙用,大对象是否要清除。不重视扩展性,模块之间紧密耦合,常用方法不提取成工具类,调用关系混乱等问题。……本文重点不在这些,故只列举了一小部分,下面进入正题。2.语法基础2.1 Java类初始化顺序这是所有情况的类初始化顺序,如果实际类中没有定义则跳过:父类静态变量——父类静态代码块——子类静态代码块——父类非静态变量——父类非静态代码块——父类构造函数——子类非静态变量——子类非静态代码块——子类构造函数2.2 值传递和引用传递可能很多人对此不屑一顾,心想老子都工作一年了,对这些还不熟悉吗?但实际情况并非这样,JDK中东西全部熟悉了吗?以一个最简单的例子开始,你觉得下图中代码执行完之后fatherList中的元素是什么? 这是一个最基础的值传递和引用传递的例子,你觉得好简单,已经想跃跃欲试的挑战了,那么请看下面的,StringBuffer很好理解,但是当你执行一遍之后发现是不是和预想中的输出不一样呢?String不是引用类型吗,怎么会这样呢?如果你无法理解,那么请看下String的实现源码,了解下其在内存中分配的实现原理。 2.3 集合的使用这部分几乎每个人都会用到,而且大家还都不陌生。下图来源于互联网,供大家复习一下。但是利用集合的特性进行巧妙的组合运用能解决优化很多复杂问题。Set不可重复性,List的顺序性,Map的键值对,SortSet/SortMap的有序性,我在工作中有很多复杂的业务都巧妙的使用了这些,涉及到公司保密信息,我就不贴出代码了。工作越久越发现这些和越巧妙。2.3 异常处理1.看着try、catch、finally非常容易,如果和事务传播结合在一起,就会变得极其复杂。2.finally不一定必须执行,return在catch/finally中处理情况(建议亲自操刀试一下)。3.catch中可以继续抛自定义异常(并把异常一步步传递到控制层,利用切面抓取封装异常,返回给调用者)。2.4 面向对象思想一提起面向对象,大家都知道抽象、封装、继承、和多态。但是实际工作经验中又知道多少呢,对于项目中如何巧用估计更不要提了。共性的机会每个都需要用的建立基类,如每个控制层方法可能要通过security获取一个登录用户id,用于根据不同的用户操作不同的数据,可以抽象出一个应用层基类,实现获取id的protect方法。同理DAO层可以利用泛型提取出一个包含增删改查的基类。多态的Override:基类的引用变量不仅可以指向基类的实例对象,也可以指向其子类的实例对象,如果指向子类的实例对象,其调用的方法应该是正在运行的那个对象的方法。在策略模式中使用很普遍。提到面向对象,就不可避免的要说设计模式,在工作中,一个技术大牛写的一个类似策略模式(更复杂一点),十分巧妙的解决了各种业务同一个方法,并且实现了订单、工单、业务的解耦,看得我是非常佩服。我想很多面试中都会问道单例模式吧,还没有理解的建议去看一看。 3.多线程3.1 线程安全这个是老生常谈的问题了,但是确实是问题和bug高发区。线程同步问题不需要单独写了,想必大家都清楚,不太熟悉的建议百度一下。3.1.1 线程安全问题1.代码中如果有同步操作,共享变量要特别注意(这个一般都能意识到)2多个操作能修改数据表中同一条数据的。(这个容易被忽略,业务A可能操作表a,业务B也可以操作表a,业务A、B即使在不同的模块和方法中,也会引起线程安全问题。例如如果一个人访问业务A接口,另一个人访问业务B接口,在web中每个业务请求都是会有单独的一个线程进行处理的,就会出现线程安全问题)。3.不安全的类型使用,例如StringBuffer、StringBuild,HashTable、HashMap等。在工作中我就遇到过有人在for循环进行list的remove,虽然编译器不报错,程序可以运行,但是结果却可想而知。4.Spring的bean默认是单例的,如果有类变量就要特别小心了(一般情况下是没人在控制层、业务层、DAO层等用类变量的,用的话建议是final类型,例如日志log,gson等)。5.多个系统共享数据库情况,这个其实和分布式系统类似用户重复提交问题(即使代码中从数据库读取是否存在进行限制不能解决问题)3.1.2 线程安全解决在需要同步的地方采用安全的类型。JDK锁机制,lock、tryLock,synchronized,wait、notify、notifyAll等Concurrent并发工具包,在处理一些问题上,谁用谁知道。强烈建议查看源码!数据表加锁。(除非某个表的访问频率极低,否则不建议使用)涉及分布式的,采用中间件技术例如zookeeper等解决。3.2 异步异步使用场景不影响主线程,且响应较慢的业务。例如IO操作,第三方服务(短信验证码、app推送、云存储上传等)。如果异步任务很多,就需要使用任务队列了,任务队列可以在代码级别实现,也可以利用redis(优势太明显了)。3.3 多线程通信这方面文章非常多,这里不在详述。1.共享变量方式(共享文件、全局变量,信号量机制等)2.消息队列方式3. 忙等,锁机制3.4多线程实现1.集成Thread类,重写(这里的重写指的是override)run方法,调用start方法执行。2.实现Runable接口,实现run方法,以Runable实例创建thread对象。3.实现Callable接口,实现call方法,FutureTask包装callable接口,FutureTask对象创建thread对象,常用语异步操作,建议使用匿名内部类,方便阅读和使用。额外需要说明的是:1.理解thread的join方法;2.不要认为volitate是线程安全的(不明白原因的建议去看jvm运行时刻内存分配策略);3.sleep时间片结束后并不保证立马获取cpu。4.ThreadLocal能够为每一个线程维护变量副本,常用于在多线程中用空间换时间。4. 开源框架4.1 Hibernate、Mybatis相信每一个java程序员对这些都不陌生,这里不再详述。需要说明的主要以下几点:1.hibernate一级缓存(内置session缓存),二级缓存(可装配sessionFactory缓存),二级缓存会引起并发问题。2.hibernate延迟加载原理理解。3.hibernate 的get、load方法,sava、persist、savaOrUpdate方法区别4.session重建了关联关系却并没有同数据库进行同步和更新5.hibernate session关联关系:detached对象、persistent对象6.Spring data集成,注解方式配置属性和实体。7.mybatis 插件。8.分页查询(数据库)。9.连接池技术4.2 Spring IOC4.1.1 Spring bean1.bean注入 注解方式方便易读,引用第三方(数据库连接,数据库连接池,JedisPool等)采用配置文件方式。2. bean作用域:Singleton,prototype,request,session,global session3.bean生命周期:如下图所示(图片来源于互联网): 4.3 Spring AOP基本概念:关注点、切面Aspect、切入点pointcut、连接点joinpoint、通知advice、织入weave、引入introduction。Spring AOP支持5中类型通知,分别是MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice、MethodInterceptor、IntroductionInterceptor(吐槽一下名字太长)实现方式如下:1.基于代理的AOP2.基于@Aspect注解驱动的切面。(强烈推荐:可读性好,易维护,易扩展,开发快)3.纯POJO切面。4.注入式Aspect切面。4.4 Srping事务4.4.1 事务传播概念:某些操作需要保证原子性,如果中间出错,需要事务回滚。如果某个事务回滚,那么调用该事务的方法中的事务的作出如何的动作,就是事务传播。短时间内写不清楚,建议访问 http://www.cnblogs.com/yangy608/archive/2010/12/15/1907065.html 查看。事务传播属性:1. PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。2. PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。3. PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。4. PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。5. PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。6. PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。事务隔离级别: 1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。4.5 其他Spring 技术栈spring boot 轻量级启动框架spring security 用户权限管理,根据角色和用户,实现UserDetailsService,进行自定义权限管理。spring task 代码级定时任务,注解方式,使用起来非常方便。需要注意的是,如果某次定时任务出了异常而没有进行处理,会导致接下来定时任务失效。如果各个任务相互独立,可以简单用try,catch包围(之前就吃过这方面的亏)。spring data 注解方式定义实体,属性等spring mvc 简单明了的mvc框架。url传值、数组传值、对象传值、对象数组等传值类型,上传/下载文件类型需要注意。spring restful 注意命名,对命名要求很严格。spring shell 命令行方式执行命令,救火、导入导出数据等用起来非常方便、制作报表。5. Web基础 5.1 web容器启动1.web.xml加载顺序: listener -> filter -> servlet2.webt容器启动过程,java新手很怕配置文件,理解完这些有助于熟悉配置文件 http://blog.csdn.net/u014431852/article/details/47042
Java这些冷知识你知道吗?
1)jvm有很多种,其实jvm是一个标准,sun做的那个叫hotspot,作者就是后来v8的作者lars bak,其他公司也做过jvm,其中做得比较好的有bea的jrockit,其他的包括ibm的r9,apple的jvm等在内,都做得不行,所以jvm主要是整合淘汰掉这些做得不好的jvm(s),整合成一个统一的openjdk。2)java是典型的oop语言,其执行效率的优化,最早就是lars bak等人从smalltalk等长期优化的经验中总结出来并apply到hotspot上去滴,而smalltalk在早期apple机上搞出了那种拖拖拽拽就开发出app的做法,后来vc,delphi之类的其实都是抄袭或者说借鉴apple的smalltalk的做法,jobs说微软从头到尾都在抄袭apple是空穴来风,这里空穴来风跟王垠使用的空穴来风是一个意思,有趣的是,java的gui并没有继承这种搞法,反而对这种拖拖拽拽就作出app的做法批判有加,到今天,其实java的gui都还不能真正做到拖拖拽拽就作出来,问题很多,个人建议对于纯java的gui开发,还是以写代码为主。3)jee也是一个或者说是一堆标准,知乎上有些人把maven,jenkins都算做jee是不对滴,jee的标准核心是ejb,其实就是一个xml配置化的java文件,这个标准在4的时候,达到了顶峰状态,几乎所有的挨踢大厂都主动支持该标准,之后开始走下坡路,支持的厂越来越少。4)java和javascript的关系比很多人认为的要密切,javascript里面的java这四个字母可不是白叫的,比如js的版权和商标都控制在oracle手里,oracle对于js的支持甚至超过其对java的支持,并且喜欢捆绑销售,比如jvm里面就有一个js引擎。5)jvm里面除了js engine以外还有一个浏览器排版引擎webkit,就是apple safari和google chrome用的那个那个。6)java支持绝大多数脚本语言,你能叫得上名字的脚本语言,几乎都可以在jvm上执行,比如常见的js,ruby,python,甚至php,lua,只不过除了js以外你需要找到相关的脚本引擎。7)spring的版权被控制在vmware手里,其实spring的那一大堆东西,本质上是一个非标准的jee实现,比如在jee里面用的inject,在spring里面就是autowire,当然spring曾经深刻滴影响了jee,所以有些东西比如di标准,是spring影响下制定出来的,所以spring的做法会比较特例一点。8)maven上的jars数量前两天突破800万,其他语言的类库,排名第二的是npm,大概数量是maven的十分之一,也就是几十万,不知道现在突破100万没有,然后是gem,也就是ruby那个,大概是十几万,下来是python的module,大概数量级是几万,没突破十万。9)java的标准是由一个叫做jcp的组织制定的,所有标准需要经过jcp的执行委员会通过方可执行,jcp几乎包括了你所知道的绝大多数知名挨踢公司和组织,比如google,apple,ibm,intel,arm,red hat,twitter等,还有一些教育机构,比如我国的北京大学,阿里最近一次申请jcp执行委员会成员资格,似乎投票不通过,最近一次执行委员会新增两个成员是arm和jetbrains。10)微软也曾经是jcp甚至是java的主要贡献者,但是利益驱使下,想扩展java,从而破坏java跨平台的特性,所以跟sun闹翻,其本质原因就是想让客户写的java代码跟windows绑定,sun坚决不同意,闹翻,今天回头看这个结果,只能说:双输,sun挂了,微软的ria也离挂不太远了,silverlight已经放弃了,比起当年ie自带有jvm的支持来说,那完全就是两回事。11)除了微软以外,jcp还缺少一个重要组织apache,因为apache跟oracle也闹翻了,oracle似乎并不在乎开源组织,而更在意商业公司的支持。12)java曾经有一个内置的数据库,9之后被剥离。13)j2me是j2se的子集。14)vert.x作者tim fox最早在vmware做spring时候看到了node.js,萌生出了制作支持多核的node.x的想法,并在离开vmware后加入red hat将其实现,vmware看到后开始耍无赖,claim node.x后来改叫vert.x的版权,不惜跟red hat打官司,后来各方妥协,将其交给eclipse foundation。15)oracle在收购bea之前,一开始的目标并不是bea和bea的weblogic,而是jboss,但是jboss表现出了极为有种的一面,在oracle收购成功之前,投入了red hat的怀抱,因为都是开源组织,从此jboss成了red hat的一个子部门,oracle收购jboss失败之后,转向bea,庄思浩气死了,但是没用,最后还是被恶意收购。16)sun在玩不下去之前最早尝试接触的目标是ibm,ibm嫌太贵,放弃之后,被转手给了oracle。17)vert.x的作者tim fox在离开red hat之前曾经发过twitter抱怨,外人比如我们,猜测是因为red hat内部已经有了一个jboss,所以跟vert.x在应用上有了重叠,所以导致tim fox的出走,但是出走之后,red hat答应对vert.x做持续性的战略投入,所以vert.x core的几个developers,其实拿的是red hat的工资,但是vert.x的版权并不在red hat手里,而在eclipse foundation手里。18)vert.x的几个核心开发人员都是google summer of code的导师,每年年初时候会招收在校大学生搞项目。19)教育机构相关:scala的作者马丁是德国人,eth的博导,groovy的主要领导人是法国人,jruby背后是东京大学,jboss的作者是法国大学校x的校友,x就是伽罗瓦考不进去的那所大学,伽罗瓦进不了x,所以改读巴黎高师,tim fox毕业于帝国理工,主席去的那个,netty作者trustin lee是acm银牌,现在line工作,毕业于sky里面的延世大学,kotlin是毛子公司jetbrains的作品,看linkedin,很多人毕业自圣彼得堡大学,spring作者rod johnson是悉尼大学的音乐博士,hibernate作者gavin king是澳洲莫那什大学的数学本科毕业生,james gosling这种cmu和calgory的估计烂大街了,sun是斯坦福大学网络的意思,夹带两个私货,aspectj有一个维护小组在mcgill,hbase跟waterloo关系密切。20)java早期被人认为慢,跟java坚持不用硬件加速渲染有关,死活就是不肯接入directx和opengl,7之后总算开窍,搞了一个图形引擎接入了directx/opengl。21)casssandra是facebook做失败的项目,被贡献给了apache之后老树开花。22)groovy被贡献给了apache,现在叫做apache groovy,ceylon被贡献给了eclipse,现在叫做eclipse ceylon。23)netflix现在是java shop,之前是用.net的。先想到这么多,有空再写。我的Java学习交流QQ群:589809992  禁止闲聊,非喜勿进!
Java限流策略
概要在大数据量高并发访问时,经常会出现服务或接口面对暴涨的请求而不可用的情况,甚至引发连锁反映导致整个系统崩溃。此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速率,就进行等待、排队、降级、拒绝服务等。在限流时,常见的两种算法是漏桶和令牌桶算法算法。限流算法令牌桶(Token Bucket)、漏桶(leaky bucket)和计数器算法是最常用的三种限流的算法。1. 令牌桶算法 令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 当桶满时,新添加的令牌被丢弃或拒绝。令牌桶算法示例public class RateLimiterDemo {private static RateLimiter limiter = RateLimiter.create(5);public static void exec() {limiter.acquire(1);try {// 处理核心逻辑TimeUnit.SECONDS.sleep(1);System.out.println("--" + System.currentTimeMillis() / 1000);} catch (InterruptedException e) {e.printStackTrace();}}}Guava RateLimiter 提供了令牌桶算法可用于平滑突发限流策略。该示例为每秒中产生5个令牌,每200毫秒会产生一个令牌。limiter.acquire() 表示消费一个令牌。当桶中有足够的令牌时,则直接返回0,否则阻塞,直到有可用的令牌数才返回,返回的值为阻塞的时间。2. 漏桶算法 它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量,数据可以以任意速度流入到漏桶中。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。 漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶为空,则不需要流出水滴,如果漏桶(包缓存)溢出,那么水滴会被溢出丢弃。3. 计数器限流算法计数器限流算法也是比较常用的,主要用来限制总并发数,比如数据库连接池大小、线程池大小、程序访问并发数等都是使用计数器算法。使用计数器限流示例1public class CountRateLimiterDemo1 {private static AtomicInteger count = new AtomicInteger(0);public static void exec() {if (count.get() >= 5) {System.out.println("请求用户过多,请稍后在试!"+System.currentTimeMillis()/1000);} else {count.incrementAndGet();try {//处理核心逻辑TimeUnit.SECONDS.sleep(1);System.out.println("--"+System.currentTimeMillis()/1000);} catch (InterruptedException e) {e.printStackTrace();} finally {count.decrementAndGet();}}}}使用AomicInteger来进行统计当前正在并发执行的次数,如果超过域值就简单粗暴的直接响应给用户,说明系统繁忙,请稍后再试或其它跟业务相关的信息。弊端:使用 AomicInteger 简单粗暴超过域值就拒绝请求,可能只是瞬时的请求量高,也会拒绝请求。使用计数器限流示例2public class CountRateLimiterDemo2 {private static Semaphore semphore = new Semaphore(5);public static void exec() {if(semphore.getQueueLength()>100){System.out.println("当前等待排队的任务数大于100,请稍候再试...");}try {semphore.acquire();// 处理核心逻辑TimeUnit.SECONDS.sleep(1);System.out.println("--" + System.currentTimeMillis() / 1000);} catch (InterruptedException e) {e.printStackTrace();} finally {semphore.release();}}}使用Semaphore信号量来控制并发执行的次数,如果超过域值信号量,则进入阻塞队列中排队等待获取信号量进行执行。如果阻塞队列中排队的请求过多超出系统处理能力,则可以在拒绝请求。相对Atomic优点:如果是瞬时的高并发,可以使请求在阻塞队列中排队,而不是马上拒绝请求,从而达到一个流量削峰的目的。我的Java学习交流QQ群:589809992  禁止闲聊,非喜勿进!
有效处理Java异常三原则
Java中异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮、易于调试。异常之所以是一种强大的调试手段,在于其回答了以下三个问题:什么出了错?在哪出的错?为什么出错?在有效使用异常的情况下,异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出,如果你的异常没有回答以上全部问题,那么可能你没有很好地使用它们。有三个原则可以帮助你在调试过程中最大限度地使用好异常,这三个原则是:具体明确提早抛出延迟捕获为了阐述有效异常处理的这三个原则,本文通过杜撰个人财务管理器类JCheckbook进行讨论,JCheckbook用于记录及追踪诸如存取款,票据开具之类的银行账户活动。具体明确Java定义了一个异常类的层次结构,其以Throwable开始,扩展出Error和Exception,而Exception又扩展出RuntimeException.如图1所示.图1.Java异常层次结构这四个类是泛化的,并不提供多少出错信息,虽然实例化这几个类是语法上合法的(如:new Throwable()),但是最好还是把它们当虚基类看,使用它们更加特化的子类。Java已经提供了大量异常子类,如需更加具体,你也可以定义自己的异常类。例 如:java.io package包中定义了Exception类的子类IOException,更加特化确的是 FileNotFoundException,EOFException和ObjectStreamException这些IOException的子 类。每一种都描述了一类特定的I/O错误:分别是文件丢失,异常文件结尾和错误的序列化对象流.异常越具体,我们的程序就能更好地回答”什么出了错”这个 问题。捕 获异常时尽量明确也很重要。例如:JCheckbook可以通过重新询问用户文件名来处理FileNotFoundException,对于 EOFException,它可以根据异常抛出前读取的信息继续运行。如果抛出的是ObjectStreamException,则程序应该提示用户文件 已损坏,应当使用备份文件或者其他文件。Java让明确捕获异常变得容易,因为我们可以对同一try块定义多个catch块,从而对每种异常分别进行恰当的处理。1234567891011121314151617181920File prefsFile = new File(prefsFilename); try{    readPreferences(prefsFile);}catch (FileNotFoundException e){    // alert the user that the specified file    // does not exist}catch (EOFException e){    // alert the user that the end of the file    // was reached}catch (ObjectStreamException e){     // alert the user that the file is corrupted}catch (IOException e){    // alert the user that some other I/O    // error occurred}JCheckbook 通过使用多个catch块来给用户提供捕获到异常的明确信息。举例来说:如果捕获了FileNotFoundException,它可以提示用户指定另一 个文件,某些情况下多个catch块带来的额外编码工作量可能是非必要的负担,但在这个例子中,额外的代码的确帮助程序提供了对用户更友好的响应。除前三个catch块处理的异常之外,最后一个catch块在IOException抛出时给用户提供了更泛化的错误信息.这样一来,程序就可以尽可能提供具体的信息,但也有能力处理未预料到的其他异常。有 时开发人员会捕获范化异常,并显示异常类名称或者打印堆栈信息以求"具体"。千万别这么干!用户看到java.io.EOFException或者堆栈信息 只会头疼而不是获得帮助。应当捕获具体的异常并且用"人话"给用户提示确切的信息。不过,异常堆栈倒是可以在你的日志文件里打印。记住,异常和堆栈信息是用来帮助开发人 员而不是用户的。最后,应该注意到JCheckbook并没有在readPreferences()中捕获异常,而是将捕获和处理异常留到用户界面层来做,这样就能用对话框或其他方式来通知用户。这被称为"延迟捕获",下文就会谈到。提早抛出异常堆栈信息提供了导致异常出现的方法调用链的精确顺序,包括每个方法调用的类名,方法名,代码文件名甚至行数,以此来精确定位异常出现的现场。1234567java.lang.NullPointerExceptionat java.io.FileInputStream.open(Native Method)at java.io.FileInputStream.<init>(FileInputStream.java:103)at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225)at jcheckbook.JCheckbook.startup(JCheckbook.java:116)at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)at jcheckbook.JCheckbook.main(JCheckbook.java:318)以 上展示了FileInputStream类的open()方法抛出NullPointerException的情况。不过注意 FileInputStream.close()是标准Java类库的一部分,很可能导致这个异常的问题原因在于我们的代码本身而不是Java API。所以问题很可能出现在前面的其中一个方法,幸好它也在堆栈信息中打印出来了。不幸的是,NullPointerException是Java中信息量最少的(却也是最常遭遇且让人崩溃的)异常。它压根不提我们最关心的事情:到底哪里是null。所以我们不得不回退几步去找哪里出了错。通过逐步回退跟踪堆栈信息并检查代码,我们可以确定错误原因是向readPreferences()传入了一个空文件名参数。既然readPreferences()知道它不能处理空文件名,所以马上检查该条件:123456789101112public void readPreferences(String filename)throws IllegalArgumentException{    if (filename == null){         throw new IllegalArgumentException("filename is null");    }  //if    //...perform other operations...    InputStream in = new FileInputStream(filename);    //...read the preferences file...}通过提早抛出异常(又称"迅速失败"),异常得以清晰又准确。堆栈信息立即反映出什么出了错(提供了非法参数值),为什么出错(文件名不能为空值),以及哪里出的错(readPreferences()的前部分)。这样我们的堆栈信息就能如实提供:12345java.lang.IllegalArgumentException: filename is nullat jcheckbook.JCheckbook.readPreferences(JCheckbook.java:207)at jcheckbook.JCheckbook.startup(JCheckbook.java:116)at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)at jcheckbook.JCheckbook.main(JCheckbook.java:318)另外,其中包含的异常信息("文件名为空")通过明确回答什么为空这一问题使得异常提供的信息更加丰富,而这一答案是我们之前代码中抛出的NullPointerException所无法提供的。通过在检测到错误时立刻抛出异常来实现迅速失败,可以有效避免不必要的对象构造或资源占用,比如文件或网络连接。同样,打开这些资源所带来的清理操作也可以省却。延迟捕获菜鸟和高手都可能犯的一个错是,在程序有能力处理异常之前就捕获它。Java编译器通过要求检查出的异常必须被捕获或抛出而间接助长了这种行为。自然而然的做法就是立即将代码用try块包装起来,并使用catch捕获异常,以免编译器报错。问 题在于,捕获之后该拿异常怎么办?最不该做的就是什么都不做。空的catch块等于把整个异常丢进黑洞,能够说明何时何处为何出错的所有信息都会永远丢失。把异常写到日志中还稍微好点,至少还有记录可查。但我们总不能指望用户去阅读或者理解日志文件和异常信息。让readPreferences()显示错误信息对话框也不合适,因为虽然JCheckbook目前是桌面应用程序,但我们还计划将它变成基于HTML的Web应用。那样的话,显示错误对话框显然不是个选择。同时,不管HTML还是C/S版本,配置信息都是在服务器上读取的,而错误信息需要显示给Web浏览器或者客户端程序。 readPreferences()应当在设计时将这些未来需求也考虑在内。适当分离用户界面代码和程序逻辑可以提高我们代码的可重用性。在有条件处理异常之前过早捕获它,通常会导致更严重的错误和其他异常。例如,如果上文的readPreferences()方法在调用FileInputStream构造方法时立即捕获和记录可能抛出的FileNotFoundException,代码会变成下面这样:1234567891011121314151617public void readPreferences(String filename){   //...    InputStream in = null;    // DO NOT DO THIS!!!try{    in = new FileInputStream(filename);}catch (FileNotFoundException e){    logger.log(e);} in.read(...); //...}上 面的代码在完全没有能力从FileNotFoundException中恢复过来的情况下就捕获了它。如果文件无法找到,下面的方法显然无法读取它。如果 readPreferences()被要求读取不存在的文件时会发生什么情况?当然,FileNotFoundException会被记录下来,如果我们 当时去看日志文件的话,就会知道。然而当程序尝试从文件中读取数据时会发生什么?既然文件不存在,变量in就是空的,一个 NullPointerException就会被抛出。调试程序时,本能告诉我们要看日志最后面的信息。那将会是NullPointerException,非常让人讨厌的是这个异常非常不具体。错误信息不仅误导我们什么出了错(真正的错误是FileNotFoundException而不是NullPointerException),还误导了错误的出处。真正 的问题出在抛出NullPointerException处的数行之外,这之间有可能存在好几次方法的调用和类的销毁。我们的注意力被这条小鱼从真正的错误处吸引了过来,一直到我们往回看日志才能发现问题的源头。既然readPreferences() 真正应该做的事情不是捕获这些异常,那应该是什么?看起来有点有悖常理,通常最合适的做法其实是什么都不做,不要马上捕获异常。把责任交给 readPreferences()的调用者,让它来研究处理配置文件缺失的恰当方法,它有可能会提示用户指定其他文件,或者使用默认值,实在不行的话也 许警告用户并退出程序
成为伟大程序员的 10 个要点
最近我在接受采访时被问到我关于成为一名伟大程序员的见解。这是一个有趣的问题,我认为我们都可以是伟大的程序员,无论我们的天赋如何,如果我们遵循一些规则的话——我相信——这应该是常识。实际上,这些规则并不只适用于编程领域,也适合任何专业。当然,这10个要点中的所有内容并不都是完全正儿八经的,有些事情只是我的看法,你的情况可能会有所不同,所以如果出现矛盾的话,不要耿耿于怀。这些要点是:1.学习如何提问提问题的程序员基本上有这些类型:完美主义者:特别是在询问关于某些开源工具的问题时,他们可能已经通过代码进行了调试,发现了问题的真正原因。但是即使没有发现真正原因,完美主义者也会讲明白这个问题,重现步骤,建议可能行得通的解决方法,或者甚至是,建议可能行得通的修复途径。事实上,完美主义者没有问题。只有答案。话匣子:这个人实际上没有问问题。他们表明他们的想法,有时会到处放置浮夸的问号。对于问题,他们给出的是他们的思路流程,如果你揣着答案等的话,他们要么自己找到了答案,要么在多封电子邮件之后才问出真正的问题。“哦,对了,我发现这个需求是完全错误的,我用一些其他的技术解决了这个问题。实际上,我完全改变了库。”呵呵。只希望他们别再问问题了。笨蛋:代码在这。我不知道哪里出错了?请帮帮我。经理:对于这种类型的人,时间就是金钱。问题一定很短,答案越快越好。令人令人啼笑皆非的是,因为保持问题简短(意即:不完整,不简洁),大多数情况下,会丢失很多重要的细节,然后为了解答问题,程序员只能请求更多细节。所以,经理(自然会失望,因为他得到的并非是一个答案而是一个新的问题)会再次发送一个短的讯息,并且更紧急地要求答案。循环往复。最后可能需要1-2周的时间才能解答。抱怨者:这类人不问问题。他们一直一直抱怨,直到问题消失。如果情况没有变好,那就有了更多的理由抱怨。现在应该清楚的是,一个精心准备的问题(简明扼要,简单,简短,但有足够的细节)将会产生更佳的答案。如果你确切知道对于该问题你需要学习什么,那么更有可能得偿所愿。2.学习如何不提出问题实际上,最好尽量避免提问。或许你可以自己弄清楚呢?当然情况并不总是如此。许多事情你根本无法知道,通过询问领域专家,有助于找到抵达成功最快和最有效的途径。但是,经常自己去尝试解决问题有很多好处:通过这种艰辛的方法学到的东西能够更好地保存到记忆中——我们将牢牢记住所学到东西。自己去寻找答案更有价值。你不会制造“噪音”。还记得前面所说的“话匣子”吗?除非你询问的人有责任回答问题(从而推迟他们的工作),否则他们可能会在不了解你的思维过程的情况下,来尝试回答每一个不完整的“问题”。这对任何人都没有帮助。通过推迟问问题(至少一段时间),你可以收集更多的相关信息,然后提供给可能能够回答问题的人。想想“完美主义者”,他们首先花更多时间寻找细节,然后自己解答问题。通过训练你可以更擅于提问。这需要时间。3.不要遗留破碎的窗户最近有一篇非常有趣的文章,是关于不要留下破窗户的。文章的本质是永远不要妥协于质量。永远不要成为逃兵。永远不要遗留…破碎的窗户。以下引用自这篇文章:“当我们采取一些捷径在最短的时间内提供一些东西时,反映了我们的粗心大意的代码会让我们之后的开发人员(来自同一个团队,未来的团队,甚至我们自己!)得出一个重要的结论:对我们所生产的代码付出足够的关注并不重要。应用程序渐渐开始恶化将是一个不可阻挡的过程。”其实,这并非意味着要成为一个完美主义者。有时,修复破碎的窗户是可以推迟的。但是,通常情况下,对于允许窗户被打破和保持打破状态,没有人会觉得开心。我们程序员不开心,我们的客户不开心,我们的用户不开心,我们的项目经理也不开心。这是一种态度,是作为专业人士的核心内容。Benjamin Franklin怎么看呢?“低价格的甜蜜被遗忘之后,低质量的苦涩将回味悠长。”一切都是如此。“低价”是我们用一种草率的方式来实现某些东西而获得的快速胜利。4.软件应该是确定性的。这就是要瞄准的目标!在理想化的世界中,软件中的一切都应该是“确定性的”。我们都应该是函数式程序员,编写没有副作用的纯粹的函数。如String.contains()。无论执行以下操作多少次:assertTrue("abcde".contains("bc"));…结果总是相同的,都是预期的结果。哪怕宇宙爆炸对这一计算也没有影响。这是确定性的。我们也可以在我们自己的程序中,而不仅仅是在标准库中做到这一目标。我们可以尝试尽可能多地编写无副作用的确定性模块。这真的与我们选择什么技术无关。确定性编程可以用任何语言完成——即使函数语言有更多工具也可以通过更复杂的类型系统来防止意外的副作用。但是我所示的例子是一个Java示例。对象方向允许确定性。对的,像PL / SQL这样的程序语言允许确定性。如果要在索引中使用函数,那么需要请求确定性的函数:CREATE INDEX upper_first_name ON customer (upper (first_name));-- Deterministic function here: -----------^^^^^^^^^^^^^^^^^^这又是一个规则问题。有副作用的过程/方法/“函数”是为“破窗户”。有副作用也许会更容易维护,当然希望最终可以消灭副作用。但这通常是自己骗自己。当将来的某一天意外突现的时候,就是你付出昂贵代价的时候。别不相信,说曹操曹操就到。5.接受意料之外的事情程序员始终应该遵守墨菲定律。一切都可能被打破。并且它即将被打破。作为软件工程师,我们应该谨记它是会破掉的。因为我们的世界是不确定的,所以我们正在实现的业务需求也是不确定的。我们只有在终于能够确定的时候,才能实现技巧#4(确定论)。否则,我们将不可避免地进入不确定论的世界(也就是“现实世界”),即一个将会出错的世界。所以,要以此为基础。接受意料之外的事情。训练你内心的洪荒之力,从积极的角度预见各种麻烦。当然,如何以简洁的方式写代码来预见各种麻烦就是另一个故事了。如何从那些可能会失败的东西(因此不需要处理)中辨别那些将会失败的东西(因此需要处理),还是需要通过一些实践滴。6.不要货物崇拜。不要教条主义。始终具体情况具体对待。所有教给你的内容都存在潜在的错误。即使是那些流行语。引用一句很不错的话:“我的职业生涯至少有50%是为了帮助或解脱由教条主义引发的一个个灾难。我们的职业充满了虚假。我们喜欢把自己当作数学家,坚持最纯粹的思想,认为它们一定是正确的。那是一条歧路。我们的职业构建在数学的基础之上,但除非你进入范畴论或关系代数的时髦世界(即便你真的进入,我也怀疑一切是否是“正确的”),否则你就得面对现实世界务实的业务需求。好吧,坦率地说,这离完美还有十万八千里。让我们来看看一些最流行的编程语言:CJavaSQL你真的觉得这些语言一点都不像数学吗?行,不如我们先来讨论段错误,Java泛型和SQL三值逻辑。这些语言是由实用主义者建立的平台。所有这些都有一些非常酷的理论背景,但最终,还是有了这些工具。对于建立在语言之上的所有东西也是如此:库,框架,设计模式,甚至架构。没有什么是对的或是错的。一切都是为某些上下文设计的工具。想想在其上下文中的工具。永远不要把这个工具当成一个独立的理由。我们不是“为艺术而艺术”。所以对这些质疑说不:XMLJSON功能编程面向对象编程设计模式微服务三层架构DDDTDD实际上:*DD不胜枚举所有这些都是某些给定上下文的好工具,但并不总是如此,要学会具体情况具体对待。保持好奇心,开发创造力,知道何时才需要使用这些工具,将有助于你成为一个更优秀的程序员。7.就是干这是真理。话说,总有一些牛人出类拔萃,能够傲视群雄,让人鞭长莫及。但大多数程序员只达到“好”的级别,或是有潜力达到“好”的程度。那么怎么才能成为一名好的程序员呢?正如罗马不是一天建成的,伟大的软件也不是一天可以写成的,受欢迎的人并非我们这个时代唯一的英雄。我遇到过许多默默无闻但伟大的程序员,他们孜孜不倦地攻克软件难题,解决了许多小公司隐蔽的问题。伟大的程序员都有一个共同点:遇到问题就是干。练习,实践。每天都致力于工作与学习,然后变得越来越优秀。想要更擅长SQL?那就干吧!每天都尝试用一些新功能编写一个SQL语句。使用window functions。分组。递归。分区的外连接。MODEL和/或MATCH_RECOGNIZE子句。不需要每次都交付生产,就是为了实践。这些都是有价值的。8.专注一个主题(从长远的角度)可能只有很少一部分“优秀的”全栈开发人员独领风骚。事实上,大多数全栈开发人员都将位于中间水平。当然,一个小团队可能只需要几个全栈开发人员,就可以涵盖很多业务逻辑,快速推出一个新的软件。但是,软件将非常笨拙,“马马虎虎能工作”。也许这对于只要可行即可的产品阶段来说就已足够,但从长远来看,会导致全栈开发人员将没有时间来正确分析(或预见!)更复杂的问题。主要专注一个主题,并真正擅长这个方面。真金不怕火来炼,只要你有本事,那么走到哪里都需要。所以,致力于你的职业生涯,做一些真正好的东西,而不是“差不多就行”。9.涉猎广泛虽然你应该主要关注一个主题,但不应该完全遗忘其他方面。你永远不能马上真正擅长SQL、扩大、扩展、低级性能、CSS、面向对象、需求工程、架构等等的所有内容(见技巧#8)。这是不可能的。但你至少应该明白它们每一个的本质。你需要明白何时SQL是正确选择(以及何时不是)。何时低级别性能的调整很重要(何时不是)。CSS原则上如何工作。面向对象、FP优点。等等。你应该花一些时间涉猎这些(以及更多)概念和技术,以便更好地了解它们的重要性。知道何时应用它们,然后再找专业人士来实际执行工作。涉猎新的范式和技术,有助于你用全然不同的思维方式思考,可能你会在以后的日常工作中不自觉地以某种方式用到它们。10.保持简单,傻瓜式爱因斯坦曾说:“Everything should be made as simple as possible, but no simpler.”(“任何事情都应该尽可能简化,直到没法再简化为止。”)没有人能够处理巨大的复杂性。在软件中不能,在生活的任何其他方面也不能。复杂性是好软件的杀手,因此简单性是使能者。易于明白。难于实现。你需要大量时间和实践才能识别和生产出简单。当然,你可以遵循许多规则来实现简单化。最简单的规则之一就是使用只有几个参数的方法/函数。让我们来看看吧。前面提到的String.contains()方法就是如此。我们可以写”abcde”.contains(“bcd”),不阅读任何文档,每个人都能立即了解这做什么以及为什么。该方法做了一件事情,并且只做这一件。没有复杂的上下文/设置/其他传递给该方法的参数。没有“特殊情况”,也没有任何警告。此外,在库中简化比在业务逻辑中要简单得多。那么我们能实现吗?也许吧。通过实践。通过重构。但像伟大的软件一样,简单性也不是一天可以搞定的。(高级技巧:应用康威定律。在一个业务超级复杂的环境中编写又好又简单的软件是完全不可能的。要么你选择复杂性和丑陋,要么你最好摆脱那个业务)。我的Java学习交流QQ群:589809992 我们一起学Java!
Java进阶面试问题列表
面向对象编程的基本理念与核心设计思想解释下多态性(polymorphism),封装性(encapsulation),内聚(cohesion)以及耦合(coupling)。继承(Inheritance)与聚合(Aggregation)的区别在哪里。你是如何理解干净的代码(Clean Code)与技术负债(Technical Debt)的。描述下常用的重构技巧。阐述下 SOLID原则。其他的譬如 KISS,DRY,YAGNI 等原则又是什么含义。什么是设计模式(Design Patterns)?你知道哪些设计模式?你有了解过存在哪些反模式(Anti-Patterns)吗?你会如何设计登陆舰/数学表达式计算程序/一条龙?你知道哪些基本的排序算法,它们的计算复杂度如何?在给定数据的情况下你会倾向于使用哪种算法呢?尝试编写如下代码: -计算指定数字的阶乘 -开发 Fizz Buzz 小游戏 -倒转句子中的单词 -回文字符串检测 -枚举给定字符串的所有排列组合Java 核心概念equals 与 hashCode 的异同点在哪里?Java 的集合中又是如何使用它们的。描述下 Java 中集合(Collections),接口(Interfaces),实现(Implementations)的概念。LinkedList 与 ArrayList 的区别是什么?基础类型(Primitives)与封装类型(Wrappers)的区别在哪里?final 与 static 关键字可以用于哪里?它们的作用是什么?阐述下 Java 中的访问描述符(Access Modifiers)。描述下 String,StringBuilder 以及 StringBuffer 区别。接口(Interface)与抽象类(Abstract Class)的区别在哪里。覆盖(Overriding)与重载(OverLoading)的区别在哪里。异常分为哪几种类型?以及所谓的handle or declare原则应该如何理解?简述垃圾回收器的工作原理。你是如何处理内存泄露或者栈溢出问题的?如何构建不可变的类结构?关键点在哪里?什么是 JIT 编译?Java学习交流QQ群:589809992 我们一起学Java!Java 8 / Java 7 为我们提供了什么新功能?即将到来的 Java 9 又带来了怎样的新功- - 能?Hibernate / 数据库请解释下 ORM。简述下 Hibernate 的优劣特性。Hibernate 与 JPA 区别在哪?Hibernate 最新版提供了哪些特性?什么是懒加载(Lazy Loading)?什么是 N+1 难题?介绍一些熟悉的 Hibernate 注释。简介下 Hibernate Session 与 SessionFactory。Entity Beans 的状态有哪些。Hibernate 中的缓存分为几层。Hibernate 中事务的支持分为几级?什么是乐观锁(Optimistic Locking)?简述下 ACID 原则。简述下数据库正则化(Normalizations)。请介绍下你日常工作中优化慢查询(Slow Query)的策略。Spring新版的 Spring 中有哪些新特性?介绍下 Spring 的优势与缺陷。什么是控制反转(Inversion of Control)与依赖注入(Dependency Injection)?你用过哪些 Spring 的模块?Spring 中是如何使用依赖注入的?Spring 中提供了几种自动注入的机制?介绍下 Spring MVC。Spring 中 Scopes 有哪些?Spring 中 Bean 的生命周期包含哪些步骤?Java学习交流QQ群:589809992 我们一起学Java!Spring Bean 与 EJB Bean 的区别在哪里?其他主题介绍下切面编程(Aspect Oriented Programming)。概述下 GET 与 POST 的区别。Web Server、Web Container 与 Application Server 的区别是什么?简要介绍下从浏览器输入 URL 开始到获取到请求界面之后 Java Web 应用中发生了什么。什么是 N 层架构?微服务(MicroServices)与巨石型应用(Monolithic Applications)之间的区别在哪里?你知道哪些商业级设计模式?你是如何测试一个应用的?知道哪些测试框架?你是如何测试单个方法的?在你的职业生涯中,算得上最困难的技术挑战是什么?什么是领域驱动开发(Domain Driven Development)?介绍下一些你最爱的 IDE 的常用插件。除了 IDE 之外,你的日常工作中还会用到哪些工具?你使用什么版本管理工具?分支(Branch)与标签(Tag)之间的区别在哪里?你常用的持续集成(Continuous Integration)、静态代码分析(Static Code Analysis)工具有哪些?我有一个微信公众号,经常会分享一些Java技术相关的干货。如果你喜欢我的分享,可以用微信搜索“Java团长”或者“javatuanzhang”关注。