Java开发成长之路第一年

  • 介绍

本文总结了要走Java开发这条路的成长轨迹。欢迎各位看客留下脚印。

正文开始之前,先申明几点:本文说的“Java开发”指的是以Java后端为主、少量的前端开发,如果要了解更多前端开发的内容请移步;另外,本文假设您在学习Java之前没有其他语言的基础。

这是一个系列的文章,而且会长期更新,欢迎各位看客关注!

  • 前言

这时候,作为刚刚加入“码农”行列的您,还是有几点是必须要搞定的:Java语法、行业代码规范(类、方法、变量等的命名规范)、计算机基本的运行原理、Java程序运行的基本原理。我这里说的“搞定”的更加准确的含义指的是:要很掌握的很熟练。因为这就像是楼梯的第一个台阶、高楼大厦的地基一样,它直接影响你后续阶段的发展。

您别小瞧这几点,以为在学校里就早早地对Java语法很了解了;不止您在成长,Java本身也在发展中,所以Java的不同版本之间可能也会有或大或小、或多或少的差异,忽略这一点的童鞋可能会被“绊脚石”磕碰的很惨。

程序是运行在计算机上的,为了以后的发展,可以说对计算机的原理研究的不管多么深入都是很有必要的。但现在,你至少要对CPU、内存、硬盘、网卡等大部件的性能参数有所了解。

而Java呢,是需要编译为class文件(而不是机器码),然后在虚拟机(JVM)上运行的。这一点,决定了Java是跨平台(Linux、Windows等)的,也决定了它可能会比C语言要运行的慢一点。它的内存管理机制,又决定了Java是比较耗内存的——虽然所占用的内存并不是时时刻刻都需要的。

以上三点相对来说都是比较范范的东西,下面我来说一下对于初出茅庐的Java程序员们有哪些技能是必须掌握的,换句话说说,掌握了下面的内容你就可以轻松应对一些面试了:

  • 封装、继承、多态

学Java或者其他面向对象的童鞋们,这三个特性应该都是记得很清楚。它很重要,会一直贯穿你的Java开发生涯;但这六个字过于浓缩、抽象,是很难理解的。其实,我建议作为Java开发人员的第一年,反而可以忘记这些特性,先是多多动手,积累一些项目经验,积累足够的业务场景,积累足够的常见问题后,再回过头来细细研究这三个特性。

  • 方法的重写的重载

方法的重写(override),指的是子类重写(覆盖)父类的方法。要求方法签名要保持一致,不能抛出更多的异常(封装),不能有更加严格的访问权限(多态)。

方法的重载(overload),指的是在同一个类或者子类中来重载一个方法,两个方法之间要求参数列表不能相同,而其他的要相同。这是为了能够有多个过程类似的方法实现的。

  • Java接口、类(抽象)的概念

接口可以理解为是一种规范、标准、协议,而并不关心具体实现过程。接口中的方法只能使用public、abstract来修饰,而属性只能是用public、static、final来修饰。换句话说,在接口中即使不添加任何修饰符也可以,因为它只能添加那些修饰符——方法必然是public abstract的,而属性一定是public static final的。根据接口中属性的特点,我们可以推断,在申明属性变量时必须给赋初始值,而且不能被修改。

如果有人要问,在接口的方法上添加abstract或者不添加有没有区别的话,从上文就可以推断出——是一样的。对应的属性也是这个道理。

普通类中,所有的方法必须要有方法体——也就是说必须要有实现,同样的方法不可以使用abstract来修饰。类只允许继承一个类,但是可以实现多个接口。类A实现类B,可以理解为类A也是一种B;而类A实现接口B、C,则可以理解为类A有B和C的特性(特点)。

而抽象类,就像是接口和普通类的结合体,方法既可以有抽象的(abstract)的,也可以没有。对属性则没有任何限制。另外,由于抽象类中有可能有抽象方法,所以是无法直接进行实例化的。

  • Java的集合(Collection)体系

这是必须要弄明白的,在日常工作中跟它打交道的次数太多了。首先是java.util.Collection这个接口,它是集合的顶层接口,另外要留心它所在的包(package)是java.util。它的子接口包括List、Set,List的实现类包括:ArrayList、LinkedList、Vector。Set的实现类包括:HashSet、LinkedHashSet、TreeSet。

ArrayList的内部是用动态数组(普通数组的容量必须在初始化时给定)实现的,根据数组下标来对元素进行操作,具有访问快速、修改较慢的特点,这些是由于ArrayList的存储结构导致的。如果调用无参构造函数的话,会把内部数组elementData赋值一个空数组;如果调用的是带有一个int类型参数(容量capacity)的构造函数的话,会创建一个指定大小的数组。在调用add方法时,会先检查当前数组是否可以在容纳一个元素(这时候,保证至少会有10的容量),如果不够的话就准备扩容(最大容量为Integer.MAX_VALUE)——先是创建一个新容量的数组,然后把之前的元素拷贝过去,最后把要添加的元素加入新的数组中。

根据以上ArrayList类的特点,我们可以总结出一些ArrayList的使用技巧:如果您估计ArrayList中要添加的元素个数超过10个的话,最好调用有int参数的构造函数给定一个初始容量,这样能有效地避免频繁扩容导致的频繁申请内存(堆),甚至能避免一些不必要的gc事件;ArrayList适合读取和修改操作较多的场合,而不适合于添加或者删除较多的场合;ArrayList并没有实现了多线程之间的同步,所以是线程不安全的。

可以通过工具类java.util.Collections的sort方法给List排序。

通过Collections的binarySearch来快速地查找List中的元素。

LinkedList<E>实现的是双向链表(Doubly-linked),另外还实现了接口java.util.Deque<E>。它有add、addFirst、addLast、addAll等方法,add等同于addLast,set方法可以添加到指定位置。调用get方法时,它会从所有的元素中遍历查找。所以,LinkedList和ArrayList相反,插入、新增速度快,查找速度慢。另外,它也是线程不安全的。

Vector<E>除了实现了List接口外还实现了java.util.RandomAccess接口,默认的容量为10,内部也是用数组的形式实现的。它的add以及其他操作元素的方法使用了关键字synchronized,因此是线程安全的,也可以认为Vector是线程安全版本的ArrayList。

TreeSet<E>内部是利用TreeMap实现的,调用add方法时其实就是向map中添加一条key,value的值统一都是Object类。

HashSet<E>内部是利用HashMap实现的。add方法实现过程与TreeSet类似。

Java集合体系中的java.util.Map比较特殊,和Collection不是一类,它本身就是顶层接口。它的实现类包括:HashMap、Hashtable。

HashMap<K,V>默认的构造函数会初始化一个容量(应该是2的倍数,最大容量为Integer.MAX_VALUE / 2 + 1,或者为1 << 30)为10、因子(load factor)为0.75的集合,之后调用init方法(默认为空实现,是让子类来扩展的)。

HashMap第一次调用put方法时,内部会调用方法inflateTable来初始化,所以说如果只是申明了一个Map对象后并不会占用任何空间。从这里可以看到HashMap内部也是通过数组实现的,数组的元素类型为HashMap.Entry;而Entry类包含了key、value、下一个Entry的引用、hash值。而当key为null时,则内部会调用方法putForNullKey,把key为null的Entry会放到数组的第一个。这时候会通过hash方法来计算key的hash值,hash方法会先调用key对象的hashCode,然后再进行一些hash运算。之后利用hash值和数组的长度计算出来一个数组下标值,如果数组中的该下标(index)中已经有了Entry对象,则从Entry链中通过hash查找是否已经存在了相同的key,有的话更新;没有的话,则新增一个Entry放到数组的该下标中,然后该Entry指向之前的Entry,形成一个Entry链。

从上面对HashMap的分析,可以得出一些结论。它内部是由多个元素类型为HashMap.Entry的数组实现,因此占用的内存空间还是比较大的。key可以为null。它从Java1.6开始出现,是非线程安全的。

Hashtable实现了抽象类Dictionary<K,V>。它从Java1.0开始出现。它的初始化过程和HashMap类似,但初始容量(capacity)为11,因子(load factor)为0.75f。

Hashtable的put方法使用关键字(synchronized)保证了线程间的同步,当key或者value为空(null)时会抛出空指针异常。

TreeMap<K,V>实现了接口NavigableMap<K,V>。该集合和HashMap实现的过程类似,只是在调用put方法时就会先对key进行排序。这个排序就要求key实现接口Comparator<? super K>,或者是在实例化TreeMap时给定一个Comparator。

NavigableMap<K,V>该接口继承了接口SortedMap<K,V>,从Java1.6开始出现。

  • Java流(Stream)体系

Java中的“流”相关内容都在包java.io中,想要系统的了解Java流就要知道有哪些分类。

按照大的类型分为:输入流(InputStream)和输出流(OutputStream)。这两个顶层的流都是用抽象类来表示的,它们规定了输入、输出流的基本方法,它们是基于字节(byte)流。

java.lang.Readable和java.lang.Appendable是另一对Java“流”接口。注意这两接口在java.io中对应的顶层抽象类分别为:Reader、Writer,它们是针对字符类型的流。

DataInput和DataOutput是Java“流”中的面向数据类型的输入、输出流。

所有类型的输入、输出流都实现了接口Closeable,它只有一个close方法,那这个close方法应该由谁来调用呢,我这里有个建议——谁打开的“流”谁来关闭。

  • Java异常(Exception)体系

Java的异常体系的根类是java.lang.Throwable,它有两个子类java.lang.Error和java.lang.Exception。

Error是比较严重的错误,基本会在程序无法继续执行的情况下抛出,我们无法捕获更无法处理。

Exception是“编译时”异常的根类,它的子类java.lang.RuntimeException是“运行时”异常。所谓编译时指的是在编译Java源文件时抛出的异常,必须在处理(捕获或者抛出)后源文件才可以正常编译;所谓运行时异常,指的是在Java程序运行过程中有“可能”抛出的异常。

除了Error之外,编译时异常也是比较严重的异常情况,所以必须显示地处理。而运行时异常通常不严重,即遇到了这种异常情况程序也还是可以继续的。

实现一个多线程类,可以实现接口java.lang.Runnable或者类java.lang.Thread。这里要注意的是类Thread也实现了接口Runnable。启动一个线程要调用start方法,如下例子所示:

线程间同步,可以在类或者方法上使用关键字synchronized。

另外,还有个线程相关的知识点。使用Thread.sleep和Object.wait相比较,前者会一直占用CPU,而后者会放弃CPU的控制权。

有关Java多线程的更多内容请搜索“Java多线程”。

  • 设计模式“单例”

好多人都在谈论“设计模式”,还有一些人可以说出很多种设计模式,例如:单例模式、工厂模式、观察者模式等等。但对于刚刚进入这个行业的人来说,我不认为记住这么多的名称有任何用处,而且在实际项目中也不会有实践的机会。学习设计模式这是一件长期的事情,而且在有一定项目编程经验以后再来学习会更有效果。

我在这里先介绍一个很容易理解的设计模式——单例模式。所谓单例,指的是在虚拟机(JVM)中某个类只有一份实例。通常情况下,单例的实现有几个需要注意的地方:构造函数私有化、提供一个静态方法用于获取实例对象、考虑多线程情况。

  • 反射

在Java中所有的都是类,而所有java.lang.Object类则是所有类的父类——即使没有“显示地”继承。而JDK中有些类是用来描述“类本身”信息的,它们大多数在包java.lang.reflect中,例如:Method、Field、Constructor、Member、Type、Modifier等。

而我这里提到的反射就是JDK提供的用于获取“类本身”信息的API,换句话说:通过反射技术可以动态第获取甚至修改“类本身”的信息。如果你对C语言有点了解的话,这一点可以和C语言的指针来做对比理解。

那么反射技术通常都用在什么场合呢?利用反射可以做到很多常规程序无法做到的事情,例如代理,而在框架设计中经常会用到反射技术。

  • Java用户界面编程Swing

JDK中提供的用于编程GUI(用户界面)程序的是Swing框架。这里的API都是在java.awt和javax.swing中,我们可以认为javax.swing包中的API是对java.awt中API的升级或者改进。例如,按钮的实现类Button就有两份,分别是java.awt.Button和javax.swing.JButton。从包的分布和类名上,我们可以看到区别,在javax.swing包中的组件类基本都是大写字母J开头的。这里,我推荐使用javax.swing包中的API。

Java是跨平台的,由此提供的Swing框架也是在所有的平台上运行时可以保持一致的风格。但由此引发的一个问题是:利用Swing编写的GUI程序(C/S架构)相对比较丑陋一点,导致在企业开发中用的相对比较少。但是,利用Swing来开发一些小的工具还是完全可以的。

这里我介绍一些GUI程序原理性的内容,这样你就算没写过也可以多少有点了解。所有的GUI程序的底层都是在调动API在屏幕上“绘制图形”,因此呢,可以理解GUI程序中会有一个单独的线程来做“绘制”工作,而且这个线程就是主线程。然后,我们继续思考,如果一个界面中的组件(元素)过多的话,就会导致“绘制”线程占用的时间比较多,使用起来显得比较“卡”。

既然上面我已经提到了线程,那么在程序中读取、处理数据时占用的时间比较长的话,就会导致界面更加“卡”。为了解决这个问题,我们通常都会选择新开一个线程来处理占用时间比较长的任务。到此为止呢,我们至少已经有了两个线程,那么在读取、处理数据的线程中要修改界面元素的话,就必然涉及到了线程间同步的问题——我们不可以在新的线程中修改界面元素,否则会发生数据不同步的问题。

  • Java网络编程

Java网络编程是很大的一块内容,现在呢,您只需要了解Java网络编程中都有哪些接口(功能),以及简单了解网络协议即可。

TCP/IP协议栈分为应用层、传输层、链路层等。Java提供了传输层协议的网络编程接口,也就是可以对TCP、UDP协议数据包进行操作。而应用层的网络协议,例如:FTP、HTTP、SFTP、SNAP等都需要利用第三方的库了。HttpClient即是Apache的一个子项目,用于操作HTTP协议。

TCP协议是可靠的协议,它能保证数据包完整地在网络中进行传输,而UDP协议则不能保证这一点,但UDP传输的速度相对比较快。据此特点,UDP经常用在需要传输大量数据,但对数据完整性要求不是很高的场合,例如视频传输等。另外,TCP在发送数据包时,必须要求对方正在简体你指定的端口,否则无法发送;而UDP协议则不需要对方一定是正在监听中,它只管发送,不关心对方是否已经接收了。

  • JSP原理

JSP(Java Server Pages)作为常用的一种动态页面技术,有几个很关键的地方是必须理解清楚、正确的,这也是作为一个合格、称职、专业的程序员所应该了解的。

首先,从JSP的字面上来说,它一定是服务器端(后端)的技术,而不是前端技术,不可以和HTML混为一谈。和其他的动态页面技术不同,它是需要编译的——当它被访问时,将会由JSP引擎编译(class字节码文件);如果是在Tomcat中运行的话,编译后的字节码文件会保存在目录work/Catalina/localhost/${webroot}/org/apache/jsp中。

那JSP编译完的class文件是什么呢?从它所继承(实现)的类上看,就能一目了然了。它继承了类org.apache.jasper.runtime.HttpJspBase,实现了接口org.apache.jasper.runtime.JspSourceDependent。

而HttpJspBase是个抽象类,它继承了抽象类javax.servlet.http.HttpServlet,实现了接口javax.servlet.jsp.HttpJspPage。

接口JspSourceDependent是为了能够跟踪JSP源码文件的依赖关系,从而能够编译过期的JSP文件。

  • jQuery常用方法

这里至少应该知道如何使用jQuery定位元素、设置(获取)值、ajax请求。

jQuery有类(class)选择器、名称(tagname)选择器、ID选择器、属性(attribute)选择器、派生选择器等。

  • JavaScript基本概念

  • 各种浏览器的调试技巧

本站有一篇《JavaScript调试介绍》您可以搜索一下,介绍了各种主流浏览器的JavaScript调试方法和技巧。

  • 集成开发工具Eclipse的熟练使用

本站有一篇《Eclipse使用技巧》您可以搜索一下,介绍了Eclipse的一些使用技巧。

  • 应用服务器配置

本章有一篇《玩转Tomcat》您可以搜索一下,介绍了Tomcat所有常用的配置方法。

  • 数据库

除了要会使用数据库的带有界面的客户端(例如:Navicat等)连接,还需要知道如何通过命令行来连接,例如:mysql -uroot -proot。

创建数据库(database)、表(table)的SQL应该会写,简单的查询语句更是必须的。

  • Struts2框架

  • Hibernate框架

本站有几篇有关Hibernate的专题介绍,大家可以搜索一下《Hibernate注解类介绍》、《Hibernate常见异常》。

  • Spring框架

  • 本系列文章

Java开发成长之路第一年

Java开发成长之路第二年

Java开发成长之路第三年

Java开发成长之路第四年

Java开发成长之路第五年

Java开发成长之路第六年

Java开发成长之路第七年

发表评论

电子邮件地址不会被公开。 必填项已用*标注