Java编程思想(一)第1~1三、16章
Java编程思想(二)第14章-类型信息
Java编程思想(三)第15章-泛型
Java编程思想(四)第17章-容器深刻研究
Java编程思想(五)第18章-Java IO系统
Java编程思想(六)第19章-枚举类型
Java编程思想(七)第20章-注解
Java编程思想(八)第21章-并发javascript
面向对象程序设计语言使用了后期绑定的概念。当向对象发送消息时,被调用的代码直到运行时才能肯定。也叫动态绑定。
编译器确保被调用方法的存在,并对调用参数和返回值执行类型检查(Java是强类型的语言,没法提供此类保证的语言被称为是弱类型的),可是并不知道将被执行的确切代码。
在某些语言中,必须明确地声明但愿某个方法具有后期绑定属性所带来的灵活性(C++是使用virtual
关键字来实现的)。在这些语言中,方法在默认状况下不是动态绑定的。而在Java中,动态绑定是默认行为(Java中除了static方法、final方法和private方法以外,其它全部方法都是动态绑定),不须要添加额外的关键字来实现多态。
把将导出类看作是它的基类的过程称为向上转型(upcasting)java
在Java中(事实上还包括除C++之外的全部OOP语言),全部的类最终都继承自单一的基类,这个终极基类的名字就是Object
。
单根继承结构的好处:ios
Java彻底采用了动态内存分配方式(基本类型只是一种特例)。每当想要建立对象时,就要使用new关键字来构建对象的动态实例。程序员
基本类型是一个并不是是引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中,所以更加高效。Java的基本类型所占存储空间大小不随机器硬件架构的变化而变化。这种所占存储空间大小的不变性是Java程序比用其余大多数语言编写的程序更具可移植性的缘由之一。web
基本类型 | 大小 | 最小值 | 最大值 | 包装器类 |
---|---|---|---|---|
boolean | - | - | - | Boolean |
byte | 8 bits | -128 | +127 | Byte |
char | 16 bits | Unicode 0 | Unicode 216-1 | Character |
short | 16 bits | -215 | +215-1 | Short |
int | 32 bits | -232 | +232-1 | Integer |
float | 32 bits | IEEE754 | IEEE754 | Float |
long | 64 bits | -263 | +263-1 | Long |
double | 64 bits | IEEE754 | IEEE754 | Double |
void | - | - | - | Void |
全部数值类型都有正负号
boolean类型所占存储空间的大小没有明确指定(要看具体虚拟机的实现),仅定义为可以取字面值true或false。编程
Java确保数组会被初始化。并且不能在它的范围以外被访问。这种范围检查,是以每一个数组上少许的内存开销及运行时的下标检查为代价的。数组
《深刻理解Java虚拟机》:
Java 语言中对数组的访问比C/C++相对安全是由于:若有一维数组,其元素类型为mypackage.MyClass
,则虚拟机会自动生成一个直接继承于java.lang.Object
的子类[Lmypackage.MyClass
,建立动做由字节码指令newarray
触发。这个类表明了一个元素类型为mypackage.MyClass
的一维数组,数组中应有的属性和方法(用户可直接使用的只有被修饰为public的length
属性和clone()
方法)都实如今这个类里。Java语言中对数组的访问比C/C++相对安全就是由于这个类封装了数组元素的访问方法(准确地说,越界检查不是封装在数组元素访问的类中,而是封装在数组访问的xaload
、xastore
字节码中),而C/C++直接翻译为对数组指针的移动。安全
// NotInit.java
package mypackage;
class MyClass{
static{
System.out.println("MyClass Init...");
}
}
public class NotInit{
public static void main(String[] args){
MyClass[] a = new MyClass[3];
}
}
/* * 没有输出,说明数组元素没有初始化(虚拟机自动生成了别的类)。 */
java.lang.ArrayIndexOutOfBoundsException
异常。1. Java 与 C/C++ 关于做用域的区别:以下,对于Java,非法,而对于 C/C++ 合法。(在 C/C++ 里将一个做用域的变量“隐藏”起来的作法,在Java里是不容许的。由于Java设计者认为这样作会致使程序混乱。)闭包
{
int x = 12;
{
int x = 96; // Illegal for Java, but legal for C/C++
}
}
2. Java对象不具有和基本类型同样的生命周期。当用new建立一个Java对象时,它能够存活于做用域以外。如:架构
{
String s = new String("a string");
} // End of scope
引用 s
在做用域终点就消失了。然而,s
指向的 String 对象仍继续占据内存空间。
import关键字指示编译器导入一个包,也就是一个类库(在其余语言中,一个库不只包含类,还可能包括方法和数据,可是Java中全部的代码都必须写在类里)。
特定类 java.lang
会被自动导入到每个Java文件中。
有些面向对象语言采用类数据和类方法两个术语,表明那些数据和方法只是做为整个类,而不是类的某个特定对象而存在的。例:
class StaticTest{
static int i = 47;
}
以下建立两个对象,st1.i 和 st2.i 指向同一存储空间,共享同一个 i
,所以它们具备相同的值47。
StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
static
做用于字段时,会改变数据的建立方式,但做用于方法时,差异却没有那么大。static
方法的一个重要用法就是在不建立任何对象的前提下就能够调用它。这一点对定义main()
方法很重要(因此main()
方法是一个 satic 方法),这个方法是运行一个应用时的入口点。 static
方法能够建立或使用与其类型相同的被命名对象,所以,static
方法经常拿来作“牧羊人”的角色,负责看护与其隶属同一类型的实例群。 static
方法的含义:static
方法就是没有this
的方法。关于static方法内部是否能调用非静态方法:由于没有this
,就没有对象,因此不能直接调用非静态方法,但能够传递一个对象引用到静态方法里,而后经过这个引用(和this
效果相同)来调用非静态方法和访问非静态数据成员。static
方法不是“面向对象”的,由于它们的确具备全局函数的语义;使用static
方法时,因为不存在this
,因此不是经过“向对象发送消息”的方式来完成的。==
和 !=
比较的是对象的引用equals()
的默认行为也是比较引用// Equivalence.java
public class Equivalence{
public static void main(String[] args){
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println(n1 == n2);
System.out.println(n1 != n2);
System.out.println(n1.equals(n2));
Value v1 = new Value();
Value v2 = new Value();
v1.i = v2.i = 47;
System.out.println(v1.equals(v2));
}
}
class Value{
int i;
}
/* Output: * false * true * true * false */
以上,
1. n1
和 n2
是两个不一样的引用(明显是两个不一样的存储区域),因此两者 !=
。
2. equals()
方法是全部对象的特殊方法(继承自Object
类),Integer重定义了equals()方法以比较其内容是否相等,因此这里n1.equals(n2)
为 true
。equals()
不适用于“基本类型”,基本类型直接使用==
和!=
便可。
3. v1.equals(v2)
为 false
验证了 equals()
方法默认行为是比较引用,除非在自定义类Value
中重定义 equals()
方法。
Integer
和Long
类的静态方法toBinaryString()
能够很容易地实现这一点。注意,若是将比较小的类型传递给Integer.toBinaryString()
方法,则该类型将自动转换为int
。// Literals.java
public class Literals{
public static void main(String[] args){
int i1 = 0x2f; // Hexadecimal (lowercase)
System.out.println("i1: " + Integer.toBinaryString(i1));
int i2 = 0X2F; // Hexadecimal (uppercase)
System.out.println("i2: " + Integer.toBinaryString(i2));
int i3 = 0177; // Octal (leading zero)
System.out.println("i3: " + Integer.toBinaryString(i3));
char c = 0xffff; // max char hex value
System.out.println("c: " + Integer.toBinaryString(c));
byte b = 0x7f; // max short hex value
System.out.println("b: " + Integer.toBinaryString(b));
short s = 0x7fff; // max short hex value
System.out.println("s: " + Integer.toBinaryString(s));
long n1 = 200L; // long suffix
long n2 = 200l; // long suffix (but can be confusing)
long n3 = 200;
float f1 = 1;
float f2 = 1F; // float suffix
float f3 = 1f; // float suffix
double d1 = 1d; // double suffix
double d2 = 1D; // dobule suffix
// (Hex and Octal also work with long)
}
/* OUtput: * i1: 101111 * i2: 101111 * i3: 1111111 * c: 1111111111111111 * b: 1111111 * s: 111111111111111 * */
}
e
表明“10的幂次”,与科学与工程领域中“e”表明天然对数的基数(约等于2.718,Java中的Math.E给出了更精确的double型的值)不一样。 根据John Kirkham的描述,Java语言中 e 与 科学工程领域不一样,可能跟60年代的FORTRAN有关。
// Exponents.java
// "e" means "10 to the power."
public class Exponents {
public static void main(String[] args){
// Uppercase and lowercase 'e' are the same:
float expFloat = 1.39E-43f;
expFloat = 1.39e-43f;
System.out.println(expFloat);
double expDouble = 47e47d; // 'd' is optional
double expDouble2 = 47e47; // Automaticall double
System.out.println(expDouble);
}
/* Output: *1.39E-43 *4.7E48 */
}
将float和double转型为整型值时,老是对该数字执行截尾。若是想要获得舍入的结果,就须要使用java.lang.Math
中的round()
方法。
// CastingNumbers.java
// What happens when you cast a float or double to an integral value ?
public class CastingNumbers{
public static void main(String[] args){
double above = 0.7, below = 0.4;
float fabove = 0.7f, fbelow = 0.4f;
System.out.println("(int)above: " + (int)above);
System.out.println("(int)below: " + (int)below);
System.out.println("(int)fabove: " + (int)fabove);
System.out.println("(int)fbelow: " + (int)fbelow);
System.out.println("Math.round(above): " + Math.round(above));
System.out.println("Math.round(above): " + Math.round(above));
System.out.println("Math.round(below): " + Math.round(below));
System.out.println("Math.round(fabove): " + Math.round(fabove));
System.out.println("Math.round(fbelow): " + Math.round(fbelow));
}
}
/* Output: (int)above: 0 (int)below: 0 (int)fabove: 0 (int)fbelow: 0 Math.round(above): 1 Math.round(below): 0 Math.round(fabove): 1 Math.round(fbelow): 0 */
char
、byte
或者short
),那么在运算以前,这些值会自动转换成int。这样一来,最终生成的结果就是int
型。若是想把结果赋值给较小的类型,就必须使用类型转换(既然把结果赋给了较小的类型,就可能出现信息丢失)。一般,表达式中出现的最大的数据类型决定了表达式最终结果的数据类型。若是一个float值与一个double值相乘,结果就是double,若是将一个int和一个long值相加,则结果就为long。溢出。若是对两个足够大的int值执行乘法运算,结果就会溢出。编译器不会发出错误或警告信息,运行时也不会出现异常。这说明Java虽然是好东西,但也没有那么好!
// Overflow.java
// Surprise! Java lets you overflow.
public class Overflow{
public static void main(String[] args){
int big = Integer.MAX_VALUE;
System.out.println("big = " + big);
int big1 = big + 1;
System.out.println("big1 = " + big1);
int bigger = big * 4;
System.out.println("bigger = " + bigger);
}
}
/* Output: big = 2147483647 big1 = -2147483648 bigger = -4 */
sizeof()
操做符 在C和C++中,sizeof()
操做符能够告诉你为数据项分配的字节数。使用这个操做符的最大缘由是为了进行一些与存储空间有关的运算,使程序能够在不一样平台上“移植”。而Java不须要sizeof()
操做符来知足这方面的须要,由于全部数据类型在全部机器中的大小是相同的。咱们没必要考虑移植问题——它已经被设计在语言中了。
注意Java不容许咱们将一个数字做为布尔值使用,这与C和C++ 不一样(C/C++中,“真”是非零,而“假”是零)。若是将数字做为布尔表达式,Java编译器会直接报错。
switch要求使用一个选择因子:
enum
,使得enum能够与switch协调工做。String.equals()
。所以,须要注意,传给switch的String变量不能为null,同时switch的case子句中使用的字符串也不能为null。显然是由于: hashCode
(String.equals()会调用)方法会出现NullPointException
。switch支持String只是一个语法糖,由javac来负责生成相应的代码。底层的JVM在switch上并无进行修改。
重载方法,方法名相同,形式参数列表不一样(参数列表又叫参数签名,包括参数的类型、参数的个数和参数的顺序,只要有一个不一样就叫作参数列表不一样)。重载是面向对象的一个基本特性。
1. 《深刻理解Java虚拟机》:虚拟机(准确地说是编译器)在重载时是经过参数的静态类型(Static Type )或叫外观类型(Apparent Type)而不是实际类型(Actual Type)做为断定依据的。
2. 《深刻理解Java虚拟机》:编译期间选择静态分派目标的过程是Java语言实现方法重载的本质。
finalize()
不等于C++中的析构函数
finalize()
才获得调用 因为垃圾回收器会负责释放对象占据的全部内存,这就将finalize()
的需求限制到一种特殊状况,即经过某种建立对象方式之外的方式为对象分配了存储空间。因为Java中一切皆为对象,因此那种特殊状况主要发生在使用“本地方法”的状况下,本地方法是一种在Java中调用非Java代码的方式。
不要过多地使用finalize()
,它不是进行普通的清理工做的合适场所。
Joshua Bloch在题为“避免使用终结函数”一节中走得更远,他提到:“终结没法预料,经常是危险的,总之是多余的。”《Effective Java》,第20页,(Addison-Wesley 2001)
Java尽力保证:全部变量在使用前都能获得恰当的初始化。
对于类的成员变量:
// InitialValues.java
public class InitialValues{
int j;
char c;
MyClass mc;
public static void main(String[] args){
int i;
//i++; // Error -- i not initialized
InitialValues obj = new InitialValues();
System.out.println(obj.c);
System.out.println(obj.j);
System.out.println(obj.mc);
}
}
class MyClass{}
i
首先会被置0,而后变成7。// Counter.java
public class Counter{
int i;
Counter(){
i = 7;
}
}
假设有个名为Dog
的类:
new Dog()
时,首先将在堆上分配存储空间;能够将Java中的数组做为一种数组类型来理解。
int[] a;
能够认为是 a
是一个数组引用,初始值为null
int[] a = new int[3];
初始化各元素值为0
,对于boolean
,初始值为false
;int[] a = {1, 2, 3};
初始化元素分别为1, 2, 3
;Sun 将Java2中的JDK改造得更聪明了一些。在安装后你会发现,即便你未设立CLASSPATH,你也能够编译并运行基本的Java程序。
在C++中,若是基类拥有一个已被屡次重载的方法名称,那么在其派生类中从新定义该方法名称,就会屏蔽其基类中的任何版本,这叫作名称屏蔽。可是在Java中,就种状况下,不会发生名称屏蔽,即不管在派生类仍是在基类中对方法进行定义,重载机制均可以正常工做。
以下C++会产生名称屏蔽:
// Hide.cpp
// #include "Hide.h"
#include <iostream>
using namespace::std;
// Hide.h start
class Homer { public: Homer(); ~Homer(); void doh(char); void doh(float); }; class Milhouse { public: Milhouse(); ~Milhouse(); }; class Bart : public Homer { public: Bart(); ~Bart(); void doh(Milhouse*); }; // Hide.h end Homer::Homer(){} Homer::~Homer(){} void Homer::doh(char c){ cout << "doh(char)" << endl; } void Homer::doh(float f){ cout << "doh(float)" << endl; } Milhouse::Milhouse(){} Milhouse::~Milhouse(){} Bart::Bart(){ Homer(); } Bart::~Bart(){} void Bart::doh(Milhouse* m){ cout << "doh(Milhouse)" << endl; } int main(int argc, char* argv[]){ Bart* b = new Bart(); //b->doh('x'); // error C2664: 'void Bart::doh(Milhouse *)': cannot convert argument 1 from 'char' to 'Milhouse *' //b->doh(1.0f); // error C2664: 'void Bart::doh(Milhouse *)': cannot convert argument 1 from 'float' to 'Milhouse *' b->doh(new Milhouse()); return 0; } /* Output: * doh(Milhouse) */
而Java不会产生:
// Hide.java
class Homer{
void doh(char c){
System.out.println("doh(char)");
}
void doh(float f){
System.out.println("doh(float)");
}
}
class Milhouse{}
class Bart extends Homer{
/* 若是使用这个注解,编译时会报错: * “方法不会覆盖或实现超类型的方法” -- method does not override a method from its superclass * 由于你是想要重写的,但却进行了重载。 */
//@Override
void doh(Milhouse m){
System.out.println("doh(Milhouse)");
}
}
public class Hide{
public static void main(String[] args){
Bart b = new Bart();
b.doh('x');
b.doh(1.0f);
b.doh(new Milhouse());
}
}
/* Output: * doh(char) * doh(float) * doh(Milhouse) */
Java SE5新增长了@Override
注解,能够把它看成关键字来用,它的做用是告诉编译器我想重写这个方法,由于Java不会产生名称屏蔽,因此若是我不留心重载了,编译器就会报错来告诉我违背了个人初衷。
根据上下文环境,Java的关键字final的含义存在着细微的区别,但一般它指的是“这是没法改变的。”不想改变可能出于两种理由:设计或效率。可能使用到final的三种状况:数据、方法和类。
基本类型变量应用final关键字时,将向编译器告之此变量是恒定不变的,即它是编译期常量。这样编译器可在编译时执行计算式,从而减轻了运行时负担(提升效率)。编译期常量在定义(声明)时必须对其赋值(声明时也能够不赋(此时叫空白final),但必须在构造器中赋值,因此final域在使用前老是被初始化。)。final常量常与static
一块儿使用,强调只有一份。编译期常量(带有恒定初始值),即 static final 的基本类型变量全用大写字母命名,而且字与字之间用下划线隔开(这就像C常量同样,C常量是这一命名传统的发源地)。ds
- final 对象引用
用于对象引用,则引用恒定不变,即一旦引用初始化指向一个对象,就没法再把它改变为指向另外一个引用,但对象其自身是能够被修改的。这种情形一样适用数组,由于如前面所述,Java数组也可(看做)是引用。
指明为final的方法参数,意味着方法内只能读而不能修改参数,这一特性主要用来向匿名内部类传递数据。
final
方法的缘由有两个: final
,就是赞成编译器针对该方法的全部调用都转为内嵌调用。而在Java SE5/6时,应该让编译器和JVM云处理效率问题,只有在想要明确禁止覆盖时,才将方法设置为final的。final
和private
关键字 private
方法都隐式地指定为是final
的。因为没法取用private
方法,因此也就没法覆盖它。private
方法(隐含是final
的),彷佛奏效,编译器不会出错,但实际上只是在派生类中生成了一个新的方法,此时并无覆盖父类的private
方法。// FinalOverridingIllusion.java
class WithFinals{
private final void f(){
System.out.println("WithFinals.f()");
}
// Automatically "final"
private void g(){
System.out.println("WithFinals.g()");
}
}
class OverridingPrivate extends WithFinals{
public final void f(){
System.out.println("OverridingPrivate.f()");
}
public void g(){
System.out.println("OverridingPrivate.g()");
}
}
public class FinalOverridingIllusion{
public static void main(String[] args){
OverridingPrivate op = new OverridingPrivate();
op.f();
op.g();
// You can upcast
WithFinals wf = op;
// But you can't call the methods:
//wf.f();
//wf.g();
}
}
/* Output: * OverridingPrivate.f() * OverridingPrivate.g() * */
final类代表对该类的设计永不须要变更,或者出于安全的考虑,你不但愿它有子类。由于final类禁止继承,因此final类中全部的方法都隐式指定为是final的,由于没法覆盖它们。在final类中能够给方法添加final修饰词,但这不会增添任何意义。
在设计类时,将方法指明是final
的,应该说是明智的。
- Java1.0/1.1中Vector
类中的方法均没有设计成final
的,而后Statck
继承了Vector
,就是说Stack
是个Vector
,这从逻辑观点看是不正确的,还有Vector
中的addElement()
和elementAt()
是同步的,致使执行开销大,可能会抹煞final
的好处。因此Vector
的设计不合理,现代Java的容器ArrayList
替代了Vector
。ArrayList
要合理得多,但遗憾的是仍然存在用旧容器库编写新程序代码的状况。
- Java1.0/1.1中的Hashtable
类也是不包含任何final
方法。现代Java的容器库用HashMap
代替了Hastable
。
Java采用了一种不一样的对类加载的方式,Java每一个类的编译代码都存在于它本身的独立的文件中(.class文件)。该文件只在其代码须要被使用时才会被加载(That file isn’t loaded until the code is needed)。一般,能够说“类的代码在初次使用时才加载。”这一般是指加载发生于构造类的第一个对象之时,可是当访问static域或static方法时,也会发生加载。(构造器也是static方法,尽管static关键字并无地写出来。所以更准确地讲,++类是在其任何static成员被访问时加载的++。)
初次使用之处也是staic初始化发生之处。全部的static对象和static代码段都会在加载时依程序中的顺序(即,定义类时的书写顺序)而依次初始化。固然,定义为static的东西只会被初始化一次。
多态(也称做动态绑定、后期绑定或运行时绑定)。
将一个方法调用与一个方法主体关联起来称做绑定。Connecting a mehtod call to a mehtod body is called binding.
若在程序执行前进行绑定(若是有的话,由编译器和链接程序实现),叫作前期绑定。它是面向过程语言中不须要选择就默认的绑定方式。例如,C只有一种方法调用,那就是前期绑定。
When binding is performed before the program is run (by the compiler and linker, if there is one), it’s called early binding. You might not have heared the term before because it has never been an option with procedural language. C compilers have only one kind of method call, and that’s early binding.
后期绑定,就是在运行时根据对象的类型进行绑定。后期绑定也叫作动态绑定或运行时绑定。若是一种语言想实现后期绑定,就必须具备某种机制,以便在运行时能判断对象的类型,从而调用恰当的方法。也就是说,编译器一直不知道对象的类型,可是方法调用机制能找到正确的方法体,并加以调用。后期绑定机制随编程语言的不一样而有所不一样,可是只要想一下就会得知,无论怎样都必须在对象中安置某种“类型信息”。
The solution is called late binding, which means that the binding occurs at run time, based on the type of object. Late binding is also called dynamic binding or runtime binding. When a language implements late binding, there must be some mechanism to determine the type of the object at run time and to call the appropriate method. That is, the compiler still doesn’t know the object type, but the mehtod-callmechanism finds out and calls the correct method body. The late-binding mechanism varies from language to language, but you can imagine that some sort of type information must be installd in the objects.
再谈final方法。
如Chapter7所说,final方法能够防止其余人覆盖该方法。但更重要的一点是:这样作能够有效地“关闭”动态绑定,或者说,告诉编译器不须要对其进行动态绑定。这样,编译器就能够为final方法调用生成更有效的代码。然而,大多数状况下,这样作对程序的总体性能不会有什么改观。因此,最好根据设计来决定是否使用final,而不是出于试图提升性能的目的来使用final。
Why would you declare a method final? As noted in the last chapter, it prevents anyone from overriding that method. Perhaps more important, it effectively “turns off” dynamic binding, or rather it tells the compiler that dynamic binding isn’t necessary.This allows the compiler to generate slightly more efficient code for final method calls. However, in most cases it won’t make any overall performance diffeence in your program, so it’s best to only use final as a design decision, and not as an attempt to improve performance.
// FieldAccess.java
// Direct field access is determined at compile time.
class Super{
public int field = 0;
public int getField(){return field;}
}
class Sub extends Super{
public int field = 1;
public int getField(){return field;}
public int getSuperField(){return super.getField();}
}
public class FieldAccess{
public static void main(String[] args){
Super sup = new Sub(); // Upcast
System.out.println("sup.field = " + sup.field + ". sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " + sub.field + ". sub.getFiled() = " + sub.getField() + ". sub.getSuperField() = " + sub.getSuperField());
}
}
/** Output: * sup.field = 0. sup.getField() = 1 * sub.field = 1. sub.getFiled() = 1. sub.getSuperField() = 0 */
若是在构造器内部调用正在构造的对象的某个动态绑定方法,因为动态绑定是在运行时才决定的,而此时,该对象还正在构造中,因此它不知道本身属于哪一个类(父类仍是本身),而且方法所操纵的成员可能还未进行初始化,这可能会产生一引发难于发现的隐藏错误。
// PolyConstructors.java
// Constructors and polymorphism
// don't produce what you might expect
class Glyph{
void draw(){
System.out.println("Glyph.draw()");
}
Glyph(){
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
}
private int radius = 1;
void draw(){
System.out.println("RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String[] args){
new RoundGlyph(5);
}
}
/**Output: * Glyph() before draw() * RoundGlyph.draw(), radius = 0 * Glyph() after draw() * RoundGlyph.RoundGlyph(), radius = 5 */
以上代码,构造RoundGlyph
对象时,先调用父类构造器Glyph()
,父类构造器中如咱们所期,调用了多态的draw()
,可是,因为 子类还没构造完成,因此打印的成员变量radius的值是0,而并非咱们想象的其默认的初始值1。
abstract
关键字),没有接口的说法virtual
关键字将类内方法声明为虚函数(如virtual void f();
)来实现多态(在C++中,派生类只能重写父类的虚函数,而在Java中,除static方法外,其它方法都是能够被重写的,即默认都是多态的。)。除此之外,包含虚函数的类与其它类没有区别。virtual void f() = 0;
声明时,构成纯虚函数。由于纯虚函数没有函数体,不是完整的函数,没法调用,也没法为其分配内存空间,没法实例化,也就没法建立对象,因此在C++中含有纯虚函数的类被称为抽象类(Abstract Class,注意在C++中,没有abstract
关键字)。抽象类一般做为基类(叫作抽象基类),让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。abstract
和 interface
关键字,经过它们来定义抽象类和接口class
前添加abstract
关键字,定义成抽象类。 new
生成对象,但注意能够追加{}
生成匿名实现类,仍然不是它本身的实例化。abstract
关键字,定义抽象方法,至关于C++的纯虚函数,派生类必须重写该方法,而后才能实例化。Java类中若有抽象方法,则类符号前必须也要添加abstract
关键字,定义为抽象类(能够没有抽象方法)。class
关键字改为interface
关键字,这就建立了一个接口。 static
和 final
的,显然,接口中的域不能是空final,这些域不是接口的一部分,它们存储在该 接口的静态存储区域内。interface
前能够添加public
修饰符,不加默认是包访问权限,接口的方法默认都是public
的。implements
多个接口,达到C++中多重继承的效果。extends
另外的一个或多个接口来实现接口的扩展。enum
关键字,所以使用接口来群组常量就没意义了。Java普通内部类能访问其外围对象(enclosing object)的全部成员,而不须要任何特殊条件 。C++嵌套类的设计只是单纯的名字隐藏机制,与外围对象没有联系,也没有隐含的访问权。在Java中,当某个类建立一个内部类对象时,此内部类对象一定会秘密地捕获一个指向那个外围类的对象的引用。而后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。这些细节是由编译器处理的。Java的迭代器复用了这个特性。
// Sequence.java
interface Selector{
boolean end();
Object current();
void next();
}
public class Sequence{
private Object[] items;
private int next = 0;
public Sequence(int size){ items = new Object[size]; }
public void add(Object x){
if(next != items.length) items[next++] = x;
}
private class SequenceSelector implements Selector{
private int i = 0;
public boolean end(){ return i == items.length; }
public Object current(){ return items[i]; }
public void next(){ if (i < items.length) i++; }
}
public Selector selector(){ return new SequenceSelector(); }
public static void main(String[] args){
Sequence sequence = new Sequence(10);
for(int i = 0; i < 10; i++){
sequence.add(Integer.toString(i));
}
Selector selector = sequence.selector();
while(!selector.end()){
System.out.print(selector.current() + " ");
selector.next();
}
System.out.println();
}
}
/**Output: * 0 1 2 3 4 5 6 7 8 9 */
.this
与 .new
.this
返回其外围对象的引用。.new
来生成一个内部类对象。// DotThis.java
// Qualifying access to the outer-class object.
public class DotThis{
void f(){ System.out.println("DotThis.f()"); }
public class Inner{
public DotThis outer(){
return DotThis.this; // a plain "this" would be Inner's "this"
}
}
public Inner inner(){ return new Inner(); }
public static void main(String[] args){
DotThis dt = new DotThis();
DotThis.Inner dti = dt.inner();
dti.outer().f();
}
}
/*Output: * DotThis.f() */
Anonymous Inner Class.
内部类声明为static
时,再也不包含外围对象的引用.this
,称为嵌套类(与C++嵌套类大体类似,只不过在C++中那些类不能访问私有成员,而在Java中能够访问)。
- 建立嵌套类,不须要外围对象。
- 不能从嵌套类的对象中访问非静态的外围对象。
嵌套类能够做为接口的一部分(正常状况下,接口内部不能放置任何代码)。放到接口中的任何类都自动是public和static的。由于类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则
一个内部类被嵌套多少层并不重要——它能透明地访问它所嵌入的外围类的全部成员,以下:
// MultiNestingAccess.java
class MNA{
private void f(){}
class A{
private void g(){}
public class B{
void h(){
g();
f();
}
}
}
}
public class MultiNestingAccess{
public static void main(String[] args){
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
// MultiInterfaces.java
// two ways tha a clas can implement multiple interface.
interface A{}
interface B{}
class X implements A, B {}
class Y implements A {
B makeB(){
// Amonymous inner class
return new B() {};
}
}
public class MultiInterfaces{
static void takesA(A a){}
static void takesB(B b){}
public static void main(String[] args){
X x = new X();
Y y = new Y();
takesA(x);
takesB(x);
takesA(y);
takesB(y.makeB());
}
}
.this
),在此做用域内,内部类有权操做全部的成员,包括private成员。// Callbacks.java
// using inner classes for callbacks
interface Incrementable{
void increment();
}
// Very simple to just implement the interface:
class Callee1 implements Incrementable{
private int i = 0;
public void increment(){
System.out.println(++i);
}
}
class MyIncrement{
public void increment(){ System.out.println("Other operation"); }
static void f(MyIncrement mi) { mi.increment(); }
}
// If your class must implement increment() in some other way, you must use an inner class:
class Callee2 extends MyIncrement{
private int i = 0;
public void increment(){
super.increment();
System.out.println(++i);
}
private class Closure implements Incrementable{
public void increment(){
// Specify outer-class method, otherwise you'd get an infinite recursion:
Callee2.this.increment();
}
}
Incrementable getCallbackReference(){
return new Closure();
}
}
class Caller{
private Incrementable callbackReference;
Caller(Incrementable cbh){ callbackReference = cbh; }
void go(){ callbackReference.increment(); }
}
public class Callbacks {
public static void main(String[] args){
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}
/**Uoutput: * Other operation * 1 * 1 * 2 * Other operation * 2 * Other operation * 3 **/
Java的接口和内部类比其余面向对象的概念更深奥复杂,C++没有这些,将二者结合起来,一样可以解决C++中的用多重继承所能解决的问题。
Iterator
迭代器使得客户端程序员没必要知道或关心容器类的底层结构。ListIterator
只能用于各类List类的访问。ListIterator能够双向移动,而Iteraotr只能向前移动。ArrayList
底层是数组结构,即连续存储空间,因此读取元素快。因可自动扩容,因此能够把ArrayList
看成“可自动扩充自身尺寸的数组”看待。LinkedList
是链表结构,因此插入元素快。 LinkedList
具备可以直接实现栈(Stack)的全部功能的方法,所以能够直接将LinkedList做为栈使用。LinkdedList
也提供了支持队列(Queue)行为的方法,而且实现了Queue接口,因此也能够用做Queue。 在Java中,Collection
是描述全部序列容器的共性的根接口,它可能会被 认为是一个“附属接口”,即由于要表示其余若干个接口的共性而出现的接口。而在标准C++类库中并无其容器的任何公共基类——容器之间的全部共性都是经过迭代器达成的。Java将两种方法绑定到了一块儿,由于实现Collection
就意味着须要提供iterator()
方法。
foreach
语法用于任何实现了Iterable
接口的类。Collection
接口扩展了Iterable
接口,因此全部Collection
对象都适用foreach
语法。
ArrayList<int>
或 HashMap<int, int>
之类的东西。可是能够利用自动包装机制和基本类型的包装器来解决,自动包装机制将自动地实现int
到 Integer
的双向转换:// ListOfInt.java
import java.util.*;
public class ListOfInt{
public static void main(String[] args){
// 编译错误:意外的类型
// List<int> li = new ArrayList<int>();
// Map<int, Interger> m = new HashMap<int, Integer>();
List<Integer> li = new ArrayList<Integer>();
for(int i = 0; i < 5; i++){
li.add(i); // int --> Integer
}
for(int i : li){ // Integer --> int
System.out.print(i + " ");
}
}
}/* Output: 0 1 2 3 4 */
异常容许咱们(若是没有其余手段)强制程序中止运行,并告诉咱们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。
异常处理理论上有两种基本模型。长久以来,尽管程序员们使用的操做系统支持恢复模型的异常处理,但他们最终仍是转向使用相似“终止模型”的代码,而且忽略恢复行为。
全部标准异常都有两个构造器:一个是默认构造器;另外一个是接受字符串做为参数,以便能把相关信息放入异常对象的构造器。
// FullConstructors.java
class MyException extends Exception{
public MyException(){}
public MyException(String msg){ super(msg); }
}
public class FullConstructors{
public static void f() throws MyException{
System.out.println("Throwing MyException form f()");
throw new MyException();
}
public static void g() throws MyException{
System.out.println("Throwing MyException form g()");
throw new MyException("Originated in g()");
}
public static void main(String[] args){
try{
f();
}catch(MyException e){
e.printStackTrace(System.out);
}
try{
g();
}catch(MyException e){
e.printStackTrace(System.out);
}
}
}/*Output: Throwing MyException form f() MyException at FullConstructors.f(FullConstructors.java:11) at FullConstructors.main(FullConstructors.java:19) Throwing MyException form g() MyException: Originated in g() at FullConstructors.g(FullConstructors.java:15) at FullConstructors.main(FullConstructors.java:24) */
Throwable
类声明了printStackTrace()
方法,它将打印“从方法调用处直到异常抛出处”的方法调用序列。printStackTrace()
方法所提供的信息能够经过getStackTrace()
方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每个元素都表示栈中的一桢。元素0是栈顶元素,而且是调用序列中的最后一个方法调用(这个Throwable
被建立和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。以下:
// WhoCalled.java
// Programmatic access to stack trace information
public class WhoCalled{
static void f(){
// Generate an exception to fill in the stack trace
try{
throw new Exception();
}catch(Exception e){
for(StackTraceElement ste : e.getStackTrace()){
System.out.println(ste.getMethodName());
}
}
}
static void g(){ f(); }
static void h(){ g(); }
public static void main(String[] args){
f();
System.out.println("-------------------------------");
g();
System.out.println("-------------------------------");
h();
}
}/*Output: f main ------------------------------- f g main ------------------------------- f g h main */
能够声明方法将抛出异常,实际上却不抛出。这样作的好处是,为异常先占个位子,之后就能够抛出这种异常而不用修改已有的代码。
在编译时被强制检查的异常称为被检查的异常。
经常会想要在捕获一个异常后抛出另外一个异常,而且但愿把原始异常的信息保存下来,这被称为异常链。JDK1.4之后,全部Throwable的子类在构造器中均可以接受一个cause
对象做为参数。这个cause
就用来表示原始异常,这样经过把原始异常传递给新的异常,使得即便在当前位置建立并了新的异常,也能经过这个异常链追踪到异常最初发生的位置。
在Throwable
的子类中,只有Error
(用于Java虚拟机报告系统错误)、Exception
以及RuntimeException
三种基本的异常提供了带cause
参数的构造器。
只能在代码中忽略RuntimeException
(及其子类)类型的异常,其余类型异常的处理都是由编译器强制实施的。究其缘由,RuntimeException
表明的是编程错误。
用某些特殊的方式使用finally
子句,可能会丢失异常,一种简单的丢失异常的方式是从finally
子句中返回。
finanlly
子句。break
和continue
语句的时候,finally
子句也会获得执行。finally
子句会在执行return
语句前执行,即它老是会执行,因此在一个方法中, 能够从多个点返回,而且能够保证重要的清理工做仍旧会执行。当要覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制颇有用,由于这意味着,当基类使用的代码应用到其派生类对象的时候,同样可以工做。
若是在构造器内抛出了异常,清理行为也许就不能正常工做了。
抛出异常的时候,异常处理系统会按照代码的书写顺序抛出“最近”的处理程序。找到匹配的处理程序以后,它就认为异常将获得处理,而后就再也不继续查找。
“报告”功能是异常的精髓所在。Java坚决地强调将全部的错误都以异常形式报告的这一事实,正是它远远超过诸如C++这类语言的长处之一,由于在C++这类语言中,须要以大量不一样的方式来报告错误,或者根本就没有提供错误报告功能。
String对象是不可变的。String类中每一个看起来会修改String值的方法,实际上都是建立了一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动。
javap -c Concatenation.class
反编译后,可见编译器自动引入了java.lang.StringBuilder
类,帮助了优化。// Concatenation.java
public class Concatenation{
public static void main(String[] args){
String mango = "mango";
String s = "abc" + mango + "def" + 47;
System.out.println(s);
}
}
/**Output: * abcmangodef47 **/
Compiled from "Concatenation.java"
public class Concatenation {
public Concatenation();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String mango
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String abc
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #7 // String def
21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: bipush 47
26: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
29: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
32: astore_2
33: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_2
37: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: return
}
javap -c WhitherStringBuilder
反编译后,显示优化程度不够,方法implicit()
显示StringBuilder
是在循环以内构造的,这样每通过一次循环就会构造珍上新StringBuilder
对象,而explict()
只生成一个StringBuilder
对象,更优。// WhitherStringBuilder.java
public class WhitherStringBuilder{
public String implicit(String[] fields){
String result = "";
for(int i = 0; i < fields.length; i++){
result += fields[i];
}
return result;
}
public String explicit(String[] fields){
StringBuilder result = new StringBuilder();
for(int i = 0; i < fields.length; i++){
result.append(fields[i]);
}
return result.toString();
}
}
Compiled from "WhitherStringBuilder.java"
public class WhitherStringBuilder {
public WhitherStringBuilder();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #2 // String
2: astore_2
3: iconst_0
4: istore_3
5: iload_3
6: aload_1
7: arraylength
8: if_icmpge 38
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: aload_2
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1
23: iload_3
24: aaload
25: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: goto 5
38: aload_2
39: areturn
public java.lang.String explicit(java.lang.String[]);
Code:
0: new #3 // class java/lang/StringBuilder
3: dup
4: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: iload_3
11: aload_1
12: arraylength
13: if_icmpge 30
16: aload_2
17: aload_1
18: iload_3
19: aaload
20: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: iinc 3, 1
27: goto 10
30: aload_2
31: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: areturn
}
由String对象后面跟着一个“+”,再后面的对象不是String时,编译器会使后面的对象经过toString()
自动类型转换成String
,若是这发生在自定义的类的重写的toString()
方法体内,就有可能发生无限递归,运行时抛出java.lang.StackOverflowError
栈溢出异常。
// InfiniteRecursion.java
public class InfiniteRecursion{
public String toString(){
//应该调用Object.toString()方法,因此此处应为super.toString()。
return " InfiniteRecursion address: " + this + "\n";
}
public static void main(String[] args){
List<InfiniteRecursion> v = new ArrayList<InfiniteRecursion>();
for(int i = 0; i < 10; i++)
v.add(new InfiniteRecursion());
System.out.println(v);
}
}
这些章节内容算是Java的基础,整理出来做为第一部分,算是温故知新吧。