主要内容摘自《Java7入门经典》
一、Java简介
1.1 Java的运行
1.2 JVM
- Java不是运行在本地机上,而是运行在Java 2 Platform的标准化环境中,该平台作为软件以Java运行环境(JRE)的形式在非常广泛的计算机的操作系统中得到了实现
- Java 2 Platform包含两部分───名为JVM的用软件实现的假想计算机和Java API
- Java API是一系列软件组件集合,提供编写完全成熟的交互式Java应用程序所需的工具
(个人理解:JDK>JRE>JVM,JRE里面包括JVM和Java API两大部分)
1.3 环境变量
- JAVA_HOME JDK安装目录
- PATH %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin
- CLASSPATH .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar
2个路径:
- PATH:指定可执行文件的路径。当执行一个可执行文件时,如果不能在当前目录下找到,则依次在PATH中的路径寻找,如果仍找不到则报错
- CLASSPATH:指定Java编译或者运行时需要用到的类(.class)文件,可不配置
1.4 Unicode字符集
Unicode是一个标准字符集,用来支持对几乎所有的语言进行编码。Unicode使用16bit的编码来代表一个字符(即每个字符占2byte)
Java源代码是Unicode字符。注释、标识符以及字符和字符串都能使用Unicode集合中标识字母的任意字符。Java内部也支持使用Unicode来标识字符和字符串,所以框架支持一个程序中含有各国语言。最熟悉的ASCII集合对应Unicode集合的前128个字母。
超大字符集:32bit编码的超大字符集,用来标识由16bit字符定义基本多语言集合中没有包括的字符。
1.5 JDK常用包结构
java.lang 基础类,如:String,System
java.util 工具类,如:集合,随机数,日历
java.io 文件操作类,输入输出
java.net 网络操作
java.math 数学相关操作的类
java.sql 数据库操作
1.6 文档注释
Javadoc命令 Javadoc标记
--常用的文档注释
/**
*@BelongsProject:${PROJECT_NAME}
*@BelongsPackage:${PACKAGE_NAME}
*@Author: Dave
*@CreateTime: ${YEAR}-${MONTH}-${DAY} ${HOUR}:${MINUTE}
*@Desc:toto
*/
二、 程序、数据、变量和计算
2.1、变量
变量:内存的别名
字面量:显示的数据值
变量的命名规则:
- Java对大小写敏感
- 以大小写字母或下划线或$符开始,不能以数字开始
- 不能是关键字or字面量(包括为boolean类型的字面量true或false)
- 驼峰表示法
- 用大写字母定义标记为常量的变量,加关键字final
2.2、表达式
- 混合算数表达式:如果其中一个操作数为double类型,就在进行运算之前将另一个转换为double类型
- 显示转换 int a=1; (double)a
- 自动类型转换 小类型转换为大类型
2.3、数学函数和常量
Math类
abs(arg):绝对值
max(arg1,arg2) min(arg1,arg2)
sqrt(arg):平方根 cbrt(arg):立方根
pow(arg1,arg2):arg1的arg2次方
floor(arg):等同于参数or比参数小的最大整数值
ceil(arg):等同于参数or比参数大的最小整数值
2.4、枚举
enum Day{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday}
枚举定义的最后没有分号。因为这里是定义一个类型,所以反括号的后面没有分号。
Day weekday=Day.Sunday;
三、循环与逻辑
3.1、决策
语句一般均具有决策功能
缩进:逻辑的视觉提示
3.2、逻辑运算符
& 逻辑与 && 条件与
| 逻辑或 || 条件或
^ 异或 !逻辑非
& 与 &&的区别:&&若左操作数为false,将不再计算右操作数的值
| 与 ||的区别: ||若左操作数为true,将不再计算右操作数的值
四、数组与字符串
Java对象:数组 字符串
4.1、数组
Java对象 同类型变量的集合
数组名+数组的索引(从数组首位置到该该元素的位移)
4.1.1 数组变量
数组变量与指向的数组是两个独立的实体,分配给数组变量的内存存储一个指向数组对象的引用,而不是存储数组本身。数组对象本身存储在另一处不同的内存实体中。
int[] primes; //待定义的整型数组的占位符
primes = new int[10];
指向对象的所有变量实际上存储了一些引用,其中记录它们所指向对象的内存地址
4.1.2 初始化数组
① int[] primes={1,2,3,4,5,6};
② int[] primes=new int[100];
primes[0]=2;
primes[1]=3;
③ double[] data=new double[10];
for(int i=0;i<10;i++){
data[i]=i+1;
}
4.1.3 二维数组
float[][] temperature =new temperature[10][365];
for(int i=0;i<temperature.length;i++){
for(int j=0;j<temperature[i].length;j++){
expression;
}
}
4.1.4 变长二维数组
float[][] samples;
samples=new float[6][]; //samples变量现在引用一个含有6个元素的数组,其中每个元素都保存一个指向一维数组的引用。如果需要,也可以单独定义某个数组
samples[0]=new float[5];
samples[1]=new float[7];
4.1.5 变长的多维数组
long[][][] beans=new long[6][][];
beans[0]=new long[3][];
beans[1]=new long[4][];
beans[0][0]=new long[7];
beans[0][1]=new long[5];
4.1.6 字符数组
char[] message=new char[50]
char[] message={‘l’,’9’,’v’,’e’};
4.2、字符串
- String使用了final关键字修饰,不能被继承
- 字符串底层封装了针对字符数组的操作算法
- 字符串一旦创建,对象永远无法改变,但字符串引用可以重新赋值
- 静态字符串在常量池定义,并尽可能使用同一个对象,重用静态字符串
String str1="abc";
String str2="abc";
System.out.println(str1 == str2); //true
- 内存编码及长度
- String在内存中采用Unicode编码,每个字符占用2个字节。任何一个字符(无论中英文)都算一个字符长度,占用2个字节
4.2.1 字符串数组
String[] names=new String[6];
String[] color={“red”,”yellow”,”black”,”green”,”blue”,”violent”};
4.2.2字符串的操作
- str1=str2 //比较字符串索引是否指向同一个字符串
- str1.equals(str2) //比较两个字符串的值是否相等
- str1.length() //获得字符串的长度
数组长度 primes.length //调用数据成员length
字符串长度 str.leng(); //调用函数方法
- char ch=str.charAt(i); //提取字符串中的字符
- str.startsWith() str.endsWith()
- String.valueof()
- indexOf lastIndexOf //从左向右搜索 或 从右向左搜索 返回所搜索字符的索引位置,若没有找到则返回-1.在用这种方法找寻字符串索引时,一定要检验返回值是否是-1.否则,如果没有找到内容就会报错
indexOf(char ch) indexOf(char ch,int index)
indexOf(String str) indexOf(String str,int index)
- subString(index) //获取由该索引位置至末尾的所有字符
subString(index1, index2)
- 字符串词法分析
split(“[]”,int):将字符串分割成标记并且将所有标记以String对象数组的方式返回
第一个参数:String对象,设定一个模式作为定义符。任何匹配该模式的定义符都被认为是一个标记的分隔符
第二个参数:是一个整数值,表示能应用该模式寻找标记的最大次数(影响找到的标记的最大数目)。若参数为0,则模式无次数限制,并且会丢弃所有的空标记,若参数为负数,模式次数同样没有限制,但是会保留并返回所有的空标记。(若有两个相邻的定义符,则会出现额外的标记)
String str=”To be, or not to be”;
String[] primes=str.split(“[, .]”,0);
- trim() //移除字符串头尾的空白
- 将一个字符串分割成子字符串,而子字符串可能在头尾包含空白时,trim()函数非常有用
- 从String对象创建字符数组
toCharArray():可以从String对象创建char类型的数组变量。由于toCharArray()创建char[]类型的数组并返回一个指向该数组的引用,因此只需要声明char[]类型的数组变量来保存数组引用即可-不需要为数组分配内存
getChars():将一个子字符串以字符数组的形式提取。但是这种情况下需要创建一个足够大的数组来保存这些字符并且将这个数组作为传递给getChars()方法的参数
getChars()方法的四个参数:
1)原字符串提取的第一个字符的索引位置(int)
2)原字符串提取的最后一个字符后面的索引位置(int)
3)用于目标数组的数组名(char[])
4)用于保存目标数组的起始偏移量(int)
example:
String text=”To be or not to be”;
char[] textArray=new char[3];
text.getChars(9,12,textArray,0);
使字符串执行基于集合的for循环
先将字符串转换为字符数组 for(int i:str.toCharArray())
- getBytes()方法:从字符串中提取字符到一个byte[]数组中。这将原始字符串中的字符转换成为下层操作系统使用的ASCII编码的字符串。(可能无法有效的用本地机器中的字符编码表示,故getBytes()方法的效果无法确定)
String str=”To be or not to be”;
byte[] textArray=text.getBytes();
- copyValueOf():从字符数组中创建String对象
char[] text={‘T’,’o’,’ ‘,’b’,’e’,’ ‘,’o’,’r’,’ ‘,’n’,’o’,’t’,’ ‘,’t’,’o’,’ ‘,’b’,’e’};
String str=text.copyValueOf(text);
String str=new String(text);
4.3、可变长字符串 StringBuffer StringBuilder
类对象能直接修改,这两个类提供的操作没有区别,但是多线程能安全的使用StringBuffer对象。StringBuffer类操作已经被编码以避免因为两个或多个线程的并发访问而引起的错误(如并发的两个线程,一个读,一个写(改)),若确定可变长字符串只能使用单个线程访问时则考虑用StringBuilder类对象,因为StringBuilder类对象比StringBuffer类对象的操作更快。
4.3.1 创建StringBuffer类对象
StringBuffer strBuf=”A stitch in time”; //错误
初始化StringBuffer类对象必须使用new,StringBuffer类名以及括号之中的字符串字面量作为初始化值,不能像String那样只用String的字面量作为初始化值,这是因为StringBuffer对象不仅仅是它内部包含的字符串(缓冲区长度)。而字符串字面量按照定义就是String对象。
StringBuffer strBuf=new StringBuffer(“A stitch in time”);
or:
String str=”A stitch in time”;
StringBuffer strBuf=new StringBuffer(“str1”);
4.3.2 StringBuffer对象的容量
StringBuffer对象有一个被称为缓冲区的内存。StringBuffer对象中的字符串长度可以不同于该对象包含的缓冲区长度。缓冲区的长度被称为StringBuffer的容量。(长度是字符串的属性,相对的,容量是字符串缓冲区的属性)
*为什么讨论StringBuffer的容量?
StringBuffer对象的容量对存储和修改字符串的开销量影响巨大,故需要先合理分配足够的容量
*容量的变化规律?
可变长字符串的容量会随着字符串长度的增加自动增长,规律如下:默认容量为字符串长度加16(容量 n=str.length()+16),当字符串长度增加时(或通过函数修改了字符串的容量),若没超过原容量,则容量不会发生变化,若超过原容量长度,则容量变为2n+2,将该容量大小与新的字符串长度比较(或与修改的容量大小比较),二者中的较大值则为新的容量值(即容量为2n+2与newStr.length()中的较大值)
*获得字符串的容量: int theCapacity = str.capacity()
*修改字符串的容量值 str.ensureCapacity(40);
4.3.3 为StringBuffer修改字符串的长度
setLength()
主要是用于减少字符串长度,末尾部分的字符串被丢弃,不影响容量。若增加字符串长度,则增加”\0000”字符。其容量变化同上。
4.3.4 append()方法
将String和StringBuffer对象(或一部分)添加到StringBuffer中
StringBuffer strBuf=new StringBuffer();
strBuf.append(“Mary”).appen(“ hands”);
or:
StringBuffer strBuf=new StringBuffer(“A stitch in time”);
String str=”a saves nine”;
strBuf.append(str,2,12);
append()附加的基本类型:
Boolean char String Object
int long float double
Byte short char[]
4.3.4 可变长字符串的其它操作:charAt() getChars()
注意:在StringBuffer对象中没有对应的getBytes()方法,但是可以通过调用StringBuffer对象的toString()方法获得一个String对象,然后对这个Sting对象调用getBytes()方法,进而获得与StringBuffer对象对应的byte[]数组。
strBuf.setCharAt(4,’Z’); //修改第五个字符为Z
strBuf.delete(5,9); //删除第5个到第8个字符
strBuf.reverse(); //调到对象中字符的顺序
4.3.5 从StringBuffer对象中创建String对象
toString() 方法
实际上,编译器会大量 地联合使用toString与append()方法来实现String对象的串联。假定如下字符串:
String str1=”Mary” String str2=” hands” String str3=” make”
String str4=” light” String str5=” work”
当写下类似如下的语句时:
String str=str1+str2+str3+str4+str5
编译器会实现如下内容:
String str=new StringBuffer().append(str1).append(str2).append(str3).append(str4).append(str5).toString();
4.4 正则表达式
4.4.1 正则表达式
预定义字符:
\\ 反斜线字符
. 任意一个字符
\d 任意一个数字字符[0-9]
\D 任意一个非数字字符
\w 任意一个字符[a-z0-9A-Z]
\W 非单词字符
\s 空白字符
\S 非空白字符
任意一个字符:
X 字符X
[abc] a、b、c中任意一个字符
[^abc] 除了a、b、c的任意字符
[a-zA-Z] a到z或A到Z的任意字符
[a-z&&[^m-p]] a到z且非m到p,等价于[a-lq-z]
边界匹配器:
^ 行的开头
$ 行的结尾
数量词:
X? 0次或1次
X* 零次或多次
X+ 1次或多次
X{n} n次
X{n,} 至少n次
X{n,m} n次到m次之间
分组:
()
4.4.2 字符串与正则表达式
str.matches(regex) //字符串匹配正则表达式,等价于 Pattern.matches(regex,str)
str.split(regex) //按某种正则表达式匹配规则分割字符串
replaceAll(regex,rep) //替换否和某种正则表达式规则的字符串
五、定义类
5.1 类的理解
类可以很复杂,可以尽可能全面的包含某类对象的操作,如String类,几乎涵盖了在任何程序中可能进行的字符串操作。但通常只是定义一个适合特定应用程序的类。
- 域:数据成员,通常能够区分相同类中的不同对象(实例成员)
- 方法:通常对域进行操作
对象 —— 实例
5.2 类定义中的域(静态域 & 实例域)
静态域:类变量。声明时使用static关键字,与类关联。由类的所有对象共享但并不属于任何特定对象,即使没有创建类对象,他们也存在。静态域的值被改变,那么新值会同步到所有对象中。
非静态域:实例变量。与每一个对象关联,每一个实例都拥有它自己的一个副本,能将同一个类下的特定对象与其它对象区分开。
- final域:可以将类中的一个域定义为final。这意味着该域不能被类中的方法修改。在声明final域时可以为其提供初始值(常量)。
- 静态域的作用:
- 保持对类的所有对象都有相同的常量值,如:private final static double PI
- 跟踪对某个类的所有对象而言都一样,而且即使没有定义类的对象也仍然需要使用的数据值,如统计某个类对象创建的数目
5.3 类定义中的方法(静态方法 & 实例方法)
静态方法:类方法。static声明,不能引用实例变量,Java编译器也不允许这样做
- 理解:Java应用程序主方法main()声明为static——应用程序在开始之前(执行main()方法)并不存在对象,所以为了开始执行,需要在不存在对象的前提下有一个可执行方法,也就是一个静态方法
- 静态方法最普遍的应用:将类作为一些实用方法的容器使用(如Math类中作为类方法实现的数学函数)
实例方法:与对象关联
- 虽然实例方法针对类的对象,但是实际上对于每一个实例方法,内存中都只有一份副本供类的对象共享(因为每个对象都复制所有的实例方法代价过于庞大)。有一个特殊的机制允许每调用一个方法时,代码都会以针对某个对象的方式执行。
- 特殊的机制(this指针):每个实例方法都有一个名为this的变量,它指向当前调用的对象。当方法引用一个实例变量时,编译器就隐式的使用this。
5.4 访问成员和方法
静态成员:静态域 & 静态方法
实例成员:非静态域 & 非静态方法
静态成员:.运算符 double rootPi=Math.sqrt(Math.PI);
实例成员:与特定的对象有关(即只能使用对象调用实例变量和方法)
5.5 定义类
根据约定,Java中的类名要以大写字母开始
根据约定,值为常量的变量名称需要大写
5.6 定义方法
方法:自包含的代码块,有一个名称,包含一些属性并且可以重用,同一个方法可以根据需求在程序的任意多个位置执行。
如果方法不返回值,可以调用关键字return来结束方法的执行,对于不返回值的方法来说,到达包括方法体的右花括号也等同于执行一条return语句。
5.7 参数列表
参数:又称形参,有名称和类型,并且出现在方法定义的参数列表中。定义了调用方法时能够传递给方法的值的类型。
实参:在方法执行中传递给方法的值,而且参数值在方法执行过程中通过参数名引用。参数值得类型必须与方法定义中对应参数设定的类型一致。
局部变量:从声明位置开始到标识代码块结束的最近的右花括号为止,每次执行时都会重新创建这个变量,当方法执行完后,这个变量都会被销毁。局部变量不会被初始化,所以如果想要局部变量的初始值,就必须在声明它们时提供。
5.8 将参数值传递给方法的过程
- Java中,所有的参数值都使用传值(pass-by-value)机制传递到方法中。
虽然传值机制对于参数类型都一样,但是它对对象的作用与基本类型的变量的作用并不相同,方法能修改作为参数传递的对象。因为类类型的变量包含的是指向对象的引用而不是对象本身,而指向该对象的引用的副本仍然指向同一个对象,所以方法中使用的参数名也指向作为参数传递的原始对象。将对象作为参数传递给方法时,应用的机制也可以理解为传址调用(pass-by-reference)
- final参数:(不懂...)
调用方法时避免任何替代参数的参数值被修改,编译器会检查以确保方法体中的代码不会尝试修改任何final参数
将方法的参数设置为final可以避免传给方法的对象引用被意外修改(即该对象引用不能指向另一个对象),但是无法避免修改对象本身。
5.9 访问方法中的类数据成员
实例方法能够访问类中的任意数据成员(可以使用类变量),类方法不能直接使用实例变量。对象(实例)可以调用静态方法或类变量。
5.10 初始化代码块
- 初始化代码块指位于花括号之间的一段代码,它在类的对象创建之前执行
- 通常不会使用非静态的初始化代码块来初始化静态变量
- 类可以拥有多个初始化代码块,它们会按照顺序执行
- 类加载时会执行静态代码块,然后当创建每个对象时,会执行非静态代码块,然后,当创建每个对象时会执行非静态代码块
5.11 构造函数(constructor)
- 创建对象时,总会调用构造函数。如果没有为类定义构造函数,编译器会提供一个不做任何操作的构造函数(默认构造函数也称无参数(no-arg)构造函数)
ClassName(){
......
}
- 构造函数的目的:对正在创建的对象提供初始化实例变量的方法
- 任何在类中定义的初始化代码块总是会在构造函数之前执行
- 构造函数的特征
- 没有返回值
- 构造函数名与类名相同
- 如果已经为类定义了任意类型的构造函数,那么编译器不会再提供默认的构造函数,如果需要,则需显式的定义
5.12 对象
- 对象的生命周期:取决于程序中是否仍然有任意位置的变量在引用它
- 垃圾回收(garbage collection) System.gc()
处理销毁对象的过程成为垃圾回收,垃圾回收在Java中自动运行,但这并不意味着对象会从内存中直接消失。对象在程序中变得不可访问时,还需要一段时间才能消失,这不会影响程序的运行,仅仅意味着不能信任已销毁对象占有的内存会立即释放。这对于大多数情况而言没有影响,唯一可能影响的情形是涉及的对象非常大(比如达到几百万字节)或是需要不断的销毁和创建大量的对象,这时,可以试图调用定义在System类中的静态的gc()方法,鼓励JVM执行垃圾回收并释放这些对象占用的内存。这对JVM而言是尽力而为的任务,当gc()方法返回时,JVM会试图回收已舍弃的对象的内存,但不能保证全部恢复。也有可能因为调用gc()方法而使情况变的更糟:如垃圾回收正在执行一些恢复内存的准备,调用gc()会取消这种准备,导致速度变慢。
5.13 方法重载
- 在类中以同样的名称定义两个或多个方法的机制。(只要相同的名称定义的每个方法拥有唯一的一套参数即可)
- 方法签名:方法名及参数的类型和顺序构成方法的签名,返回类型和参数的名称对于方法的签名没有影响
- 从构造函数调用构造函数:类的构造函数可以在它的第一条语句中调用同一个类中的另一个构造函数,在引用同一个类中的另一个构造函数时使用this作为方法名。
5.14 使用对象
如果一个类中使用了toString()方法,并且这个类的一个对象被用作字符串连接运算+的一个操作数,那么toString()方法会将这个对象表示成一个字符串,必要时编译器会自动插入对toString()方法的调用。如假设thePoint是一个Point类型的对象,编写如下语句:
System.out.println(“The point is at ”+thePoint);
编译器会自动调用toString()方法,将变量的thePoint引用的对象转换成一个字符串。
一般的,应该注意避免在对象之间创建隐式依赖。
隐式依赖:
5.15 递归
只有当递归有明显优势时才使用它,因为递归方法会产生大量的负载。
5.16 包 (package)
包是一个唯一命名的类的集合
Java中的每个类都包含在一个包中,若没有定义,则隐式的使用默认包(default package)来保存类。而这并没有名称。
java.lang 包中的所有类在程序中总是可用
用于包中的类名不会妨碍另一个包或程序中的类名
5.17 类成员的访问控制(访问属性-access attribute)
对于类,同一个包中的类可以相互访问
对于类中的成员,访问属性如下所示
无访问属性 | 允许同一个包中的任意类的方法访问 |
public | 只要声明为public,就允许任意位置的类方法访问 |
private | 只允许内部的方法访问,完全不允许在类的外部访问 |
protect | 允许同一个包中的任意类的方法访问并且允许任意位置的子类访问 |
5.18、嵌套类(nested class)
可以将一个类的定义放到另一个类的定义中,这个内部类被称为嵌套类。如果需要,嵌套类也可以在其自身内部嵌套另一个类。包围嵌套类的外围类叫做顶级类。
定义嵌套类时,嵌套类与其它类成员一样将作为包围它的类的一个成员。可以与其它类成员一样设置嵌套类的访问属性,并且嵌套类在顶级类外部的可访问性也同样由访问属性决定
*example
public class Outside{
……
public class Inside{
……
}
……
}
Outside outer=new Outside(); //没创建嵌套类对象
Outside.Inside inner=out.new Inside();
六、扩展类与继承
6.1 继承
基类/父类/超类——派生类(基类特殊化)
访问修饰符 | 访问属性 | 继承属性 |
public | 在所有地方可用 | 完全继承 |
protected | 同一个包中的类or不同包中的子类可以访问 | 只要类本身为public,就对继承没有限制 |
默认(无访问属性) | 同一个包可以访问 | 阻止在不同包中的子类继承 |
Private | 限制在一个类中 | 不能继承 |
- 没有访问修饰符的成员通常被称为包私有(package-private)成员
- 继承关注的是哪些成员能在派生类中被访问
- 继承成员(inherited member)派生类内部可以访问的成员
- 派生类对象总是包括一个完整的基类成员,包含所有的非继承的域或方法(不懂......)
6.1.1 继承数据成员
同一个包中:不能继承private成员
不同的包中:不能继承private & 无访问属性 的成员(基类必须为public)
被隐藏的数据成员(同名即覆盖)
派生类中定义了某个与基类同名的数据成员,不管对应的类型或访问类型是否一样
6.1.2 继承方法
与数据成员相似,private类型方法不能被继承,没有声明属性的方法,只有在同一个包中才能被继承。
构造函数不能被继承
在派生类中被继承的基类方法能访问所有的基类成员,包括没有被继承的成员(子类对象包含了原来基类对象的所有成员)
- 方法的覆盖
派生类中,与基类签名相同的方法(即名称和类型的顺序与数目相同)
派生类中的覆盖方法的访问属性可以与基类一样或者比基类更少一些,但是不能更多
6.1.3 派生类的构造函数
派生类的构造函数对基类构造函数调用必须是派生类构造函数方法体中的第一条语句,如果派生类构造函数的第一条语句不是对基类构造函数的调用,编译器就会自动插入一条对基类默认构造函数(no—arg构造函数)的调用。
super();
但是,这条自动插入的语句会导致编译错误。
因为基类中定义构造函数后,就不会再自动创建默认构造函数。
6.1.4 @Override
让编译器检查派生类中的方法的签名与超类中同名方法的签名是否相同
6.2 多态
某给定类型的变量能引用不同类型的对象(不仅仅是引用给定类型的对象),并能自动调用该对象的方法——单个的方法调用会因为引用对象的不同而产生不同的行为
多态的必要条件:
1)对派生类对象起作用 派生类可以由任意直接或间接的基类类型的变量引用。
2)方法必须被声明基类(即正在使用的变量的类型的一个成员),并且必须被声明为所涉及对象所属类型的一个成员。在派生类中,这些方法的任何定义都必须与基类的签名一样,并且必须都有一个不含更多限制的修饰符
对于派生类中非基类成员的方法,不能使用基类类型的变量调用这些方法
派生类方法的返回类型必须与基类中对应方法的返回类型一样或是基类返回类型的子类,如果返回类型不同,但是派生类中方法的返回类型是基类返回类型的子类,那么返回类型被认为是协变的。
*当使用基类变量调用方法时,多态会根据引用的对象类型而不是变量的类型来选择要调用的方法
*基类变量引用的对象类型直到程序执行时才知道。因此,必须在程序运行时动态决定并选择要执行的方法——在编译时无法确定
*多态只应用于实例方法,不能应用于数据成员
6.3 抽象类
public abstract Animal{
public abstract void Voice();
}
抽象类:由关键字abstract定义,这些方法没有定义并且不能被执行。其中声明了一种or多种方法,但是没有定义它们——因为实现这些方法没有任何意义。这些方法的方法体被省略掉。抽象方法的声明以分号结束。
abstract方法不能是private类型,因为private类型不能被继承从而重写
抽象类能引用类型,但是不能申明变量 Animal aAnimal=null;
*当从一个抽象类派生另一个抽象方法时,并不需要在子类中实例化所有的方法。在这种情况下,子类是抽象的并且也不能实例化子类的任何对象
6.4 通用超类 Object
永远不要在类定义中设定Object类为基类——因为这会自动设定
Object类型的变量能存储指向任意类型对象的索引。(当要写一个方法来处理未知类型的对象时,非常有用)
Object类的成员——会被所有的类继承(7个public方法,2个protected方法)
7个public方法:
- toString():完全限定类名+@+当前对象的十六进制表示
- equals():判断当前的对象引用与参数的对象引用是否指向同一个对象
- getClass():返回当前对象所属类的Class类型的对象
- hasCode():为对象计算哈希值并以int类型返回
- notify():唤醒一个与当前对象关联的线程
- notifyAll():唤醒与当前对象关联的所有线程
- wait():导致一个线程释放当前对象的锁,直到其它线程调用当前对象的notify()或notifyAll()方法唤醒当前线程
注意:getClass(),notify(),notifyAll()和wait()都无法被覆盖——它们在Object类定义中被final关键字固定
2个protected方法:
- clone():无论当前对象的类型为何,均创建当前对象的一个副本对象(当一个对象实现了cloneable接口时,便可以被克隆)
- finalize():当对象被销毁时,这个方法可以用来进行清理工作
6.5 判断对象的类型
假定一个Animal类型的变量pet,它可能包括一个指向Dog,Cat,Duck甚至Spaniel类型的对象的引用。为了弄清它实际指向的内容
1)可以编写如下语句:
Class objectType=pet.getClass();
System.out.println(objectType.getName()); //Class类的一个成员,它返回Class 对象类的完全限定名称(com.dave.Dog)
亦可以
System.out.println(pet.getClass().getName());
2)程序在执行时,Class类的一些实例表示程序中的每个类和接口。程序中的每个数组类型和每个基本类型都对应一个Class对象。当程序加载时,JVM会生成这些内容,因为Class主要由JVM使用,所以它没有构造函数。因此不能自行创建Class类型的对象。
在任何类、接口或基本类型的名称后附加.class就会获得对应的Class对象的引用。每个类型和接口都只有一个Class对象。
if(pet.getClass()=Duck.Class){
System.out.println(“By Goerge – it is a Duck”);
}
3)instanceof():检验对象是否为所期望的类型
if(pet instanceof Duck){
Duck aDuck=(Duck)pet;
aDuck.layEgg();
}
6.6 接受可变数目参数的方法 Object … args
- Object … args参数必须是最后一个参数,在这个参数之前,可以由零个或多个参数。Object类型名与args参数名之间的省略号允许编译器决定这个参数列表是可变的。参数args代表Object[]类型的数组,而参数值是Object类型中可用的元素。
public class TryVariableArgumentList {
public static void main(String[] args) {
printAll(2,"two",4,"four",5,"five",4.5,"four point five");
printAll();
printAll(25,"Anything goes",true,4E4,false);
}
public static void printAll(Object ... args){
for(Object arg:args){
System.out.print(arg+" ");
}
System.out.println();
}
}
- 限制可变参数列表中的类型
可以将可变类型参数列表限制为任意类类型或接口类型
public static void average(Double … args){
}
6.7 转换对象
向上转换:①使用多态时执行方法,存储各种不同的基类对象并调用派生类的方法
②将参数设定为基类类型以试用任何派生类型参数
向下转换:执行某个类特用的方法(向下转换需要显式转换)
*对象必须是要转换的类的合法实例——要转换掉的类必须是对象的实际类型或是对象的超类。
将对象转换成超类时,Java会自动保留对象所属类型的实际类的所有信息。否则,就无法实现多态。因为关于对象的原始类型的信息已被保留,所以能够在层次结构中向下转换
6.8 枚举进阶
- 枚举类型是一种特殊形式的类,这个类以java.lang包中的Enum类作为超类
- 可以将枚举类型定义在一个单独的源文件中
- 表示枚举常量的对象也存储一个整数域。默认的,枚举中的每个常量都被赋予一个与其它常量不同的值,由零开始,依次后推。可通过调用枚举常量的ordinal()方法获得它的整数值
- 枚举类中同样也有默认的构造函数,每次枚举常量的出现都会导致对枚举类默认构造函数的调用。若已显式的定义构造函数,则不能再调用默认的无参构造函数
- 不能将枚举中的构造函数声明为public。如果这样做,enum类定义不会被编译。唯一允许定义为枚举的类的构造函数应用的修饰符是private,这导致只能在类的内部调用构造函数
*example
public enum JacketSize {
small(36),medium(40),large(42),extra_large(46),extra_extra_large(48);
//构造函数,只能定义为private属性或不定义
JacketSize(int chestSize){
this.chestSize=chestSize;
}
public int chestSize(){
return chestSize;
}
@Override
public String toString(){
switch(this){
case small:return "s";
case medium:return "M";
case large:return "L";
case extra_large:return "XL";
default: return "XXL";
}
}
private int chestSize;
}
6.9 接口 interface
在类中实现接口:implements
接口的理解
- 接口在本质上是一系列相关的常量和(或)抽象方法的集合,大多数情况下只包含方法
- 接口的主要作用是定义一系列表示某个特定功能的方法的表面形式(在接口中声明的方法都必须在实现接口的类中有一个定义)
- 接口的另一个重要作用是:接口的引用可以用来指向其实现类的对象——可以通过实现同一个接口的类来实现多态
- 无法创建接口类型的对象,但是能够创建接口类型的变量(用于引用子类对象)
- 一个类可以实现多个接口,在implements关键字后面用逗号将接口分开(类不支持多重继承,只有接口支持)
- 一个类可以实现部分接口,这时的类则为abstract类型(抽象类)
- 扩展接口:extends关键字 超接口 超类
interface A extends B,C{
}
接口中的方法:默认总是public和abstract访问属性,所以不用为它们设定——实际上,为它们设定任何访问属性都被认为是不好的编程习惯。决不能添加除默认属性(默认就是public和abstract)、public和abstract之外的任何属性——在接口中不能声明static方法。
接口中的常量:在接口中定义的常量被自动设置为public、final、static类型——对此没有选择。因为常量是final和static类型,所以必须为接口定义的常量提供初始化值(常量名按编程习惯最好大写)。当实用类实现接口时,在接口中定义的任何常量都会在类中直接使用。可以用与访问类中public和static域一样的方法来访问定义在接口中的常量——需要再常量的前面加上接口名称进行限定。
在实现接口的类中访问接口中的常量的方法
1)使用数据成员的限定名称从外部访问。
2)将类的静态成员导入到任何需要使用这些常量的类中,这允许常量通过非限定名称引用。(在这种情况下,MyClass类必须是有名称的包,因为import不能使用无名称的包)
import static com.dave.Coversion.*;
接口中声明的方法
1)因为接口中的方法按照定义都是public类型,所以在自己的类中定义它们时都必须使用public,否则无法编译。
2)接口中声明的方法所需的常量可在接口中一并声明,若在该接口的类中实现则显得有点笨拙。
实现接口的类
1)如果两个或更多的接口声明了具有相同签名的方法(即名称和参数的类型与顺序都完全一样),而这些接口要在同一个类中得到实现。那么这个方法必须在声明他的所有接口中都返回相同的类型,如果不这样做,编译器就会报错。因为类中的方法必须有唯一的签名,而返回类型不是其中的一部分,在这个原则下,如果接口的返回类型不一样,那么在同一个类中就无法实现两个接口中的方法。
2)使用多个接口
public MyClass implements RemoteControl,AbsoluteControl{
}
如果某个类实现了多个接口,则每个接口类型的变量引用只能调用自己的方法,若要调用其它接口中的方法,则需将其转换为另一个接口类型。
AbsoluteControl ac=new MyClass();
((RemoteControl)ac).remoteMethord();
注:虽然RemoteControl接口和AbsoluteControl接口不相关,也能将ac的引用转化成RemoteControl类型。之所以能够完成,是因为ac引用的对象时MyClass类型,而MyClass恰好实现了这两个接口,所以也就合并了这两个接口类型。
*也可以将其转化为原始类型 (MyClass)ac
转化后的原始类型变量能调用所有接口的方法,但是无法获得多态行为。
6.10 将接口类型作为参数使用
对于任意类型的对象来说,只要对象所属的类实现了该接口,指向类对象的引用就可以作为参数传入相应的接口类型的参数中。
标准类库的方法通常都有接口类型的参数。String、StringBuffer、StringBuilder和CharBuffer都实现了CharSquence接口,而大量的类方法都有一个CharSquence类型的参数,在这种情况下,这些方法都能够使用上述任意类型的变量作为参数。StringBuffer和StringBuilder都有接口CharSquence类型的参数的构造函数,因此能够从实现了CharSquence接口的任意类型对象来创建这两个类类型的新对象。
6.11 匿名类
在有些情况下,定义类只是为了在程序中定义一个对象,而且该对象的唯一用途就是作为方法的参数,在这种情况下,只要类扩展了一个已有的类或者实现了一个接口,就可以将其定义为匿名类。匿名类即不用为类提供名称
pickButton.addActionListener(new ActionListener(){
......
});
七、异常
7.1 异常的基本概述
Java中的异常是指当程序发生非正常情况时创建的一个对象,异常可以由JVM、标准类库的方法或应用程序代码创建,异常对象有一些域用来存储有关问题本质的一些信息。异常被称为抛出(throw),即标识意外情况的对象被作为参数传给一段特殊的、编写用来处理对应类型问题的程序代码。将异常对象作为参数接受称为捕获(catch)。
1)异常通常用来指示错误,它还能指示程序中值得关注的一些不寻常的事件
异常指示错误的好处:可以将处理错误的代码与正常执行的代码区分开来
2)异常的另一个积极面:提供了一种强制的对特定错误进行相应的方式。有很多种异常,它们必须在程序中包含对应的处理代码。否则,代码就不会被编译
3)处理异常涉及很多的处理负载。因此,并不是程序中所有的错误都需要被异常指示,异常应该保留给可能出现的或灾难性的情况。
7.2 异常类型 Throwable的子类
异常总是标准类Throwable某个子类的对象。包括:
1)自己定义和抛出的异常
2)Java标准包中定义的标准异常
3)其它标准包中方法抛出的异常
- 异常的两个子类
1)Error:包括ThreadDeath、LinkageError、VisualMachineError等,属于不受检异常(unChecked Exception)。因为通过操作从这些类型的错误中进行恢复的可能性很小。希望做到的事(如果的确很幸运的话):读到由抛出的异常生成的错误消息,并且之后尝试找到导致异常问题的代码。
2)Excepiton:几乎所有的Exception类型的异常都是受检异常。所以如果代码可能导致它们被抛出,就必须在程序包中包括代码来处理它们,如果不这么做,程序不会编译。处理方法有两种:
①在方法内部处理异常 try-catch-finally代码块
②声明抛出异常 throws
- 特例:RuntimeException:属于非受检异常
- 由Java类库中方法抛出的异常都是受检异常
7.3 try -catch –finally概述
- 能导致异常的代码不一定要放在try代码块中,但这些代码的方法将不能捕获抛出的任何异常,并且方法必须声明能抛出没有捕获的异常类型,否则将不能被编译。
- 如果想要捕获Error或Runtime类型的异常,就必须使用try代码块
- try代码块不能单独存在,必须至少包含一个catch或finally代码块,并且try代码块和catch代码块以及catch代码块和finally代码块之间不能包含任何的其它代码。
- try catch代码块内部声明的变量只在该代码块内部有效。如果想要在catch代码块输出有关try代码块中设置的对象或值的相关信息,就一定要确保这些变量在一个外部作用域中被声明。
- finally代码块:
- 提供了一些方法以便在执行到try代码块的最后是进行一些清理工作(finally代码块总是会执行),如关闭文件或者发布关键资源等。
- try-finally代码块的另一作用:
- try代码块的主要目的是标识可能导致异常抛出的代码。但是也有可能包含不包含异常抛出的代码以便使用finally代码块。当try代码块中代码有很多肯能的跳出点时,例如break或return语句,这会很有用。注意如果在finally代码块中返回一个值,那么这个返回值会覆盖try代码块中执行的任何其它返回语句。
- 将捕获的异常传给调用程序
try{
}catch(ArithmeticException e){
throw e;
}
7.4 定义自己的异常
- 异常最好继承自Exception类,这允许编译器在程序中跟踪这些异常抛出的位置并且检查它们是否被捕获或者方法声明为抛出
- 根据约定,异常类应该包括一个默认的构造函数以及一个接受String对象作为参数的构造函数
7.5 throws与throw的理解
- throws是用来声明一个方法可能抛出的所有异常信息
- throw则是指抛出的一个具体的异常类型
- 通常在一个方法(类)的声明处通过throws声明方法(类)可能抛出的异常信息,而在方法(类)内部通过throw声明一个具体的异常信息
- throws通常不用显示的捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法
- throw则需要用户自己捕获相关的异常,而后在对其进行相关包装,最后在将包装后的异常信息抛出