本文是一些Java基本概念和原理的总结。主要来自《Java编程思想》。
static 关键字
声明一个事物是static时,意味着这个域或方法不会与包含它的那个类的任何对象关联在一起,即使没有创建对象也能够调用这个方法。
引用static变量的两种方法:1.通过创建对象定位。2. 通过类名直接引用。 同样引用static方法也同样。最好直接用类名取调用方法或变量
Java.lang这个特定的类会被自动导入每个java文件中
Javadoc命令
只能在“/*”中出现,结束于“/”;使用Javadoc的两种方式:a.嵌入HTML b.使用‘文档标签’,文档标签以‘@’开头,且要置于注释行的最前面,而‘行内文档标签’可出现在Javadoc注释中的任何地方,以@开头,但要在花括号内。
Javadoc只能为public和protected成员进行文档注释。
常用一些标签示例
- a. @see, 引用其他类文档(加入一个超链接(see also)), javadoc不会检查超链接的有效性
- b. @link package.class#member label, 与@see类似,但它用于行内
- c. @docRoot, 该标签产生到文档跟目录的相对路径
- d. @author, 姓名信息
- e. @param, 参数的描述
- f. @return, 返回值的描述
- g. @throws, 对抛出异常进行说明
终结处理与垃圾回收
Java的对象可能不被垃圾回收,垃圾回收并不等于C++中的“析构”,垃圾回收至于内存有关。
finalize
当垃圾收集器认为没有指向对象实例的引用时,会在销毁该对象之前调用finalize()方法。该方法最常见的作用是确保释放实例占用的全部资源。java并不保证定时为对象实例调用该方法,甚至不保证方法会被调用,所以该方法不应该用于正常内存处理。 首先,只有当垃圾回收器释放该对象的内存时,才会执行 finalize() 。如果在 Applet 或应用程序退出之前垃圾回收器没有释放内存,垃圾回收器将不会调用 finalize() 。其次,除非垃圾回收器认为你的 Applet 或应用程序需要额外的内存,否则它不会试图释放不再使用的对象的内存。换句话说,这是完全可能的:一个 Applet 给少量的对象分配内存,没有造成严重的内存需求,于是垃圾回收器没有释放这些对象的内存就退出了。显然,如果你为某个对象定义了 finalize() 方法, JVM 可能不会调用它,因为垃圾回收器不曾释放过那些对象的内存。调用 System.gc() 也不会起作用,因为它仅仅是给 JVM 一个建议而不是命令。
构造器初始化
无法阻止自动初始化的进行,自动初始化将在构造器被调用之前发生。
初始化顺序
在类内部,变量定义的先后顺序决定了初始化的顺序,即使变量定义散布于方法定义之间,他们仍会在任何方法(包括构造器)被调用之前得到初始化。
静态数据的初始化: 先静态对象(如果尚未因前面的对象的创建过程而被初始化),而后是“非静态对象”。无论创建多少个对象,静态数据都只能占用一份存储区域。static关键字只能作用于域。
静态变量初始化
如果你需要通过计算来初始化你的static变量,你可以声明一个static块,Static 块仅在该类被加载时执行一次。下面的例子显示的类有一个static方法,一些static变量,以及一个static 初始化块:
这个程序展示了静态初始化的各种特性。可能会对static后加大括号感到困惑。首先要告诉你的是,static定义的变量会优先于任何其它非static变量,不论其出现的顺序如何。正如在程序中所表现的,虽然v出现在v1和v2的前面,但是结果却是v1和v2的初始化在v的前面。在static{后面跟着一段代码,这是用来进行显式的静态变量初始化,这段代码只会初始化一次,且在类被第一次装载时。如果你能读懂并理解这段代码,会帮助你对static关键字的认识。在涉及到继承的时候,会先初始化父类的static变量,然后是子类的,依次类推。
数组初始化
两种方法定义一个数组: int[] a1; 或 int a1[]
将一个数组赋值给另外一个数组时(a2=a1),其实只是复制了一个引用。 Java数组的计算也是从第0个开始到第length-1个结束,如果访问越界,会有运行时错误(异常)。
可变参数列表,也是数组的应用之一。如下 static void printArray(Object… args)
枚举类型
enum。
访问权限控制
从最大权限到最小权限依次为:public、protected、包访问权限(没有关键词)和private。 如果使用package语句,它必须是文件中除注释外的第一句程序代码;在文件起始处写: package access; 表示你在声明该编译单元式名为access的类库一部分。 无论何时创建包,都已经在给定包的名称的时候隐含地指定了目录结构,这个包必须位于其名称所指定的目录之中,而该目录须是以CLASSPATH开始的目录中可以查询到的。
类的访问权限
- 每个编译单元(文件)都只能有一个public类。
- public类的名称必须完全与含有该编译单元的文件名相匹配,包括大小写。不匹配得到编译时错误。 3.编译单元内完全不带public类也是可能的(不常用),这种情况下可随意对文件命名。
复用类
toString()
在java中,所有对象都有toString()这个方法,因为它是Object里面已经有了的方法,而所有类都是继承Object,所以“所有对象都有这个方法” 。它通常只是为了方便输出,比如System.out.println(xx),括号里面的“xx”如果不是String类型的话,就自动调用xx的toString()方法
例如:某类实现了toString方法,可以按照自己的意愿输出类的信息(在调用类似print(object)时候,没有覆盖此方法,直接打印对象地址)。 总而言之,它只是sun公司开发java的时候为了方便所有类的字符串操作而特意加入的一个方法 。
继承语法
即使一个程序中含有多个类,这些类又含有多个main函数,也只有命令行所调用的那个类的main方法被调用。
向上转型
新类是现有类的一种类型。
由导出类(继承类)转型成基类,在继承图上时向上移动的,所以称为向上转型;由于向上转型是一个较专用类到通用类类型转换,所以是安全的,即导出类是基类的超集。
到底该用组合还是继承:问问自己是否需要从新类向基类进行向上转型,如果必须向上转型,则继承是必须的,否则要好好考虑是否要继承。
final关键字
可能用到final的三种情况:数据、方法和类。
final数据
告知编译器一块数据是恒定不变的。如:一个永不改变的编译时常量,一个在运行时被初始化的值,但是你不希望它被改变。
一个既是static又是final的域只占据一段不能改变的存储空间。
当对象引用而不是基本类型运用final时,其含义可能令人困惑:对于基本类型,final使数值恒定不变;而对于对象引用,final使引用恒定不变,一旦引用被初始化成指向一个对象,就无法再把它改为指向另外一个对象,然而对象其自身是可以被修改的(数组也是一样,不过是另外一种引用)。
空白final是指被声明为final但又未给定初值的域。无乱什么情况,编译器都保证空白final在使用前必须初始化。
final参数
Java允许在参数列表中以声明的方式将参数之名为final,这意味着无法再方法中更改参数引用所指向的对象。
final方法
使用原因:把方法锁定,以防任何继承类修改它的含义。 类中所有的private方法都隐式地指定为final的。
final类
将某个类定义为final时,表明你不打算继承该类,而且也不允许别人这么做;即出于某种考虑,你对该类的设计用不需要做任何变动,或处于安全的考虑,你不希望它有子类。final类禁止继承,所以final类中所有的方法都隐式指定为final的,因此无法覆盖它们。
多态
多态==晚绑定。不要把函数重载理解为多态。
因为多态是一种运行期的行为,不是编译期的行为。
多态:父类型的引用可以指向子类型的对象。
比如 Parent p = new Child();
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的该同名方法。
注意此处,静态static方法属于特殊情况,静态方法只能继承,不能重写Override,如果子类中定义了同名同形式的静态方法,它对父类方法只起到隐藏的作用。调用的时候用谁的引用,则调用谁的版本。
如果想要调用子类中有而父类中没有的方法,需要进行强制类型转换,如上面的例子中,将p转换为子类Child类型的引用。 因为当用父类的引用指向子类的对象,用父类引用调用方法时,找不到父类中不存在的方法。这时候需要进行向下的类型转换,将父类引用转换为子类引用。
向下转型
对于向下的类型转换,必须要显式指定,即必须要使用强制类型转换。
构造器于多态
基类的构造器总是在导出类的构造过程中被调用,而且按照集成层次逐渐向上链接,以使每个基类的构造器都能得到调用。
复杂对象调用构造器的顺序
a. 调用基类构造器。这个步骤会不断的反复递归下去,首先是构造这种层次结构的根,然后是下一层的导出类等等,直到最底层的导出类。
b. 按声明顺序调用成员的初始化方法。
c. 调用导出类构造器的主体。
接口
接口和内部类提供了一种将接口与实现分离的更加结构化的方法。
抽象类
-
一个类包含一个或多个抽象方法时,该类必须被限定为抽象的,否则编译器报错。
-
继承抽象类,并想创建该类的对象,必须为基类中的所有抽象方法提供方法定义,否则,导出类也是抽象类,且编译器会强制用abstract关键字来限定这个类。
接口
interface关键字产生一个完全抽象的类,雨荨创建者确定方法名、参数列表和返回类型,但是没有任何方法体。接口仅仅提供形式,不提供任何实现。实现某接口用implements关键字。实现一个接口,接口定义的方法必须是public的。
使用接口的核心原因:a. 为了能够向上转型为多个基类型。 b. 防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口(跟抽象类的作用类似)。
可以通过集成来扩展接口(获得新接口)。
接口常见的一种用法就是 策略模式 ,工厂方法设计模式。
接口中的任何域都自动式static和final的,所以接口成为了一种便捷的常见常量组的工具(和enum具有相同效果)。
接口可以嵌套在类或其他接口中。
内部类
将一个类的定义放在另一个类的定义内部,这就是内部类。
内部类自动拥有对其外围类所有成员的访问权。
内部类想生成对外部类的引用,可以使用 外部类的名字.this。在主程序创建外部类里的内部类,必须在new表达式中提供对其他外部类对象的引用,需要.new语法。
在方法和作用域中的内部类: 超出了方法的作用域,内部类失效。
匿名内部类也就是没有名字的内部类,正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写;但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口。
只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现;最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口。
嵌套类
将内部类声明为static,称此类为嵌套类。嵌套类意味着:1. 不能与外围类对象联系;2. 不能从嵌套类的对象中访问非静态的外围类对象。
内部类的继承。
内部类标示符
内部类也必须生成一个.class文件以包含它们的Class对象信息;命名规则:外围类的名字,加上 $ ,再加上内部类的名字。
Java容器初步
一个类没有显式声明继承某个类,都自动继承Object类。
容器主要分为两类:Collection和Map。
List
分为两种List:
- 基本的ArrayList:快速随机访问元素,但插入或移除元素较慢。
- LinkedList:较快插入或删除元素,但随机访问慢。
常用的方法: contains()方法:确定某个对象是否在列表中 remove()方法:移除对象 indexOf():该对象在List中所处位置的索引编号 subList():从较大列表中创建出一个片段
迭代器
-
使用方法iterator()要求容器返回一个Iterator,Iterator将准备好返回序列的第一个元素。
-
使用next()获得序列中的写一个元素。
-
使用hasNext()检查序列中是否还有元素。
-
使用remove()将迭代器最近返回的元素删除。
如果仅仅向前遍历List,那么foreach语法更加简洁。
ListIterator:只能用于List类的访问,可以双向移动。
LinkedList
LinkedList添加了可以使用其作为栈、队列或双端队列的方法。 LinkedList线程非安全。
Vector
和ArrayList的不同点有 a. Vector是基于Synchronized实现的线程安全的ArrayList。即Vector是线程安全的。 b. 在插入元素时容量扩充的机制和ArrayList稍微有所不同,Vector是扩充2倍,并可通过传入capacityIncrement来控制容量的扩充。而ArrayList是扩充1.5倍并加1。
Stack
LinkedList具有能够直接实现栈的所有功能的方法,可直接将LinkedList作为栈使用,但注意LinkedList县城非安全。 Java中的Stack继承Vector,在Vector的基础上实现了Stack所要求的后进先出(LIFO)的弹出即压入操作,其提供了push,pop,peek等三个主要方法。
Set
Set不保存重复的元素。
HashSet
- HashSet基于HashMap实现,无容量限制。
- HashSet是非线程安全的。
- HashSet不保证有序。
TreeSet
- TreeSet基于TreeMap实现,支持排序。
- TreeSet是非线程安全的。
从对HashSet和TreeSet的描述来看,TreeSet和HashSet一样,也是完全基于Map来实现的,并且都不支持get(int)来获取指定位置的元素(需要遍历获取),另外TreeSet还提供了一些排序方面的支持。例如传入Comparator实现、descendingSet以及descendingIterator等。
LinkedHashSet:
- LinkedHashSet 以元素插入的顺序来维护集合的链接表,允许以插入的顺序在集合中迭代
- LinkedHashSet同样是非线程安全的。
在同步并发环境下,为了确保Set是线程安全的,可以使用Collections。synchronizedSet(new HashSet());因为其能够返回原始hashset集合的同步版本,在多线程环境中可以访问这个同步版本。
LinkedHashSet, HashSet,TreeSet三者的区别:
HashSet:哈希表是通过使用称为散列法的机制来存储信息的,元素并没有以某种特定顺序来存放;
LinkedHashSet:以元素插入的顺序来维护集合的链接表,允许以插入的顺序在集合中迭代;
TreeSet:提供一个使用红黑树结构存储Set接口的实现,对象以升序顺序存储,访问和遍历的时间很快。
Map
HashMap
- HashMap采用数组方式存储key,value构成的Entry对象,无容量限制。
- HashMap基于Key hash查找Entry对象存放到数组的位置,对于hash冲突采用链表的方式来解决。
- HashMap在插入元素时可能会要扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数组中。
- HashMap是非线程安全的。
- HashMap遍历使用的是Iterator
HashTable
- HashTable是线程安全的。
- HashTable中无论是Key,还是Value都不允许为null。
- HashTable遍历使用的是Enumeration。
TreeMap:
- SortMap的实现
- TreeMap是一个典型的基于红黑树的Map实现,因此它要求一定要有Key比较的方法,要么传入Comparator实现,要么key对象实现Comparable接口。
- TreeMap是非线程安全的。
LinkedHashMap:
- LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.
- LinkedHashMap 也可以在构造时用带参数,按照应用次数排序。
LinkedHashMap在遍历的时候会比HashMap慢。不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。