Java基本概念原理总结(一)

| 分类 Java  | 标签 Summary 

本文是一些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 初始化块:

class Value3 {
    static int c = 0;
    Value3() {
       c = 15;
    }
    Value3(int i) {
       c = i;
    }
    static void inc() {
       c++;
    }
}
 
public class Count {
    public static void prt(String s) {
       System.out.println(s);
    }
 
    Value3 v = new Value3(10);
    static Value3 v1, v2;
    static {//此即为static块

       prt("v1.c=" + v1.c + "  v2.c=" + v2.c);
       v1 = new Value3(27);
       prt("v1.c=" + v1.c + "  v2.c=" + v2.c);
       v2 = new Value3(15);
       prt("v1.c=" + v1.c + "  v2.c=" + v2.c);
    }
 
    public static void main(String[] args) {
       Count ct = new Count();
       prt("ct.c=" + ct.v.c);
       prt("v1.c=" + v1.c + "  v2.c=" + v2.c);
       v1.inc();
       prt("v1.c=" + v1.c + "  v2.c=" + v2.c);
       prt("ct.c=" + ct.v.c);
    }
}
 
结果为:v1.c=0  v2.c=0
v1.c=27  v2.c=27
v1.c=15  v2.c=15
ct.c=10
v1.c=10  v2.c=10
v1.c=11  v2.c=11
ct.c=11

这个程序展示了静态初始化的各种特性。可能会对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开始的目录中可以查询到的。

类的访问权限

  1. 每个编译单元(文件)都只能有一个public类。
  2. 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. 调用导出类构造器的主体。

接口

接口和内部类提供了一种将接口与实现分离的更加结构化的方法。

抽象类

  1. 一个类包含一个或多个抽象方法时,该类必须被限定为抽象的,否则编译器报错。

  2. 继承抽象类,并想创建该类的对象,必须为基类中的所有抽象方法提供方法定义,否则,导出类也是抽象类,且编译器会强制用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:

  1. 基本的ArrayList:快速随机访问元素,但插入或移除元素较慢。
  2. LinkedList:较快插入或删除元素,但随机访问慢。

常用的方法: contains()方法:确定某个对象是否在列表中 remove()方法:移除对象 indexOf():该对象在List中所处位置的索引编号 subList():从较大列表中创建出一个片段

迭代器

  1. 使用方法iterator()要求容器返回一个Iterator,Iterator将准备好返回序列的第一个元素。

  2. 使用next()获得序列中的写一个元素。

  3. 使用hasNext()检查序列中是否还有元素。

  4. 使用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

  1. HashSet基于HashMap实现,无容量限制。
  2. HashSet是非线程安全的。
  3. HashSet不保证有序。

TreeSet

  1. TreeSet基于TreeMap实现,支持排序。
  2. TreeSet是非线程安全的。

从对HashSet和TreeSet的描述来看,TreeSet和HashSet一样,也是完全基于Map来实现的,并且都不支持get(int)来获取指定位置的元素(需要遍历获取),另外TreeSet还提供了一些排序方面的支持。例如传入Comparator实现、descendingSet以及descendingIterator等。

LinkedHashSet:

  1. LinkedHashSet 以元素插入的顺序来维护集合的链接表,允许以插入的顺序在集合中迭代
  2. LinkedHashSet同样是非线程安全的。

在同步并发环境下,为了确保Set是线程安全的,可以使用Collections。synchronizedSet(new HashSet());因为其能够返回原始hashset集合的同步版本,在多线程环境中可以访问这个同步版本。

LinkedHashSet, HashSet,TreeSet三者的区别:

HashSet:哈希表是通过使用称为散列法的机制来存储信息的,元素并没有以某种特定顺序来存放;

LinkedHashSet:以元素插入的顺序来维护集合的链接表,允许以插入的顺序在集合中迭代;

TreeSet:提供一个使用红黑树结构存储Set接口的实现,对象以升序顺序存储,访问和遍历的时间很快。

Map

HashMap

  1. HashMap采用数组方式存储key,value构成的Entry对象,无容量限制。
  2. HashMap基于Key hash查找Entry对象存放到数组的位置,对于hash冲突采用链表的方式来解决。
  3. HashMap在插入元素时可能会要扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数组中。
  4. HashMap是非线程安全的。
  5. HashMap遍历使用的是Iterator

HashTable

  1. HashTable是线程安全的。
  2. HashTable中无论是Key,还是Value都不允许为null。
  3. HashTable遍历使用的是Enumeration。

TreeMap:

  1. SortMap的实现
  2. TreeMap是一个典型的基于红黑树的Map实现,因此它要求一定要有Key比较的方法,要么传入Comparator实现,要么key对象实现Comparable接口。
  3. TreeMap是非线程安全的。

LinkedHashMap:

  1. LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.
  2. LinkedHashMap 也可以在构造时用带参数,按照应用次数排序。

LinkedHashMap在遍历的时候会比HashMap慢。不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

容器分类


上一篇     下一篇