Java 语言是 SUN (Stanford University Network) 公司于1995年推出的一门高级编程语言,舍弃了 C 语言中容易引起错误的指针(以引用取代)、运算符重载(operator overloading)、多重继承 (以接口取代)等特性,增加了垃圾回收器等功能,具有面向对象、健壮性、跨平台性等特性。我选择的 Java课程为尚学堂 Java 零基础入门教程,但由于我个人不是零基础入门,所以这篇笔记不含编程入门和语言概述,仅包含 Java 基础编程知识的总结。
运行机制
Java 虚拟机
- JVM 是一个虚拟的计算机,具有指令集并使用不同的存储区域,负责执行指令,管理数据、内存、寄存器
- 对于不同的平台,有不同的虚拟机,只有某平台提供了对应的 Java 虚拟机,Java 程序才可在此平台运行
- Java 虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”
垃圾回收
- Java 语言消除了程序员回收无用内存空间的责任:提供一种系统级线程跟踪存储空间的分配情况,并在 JVM 空闲时,检查并释放那些可被释放的存储空间
- 垃圾回收在 Java 程序运行过程中自动进行,程序员无法精确控制和干预
JRE 与 JDK
- Java Development Kit 是提供给 Java 开发人员使用的 Java 开发工具包,其中包含了 Java 的开发工具,也包括了 JRE
- Java Runtime Environment 包括 Java 虚拟机(Java Virtual Machine)和 Java 程序所需的核心类库等,如果想要运行一个开发好的 Java 程序,计算机中只需要安装 JRE 即可
- JDK = JRE + 开发工具集(例如 Javac 编译工具等),JRE = JVM + Java SE 标准类库
通过 javac 命令编译 Java 文件
通过编译器将 Java 源文件编译成 JVM 可以识别的字节码文件
1
javac Test.java //此时有了可执行的 java 程序
通过 java 命令运行生成的 class 文件
1
java Test //通过运行工具 java.exe 对字节码文件进行执行
一个源文件可以声明多个 class,但最多只能有一个 public 类,而且 public 类名必须与文件名相同
注释
被注释的文字不会被 JVM 解释执行,多行注释里面不允许有多行注释嵌套
1
2//单行注释
/* 多行注释/*文档注释
1
2
3
4/**
* @author java程序的作者**
* @version 源文件的版本**
*/注释内容可以被 JDK 提供的 javadoc 所解析,生成该程序的说明文档
命名规范
- 标识符(Identifier)不可以数字开头,不能包含空格,严格区分大小写,长度无限制
- 包名:所有字母都小写
- 类名、接口名:所有单词的首字母大写
- 变量名、方法名:第一个单词首字母小写,第二个单词开始每个单词首字母大写
- 常量名:所有字母都大写,每个单词用下划线连接
变量
- 变量是程序中最基本的存储单元,包含变量类型、变量名和存储的值
- 先声明,后使用:
<数据类型> <变量名称>
- 赋值:
<变量名称> = <值>
,<数据类型><变量名>= <初始化值>
- 成员变量:在类体内,方法体外声明的变量
- 以 static 修饰的是类变量,不以 static 修饰的是实例变量
- 局部变量:在方法体内部声明的变量
- 方法、构造器中定义的变量成为实参
- 局部变量除形参外,都需显式初始化
- 基本数据类型
- 整型:byte \ short \ int \ long(整型常量默认为 int 型,声明 long 型常量须后加 l 或 L)
- 浮点型:float \ double(浮点型常量默认为双精度,声明 float 型常量须后加 f 或 F)
- 字符型:char(使用 Unicode 编码,
‘\uXXXX’
) - 布尔型:boolean(true 用1表示,false 用0表示)
- 引用数据类型
- 类:class
- 接口:interface
- 字符串:String(
“aaaa”
) - 数组:array
- 数据类型转换
- byte、short、char之间不会相互转换,在计算时首先转换为 int 类型
- boolean 类型不能与其它数据类型运算,不可以转换为其它的数据类型
- 任何基本数据类型的值和 String 进行连接运算时,基本数据类型的值将自动转化为 String 类型
- 强制转换符:
(byte)(a+b)
- 通过基本类型对应的包装类则可以实现把字符串转换成基本类型:
String a = “43”; inti= Integer.parseInt(a);
运算符
- 算术运算符
- 赋值运算符
- 当
=
两侧数据类型不一致时,可以使用自动类型转换或使用强制类型转换原则进行处理 - 支持连续赋值
- 扩展赋值运算符:
+=, -=, *=, /=, %=
- 当
- 比较运算符(关系运算符)
- 比较运算符的结果都是 boolean 型
- 逻辑运算符
&
:逻辑与,|
:逻辑或,!
:逻辑非&&
:短路与,||
:短路或,^
:逻辑异或- 单
&
时,左边无论真假右边都进行运算;双&
时,如果左边为假,那么右边不参与运算 ||
时:当左边为真,右边不参与运算
- 位运算符
- 位运算符操作的都是整型的数据变量
<<
: 在一定范围内,每向左移一位,相当于\* 2
>>
: 在一定范围内,每向右移一位,相当于/ 2
>>>
:无符号右移&
:与运算,|
:或运算,^
:异或运算,~
:取反运算
- 三元运算符
(条件)?a : b
- 条件表达式的结果为 boolean 类型
- 三元运算符是可以嵌套的
- 只有单目运算符、三元运算符、赋值运算符是从右向左运算的
流程控制
分支结构
if-else
条件表达式- 必须是布尔表达式(关系表达式或逻辑表达式)、布尔变量
1
2
3
4
5
6
7
8
9
10
11if(条件表达式){
执行表达式1
}else if{
执行表达式2
}else if(条件表达式){
执行表达式3
}
...
else{
执行表达式n
}输入语句
- 导包:
import java.util.Scanner;
- Scanner 的实例化
- 调用 Scanner 类的相关方法来获取指定的变量
1
2
3
4
5
6
7
8
9import java.util.Scanner;
class ScannerTest{
public static void main(String[] args){
Scanner scan = new Scanner(System.in); //声明一个Scanner
int num = scan.nextInt(); //获取变量
System.out.println(num);
}
}- 导包:
switch-case
结构- switch 结构中的表达式只能是如下的六种数据类型:
byte
、short
、char
、int
、枚举类型
(JDK5.0)、String类型
(JDK7.0) - case 之后只能声明常量,不能声明范围
1
2
3
4
5
6
7
8
9
10switch(表达式){
case 常量1:
执行语句1;
//break; //break关键字是可选的
case 常量2:
执行语句2;
...
default:
执行语句n:
}- switch 结构中的表达式只能是如下的六种数据类型:
循环结构
for 循环
- 初始化部分可以声明多个变量,但必须是同一个类型,用逗号分隔
- 可以有多个变量更新,用逗号分隔
1
2
3for(初始化; 循环条件; 迭代){
循环体;
}while 循环
- for 循环和 while 循环可以相互转换
1
2
3
4
5初始化
while(循环条件){
循环体;
迭代;
}do-while 循环
- 至少执行一次循环体
1
2
3
4
5初始化
do{
循环体;
迭代;
}while(循环条件);for
、while
、do…while
均可以作为外层循环或内层循环break
用于终止某个语句块的执行,continue
用于跳过其所在循环语句块的一次执行,继续下一次循环break
只能用于switch
语句和循环语句中,continue
只能用于循环语句中continue
出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环
- 与
break
和continue
不同的是,return
直接结束整个方法
数组
数组属于引用类型的变量,数组的元素既可以是基本数据类型,也可以是引用数据类型
创建数组对象会在内存中开辟一整块连续的空间,数组的长度一旦确定就不能修改
数组是有序排列的
1
2
3
4
5
6
7
8
9
10
11
12//静态初始化:数组的初始化和数组元素的赋值操作同时进行
ids = new int[]{1001,1002,1003,1004};
//动态初始化:数组的初始化和数组元素的赋值操作分开进行
String[] names = new String[5];
//通过角标的方式调用数组指定位置的元素
names[0] = "张学良"; //如果数组超过角标会通过编译,运行失败
//获取数组的长度
Syst遍历数组em.out.println(names.length);
//遍历数组
for(int i = 0;i < names.length;i++){
System.out.println(names[i]);
}
二维数组
从数组底层的运行机制来看,其实没有多维数组,可以看作 array1 作为 array2 的元素而存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//静态初始化
int[][] arr1 = new int[][]{{1,2,3},{4,5,6},{7,8,9}};
//动态初始化
String[][] arr2 = new String[3][];
//调用数组的指定位置的元素
System.out.println(arr1[0][1]);
//获取数组的长度
System.out.println(arr4.length);
System.out.println(arr4[0].length);
//遍历二维数组
for(int i = 0;i < arr4.length;i++){
for(int j = 0;j < arr4[i].length;j++){
System.out.print(arr4[i][j] + " ");
}
System.out.println();
}
类和对象
类与对象的创建
属性:类中的成员变量,行为:类中的成员方法
Field = 属性 = 成员变量 = 域、字段,Method = 成员方法 = 函数
创建类 = 类的实例化 = 实例化类
如果创建类一个类的多个对象,则每个对象都独立的拥有一套非 static 的类的属性
所有的对象实例以及数组都要在堆(Heap)上分配:此内存区域的唯一目的就是存放对象实例
虚拟机栈(Stack)用于存储局部变量:局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于对象本身,是对象在堆内存的首地址),方法执行完,自动释放
方法区(MethodArea)用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
1
2
3
4
5
6
7
8
9//类的正文要用{ }括起来
修饰符 class 类名{ //修饰符为 public:类可以被任意访问
属性声明;
方法()声明;
}
//调用属性
对象.属性;
//调用方法
对象.方法();
类中属性的使用
- 属性(成员变量)和局部变量
- 属性直接定义在类的一对
{}
内 。局部变量声明在方法内、方法形参、构造器形参、构造器内部 - 在声明属性时可以使用权限修饰符指明其权限(private、public、缺省、protected),局部变量不可以使用权限修饰符
- 类的属性都有默认初始化值,局部变量没有默认初始化值,在调用局部变量之前,一定要显式赋值(形参在调用时,赋值即可)
- 属性加载到堆空间中(非 static),局部变量加载到栈空间
- 属性直接定义在类的一对
类中方法的使用
Math类:
sqrt()、random()
Scanner类:
nextXxx()
Arrays类:
sort()、binarySearch()、toString()、equals()
1
2
3
4//方法的声明
权限修饰符 返回值类型 方法名(形参列表){
方法体
}如果方法有返回值,则必须在方法声明时指定返回值的类型,在方法中使用 return 关键字来返回指定类型的变量或常量
匿名对象的使用
- 创建对象时,没有显式的赋值给一个变量名,即为匿名对象
- 匿名对象只能调用一次,换行直接销毁匿名对象
方法重载(overload)
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可
判断是否重载与方法的返回值类型、权限修饰符、形参变量名、方法体都无关
1
2
3
4//与 void show(int a,char b,double c){} 构成重载
int show(int a,double c,char b){}
void show(int a,double c,char b){}
//void show(int a,double c,char b){} 不与上方构成重载,因为参数类型相同
可变个数的形参
JavaSE 5.0 中提供了 Varargs (variable number of arguments) 机制,允许直接定义能和多个实参相匹配的形参,从而可以用一种更简单的方式来传递个数可变的实参
可变个数形参的格式:
数据类型 ... 变量名
可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载。
可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载(二者不可共存)
可变个数形参在方法中的形参中,必须声明在末尾
可变个数形参在方法中的形参中,最多只能声明一个可变形参
1
2
3
4
5
6public void show(String... strs) {
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
}
//public void show(String[] strs){} 与上一方法不可共存
方法参数的值传递机制
如果变量是基本数据类型,此时实参赋值给形参的是实参真是存储的数据值
如果变量是引用数据类型,此时实参赋值给形参的是实参存储数据的地址值
1
2
3
4
5
6
7
8public class ArrayPrint {
public static void main(String[] args) {
int[] arr = new int[]{1,2,3}; //传进去的是一个Object的对象
System.out.println(arr); //地址值
char[] arr1 = new char[]{'a','b','c'}; //传进去的是一个数组,里面遍历数据了
System.out.println(arr1); //abc
}
}
递归(recursion)方法
一个方法体内调用它自身
方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制
递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环
属性赋值的过程
- 默认初始化值
- 显式初始化
- 构造器中赋值
- 通过
对象.方法
或对象.属性
的方式赋值
封装与隐藏
高内聚:类的内部数据操作细节自己完成,不允许外部干涉
低耦合:仅对外暴露少量的方法用于使用
将类的属性私有化(private),同时提供 public 方法来 get 和 set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42public class AnimalTest {
public static void main(String[] args) {
Animal a = new Animal();
a.name = "大黄";
a.show();
a.setLegs(-6);
a.show();
System.out.println(a.name);
System.out.println(a.getLegs());
}
}
//类的设置
class Animal{
String name;
private int age;
private int legs;
//对于属性的设置
public void setLegs(int l){
if(l >= 0 && l % 2 == 0){
legs = l;
}else{
legs = 0;
}
}
//对于属性的获取
public int getLegs(){
return legs;
}
public void eat(){
System.out.println("动物进食");
}
public void show(){
System.out.println("name = " + name + ",age = " + age + ",legs = " + legs);
}
//提供关于属性 age 的 get 和 set 方法
public int getAge(){
return age;
}
public void setAge(int a){
age = a;
}
}权限修饰符
public、protected、default(缺省)、private
置于类的成员定义前,用来限定对象对该类成员的访问权限- 对于 class 的权限修饰只可以用 public 和 default
- public 类可以在任意地方被访问,default 类只可以被同一个包内部的类访问
构造器(constructor)
构造器的作用:初始化类的对象(属性),并返回对象的地址
如果没有显示的定义类的构造器的话,则系统默认提供一个空参的构造器
无参数构造器:初始化的对象时,成员变量的数据均采用默认值
有参数构造器:在初始化对象的时候,同时可以为对象进行赋值
- 一旦定义了有参数构造器,无参数构造器就没有了,此时就需要自己写无参数构造器了
一个类中至少会有一个构造器,一个类中若定义多个构造器,则彼此构成重载
1
2权限修饰符 类名(形参列表) { }
//创建类的对象:new + 构造器
JavaBean 的使用
JavaBean 是一种 Java 语言写成的可重用组件
JavaBean 符合如下标准:类是公共的、有一个无参的公共的构造器 、有属性,且有对应的 get、set 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Customer {
private int id;
private String name;
public Customer(){
}
public void setId(int i){
id = i;
}
public int getId(){
return id;
}
public void setName(String n){
name = n;
}
public String getName(){
return name;
}
}
UML 类图
+
表示 public 类型,-
表示 private 类型,#
表示 protected 类型
this 关键字
- this 用来修饰和调用属性、方法、构造器
- 在类的方法中,可以使用
this.属性
或this.方法
调用当前对象属性和方法(通常情况下选择省略),特殊情况下,如果方法的形参和类的属性同名必须显式使用this.变量
,表明此变量是属性而非形参 - 在类的构造器中,可以使用
this.属性
或this.方法
调用当前对象属性和方法(通常情况下选择省略),特殊情况下,如果构造器的形参和类的属性同名必须显式使用this.变量
,表明此变量是属性而非形参 - this 调用构造器
- 可以在类的构造器中,显式使用
this(形参列表)
调用本类中重载的其他的构造器 - 构造器中不能通过
this(形参列表)
的方式调用自己。 - 如果一个类中声明了 n 个构造器,则最多有 n -1 个构造器中使用了
this(形参列表)
this(形参列表)
必须声明在类的构造器的首行- 在类的一个构造器中,最多只能声明一个
this(形参列表)"
- 可以在类的构造器中,显式使用
package 关键字
为了更好的实现项目中类的管理,提供包的概念
使用 package 声明类或接口所属的包,声明在源文件的首行
每
“.”
一次就代表一层文件目录。同一个包下,不能命名同名接口或同名类;不同包下,可以命名同名的接口、类
1
2
3
4
5
6
7java.lang //包含一些 Java 语言的核心类,如 String、Math、Integer、System 和 Thread,提供常用功能
java.net //包含执行与网络相关的操作的类和接口
java.io //包含能提供多种输入/输出功能的类
java.util //包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数
java.text //包含了一些 java 格式化相关的类
java.sql //包含了 java 进行 JDBC 数据库编程的相关类/接口
java.awt //包含了构成抽象窗口工具集(abstractwindowtoolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。B/S C/S
MVC 设计模式
MVC 是常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,数据模型层
这种将程序输入输出、数据处理以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性
模型层 model 主要处理数据
- 数据对象封装 model bean/domain
- 数据库操作类 model.dao
- 数据库 model. db
控制层 controller 处理业务逻辑
- 应用界面相关 controller.activity
- 存放 fragment/controller.fragment
- 显示列表的适配器 controller.adapter
- 服务相关的 controller.service
- 抽取的基类 controller.base
视图层 vieW 显示数据
- 相关工具类 view.utils
- 自定义 vIew/view.ul
import 关键字
- 在源文件中显式的使用 import 结构导入指定包下的类、接口,声明在包的声明和类的声明之间
- 如果导入的类或接口是
java.lang
包下的,或者是当前包下的,则可以省略此 import 语句- 如果在代码中使用不同包下的同名的类,需要使用类的全类名的方式指明调用的是哪个类
- 已经导入
java.a
包下的类时,如果需要使用 a 包的子包下的类的话,仍然需要导 - import static:调用指定类或接口下的静态的属性或方法
继承性
一旦子类 A 继承父类以后,子类 A 中就获取了父类 B 中声明的结构(属性、方法)
如果父类中声明为 private 的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构,只有因为封装性的影响,使得子类不能直接调用父类的结构而已
子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的拓展
子类和父类的关系不同于子集与集合的关系
1
2
3class A extends B{}
//A:子类、派生类、subclass
//B:父类、超类、基类、superclass
Java 中关于继承性的规定
- 一个类可以被多个类继承
- 类的单继承性:一个类只能有一个父类
- 子父类是相对的概念。
- 子类直接继承的父类,称为直接父类。间接继承的父类,称为间接父类。
- 子类继承父类后,就获取了直接父类以及所有间接父类中声明的属性和方法
- 如果没有显式的声明一个类的父类的话,则此类继承于
java.lang.Object
类 - 所有的 java 类具有
java.lang.Object
类声明的功能(直接或间接继承)
方法的重写(override/overwrite)
重写:子类继承父类以后,可以对父类中的方法进行覆盖操作
重写以后,当创建子类对象以后,通过子类对象去调用子父类中同名同参数方法时,执行的是子类重写父类的方法(即在程序执行时,子类的方法将覆盖父类的方法)
1
2
3权限修饰符 返回值类型 方法名(形参列表){
//方法体
}类重写的方法的方法名和形参列表必须和父类被重写的方法的方法名、形参列表相同
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
子类不能重写父类中声明为 private 权限的方法
子类方法抛出的异常不能大于父类被重写的方法抛出的异常
子类与父类中同名同参数的方法必须同时声明为非 static 的(即为重写),或者同时声明为 static 的(不是重写),因为 static 方法是属于类的,子类无法覆盖父类的方法
方法的重载与重写
- 方法的重写 Overriding 和重载 Overloading 是 Java 多态性的不同表现
- 重写 Overriding 是父类与子类之间多态性的一种表现,重载 Overloading 是一个类中多态性的一种表现
- 如果在子类中定义某方法与其父类有相同的名称和参数,该方法被重写 Overriding
- 子类的对象使用这个方法时,将调用子类中的定义,对它而言父类中的定义被屏蔽了
如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载 Overloading
返回值类型
- 父类被重写的方法的返回值类型是 void,则子类重写的方法的返回值类型只能是 void
- 父类被重写的方法的返回值类型是 A 类型,则子类重写的方法的返回值类型可以是 A 类或 A 类的子类
- 父类被重写的方法的返回值类型如果是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型
super 关键字
- super:父类的,可以用来调用属性、方法、构造器
- 可以在子类的方法或构造器中,通过
super.属性
或super.方法
显式的调用父类中声明的属性或方法(通常情况下省略这个) - 当子类和父类中定义了同名的属性时,要想在子类中调用父类中声明的属性,则必须显式的使用
super.属性
表明调用的是父类中声明的属性 - 当子类重写了父类中的方法后,想在子类的方法中调用父类中被重写的方法时,必须显式的使用
super.方法
表明调用的是父类中被重写的方法 - super 调用构造器
- 可以在子类的构造器中显式的使用
super(形参列表)
调用父类中声明的指定的构造器 super(形参列表)
的使用必须声明在子类构造器的首行- 在类的构造器中,针对于
this(形参列表)
或super(形参列表)
只能二选一,不能同时出现 - 在构造器的首行,既没有显式的声明
this(形参列表)
或super(形参列表)
则默认的调用的是父类中的空参构造器super()
- 在类的多个构造器中,至少有一个类的构造器使用了
super(形参列表)
调用父类中的构造器
- 可以在子类的构造器中显式的使用
子类对象实例化过程
- 从结果上看
- 子类继承父类以后,就获取了父类中声明的属性或方法
- 创建子类的对象中,在堆空间中,就会加载所有父类中声明的属性
- 从过程上看
- 通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类构造器, 直到调用了
java.lang.Object
类中空参的构造器为止 - 正因为加载过所有的父类结构,所以才可以看到内存中有父类中的结构,子类对象可以考虑进行调用
- 虽然创建子类对象时调用了父类的构造器,但自始至终就创建过一个对象,即为
new
的子类对象
多态性
- 父类的引用指向子类的对象(或子类的对象赋值给父类的引用)
- 多态的使用:当调用子父类同名同参数方法时,实际调用的是子类重写父类的方法(虚拟方法调用)
- 有了对象多态性以后,在编译期,只能调用父类声明的方法,但在执行期实际执行的是子类重写父类的方法
- 编译时,看左边,看的是父类的引用(父类中不具备子类特有的方法)
- 运行时,看右边,看的是子类的对象(实际运行的是子类重写父类的方法)
- 多态性的使用前提:类的继承关系、方法的重写
- 对象的多态性只适用于方法,不适用于属性(编译和运行都看左边)
- 多态是运行时行为
虚拟方法
重载是指允许存在多个同名方法,而这些方法的参数不同,编译器根据方法不同的参数表,对同名方法的名称做修饰
对于编译器而言,这些同名方法就成了不同的方法,它们的调用地址在编译期就绑定了
Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为早绑定或静态绑定
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为晚绑定或动态绑定
不要犯傻,如果它不是晚绑定,它就不是多态。—— Bruce Eckel
向下转型
使用强制类型转换符调用子类所特有的属性和方法
可能出现 ClassCastException 异常
进行向下转型之前,先进行
instanceof
的判断,返回 true 就进行向下转型,返回 false 不进行向下转型1
2
3
4
5if (p2 instanceof Man) {
Man m2 = (Man) p2;
m2.earnMoney();
System.out.println("Man");
}
Object 类的使用
Object 类是所有 Java 类的根父类
如果在类的声明中未使用 extends 关键字指明其父类,则默认父类为
java.lang.Object
类- Object 类中的功能(属性、方法)具有通用性
- 方法:
equals()、toString()、getClass()、hashCode()、clone()、finalize()、wait() 、notify()、notifyAll()
- Object类只声明了一个空参的构造器
- 方法:
final、finally、finalize的区别
- 被 final 修饰符(关键字)修饰的类,不能再派生出新的子类,不能作为父类而被子类继承
- 一个类不能既被 abstract 声明,又被 final 声明
- 将变量或方法声明为 final,可以保证他们在使用的过程中不被修改
- 被声明为 final 的变量必须在声明时给出变量的初始值,而在以后的引用中只能读取
- 被 final 声明的方法也同样只能使用,即不能被子类重写
- final 修饰局部变量(尤其是使用 final 修饰形参)时,表明此形参是一个常量,当我们调用此方法时,给常量形参赋一个实参,一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值
- static final 用来修饰全局常量
- finally 是在异常处理时提供 finally 块来执行任何清除操作,不管有没有异常被抛出、捕获,finally块都会被执行
- try 块中的内容是在无异常时执行到结束
- catch 块中的内容是在 try 块内容发生 catch 所声明的异常时跳转到 catch 块中执行
- finally 块则是无论异常是否发生都会执行 finally 块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在 finally 块中
- Java技术允许使用
finalize()
方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作- 这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的
- 它是在 object 类中定义的,因此所有的类都继承了它,子类覆盖
finalize()
方法以整理系统资源或者被执行其他清理工作 finalize()
方法是在垃圾收集器删除对象之前对这个对象调用的
== 操作符与 equals 方法
== 运算符可以使用在基本数据类型变量和引用数据类型变量中
- 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等(类型不一定要相同)
- 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同(两个引用是否指向同一个对象实体)
equals()
是一个方法,而非运算符,只能适用于引用数据类型1
2
3public boolean equals(Object obj){
return (this == obj);
} //Object 类中 equals() 的定义,和 == 的作用相同重写 equals() 方法
- 对称性、自反性、传递性、一致性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Customer other = (Customer) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
} //重写原则,比较两个对象的实体内容(name和age)是否相同
toString()
输出一个引用对象时,实际上就是调用当前对象的
toString()
自定义类如果重写
toString()
方法:当调用此方法时,返回对象的实体内容1
2
3public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
包装类(Wrapper)
JUnit 单元测试
- 选中当前项目工程 -> 右键 build path-> add libraries-> JUnit 4
- 创建一个 Java 类进行单元测试。
- 此时的Java类要求:此类是公共的、此类提供一个公共的无参构造器、此类中声明单元测试方法
- 此时的单元测试方法:方法的权限是 public,没有返回值,没有形参
- 此单元测试方法上需要声明注解:
@Test
并在单元测试类中调用:import org.junit.Test;
- 声明好单元测试方法以后,就可以在方法体内测试代码
- 写好代码后 -> 双击单元测试方法名 -> 右键 run as -> JUnit Test
- 如果执行结果无错误,则显示是一个绿色进度条,反之,错误即为红色进度条
包装类的使用
- Java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
- byte -> Byte,short -> Short,int -> Integer,long -> Long,float -> Float,double -> Double,boolean -> Boolean,char -> Character
- 其中 Byte、Short、Integer、Long、Float、Double 的父类是 Number
包装类与基本数据类型相互转换
- 基本数据类型 -> 包装类(装箱)
- 通过构造器:
Integer t = new Integer(11);
- 通过字符串参数:
Float f = new float("32.1F");
- 自动装箱
- 通过构造器:
- 包装类 -> 基本数据类型(拆箱)
- 调用包装类的方法:
xxxValue()
- 自动拆箱
- 调用包装类的方法:
- String 类 -> 基本数据类型
- 调用相应的包装类的
parseXxx(String)
静态方法 - 通过包装类构造器:
boolean b = new Boolean ("true");
- 调用相应的包装类的
- 基本数据类型 -> String 类
- String 类的
valueof (3.4f)
方法 2.23.4+””
- String 类的
- 包装类 -> String 类
- 包装类对象的
toString()
方法 - 调用包装类的
toString(形参)
方法
- 包装类对象的
- String 类 -> 包装类:装箱
Static 关键字
static 的使用
使用 static 修饰属性:静态变量(类变量)
实例变量:创建类的多个对象,每个对象都独立的拥有了一套类中的非静态属性,当修改其中一个非静态属性时,不会导致其他对象中同样的属性值的修饰
- 静态变量:创建类的多个对象,多个对象共享同一个静态变量,当通过静态变量去修改某一个变量时,会导致其他对象调用此静态变量时,是修改过的
静态变量随着类的加载而加载,可以通过
类.静态变量
的方式进行调用静态变量的加载要早于对象的创建,由于类只会加载一次,则静态变量在内存中也只会存在一次,存在方法区的静态域中
1
System.out.Math.PI;
static 修饰方法
- 使用 static 修饰方法:静态方法
- 静态方法中,只能调用静态的方法或属性,非静态的方法中,可以调用所有的方法或属性
- 在静态的方法内,不能使用 this 关键字、super 关键字
- 操作静态属性的方法,通常设置为 static ,工具类中的方法,习惯上声明为 static (Math、Arrays、Collections)
单例(Singleton)设计模式
- 取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
- 饿汉式:对象加载时间过长,但线程安全的
- 懒汉式:延迟对象的创建
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
- Singleton 设计模式的应用场景
- 网站的计数器
- 应用程序的日志应用,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
- 数据库连接池,因为数据库连接是一种数据库资源。
- 项目中读取配置文件的类,一般也只有一个对象,没有必要每次使用配置文件数据,都生成一个对象去读取
- Application、Windows 的 Task Manager、Recycle Bin
代码块(初始化块)
- 用来初始化类、对象
- 代码块如果有修饰的话,只能使用 static
- 静态代码块:初始化类的信息
- 内部可以有输出语句
- 随着类的加载而执行,而且只执行一次
- 如果一个类中,定义了多个静态代码块,则按照声明的先后顺序执行
- 静态代码块的执行,优先于非静态代码块的执行
- 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
- 非静态代码块:可以在创建对象时,对对象的属性等进行初始化
- 内部可以有输出语句
- 随着对象的创建而执行
- 每创建一个对象,就执行一次非静态代码块
- 如果一个类中,定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
- 对属性可以赋值的位置
- 默认初始化、显式初始化、构造器中初始化
- 有了对象以后,可以通过
对象.属性
或对象.方法
的方式进行赋值 - 在代码块中赋值
抽象类与抽象方法
- Java 允许类设计者指定超类声明一个方法但不提供实现,该方法的实现由子类提供
- 这样的方法称为抽象方法,有一个或更多抽象方法的类称为抽象类
abstract 关键字的使用
- abstract 修饰抽象类
- 此类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用
- 开发中都会提供抽象类的子类,让子类对象实例化,实现相关的操作
- abstract 修饰抽象方法
- 抽象方法:只有方法的声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类,但抽象类中可以没有抽象方法
- abstract 不能用来修饰变量、代码块、构造器
- abstract 不能用来修饰私有方法、静态方法、final 的方法、final 的类
- 此类不能实例化
模板方法设计模式(TemplateMethod)
- 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式
- 模板模式:在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了,但是某些部分易变,易变部分可以抽象出来,供不同子类实现
- 常见的模板方法设计模式
- 数据库访问的封装
- Junit 单元测试
- JavaWeb 的 Servlet 中关于 doGet/doPost 方法调用
- Hibernate 中模板程序
- Spring 中 JDBCTemlate、HibernateTemplate 等
接口(interface)
- 接口是抽象方法和常量值定义的集合,用 interface 来定义
- 接口中的所有成员变量都默认是由
public static final
修饰的,接口中的所有抽象方法都默认是由public abstract
修饰的 - 接口中没有构造器,意味着接口不可以实例化
- JDK8 后除了全局常量和抽象方法之外,还可以定义静态方法、默认方法
- 接口和类是并列的两个结构
- 接口通过让类去实现 implements 使用,如果实现类覆盖了接口中的所有方法,则此实现类就可以实例化,如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
- Java 类可以实现多个接口 ,弥补了 Java 单继承性的局限性,接口与接口之间是继承,而且可以多继承
- 接口的具体使用,体现多态性 ,主要用途就是被实现类实现,实际可以看作是一种规范
代理模式(Proxy)
- 代理设计就是为其他对象提供一种代理以控制对这个对象的访问
- 分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类):JDK 自带的动态代理
- 应用场景
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
接口和抽象类之间的对比
区别点 | 抽象类 | 接口 |
---|---|---|
定义 | 包含抽象方法的类 | 主要是抽象方法和全局常量的集合 |
组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法、默认方法、静态方法 |
使用 | 子类继承抽象类(extends) | 子类实现接口(implements) |
关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
常见设计模式 | 模板方法 | 简单工厂、工厂方法、代理模式 |
对象 | 都通过对象的多态性产生实例化对象 | |
局限 | 抽象类有单继承的局限 | 接口没有此局限 |
实际 | 作为一个模板 | 是作为一个标准或是表示一种能力 |
选择 | 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限 |
Java 8 中关于接口的改进
- 静态方法
- 使用 static 关键字修饰,可以通过接口直接调用静态方法,并执行其方法体
- 经常在相互一起使用的类中使用静态方法,可以在标准库中找到像 Collection/Collections 或者 Path/Paths 这样成对的接口和类
- 默认方法
- 默认方法使用 default 关键字修饰,可以通过实现类对象来调用
- 在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性,如:java 8 API 中对 Collection、List、Comparator 等接口提供了丰富的默认方法
内部类
- Java 中允许将一个类 A 声明在另一个类 B 中,则类 A 就是内部类,类 B 就是外部类
- 成员内部类
- 作为外部类的成员,调用外部类的结构,可以被static修饰,可以被4种不同的权限修饰
- 作为一个类,类内可以定义属性、方法、构造器等,可以被 final 修饰,表示此类不能被继承。言外之意,不使用 final,就可以被继承,可以 abstract 修饰
匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例
一个匿名内部类一定是在 new 的后面,用其隐含实现一个接口或实现一个类
匿名内部类必须继承父类或实现接口 ,=只能有一个对象,匿名内部类对象只能使用多态形式引用
1
2
3new 父类构造器(实参列表)|实现接口(){
//匿名内部类的类体部分
}
局部内部类
- 方法内、代码块内、构造器内
- 局部内部类的方法中(如:show)如果调用局部内部类所声明的方法(如:method)中的局部变量(如:num)的话,要求此局部变量声明为 final 的
- jdk 8及之后的版本可以省略 final 的声明
异常
- 将程序执行中发生的不正常情况称为异常,开发过程中的语法错误和逻辑错误不是异常
- Error:Java 虚拟机无法解决的严重问题,如:JVM系统内部错误、资源耗尽等严重情况
- StackOverflowError 和 OutOfMemoryError:一般不编写针对性的代码进行处理
- Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理
- 空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界
- 解决方法:遇到错误就终止程序的运行,或在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理
- 捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生,如:除数为0,数组下标越界等
异常分类
- 运行时异常
- 编译器不要求强制处置的异常,一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常
java.lang.RuntimeException
类及它的子类都是运行时异常- 对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
- 编译时异常
- 编译器要求必须处置的异常,即程序在运行时由于外界因素造成的一般性异常
- 编译器要求 Java 程序必须捕获或声明所有编译时异常
- 对于这类异常,如果程序不处理,可能会带来意想不到的后果
try-catch-finally
try
- 用 try{…} 语句块选定捕获异常的范围,将可能出现异常的代码放在 try 语句块中
catch(Exceptiontypee)
- 在 catch 语句块中是对异常对象进行处理的代码,每个 try 语句块可以伴随一个或多个 catch 语句,用于处理可能产生的不同类型的异常对象
- 捕获异常的有关信息与其它对象一样,可以访问一个异常对象的成员变量或调用它的方法
- getMessage() 获取异常信息,返回字符串
- printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置,返回值 void
finally
- 通过 finally 语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理
- 不论在 try 代码块中是否发生了异常事件、catch 语句是否执行、catch语 句是否有异常、catch 语句中是否有 return,finally 块中的语句都会被执行
- 像数据库连接、输入输出流、网络编程 Socket 等资源,JVM 是不能自动的回收的,我们需要自己手动的进行资源的释放,此时的资源释放,就需要声明在 finally 中
- finally 语句和 catch 语句是任选的
1
2
3
4
5
6
7
8
9
10
11try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}
...
finally{
//一定会执行的代码
}
throws
- 声明抛出异常是 Java 中处理异常的第二种方式
- 如果一个方法中的语句执行时可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理
- 在方法声明中用 throws 语句可以声明抛出异常的列表,throws 后面的异常类型可以是方法中产生的异常类型,也可以是它的父类
throws + 异常类型
写在方法的声明处,指明此方法执行时,可能会抛出的异常类型
用户自定义异常类
- 一般用户自定义异常类都是
RuntimeException
的子类 - 自定义异常类通常需要编写几个重载的构造器,需要提供 serialVersionUID
- 自定义的异常通过throw抛出
- 如何自定义异常类
- 继承于现有的异常结构:RuntimeException 、Exception
- 提供全局常量:serialVersionUID
- 提供重载的构造器
课程代码
- 课程代码已上传 Github 仓库