您的位置:首页 > 编程语言 > Java开发

Java编程思想学习笔记

2015-10-15 15:56 543 查看
多态

后期绑定:向对象发送消息时,被调用的代码直到运行时才能确定。编译器确保被调用方法的存在,并对调用参数和返回值执行类型检查,但并不知道将要执行的确切代码。

为了执行后期绑定,Java使用一小段特殊的代码来替代绝对地址调用。这段代码使用在对象中存储的信息来计算方法体的地址。

向上转型:将导出类看做是它的基类

单根继承结构

Object

单根继承体系保证所有对象都拥有某些功能。在整个系统里,你因此知道可以在每个对象身上执行某些基本操作。单根继承体系再加上<在heap之中产生所有对象>,大大的简化了参数传递动作(这也是C++里十分复杂的问题之一)。

单根继承体系也使垃圾回收器的实现更加容易。所有必备功能都可以安置于基类本身,然后垃圾回收器便可以发送适当消息给系统中的每个对象。如果缺乏<单根继承体系>及<完全透过引用来操作对象>的系统特性,垃圾回收器的实现就会十分困难。

由于所有对象都保证具有其类型信息,所以你必不会因为无法判断对象的确切类型而陷入动弹不得的僵局。对于异常处理之类的系统级操作行为而言,这一点格外重要,并且也能为程序设计带来更加优秀的弹性。

容器

任何时候都可以动态扩充。

JavaSE5的重大变化之一就是增加了参数化类型,即范型。通过范型,容器不再只是存储Object,而可以作用于特定类型。

常用集合类(容器类)的继承结构如下: 

Collection<--List<--Vector 

Collection<--List<--ArrayList 

Collection<--List<--LinkedList 

Collection<--Set<--HashSet 

Collection<--Set<--HashSet<--LinkedHashSet 

Collection<--Set<--SortedSet<--TreeSet 

Map<--SortedMap<--TreeMap 

Map<--HashMap 

用引用操纵对象

尽管一切都看做对象,但操纵的标识符实际上是对象的一个引用。

特例:基本类型

不用new,而是创建一个并非是引用的“自动”变量,这个变量直接存储“值”,并置于堆栈中,因此更加高效。

对象的作用域

当用new创建一个Java对象时,它可以存活于作用域之外。引用s在作用域终点就消失了,然而s指向的String对象仍然占据内存空间。(GC)

参数传递

不管Java参数的类型是什么,一律传递参数的副本。对此,thinking in Java一书给出的经典解释是When you’re passing primitives into a method, you get a distinct copy of the primitive. When you’re passing a reference into a method, you get a copy of the reference.(如果Java是传值,那么传递的是值的副本;如果Java是传引用,那么传递的是引用的副本。)

  在Java中,变量分为以下两类:

  ① 对于基本类型变量(int、long、double、float、byte、boolean、char),Java是传值的副本。(这里Java和C++相同)

  ② 对于一切对象型变量,Java都是传引用的副本。其实传引用副本的实质就是复制指向地址的指针,只不过Java不像C++中有显著的*和&符号。(这里Java和C++不同,在C++中,当参数是引用类型时,传递的是真实引用而不是引用副本)

  需要注意的是:String类型也是对象型变量,所以它必然是传引用副本。不要因为String在Java里面非常易于使用,而且不需要new,就被蒙蔽而把String当做基本变量类型。只不过String是一个非可变类,使得其传值还是传引用显得没什么区别。

  对基本类型而言,传值就是把自己复制一份传递,即使自己的副本变了,自己也不变。而对于对象类型而言,它传的引用副本(类似于C++中的指针)指向自己的地址,而不是自己实际值的副本。

有一个特定类会自动被导入每一个Java文件:java.lang

Java没有sizeof

在C/C++中,需要使用sizeof()的最大原因是为了移植。不同数据类型在不同机器上可能有不同大小。Java不需要,因为所有数据类型在所有机器中的大小都是相同的。

Foreach语法

JavaSE5引入了一种新的更加简洁的for语法用于数组和容器,表示不必创建int变量去对由访问项构成的序列进行计数。

任何返回一个数组的方法都可以使用foreach,例如

for (char c : "An African Swallow".toCharArray()) 

foreach还可以用于任何Iterable对象

标签

语法: lable:

在Java中,标签起作用的唯一一个地方是在迭代语句之前。在标签和迭代语句之间不能有任何语句。在迭代之前设置标签的唯一理由是:我们希望在其中嵌套另一个迭代或者一个开关。这是由于break和continue通常只中断当前循环,但若随标签一起使用,它们就会中断循环,跳到标签所在的地方。

构造器

名称与类名相同。

构造器是一种特殊类型的方法,因为它没有返回值。

如果已经定义了一个构造器,编译器就不会帮你自动创建默认构造器。

this关键字

Banana a = new Banana();

a.peel(1);

编译器在内部把“所操纵对象的引用”作为第一个参数传递给peel()。即Banana.peel(a,1);

假设希望在方法内部获得对当前对象的引用,由于这个引用是由编译器“偷偷”传入的,所以没有标识符可用。但为此有个专门的关键字this。

this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。

例如,当需要返回对当前对象的引用时,写为 return this;

this关键字对于将当前对象传递给其他方法也很有用。

垃圾回收

思想:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。由此,从堆栈和静态存储区开始,遍历所有引用...

对象的创建

总结一下对象的创建过程,假设有个名为Dog的类:

1.即使没有显式地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成是静态方法),或者Dog类的静态方法或静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。

2.然后载入Dog.class(后面会学到,这将创建一个Class对象(确实很重要)),有关静态初始化的所有动作都会执行。因此,静态初始化只有在Class对象首次加载的时候进行一次。

3.当用new Dog()创建对象的时候,首先在堆上为Dog对象分配足够的存储空间。

4.这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置为默认值(对数字来说就是0,对布尔型和字符型也相同),而引用则被设置成null。

5.执行所有出现于字段定义处的初始化动作。

6.执行构造器。

枚举类型

enum关键字

public enum Spiciness {

  NOT, MILD, MEDIUM, HOT, FLAMING

}

使用:

1. Spiciness howHot = Spiciness.MEDIUM;

2. for (Spiciness s : Spiciness.values())

enum可以在switch语句内使用。 

代码组织

Java可运行程序是一组可以打包并压缩为一个Java文档文件(JAR)的.class文件。

java解释器的运行过程:首先,找出环境变量CLASSPATH。CLASSPATH包含一个或多个目录,用作查找.class文件的根目录。从根目录开始,解释器获取包的名称并将每个句点替换成反斜杠,以从CLASSPATH根中产生一个路径名称,得到的路径会与CLASSPATH中的各个不同的项连接,解释器就在这些目录中查找与你要创建的类名称相关的.class文件。

toString()

每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方
b916
法便会被调用.

如果没有显式定义,则会使用Object默认的toString方法,该方法将打印类名,后面跟随该对象的散列码的无符号十六进制表示。

super

Java用super关键字表示超类的意思,比如用于调用从基类继承而来的方法。

初始化基类

Java会自动在导出类的构造器中插入对于基类构造器的调用。构造过程是从基类“向外”扩散的。

但是,如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显式地编写调用基类构造器的语句。例如super(i);

@Override

Java SE5新增加了@Override注解,当你想要覆写某个方法时,可以选择添加这个注解,在你不留心重载而非覆写了该方法时,编译器会报错。

final

当对对象的引用不是基本类型时运用final,含义不同,请注意:

1. 对于基本类型,final使数值恒定不变;

2. 用于对象引用,final使引用恒定不变,一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象,然而对象其自身是可以被修改的,Java并没有提供使任何对象恒定不变的途径(但可以自己编写类以取得使对象恒定不变的效果)。

初始化及类的加载

Java中,每个类的编译代码都存在于它自己的独立的文件中。该文件只在需要使用程序代码时才会被加载。一般来说,可以说:“类的代码在初次使用时才加载”。这通常指加载发生于创建类的第一个对象之时,但是当访问static域或方法时,也会发生加载。(构造器也是static方法,尽管static关键字并没有显式地写出来。因此更准确的讲,类是在其任何static成员被访问时加载的。)

---------------------

对Beetle 运行Java 时,发生的第一件事情是试图访问Beetle.main()(一个static方法),于是装载程序到外面找到那个类(Beetle.class)。

在装载过程中,装载程序注意它有一个基础类(即extends 关键字要表达的意思),所以随之将其载入。

无论是否准备生成那个基础类的一个对象,这个过程都会发生(请试着将对象的创建代码当作注释标注出来,自己去证实)。

若基础类含有另一个基础类,则另一个基础类随即也会载入,以此类推。

接下来,会在根基础类(此时是Insect)执行static 初始化,再在下一个衍生类执行,以此类推。

保证这个顺序是非常关键的,因为衍生类的初始化可能要依赖于对基础类成员的正确初始化。

此时,必要的类已全部装载完毕,对象就可以被创建了。

首先,这个对象中的所有基本数据类型都会设成它们的默认值,而将对象引用设为null(这是通过将对象内存设为二进制零值而一举生成的)。

随后会调用基础类构建器。在这种情况下,调用是自动进行的。但也完全可以用super 来自行指定构建器调用(就象在Beetle()构建器中的第一个操作一样)。

基础类的构建采用与衍生类构建器完全相同的处理过程。基础顺构建器完成以后,实例变量会按本来的顺序得以初始化。

最后,执行构建器剩余的主体部分。

抽象方法和抽象类

抽象方法:仅有声明而没有方法体

abstract void f();

包含抽象方法的类叫做抽象类,如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。

接口

接口中的方法必须是public的。即使不显式声明为public,但它们自动就是public的。

接口用于实现Java中的“伪多重继承”:如果要从一个非接口的类继承,那么只能从一个类去继承。其余的基元素都必须是接口,需要将所有接口名都置于implements关键字之后,用逗号将它们一一隔开。

接口中的域都自动是public,static和final的。

内部类

使用内部类最吸引人的原因是:每个内部类都能独立的继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

从这个角度看,内部类使得多重继承的解决方案变得完整。

也就是说,内部类允许继承多个非接口类型。

。。。。。。。。。。。。。。。

异常参数

所有标准异常类都有两个构造器,一个是默认构造器,另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器。

捕获异常

try {

  ...

} catch (Type1 id1) {

  ...

} catch (Type2 id2) {

  ...

} ...

e.printStackTrace()

在Throwable类声明(Exception从此类继承),打印“从方法调用处直到异常抛出处”的方法调用序列。

信息将被输出到标准错误流。

异常与记录日志

class LoggingException extends Exception {

  private static Logger logger = 

    Logger.getLogger("LoggingException");

  public LoggingException() {

    StringWriter trace = new StringWriter();

    printStackTrace(new PrintWriter(trace));

    logger.severe(trace.toString());

  }

}

异常说明

使用关键字throws,后面接一个所有潜在异常类型的列表。这种在编译时被强制检查的异常称为被检查的异常。

但从RuntimeException继承的异常,可以在没有异常说明的情况下被抛出。

重新抛出异常

如果只是把当前异常对象重新抛出,那个printStackTrace()方法显示的将是原来异常抛出点的调用栈信息。要想更新这个信息,可以调用fillInStackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的。

异常链

常常会想要在捕获一个异常后抛出另一个异常,并且希望把原异常的信息保存下来,这被称为异常链。

通过把原始异常传递给新的异常作为cause来实现,使用initCause()方法。

Java标准异常

Thorwable: Error(编译时和系统错误), Exception(可被抛出的基本类型)

Exception: IOException(如FileNotFoundException), ClassNotFoundException, RuntimeException(不受检查异常,比如NullPointerException, IndexOutOfBoundsException)

finally

无论异常是否被抛出,finally子句总能被执行。

用途:当要把除内存之外的资源恢复到它们的初始状态时,就要用到。例如已经打开的文件或网络连接,在屏幕上画的图形。

在编写构造器时要格外细心,因为finally会每次都执行清理代码,如果构造器在其执行过程中半途而废,也许该对象的某些部分还没有被成功创建,而这些部分在finally子句中却是要被清理的。

对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的方法是使用嵌套的try子句。

其基本规则是,在创建需要清理的对象之后,立即进入一个try-finally语句块。

不可变String

String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。

不可变性会带来一定的效率问题,比如重载的“+”操作符。

使用StringBuilder(效率更高)/StringBuffer(线程安全)

正则表达式

String类自带了一个非常有用的正则表达式工具——split()方法。

Pattern和Matcher

Pattern p = Pattern.compile(arg);

Matcher m = p.matcher(args[0]);

while (m.find()) {

  ...

}

Scanner

Java SE5新增了Scanner类,它可以大大减轻扫描输入的工作负担。

其构造器可以接受包括File, InputStream, String或Readable对象。

所有的输入、分词以及翻译的操作都隐藏在不同类型的next方法中。

默认情况下,Scanner根据空白字符对输入进行分词。也可以用useDelimiter()来设置定界符。

Class对象

Class.forName()

RTTI

注册工厂

instanceof

反射

动态代理

泛型。。。

I/O流的典型使用方式

-缓冲输入文件:

public static String read(String filename) throws IOException {

  BufferedReader in = new BufferedReader(new FileReader(filename));

  String s;

  StringBuilder sb = new StringBuilder();

  while ((s - in.readLine()) != null)

    sb.append(s + "\n");

  in.close();

  return sb.toString();

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: