一、JAVA语言基础知识
Java语言是一个面向对象的程序设计语言。
除了面向对象的特点以外,Java语言还在安全性、平台无关性、支持多线程、内存管理等许多方面具有卓越的优点。
计算机程序设计:
- 对问题进行抽象
- 用计算机语言表述,利用机器求解
面向对象的思想
面向对象的思想
将客观事物看作具有状态和行为的对象,通过抽象找出同一类对象的共同状态和行为,构成类。
例如:
构建一个汽车类,需要提取所有汽车对象的共有的状态和行为。
将状态用变量表示,行为用方法表示。
class Car {
int color_number;
int door_number;
int speed; ……
void brake() { … }
void speedUp() {…};
void slowDown() { … };……
}
面向对象技术给软件发展带来的益处
- 可重用性
- 可靠性
面向对象语言的基本特征
- 抽象和封装
- 继承性
- 多态性
Java语言的特点:
- 面向对象
- 安全性
Java不支持指针
Java的内部安全措施 - 平台无关性
编译后的字节码对应于Java虚拟机,因此可在不同平台上运行 - 多线程
Java是第一个在语言级提供内置多线程支持的高级语言 - 内存管理
Java对内存自动进行管理并进行垃圾回收
Java与C++的区别
Java中没有#include 和#define 等预处理功能,用import语来句包含其它类和包;
Java中没有structure,union及typedef;
Java中没有不属于类成员的函数,没有指针和多重继承,Java只支持单重继承;
Java中禁用goto,但goto还是保留的关键字;
Java中没有操作符重载;
Java中没有全局变量,可以在类中定义公用、静态的数据成员实现相同功能;
……
组成Java程序的最小单位是类,类封装了数据与处理数据的方法。
对于大多数常用的功能,有大量已经编译好、经过测试的类,这些类的集合就是Java类库。
Java类库主要是随编译器一起提供,也有些类库是由独立软件开发商提供的。
Java程序编译执行的过程:
一次编写,各处运行
JAVA平台:
Java APIs (应用程序接口)
经过编译的,可在程序中使用的Java代码标准库。
Java VM(虚拟机)Virtual Machine
Java 程序由Java虚拟机程序执行(或解释执行)。
Java 平台Java2 SDK(Software Development Kit):
Java开发工具包括
Javac:Java编译器,用来将java程序编译成 Bytecode。
Java:Java解释器,执行已经转换成Bytecode的java应用程序。
Jdb:Java调试器, 用来调试java程序。
Javap:反编译,将类文件还原回方法和变量。
Javadoc:文档生成器,创建HTML文件。
Appletviwer:Applet解释器, 用来解释已经转换成Bytecode的java小应用程序。
java程序举例:
运行在客户端Java虚拟机上的Java程序
可在客户端机器中读写
可使用自己的主窗口、标题栏和菜单
程序可大可小
能够以命令行方式运行
主类必须有一个主方法main(),作为程序运行的入口。
一个Java源代码文件称为一个编译单元。
一个编译单元中只能有一个public类,该类名与文件名相同,编译单元中的其他类往往是public类的辅助类,经过编译,每个类都会产一个class文件。
使用如下命令编译并运行程序:
javac MyClass.java
java MyClass
变量和常量
变量
一个由标识符命名的项
每个变量都有类型, 例如 int 类型或 Object类型, 变量还有作用域.
变量的值可以被改变.
常量
常量一旦被初始化以后就不可改变。
标识符
标识符是一个名称,与内存中的某个位置(地址)相对应
标识符的第一个字符必须是下列字符之一:
大写字母 (A-Z)
小写字母 (a-z)
下划线(_)
美元符号 ($)
标识符的第二个字符及后继字符必须是:
上述列表中的任意字符
数字字符 (0-9)
整数
byte 8 bits
short 16 bits
int 32 bits
long 64
char 16
整数运算
比较运算符(关系运算符)
算术比较运算符 <, <=, >, and >=
算术相等比较运算符 == and !=
算术运算符
一元运算符 + 和 -
+,- *, /, 和 % (取余 )
自增/自减运算符 ++/–
移位运算符 <<, >>, and >>>
位运算符 ~, &, |, and ^
条件运算符 ? :
类型转换运算符
字符串连接运算符 +
浮点数
float
单精度浮点数
32-bit
-m·2e ~ m·2e
m 是一个小于 224的正整数
e 是一个介于 -149和104之间(含)的整数
double
双精度浮点数
64-bit
m·2e~ m·2e
m是一个小于 253的正整数
e是一个介于 -1045 和 1000之间(含)的整数
浮点运算
比较运算符(关系运算符)
算术比较运算符 <, <=, >, and >=
算术相等比较运算符 == and !=
算术运算符
一元运算符 + 和 -
+,- *, /, 和 % (取余 )
自增/自减运算符 ++/–
移位运算符 <<, >>, and >>>
位运算符 ~, &, |, and ^
条件运算符 ? :
类型转换运算符
字符串连接运算符 +
布尔类型和布尔值
布尔类型表示一个逻辑量, 有两个取值: true和false
布尔运算符
关系运算符 == and !=
逻辑“非”运算符 !
逻辑运算符 &, ^, 和 |
条件“与” 和条件“或”运算符 && 和 ||
条件运算符 ? :
字符串连接运算符 +
String——字符串
String 是一个类
String类JDK标准类集合中的一部分
String animal = “walrus”;
文字量
直接出现在程序中并被编译器直接使用的值.
整数文字量
十进制 : 如: 15
十六进制 : 如: 0xff
八进制 : 如: 0377
浮点文字量
一个浮点文字量包括以下几个部分
整数部分
小数点
小数部分
指数 (e or E)
类型后缀 (f or F for float, d or D for double)
float 类型文字量举例:
1e 1f 2.f .3f 0f 3.1 4f 6.022137e+23f
double 类型文字量举例:
1e1 2. .3 0.0 3.1 4 1e-9d 1e137
布尔文字量
布尔类型只有两个值,由文字量 true 和 false表示
字符文字量
一个字符文字量表示为一个字符或者一个转义序列,用单引号括起
例如 ‘a’ ‘Z’ ‘@’
格式字符
\ b backspace BS
\ t horizontal tab HT
\ n linefeed LF
\ f form feed FF 进纸键;跳页
\ r carriage return CR
\ " double quote "
\ ’ single quote ’
\ \ backslash \
字符串文字量
由零个或多个字符组成,以双引号括起
每一个字符都可以用转义序列来表示
例如:
“” // 空字符串
" \ “” // 只包含 " 的字符串
“This is a string” // 有16个字符的字符串
"This is a " + “string” //字符串常量表达式,由两个字符串常量组成
算术运算符
运算符 ++ 和 –
例如: i++; --j;
一元运算符 + 和 –
加法运算符 + 和 -
乘法运算符 *, /, 和 %
赋值运算符
简单赋值运算符 =
复合赋值运算符
*= /= %= += -= <<= >>= >>>= &= ^= |=
E1 op= E2 等效于 E1 = (T)((E1) op (E2)), 其中T 是 E1的类型
关系运算符
关系表达式的类型永远是布尔类型(bool).
算术比较运算符 <, <=, >, and >=
类型比较运算符 instanceof
例如: e instanceof Point //Point 是一个类
相等关系运算符
相等关系运算符
数字相等运算符 == , !=
布尔相等运算符 == , !=
引用相等运算符 == , !=
逻辑运算符
“与”运算 &&
如果两个操作数的值都为true运算结果为true; 否则,结果为false.
“或”运算 ||
如果两个操作数的值都为false运算结果为false;否则,结果true
“非”运算符!
操作数的类型必须是布尔类型
如果操作数的结果为 false,则表达式的结果为 true ,如果操作数的结果为 true则表达式的结果为 false
条件运算符 (表达式1?表达式2:表达式3)
首先计算表达式1
如果表达式1的值为 true, 则选择表达式2的值
如果表达式1的值为 false,则选择表达式3的值
类型转换
每个表达式都有类型
如果表达式的类型对于上下文不合适
有时可能会导致编译错误
有时语言会进行隐含类型转换
例题
答案:不正确。
原因:精度不准确,应该用强制类型转换,如下所示:float f=(float)3.4 或float f = 3.4f
在java里面,没小数点的默认是int,有小数点的默认是 double;
编译器可以自动向上转型,如int 转成 long 系统自动转换没有问题,因为后者精度更高
double 转成 float 就不能自动做了,所以后面的加上个 f;
类型转换
赋值转换
将表达式类型转换为制定变量的类型
方法调用转换
适用于方法或构造方法调用中的每一个参数
强制转换
将一个表达式转换为指定的类型
例如 (float)5.0
字符串转换
任何类型(包括null类型)都可以转换为字符串类型
只当一个操作数是String类型时,适用于+运算符的操作数
数字提升
将算术运算符的操作数转换为共同类型
一元数字提升
如果一个操作数是 byte, short, 或 char类型, 一元数字提升通过扩展转换将它转换为int类型
二元数字提升
二元数字提升作用在特定操作符的操作数上*, /, %, +, -, <, <=, >, >=, ==, !=, &, ^, | and?:
在必要时使用扩展转换来转换操作数类型
标准输入输出
标准输入流 System.in
标准输出流 System.out
例如
System.out.println("Hello world!");
数组
数组元素
数组中的变量被称作数组的元素
元素没有名字,通过数组名字
和非负整数下标值
引用数组元素。
每个数组都有一个由 public final 修饰的成员变量:length ,即数组含有元素的个数(length可以是正数或零)
数组的声明
声明(Declaration)
声明数组时无需指明数组元素的个数,也不为数组元素分配内存空间
不能直接使用,必须经过初始化分配内存后才能使用
Type[ ] arrayName;
//例如:
int[] intArray;
String[] stringArray;
Type arrayName[ ];
//例如:
int intArray[];
String stringArray[];
数组的创建
用关键字new
构成数组的创建表达式,可以指定数组的类型和数组元素的个数。元素个数可以是常量也可以是变量
基本类型数组的每个元素都是一个基本类型的变量;引用类型数组的每个元素都是对象的引用。
补充:
Java的变量分为两大类:基本数据类型和引用数据类型。
其中基本类型变量有四类8种:
byte short int long float double char boolean
除了8种基本数据类型变量,其他变量都是引用数据类型,如类、接口、数组等。
基本数据类型,只有一块存储空间, 在栈中,存放的是具体数据值。
引用数据类型,有两块存储空间,一个在栈(Stack)中,一个在堆(heap)中。堆中存放对象实体(使用new关键字,即表示在堆中开辟一块新的存储空间),栈中存放对象在堆中所在位置的首地址。引用类型变量类似于C/C++中的指针。
引用:一个引用类型变量(栈中的一块内存空间)保存了一个该类型对象在堆中所在位置的首地址,也称作一个引用类型变量指向了一个该类型的对象,通过这个变量就可以操作对象中的数据。
堆与栈的区别有:
栈由系统自动分配,而堆是人为申请开辟;
栈获得的空间较小,而堆获得的空间较大;
栈由系统自动分配,速度较快,而堆一般速度比较慢;
栈是连续的空间,而堆是不连续的空间。
创建数组:
arryName=new Type[componets number];
//例如:
int[] ai; ai=new int[10];
String[] s; s=new String[3];
//或者可以将数组的声明和创建一并执行
int ai[]=new int[10];
//可以在一条声明语句中创建多个数组
String[] s1=new String[3], s2=new String[8];
数组元素的初始化
声明数组名时,给出了数组的初始值,程序便会利用数组初始值创建数组并对它的各个元素进行初始化
int a[]={22, 33, 44, 55};
创建数组时,如果没有指定初始值,数组便被赋予默认值初始值。
基本类型数值数据,默认的初始值为0;
boolean类型数据,默认值为false;
引用类型元素的默认值为null。
程序也可以在数组被构造之后改变数组元素值
数组的引用
通过下面的表达式引用数组的一个元素:
arrayName[index]
数组下标必须是 int , short, byte, 或者 char.
下标从零开始计数.
元素的个数即为数组的长度,可以通过 arryName.length
引用
元素下标最大值为 length – 1,如果超过最大值,将会产生数组越界异常(ArrayIndexOutOfBoundsException
)
举例:
public class MyArray {
public static void main(String[] args){
int myArray[]; //声明数组
myArray=new int[10]; //创建数组
System.out.println("Index\t\tValue");
for(int i=0; i<myArray.length;i++)
System.out.println(i+"\t\t"+myArray[i]);
//证明数组元素默认初始化为0
//myArray[10]=100; //将产生数组越界异常
}
}
数组名是一个引用:
例子
public class Arrays
{ public static void main(String[] args)
{ int[] a1 = { 1, 2, 3, 4, 5 };
int[] a2;
a2 = a1;
for(int i = 0; i < a2.length; i++) a2[i]++;
for(int i = 0; i < a1.length; i++)
System.out.println( "a1[" + i + "] = " + a1[i]);
}
}
运行结果:
a1[0] = 2
a1[1] = 3
a1[2] = 4
a1[3] = 5
a1[4] = 6
数组的复制:
public static void arraycopy(Object source , int srcIndex , Object dest , int destIndex , int length )
例子
public class ArrayCopyDemo
{ public static void main(String[] args)
{ char[] copyFrom = { 'd', 'e', 'c', 'a', 'f', 'f', 'e',
'i', 'n', 'a', 't', 'e', 'd'};
char[] copyTo = new char[7];
System.arraycopy(copyFrom, 2, copyTo, 0, 7);
System.out.println(new String(copyTo));
}
}
复制的是caffein
多维数组
二维数组的声明和构造
int[ ][ ] myArray ;
//myArray 可以存储一个指向2维整数数组的引用。其初始值为null。
int[ ][ ] myArray = new int[3][5] ;
//建立一个数组对象,把引用存储到myArray。这个数组所有元素的初始值为零。
int[ ][ ] myArray = { {8,1,2,2,9}, {1,9,4,0,3}, {0,3,0,0,7} };
//建立一个数组并为每一个元素赋值。
多维数组的长度
二维数组的长度
class UnevenExample2
{ public static void main( String[ ] arg )
{ int[ ][ ] uneven =
{ { 1, 9, 4 },
{ 0, 2},
{ 0, 1, 2, 3, 4 } };
System.out.println("Length is: " + uneven.length );
}
}
运行结果:
Length is: 3
// 数组的长度 (行数)
System.out.println("Length of array is: " + uneven.length );
// 数组每一行的长度(列数)
System.out.println("Length of row[0] is: " + uneven[0].length );
System.out.println("Length of row[1] is: " + uneven[1].length );
System.out.println("Length of row[2] is: " + uneven[2].length );
}
}
运行结果:
Length of array is: 3
Length of row[0] is: 3
Length of row[1] is: 2
Length of row[2] is: 5
二、类和对象的基本概念
面对对象程序设计方法概述
面向对象的程序设计
与结构化程序设计方法相比,更符合人类认识现实世界的思维方式
已成为程序设计的主流方向
涉及的主要概念
抽象
封装
继承
多态
对象
现实世界中
万物皆对象。
都具有各自的属性,对外界都呈现各自的行为 。
程序中
一切都是对象。
都具有标识 (identity), 属性和行为(方法)
通过一个或多个变量来保存其状态。
通过方法(method) 实现他的行为。
类
将属性及行为相同或相似的对象归为一类。
类可以看成是对象的抽象,代表了此类对象所具有的共有属性和行为。
在面向对象的程序设计中,每一个对象都属于某个特定的类。
结构化程序设计
通常由若干个程序模块组成,每个程序模块都可以是子程序或函数。
数据和功能分离,代码难于维护和复用。
面向对象程序设计
基本组成单位是类。
程序在运行时由类生成对象,对象是面向对象程序的核心。
对象之间通过发送消息进行通信,互相协作完成相应功能。
抽象
忽略问题中与当前目标无关的方面,以便更充分地注意与当前目标有关的方面
例:钟表
数据(属性)
int Hour; int Minute; int Second;
方法(行为)
SetTime(); ShowTime();
封装
是一种信息隐蔽技术。
利用抽象数据类型将数据和基于数据的操作封装在一起。
用户只能看到对象的封装界面信息,对象的内部细节对用户是隐蔽的。
封装的目的在于将对象的使用者和设计者分开,使用者不必知道行为实现的细节,只需使用设计者提供的消息来访问对象。
封装的定义
- 清楚的边界
所有对象的内部信息被限定在这个边界内。
- 接口
对象向外界提供的方法,外界可以通过这些方法与对象进行交互。
- 受保护的内部实现
功能的实现细节,不能从类外访问。
继承
是指新的类可以获得已有类(称为超类、基类或父类)的属性和行为,称新类为已有类的派生类(也称为子类)。
在继承过程中派生类继承了基类的特性,包括方法和实例变量。
派生类也可修改继承的方法或增加新的方法,使之更适合特殊的需要。
有助于解决软件的可重用性问题,使程序结构清晰,降低了编码和维护的工作量。
单继承
任何一个派生类都只有单一的直接父类。
类层次结构为树状结构。
多继承
一个类可以有一个以上的直接父类。
类层次结构为网状结构,设计及实现比较复杂。
Java语言仅支持单继承。
多态
一个程序中同名的不同方法共存。
主要通过子类对父类方法的覆盖来实现。(继承)
不同类的对象可以响应同名的消息(方法) ,具体的实现方法却不同。
使语言具有灵活、抽象、行为共享、代码共享的优势,很好地解决了应用程序方法同名问题。
int add(int a, int b) 3+5
float add(float a, float b) 3.2 +3.5
类的声明
类与对象
在程序中,对象是通过一种抽象数据类型来描述的,这种抽象数据类型称为类(Class)。
一个类是对一类对象的描述。类是构造对象的模板。
对象是类的具体实例。
声明形式
[public] [abstract | final] class 类名称
[extends 父类名称]
[implements 接口名称列表]
{
变量成员声明及初始化;
方法声明及方法体;
}
关键字
- class
表明其后声明的是一个类。
- extends
如果所声明的类是从某一父类派生而来,那么,父类的名字应写在extends之后。
- implements
如果所声明的类要实现某些接口,那么,接口的名字应写在implements之后。
修饰符
可以有多个,用来限定类的使用方式。
public
表明此类为公有类。
abstract
指明此类为抽象类。
final
指明此类为终结类 。
类声明体
变量成员声明及初始化
可以有多个
方法声明及方法体
可以有多个
//钟表类
public class Clock { // 成员变量
int hour ;
int minute ;
int second ;
// 成员方法
public void setTime(int newH, int newM, int newS){
hour=newH ;
minute=newM ;
second=news ;
}
public void showTime() {
System.out.println(hour+":"+minute+":"+second);
}
}
对象的声明和引用
变量和对象
变量除了存储基本数据类型的数据,还能存储对象的引用,用来存储对象引用的变量称为引用变量。
类的对象也称为类的实例。
对象的声明
格式
类名 变量名;
例如Clock是已经声明的类名,则下面语句声明的变量aclock将用于存储该类对象的引用:
Clock aclock;
声明一个引用变量时并没有对象生成
对象的创建
生成实例的格式:
new <类名>()
例如:
Clock aclock=new Clock()
其作用是:
在内存中为此对象分配内存空间
返回对象的引用(reference ,相当于对象的存储地址)
引用变量可以被赋以空值
例如:aclock=null;
自动装箱拆箱
Java 5新增特性,基本数据类型的自动装箱拆箱
自动装箱
Java 5之前:Integer i = new Integer(2);
Java 5:Integer i = 3;
自动拆箱
Java 5 之前:int j = i.intValue(); //i为Integer类型的对象。
Java 5:int j = i; //i为Integer类型的对象。
数据成员
表示Java类的状态
声明数据成员必须给出变量名及其所属的类型,同时还可以指定其他特性
在一个类中成员变量名是唯一的
数据成员的类型可以是Java中任意的数据类型(简单类型,类,接口,数组)
声明格式
[public | protected | private]
[static][ final][transient] [volatile]
变量数据类型 变量名1[=变量初值],
变量名2[=变量初值], … ;
格式说明
public、protected、private 为访问控制符
static指明这是一个静态成员变量
final指明变量的值不能被修改
transient指明变量是临时状态
volatile指明变量是一个共享变量
实例变量
没有static
修饰的变量称为实例变量(Instance Variables)
用来存储所有实例都需要的属性信息,不同实例的属性值可能会不同
可通过下面的表达式访问实例属性的值
<实例名>.<实例变量名>
Object类
Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。
Object 类位于 java.lang 包中,编译时会自动导入,我们创建一个类时,如果没有明确继承一个父类,那么它就会自动继承 Object,成为 Object 的子类。
Object toString()
方法用于返回对象的字符串形式表示。
返回值:
返回对象的字符串表示形式。
默认返回对象的:类名+@+hashCode的十六类字符串
。
例:
声明一个表示圆的类,保存在文件Circle.java 中。然后编写测试类,保存在文件ShapeTester.java
中,并与Circle.java
放在相同的目录下
见:
JAVA实验二
public class Circle {
int radius;
}
public class ShapeTester {
public static void main(String args[]) {
Circle x;
x = new Circle();
System.out.println(x); //x.toString()
System.out.println("radius = " + x.radius);
}
}
编译后运行结果如下:
Circle@26b249
radius =0
解释
默认的toString()
返回:x.toString()
getClass().getName() + “@” + Integer.toHexString(hashCode())
toString()
是Object类的一个公有方法,而所有类都继承自Object类。所以所有类即使不实现toString方法,也会存在从Object
类继承来的toString
。
类变量
也称为静态变量,声明时需加static
修饰符
不管类的对象有多少,类变量只存在一份,在整个类中只有一个值
类初始化的同时就被赋值
适用情况
类中所有对象都相同的属性
经常需要共享的数据
系统中用到的一些常量值
引用格式
<类名 | 实例名>.<类变量名>
实例变量
实例变量定义在类中,前面没有static
修饰,它在一个对象创建时创建,摧毁时摧毁。
类变量
类变量定义在类中,前面有static
修饰,所以也叫静态变量。它在JVM加载类,执行类中静态区码块时创建。类变量是所有对象共有,其中一个对象将它值改变,其他对象得到的就是改变后的结果。
举例:
对于一个圆类的所有对象,计算圆的面积时,都需用到π的值,可在Circle类的声明中增加一个类属性PI
public class Circle {
static double PI = 3.14159265;
int radius;
}
当我们生成Circle
类的实例时,在每一个实例中并
没有存储PI的值,PI的值存储在类中
方法成员
定义类的行为
一个对象能够做的事情
我们能够从一个对象取得的信息
可以没有,也可以有多个;一旦在类中声明了方法,它就成为了类声明的一部分
分为实例方法和类方法
声明格式
[public | protected | private]
[static][ final][abstract] [native] [synchronized]
返回类型 方法名([参数列表]) [throws exceptionList]
{
方法体
}
格式说明
方法修饰
public、protected、private 为存取控制符
static指明方法是一个类方法
final指明方法是一个终结方法
abstract指明方法是一个抽象方法
native用来集成java代码和其它语言的代码
synchronized控制多个并发线程对共享数据的访问
补充:
将方法声明为final,那就说明你已经知道这个方法提供的功能已经满足你要求,不需要进行扩展,并且也不允许任何从此类继承的类来覆写这个方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。另外有一种被称为inline的机制,它会使你在调用final方法时,直接将方法主体插入到调用处,而不是进行例行的方法调用,例如保存断点,压栈等,这样可能会使你的程序效率有所提高,然而当你的方法主体非常庞大时,或你在多处调用此方法,那么你的调用主体代码便会迅速膨胀,可能反而会影响效率,所以你要慎用final进行方法定义。
Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。
可以将native方法比作Java程序同C程序的接口,其实现步骤:
1、在Java中声明native()方法,然后编译;
2、用javah产生一个.h文件;
3、写一个.cpp文件实现native导出方法,其中需要包含第二步产生的.h文件(注意其中又包含了JDK带的jni.h文件);
4、将第三步的.cpp文件编译成动态链接库文件;
5、在Java中用System.loadLibrary()方法加载第四步产生的动态链接库文件,这个native()方法就可以在Java中被访问了。
方法调用
给对象发消息意味着调用对象的某个方法
从对象中取得信息
修改对象的状态或进行某种操作
进行计算及取得结果等
调用格式
<对象名>.<方法名>([参数列表])
称点操作符“.”前面的<对象名>为消息的接收者(receiver)
参数传递
值传递:参数类型为基本数据类型时
引用传递:参数类型为对象类型或数组时
举例;
在Circle类中声明计算周长的方法
public class Circle {
static double PI = 3.14159265;
int radius;
public double circumference() {
return 2 * PI * radius;
}
}
由于radius是实例变量,在程序运行时,Java会自动取其接收者对象的属性值
也可将circumference方法体改为:
return 2 * PI * this.radius;
关键字this代表此方法的接收者对象
方法调用示例:
public class CircumferenceTester {
public static void main(String args[]) {
Circle c1 = new Circle();
c1.radius = 50;
Circle c2 = new Circle();
c2.radius = 10;
double circum1 = c1.circumference();
double circum2 = c2.circumference();
System.out.println("Circle 1 has circumference " + circum1);
System.out.println("Circle 2 has circumference " + circum2);
}
}
运行结果
Circle 1 has circumference 314.159265
Circle 2 has circumference 62.831853
说明
在使用实例方法时,需要将其发送给一个实例对象(也称给对象发送一条消息),radius的值即是接收者对象的值。
在执行c1.circumference()
时,radius
的值为c1
的radius
属性值;在执行c2.circumference()
时,radius
的值为c2
的radius
属性值。
不同的类中可以声明相同方法名的方法。
使用时,系统会根据接收者对象的类型找到相应类的方法。
类方法
也称为静态方法
,表示类中对象的共有行为
声明时前面需加static
修饰符
不能被声明为抽象的
类方法可以在不建立对象的情况下用类名直接调用,也可用类实例调用
静态方法只能访问静态的成员,不可以直接访问实例成员。
实例方法可以访问静态的成员,也可以访问实例成员。
静态方法中是不可以出现this
关键字的。
可变长参数
从Java 5开始,可以在方法的参数中使用可变长参数
可变长参数使用省略号表示,其实质是数组
例如,“String … s”表示“String[] s”
对于可变长参数的方法,传递给可变长参数的实际参数可以是多个对象,也可以是一个对象或者是没有对象。
类的组织
包
包
是一组类的集合
一个包可以包含若干个类文件,还可包含若干个包
包的作用
将相关的源代码文件组织在一起
类名的空间管理,利用包来划分名字空间,便可以避免类名冲突
提供包一级的封装及存取权限
包的命名
每个包的名称必须是“独一无二”的
Java中包名使用小写字母表示
命名方式建议
将机构的Internet域名反序,作为包名的前导
cn.edu.zzu.www
若包名中有任何不可用于标识符的字符,用下划线替代
若包名中的任何部分与关键字冲突,后缀下划线
若包名中的任何部分以数字或其他不能用作标识符起始的字符开头,前缀下划线
编译单元与类空间
一个Java源代码文件称为一个编译单元,由三部分组成
所属包的声明(省略,则属于默认包)
import
(引入)包的声明,用于导入外部的类
类和接口的声明
一个编译单元中只能有一个public类,该类名与文件名相同,编译单元中的其他类往往是public类的辅助类,经过编译,每个类都会产一个class文件。
利用包来划分名字空间,便可以避免类名冲突。
包的声明
命名的包(Named Packages)
例如: package Mypackage;
默认包(未命名的包)
不含有包声明的编译单元是默认包的一部分
包与目录
Java使用文件系统
来存储包和类
包名就是文件夹名,即目录名
目录名并不一定是包名
用javac编译源程序时,如遇到当前目录(包)中没有声明的类,就会以环境变量classpath为相对查找路径,按照包名的结构来查找。因此,要指定搜寻包的路径,需设置环境变量classpath
。
引入包
为了使用其它包中所提供的类,需要使用import
语句引入所需要的类
Java编译器为所有程序自动引入包java.lang
impor
语句的格式
import package1[.package2…]. (classname |*);
其中package1[.package2…]表明包的层次,它对应于文件目录
classname
则指明所要引入的类名
如果要引入一个包中的所有类,则可以使用星号(*)来代替类名
类的访问控制
类的访问控制
类的访问控制只有public
(公共类)及无修饰符(缺省类)两种
访问权限符与访问能力之间的关系如表
类成员的访问控制
- 公有(public)
可以被其他任何对象访问(前提是对类成员所在的类有访问权限) - 保护(protected)
只可被同一类及其子类的实例对象访问 - 私有(private)
只能被这个类本身访问,在类外不可见 - 默认(default)
仅允许同一个包内的访问;又被称为“包(package)访问权限”
get方法
功能是取得属性变量的值
get
方法名以get
开头,后面是实例变量的名字
一般具有以下格式:
public <fieldType> get<FieldName>() {
return <fieldName>;
}
对于实例变量radius,声明其get方法如下:
public int getRadius(){
return radius;
}
set方法
功能是修改属性变量的值
set
方法名以set
开头,后面是实例变量的名字
一般具有以下格式
public void set<FieldName>(<fieldType> <paramName>) {
<fieldName> = <paramName>;
}
声明实例变量radius的set方法如下:
public void setRadius(int r){
radius = r;
}
关键字this的使用
如果形式参数名与实例变量名相同,则需要在实例变量名之前加this关键字,否则系统会将实例变量当成形式参数。
在上面的set
方法中,如果形式参数为radius
,则需要在成员变量radius
之前加上关键字this
。代码如下:
public void setRadius(int radius){
this.radius = radius;
}
对象初始化和回收
对象初始化
系统在生成对象时,会为对象分配内存空间,并自动调用构造方法对实例变量进行初始化。
对象回收
对象不再使用时,系统会调用垃圾回收程序将其占用的内存回收。
构造方法
构造方法
一种和类同名的特殊方法
用来初始化对象
Java中的每个类都有构造方法,用来初始化该类的一个新的对象
没有定义构造方法的类,系统自动提供默认的构造方法
注意:只要用户声明了构造方法,即使没有声明无参的构造方法,系统也不再赋默认的构造方法。
构造方法的特点
方法名与类名相同
没有返回类型,修饰符void也不能有
通常被声明为公有的(public)
可以有任意多个参数
主要作用是完成对象的初始化工作
不能在程序中显式的调用
在生成一个对象时,系统会自动调用该类的构造方法为新生成的对象初始化
默认构造方法
系统提供的默认构造方法
如果在类的声明中没有声明构造方法,则Java编译器会提供一个默认的构造方法
默认的构造方法没有参数,其方法体为空
使用默认的构造方法初始化对象时,如果在类声明中没有给实例变量赋初值,则对象的属性值为零、空或者false。
注意:只要用户声明了构造方法,即使没有声明无参的构造方法,系统也不再赋默认的构造方法。
默认构造方法举例:
//声明一个银行帐号类及测试代码
class BankAccount{
String ownerName;
int accountNumber;
float balance;
}
public class BankTester{
public static void main(String args[]){
BankAccount myAccount = new BankAccount(); //调用默认构造方法
System.out.println("ownerName=" + myAccount.ownerName);
System.out.println("accountNumber=" + myAccount.accountNumber);
System.out.println("balance=" + myAccount.balance);
}
}
自定义构造方法与方法重载
可在生成对象时给构造方法传送初始值,使用希望的值给对象初始化。
构造方法可以被重载
,构造方法的重载和方法的重载一致。
一个类中有两个及以上同名的方法,但参数表不同,这种情况就被称为方法重载。不包含方法的返回值!
在方法调用时,Java可以通过参数列表的不同来辨别应调用哪一个方法。
为BankAccount声明一个有三个参数的构造方法
public BankAccount(String initName, int initAccountNumber, float initBalance){
ownerName = initName;
accountNumber = initAccountNumber;
balance = initBalance;
}
假设一个新帐号的初始余额可以为0,则可增加一个带有两个参数的构造方法
public BankAccount(String initName, int initAccountNumber) {
ownerName = initName;
accountNumber = initAccountNumber;
balance = 0.0f;
}
自定义无参的构造方法-对子类影响
无参的构造方法对其子类的声明很重要。如果在一个类中不存在无参的构造方法,则要求其子类声明时必须声明构造方法,否则在子类对象的初始化时会出错。
如子类构造方法的声明中没有明确调用父类构造方法,则系统在执行子类的构造方法时会自动调用父类的默认构造方法(即无参的构造方法)
说明:
用户在进行类声明时,如果没有声明任何构造方法,系统会赋给此类一个默认(无参)的构造方法。
但是,只要用户声明了构造方法,即使没有声明无参的构造方法,系统也不再赋默认的构造方法。
自定义无参的构造方法
在声明构造方法时,好的声明习惯是
不声明构造方法
如果声明,至少声明一个无参构造方法
this关键字的使用
可以使用this关键字在一个构造方法中调用另外的构造方法
代码更简洁,维护起来也更容易
通常用参数个数比较少的构造方法调用参数个数最多的构造方法
举例:
public BankAccount(String initName, int initAccountNumber,
float initBalance) {
ownerName = initName;
accountNumber = initAccountNumber;
balance = initBalance;
}
public BankAccount() {
this("", 999999, 0.0f); //调用了上方的构造方法
}
public BankAccount(String initName, int initAccountNumber) {
this(initName, initAccountNumber, 0.0f); //调用第一个构造方法
}
内存回收技术
内存回收技术
当一个对象在程序中不再被使用时,就成为一个无用对象
当前的代码段不属于对象的作用域
把对象的引用赋值为空
Java运行时系统通过垃圾收集器周期性地释放无用对象所使用的内存
Java运行时系统会在对对象进行自动垃圾回收前,自动调用对象的finalize()
方法
垃圾收集器
自动扫描对象的动态内存区,对不再使用的对象做上标记以进行垃圾回收
作为一个线程运行
通常在系统空闲时异步地执行
当系统的内存用尽或程序中调用System.gc()
要求进行垃圾收集时,与系统同步运行
finalize()
方法
在类java.lang.Object
中声明,因此 Java中的每一个类都有该方法
用于释放系统资源,如关闭打开的文件或socket
(套接字)等
声明格式
protected void finalize() throws throwable
如果一个类需要释放除内存以外的资源,则需在类中重写finalize()
方法
内存溢出:(out of memory
)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。比如申请了一个int,但给它存了long才能存下的数,那就是内存溢出。
内存泄漏:(Memory Leak
)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
内存泄露====》内存溢出
指针悬挂
指针悬挂是指指针指向了一块没有分配给用户使用的内存。
指针未初始化
这不仅仅是初学者才会犯的错误。尤其是全局指针变量,不初始化就使用的情况很正常,考虑如下代码:
指针拷贝后删除了指针
应用举例
枚举类型
需要一个有限集合,而且集合中的数据为特定的值时,可以使用枚举类型
格式:
[public] enum 枚举类型名称 [implements 接口名称列表]
{
枚举值;
变量成员声明及初始化;
方法声明及方法体;
}
举例:
enum Score {
EXCELLENT,
QUALIFIED,
FAILED;
};
public class ScoreTester {
public static void main(String[] args) {
giveScore(Score.EXCELLENT);
}
public static void giveScore(Score s){
switch(s){
case EXCELLENT:
System.out.println("Excellent");
break;
case QUALIFIED:
System.out.println("Qualified");
break;
case FAILED:
System.out.println("Failed");
break;
}
}
}
toString方法
public String toString()
方法
是Object的方法,将对象的内容转换为字符串
Java的所有类都有一个默认的toString()方法,其方法体如下:
getClass().getName() + '@' Integer.toHexString(hashCode())
public static String toHexString(int i)
Integer类下的一个静态方法,把一个整数转化为16进制表示的字符串。
public native int hashCode()
默认情况下,Object中的hashCode()
返回对象的32位jvm内存地址。
下面的两行代码等价
System.out.println(anAccount);
System.out.println(anAccount.toString());
如果需要特殊的转换功能,则需要自己重写toString()方法
public String toString()
方法
toString()方法的几点说明
必须被声明为public
返回类型为String
方法的名称必须为toString
,且没有参数
在方法体中不要使用输出方法System.out.println()
为BankAccount类添加自己的toString()方法
java.text.DecimalFormat类
DecimalFormat 是 NumberFormat 的子类,用于格式化十进制数字。
//默认的方言和格式化模板
DecimalFormat defaultFormat = new DecimalFormat();
//指定格式化模板,默认方言
DecimalFormat patternFormat = new DecimalFormat("0");
//指定中国方言和格式化模板
DecimalFormat decimalFormat = new DecimalFormat("0",new DecimalFormatSymbols(Locale.CHINA));
String pattern = "0";//指定格式化模式
defaultFormat.applyPattern(pattern);//应用格式化模式
java.text.DecimalFormat类
//指定格式化模式
String pattern = "0";
//应用格式化模式
defaultFormat.applyPattern(pattern);
DecimalFormat 类主要靠 # 和 0 两种占位符号来指定数字长度。
0 表示如果位数不足则以 0 填充。如果number的位数大于等于pattern的0的个数,result=number。
# 否则不够的位数以0填充。
# 表示数字,如果是0则不显示(仅限开始和小数点后的末尾位置)
DecimalFormat
类
实例方法format()
对数据进行格式化
public String format(int/float/double number)
修改后的toString()方法如下
public String toString() {
return("Account #" + accountNumber + " with balance " +
new java.text.DecimalFormat("$0.00").format(balance));
}
重写(Override/Overriding)是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
在java中如果方法上加@Override
的注解的话,表示子类重写了父类的方法。当然也可以不写,写的好处是:
可读性提高
编译器会校验写的方法在父类中是否存在
三、类中的方法
异常处理
异常exception
就是阻止当前程序或方法继续执行的问题。
如果用System.out.println(11/0)
,那么因为用0做了除数,会抛java.lang.ArithmeticException
的异常。
异常发生的原因有很多,通常包含以下几大类:
- 用户输入了非法数据。
- 要打开的文件不存在。
- 网络通信时连接中断,或者JVM内存溢出。
这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
java异常分为两种:
- 运行时异常(RuntimeException),非检查型异常.
- 非运行时异常(CheckedException),检查型异常。
非检查型异常(运行时异常unchecked exceptions):
这类异常是不需要捕获的,程序员可以不去处理,当异常出现时,虚拟机会处理。
NullPointerException(空指针异常)、
IndexOutOfBoundsException(下标越界异常)
ClassCastException(类转换异常)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
IO操作的BufferOverflowException异常
检查型异常(非运行时异常 checked exceptions):
这类异常用户程序必须捕获处理,否则编译不过去,java编译器要求程序员必须对这种异常进行catch,Java认为检查型异常都是可以被处理(修复)的异常,所以Java程序必须显式处理检查型异常。
IOException
SQLException
用户自定义的Exception异常
在写代码时出现红线,需要try catch或者throws处理异常。
错误error :
错误不是异常,是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。
error 表示应用程序本身无法克服和恢复的一种严重问题,程序只有死的份了。
error 和exception 有什么区别? 【基础】
答:error
表示系统级的错误和程序不必处理的异常;比如内存溢出,不可能指望程序能处理这样的情况;Error类对象由 Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。在Java中,错误通常是使用Error的子类描述。
exception
表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。Exception又分为可检查(checked)异常和不可检查(unchecked)异常。
检查型异常在源代码里必须显式的进行捕获处理,这是编译期检查的一部分。
非检查型异常是指运行时异常,像NullPointerException、ArrayIndexOutOfBoundsException之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。
对于检查型异常,Java强迫程序必须进行处理。处理方法有两种:
捕获异常
使用try{}catch(){}块,捕获到所发生的异常,并进行相应的处理。
声明抛出异常
不在当前方法内处理异常,而是把异常抛出到调用方法中。
适用于非检查型异常
捕获异常:
//语法格式
try {
statement(s)
} catch (exceptiontype name) {
statement(s)
} catch (exceptiontype name) {
statement(s)
}
………
finally {
statement(s)
}
说明:
try
语句
其后跟随可能产生异常的代码块
catch
语句
其后跟随异常处理语句,通常用到两个方法
getMessage()
– 返回一个字符串对发生的异常进行描述。
printStackTrace()
– 给出方法的调用序列,一直到异常的产生位置
finally
语句
不论在try
代码段是否产生异常,finally
后的程序代码段都会被执行。通常在这里释放内存以外的其他资源
final, finally, finalize 的区别? 【基础】
答:final
:修饰符(关键字);如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承,因此一个类不能既被声明为abstract的,又被声明为final 的;将变量或方法声明为final,可以保证它们在使用中不被改变;被声明为final 的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改;被声明为final 的方法也同样只能使用,不能重载。
finally
:在异常处理时提供finally 块来执行任何清除操作;不论在try代码段是否产生异常,finally 后的程序代码段都会被执行。
finalize
:方法名;Java 技术允许使用finalize()
方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object 类中定义的,因此所有的类都继承了它。子类覆盖finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。
捕获 NumberFormatException类型的异常
捕获被零除的异常 ( ArithmeticException类型的异常)
对程序进行改进:重复提示输入,直到输入合法的数据。为了避免代码重复,可将数据存入数组
声明抛出异常
如果程序员不想在当前方法内处理异常,可以使用throws
子句声明将异常抛出到调用方法中。
如果所有的方法都选择了抛出此异常,最后 JVM
将捕获它,输出相关的错误信息,并终止程序的运行。在异常被抛出的过程中, 任何方法都可以捕获它并进行相应的处理。
throws和throw的区别
throws
出现在方法的声明中,表示该方法可能会抛出的异常,允许throws
后面跟着多个异常类型
throw
出现在方法体中,用于抛出异常。当方法在执行过程中遇到异常时,将异常信息封装为异常对象,然后throw
。
如果在openThisFile中抛出了FileNotfoundException异常,getCustomerInfo将停止执行,并将此异常传送给它的调用者
public void openThisFile(String fileName)
throws java.io.FileNotFoundException { //code for method
}
public void getCustomerInfo()
throws java.io.FileNotFoundException {
// do something this.openThisFile("customer.txt");
// do something }
在功能方法内部出现异常,程序不能继续运行,就用throw把异常对象抛出。
格式:throw new xxxException("异常产生的原因");
如果throw抛出的是运行时异常或其子类,不需要异常处理,如果不是,则还需要处理异常(try-catch
或throws
)。
声明自己的异常类
除使用系统预定义的异常类外,用户还可声明自己的异常类。
自定义的所有异常类都必须是Exception
的子类。
一般的声明方法如下
public class MyExceptionName extends SuperclassOfMyException {
public MyExceptionName() {
super("Some string explaining the exception");
}
}
声明当除数为零时抛出的异常类DivideByZeroException
super
可以理解为是指向离自己最近的父类对象的一个指针,
super.xxx
来引用父类的成员,包括变量和方法。
super(参数)
:调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
this.xxx
来引用本类的成员,包括变量和方法。
this(参数)
:调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。
throw
语句语法:用在方法体内
throw new
异常类构造方法名(参数列表);
如:
throw new Exception("除数为零无意义");
使用throws
子句在方法中抛出异常,用在方法声明
格式如下:
返回类型 方法名(可选参数表) throws 异常类名{
自动或主动引发异常的方法体代码
}
错误与断言
assert
(断言)语句:武断地宣称的语句,如宣称某条件必须成立。
老大爷指着西瓜:这是一个西瓜,断言成立。
老大爷指着西瓜:我的西瓜是世界上NO.1 的甜。断言不成立。
语法有二:
assert 条件表达式
assert 条件表达式 : 字符串型断言消息
虚拟机默认总是关闭断言。 断言语句适用程序调试排错。
遇到断言错误,如果虚拟机处于启用断言状态,则程序非正常终止
允许执行断言命令格式:
java -ea 主类名
举例:
import java.util.*;
public class ch31 {
public static void main(String[] args) {
try {
Scanner input = new Scanner(System.in);
System.out.println("请输入要计算平方根的数");
double x = input.nextDouble();
//assert x > 0 : "负数不能计算平方根";
double sqr = Math.sqrt(x);
System.out.printf("平方根:%.4f", sqr);
} catch (Exception e) {
System.out.println("能运行到这里吗?");
System.out.println("异常" + e.getMessage());
}
}
}
不开启断言运行如下:
IDEA默认不开启断言(即使断言写上去了也没用)
现在开启断言
在vm选项中填写-ea
重新运行,发现断言已触发。
总结:
- 异常是运行过程中出现了意外,无法按常规运行下去。使用try-catch-finally代码块捕获并处理异常。
- 捕获、处理异常的代码块也可嵌套,以满足复杂的需求。
- 所有异常类构成树状的层次结构,最顶层的异常类是Exception。
- 异常可使用throw语句主动精确地抛出。还可把在方法中出现的异常踢给调用它的方法来处理,这时要在方法头部使用throws子句。
- 除了异常外,还有Error(错误)。错误比异常严重,无法在程序中捕获和处理。例如断言错误、虚拟机本身的错误等。
- 在断言语句中,如果断言条件不成立,就发生了断言错误,这时只好任由程序自行中断运行。断言语句默认是不执行的,一般只在程序调试阶段启用断言。
方法重载
方法重载
一个类中名字相同的多个方法
这些方法的参数必须不同,Java可通过参数列表的不同来辨别重载的方法
参数个数不同 或 参数类型不同
返回值可以相同,也可以不同
重载的价值在于它允许通过使用一个方法名来访问多个方法
例:
//通过方法重载分别接收一个或几个不同数据类型的数据
class MethodOverloading {
public void receive(int i){
System.out.println("Receive one int parameter. ");
System.out.println("i="+i);
}
public void receive(double d){
System.out.println("Receive one double parameter. ");
System.out.println("d="+d);
}
public void receive(String s){
System.out.println("Receive one String parameter. ");
System.out.println("s="+s);
}
public void receive(int i,int j){
System.out.println("Receive two int parameters. ");
System.out.println("i=" + i + " j=" + j);
}
public void receive(int i,double d){
System.out.println("Receive one int parameter and one double parameter. ");
System.out.println("i=" + i + " d=" + d);
}
}
public class c32 {
public static void main(String args[]){
MethodOverloading m = new MethodOverloading();
m.receive(2);
m.receive(5.6);
m.receive(3,4);
m.receive(7,8.2);
m.receive("Is it fun?");
}
}
运行得到:
在方法中执行脚本
Java 6的新特色
Java的脚本API提供了一个独立于各种脚本语言的框架,利用该框架可以在Java代码中执行脚本。
通过脚本API,程序员可以使用Java编写可定制和扩展的应用程序,并将需要定制和扩展的部分留给其他人使用脚本实现。
Java的脚本API位于javax.script
包中,该包中的ScriptEngineManager类可以获得脚本引擎。
脚本简单地说就是一条条的文字命令,这些文字命令是可以看到的(如可以用记事本打开查看、编辑),脚本程序在执行时,是由系统的一个解释器,将其一条条的翻译成机器可识别的指令,并按程序顺序执行。因为脚本在执行时多了一道翻译的过程,所以它比二进制程序执行效率要稍低一些。
批处理文件,也称为批处理脚本。顾名思义,批处理就是对某对象进行批量的处理,通常被认为是一种简化的脚本语言,它应用于DOS和Windows系统中。在DOS和Windows(任意)系统中,.bat文件是可执行文件,由一系列命令构成,其中可以包含对其他程序的调用。
Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。
Shell俗称壳(用来区别于核),是指“为使用者提供操作界面”的软件(command interpreter,命令解析器)。它类似于DOS下的COMMAND.COM和后来的cmd.exe。它接收用户命令,然后调用相应的应用程序。
Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
Ken Thompson 的 sh 是第一种 Unix Shell,Windows Explorer 是一个典型的图形界面 Shell。
Shell 脚本(shell script),是一种为 shell 编写的脚本程序。
业界所说的 shell 通常都是指 shell 脚本,shell 和 shell script 是两个不同的概念。
在方法中执行脚本
最简单的执行脚本的过程如下:
1.创建一个ScriptEngineManager对象;
2.通过ScriptEngineManager对象获得一个ScriptObject对象;
3.通过ScriptObject对象的eval方法执行脚本。
四、类的重用、泛型、基础类
类的继承
一种由已有的类创建新类的机制,是面向对象程序设计的基石之一。
通过继承,可以根据已有类来定义新类,新类拥有已有类的所有功能
Java只支持类的单继承,每个子类只能有一个直接父类
父类是所有子类的公共属性及方法的集合,子类则是父类的特殊化
继承机制可以提高程序的抽象程度,提高代码的可重用性
基类(base class)
也称超类
(superclass)、父类
是被直接或间接继承的类
派生类(derived-class)
也称子类
(subclass)
继承其他类而得到的类
继承所有祖先的状态和行为
派生类可以增加变量和方法
派生类也可以覆盖/重写(override)
继承的方法
继承的概念—is_a关系
子类对象与父类对象存在“IS A”
(或“is a kind of”)的关系
以动物层次举例:
继承的概念—派生类对象
派生类产生的对象
从外部来看,它应该包括
- 与基类相同的接口
- 可以具有更多的方法和数据成员
其内包含着一个基类类型的子对象
继承的语法:
class childClass extends parentClass
{
//类体
}
举例说明:
在一个公司中,有普通员工(Employees)及管理人员(Magagers)两类人员
普通员工(Employees)可能有的属性信息包括
员工号(employeeNumber)
姓名(name)
地址(address)
电话号码(phoneNumber)
管理人员(Managers)除具有普通员工的属性外,还可能具有下面的属性
职责(responsibilities)
所管理的职员(listOfEmployees)
Employee与Manager的类图
//父类Employee
class Employee
{
int employeeNumbe ;
String name, address, phoneNumber ;
}
//子类Manager
class Manager extends Employee
{
//子类增加的数据成员
String responsibilities, listOfEmployees;
}
示例:
设有三个类:Person, Employee, Manager
package c4;
class person{
public String name;
public String getName(){
return name;
}
}
class Employee extends person{
public int employeeNumber;
public int getEmployeeNumber(){
return employeeNumber;
}
}
class Manager extends Employee{
public String responsibilities;
public String getResponsibilities(){
return responsibilities;
}
}
public class c41 {
public static void main(String args[]){
Employee zhang = new Employee();
zhang.name = "张三";
zhang.employeeNumber = 20220606;
System.out.println(zhang.getName());
System.out.println(zhang.getEmployeeNumber());
Manager li = new Manager();
li.name = "李四";
li.employeeNumber = 20200505;
li.responsibilities = "Internet project";
System.out.println(li.getName());;
System.out.println(li.getEmployeeNumber());
System.out.println(li.getResponsibilities());
}
}
子类不能直接访问从父类中继承的私有属性及方法,但可使用公有(及保护)方法进行访问
public class B {
public int a = 10;
private int b = 20;
protected int c = 30;
public int getB() { return b; }
}
public class A extends B {
public int d;
public void tryVariables() {
System.out.println(a); //允许
System.out.println(b); //不允许
System.out.println(getB()); //允许
System.out.println(c); //允许
}
}
隐藏和覆盖/重写(override/overriding)
隐藏
子类对从父类继承来的属性变量及方法可以重新定义。
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 方法名和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
如何访问被隐藏的父类属性
调用从父类继承的方法,则操作的是从父类继承的属性
使用super.
属性,super
代表父类对象的引用
举例说明:
package c4;
class A1{
int x = 2;
public void setX(int i) {
x = i;
}
void printa()
{
System.out.println("A.x="+x);
}
}
class B1 extends A1{
int x = 100;
void printb(){
super.x = super.x + 10;
System.out.println("super.x="+super.x+" B.x="+x);
}
}
public class c42 {
public static void main(String []args){
A1 a1 = new A1();
a1.printa();
a1.setX(4);
a1.printa();
B1 b1 = new B1();
b1.printb();
b1.printa();
b1.setX(5);
b1.printb();
b1.printa();
b1.printa();
}
}
子类不能继承父类中的静态属性,但可以对父类中的静态属性进行操作。
如在上面的例子中,将int x = 2;
改为static int x = 2;
,再编译及运行程序,会得到下面的结果:
方法覆盖
方法覆盖
子类对父类的允许访问的方法的实现过程进行重新编写,方法覆盖
覆盖方法的方法名称、参数的个数必须和被覆盖的方法一模一样。
仅当返回值为类类型时,重写的方法才可以修改返回值类型,且必须是父类方法返回值的子类。
只需在方法名前面使用不同类的对象名即可区分覆盖方法和被覆盖方法。
覆盖方法的访问权限可比被覆盖的宽松,但是不能更为严格。
父类的成员方法只能被它的子类重写。
子类方法不能比父类方法抛出更多的编译时异常(检查型异常
、非运行异常
)。
必须覆盖的方法
派生类必须覆盖基类中的抽象的方法,否则派生类自身也成为抽象类
不能覆盖的方法
基类中声明为`final`的终结方法
基类中声明为`static` 的静态方法
构造方法
调用被覆盖的方法
super.overriddenMethodName();
方法覆盖的应用场合
子类中实现与父类相同的功能,但采用不同的算法或公式
在名字相同的方法中,要做比父类更多的事情
在子类中需要取消从父类继承的方法
有继承时的构造方法
有继承时的构造方法遵循以下的原则
- 子类不能从父类继承构造方法
- 子类构造方法中调用父类构造方法,调用语句必须出现在子类构造方法的第一行,可使用super关键字
- 如子类构造方法的声明中没有明确调用父类构造方法,则系统在执行子类的构造方法时会自动调用父类的默认构造方法(即无参的构造方法)
- 子类构造方法的第一行语句默认都是:
super()
,不写也存在。
this和super比较
this:代表本类对象的引用
super:代表父类对象的引用
this(…)和super(…)使用注意点:
子类通过 this (…)去调用本类的其他构造方法。
注意:this(…) super(…) 都只能放在构造方法的第一行。
Object 类
Object类
Java程序中所有类的直接或间接父类,类库中所有类的父类,处在类顶层。
包含了所有Java类的公共属性。
Object
类定义了所有对象必须具有的状态和行为,较主要的方法如下
public final Class getClass()
获取当前对象所属的类信息,返回Class对象
public String toString()
返回当前对象本身的有关信息,按字符串对象返回
public boolean equals(Object obj)
比较两个对象是否是同一对象,或者对象是否相等(重写)
protected Object clone( )
生成当前对象的一个拷贝,并返回这个复制对象
public native int hashCode()
返回该对象的哈希码值
protected void finalize() throws Throwable
定义回收当前对象时所需完成的资源释放工作
Object类(续)—hashCode()
public native int hashCode()
- 默认返回对象的32位jvm内存地址,是int类型的散列码。对象的散列码是为了更好地支持基于哈希机制的Java集合类,例如 Hashtable, HashMap, HashSet 等。
native
本地方法,具体的实现在不同的环境下可能不同,获取的hash码也不一定相同,通常都与对象的地址有关。native
表明这个方法要借助其他语言写的方法来实现它的功能。一个native method就是一个Java调用非Java代码的接口。方法的实现由非Java语言实现,比如C或C++。
什么是哈希表?如何实现哈希查找?
哈希表
(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。它通过把关键码值Key映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
Hash (散列)
Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。
这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间。
不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。
Hash算法也被称为散列算法,Hash算法虽然被称为算法,但实际上它更像是一种思想。Hash算法没有一个固定的公式,只要符合散列思想的算法都可以被称为是Hash算法。
散列技术的原理–除留余数法/模运算
哈希算法:用“模运算”计算,得出存储位置。
优点:快速命中搜索的目标
Object类(续)—toString()
public String toString()
返回当前对象本身的有关信息,按字符串对象返回。
public String toString() {
return getClass().getName() + "@" +
Integer.toHexString(hashCode());
}
hashCode()
是一个对象在堆里对应的一个地址。
public static String toHexString(int i)
Integer
类下的一个静态方法,把一个整数转化为16进制表示的字符串。
例:自动调用toString()
package c4;
public class ObjectToString {
public static void main(String args[]) {
ObjectToString ots=new ObjectToString();
System.out.println("ots is:"+ots);
System.out.println("ots's toString is:"+ots.toString());
}
}
Object类—相等和同一(equal & ==)
相等和同一的概念
两个对象具有相同的类型,及相同的属性值,则称二者相等(equal)。
如果两个引用变量指向的是同一个对象,则称这两个变量(对象)同一(identical)。
两个对象同一,肯定相等。
两个对象相等,不一定同一。
比较运算符“==” 判断的是这两个对象是否同一。
==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等
如果作用于引用类型的变量,则比较的是所指向的对象的地址。== 判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象
public boolean equals(Object obj)
所有Java类都继承equals()
方法。
Object类 equals() 方法定义如下,可见,也是判断两个对象是否同一。
public boolean equals(Object x) {
return this == x;
}
equals方法的重写
要判断两个对象各个属性域的值是否相同,则不能使用从Object类继承来的equals方法,而需要在类声明中对equals方法进行重写。
String类中已经重写了Object类的equals方法,可以判别两个字符串是否内容相同。
在BankAccount类中增加equals方法,由于是对Object类中的equals方法进行重写,因此方法定义头必须与Object类中的equals方法完全相同。
实验举例:
编写一Person类,通过重写toString()、equals()、hashcode()方法,实现可以对Person类实例化出的对象进行equals和==的比较。
package test5;
import java.util.Objects;
class person {
String sex;
int age;
String name;
boolean notempty;
public person(String sex,int age,String name){
this.sex = sex;
this.age = age;
this.name = name;
this.notempty = true;
}
@Override
public String toString() {
return "person{" +
"sex='" + sex + '\'' +
", age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
person person = (person) o;
return age == person.age && sex.equals(person.sex) && name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(sex, age, name);
}
public static void main(String args[]){
person p1 = new person("男",20,"张三");
person p2 = new person("男",20,"张三");
System.out.println("p1 is " + p1 );
System.out.println("p2 is " + p2 );
System.out.println(p1.hashCode() == p2.hashCode());
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
}
}
Object clone( )
注:这个知识点我是按照老师的ppt学习的,完整整理后感觉老师的思路比较绕,跳过了一些基础知识,建议先看以下参考博客
参考博客:
https://www.cnblogs.com/ysocean/p/8482979.html
Clone 是 Object 类中的一个方法,通过 对象A.clone() 方法会创建一个内容和对象 A 一模一样的对象 B,clone 克隆,顾名思义就是创建一个一模一样的对象出来。
Person p4 = (Person) p3.clone();
clone方法
对克隆的类实现Cloneable
接口,并重写Object类的clone方法,分为深浅两种方式。
要在clone对象时进行深拷贝
,除了调用父类中的clone方法得到新的对象,还要将该类中的引用变量也clone出来。
如果只是用Object中默认的clone方法,是浅拷贝
的。
Object 类提供的 clone 是只能实现 浅拷贝的。
protected Object clone( )
根据已存在的对象构造一个新的对象
在根类Object中被定义为protected
,所以需要覆盖为public
类被clone,必须实现Cloneable
接口,赋予类被克隆的能力
class MyObject implements Cloneable
{
//…
}
1.为什么进行拷贝
希望生成一个和原来一样的对象,对这个对象的修改不改变原对象的属性值(深拷贝)。
2.深拷贝和浅拷贝的区别
浅拷贝对基本数据类型生成一份新的拷贝;
对引用类型,只是复制了引用,引用所指向的内存空间并没有拷贝。
深拷贝除了复制引用,对引用指向的内存空间也进行拷贝(新内存空间)。
深拷贝把复制对象所引用的对象也进行了拷贝。Line类(属性:Point p1,p2)
通过两个项目示例来分析;
浅拷贝项目举例:
BirthDate.java
文件
package c4.qiankaobei;
public class BirthDate {
private int year;
private int month;
private int day;
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public BirthDate() {
}
public BirthDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public String toString() {
return "BirthDate [year=" + year + ", month="
+ month + ", day=" + day + "]";
}
}
Student.java
package c4.qiankaobei;
public class StudentQianClone implements Cloneable{
private int stuNo;
private String name;
private BirthDate birthday;
public int getStuNo() {
return stuNo;
}
public void setStuNo(int stuNo) {
this.stuNo = stuNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BirthDate getBirthday() {
return birthday;
}
public void setBirthday(BirthDate birthday) {
this.birthday = new BirthDate(); //深复制
this.birthday.setYear(birthday.getYear());
this.birthday.setMonth(birthday.getMonth());
this.birthday.setDay(birthday.getDay());
}
public StudentQianClone() {
}
public StudentQianClone(int stuNo, String name, BirthDate birthday) {
this.stuNo = stuNo;
this.name = name;
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException{
return (StudentQianClone) super.clone();
}
}
package c4.qiankaobei;
public class CopyStudentQianClone {
public static void main(String[] args) throws CloneNotSupportedException {
BirthDate birthday= new BirthDate(2000,1,1);
StudentQianClone s1 = new StudentQianClone(202201,"张三", birthday);
System.out.println(s1.getStuNo());
System.out.println(s1.getName());
System.out.println(s1.getBirthday());
StudentQianClone s2 = (StudentQianClone) s1.clone();
System.out.println(s1.getStuNo());
System.out.println(s1.getName());
System.out.println(s1.getBirthday());
System.out.println("s2==s1:"+ (s2==s1));
System.out.println("s2.birthday==s1.birthday:"+
(s2.getBirthday()==s1.getBirthday()));
}
}
运行截图:
这里我们可以看到,s2和s1的信息完全一样,但是s2和s1并不同一
,并不是一个对象。
但s1的birthday和s2的birthday是同一
的。
以上为浅复制,
深拷贝对引用指向的内存空间也进行拷贝(新内存空间)。深拷贝要把复制的对象所引用的对象也都进行拷贝。
深复制项目举例
项目结构如上:
BirthDateShenClone.java
package c4.shenclone;
public class BirthDateShenClone implements Cloneable{
private int year;
private int month;
private int day;
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public BirthDateShenClone() {
}
public BirthDateShenClone(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public String toString() {
return "BirthDate [year=" + year + ", month="
+ month + ", day=" + day + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return (BirthDateShenClone)super.clone();
}
}
StudentShenClone.java
package c4.shenclone;
public class StudentShenClone implements Cloneable {
private int stuNo;
private String name;
private BirthDateShenClone birthday;
public int getStuNo() {
return stuNo;
}
public void setStuNo(int stuNo) {
this.stuNo = stuNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BirthDateShenClone getBirthday() {
return birthday;
}
public void setBirthday(BirthDateShenClone birthday) {
this.birthday = new BirthDateShenClone(); //深复制
this.birthday.setYear(birthday.getYear());
this.birthday.setMonth(birthday.getMonth());
this.birthday.setDay(birthday.getDay());
}
public StudentShenClone() {
}
public StudentShenClone(int stuNo, String name, BirthDateShenClone birthday) {
this.stuNo = stuNo;
this.name = name;
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException{
StudentShenClone stu =(StudentShenClone) super.clone();
stu.birthday = (BirthDateShenClone)birthday.clone();
return stu;
}
}
CopyStudentShenClone.java
package c4.shenclone;
public class CopyStudentShenClone {
public static void main(String[] args)throws CloneNotSupportedException {
BirthDateShenClone birthday= new BirthDateShenClone(2000,1,1);
StudentShenClone s1 = new StudentShenClone(202201,"张三", birthday);
System.out.println(s1.getStuNo());
System.out.println(s1.getName());
System.out.println(s1.getBirthday());
StudentShenClone s2 = (StudentShenClone)s1.clone();
System.out.println(s1.getStuNo());
System.out.println(s1.getName());
System.out.println(s1.getBirthday());
System.out.println("s2==s1:"+ (s2==s1));
System.out.println("s2.birthday==s1.birthday:"+
(s2.getBirthday()==s1.getBirthday()));
}
}
注意到在深复制后,s1的birthday和s2的birthday不再是同一
的了。
两个项目的StudentShenClone其实是一样的,不一样的点在于BirthDateShenClone对clone方法进行了重写;
思路是:
既然引用类型不能实现深拷贝,那么我们将每个引用类型都拆分为基本类型,分别进行浅拷贝。
Object类(续)—clone方法-String类
String类型的clone比较特殊,这里单独学习;
String不是基本数据类型,那么一个String的内存位置是什么呢?一共有两种情况:
1、String直接赋值:
String s = "aa"; //String实例出来的对象是常量!
s的引用存在栈内存中,引用指向的“aa”存在堆内存的常量池中(先判断常量池中是否有一个“aa”,存在则直接指向),不管创建多少个对象,都是引用常量池的那个字符串。
2、String对象new创建
String s = new String("aa")
s的引用存在栈内存中,引用指向的“aa”对象,存在堆内存中(每new一次,在堆中创建一个新的“aa”对象) ,每个变量都会开辟出新的内存空间来完成储存,不管字符串是否一致。
String克隆的特殊性在那里?StringBuffer
和StringBuilder
呢?
对基本数据类型都能自动实现深拷贝。而对引用类型是浅拷贝。
String
是引用类型的特例。因为String是不允许修改的。所以相当于进行了深拷贝,是安全的。由于String是不可变类,对String类中的很多修改操作都是通过new对象复制处理的。所以当我们修改clone前后对象里面String属性的值时,实际上就指向了新的内存空间。自然对clone前的源对象没有影响,类似于深拷贝。虽然它是引用类型,但是不影响我们深拷贝的使用。
而对于StringBuffer
和StringBuilder
,需要主动进行clone
重写。否则就是浅拷贝。
实现对象克隆的常见方式有哪些,具体怎么做?
常用的方式有三种。
- 通过new一个新的对象,赋值实现深度克隆,繁琐容易出错。
- 对要克隆的类实现
Cloneable
接口,并重写Object
类的clone
方法,分为深浅两种方式。 - 通过
Serializable
接口并用对象的序列化和反序列化来实现真正的深拷贝。
Object类(续)—finalize()方法
protected void finalize() throws Throwable
在对象被垃圾回收器回收之前,系统自动调用对象的finalize方法。
如果要覆盖finalize方法,覆盖方法的最后必须调用super.finalize()
。
Object类(续)—getClass方法
public final Class getClass()
final 方法,返回一个Class对象,用来代表对象隶属的类。
通过Class 对象,可以查询Class对象的各种信息:比如它的名字,它的基类,它所实现接口的名字等。
void PrintClassName(Object obj) {
System.out.println("The Object's class is " +
obj.getClass().getName());
}
终结类与终结方法
终结类与终结方法
被final
修饰符修饰的类和方法
终结类不能被继承
终结方法不能被当前类的子类重写
终结类的特点
不能有派生类
终结类存在的理由
安全: 黑客用来搅乱系统的一个手法是建立一个类的派生类,然后用他们的类代替原来的类。
设计: 你认为你的类是最好的或从概念上你的类不应该有任何派生类。
终结方法的特点
不能被派生类覆盖。
终结方法存在的理由
- 对于一些比较重要且不希望子类进行更改的方法,可以声明为终结方法。可防止子类对父类关键方法的错误重写,增加了代码的安全性和正确性。
- 提高运行效率。当java运行环境(如java解释器)运行方法时,它将首先在当前类中查找该方法,接下来在其超类中查找,并一直沿类层次向上查找,直到找到该方法为止。
抽象类
抽象类
代表一个抽象概念的类
没有具体实例对象的类,不能使用new
方法进行实例化
类前需加修饰符abstract
可包含常规类能够包含的任何东西,例如构造方法,非抽象方法
也可包含抽象方法,这种方法只有方法的声明,而没有方法的实现
抽象方法
声明的语法形式为
public abstract <returnType> <methodName>(...);
仅有方法头,而没有方法体和操作实现
具体实现由当前类的不同子类在它们各自的类声明中完成
抽象类可以包含抽象方法
只有抽象类才能具有抽象方法,即如果一个类中含有抽象方法,则必须将这个类声明为抽象类
抽象类的存在意义
- 基类定义为抽象类,如果不继承并实现其方法,不能创建实例对象,无法使用。
Java里面不鼓励方法体的内容为空。 - 子类在继承父类时,要求子类必须重写父类中的抽象方法,起到一个提醒和约束的作用。
举例1:
举例2:
贷款(Loan)分为许多种类,如
租借(Lease)
、抵押(Mortgage)
、房屋贷款(HouseLoan)
、汽车贷款(CarLoan)
等
将Loan声明为抽象类,并指定所有的子类对象都应具有的行为,如计算月还款值(calculateMonthlyPayment)
,还款(makePayment)
,取得客户信息(getClientInfo)
,其中前两个因贷款种类不同计算方法也不同,可声明为抽象方法,Loan的所有子类都必须对这两个抽象方法进行重写
public abstract class Loan {
public abstract float calculateMonthlyPayment();
public abstract void makePayment(float amount);
public Client getClientInfo() { }
}
泛型
泛型是Java 5的新特性,可以使Java语言变得更加简单、安全,其本质是参数化类型,即所操作的数据类型被指定为一个参数。
泛型可以使用在类、接口以及方法的创建中,分别称为泛型类、泛型方法和泛型接口
泛型类:在类名后面加上<Type>
语法如下:
[访问修饰符] class 类名称 <T1,T2,T3,....>
例:
public class Base <T>
使用:
类名称 <T1,T2,T3,....> obj = new 类名称 <T1,T2,T3,....>( );
例:
Base<String> obj =new Base<String>();
泛型类举例:
package chapter04.chapter04.generics;
class Base<T> {
T m;
Base(T t) {
m = t;
}
public T getM(){
return m;
}
public void print() {
System.out.println("Base Type is: " +
m.getClass().getName());
}
}
public class GenericClass {
public static void main(String[] args) {
Base<String> base=new Base<String>("Type is String");
System.out.println(base.getM());
base.print();
Base<Integer> base1=new Base<Integer>(111);
System.out.println(base1.getM());
base1.print();
}
}
这里的T
在使用的时候决定的是该类的具体类型。
泛型方法
主要用于容器类,Java中任何方法,包括静态的(注意,泛型类不允许在静态环境中使用)和非静态的,均可以用泛型来定义,而且和所在类是否是泛型没有关系。
定义
[public] [static] <T> 返回值类型 方法名(T 参数列表)
举例:
package chapter04.chapter04.generics;
public class GenericMethod {
public static <U> void print(U[] list) {
System.out.println();
for (int i = 0; i < list.length; i++) {
System.out.print(" " + list[i]);
}
}
public static void main(String[] args) {
String a[]={"a","b","c","d","e"};
Character b[]={'1','2','3','4','5'};
Integer c[]={1,2,3,4,5};
GenericMethod.print(a);
GenericMethod.print(b);
GenericMethod.print(c);
}
}
这里的T
决定的是该方法所用的变量的类型
泛型的意义:
优点
:使Java语言变得更加简单、安全。- 在没有泛型的情况下,通常通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是必须做强制的类型转换
要求程序员预先知道实际参数的类型
对于强制类型转换错误的情况,编译器可能不会提示错误,只在运行时才出现异常,从而在代码中存在安全隐患
- 而在使用泛型的情况下,编译器会检查类型是否安全,并且所有的类型转换都是自动和隐式的,可以提高代码的重用率。
通配符泛型和有限制的泛型
在Java 5之前,为了让类具有通用性,往往将属性类型、函数参数、返回类型定义为Object的类
例:
下面我们来了解通配符泛型和有限制的泛型
以下给出一个无法通过编译的错误案例:
package chapter04.chapter04.generics;
class GeneralType1 <Type> {
Type object;
public GeneralType1(Type object) {
this.object = object;
}
public Type getObj() {
return object;
}
}
class ShowType {
//public void showType(GeneralType1<Object> o) {
public void showType(GeneralType1<Object> o) {
System.out.println(o.getObj().getClass().getName());
}
}
public class GenericTypeTester {
public static void main(String args[]){
ShowType st = new ShowType();
GeneralType1 <Integer> i = new GeneralType1 <Integer> (2);
st.showType(i); //这行语句是否合法?
}
}
上面的代码并不能通过编译,这是因为,不能将General<Integer>
类型的变量当做参数传递给General<Object>
事实上,这里能传递的类型只能是General<Object>
。因此,在使用泛型时应该注意和继承类的区别。
使用通配符泛型 可以让showType
函数发挥应有的作用
?
代表任意一种类型,它被称为通配符
修正后的代码:
package chapter04.chapter04.generics;
class GeneralType1 <Type> {
Type object;
public GeneralType1(Type object) {
this.object = object;
}
public Type getObj() {
return object;
}
}
class ShowType {
//public void showType(GeneralType1<Object> o) {
public void showType(GeneralType1<?> o) {
System.out.println(o.getObj().getClass().getName());
}
}
public class GenericTypeTester {
public static void main(String args[]){
ShowType st = new ShowType();
GeneralType1 <Integer> i = new GeneralType1 <Integer> (2);
st.showType(i); //这行语句是否合法?
}
}
有限制的泛型
有时候需要将泛型中参数代表的类型做限制,此时就可以使用有限制的泛型
上限extends
:所指定的类型或者是此类型的子类。
下限super
:指定的类型,或者此类型的父类,或是Object类。
有限制的泛型是指,在参数“Type”后面使用“extends”关键字并加上类名或接口名,表明参数所代表的类型必须是该类的关键字或者实现了该接口
注意,对于实现了某接口的有限制泛型,也是使用extends关键字,而不是implements关键字
继承接口的泛型
interface MyInterface<T> {}
实现接口的时候, 指明泛型
class test1 implements MyInterface<String> {}
实现类也带泛型
class test<T> implements MyInterface<T> {}
类的组合
类的组合
Java的类中可以有其他类的对象作为成员,这便是类的组合
组合的语法很简单,只要把已存在类的对象放到新类中即可
可以使用has a
语句来描述这种关系
例如,考虑Kitchen类提供烹饪和冷藏食品的功能,很自然的说“my kitchen ‘has a’ cooker/refrigerator”。所以,可简单的把对象myCooker和myRefrigerator放在类Kitchen中。格式如下
class Cooker{ // 类的语句 }
class Refrigerator{ // 类的语句}
class Kitchen{
Cooker myCooker;
Refrigerator myRefrigerator;
}
组合项目实例:
项目结构如图:
Point.java
package chapter04.chapter04.zuhe.line;
public class Point {
private int x, y;
public Point(int x, int y) { this.x = x; this.y = y;}
public int GetX() { return x; }
public int GetY() { return y; }
}
Line.java
package chapter04.chapter04.zuhe.line;
public class Line {
private Point p1,p2; // 两端点
Line(Point a, Point b) {
p1 = new Point(a.GetX(),a.GetY());
p2 = new Point(b.GetX(),b.GetY());
}
public double Length() {
return Math.sqrt(Math.pow(p2.GetX()-p1.GetX(),2)
+ Math.pow(p2.GetY()-p1.GetY(),2));
}
}
LineTester.java
package chapter04.chapter04.zuhe.line;
public class LineTester {
public static void main(String args[]) {
Point p1 = new Point(1,2);
Point p2 = new Point(11,18);
Line l1 = new Line(p1,p2);
System.out.println(String.format("line's length :%.2f ",l1.Length()));
}
}
组合与继承的比较
- “包含”关系用组合来表达
如果想利用新类内部一个现有类的特性,而不想使用它的接口,通常应选择组合,我们需在新类里嵌入现有类的private对象
如果想让类用户直接访问新类的组合成分,需要将成员对象的属性变为public - “属于”关系用继承来表达
取得一个现成的类,并制作它的一个特殊版本。通常,这意味着我们准备使用一个常规用途的类,并根据特定需求对其进行定制
组合与继承的结合
许多时候都要求将组合与继承两种技术结合起来使用,创建一个更复杂的类
复杂对象调用构造方法的顺序为:
- 调用基类构造方法。这个步骤会不断反复递归执行下去,首先是构造层次结构的根,然后是下一层导出类,等等,直到最底层的导出类;
我--》父亲--》爷爷--》曾祖父--》高祖父--》太祖父
- 按照声明顺序调用成员(其他类实例对象)的构造方法;
- 调用自身类的构造方法。
举例:
package chapter04.chapter04.zuhe.jicheng;
class Meal{
Meal() {
System.out.println("Meal Constructor");
}
}
class Bread{
Bread(){
System.out.println("Bread Constructor");
}
}
class Cheese{
Cheese(){
System.out.println("Cheese Constructor");
}
}
class Lettuce{
Lettuce(){
System.out.println("Lettuce Constructor");
}
}
class Lunch extends Meal{
Lunch(){
System.out.println("Lunch Constructor");
}
}
class PortableLunch extends Lunch{
PortableLunch(){
System.out.println("PortableLunch Constructor");
}
}
public class ZuHeJiChengOrder extends PortableLunch {
private Bread b=new Bread();
private Cheese c=new Cheese();
private Lettuce l=new Lettuce();
public ZuHeJiChengOrder(){
System.out.println("ZuheJichengOrder Constructor");
}
public static void main(String[] args){
new ZuHeJiChengOrder();
}
}
JAVA基本库
Java基础类库
Java提供了用于语言开发的类库,称为Java基础类库(JFC
,Java Foundational Class) ,也称应用程序编程接口(API
,Application Programming Interface),分别放在不同的包中
Java提供的包主要有
java.lang,java.io,java.math,java.util
java.applet,java.awt,java.awt.datatransfer
java.awt.event,java.awt.image,java.beans
java.net,java.rmi,java.security,java.sql等
java语言包 java.lang
语言包(java.lang)
语言包java.lang提供了Java语言最基础的类,包括
Object类
数据类型包裹类(the Data Type Wrapper)
字符串类(String、StringBuffer)
数学类(Math)
系统和运行时类(System、Runtime)
类操作类(Class,ClassLoader)
数据包裹类
对应Java的每一个基本数据类型(primitive data type)
都有一个数据包裹类
数据包裹类的意义:
将一个基本数据类型的数据转换成对象的形式,使它们可以像对象一样参与运算和传递。
每个包裹类都只有一个类型为对应的基本数据类型的属性域
生成数据类型包裹类对象的方法
从基本数据类型的变量或常量生成包裹类对象
double x = 1.2;
Double a = new Double(x);
Double b = new Double(-5.25);
从字符串生成包裹类对象
Double c = new Double("-2.34");
Integer i = new Integer("1234");
已知字符串,可使用valueOf方法将其转换成包裹类对象:
Integer.valueOf("125");
Double.valueOf("5.15");
自动装箱
Integer i =3; Double d = -5.25
得到基本数据类型数据的方法
每一个包裹类都提供相应的方法将包裹类对象转换回基本数据类型的数据
anIntegerObject.intValue() // 返回 int类
aCharacterObject.charValue() // 返回 char类型的数据
Integer、Float、Double、Long、Byte 及Short 类提供了特殊的方法能够将字符串类型的对象直接转换成对应的int、float、double、long、byte或short类型的数据
Integer.parseInt(“234”) // 返回int类型的数据
Float.parseFloat(“234.78”) // 返回float类型的数据
自动拆箱
Integer a = new Integer(3); int i = a;
装箱与拆箱
装箱(boxing):把基本类型数据包装成对象,如:
Integer obj = new Integer(8);
拆箱(unboxing):把对象内部基本数据提取出来。
要先装后拆,如:
Boolean objb = new Boolean(false); //装箱
boolean b = objb.booleanValue(); //拆箱
装箱的两种做法:
(1)调用数据封装类构造方法,如:
Integer obj = new Integer(i),设int i = 8。
(2)(应优先采用)调用封装类数据转对象的静态方法,如:
Integer obj = Integer.valueOf(i)。
数据封装类还提供一些静态常量字段,如:
Integer.MAX_VALUE:int型最大值2147483647。
Integer.MIN_VALUE:int型最小值-2147483648。
数据封装类还提供与String类型转换的方法。如 Integer类方法:
toString(int i)、 parseInt(String s)
自动装箱和自动拆箱
基本数据和对应封装类对象之间的自动转换。如:
Integer obj = 8; //自动装箱,
相当于 Integer obj = new Integer(8);
自动拆箱例:
int i = obj; //自动拆箱,拆箱后再赋值
int sum = obj + obj; //自动拆箱,拆箱后再进行加法运算
Integer obj2 = obj + obj; //自动拆箱后相加,再自动装箱为对象
相当于下面拆箱、装箱语句:
int i = obj.intValue();
int sum = obj.intValue() + obj.intValue();
Integer obj2 = Integer.valueOf(obj.intValue() + obj.intValue());
正是有了自动装箱操作,才能使默认元素类型为 Object 的 ArrayList 集可以直接添加char、int和double等基本类型数据。
注:自动装箱和拆箱会降低程序运行效率,要慎用。
String类
String类
该类字符串对象的值和长度都不变化
称为常量字符串
生成String类对象的方法
可以这样生成一个常量字符串
String aString;
aString = “This is a string”
调用构造方法生成字符串对象
new String();
new String(String value);
new String(char[] value);
new String(char[] value, int offset, int count);
new String(StringBuffer buffer);
String类的常用方法
变量字符串类StringBuffer
StringBuffer
类
其对象是可以修改的字符串
字符的个数称为对象的长度(length
)
分配的存储空间称为对象的容量(capacity
)
与String
类的对象相比,执行效率要低一些
该类的方法不能被用于String类的对象
生成StringBuffer类的对象
new StringBuffer();
//生成容量为16的空字符串对象
new StringBuffer(int size);
//生成容量为size的空字符串对象
new StringBuffer(String aString);
//生成aString的一个备份,容量为其长度 +16
数学类(Math)
数学类
提供一组常量和数学函数,例如
E和PI常数
求绝对值的abs
方法
计算三角函数的sin方法和cos方法
求最小值、最大值的min方法和max方法
求随机数的random方法等
其中所有的变量和方法都是静态的(static)
是终结类(final),不能从中派生其他的新类
计算随机数
java.lang.Math.random();
java.util.Random类
获取系统时间System.currentTimeMillis()
java.lang.Math.random();
调用这个Math.random()函数能够返回带正号的double值,该值大于等于0.0且小于1.0,即取值范围是[0.0,1.0)的左闭右开区间,返回值是一个伪随机选择的数,在该范围内(近似)均匀分布。
java.util.Random
类
Random类的两种构造方法:
Random()
:创建一个新的随机数生成器。
Random(long seed)
:使用单个 long 种子创建一个新的随机数生成器。
可以在构造Random对象的时候指定种子,如:
Random r1 = new Random(20);
或者默认当前系统时间的毫秒数作为种子数:
Random r1 = new Random();
需要说明的是:在创建一个Random对象的时候可以给定任意一个合法的种子数,种子数只是随机算法的起源数字,和生成的随机数的区间没有任何关系。
java.util.Random
类方法
生成[0,1.0)区间的小数:
double d1 = r.nextDouble();
生成[0,5.0)区间的小数:double d2 = r.nextDouble() * 5;
生成[1,2.5)区间的小数:double d3 = r.nextDouble() * 1.5 + 1;
生成随机整数:int n = r.nextInt();
生成[0,10)区间的整数:
int n2 = r.nextInt(10);//方法一
n2 = Math.abs(r.nextInt() % 10);//方法二
JAVA实用包(java.util)
实用包(java.util)——实现各种不同实用功能
日期类:描述日期和时间
Date
Calendar
GregorianCalendar
集合类
Collection(无序集合)、Set(不重复集合)
List(有序不重复集合)、Enumeration(枚举)
LinkedList(链表)、Vector(向量)
Stack(栈)、Hashtable(散列表)、TreeSet(树)
StringTokenizer类
允许以某种分隔标准将字符串分隔成单独的子字符串
Date类
构造方法
Date()
获得系统当前日期和时间值。
Date(long date)
以date创建日期对象,date表示从GMT(格林威治)时间1970-1-1 00:00:00开始至某时刻的毫秒数
常用方法
getTime()
返回一个长整型表示时间,单位为毫秒(millisecond)
after(Date d)
返回接收者表示的日期是否在给定的日期之后
before(Date d)
返回接收者表示的日期是否在给定的日期之前
JAVA文本包(java.text)
提供各种文本或日期格式,包含
Format类(抽象类)
DateFormat类(抽象类)
DateFormat df = DateFormat.getDatelnstance();
//得到SimpleDateFormat对象
SimpleDateFormat类(是DateFormat的子类)
使用已定义的格式对日期对象进行格式化
构造方法 以一指定格式的字符串作为参数
new java.text.SimpleDateFormat(formatString);
format(Date d) 将此种格式应用于给定的日期
aSimpleDateFormat.format(aDate);
自定义包
自定义包
包是一组类的集合,利用包来管理类,可实现类的共享与复用
同一包中的类在默认情况下可以互相访问,通常把需要在一起工作的类放在一个包里
在实际使用中,用户可以将自己的类组织成包结构
包的声明
包名通常全部用小写字母
且每个包的名称必须是“独一无二”的,为避免包名冲突,可将机构的Internet域名反序,作为包名的前导
例如:
cn.edu.tsinghua.computer.class0301
声明语句
package mypackage;
说明当前文件中声明的所有类都属于包mypackage
此文件中的每一个类名前都有前缀mypackage,即实际类名应该是mypackage.ClassName
,因此不同包中的相同类名不会冲突