一、简学
一、基础认识与安装
Java,划分为三个技术平台,Java SE 标准版、Java EE 企业版、Java ME 微型版。学习使用的是SE。Java的跨平台性:Java通过JVM(Java Virtual Machine,Java虚拟机)以及字节码实现跨平台性。Java程序由javac编译器编译为字节码文件(.class文件),JVM中的Java解释器会将字节码文件翻译成所在平台上的机器码文件,执行对应的机器码文件就可以了。Java程序只要一次编写,就可以到处运行。Java的跨平台能力依赖于为每个目标平台提供的JVM。字节码文件不是直接的机器代码而是一种更高级易于解析的指令集。
JDK(Java Development Kit,Java开发工具)。JDK包括Java编译器、Java运行器、Java文档生成工具、Java打包工具等。JRE(Java Runtime Environment,Java运行时环境)工具,它提供给普通用户使用的Java运行环境。与JDK相比,JRE中只包含Java运行工具,不包含Java编译工具。为了方便,JDK中封装了JRE,即Java开发环境包含了Java运行环境,只需安装JDK即可。
第一个程序
用文本编辑器编写源代码,保存成.java格式
class hello{
public static void main(String[] args){
System.out.println("hello world!");
}
}
1.javac命令编译Java源程序,javac hello.java 生成.class文件
2.java命令运行程序,java hello
这张图片显示的是一个Java源代码文件,其中包含两个类:Hello和HelloTest。以下是关于这个源文件的一些问题及其答案:
1) 上述源文件的名称应该是什么?
答:根据Java命名规范,源文件名应该与公共类(public class)的名字相同,并且扩展名为.java。在这个例子中,公共类是Hello,所以源文件的名称应该是Hello.java。
2) 上述源文件编译后生成几个字节码文件?这些字节码文件的名字都是什么?
答:编译后会生成两个字节码文件,因为源文件中有两个类。这两个字节码文件分别是Hello.class 和 HelloTest.class。
3) 在控制台下执行java Hello 得到怎样的错误提示?执行java HelloTest.class 得到怎样的错误提示?执行java HelloTest得到什么输出结果?
答:
- 执行`java Hello`时,由于没有main方法,会得到以下错误提示:
```
Error: Main method not found in class Hello, please define the main method as:
public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application
```
- 执行`java HelloTest.class`时,由于命令格式不正确,会得到以下错误提示:
```
Error: Could not find or load main class HelloTest.class
Caused by: java.lang.ClassFormatError: Truncated class file
```
- 正确的执行方式是`java HelloTest`,这将打印出 "I'm glad to meet you" 的消息,因为Hello类中的speakHello()方法被调用。
二、数据类型与运算符(一)
2.1.Java中的常量
常量就是在程序中固定不变的值,是不能改变的数据。例如,数字1、字符'a'、浮点数3.2等都是常量。在Java中,常量包括整形常量、浮点型常量、字符和字符串常量、布尔常量、null常量。
2..2.Java中的变量
1.定义
程序运行时,会产生一些临时数据,应用程序会将这些数据保存在内存单元中,每个内存单元都用一个标识符标识,这些用于标识内存单元中,每个内存单元的标识符就称为变量,内存单元中存储的数据就是变量的值。
int x=0,y;
y=x+3;
2.数据类型
四种基本数据类型是Java内嵌的,在任何操作系统中都具有相同大小和属性;而引用数据类型是在Java程序中由编程人员自己定义的类型。
1.整形类型:
注意:在long类型变量赋值时,若值超出int类型的取值范围,值后面要加L(或l),若未超出,则可忽略。
2.浮点数类型变量用于存储实数
double比float更精确
E(或者小写e)表示以10为底的指数,E后的+、-代表正指数和负指数。
例:
注意:在为浮点数类型变量赋值时,float类型变量值后面必须加 F 或 f;double后面可以加上D或d,也可以不加。在程序中也可以为一个浮点数类型变量赋予一个整数值,JVM会自动将整数数值转化为浮点数类型的值。
float f=123.4f;
double d1=100.1;
double d2=199.3d;
float f=100;
double d=100;
3.字符型变量
在Java中,字符型变量用char表示,用于存储一个单一字符。Java中每个字符型的变量都会占用2字节。
在计算机的世界里,所有文字、数值都会是一连串的0和1,这些0和1是机器语言,人类难以理解,于是就产生了各种方式的编码,用一个数值代表某个字符,如常用的字符编码系统ASCII。
不同的编码系统可能会使用相同的数值标识不同的字符,这样数据跨平台时就会发生错误。Unicode字符码系统定义了绝大部分现存语言需要的字符,是一种通用的字符集,可以同时处理多种语言混合的情况。Java使用的就是Unicode字符码系统,在计算时,计算机会自动将字符转化为对应的数值。
char c='a';
char ch=97; //相当于赋值为字符a
4.布尔型变量
boolean flag=false; //定义一个布尔型的变量flag,初值为false
flag=true; //改变变量flag的值为true
3.变量的类型转换
1.自动类型转换,也叫隐式类型转换,指的是两种数据类型在转换的过程中不需要显式地进行声明,由编译器自动完成。
1)两种数据类型彼此兼容。
2)目标类型的取值范围大于源类型的取值范围。
byte b=3;
int x=b;
1)整数类型之间可以实现转换。byte→short、int、long;short、char→int、long;int→long。
2)整数类型转换成float类型。byte、char、short、int→float。
3)其他类型转换为double类型。byte、char、short、int、long、float→double。
2.强制类型转换,也叫显式类型转换,指的是两种数据类型之间的转换需要显式地声明。当两种类型彼此不兼容,或者目标类型取值范围小于源类型时,自动类型转换无法进行,就需要强制类型转换。
//目标类型 变量 = (目标类型)值;
int num=4;
byte b=(byte)num;
int b=298;
byte a=(byte)num;
//则a=42,原因如下图
即,int占用4字节在内存中,当变量被强制转换为byte类型会丢失前面3个高位字节的数据。
3.表达式类型的自动提升
所谓表达式是指由变量和运算符组成的一个算式。变量在表达式中进行运算可能发生自动类型转换,这就是表达式数据类型的自动提升。例如,一个byte类型的变量在运算期间会自动提升为int类型。
//报错代码
byte b1=3;
byte b2=4;
byte b3=b1+b2; //报错
//运算期间,变量b1和b2自动提升为int类型,需强制转换
//byte b3 =(byte)(b1+b2);
4.变量作用域(p40)
在程序中,变量一定会被定义在一对大括号中,该大括号所包含的代码区域便是这个变量的作用域
2.7.数组
1.数组中的基本要素
//数组类型[] 数组名;
//数组名 =new 数组类型[长度]
int[] x;
x=new int[100];
//即 int[] x=new int[100];
“数组名.length”的方式获得数组的长度,即元素的个数。
2.简单使用
不同类型数组元素的默认初始值:
不使用默认初始值,也可以显式地为这些元素赋值。
int [] arr = new int[4];
arr[0] = 1;
在定义数组时,只指定数组长度,由系统自动为元素赋初始值的方式称作动态初始化。在初始化数组时,还有一种方式叫作静态初始化,就是定义数组的同时为数组的每个元素赋值。
//类型[] 数组名 = new 类型[]{元素,元素,……};
//类型[] 数组名 = {元素,元素,……};
拓展.数组索引越界
数组是一个容器,存储到数组中的每个元素都有自己的自动编号,最小值为0,最大值为数组长度减1,不得超出范围,否则会报错。如果要访问数组存储的元素,必须依赖索引。
3.数组的常见操作
1.遍历
常用循环语句完成数组的遍历。
2.数组中最值的获取
int[] arr={4,1,6,3,9,8};
int max=arr[0];
for(int i=1;i<arr.length;i++){ //遍历数组查找最大值
if(arr[i]>max){
max=arr[i];
}
}
3.在数组指定位置插入一个数据
现有数组 int[ ] arr={10,11,13,14,15},要将12插入到索引2的位置:
1)初始化数组长度为5,首先创建一个长度为6的数组。
2)再将原数组值复制到新的数组中,同时将指定位置后的元素依次向后移动一个元素的位置。
3)最后将目标元素保有到指定位置。
int[] arr={10,11,13,14,15};
int score=12;
int[] arr2=new int[arr.length+1];
for(int i=0;i<3;i++){
arr2[i]=arr[i];
}
arr2[2]=score;
for(int i=3;i<arr2.length;i++){
arr2[i]=arr[i-1];
}
4.数组排序
常见方法——冒泡排序。即不断地比较数组中相邻的两个元素,较小者上浮,较大者下沉。
第一步,从第一个元素开始,依次对相邻的两个元素进行比较,直到最后两个元素完成比较。如果前一个元素比后一个大,则交换他们的位置。整个过程完成后,数组中的最后一个元素自然就是最大值,这样就完成了第一轮比较。
第二步,除了最后一个元素外,对剩余的元素进行两两比较,过程与第一步相似,这样就可以将数组中的第二大的元素放在倒数第二的位置。
第三步,以此类推,重复步骤,直到没有任何一对元素需要比较为止。
int[] arr={9,8,3,5,2};
//进行冒泡排序
//外层循环定义需要比较的轮数(两数对比,要比较n-1轮)
for(int i=1;i<arr.length;i++){
//内层循环定义第i轮需要比较的两个数
for(int j=0;j<arr.length-i;j++){
if(arr[j]>arr[j+1]){
//下面3行代码用于交换两个元素
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
假设 j=1,定义变量 int temp
4.二维数组
多维数组可以简单的理解为在数组中嵌套数组,即数组的元素是一个数组。
1)
//数据类型[][] 数组名=new 数据类型[行数][列数]
int[][] xx=new int[3][4] //定义了一个3x4的二维数组
2)列数不确定
//数据类型[][] 数组名=new 数据类型[行数][]
int[][] xx=new int[3][]
3)定义一个确定元素值的二维数组
//数据类型[][] 数组名={{第0行初始值},{第1行初始值},…,{第n行初始值}};
int[][] xx={{1,2},{3,4,5,6},{7,8,9}};
访问二维数组xx中第一个元素数组的第二个元素 → xx[0][1];
例:统计一个公司3个销售小组中每个小组的销售额,以及总销售额:
int[][] arr=new int[3][];
arr[0]=new int[]{11,12};
arr[1]=new int[]{21,22,23};
arr[2]=new int[]{31,32,33,34};
int sum=0;
for(int i=0;i<arr.length;i++){ //遍历数组元素
int groupSum=0;
for(int j=0;j<arr[i].length;j++){ //遍历小组内每个人的销售额
groupSum=groupSum+arr[i][j];
}
sum=sum+groupSum; //累加小组销售额
}
2末.课后题
1、定义一个整型的长度为6的一维数组k[6],并将数组中元素k[i] 值初始化为i。然后,将元素k[3]打印出来。
public class a1 {
public static void main(String[] args){
int[] k=new int[6];
for(int i=0;i<6;i++){
k[i]=i;
}
System.out.println(k[3]);
}
}
2、定义一个整型的长度为3 x 4的二维数组k[3][4],并将数组中元素k[i][j] 值初始化为值ixj。然后,将元素k[2][3]打印出来。(可以直接赋值)
public class a2 {
public static void main(String[] args){
int[][] k=new int[3][4];
int i,j;
for(i=0;i<3;i++){
for(j=0;j<4;j++){
k[i][j]=i*j;
}
}
System.out.println(k[2][3]);
}
}
3、从命令行输入几个字符串,统计并打印出输入字符串的个数、以及各字符串的字符个数。(提示:args.length / args[k].length() )
public class a3 {
public static void main(String[] args){
System.out.println(args.length);
for(int i=0;i<args.length;i++){
System.out.println(args[i].length());
}
}
}
4、从命令行输入一个数字,判断它是奇数还是偶数。(提示:利用%;三元条件 或 if/else ; int a=Integer.parseInt(args[0]) //数据输入)
public class a4 {
public static void main(String[] args){
int a=Integer.parseInt(args[0]);
if(a%2==1){
System.out.println("奇数");
}else{
if(a%2==0)
System.out.println("偶数");
}
}
}
5、从命令行输入两个数字,判断两个数谁大谁小。(提示:读输入参数args[];三元条件 或 if/else )
public class a5 {
public static void main(String[] args){
int a=Integer.parseInt(args[0]);
int b=Integer.parseInt(args[1]);
if(a>b){
System.out.println(a+"大");
}else{
if(a==b)
System.out.println("等大");
else {
System.out.println(b+"大");
}
}
}
}
三、数据类型与运算符(二)
2.4.选择结构语句
1. if 条件语句
1.if 语句
2. if...else 语句
3. if...else if...else 语句
2.三元运算符
判断条件 ?表达式1:表达式2;
1)条件运算符 “ ?” 和“ :”是一对运算符,不能分开单独使用。
2)条件运算符的优先级低于关系运算符与算术运算符,但高于赋值运算符。
3)条件运算符可以进行嵌套。如:
a>b?a:c>d?C:d; //即:a>b?a:(c>d?c:d);
3.switch条件语句
switch(表达式){
case 目标值 1:
执行语句
break;
case 目标值 2:
执行语句 2
break;
...
case 目标值 n:
执行语句 n
break;
deafult:
执行语句 n+1
break;
}
//switch语句将表达式的值与每个case中的目标值进行匹配。
//break的作用是跳出switch语句。
2.5循环语句结构
1.while循环语句
只有循环条件成立,{ }内执行语句就会执行,直到循环条件不成立,while循环结束。
2.do...while循环语句
循环体会无条件的执行一次,然后再根据循环条件决定是否继续执行。
3.for循环语句
一般用在循环次数已知的情况。
4.while、do...while和for循环的区别
相同点:这三种循环都遵循循环四要素,即初始化循环变量、循环条件、循环体、更新循环变量。
不同点:1) while和do...while适用于循环次数不确定的场景;for适用于循环次数确定的场景。 2) while和for先判断循环条件,再执行循环体;do...while先执行循环体,再判断循环条件
5.循环嵌套
while、do...while、for循环语句都可以嵌套,并且他们之间也可以互相嵌套,其中常见的是在for循环中嵌套for循环。
6.跳转语句
跳转语句用于实现循环执行过程中程序流转的跳转。Java中的跳转语句有break语句和continue语句。
1.break语句
当它出现在switch语句中,作用是终止某个case并跳出switch结构。当它出现在循环语句时,作用是跳出循环语句,执行循环后面的代码。当break语句出现在嵌套循环时,它只能跳出内层循环。如果想使用break语句跳出外层循环,则需要在外层循环中使用break语句。
2.continue语句
continue语句用在循环语句中,它的作用是终止本次循环,执行下一次循环。
2.6 方法
1 什么是方法
为了不使程序变得很臃肿和可读性差。通常会将要频繁用到的一套代码提取出来,放在一对大括号中,并为这段代码起个名字,提取出来的代码可以看作程序中定义的一个方法。有些书中把方法称为函数。格式如下:
修饰符:方法的修饰符比较多,如 对访问权限进行限定的修饰符、static修饰符、final修饰符等。
返回值类型:用于限定方法返回值的数据类型。
参数类型:用于限定调用方法时,传入参数的数据类型。
参数名:是一个变量,用于接收调用方法时传入的数据。
return关键字:用于返回方法指定类型的值并结束方法。
返回值:被return语句返回的值,该值会返回给调用者。
需要注意的是,方法中的“参数类型 参数名1,参数类型 参数名2,...”称作参数列表,参数列表用于描述方法在被调用时,需要接收的参数,如果方法不需要接收任何参数,则参数列表为空,即() 不写任何内容。方法的返回值类型必须是方法声明的返回值类型。如果方法没有返回值,返回值类型要声明void,此时,方法中的return语句可以省略。
2 方法的重载
在同一个作用域内,方法名相同,但参数个数或者参数类型不同的方法。
3末课后题
1) 角谷猜想:任何一个正整数n,如果它是偶数则除以2,如果是奇数则乘3加1,这样得到一个新的整数,如此继续进行上述处理,则最后得到的数一定是1。编写应用程序证明:在3~10000之间的所有正整数都符合上述规则。
public class b1 {
public static void main(String[] args){
int a=Integer.parseInt(args[0]);
while(true){
if(a%2==0){
a=a/2;
} else if (a%2==1) {
a=a*3+1;
}
if (a==1){
System.out.println("符合角谷猜想");
break;
}
}
}
}
2) 编写一个模拟同时掷骰子的程序。要用Math.random()模拟产生两个骰子,将两个结果相加,相加的和等于7的可能性最大,等于2和12的可能性最小。程序模投掷3600次,判断求和的结果是否合理。
public class b2 {
public static int rollDice() {
return (int) (Math.random() * 6) + 1;
}
public static void main(String[] args){
int sum=3600,i,f=0,X=0;
int[] n=new int[13];
for(i=0;i<3600;i++){
int p1=rollDice();
int p2=rollDice();
int num=p1+p2;
n[num]++;
}
for(int x=2;x<13;x++){
System.out.println(x+"的次数为:"+n[x]);
}
System.out.println();
System.out.println();
for(int x=2;x<13;x++){
f=f+n[x];
}
System.out.println("总的次数为:"+f);
System.out.println();
int max=n[2];
for(int x=2;x<13;x++){ //遍历数组查找最大值
if(n[x]>max){
max=n[x];
X=x;
}
}
System.out.println("次数最大的为:"+X+" 即:"+max);
for(int x=2;x<4;x++){
//内层循环定义第i轮需要比较的两个数
for(int j=12;j>0;j--){
if (n[j]<n[j-1]){
//下面3行代码用于交换两个元素
int temp=n[j];
n[j]=n[j-1];
n[j-1]=temp;
}
}
System.out.println("次数最小为:"+n[x]);
}
}
}
public class b3 {
public static void main(String[] args){
for(int i=1;i<11;i++){
for(int j=0;j<11-i;j++){
System.out.print(" ");
}
for(int x=i;x>0;x--){
System.out.print("*");
}
System.out.println();
}
System.out.println();
for(int i=0;i<10;i++){
for(int x=i;x>0;x--){
System.out.print("*");
}
System.out.println();
}
for(int i=1;i<11;i++){
for(int j=0;j<11-i;j++){
System.out.print("*");
}
for(int x=i;x>0;x--){
System.out.print(" ");
}
System.out.println();
}
System.out.println();
for(int i=0;i<10;i++){
for(int x=i;x>0;x--){
System.out.print("*");
}
System.out.println();
}
for(int i=0;i<10;i++){
for(int x=i;x>0;x--){
System.out.print(" ");
}
for(int j=0;j<10-i;j++){
System.out.print("*");
}
System.out.println();
}
}
}
4)编程:读取一个星号的长度,采用循环语句打印:
public class b4 {
public static void main(String[] args){
for(int i=1;i<11;i++){
for(int x=10-i;x>0;x--){
System.out.print(" ");
}
for(int j=0;j<2*i-1;j++){
System.out.print("*");
}
for(int x=10-i;x>0;x--){
System.out.print(" ");
}
System.out.println();
}
System.out.println();
System.out.println();
for(int i=1;i<10;i++){
for(int x=10-i;x>0;x--){
System.out.print(" ");
}
for(int j=0;j<2*i-1;j++){
System.out.print("*");
}
for(int x=10-i;x>0;x--){
System.out.print(" ");
}
System.out.println();
}
for(int i=10;i>0;i--){
for(int x=10-i;x>0;x--){
System.out.print(" ");
}
for(int j=0;j<2*i-1;j++){
System.out.print("*");
}
for(int x=10-i;x>0;x--){
System.out.print(" ");
}
System.out.println();
}
}
}
5) 编程:读取一个矩形的长度,然后输出一个空心矩形。
public class b5 {
public static void main(String[] args){
int a=Integer.parseInt(args[0]);
for(int j=0;j<a;j++){
System.out.print("*");
}
System.out.println();
for(int j=0;j<a-2;j++){
System.out.print("*");
for(int x=0;x<a-2;x++){
System.out.print(" ");
}
System.out.print("*");
System.out.println();
}
for(int j=0;j<a;j++){
System.out.print("*");
}
}
}
6) 编程:读取一个矩形外边和内边的长度, 然后输出一个空心矩形。
import java.util.Scanner;
public class b6 {
public static void main(String[] args){
int l,k,i,o,d;
System.out.print("Please enter outside 外层:");
Scanner in= new Scanner(System.in);
int outside=in.nextInt();
System.out.print("Please enter inside 内层 (inside<outside):");
int side=in.nextInt();
if(1==(outside-side)%2) o=1;
else o=0;
for(i=0;i<(outside-side-o)/2+1;i++){
for(l=0;l<outside;l++){
System.out.print("*");
}
System.out.println();
}
for(i=0;i<side-2;i++){
for(l=0;l<(outside-side-o)/2;l++)
System.out.print("*");
for(k=0;k<side;k++){
System.out.print(" ");
}
for(l=0;l<(outside-side-o)/2+o;l++)
System.out.print("*");
System.out.println();
}
for(i=0;i<(outside-side-o)/2+o+1;i++){
for(l=0;l<outside;l++){
System.out.print("*");
}
System.out.println();
}
}
}
四、面向对象(上)
3.1 面向对象的思想
面向对象是一种符合人类思维习惯的编程思想。现实生活中存在各种形态的事物,这些事物之间存在着各种各样的联系。在程序中使用对象映射现实中的事物,使用对象的关系,描述事物之间的联系,这种思想就是面向对象。
面向对象的特性可以概括为封装性、继承性和多态性:
1.封装性
封装是面向对象的核心思想。它有两层含义:一层含义是把对象的属性和行为看成一个密不可分的整体,将两者“组合”在一起(即封装在对象中);另一层含义指信息隐藏,将不想让外界知道的信息影藏起来,例如,学开车时,只需知道如何操作汽车,不必知道汽车内部是如何工作的。
2.继承性
继承性主要描述的是类与类之间的关系。通过继承,可以在原有类的基础上对功能进行扩展。继承不仅增强了代码的复用性,提高了开发效率,还降低了程序产生错误的可能性,为了程序的维护以及扩展提供便利。
3.多态性
多态性是指在一个类中定义的属性和方法被其他类继承后,它们可以具有不同的数据类型或表现出不同的行为,这使得同一个属性和方法在不同的类中具有不同的语义。多态性使程序更抽象、便捷,有助于开发人员设计程序时分组协同开发。
面向对象的思想仅靠上面的介绍是无法真正理解的,只有通过大量的实践才能真正领悟面向对象的思想。
3.2 类与对象
在面向对象技术中,为了做到让程序对事物的描述与事物在现实中的形态保持一致,提出两个概念,即类与对象。
在Java程序中,类和对象是最基本、最重要的单元。类表示某类群体的一些基本特征抽象,对象表示一个个具体的事物。
类用于描述多个对象的共同特征,它是对象的模板。对象用于描述现实中的个体,它是类的实例。对象是根据类创建的,一个类可以对应多个对象。
1.类的定义
面向对象的思想中最核心的就是对象,创建对象的前提是定义一个类。类是Java中一个重要的应用数据类型,也是组成Java程序的基本要素,所有的Java程序都是基于类的。
类是对象的抽象,用于描述一组对象的共同特征和行为。
类中可以定义成员变量和成员方法。成员变量用于描述对象的特征,成员变量也被称作对象的属性;成员方法用于描述对象的行为,可简称为方法。
class 类名{
成员变量;
成员方法;
}
注意:局部变量与成员变量的不同
在Java中,定义在类中的变量称为成员变量,定义在方法中的变量称为局部变量。如果在某个方法中定义的局部变量与成员变量同名,这种情况是允许的,此时在方法中通过变量名访问的是局部变量,而非成员变量:
class Student{
int age=30; //类中定义变量为成员变量
void read(){
int age=50; //方法中定义变量为局部变量
System.out.println("大家好,我"+age+"岁了!");
}
}
//即 当一个程序调用read()方法时,输出50,而不是30.
2.对象的创建与使用
要想使用一个类,则必须创建类的对象。使用new关键字创建对象:
类名 对象名 = null;
对象名 = new 类名();
//创建对象 分为声明对象和实例化对象两步
//直接创建
类名 对象名 =new 类名();
//Student stu = new Student();
上述代码,new Student( ) 用于创建一个Student类的一个实例对象(称为Student对象),Student stu 声明了一个Student类的变量stu。运算符 = 将创建的实例化对象地址赋值给变量stu,变量stu引用的对象简称为stu对象。
例.使用类创建对象:
class Student{
String name;
void read(){
System.out.println("大家好,我是"+name+",我在看书!");
}
}
public class Test{
public static void main(String[] args){
Student stu=new Student(); //创建并实例化Student对象
}
}
对象名stu保存在栈内存中,而对象的属性信息则保存在对应的堆内存中。
创建对象后,可以使用对象访问类中的某个属性或方法,对象属性和方法的访问通过 点(.)运算符实现:
对象名.属性名
对象名.方法名
3.对象的引用传递
类属于引用数据类型,引用数据类型的内存空间可以同时被多个栈内存使用。
class Student{
String name; //声明name属性
int age; //声明age属性
void read(){
System.out.println("大家好,我是"+name+",年龄"+age);
}
}
class Example{
public static void main(String[] args){
Student stu1=new Student(); //创建stu1对象并实例化
Student stu2=null; //创建stu2对象,但不实例化
stu2=stu1; //stu1给stu2分配空间使用权
stu1.name="小明"; //为stu1对象的name属性赋值
stu1.age=20;
stu2.age=50;
stu1.read(); //调用对象方法
stu2.read();
}
}
实际上所谓的引用传递,就是将一个堆内存空间的使用权分配给多个栈内存使用,每个栈内存空间都可以修改堆内存的内容。
小提示:一个栈内存空间只能指向一个堆内存的空间。如果想要再指向其他堆内存空间,就必须先断开已有的指向,才能分配新的指向。
4. 访问控制权限制
1)private:私有访问权限。
2)default:默认访问权限。
3)protected:受保护的访问权限。
4)public:公共访问权限。
需要注意的是,外部类的访问权限只能是public或default。如果一个类被定义为protected,那么只有其子类能访问这个类,这对于外部类来说没有意义,因为外部类的“父包”并不能定义继承关系。同理,private对外部类也没有意义,因为它将使得该类无法在任何其他地方被访问到。
局部成员是没有访问控制权限的,因为局部成员只在其所在的作用域内起作用,不可能被其他类访问,如果在程序中对局部成员使用访问控制权限修饰符,编译器会报错。
Java程序的文件名:如果一个源文件中定义的所有类都没有使用public修饰,那么这个源文件的文件名可以是一切合法的文件名;如果一个源文件中定义了一个使用public修饰的类,那么这个源文件的文件名必须与public修饰的类名相同。
3.3 封装性
1.为什么要封装
封装是指将类的实现细节包装、隐藏起来的方法。封装可以被认为是一道保护屏障,防止本类的代码和数据被外部类定义的代码随机访问。
2.如何实现封装
类的封装是指将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象的内部信息,而是通过类提供的方法实现对内部信息的访问。
封装的实现过程:在定义一个类时,将类中的属性私有化,即使用private关键字修饰类的属性。私有属性只能在它所在的类中被访问。
如果外界想要访问私有属性,需要提供一些使用public修饰的公有方法,其中包括用于获取属性值的 getXxx()方法(也称为getter方法)和设置属性值的 setXxx()方法(也称为setter方法)
class Student{
private String name;
private int age;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public int getAge(){
return age;
}
public void setAge(int age){
if(age<0){
System.out.prinln("您输入的年龄有误!");
}else{
this.age=age;
}
}
public void read(){
System.out.println("大家好,我是"+name",年龄"+age);
}
}
public class Example{
public static void main(String[] args){
Student stu=new Student();
stu.setName("张三");
stu.setAge("-18");
stu.read();
}
}
// 其中getXxx()方法 用于获取Xxx属性;setXxx()方法用于设置Xxx属性
3.4 构造方法
实例化一个对象后,如果要为这个对象中的属性赋值,则必须直接访问对象的属性或调用setter方法。如果需要在实例化对象时为这个对象的属性赋值,可以通过构造方法实现。
构造方法(也称为构造器)是类的一个特殊成员方法,在类实例化对象时自动调用。
1.定义构造方法
注意:1)构造方法的名称必须与类名一致。
2)构造方法名称前不能有任何返回值类型的声明。
3)不能在构造方法中使用return返回一个值,但可以单独写return语句,作为方法的结束。
构造方法分为 有参 和 无参:
//无参
class Student{
public Student(){
System.out.println("调用了无参构造方法");
}
}
//有参
class Student{
private String name;
private int age;
public Student(String n,int a){
name=n;
age=a;
System.out.println("调用了有参构造方法");
}
public void read(){
System.out.println("我是:"+name+",年龄:"+age);
}
}
public class test{
public static void main(String[] args){
Student stu=new Student("张三",18);
stu.read();
}
}
2.构造方法的重载
与普通方法一样,构造方法也可以重载。
class Student{
private String name;
private int age;
public Student(String n){
name=n;
System.out.println("调用一个参数的构造方法");
}
public Student(String n,int a){
name=n;
age=a;
System.out.println("调用两个参数的构造方法");
}
public void read(){
System.out.println("我是:"+name+",年龄:"+age);
}
}
public class test{
public static void main(String[] args){
Student stu1=new Student("张三");
Student stu2=new Student("张三",18);
stu1.read();
stu2.read();
}
}
3.默认构造方法
在Java中的每一个类都至少有一个构造方法。如果一个类中没有定义构造方法,系统会自动为这个类创建一个默认的构造方法,这个默认的构造方法没有参数,方法体中没有任何代码,所以Java中默认的构造方法在程序运行时什么也不做。
类中定义了一个有参构造方法,系统不再提供无参构造方法。
注意:构造方法通常用public进行修饰。
3.5 this关键字
在开发中,当成员变量与局部变量发生重名问题时,需要使用this关键字分辨成员变量与局部变量。Java中this关键字语法比较灵活,其作用主要有以下3个:
1)使用this关键字调用本类中的属性
2)使用this关键字调用成员方法
3)使用thsi关键字调用构造方法
1.使用this关键字调用本类中的属性
private int age;
public Student (int age){
this.age=age;
}
// this关键字明确标识了类中的属性
2.使用this关键字调用成员方法
class Student{
public void openMouth(){
...
}
public void read(){
this.openMouth();
}
}
3.使用this关键字调用构造方法
构造方法在实例化对象时被Java虚拟机自动调用。在程序中,不能像调用其他方法一样调用构造方法,但可以在一个构造方法中使用 “ this(参数一、参数二、...)”的形式调用其他的构造方法。
class Student{
private String name;
private int age;
public Student(){
System.out.println("调用了无参构造方法");
}
public Student(String name,int age){
this(); //调用了无参构造方法
this.name=name;
this.age=age;
}
public String read(){
return"我是:"+name+",年龄:"+age;
}
}
public class Example{
public static void main(String[] args){
Student stu=new Student("张三",18);
System.out.println(stu.read());
}
}
使用this调用类的构造方法时,注意:
1)只能在构造方法中使用this调用其他的构造方法,不能在成员中通过this调用构造方法。
2)在构造方法中,使用this调用其他构造方法的语句必须位于第一行,且只能出现一次。
3)不同在同一个类的两个构造方法中使用this互相调用。
3.6 代码块
代码块,简单来讲,就是用{}括起来的一段代码。
根据位置及声明关键字不同,代码块可以分为4中:普通代码块、构造块、静态代码块、同步代码块。
1.普通代码块
public class Hi{
public static void main(String[] args){
{System.out.println("Hello World!!");}
}
}
每一对 “{}” 括起来的代码都称为一个代码块。
Hi是一个大的代码块,在Hi代码块中包含了 main()方法的代码块,在 main()方法中又定义了一个局部代码块。
2.构造块
构造块是直接定义在类中的代码块
class Student{
String name{
System.out.println("我是构造块");
}
public Student(){
System.out.println("我是Student类的构造方法");
}
}
代码中,与构造方法同级的就是构造块。
1)构造块先于构造方法执行(这里和构造块写在前面还是后面没有关系)。
2)每当实例化一个对象时,都会在执行构造方法之前执行构造块。
3.7 static关键字
被static修饰的成员具有一定特殊性
1.静态属性
使用static修饰属性,则该属性称为静态属性(也称为全局属性)。
静态属性可以使用类名直接访问: 类名.属性名
class Student{
String name;
int age;
String school="A大学";
public Student(String name,int age){
this.name=name;
this.age=age;
}
}
public class Test{
public static void main(String[] args){
Student stu1=new Student("张三",18);
stu1.school="B大学";
stu1.age=20;
}
}
表明非静止属性是对象所有的,改变当前对象的属性值。不影响其他对象的属性值。假设A大学改名为B大学 ,而此时已经产生了十万个学生对象,那么意味着,如果要修改,则要把十万个学生的属性全部修改一次,共十万次,肯定是麻烦的。
为解决这个问题,可以将static关键字修饰school属性,将其变为公共属性。这样school属性只被分配一块内存空间,被Student类的所有对象共享,只要某个对象进行了一次修改,全部学生对象的school属性值都会发生变化。
//使用static关键字修饰school属性
class Student{
String name;
int age;
static String school="A大学";
public Student(String name,int age){
this.name=name;
this.age=age;
}
public void info(){
System.out.println("姓名:"+this.name+",年龄:"+this.age+
",学校:"+school);
}
}
public class Test{
public static void main(String[] args){
Student stu1=new Student("张三",18);
Student stu2=new Student("李四",19);
Student stu3=new Student("王五",20);
stu1.info();
stu2.info();
stu3.info();
stu1.school="B大学";
System.out.println("修改stu1对象的学校后");
stu1.info();
stu2.info();
stu3.info();
}
}
提示:static不能修饰局部变量
static关键字只能修饰成员变量,不能修饰局部变量,否则编译器会报错。
2.静态方法
如果要想使用类中的成员方法,就需要先将这个类实例化。而在实际开发时,开发人员有时希望在不创建对象的情况下,通过类名就可以直接调用某个方法,这时就需要使用静态方法,只需在成员方法前加上static关键字。
格式:类名.方法 或 实例对象名.方法
class Student{
private static String school="A大学";
public static String getSchool(){
return school;
}
public static void setSchool(){
school=s;
}
}
静态方法只能访问静态成员。费静态成员需先创建对象才能访问,即随着对象的创建,非静态成员才会分配内存。而静态方法在被调用时可以不创建任何对象。
3.静态代码块
在Java类中,用static关键字修饰的代码块称为静态代码块。当类被加载时,静态代码块就会执行。由于类只加载一次,所以静态代码块只执行一次。在程序中,通常使用静态代码块对类的成员变量进行初始化。
class Student{
String name;
{
System.out.println("我是构造代码块");
}
static{
System.out.println("我是静态代码块");
}
public Student(){
System.out.println("我是Student类的构造代码块");
}
}
class Example{
public static void main(String[] args){
Student stu1=new Student();
Student stu2=new Student();
Student stu3=new Student();
}
}
代码块的执行顺序为: 静态代码块→构造代码块→构造方法
static修饰的代码块会随着class文件一同加载,属于优先级最高的代码块。
三次实例化对象的过程中,静态代码块中的内容只输出一次,这是因为静态代码块在类第一次使用时才会被加载,并且只被加载一次。
4.2 final关键字
在默认情况下,所有的成员变量和成员方法都可以被子类重写。如果父类的成员不希望被子类重写,可以在声明父类的成员时使用final关键字修饰。final有 “最终” “不可更改” 的含义。在Java中,可以使用final关键字修饰 类、属性、方法 ,注意:
1)使用final关键字修饰的类不能有子类
2)使用final关键字修饰的方法不能被子类重写
3)使用final关键字修饰的变量是常量,常量不可修改
1.final关键字修饰类
Java中使用final关键字修饰的类不可以被继承,也就是这样的类不能派生子类。
2.final关键字修饰方法
当一个类的方法被final关键字修饰后,该类的子类将不能重写该方法。
3.final关键字修饰变量
Java中被final修饰的变量为常量,常量只能在声明时被赋值一次,在后面的程序中,常量的值不能被改变。
需要注意的是:在使用final声明变量时,变量的名称要求全部为大写字母。如果一个程序中的变量使用public static final声明,则此变量将成为全局变量。
public static final String NAME="哈士奇";
4末课后题
1. 定义一个复数类complex,它的内部具有两个实例变量:realPart和imagPart,分别代表复数的实部和虚部,编程实现要求的数学运算:1)实现两个复数相加;2)实现两个复数相减;3)输出运算的结果。然后,调用上述方法实现两个复数18+2i、19-13i的相加、相减,并打印出结果。
class complex{
private int realPart,imagPart;
public complex(int realPart,int imagPart){
this.realPart=realPart;
this.imagPart=imagPart;
}
void add(complex c1,complex c2){
realPart=c1.realPart+c2.realPart;
imagPart=c1.imagPart+c2.imagPart;
}
void sub(complex c1,complex c2){
realPart=c1.realPart-c2.realPart;
imagPart=c1.imagPart-c2.imagPart;
}
void show(){
if(imagPart==0){
System.out.println("运算结果为:"+realPart);
}else if(imagPart>0){
System.out.println("运算结果为:"+realPart+"+"+imagPart+"i");
}else{
System.out.println("运算结果为:"+realPart+imagPart+"i");
}
}
}
public class c1 {
public static void main(String[] args){
complex co1=new complex(18,2);
complex co2=new complex(19,-13);
complex co0=new complex(0,0);
co0.add(co1,co2);
co0.show();
co0.sub(co1,co2);
co0.show();
}
}
2. 首先定义一个计算二维坐标系中圆面积的类circleClass,要求类中有一个定义圆心座标,圆上一点座标的构造函数,以及一个通过圆上一点座标与圆心座标计算圆面积的方法area。然后,通过上述类生成两个圆对象circle1、circle2进行测试:一个圆心、圆上一点座标分别为(0,0)、(8.5,9),另一个圆心、圆上一点座标分别为(2,3.5)、(9,6),并分别显示各自面积。
class circleClass{
private double across,endlong,ar;
public circleClass(double across,double endlong){
this.across=across;
this.endlong=endlong;
}
void area(circleClass c1,circleClass c2){
across=c1.across-c2.across;
endlong=c1.endlong-c2.endlong;
ar=across*across+endlong*endlong;
}
void show(){
System.out.println("面积为:"+ar+"Π");
}
}
public class c2 {
public static void main(String[] args){
circleClass cc11=new circleClass(0,0);
circleClass cc12=new circleClass(8.5,9);
circleClass cc21=new circleClass(2,3.5);
circleClass cc22=new circleClass(9,6);
circleClass cc0=new circleClass(0,0);
cc0.area(cc11,cc12);
cc0.show();
cc0.area(cc21,cc22);
cc0.show();
}
}
3. 首先定义一个计算长方形面积的类rectangleClass,要求类中有一个定义长方形左上角和右下角座标的构造函数,以及一个通过长方形右下角座标与左上角座标计算长方形面积,并实例化两个长方形进行测试.
class rectangleClass{
private double across,endlong,area;
public rectangleClass(double across,double endlong){
this.across=across;
this.endlong=endlong;
}
void area(rectangleClass a1,rectangleClass a2){
across=a1.across-a2.across;
endlong=a1.endlong-a2.endlong;
if(across<0){
across=-across;
}
if(endlong<0){
endlong=-endlong;
}
area=across*endlong;
}
void show(){
System.out.println("面积是:"+area);
}
}
public class c3 {
public static void main(String[] args){
rectangleClass r11=new rectangleClass(0,0);
rectangleClass r12=new rectangleClass(8.5,9);
rectangleClass r21=new rectangleClass(2,3.5);
rectangleClass r22=new rectangleClass(9,6);
rectangleClass r0=new rectangleClass(0,0);
r0.area(r11,r12);
r0.show();
r0.area(r21,r22);
r0.show();
}
}
4. 将笛卡尔坐标系上的点定义为一个类Point,该类要求提供求得坐标系上两点间距离的功能、获取和设置坐标的功能、获取极坐标的功能,以及完成对已创建的Point类对象进行个数统计的功能。设计测试Point类的应用程序主类,测试并显示输出提供所有功能的结果。
class point{
private double x,y;
static int num=0;
public point(double x,double y){
this.x=x;
this.y=y;
num++;
}
void distance(point p){
double dis=0;
dis=Math.sqrt(Math.pow((this.x-p.x),2)+Math.pow((this.y-p.y),2));
System.out.println("两点之间的距离="+dis);
}
void getpoint(){
System.out.println("x="+x);
System.out.println("y="+y);
}
void setpoint(double x,double y){
this.x=x;
this.y=y;
}
void getposition(){
double length,arc;
length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2));
arc=60.0;
System.out.println("极坐标:"+length+"\t"+arc);
}
void total(){
System.out.println("总共创建对象数:"+num);
}
}
public class c4 {
public static void main(String[] args){
point p0=new point(0,0);
point p1=new point(3,4);
point p2=new point(20,5);
point p3=new point(12,18);
point p4=new point(7,9);
p0.distance(p1);
p0.getpoint();
p0.setpoint(10,10);
p0.getpoint();
p0.getposition();
p0.total();
}
}
5. 设计一个表示图书的Book类,它包含图书的书名、作者、月销售量等属性,另有两个构造方法(一个不带参数,另一个带参数),成员方法setBook( ) 和printBook()分别用于设置和输出书名、作者、月销售量等数据。并设计相应的测试Book类的应用程序主类,测试并显示输出提供所有功能的结果。
class book{
String book,name;
int sale;
public book(){
}
public book(String book,String name,int sale){
this.book=book;
this.name=name;
this.sale=sale;
}
void setbook(String book,String name,int sale){
this.book=book;
this.name=name;
this.sale=sale;
}
void printbook(){
System.out.println("书名:"+book+" 作者:"+name+" 月销量:"+sale);
}
}
public class c5 {
public static void main(String[] args){
book b1=new book("史记","司马迁",999);
b1.printbook();
}
}
6. 请创建一个银行帐户类,要求如下:(1)类包括帐户名、帐户号、存款额等属性;(2)可实现余额查询,存款和取款的操作。(3)创建该类的对象,验证以上两项。
import java.util.Scanner;
class bank{
String name,number;
int balance,c;
public bank(String name,String number,int balance){
this.name=name;
this.number=number;
this.balance=balance;
}
void inquire(){
System.out.println("账户名:"+name+" 账户号:"+number+" 余额:¥"+balance);
}
bank access(int c){
balance+=c;
return new bank(name,number,balance);
}
}
public class c6 {
public static void main(String[] args){
Scanner in=new Scanner(System.in);
System.out.println("请输入账户名:");
String name=in.next();
System.out.println("请输入账户号:");
String number=in.next();
int balance=(int)(Math.random()*99999999+1);
bank b1=new bank(name,number,balance);
b1.inquire();
System.out.println("请输入存取金额:");
int c=in.nextInt();
bank b2=b1.access(c);
b2.inquire();
}
}
拓展
//4中的代码
Math.sqrt(Math.pow((this.x-p.x),2)+Math.pow((this.y-p.y),2))
//Math.sqrt() 用来计算平方根
//Math.pow(base, exponent)
// base 是底数,即被乘数。
// exponent 是指数,即乘数。
计算器代码
import javax.swing.*;
class test{
public static void main(String[] args){
JFrame frame=new JFrame("AJie's calculation");
JPanel panel=new JPanel();
JTextField oneField=new JTextField(10);
JTextField twoField=new JTextField(10);
JTextField resultField=new JTextField(10);
JButton add=new JButton("+");
JButton sub=new JButton("-");
JButton mul=new JButton("*");
JButton div=new JButton("/");
panel.setLayout(null);
oneField.setBounds(10,10,150,20);
twoField.setBounds(10,40,150,20);
add.setBounds(10,70,50,20);
sub.setBounds(10,100,50,20);
mul.setBounds(10,130,50,20);
div.setBounds(10,160,50,20);
resultField.setBounds(10,190,150,20);
panel.add(oneField);
panel.add(twoField);
panel.add(add);
panel.add(sub);
panel.add(mul);
panel.add(div);
panel.add(resultField);
frame.add(panel);
add.addActionListener(e->{
int one=Integer.parseInt(oneField.getText());
int two=Integer.parseInt(twoField.getText());
resultField.setText(String.valueOf(one+two));
});
sub.addActionListener(e->{
int one=Integer.parseInt(oneField.getText());
int two=Integer.parseInt(twoField.getText());
resultField.setText(String.valueOf(one-two));
});
mul.addActionListener(e->{
int one=Integer.parseInt(oneField.getText());
int two=Integer.parseInt(twoField.getText());
resultField.setText(String.valueOf(one*two));
});
div.addActionListener(e->{
int one=Integer.parseInt(oneField.getText());
int two=Integer.parseInt(twoField.getText());
resultField.setText(String.valueOf(one/two));
});
frame.setBounds(500,260,300,260);
frame.setVisible(true);
}
}
//@echo off
// start javaw -classpath "D:\MrJie\java" test
@echo off :是Windows命令提示符(CMD)中的一个命令,用于关闭命令回显。当执行这个命令时,它会阻止在命令提示符窗口中显示当前执行的命令及其结果。这通常用于批处理脚本中,以避免显示不必要的信息。
start :是Windows命令行的一个内置命令,用于在新命令解释器窗口中启动一个程序或命令。它允许在不阻塞当前命令行窗口的情况下允许命令或程序。
javaw : 它是Java虚拟机(JVM)的无窗口版本,通常用于在图形界面环境下运行Java应用程序,不会打开命令行窗口。
-classpath"D:\MrJie\java" : 指定Java类文件的搜索路径。
test :Java程序的主类(即包含main方法的类)。
//创建一个.bat文件 并用记事本书写
五、面向对象(下)
4.1 继承
1.继承的概念
在Java中,类的继承是指在一个现有类的基础上构建一个新的类,构建的新类被称作子类,现有类被称作父类。子类会自动继承父类的属性和方法,使得子类具有父类的特征和行为。
class 父类{
...
}
class 子类 extends 父类{
...
}
例:
class Animal{
private String name;
private int age;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
publid int getAge(){
return age;
}
public void setAge(int age){
this.age=age;
}
}
class Dog extends Animal{
private String color;
public String getColor(){
return color;
}
public void setColor(String color){
this.color=color;
}
}
public class test{
public static void main(String[] args){
Dog dog=new Dog();
dog.setName("牧羊犬");
dog.setAge(3);
dog.setColor("黑色");
System.out.println("名称:"+dog.getName()+",年龄:"+dog.getAge()+",颜
色:"+dog.getColor());
}
}
//因为父类Animal中 name属性和age属性使用private关键字修饰,即name属性和age属性为Animal的
//私有属性,所以需要使用getter方法和setter方法 访问
子类除了可以继承父类的属性和方法,也可以定义自己的属性和方法。
需要注意的是,子类虽然可以通过继承访问父类的成员和方法,但不是所有的父类属性和方法都可以被子类访问。子类只能访问父类中用public和protected修饰的属性和方法,父类中被默认修饰符default和private修饰的属性和方法不能被子类访问。
1)在Java中,类支持单继承 不允许多继承 即一个类只能有一个直接父类。
2)多个类可以继承一个类。
3)在Java中,多层继承是可以的。
4)在Java中,子类和父类是相对的 ,一个类可以是某个类的父类,也可以是另一个类的子类。
2.方法的重写
在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。在子类中重写的方法需要和父类中被重写的方法具有相同的方法名、参数列表以及返回值类型。
class Animal{
void shout(){
System.out.println("动物发出叫声 ");
}
}
class Dog extends Animal{
void shout(){
System.out.println("汪汪汪...");
}
}
public class test{
public static void main(String[] args){
Dog dog=new Dog();
dog.shout();
}
}
注意:子类重写父类方法时的访问权限
子类重写父类的方法时,不能使用比父类方法更严格的访问权限。例如,父类的方法是public权限,子类的方法就不能是private权限。如果子类在重写父类方法时定义的权限更严格,则在编译时将会出现错误。
3.super关键字
当子类重写父类的方法后,子类对象将无法访问父类中被子类重写过的方法。
(1)使用super关键字访问父类的非私有属性或调用父类的非私有方法
super.属性;
super.方法(参数1,参数2,...);
(2)使用super关键字调用父类中指定的构造方法
super(参数1,参数2,...);
class Animal{
String name="牧羊犬";
void shout(){
System.out.println("动物发出叫声");
}
}
class Dog extends Animal{
public void shout(){
super.shout();
System.out.println("汪汪汪...");
}
public void printName(){
System.out.println("名字:"+super.name);
}
}
public class test{
public static void main(String[] args){
Dog dog=new Dog();
dog.shout();
dog.printName();
}
}
注意:通过super调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次。
需要注意:this和super 不可以同时出现,因为使用 this 和 super 调用构造方法的代码都要求必须放在构造方法的首行。
4.3抽象类和接口
1.抽象类
定义一个类时,常常需要定义一些成员方法用于描述类的行为特征,但有时这些方法的实现方式是无法确定的。
针对这种情况,Java提供了抽象方法来满足这种需求。抽象方法是使用abstract关键字修饰的成员方法,抽象方法在定义时不需要实现方法体。
abstract 返回值类型 方法名称(参数列表);
当一个类包含了抽象方法,该类就是抽象类。抽象类和抽象方法一样,必须使用abstract关键字进行修饰。
abstract class 抽象类名称{
属性;
访问权限 返回值类型 方法名称(参数){
return [返回值];
}
访问权限 abstract 返回值类型 抽象方法名称(参数);
}
从格式来看,抽象类定义比普通类多了一个或多个抽象方法,其他地方与普通类的组成基本相同。
抽象类的定义规则:
1)包含抽象方法的类必须是抽象类。
2)声明抽象类和抽象方法时 都要使用abstract关键字修饰。
3)抽象方法只需要声明而不需要实现。
4)如果一个非抽象类继承了抽象类之后,那么该类必须重写抽象类中的全部抽象方法。
abstract class Animal{
abstract void shout();
}
class Dog extends Animal{
void shout(){
System.out.println("汪汪...");
}
}
public class test{
public static void main(String[] args){
Dog dog=new Dog();
dog.shout();
}
}
注意:使用abstract关键字修饰的抽象方法不能使用private关键字修饰 因为抽象方法必须被子类实现,如果使用了private关键字修饰抽象方法,则子类无法实现该方法。
2.接口
在Java中,使用接口的目的是克服单继承的限制,因为一个类只能有一个父类,而一个接口可以实现多个父接口。
在JDK8之前,接口是全局变量和抽象方法组成的。JDK8对接口进行了重新定义,接口中除了抽象方法外,还可以定义默认方法和静态方法,默认方法用default关键字修饰,静态方法使用static关键字修饰 而这两种方法都允许有方法体。
接口使用interface关键字说明:
[public] interface 接口名 [extends 接口1,接口2,...]{
[public] [static] [final] 数据类型 常量名 = 常量;
[public] [abstract] 返回值的数据类型 方法名(参数列表);
[public] static 返回值的数据类型 方法名(参数列表){};
[public] default 返回值的数据类型 方法名(参数列表){};
}
父接口之间使用逗号隔开。接口中的变量默认使用 public static final进行修饰,即全局变量。接口中定义的抽象方法默认使用 public abstract 进行修饰。
注意:不管写不写访问权限,接口中方法的访问权限永远是 public。
接口本身不能直接实例化,接口中的抽象方法和默认方法只能通过接口实现类的实例对象进行调用。实现类通过implements关键字实现接口,并且实现类必须重写接口中所有的抽象方法。需要注意的是一个类可以同时实现多个接口,实现多个接口时2,多个接口名需要使用英文逗号隔开。
修饰符 class 类名 implements 接口1,接口2,...{
...
}
interface Animal{
int ID=1;
String Name="牧羊犬";
void shout();
public void info();
static int getID(){
return Animal.ID;
}
}
abstract class Action{
public abstract void eat();
}
class Dog extends Action implements Animal{
public void eat(){
System.out.println("喜欢吃骨头");
}
public void shout(){
System.out.println("汪汪...");
}
public void info(){
System.out.println("名称:"+Name);
}
}
class test{
public static void main(String[] args){
Dog dog=new Dog;
System.out.println("编号"+Animal.getID());
dog.info();
dog.shout();
dog.eat();
}
}
在Java中,接口不允许继承抽象类,但是允许接口继承接口,并且一个接口可以同时继承多个接口。
4.4多态
1.多态概述
在Java中,多态是指不同类的对象在调用同一个方法时表现出的多种不同行为。
在同一个方法中,由于参数类型不同而导致执行效果不同的现象就是多态。Java中多态主要有以下两种形式:
1)方法的重载
2)对象的多态(方法的重写)
abstract class Animal{
abstract void shout();
}
class Cat extends Animal{
public void shout(){
System.out.println("喵喵喵...);
}
}
class Dog extends Animal{
public void shout(){
System.out.println("汪汪汪... ");
}
}
public class test{
public static void main(String[] args){
Animal an1=new Cat();
Animal an2-new Dog();
an1.shout();
an2.shout();
}
}
//an1 an2 调用的分别是Cat类和Dog类的shout方法,这样就实现了多态
2.对象类型的转换
分两种情况:
1)向上转型:子类对象→父类对象
2)向下转型:父类对象→子类对象
1.对象向上转型
父类对象可以调用子类重写父类的方法,这样当需要新添功能时,只需要新增一个子类,在子类中对父类的功能进行扩展,而不用更改父类的代码,保证了程序的安全性。对于向上转型,程序会自动完成:
父类类型 父类对象=子类实例;
class Animal{
public void shout(){
System.out.println("喵喵喵...");
}
}
class Dog extends Animal{
public void shout(){
System.out.println("汪汪...");
}
public void eat(){
System.out.println("吃骨头...");
}
}
public class test{
public static void main(String[] args){
Dog dog=new Dog();
Animal an=dog; //向上转型
an.shout();
}
}
//父类对象an调用了shout()方法,但实际上调用的是被子类重写过的shout()方法。也就是说
//如果对象发生了向上转型后,调用的方法一定是被子类重写过的方法。
//需要注意的是,父类的对象an是无法调用Dog类中的eat()方法的,因为eat()方法只在子类中
//定义,而没有在父类中定义。
2.对象向下转型
向下转型一般是为了重新获得因为向上转型而丢失的子类特性。对象在进行向下转型前,必须先进行向上转型,否则将出现对象转换异常。
向下转型时,必须指明要转为的子类类型:
父类类型 父类对象=子类实例;
子类类型 子类对象=(子类)父类对象;
class Animal{
public void shout(){
System.out.println("喵喵喵...");
}
}
class Dog extends Animal{
public void shout(){
System.out.println("汪汪...");
}
public void eat(){
System.out.println("吃骨头...");
}
}
public class test{
public static void main(String[] args){
Animal an=new Dog(); //向上转型
Dog dog=(Dog) an; //向下转型
dog.eat();
dog.shout();
}
}
//因为Animal类的shout方法已被子类(Dog类)重写,所以dog对象调用的方法是被子类
//重写过的方法。
3.instanceof 关键字
Java中可以使用instanceof关键字判断一个对象是否是某个类(或接口)的实例:
对象 instanceof 类(或接口)
如果“对象”是指定的类或接口的实例对象,则返回true,否则返回false
class Animal{
public void shout(){
System.out.println("动物叫...");
}
}
class Dog extends Animal{
public void shout(){
System.out.println("汪汪...");
}
public void eat(){
System.out.println("吃骨头...");
}
}
public class test{
public static void main(String[] args){
Animal a1=new Dog(); //通过向上转型,实例化Animal对象
System.out.println("Animal a1=new Dog():"+(a1 instanceof Animal));
System.out.println("Animal a1=new Dog():"+(a1 instanceof Dog));
Animal a2=new Animal();
System.out.println("Animal a2=new Dog():"+(a2 instanceof Animal));
System.out.println("Animal a2=new Dog():"+(a2 instanceof Dog));
}
}
//可见,a1既是Animal类的实例 也是Dog类的实例
4.5 Object类
Java提供了Object类,他是所有类的父类,每个类都直接或间接继承了Object类,因此Object类通常被称为超类。当定义一个类时,如果没有使用extends关键字为这个类显式地指定父类,那么该类会默认继承Object类。
在实际开发中,通常情况下不会直接用Object类中的方法,因为Object类中的方法并不使用于所有子类,这事需要对Object类中的方法进行重写,以满足实际开发需求。
class Animal{
public String toString(){
return "这是一个动物";
}
}
public class test{
public static void main(String[] args){
Animal animal=new Animal();
System.out.println(animal.toStrong());
}
}
4.6内部类
在Java中,允许在一个类的内部定义类,这样的类称为内部类,内部类所在的类称作外部类。
在实际开发中,根据内部类的位置、修饰符和定义方式的不同,内部类可分为4种,分别是:成员内部类、局部内部类、静态内部类、匿名内部类。
1.成员内部类
在一个类中,除了可以定义成员变量、成员方法,还可以定义类,这样的类称作成员内部类。成员内部类可以访问外部类的所有成员,无论外部类的成员是何种访问权限。如果想通过外部类访问内部类,则需要通过外部类创建内部类对象。格式:
外部类名 外部类对象=new 外部类名();
外部类名.内部类名 内部类对象=外部类对象.new 内部类名();
class Outer{
int m=0;
void test1(){
System.out.println("外部类成员方法 test1()");
}
class Inner{
int n=1;
void show1(){
System.out.println("外部类成员变量m="+m);
test1(); //成员内部类的方法中访问外部类的成员方法test1()
}
void show2(){
System.out.println("内部类成员方法show2()");
}
}
void test2(){
Inner inner=new Inner();
System.out.println("内部类成员变量n="+inner.n);
inner.show2();
}
}
public class test{
public static void main(String[] args){
Outer outer=new Outer(); //实例化外部类对象outer
Outer.Inner inner=outer.new Inner(); //实例化内部类对象inner
inner.show1(); //内部类中访问外部类
outer.test2(); //外部类中访问内部类
}
}
2.局部内部类
也称为方法内部类,是指定义在某个局部范围中的类,它和局部变量都是在方法中定义的,有限范围只限于方法内部。
局部内部类可以访问外部类的所有成员变量和成员方法,而在外部类中无法直接访问局部内部类的变量和方法。如果要在外部类中访问局部内部类的成员,只能在局部内部类的所属方法中创建局部内部类的对象,通过对象访问局部内部类的所属方法中创建局部内部类的对象,通过对象访问局部内部类的变量和方法。
class Outer{
int m=0;
void test1(){
System.out.println("外部类成员方法test1()");
}
void test2(){
class Inner{
int n=1;
void show(){
System.out.println("外部类成员变量m="+m);
test1();
}
}
Inner inner=new Inner(); //实例化对象,创建局部内部类的对象
System.out.println("局部内部类n="+inner.n);
inner.show();
}
}
public class test{
public static void main(String[] args){
Outer outer=new Outer();
outer.test2(); //调用有局部内部类的方法
}
}
3.静态内部类
就是使用static关键字修饰的成员内部类。与成员内部类相比,在形式上,静态内部类只是在内部类前增加了static关键字,但在功能上,静态内部类只能访问外部类的静态成员,通过外部类访问静态内部类成员时,因为程序已经提前在静态常量区为静态内部类分配好了内存,所以即使静态内部类没有加载,依然可以通过外部类直接创建一个静态内部类对象。格式:
外部类名.静态内部类名 变量名=new 外部类名.静态内部类名();
class Outer{
static int m=0;
static class Inner{
int n=1;
void show();
System.out.println("外部类静态变量m="+m);
}
}
}
public class test{
public static void main(String[] args){
Outer.Inner inner=new Outer.Inner();
inner.show();
}
}
// 这里的关键点在于,你不需要创建Outer类的对象,可以直接通过Outer.Inner的形式来创建和
// 访问静态内部类的对象
4.匿名内部类
在Java中调用某个方法时,如果该方法的参数是接口类型,那么在传参时,除了可以传入一个接口实现类,还可以传入实现接口的匿名内部类作为参数,在匿名内部类中实现接口方法。
匿名内部类就是没有名称的内部类,定义匿名内部类时,其类体作为new语句的一部分。格式:
new 继承的父类或实现的接口名(){
匿名内部类的类体
}
//该语法结构创建了一个匿名内部类的对象,该匿名内部类继承了
//指定父类或实现了指定接口
例:定义了animalShout()方法,该方法的参数为Animal接口类型的对象,那么在调用该方法时,可以在方法的参数位置定义一个实现Animal接口的匿名内部类。
animalShout(new Animal(){});
//在animalShout()方法的参数位置写上 new Animal(){}
//相当于创建了一个Animal接口的实现类对象,并将该对象作为参数传给animalShout()方法。
//
//在new Animal() 后面有一对大括号,表示创建对象为Animal的实现类实例,该实现类是匿名的。
interface Animal{
void shout();
}
public class test{
public static void main(String[] args){
String name="小花";
animalShout(new Animal(){
@Override
public void shout(){
System.out.println(name+"喵喵...");
}
});
}
public static animalShout(Animal an){
an.shout();
}
}
//调用了animalShout()方法,将实现Animal接口的匿名内部类作为animalShout()方法
//的参数。animalShout(new Animal(){})相当于创建了一个Animal接口的匿名内部类对象
//并将该对象的参数传给animalShout()方法。最后在匿名内部类中重写了shout方法。
//简单说,程序调用了animalShout()方法,并通过animalShout()方法成功调用了匿名内部类的
//shout方法
补充:面向对象程序设计
1.package(包)
为了便于管理大型软件系统中数目众多的类,解决类命名冲突的问题,java引入了包。
package语句必须是文件的第一条语句。也就是说,在package语句之前,除了空白和注释外不能有任何语句。
如果不加package语句,则指定为缺省包或无名包。
//代码
package school.garden
//在此目录下创建包
import java.io.File;
//引入包中的类
import java.io.*;
//引入整个包
//在同一个包中的类可以相互引用,无需import语句
//编译命令
javac -d.Test.java
//在当前目录下生成包
javac -d E:\JavaLesson Test.java
//在指定目录下生成包
jar cvf test.jar hello.class
//创建并显示打包过程
//jar格式:jar [ctuxf][vmeomi][目录] 文件名
2.类的说明符
类的访问说明符
1)public,要访问其他包中的类,必须首先用import语句导入这个包,并且该类必须为public,并且该Java文件必须以public类的类名为文件名。
2)default,只能被同一个包中的类访问。
类的其他修饰符
1)final
2)abstract
3.编译中的问题(package)
在用VS code编译时,应用cmd窗口编译,且编译命令有所不同
javac -d.Test.java
在用idea编译时,在没有main方法的类中无法直接编译,但可以在项目栏中,右击项目选择(构建模块“项目名”) 便可编译项目。
正常的应该这样,既写好没有main方法的类,也写好有main方法且调用此无main方法符类。在这个有main方法直接运行也就把无main方法的类编译了。
课后题
class Student{
String name,degree;
int age;
Student(String name,int age){
this.name=name;
this.age=age;
}
}
class Undergraduate extends Student{
String specialty;
Undergraduate(String name,int age,String specialty){
super(name,age);
this.specialty=specialty;
}
void show(){
System.out.println("姓名:"+name+" 年龄:"+age+" "+" 学位:学士"+" 专业:"+specialty);
}
}
class Graduate extends Student{
String specialty;
Graduate(String name,int age,String degree,String specialty){
super(name,age);
this.degree=degree;
this.specialty=specialty;
}
void show(){
System.out.println("姓名:"+name+" 年龄:"+age+" "+" 学位:"+degree+" 研究方向:"+specialty);
}
}
public class d1 {
public static void main(String[] args){
Undergraduate b1=new Undergraduate("张三",20,"Communication engineering");
b1.show();
Undergraduate b2=new Undergraduate("李四",21,"Electronic engineering");
b2.show();
Graduate b3=new Graduate("王五",25,"硕士","Software engineering");
b3.show();
Graduate b4=new Graduate("李六",36,"博士","ergonomic");
b4.show();
}
}
class phone {
char[] number = new char[12];
void setnumber(char[] number) {
this.number = number;
}
void getnumber() {
System.out.print("本机号码:");
for (int i = 0; i < number.length; i++) {
System.out.print(number[i]);
}
System.out.println();
}
void answer() {
System.out.println("正在通过电信固网拨打电话...");
}
void dial() {
System.out.println("正在通过电信固网接听电话...");
}
}
class mobile extends phone{
char[] number=new char[11];
void answer(){
System.out.println("正在通过移动网络拨打电话...");
}
void dial(){
System.out.println("正在通过移动网络接听电话...");
}
}
class fix extends phone{
}
class cordless extends fix{
char[] number=new char[4];
void move(){
System.out.println("正在移动通话...");
}
}
public class d2 {
public static void main(String[] args){
fix b1=new fix();
b1.setnumber(new char[]{'0','5','7','4','8','8','2','2','2','0','9','6'});
b1.getnumber();
b1.answer();
b1.dial();
mobile b2=new mobile();
b2.setnumber(new char[]{'1','5','7','8','8','2','2','2','0','9','6'});
b2.getnumber();
b2.answer();
b2.dial();
cordless b3=new cordless();
b3.setnumber(new char[]{'2','0','9','6'});
b3.getnumber();
b3.answer();
b3.dial();
b3.move();
}
}
class salary{
String name,occupation;
int month;
void set(String name,String occupation,int month){
this.name=name;
this.occupation=occupation;
this.month=month*12;
}
void output(){
System.out.println(name+" "+occupation+" 年薪$"+month);
}
}
class common extends salary{
}
class teacher extends salary{
int unit,quantity,all;
void set(String name,String occupation,int month,int unit,int quantity){
super.set(name,occupation,month);
all=unit*quantity;
}
void output(){
System.out.println(name+" "+occupation+" 年薪$"+(month+all));
}
}
class scientist extends salary{
int unit;
void set(String name,String occupation,int month,int unit){
super.set(name,occupation,month);
this.unit=unit*4;
}
void output(){
System.out.println(name+" "+occupation+" 年薪$"+(month+unit));
}
}
class less3{
public static void main(String[] args){
common b1=new common();
b1.set("张三","工人",4000);
b1.output();
common b2=new common();
b2.set("李四","服务员",3500);
b2.output();
teacher b3=new teacher();
b3.set("王五","教师",5000,100,200);
b3.output();
scientist b4=new scientist();
b4.set("李六","科学家",7000,20000);
b4.output();
}
}
//huamn包
package biology.animal;
public class human
{
String name;
double height,weight;
public human(String name)
{
this.name=name;
}
public void eat()
{
System.out.println(name+"在吃饭。");
}
public void sleep()
{
System.out.println(name+"在睡觉。");
}
public void work()
{
System.out.println(name+"在工作。");
}
}
//flower包
package biology.plant;
public class flower
{
String name,color,smell;
public flower(String name){
this.name=name;
}
public void drink()
{
System.out.println(name+"is drinking");
}
public void blossom()
{
System.out.println(name+"is blossoming");
}
}
package shool.garden;
import biology.animal.human;
import biology.plant.flower;
class packTest
{
public static void main(String[] args)
{
human h=new human("张三");
flower f=new flower("rose");
h.eat();
h.sleep();
h.work();
f.drink();
f.blossom();
}
}
//VGACard类
package computer.mainbroad;
public class VGACard {
public void show(){
System.out.println("VGA checked success");
}
}
package server.mainbroad;
import computer.mainbroad.*;
public class showCard extends VGACard {
public static void main(String[] args){
showCard sc=new showCard();
sc.show();
}
}
//Cylinder类
package com.graphic;
public class Cylinder {
final double PI=3.14;
private double r,h;
double v;
public Cylinder(double r,double h){
this.r=r;
this.h=h;
}
public void volume(){
v=r*r*h*PI;
System.out.println("体积为:"+v);
}
}
package com.test;
import com.graphic.*;
public class test {
public static void main(String[] args){
Cylinder c=new Cylinder(5.34,2);
c.volume();
}
}
张三:20,本科,通信;李四:21,本科,电子;王五:25,硕士,通信;刘六:36,博士,通信
abstract class student{
String name,degree;
int age;
student(String name,int age,String degree){
this.name=name;
this.age=age;
this.degree=degree;
}
abstract void show();
}
class undergraduate extends student{
String specialty;
undergraduate(String name,int age,String degree,String specialty){
super(name,age,degree);
this.specialty=specialty;
}
void show(){
System.out.println(name+": "+age+","+degree+","+specialty+";");
}
}
class graduate extends student{
String direction;
graduate(String name,int age,String degree,String direction){
super(name,age,degree);
this.direction=direction;
}
void show(){
System.out.println(name+": "+age+","+degree+","+direction+";");
}
}
class less1{
public static void main(String[] args){
undergraduate u1=new undergraduate("Z3",20,"undergraduate","communication");
undergraduate u2=new undergraduate("L4",21,"undergraduate","corpuscle");
graduate u3=new graduate("W5",25,"master","communication");
graduate u4=new graduate("L6",36,"doctor","communication");
u1.show();
u2.show();
u3.show();
u4.show();
}
}
2 、首先定义一个接口circleInterface,要求接口中有一个定义PI的常量以及一个计算圆面积的空方法circleArea()。然后设计一个类circleClass实现该接口,通过构造函数circleClass(double r)定义圆半径,并增加一个显示圆面积的方法。最后,通过上述类生成两个半径分别为3.5、5.0的圆对象circle1、circle2进行测试。
interface circleInterface{
final double PI=3.14;
void circleArea();
}
public class circleClass implements circleInterface {
circleInterface inerface;
double r;
circleClass(double r){
this.r=r;
}
void set(circleInterface inerface){
this.inerface=inerface;
}
public void circleArea(){
System.out.println("半径:"+r+" 面积是:"+PI*r*r);
}
public static void main(String[] args){
circleClass c1=new circleClass(3.5);
circleClass c2=new circleClass((5));
c1.circleArea();
c2.circleArea();
}
}
abstract class Graphics{
String name;
Graphics(String name){
this.name=name;
}
abstract void parameter();
abstract void area();
}
class gra1 extends Graphics{
int l,w,r;
double s,PI=3.14;
gra1(String name,int l,int w){
super(name);
this.l=l;
this.w=w;
s=l*w;
}
gra1(String name,int r){
super(name);
this.r=r;
s=PI*r*r;
}
void parameter(){
System.out.println("形状是: "+name);
}
void area(){
System.out.println(" 面积是:"+s);
}
}
public class gra {
public static void main(String[] args){
gra1 g1=new gra1("长方形",3,2);
gra1 g2=new gra1("圆形",4);
g1.parameter();
g1.area();
g2.parameter();
g2.area();
}
}
//double area(double length); 是一个函数声明,表示一个名为 area 的函数,它接受一个 double 类型的参数 length,并返回一个 double 类型的值。这个函数可能是用于计算某种形状(如正方形或圆形)的面积。
interface Shape{
abstract double area(double m);//接口中的方法默认是 public 和 abstract 的 所以不需要显式地声明 abstract.
}
class Square implements Shape{
public double area(double m){ //在 Square 和 Circle 类中实现 area 方法时,需要加上 public 访问修饰符
return m*m;
}
}
class Circle implements Shape{
public double area(double m){ //要加 public
return Math.PI*m*m;
}
}
public class test {
public static void main(String[] args){
Shape square=new Square();
Shape circle=new Circle();
System.out.println("正方形面积为:"+square.area(2));
System.out.println("圆形面积为:"+circle.area(3));
}
}
六、异常处理
5.1 什么是异常
Java中的异常是指Java程序在运行时可能出现的错误或非正常情况,例如,在程序中试图打开一个根本不存在的文件、除以0、程序运行时磁盘空间不足、网络中断、加载的类不存在 等。
程序抛出异常之后,会对异常进行处理。异常处理将会改变程序的控制流程,出于安全性考虑,同时避免异常程序影响其他正常程序运行,操作系统通常将出现异常的程序强行中止,并弹出系统错误提示。
//计算以0为除数的表达式,发生错误
package com.itheima;
public class test{
public static void main(String[] args){
int result=divide(4,0);
System.out.println(result);
}
public static int divide(int x,int y){
int result=x/y;
return result;
}
}
//程序发生了算数异常(ArithmeticException),提示运算时出现了被0除
//的情况。异常发生后,程序会立即结束,无法继续向下执行。
ArithmeticException是一个异常类。Java中提供了大量的异常类。每一个异常类都表示一种预定义的异常。这些异常都继承自Java.lang包下的Throwable类。
Throwable类是所有异常类的父类,它有两个直接子类--Error类和Exception类,其中Error类代表程序中产生的错误,Exception类代表程序中产生的异常。
Error类称为错误类:它表示Java程序运行时产生的系统内部错误或资源耗尽的错误,这类错误比较严重,仅靠修改程序本身是不能恢复执行的。例如,使用Java命令运行一个不存在的类。
Exception类称为异常类,它表示程序本身可以处理的错误,在Java程序中进行的异常处理,都是针对Exception类及其子类的。在Exception类的众多子类中有一个特殊的子类--RuntimeException类,该类及其子类用于表示运行时异常。Exception类的其他子类用于表示编译时异常。
5.2 运行时异常与编译时异常
程序在编译时产生的异常,这些异常必须处理,否则程序无法正常运行,这种异常称为编译时异常,也称为checked异常。另一种,异常在程序运行时产生,这种异常即使不编写异常处理代码,依然可以通过编译,称为运行时异常,也称unchecked异常。
1.编译时异常
在Exception类中,除了RuntimeException类以外,其他子类都是编译时异常。编译时异常必须进行处理,两种方法:
1)使用try...catch语句对异常进行捕获处理。
2)使用throws关键字声明抛出异常,由调用者对异常进行处理。
2.运行时异常
RuntimeException类及其子类都是运行时异常。该异常是在程序运行时由Java虚拟机自动进行捕获处理的,Java编译器不会对异常进行检查。即编译可通过,但在运行时会报错。
运行时异常一般由程序中的逻辑错误引起的,在程序运行时,无法恢复。例如,通过数组的索引访问数组的元素时,如果索引超过了数组范围,就会发生索引越界异常。代码如下:
int[] arr=new int[5];
System.out.println(arr[6]);
5.3 异常处理即语法
1.异常的产生及处理
一般情况下,当程序在运行过程中发生异常时,系统会捕获抛出的异常对象并输出相应的信息,同时中止程序的运行。这种情况并不是用户所期望的,因此需要让程序接收和处理异常对象,从而避免影响其他代码的执行。当一个异常类的对象被捕获或接收后,程序就会发生流程跳转,系统中止当前的流程而跳转到专门的异常处理语句块,或直接跳出当前程序和Java虚拟机回到操作系统。
2.try...catch语句
为了使异常发生后的程序代码正常执行,程序需要捕获异常并进行处理。
try{
代码块
}catch(ExceptionType e){
代码块
}
//在try代码块中编写可能发生异常的Java语句,在catch代码块中编写针对异常进行处理的代码。
//当try代码块中的程序发生了异常时,系统会将异常的信息封装成一个异常对象,并将这个对象传递
//给catch代码块进行处理。 catch代码块需要一个参数指明它能够接收的异常类型,这个参数必须
//是Exception类或其子类。
编写try...catch语句时,需要注意以下几点:
1)try代码块是必须的。
2)catch代码块可以有多个,但捕获父类异常的catch代码块必须位于捕获子类异常的catch代码块后面。
3)catch代码块必须位于try代码块之后。
public class test{
public static void main(String[] args){
//下面代码定义了一个try...catch语句用于捕获异常
try{
int result=divide(4,0);
System.out.println(result);
}catch(Exception e){
System.out.println("捕获的异常信息为:"+e.getMessage());
}
System.out.println("程序继续向下执行...");
}
public static int divide(int x,int y){
int result=x/y;
return result;
}
}
//调用Exception对象的getMessage()方法,返回异常信息"/by zero"
需要注意的是,在try代码块中,发生异常的语句后面的代码是不会被执行的。
3.finally语句
在程序中,有时希望一些语句无论程序是否发生异常都要执行,这时就可以在try...catch语句后加一个finally代码块。finally代码块里try...catch...finally或try...finally结构的一部分,不能单独出现。
try{
代码块
}catch(ExceptionType e){
代码块
}finally{
代码块
}
由图可知,在try...catch...finally语句中,不管程序是否发生异常,finally代码块中的代码都会被执行。需要注意的是,如果程序发生异常,但是异常没有被捕获,在执行完finally代码块中的代码之后,程序会中断执行。
public class test{
public static void main(String[] args){
//下面的代码定义了一个try...catch...finally语句用于捕获异常
try{
int result=divide(4,0);
System.out.println(result);
}catch(Exception e){
System.out.println("捕获的异常信息:"+e.getMessage());
return; //用于结束当前语句
}
System.out.println("程序继续向下...");
}
public static int divide(int x,int y){
int result=x/y;
return result;
}
}
//第9行代码在catch代码块中增加了一个return语句,用于结束当前方法,这样,当catch
//代码执行完成之后,第13行代码就不会执行了。但是finally代码块中的代码仍会执行,
//不受return语句影响。也就是说,无论程序是发生异常还是使用return语句结束,finally
//代码块中的语句都会执行。
因此,在程序设计时,通常会使用finally代码块处理必须做的事,如释放系统资源。
需要注意的是,如果在try...catch中执行了System.exit(0)语句,那么finally代码块不再执行。System.exit(0)表示退出当前的Java虚拟机,Java虚拟机停止了,任何代码都不能再执行。
5.4 抛出异常
在编程过程中,有些异常暂时不需要处理,此时可以将异常抛出,让该类的调用者处理。
1.throws关键字
在实际开发中,大部分情况下程序开发者会调用别人编写的方法,并不知道别人编写的方法是否会发生异常。针对这种情况,Java允许在方法后面使用throws关键字声明该方法有可能发生的异常,这样调用者在调用该方法时,就明确地知道该方法有异常,并且必须在程序中对异常进行处理,否则无法通过编译。
修饰符 返回值类型 方法名(参数1,参数2,...) throws 异常类1,异常类2...{
方法体
}
//throws关键字需要写在方法声明的后面,throws后面还需要声明方法中发生异常的类型
public class test{
public static void main(String[] args){
int result=divide(4,2);
System.out.println(result);
}
//下面整除方法,使用throws关键字声明抛出异常
public static int divide(int x,int y) throws Exception{
int result=x/y;
return result;
}
}
//程序提示错误。这是因为定义divide()方法时使用throws关键字声明了该方法
//可能抛出的异常,调用者必须在调用divide()方法时对抛出的异常进行处理,
//否则就会发生错误
修改:
public class test{
public static void main(String[] args){
//下面定义了一个try...catch语句用于捕获异常
try{
int result=divide(4,2);
System.out.println(result);
}catch(Exception e){
e.printStackTrace();
}
}
public static int divide(int x,int y) throws Exception{
int result=x/y;
return result;
}
}
在调用divide()方法时,如果不知道如何处理声明抛出的异常,也可以使用throws关键字继续将异常抛出,这样程序也能编译通过。需要注意的是,使用throws关键字重抛异常时,如果程序发生了异常,并且上一层调用者也无法处理异常时,那么异常就会继续被向上抛出,最终直到系统接收到异常,中止程序执行。
public class test{
public static void main(String[] args) throws Exception{
int result=divide(4,0);
System.out.println(result);
}
public static int divide(int x,int y) throws Exception{
int result=x/y;
return result;
}
}
//程序通过了编译,但最终程序中止了运行
2.throw关键字
与throws关键字不同的是,throw关键字用于方法体内,抛出的是一个异常实例,并且每次只能抛出一个异常实例。
throw ExceptionInstance;
在方法中,通过throw关键字抛出异常后,还需要使用throws关键字或try...catch语句对异常进行处理。如果throw抛出的是Error、RuntimeException或它们的子类异常对象,则无法使用throws关键字或try...catch语句对异常进行处理。
使用throw关键字抛出异常,通常有如下两种情况:
1)当throw关键字抛出的异常是编译时异常时,有两种处理办法:一是,在try代码块里使用throw关键字抛出异常,通过try代码块捕获该异常;二是,在一个有throws声明的方法中使用throw关键字抛出异常,把异常交给该方法的调用者处理。
2)当throw关键字抛出的异常是运行时异常时,程序既可以使用try...catch语句捕获并处理该异常,也可以完全不理会该异常,而把该异常交给方法的调用者处理
public class test{
//定义printAge()输出年龄
public static void printAge(int age) throws Exception{
if(age<=0){
//对业务逻辑进行判断,当输入年龄为负数时抛出异常
throw new Exception("输入的年龄有误,必须是正整数!");
}else{
System.out.println("此人年龄为:"+age);
}
}
public static void main(String[] args){
//下面代码定义了一个try...catch语句用于捕获异常
int age=-1;
try{
printAge(age);
}catch(Exception e){
System.out.println("捕获的异常信息为:"+e.getMessage());
}
}
}
throw关键字除了可以抛出代码的逻辑性异常外,也可以抛出Java能够自动识别的异常。
5.5 自定义异常类
Java中定义了大量的异常类,虽然这些异常类可以描述编程时出现的大部分异常情况,但是在程序开发中有时可能需要描述程序中特有的情况。
Java允许用户自定义异常类,自定义异常类必须继承Exception类或其他子类。
class DivideByMinusException extends Exception{
public DivideByMinusException(){
super();
}
public DivideByMinusException(String message){
super(message);
}
}
在实际开发中,如果没有特殊的要求,自定义的异常类只需继承Exception类,在构造方法中使用super()语句调用Exception的构造方法即可。
使用自定义的异常类,需要用到throw关键字。使用throw关键字在方法中声明异常的实例对象:
throw Exception 异常对象
上小节代码中,divide()方法,在该方法中判断被除数是否为负数,如果为负数,就使用throw关键字在方法中向调用者抛出自定义的DivideByMinusException异常对象。
class DivideByMinusException extends Exception{
public DivideByMinusException(){
super();
}
public DivideByMinusException(String message){
super(message);
}
}
public class test{
public static void main(String[] args){
int result=divide(4,-2);
System.out.println(result);
}
//实现两个整数相除
public static int divide(int x,int y){
if (y<0){
throw new DivideByMinusException("除数是负数");
}
int result=x/y;
return result;
}
}
//程序在编译时就发生了异常,因为在一个方法内使用throw关键字抛出异常对象时,需要
//使用try...catch语句对抛出的异常进行处理,或者在divide()方法后面使用throws关
//键字声明抛出异常,由该方法的调用者负责处理
class DivideByMinusException extends Exception{
public DivideByMinusException(){
super();
}
public DivideByMinusException(String message){
super(message);
}
}
public class test{
public static void main(String[] args){
try{
int result=divide(4,-2);
}catch(DivideByMinusException e){
System.out.println(e.getMessage());
}
}
public static int divide(int x,int y) throws DivideByMinusException{
if(y<0){
throw new DivideByMinusException("除数是负数");
}
int result=x/y;
return result;
}
}
//在调用divide()方法时,如果传入的除数为负数,程序会抛出自定义的DivideByMinusException
//异常,该异常最终被catch代码块捕获并处理,最后打印出异常现象
课后题
1.通过键盘输入一个int类型的整数,输出其二进制形式(Integer.toBinaryString())。用异常处理语句处理:如果录入的整数过大(BigInteger)、录入的是小数(BigDecimal)或录入的是其它字符,请给予提示。(提示:建议首先键盘录入的结果存储在String类型的字符串中(Scanner.nextLine()),如果直接用nextInt()将其存入int类型数据中,若有非法字符就会直接报错,无法进行后续判断)
import java.util.Scanner;
import java.math.BigDecimal;
import java.math.BigInteger;
class a1 {
public static void main(String[] args) {
// 创建一个Scanner对象,用于接收用户输入
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个整数:");
// 使用while循环不断接收用户输入,直到输入的是一个合法的整数
while (true) {
String line = sc.nextLine(); // 读取一行输入
try {
int num = Integer.parseInt(line); // 尝试将输入转换为整数
System.out.println(Integer.toBinaryString(num)); // 输出整数的二进制表示
break; // 如果转换成功,跳出循环
} catch (Exception e) {
try {
new BigInteger(line); // 尝试将输入转换为大整数
System.out.println("录入错误,您录入的是一个过大整数,请重新输入一个整数:");
} catch (Exception e2) {
try {
new BigDecimal(line); // 尝试将输入转换为小数
System.out.println("录入错误,您录入的是一个小数,请重新输入一个整数:");
} catch (Exception e1) {
System.out.println("录入错误,您录入的是非法字符,请重新输入一个整数:");
}
}
}
}
}
}
2.从命令行得到5个整数,放入一整型数组,然后打印输出。要求:如果输入的数据不为整数(Integer.parseInt()),要进行异常捕获,提示“请输入整数”;如果输入的整数不足5个,进行异常捕获,提示“请输入5个整数”。
import java.util.*;
public class a2 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int a[] = new int[6];
int count = 0; // 记录已经输入的数字数量
System.out.println("请输入5个整数,用空格分隔:");
String input = in.nextLine(); // 读取一行输入
String[] tokens = input.split("\\s+"); // 按空格分割输入
for (String token : tokens) {
try {
a[count] = Integer.parseInt(token);
count++;
if (count > 5) {
System.out.println("输入过多,请只输入5个数!");
return; // 终止程序
}
} catch (NumberFormatException e) {
System.out.println("无效输入,请输入整数!");
return; // 终止程序
}
}
if (count < 5) {
System.out.println("输入不足,请输入5个数!");
} else {
System.out.println("您输入的数字为:");
for (int num : a) {
System.out.print(num + " ");
}
System.out.println("\n感谢使用本程序!");
}
in.close();
}
}
七、Java API
API(Application Programming Interface)指的是应用程序编程接口,API可以让编程变得更加简单方便。Java也提供了大量的API,即Java API。Java API指的就是JDK提供的各种功能的Java类库。
6.1 字符串类
字符串是指由一对英文双引号括起来的有限序列。Java提供了3个定义字符串的类,分别是String、StringBuffer、StringBuilder,他们位于Java.lang包中。
1.String类
在使用String类进行操作前,首先需要初始化一个String类对象,有两种方式:
1)使用字符串常量直接初始化一个String对象
String 变量名=字符串;
//String类是专门用于处理字符串的类。字符串一旦被创建,其内容就不能再改变。
2)调用String类的构造方法初始化字符串对象
String 变量名=new String(字符串);
//字符串同样可以为空或一个具体的字符串。当为具体字符串时,String会根据参数类型调用
//相应的构造方法来初始化字符串对象。
public class test{
public static void main(String[] args){
//创建一个空字符串
String str1=new String();
String str2=new String("abcd");
char[] charArray=new char[]{'D','E','F'};
String str3=new String(charArray);
byte[] arr={97,98,99};
String str4=new String(arr);
System.out.println("a"+str1+"b");
System.out.println(str2);
System.out.println(str3);
System.out.println(str4);
}
}
注意:连接字符串可以通过运算符 + 来实现。再Java程序中,如果 + 两边的操作数中有一个String类型,那么 + 就表示字符串连接运算符。
2.String类的常用方法
在Java程序中可以调用String类的方法来操作字符串。
1.获取字符串长度以及访问字符串中的字符
//有时需要获取字符串的一些信息,如获取字符串长度,获取指定位置的字符
public class test{
public static void main(String[] args){
String s="ababcdedcba";
System.out.println("字符串的长度为:"+s.length());
System.out.println("字符串中第一个字符:"+s.charAt(0));
System.out.println("字符c第一次出现的位置:"+s.indexOf('c'));
System.out.println("字符c最后一次出现的位置:"+s.lastIndexOf('c'));
System.out.println("子字符串ab第一次出现的位置:"+s.indexOf("ab"));
System.out.println("子字符串ab最后一次出现的位置:"+s.lastIndexOf("ab"));
}
}
2.字符串的转换操作
//例如,将字符串转换成数组的形式,对字符串中的字符进行大小写转换,等等
public class test{
public static void main(String[] args){
String str="abcd";
System.out.println("将字符串转为字符数组后的结果:");
char[] charArray=str.toCharArray();
for(int i=0;i<charArray.length;i++){
if(i!=charArray.length-1){
System.out.println(charArray[i]+",");
}else{
System.out.println(charArray[i]);
}
}
//将int类型12 转换为字符串
System.out.println("将int值转换为String类型之后的结果:"+String.valueOf(12));
System.out.println("将字符串转换成大写之后的结果:"+str.toUpperCase());
System.out.println("将字符串转换成小写之后的结果: "+str.toLowerCase());
}
}
3.字符串的替换和去除空格操作
//在程序开发中,用户经常会不小心输入错误的数据或多余的空格,这时调用String类的
//replace()和trim()方法,进行字符串的替换和去除空格操作。trim()方法用于去除
//字符串两端的空格,不能去除中间的空格;若想去除中间的空格,需要调用replace()
//方法
public class test{
public static vodi main(String[] args){
String s="itcast";
System.out.println("将it替换成cn.it的结果是:"+s.replace("it","cn.it"));
String s1=" i t c a s t ";
System.out.println("去除字符串两端空格后的结果:"+s1.trim());
System.out.println("去除字符串中所有空格后的结果:"+s1.replace(" ",""));
}
}
4.字符串判断
//例如,判断字符串是否以指定的字符串开始或结束,判断字符串是否包含指定的字符串,
//字符串是否为空,等等
public class test{
public static void main(String[] args){
String s1="String";
String s2="string";
System.out.println("判断s1字符串对象是否以Str开头:"+s1.startsWith("Str"));
System.out.println("判断是否以字符串ng结尾:"+s1.endsWith("ng"));
System.out.println("判断是否包含字符串tri:"+s1.contains("tri"));
System.out.println("判断字符串是否为空:"+s1.isEmpty());
System.out.println("判断s1和s2内容是否相同:"+s1.equals(s2));
System.out.println("忽略大小写的情况判断s1和s2内容是否相同:"
+s1.equalsIgnoreCase(s2));
System.out.println("按对应字符的Unicode比较s1和s2的大小:"
+s1.compareTo(s2));
}
}
//在判断两个字符串是否相等时。equals()方法用于比较两个字符串内容是否相等,==用于
//比较两个字符串对象的地址是否相同。对于两个内容完全一样的字符串对象,调用equals()
//方法判断的结果是true,使用==判断的结果为false
String str1=new String("abc");
String str2=new String("abc");
//使用==判断的结果为false,因为str1和str2是两个对象,地址不同
System.out.println(str1==str2);
//调用equals(),结果为true
System.out.println(str1.equals(str2));
5.字符串的截取和分割操作
//例如,截取一个文本中的部分内容,使用特殊的符号将字符串分割为若干段。
//String类提供了substring()方法和split()方法实现字符串的截取和分割操作,substring()
//方法用于截取字符串的一部分,split()方法用于将字符串按照某个字符进行分割。
public class test{
public static void main(String[] args){
String str="石家庄-武汉-哈尔滨";
System.out.println("从第5个字符截取到末尾的结果:"+str.substring(4));
System.out.println("从第5个字符截取到第6个字符:"+str.substring(4,6));
System.out.println("分割后的字符串数组中的元素依次为:");
String[] strArray=str.split("-");
for(int i=0;i<strArray.length;i++){
if(i!=strArray.length-1){
System.out.println(strArray[i]+",");
}else{
System.out.println(strArray[i]);
}
}
}
}
注意:String字符串在获取某个字符时,会用到字符的索引。当访问字符串中的字符时,如果字符的索引不存在,则会发生StringIndexOutOfBoundsException(字符串索引越界异常) 这与数组中的索引越界异常非常相似。
3.StringBuffer类
因为String类是final类型的,所以使用String定义的字符串是一个常量,也就是说使用String定义的字符串一旦创建,其内容和长度是不可改变的。为了便于对字符串进行修改,Java提供了StringBuffer类(也称字符缓冲区)来操作字符串。StringBuffer类和String类最大的区别在于它的内容和长度都可以改变。
StringBuffer类就像一个字符容器,当在其中添加或删除字符时,操作的都是这个字符容器,因此并不会产生新的StringBuffer对象。
public class test{
public static void main(String[] args){
Systrm.out.println("1.添加-----");
add();
Systrm.out.println("2.删除-----"):
remove();
Systrm.out.println("3.修改-----");
alter();
Systrm.out.println("4.截取-----");
sub();
}
public static void add(){
StringBuffer sb=new StringBuffer();//定义一个字符缓冲区
sb.append("abcdefg"); //在末尾添加字符串
sb.append("hij").append("klmn"); //连续调用append()方法添加字符串
Systrm.out.println("append添加结果:"+sb);
sb.insert(2,"123"); //在指定位置插入字符串
Systrm.out.println("insert添加结果:"+sb);
}
public static void remove(){
StringBuffer sb=new StringBuffer("abcdefg");
sb.delete(1,5); //指定范围删除
Systrm.out.println("删除指定范围结果:"+sb);
sb.deleteCharAt(2); //指定位置删除
Systrm.out.println("删除指定位置结果:"+sb);
sb.delete(0,sb.length()); //清空字符串缓冲区
Systrm.out.println("清空缓冲区结果:"+sb);
}
public static void alter(){
StringBuffer sb=new StringBuffer("abcdefg");
sb.setCharAt(1,'p'); //修改指定位置字符
Systrm.out.println("修改指定位置字符结果:"+sb);
sb.replace(1,3,"qq"); //替换指定位置字符串或字符
Systrm.out.println("替换指定位置字符(串)结果:"+sb);
Systrm.out.println("字符串翻转结果:"+sb.reverse());
}
public static void sub(){
StringBuffer sb=new StringBuffer();
Systrm.out.println("获取sb的初始容量:"+sb.capacity());
sb.append("itcast123");
Systrm.out.println("append添加结果:"sb);
Systrm.out.println("截取第7~9个字符:"+sb.substring(6,9));
}
}
4.StringBuilder类
StringBuilder类与StringBuffer类的功能相似,且两个类中提供的方法也基本相同。二者最大的不同在于StringBuffer类的方法是线程安全的,而StringBuilder类没有实现线程安全功能。
针对StringBuilder类、StringBuffer类和String类进行对比。
1)String类表示的字符串是常量,一旦创建后,内容和长度都无法改变的。而StringBuilder类和StringBuffer类表示字符容器,其内容和长度可以随时修改。在操作字符串时,如果需要对字符串中的字符进行增删操作,则使用StringBuffer类与StringBuilder类。如果有大量字符串拼接操作,斌且不要求线程安全,使用StringBuilder类更高效,相反,如果需要线程安全,使用StringBuffer类。
2)StringBuffer类与StringBuilder类并没有重写Object类的equals()方法,即equals不会起作用。
String s1=new String("abc");
String s2=new String("abc");
System.out.println(s1.equals(s2)); //打印true
StringBuffer sb1=new StringBuffer("abc");
StringBuffer sb2=new StringBuffer("abc");
System.out.println(sb1.equals(sb2)); //打印false
StringBuilder sbr1=new StringBuilder("abc");
StringBuilder sbr2=new StringBuilder("abc");
System.out.println(sbr1.equals(sbr2)); //打印false
3)String类对象可以用+进行拼接,而StringBuffer类和StringBuilder类的对象则不能
String s1="a";
String s2="b";
String s3=s1+s2; //合法
System.out.println(s3);
StringBuffer sb1=new StringBuffer("a");
StringBuffer sb2=new StringBuffer("b");
StringBuffer sb3=sb1+sb2; //出错
StringBuilder sb4=new StringBuilder("a");
StringBuilder sb5=new StringBuilder("b");
StringBuilder sb6=sb4+sb5; //出错
6.2 System类和Runtime类
1.System类
System类定义了一些与系统相关的属性和方法,它提供的属性和方法都是静态的,因此可以使用System类直接引用类中的属性和方法。
1.arraycopy()方法
//arraycopy()方法用于将源数组中的元素复制到目标数组
static void arraycopy(Object src,int srcPos,Object dest,int destPoe,int length)
//src: 表示源数组
//dest:表示目标数组
//srcPos:表示元素组中复制元素的起始位置,即从哪个位置开始复制元素
//destPos:表示复制到目标数组的起始位置,即从哪个位置开始放入复制元素
//length:表示复制元素个数
注意:在进行数组复制时,目标数组必须有足够的空间来存放复制的元素,否则会发生索引越界异常。
public class test{
public static void main(String[] args){
int[] fromArray={10,11,12,13,14,15}; //源数组
int[] toArray={20,21,22,23,24,25,26}; //目标数组
System.arraycopy(fromArray,2,toArray,3,4);//复制数组元素
//打印复制狗的数组元素
System.out.println("复制后的数组元素为:");
for(int i=0;i<toArray.length;i++){
System.out.println(toArray[i]+" ");
}
}
}
2.currentTimeMillis()方法
//该方法用于获取当前系统的时间,返回值类型是long,该值表示当前时间与1970年1月1日0时0分0秒
//之间的时间差,单位是毫秒,通常也将该值称作时间戳(系统当前的时间)。
public class test{
public static void main(String[] args){
long startTime=System.currentTimeMillis(); //循环开始时的当前时间
int sum=0;
for(int i=0;i<1000000000;i++){
sum+=i;
}
long endTime=System.currentTimeMills(); //循环结束时的当前时间
System.out.println("程序运行的时间为:"+(endTime-startTime)+"ms");
}
}
3.getProperties()和getProperty()方法
//前者用于获取当前系统的全部属性,该方法会会返回一个Properties对象,该对象封装了系统的
//所有属性,这些属性以键值对形式存在。
//后者可以根据系统的属性名获取对应的属性值。
import java.util.*;
public class test{
public static void main(String[] args){
//获取当前系统属性
Properties properties=System.getProperties();
//获取所有系统属性的键,返回Enumeration对象
Enumeration propertyNames=properties.propertyNames();
while(propertyNames.hasMoreElements()){
//获取系统属性的键
String key=(String)propertyNames.nextElement();
//获取当前键的对应值
String value=System.getProperty(key);
System.out.println(key+"--->"+value);
}
}
}
Enumeration 是 Java 中的一个接口,用于遍历集合中的元素。它提供了一种标准的方法来顺序访问集合中的每个元素,而无需知道集合的具体实现细节。Enumeration 接口主要包含以下两个方法:
boolean hasMoreElements():
检查枚举是否还有更多元素可以返回。
返回 true 表示还有更多元素,返回 false 表示没有更多元素。
E nextElement():
返回集合中的下一个元素。
如果没有更多元素,则抛出 NoSuchElementException 异常。
4.gc()方法
在Java中一个对象如果不再被任何栈内存引用,该对象就成为垃圾对象。一个对象成为垃圾对象后仍会占用内存空间,时间一长,垃圾对象就越来越多,就会导致内存空间不足。针对这种情况,Java引入垃圾回收机制。有了这种机制,程序员不需要过多关心垃圾回收的问题,Java虚拟机会自动回收垃圾对象所占用的内存空间。
一个对象在成为垃圾对象后,会暂时保留在内存中。当这样的垃圾对象堆积到一定程度时,Java虚拟机就会启动垃圾回收器将这些垃圾对象从内存中释放,从而使程序获取更多可用的内存空间。除了等待Java虚拟机进行自动垃圾回收外,还可以通过调用System.gc()方法通知Java虚拟机立即进行垃圾回收,在系统回收垃圾对象占用内存时,会自动调用Object类的finalize()方法,因此可以在类中通过重写finalize()方法观察对象何时被释放。
class Person{
private String name;
private int age;
public Person(String name,int sge){
this.name=name;
this.age=age;
}
@Override
public String toString(){
return "姓名:"+this.name+",年龄"+this.age;
}
//下面定义的finalize()方法会在垃圾回收前被调用
public void finalize() throws Throwable{
System.out.println("对象被释放-->"+this);
}
}
public class test{
public static void main(String[] args){
//创建Person对象
Person p=new Person("张三",20);
//将变量置为null,让对象p变为垃圾
p=null;
//调用gc()方法进行垃圾回收
System.gc();
for(int i=0;i<1000000;i++){
//为延长程序运行时间执行空循环
}
}
}
//Person类的finalize()方法被执行了
2.Runtime类
Runtime类用于封装,Java虚拟机进程,通过Runtime类,可以获取Java虚拟机运行时状态。每一个Java虚拟机都对应一个Runtime类的实例。在JDK文档中读者不会发现任何有关Runtime类的构造方法的定义,这是因为Runtime类本身的构造方法是私有化的(单例设计模式),若想在程序中获取有关Runtime类实例,只能通过调用getRuntime()方法获取,该方法是Runtime类提供的有关静态方法,用于获取runtime类实例。通过调用getRuntime()方法获取Runtime类实例:
Runtime run=Runtime.getRuntime();
由于Runtime类封装了Java虚拟机进程,因此,在程序中通常会通过Runtime类的实例对象获取当前Java虚拟机的相关信息。
1.获取当前虚拟机信息
//Runtime类可以获取当前Java虚拟机的处理个数,空闲内存量,最大可用内存量和内存总量的信息等。
public class test{
public static void main(String[] args){
Runtime rt=Runtime.getRuntime();
System.out.println("处理器的个数:"+rt.availableProcessors()+"个");
System.out.println("空闲内存量:"+rt.freeMemory()/1024/1024+"MB");
System.out.println("内存总量:"+rt.totalMemory()/1024/1024+"MB");
}
}
2.操作系统进程
//Runtime类中提供了exec()方法,该方法用于执行一个DOS命令,其执行效果与直接执行DOS命令的效果相同。
import java.io.IOException;
public class test{
public static void main(String[] args){
Runtime rt=Runtime.getRuntime(); //创建Runtime对象
rt.exec("notepad.exe"); //调用exec()方法
}
}
//Runtime类的exec()方法的返回值为Process类型的对象,表示一个操作系统的进程类。通过Process类
//可以进行系统进程的控制,如果要关闭进程,只需调用Process类的destroy()方法即可。
public class test{
public static void main(String[] args){
Runtime rt=Runtime.getRuntime(); //创建一个Runtime实例对象
Process.process=rt.exec("notepad.exe"); //得到表示进程的Process对象
Thread.sleep(3000); //程序休眠3s
process.destroy(); //关闭进程
}
}
//调用了Thread类的静态方法sleep(long mills)
6.3 Math类与Random类
1.Math类
Math类是一个工具类,其中包含许多用于进行科学计算的方法。因为Math类构造方法的访问权限为private,所以无法创建Math类的对象。Math类中的所有方法都是静态方法,可以直接通过类名调用Math类中的方法。除静态方法外,Math类中还定义了两个静态常量——PI和E,分别代表数学中的 Π 和 e.
public class test{
public static void main(String[] args){
System.out.println("计算-10的绝对值:"+Math.abs(-10));
System.out.println("求大于5.6的最小整数:"+Math.ceil(5.6));
System.out.println("求小于-4.2的最大整数:"+Math.floor(-4.2));
System.out.println("对-4.6进行四舍五入:"+Math.round(-4.6));
System.out.println("求2.1和-2.1中的较大值:"+Math.max(2.1,-2.1));
System.out.println("求2.1和-2.1中的较小值:"+Math.min(2.1,-2.1));
System.out.println("生成一个大于或等于0.0且小于1.0的随机数:"+Math.random());
System.out.println("计算1.57的正弦值:"+Math.sin(1.57));
System.out.println("计算4的平方根:"+Math.sqrt(4));
System.out.println("计算2的3次方的值:"+Math.pow(2,3));
}
}
2.Random类
Random类可以产生指定取值范围的随机数。Random类提供了两个构造方法。
第一个构造方法是无参的,通过它创建的Random对象每次使用的种子是随机的,因此每个对象产生的随机数不同。如果希望创建的多个Random对象产生相同的随机数,则可以在创建对象时调用第二个构造方法,传入相同的参数即可。
//第一个构造方法,无参
import java.util.Random;
public class test{
public static void main(String[] args){
Random random=new Random(); //不传入种子
//随机产生10个[0~100)区间的整数
for(int x=0;x<10;x++){
System.out.println(random.nextInt(100));
}
}
}
//第二个构造方法,有参
import java.util.Random;
public class test{
public static void main(String[] args){
Random r=new Random(13); //创建对象时传入种子
//随机产生10个[0,100)区间的整数
for(int x=0;x<10;x++){
System.out.println(r.nextInt(100));
}
}
}
Math类中提供了生成随机数的random()方法。相对于Math类,Random类提供了更多的方法来生成随机数,不仅可以生成整数类型的随机数,还可以生成浮点数类型的随机数。
nextBoolean()方法返回的是true或false,nextDouble()方法返回的是0.0(包括)~1.0(不包括)的double类型的值,nextFloat()方法返回的是0.0(包括)~1.0(不包括)的float类型的值,nextLong()方法返回的是long类型的值,nextInt(int i)方法返回的是0(包括)到指定值n(不包括)的int类型的值。
import java.util.Random;
public class test{
public static void main(String[] args){
Random r=new Random();
System.out.println("生成boolean类型的随机数:"+r.nextBoolean());
System.out.println("生成float类型的随机数:"+r.nextFloat());
System.out.println("生成double类型的随机数:"+r.nextDouble());
System.out.println("生成int类型的随机数:"+r.nextInt());
System.out.println("生成0~100的int类型的随机数:"+r.nextInt(100));
System.out.println("生成long类型的随机数:"+r.nextLong());
}
}
6.4 BigInteger类与BigDecimal类
1.BigInteger类
当程序需要处理一个非常大的整数时,如果这个数值超出了long类型的取值范围,则无法使用基本类型的对象接收。早期程序开发者使用String类进行大整数的接收,再采用拆分的方式进行计算,操作过程非常麻烦。为了解决这个问题,Java提供了BigInteger类
BigInteger类表示最大整数,定义在java.math包中,如果在开发时需要定义一个超出long类型的取值范围的整型数据,可以使用BigInteger类的对象接收该数据。
import java.math.BigInteger;
class test{
public static void main(String[] args){
BigInteger bi1=new BigInteger("123456789");
BigInteger bi2=new BigInteger("987654321");
System.out.println("bi2和bi1的和:"+bi2.add(bi1));
System.out.println("bi2和bi1的差:"+bi2.subtract(bi1));
System.out.println("bi2和bi1的积:"+bi2.multiply(bi1));
System.out.println("bi2和bi1的商:"+bi2.divide(bi1));
System.out.println("bi2和bi1的较大值:"+bi2.max(bi1));
System.out.println("bi2和bi1的较小值:"+bi2.min(bi1));
//创建BigInteger数组接收bi2除以bi1的商和余数
BigInteger result[]=bi2.divideAndRemainder(bi1);
System.out.println("bi2除以bi1的商:"+result[0]+":bi2除以bi1的余数:"+result[1]);
}
}
2.BigDecimal类
在进行浮点数运算的时候,float类型和double类型很容易丢失精度,为了能够精确地表示和计算浮点数,Java提供了BigDecimal类。BigDecimal类可以表示任意精度的小数,多用于数字精度要求高的场景,例如商业计算、货币值计算等。
import java.math.BigDecimal;
public class test{
public static void main(String[] args){
BigDecimal bd1=new BigDecimal("0.001"); //创建BigDecimal对象
BigDecimal bd2=BigDecimal.valueOf(0.009); //创建BigDecimal对象
System.out.println("bd2与bd1的和:"+bd2.add(bd1));
System.out.println("bd2与bd1的差:"+bd2.subtract(bd1));
System.out.println("bd2与bd1的积:"+bd2.multiply(bd1));
System.out.println("bd2与bd1的商:"+bd2.divide(bd1));
System.out.println("bd2与bd1之间的较大值:"+bd2.max(bd1));
System.out.println("bd2与bd1之间的较小值:"+bd2.min(bd1));
}
}
6.5 日期与时间类
1.Date类
JDK的java.util包提供了Date类,用于表示日期和时间。随着JDK版本的不断升级和发展,Date类中的大部分的构造方法和普通方法都已经不再推荐使用,只有下面两个构造方法是实际开发中经常被用到的:
Date():用于创建当前日期和时间的Date对象。
Date(long date):用于创建指定日期和时间的Date对象,其中date参数表示1970年1月1日0时0 分0秒(称为历元)以来的毫秒数,即时间戳。
import java.util.*;
public class test{
public static void main(String[] args){
//创建表示当前日期的时间Date对象
Date date1=new Date();
//获取当前日期和时间后1s的时间
Date date2=new Date(System.currentTimeMillis()+1000);
System.out.println(date1);
System.out.println(date2);
}
}
//程序输出当前日期和时间 以及 1s后的日期和时间
2.Calendar类
Date类设计之初没有考虑国际化的问题。Date类输出的日期格式并不符合中国的日期标准格式。Java提供了Calendar类,用Calendar类中的方法取代了Date类的相应功能。
Calendar类也用于完成日期和时间字段的操作,它可以通过特定的方法设置和读取日期和时间的特定部分,如年、月、日、时、分、秒等。
Calendar类是一个抽象类,不可以被实例化,如果想在程序中获取一个Calendar实例,则需要调用Calendar类的静态方法getInstance()。
Calendar calendar=Calendar.getInstance();
表中大多数方法都用到了int类型的参数field,该参数需要接收Calendar类中定义的常量值,这些常量值分别表示不同的字段,Calendar类常用的常量值如下:
Calendar.YEAR:用于获取当前年份。
Calendar.MONTH:用于获取当前月份(注意,月份起始值从0开始,要在基础上加1)。
Calendar.DATE:用于获取当前日。
Calendar.HOUR:用于获取当前的时。
Calendar.MINUTE:用于获取当前的分。
Calendar.SECOND:用于获取当前的秒。
import java.util.*;
public class test{
public static void main(String[] args){
//获取表示当前日期和时间的Calendar对象
Calendar calendar=Calendar.getInstance();
int year=calendar.get(Calendar.YEAR); //获取当前年份
int month=calendar.get(Calendar.MONTH); //获取当前月份
int date=calendar.get(Calendar.DATE); //获取当前的日
int hour=calendar.get(Calendar.HOUR); //获取当前的时
int minute=calendar.get(Calendar.MINUTE); //获取当前的分
int second=calendar.get(Calendar.SECOND); //获取当前的秒
System.out.println("当前时间为:"+year+"年"+month+"月"+date+"日"+hour+"时"+minute+
"分"+second+"秒");
}
}
//在程序中除了要获取计算机的当前日期和时间外,还会经常设置或修改某个日期和时间。其中添加和修改日期
//的功能就可以通过Calendar类中的add()和set()方法来实现。
import java.util.*;
public class test{
public static void main(String[] args){
//获取表示当前日期和时间的Calendar对象
Calendar calendar=Calendar.getInstance();
//设置指定日期
calendar.set(2021,1,1);
//为指定日期增加天数
calendar.add(Calendar,Date,100);
//返回指定日期的年
int year=calendar.get(Calendar.YEAR);
//返回指定日期的月
int month=calendar.get(Calendar.MONTH)+1;
//返回指定日期的日
int date=Calendar.get(Calendar.DATE);
Syetem.out.println("计划竣工日期为:"+year+"年"+month+"月"+date+"日");
}
}
需要注意的是,Calendar.DATE表示的是天数。当天数累加到当月的最大值时,如果继续累加,Calendar.DATE的天数就会从1开始计数,同时月份值会自动加1,这和算术运算中的进位类似。
Calendar类不支持时区,而且不是线程安全的。为了解决这些问题,从JDK8开始增加了一个java.time包。
3.Instant类
Instant类代表的是某个时刻。其内部由两部分组成,第一部分保存的是标准Java历元到现在的秒数;第二部分是纳秒数。
import java.time.Instant;
public class test{
public static void main(String[] args){
//Instant类的时间戳类从1970-01-01 00:00:00 截止到当前时间的毫秒值
Instant now=Instant.now();
System.out.println("从系统获取的当前时刻为:"+now);
Instant instant=Instant.ofEoichMilli(1000*60*60*24);
System.out.println("计算机元年增加1000*60*60*24毫秒数后为:"+instant);
Instant instant1=Instant.ofEpochSecond(60*60*24);
System.out.println("计算机元年增加60*60*24秒数后为:"+instant1);
System.out.println("获取的秒值为:"+Instant.parse("2007-12-03T10:15:30.44Z").
getEpochSecond());
System.out.println("获取的纳秒值为:"+Instant.parse("2007-12-03T10:15:30.44Z").
getNano());
System.out.println("从时间对象获取的Instant实例为:"+Instant.from(now));
}
}
4.LocalDate类
LocalDate类表示不带时区的日期,如2021-01-21。LocalDate类不能代表时间线上的即时消息,只是日期描述。该类提供了两个获取日期对象的方法——now()和of(int year,int month,int dayOfMonth)
//按指定日期创建LocalDate对象
LocalDate date=LocalDate.of(2020,12,12);
//从默认时区的系统时钟获取当前日期
LocalDate now1=LocalDate.now();
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class test{
public static void main(String[] args){
//获取日期时分秒
LocalDate now=LocalDate.now();
LocalDate of=LocalDate.of(2015,12,12);
System.out.println("1.LocalDate的获取及格式化的相关方法----");
System.out.println("从LocalDate实例获取当前的年份是:"+now.getYear());
System.out.println("从LocalDate实例获取当前的月份是:"+now.getMonthValue());
System.out.println("从LocalDate实例获取当天为本月的第几天:"+now.getDayOfMonth());
System.out.println("将获取到的LocalDate实例格式化后:"+
now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
System.out.println("2.LocalDate判断的相关方法------");
System.out.println("判断日期of是否在now之前:"of.isBefore(now));
System.out.println("判断日期of是否在now之后:"of.isAfter(now));
System.out.println("判断日期of和now是否相等:"now.equals(of));
System.out.println("判断日期of是否是闰年:"of.isLeapYear());
//给出一个符合默认格式要求的日期字符串
System.out.println("3.LocalDate解析以及加减操作的相关方法-----");
String dateStr="2020-02-01";
System.out.println("把日期字符串解析成日期对象的结果是:"+
LocalDate.parse(dateStr));
System.out.println("将LocalDate实例年份加1后的结果是:"+now.plusYears(1));
System.out.println("将LocalDate实例天数减10后的结果是:"+now.minusDays(10));
System.out.println("将LocalDate实例的年份设置为2014后的结果是:"+
now.withYear(2014));
}
}
5.LocalTime类与LocalDateTime类
前者是一个表示时间的类,后者是一个表示日期、时间的类。
1.LocalTime类
LocalTime类用来表示不带时区的时间,通常表示时、分、秒,如14:49:20。与LocalDate类一样,LocalTime类不能代表时间线上的即使信息,只是时间的描述。LocalTime类中提供了获取时间对象的方法,与LocalDate类对应的用法类似。
import java.time.Localtime;
import java.time.format.DateTimeFormatter;
public class test{
public static void main(String[] args){
//获取当前时间,包含毫秒数
LocalTime time=LocalTime.now();
LocalTime of=LocalTime.of(9,23,23);
System.out.println("从LocalTime获取的小时为:"+time.getHour());
System.out.println("将获取的LocalTime实例格式化为:"+
time.format(DateTimeFormatter.ofPattern("HH:mm:ss")));
System.out.println("判断时间of是否在now之前:"+of.isBefore(time));
System.out.println("将时间字符串解析为时间对象后为:"+LocalTime.parse("12:15:30"));
System.out.println("从LocalTime获取当前时间,不包含毫秒数:"+time.withNano(0));
}
}
2.LocalDateTime类
LocalDateTime类包含了LocalDate类与LocalTime类的所有方法。
LocalDateTime类表示不带时区的日期和时间,默认的日期时间格式为年-月-日 T 时:分:秒,如2020-0829 T 21:23:26.774 这与日常使用的日期格式不再符合,所以LocalDateTime类通常和DateTimeFormatter类一起使用,DateTimeFormatter类用于指定日期时间格式。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class test{
public static void main(String[] args){
//获取系统当前年月日时分秒
LocalDateTime now=LocalDateTime.now();
System.out.println("获取的当前日期时间为:"+now);
System.out.println("将目标LocalDateTime转换为相应的LocalDate实例:"
+now.toLocalDate());
System.out.println("将目标LocalDateTime转换为相应的LocalTime实例:"
+now.toLocalTime());
//指定格式
DateTimeFormatter ofPattern=DateTimeFormatter.ofPattern
("yyyy年MM月dd日HH时mm分ss秒");
System.out.println("格式化后的日期为:"+now.format(ofPattern));
}
}
6.Duration类与Period类
提供了简单的时间/日期间隔计算方法。
1.Duration类
该类表示两个时间的间隔,时间间隔的单位可以是天、时、分、秒、毫秒和纳秒。
import java.time.Duraton;
import java.time.LocalTime;
public class test{
public static void main(String[] args){
LocalTime start=LocalTime.now();
LocalTime end=LocalTime.of(20,13,23);
Duration duration=Duration.between(start,end);
//间隔的时间
System.out.println("时间间隔为:"+duration.toNanos()+"纳秒");
System.out.println("时间间隔为:"+duration.toMillis()+"毫秒");
System.out.println("时间间隔为:"+duration.toHours()+"小时");
}
}
2.Period类
该类主要用于计算两个日期的间隔,时间间隔的单位可以是年、月和日。
import java.time.LocalDate;
import java.time.Period;
public class test{
public static void main(String[] args){
LocalTime birthday=LocalTime.of(2018,12,12);
LocalTime now=LocalTime.now();
//间隔的时间
Period between=Period.between(birthday,now);
System.out.println("时间间隔为:"+between.getYears()+"年");
System.out.println("时间间隔为:"+between.getMonths()+"月");
System.out.println("时间间隔为:"+between.getDays()+"日");
}
}
6.6 日期与时间格式化类
1.DateFormat类
尽管使用java.util.Date类能够获取日期和时间,但是其显示格式与日常使用的日期和时间格式不同,因此,Java提供了DateFormat类,该类可以将日期时间进行格式化,使日期和时间的格式符合人们的习惯。
DateFormat是一个抽象类。不能被直接实例化,但它提供了一系列用于获取DateFormat类实例的静态方法,并调用其他相应的方法进行操作。
DateFormat类还定义了许多常量,其中有4个常量值可以作为参数传递给DateFormat类的方法,表示不同格式的日期/时间。
FULL:用于表示完整格式的日期/时间。
LONG:用于表示长格式的日期/时间。
MEDIUM:用于表示普通格式的日期/时间。
SHORT:用于表示短格式的日期/时间。
import java.text.*;
import java.util.*;
public class test{
public static void main(String[] args){
//创建Date对象
Date date=new Date();
//Full格式的日期格式器对象
DateFormat fullFormat=DateFormat.getDateInstance(DateFormat.FULL);
//LONG格式的日期格式器对象
DateFormat longFormat=DateFormat.getDateInstance(DateFormat.LONG);
//MEDIUM格式的日期/时间格式器对象
DateFormat mediumFormat=DateFormat.getDateInstance(DateFormat.MEDIUM,
DateFormat.MEDIUM);
//SHORT格式的日期/时间格式器对象
DateFormat shortFormat=DateFormat.getDateInstance(DateFormat.SHORT,
DateFormat.SHORT);
//打印格式化后的日期/时间
System.out.println("当前日期的完整格式为:"+fullFormat.format(date));
System.out.println("当前日期的长格式为:"+longFormat.format(date));
System.out.println("当前日期的普通格式为:"+mediumFormat.format(date));
System.out.println("当前日期的短格式为:"+shortFormat.format(date));
}
}
DateFormat类还提供了parse()方法,该方法能够将一个字符串解析成Date对象,但是parse()方法要求字符串必须符合日期/时间的格式要求,否则会抛出异常。
import java.text.*;
public class test{
public static void main(String[] args) throws ParseException{
//创建LONG格式的DateFormat对象
DateFormat dt=DateFormat.getDateInstance(DateFormat.LONG);
//定义日期格式字符串
String str="2021年05月20日";
//输出对应格式的字符串解析成Date对象后的结果
System.out.println(dt.parse(str));
}
}
2.SimpleDateFormat类
在调用DateFormat对象的parse()方法将字符串解析为日期/时间,需要输入固定格式的字符串,这显然不够灵活。为了能够更好地格式化日期/时间、解析字符串,Java提供了SimpleDateFormat类。
SimpleDateFormat类是DateFormat类的子类,它可以使用new关键字创建实例对象。在创建实例对象时,SimpleDateFormat类的构造方法需要接收一个表示日期/时间格式模板的字符串参数,日期/时间格式模板通过特定的日期/时间标记可以将一个日期/时间中的数字提取出来。
SimpleDateFormat类的功能非常强大,在创建SimpleDateFormat对象时,只要传入合适的格式字符串参数,就能解析各种形式的日期/时间字符串。其中,格式字符串参数是一个使用日期/时间字段占位符的日期模板。
import java.util.*;
public class test{
public static void main(String[] args){
//创建一个SimpleDateFormat对象
SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日");
//将一个日期/时间格式的字符串格式化为Date对象
System.out.println(sdf.format(new Date()));
}
}
import java.text.*;
import java.util.*;
public class test{
public static void main(String[] args){
String strDate="2021-03-02 17:26:11.234"; //定义日期和时间的字符串
String pat="yyyy-MM-dd HH:mm:ss.sss"; //定义日期/时间格式模板
//创建一个SimpleDateFormat对象
SimpleDateFormat sdf=new SimpleDateFormat(pat);
//按SimpleDateFormat对象的日期/时间格式模板将字符串格式化为Date对象
Date d=sdf.parse(strDate);
System.out.println(d);
}
}
6.7 数字格式化类
Java提供了NumberFormat类,定义在java.text包中。NumberFormat类可以格式化和解析任何区域设置的数字,使数字的格式符合人们的阅读习惯。
NumberFormat类是一个抽象类,不能被直接实例化,但是它提供了一系列用于获取NumberFormat类实例的静态方法,并能调用其他相应的方法进行操作。
import java.text.NumberFormat;
import java.util.Locale;
public class test{
public static void main(String[] args){
double price=18.01;
int number=1000010000;
//按照当前默认语言环境的货币格式显示
NumberFormat of=NumberFormat.getCurrencyInstance();
System.out.println("按照当前默认语言环境的货币格式显示:"+nf.format(price));
//按照指定的语言环境的货币格式显示
nf=NumberFormat.getCurrencyInstance(Locale.US);
System.out.println("按照当前指定的语言环境的货币格式显示:"+nf.format(price));
//按照当前默认语言环境的数字格式显示
NumberFormat nf2=NumberFormat.getInstance();
System.out.println("按照当前默认语言环境的数字格式显示:"+nf2.format(number));
//按照指定的语言环境的数字格式显示
nf2=NumberFormat.getInstance(Locale.US);
System.out.println("按照当前指定的语言环境的数字格式显示:"+nf2.format(number));
}
}
6.8 包装类
Java设计提倡了一种思想,即万物皆对象。这样就出现了一个矛盾。因为Java中的数据类型分为基本数据类型和引用数据类型。很多类的方法都需要接收引用数据类型的对象,此时就无法将一个基本数据类型的值传入。为了解决这样的问题,就需要对基本数据类型的值进行包装,即将基本数据类型的值包装为引用数据类型的对象。能够将基本数据类型的值包装为引用数据类型的对象的类称为包装类。
除了Character和Boolean是Object类的直接子类外,Integer、Byte、Float、Double、Short、Long都属于Number类的子类。Number类是一个抽象类,它提供了一系列返回以上6种基本数据类型的方法,Number类的方法主要是将数字包装类中的内容变为基本数据类型的值。
将一个基本数据类型转换为包装类的过程称为装箱操作,反之,将一个包装类转换为基本数据类型的过程称为拆箱操作。
public class test{
public static void main(String[] args){
int a=20; //声明一个基本数据类型
Integer in=new Integer(a); //装箱
System.out.println(in);
int temp=in.intValue(); //拆箱
System.out.println(temp);
}
}
intValue()方法可以将Integer类型的值转换为int类型,这个方法可以用来进行手动拆箱操作。parseInt(String s)方法可以将一个字符串形式的数值转换成int类型,valueOf(int i)可以返回指定的int类型的值作为Integer实例。
public class test{
public sattic vodi main(String[] args){
Integer num=new Integer(20); //手动装箱
int sum=num.intValue()+10; //手动拆箱
System.out.println("将Integer类型的值转换为int类型后与10求和:"+sum);
System.out.println("返回表示10的Integer实例:"+Integer.valueOf(10));
int w=Integer.parseInt("20")+32;
System.out.println("将字符串转换为整数:"+w);
}
}
注意:
1)包装类都重写了Object类中的toString()方法,以字符串的形式返回被包装的基本数据类型的值。
2)除了Character外,包装类都有valueOf(String s)方法,可以根据String类型的参数s创建包装类对象,但参数s不能为null,而且该字符串必须是可以解析为相应基本类型的数据,否则可以通过编译,但运行时会报错。
Integer i=Integer.valueOf("123"); //合法
Integer i=Integer.valueOf("12a"); //不合法
3)除了Character外,包装类都有parseXxx(String s)静态方法,该方法的作用是将字符串转换为对应的基本数据类型的数据。参数s不能为null,而且该字符串必须可以解析为相应基本数据类型的数据,否则虽然可以通过编译,但运行时会报错。
int i=Integer.parseInt("123"); //合法
Integer in=Integer.parseInt("itcast"); //不合法
6.9 正则表达式
在实际开发中,经常需要对用户输入的信息进行格式校验。为此Java提供了正则表达式,通过正则表达式可以快速校验信息格式。
1.正则表达式语法
正则表达式是由普通字符(如字符a~z)和特殊字符(元字符)组成的文本模式。例如,正则表达式 "[a-z]*" 描述了所有仅包含小写字母的字符串,其中a、z为普通字符,连字符、左右中括号及星号则为元字符。
正则表达式中的元字符包括以下几类:
1.点号
点号(.)可以匹配除"\n"之外的任何单个字符。例如,正则表达式"t.n",可匹配"tan"、"ten"、"tcn"、"t=n"、"t n"(t和n之间有一个空格)等。
2.中括号
可在中括号内指定需匹配的若干字符,表示仅使用这些字符参与匹配。例如 "t[abcd]n" 只匹配 "tan"、"tbn"、"tcn"、"tdn"。
另外,用于匹配某一范围内的字符。例:"[a-z]" 匹配一个小写字母,"[a-zA-Z]"匹配一个字母,"[0-9]"匹配一个数字字符,"[a-z0-9]" 匹配一个小写字母或一个数字字符。
3.竖线
竖线可以匹配其左侧或右侧的符号。例如,正则表达式 "t(a|e|i|io)n" 除了 "tan"、"ten"、"tin"外,还可以匹配 "tion" ,使用竖线时,必须使用小括号将其括起来,小括号用来标记正则表达式中的组(group)。
4.^符号
^符号可以匹配一行的开始。例如,正则表达式 "^Spring.*" 匹配 "Spring MVC" 而不匹配 "a Spring MVC" 。
若 ^ 在中括号内,则表示不需要参与匹配的字符。例如,正则表达式 "[a-z&&[^bc]]" 表示匹配除b和c之外的小写字母,等价于 "[a-go-z]",正则 "[^b][a-z]+" 表示首个字符不能是b且后跟至少一个小写字母。
5.美元符号
($) 可以匹配一行的结束,例如,正则表达式 ".*App$" 中 $ 表示匹配以App结尾的字符串中,可以匹配 "Android App" 而不匹配 "iOS Apps" 和 "App." 。
6.反斜线
(\)表示其后的字符是普通字符而非元字符。例如,正则表达式 "\$" 用来匹配$字符而非结束,"\." 用来匹配 "."字符而非任一字符。
7.匹配次数元字符
用来确定其左侧符合的出现次数。
8.其他常用符号
2.Pattern类和Matcher类
Java正则表达式通过java.util.regex包下的Pattern类与Matcher类实现。
1.Pattern类
Pattern类用于创建一个正则表达式,也可以说创建一个匹配模式。Pattern类的构造方法是私有的,不可以之间创建正则表达式,为此Pattern类提供了一个静态的compile()方法可以创建一个正则表达式:
Pattern p=Pattern.compile("\\w+");
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class test{
public static void main(String[] args){
Pattern p1=Pattern.compile("a*b"); //根据参数指定的正则表达式创建模式
Matcher m1=p1.matcher("aaaaab"); //获取目标字符串的匹配器
Matcher m2=p1.matcher("aaabbb"); //获取目标字符串的匹配器
System.out.println(m1.matches()); //执行匹配器
System.out.println(m2.matches()); //执行匹配器
Pattern p2=Pattern.compile("[/]+");
String[] str=p2.split("张三//李四//王五//赵六/钱七"); //按模式分割字符串
for(String s:str){
System.out.println(s+"\t");
}
}
}
2.Matcher类
Matcher类用于验证Pattern类定义的模式与字符串是否匹配。因此Matcher实例也称为匹配器。Matcher类的构造方法也是私有的,不能直接创建Matcher实例,只能通过Pattern.matcher()方法获取该类的实例,多个Matcher对象可以使用同一个Pattern对象。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class test{
public static void main(String[] args){
//使用Pattern.compile("\\d+")创建一个模式对象p。这个正则表达式\\d+表示匹配一个或多个
//数字字符。注意,在Java字符串中,反斜杠\是一个转义字符,所以要用\\来表示一个实际的
//反斜杠。
Pattern p=Pattern.compile("\\d+");
Matcher m=p.matcher("22bb23");
//使用m.matches()检查整个字符串是否完全匹配模式p。
System.out.println("字符串是否匹配:"+m.matches());
Matcher m2=p.matcher("2223");
System.out.println("字符串2223与模式p是否匹配:"+m2.matches());
//使用m.lookingAt()检查字符串"22bb23"的开头是否匹配模式p。因为开头是数字,所以返回
//true。
System.out.println("字符串22bb23与模式p匹配结果:"+m.lookingAt());
Matcher m3=p.matcher("aa2223");
System.out.println("字符串aa2223与模式p的匹配结果:"+m3.lookingAt());
//使用m.find()查找字符串"22bb23"中是否存在与模式p匹配的子序列。因为字符串中有数字,所
//以返回true。
System.out.println("字符串22bb23与模式p是否存在下一个匹配结果:"+m.find());
m3.find(); //返回true
System.out.println("字符串aa2223与模式p是否存在下一个匹配结果:"+m3.find());
Matcher m4=p.matcher("aabb");
System.out.println("字符串aabb与模式p是否存在下一个匹配结果:"+m4.find());
Matcher m1=p.matcher("aaa2223bb");
m1.find();
System.out.println("模式p与字符串aaa2223bb第一次匹配的索引:"+m1.start());
// 输出:模式p与字符串aaa2223bb第一次匹配的索引:3
// 因为 "2223" 是从索引 3 开始的(索引从 0 开始计数)
System.out.println("模式p与字符串aaa2223bb最后一次匹配的索引:"+m1.end());
// 输出:模式p与字符串aaa2223bb最后一次匹配的索引:7
// 因为 "2223" 的最后一个字符位于索引 6,所以 end() 返回 7(即匹配项之后的索引)
System.out.println("模式p与字符串aaa2223bb匹配的子字符串:"+m1.group());
// 输出:模式p与字符串aaa2223bb匹配的子字符串:2223
// 这显示了实际匹配的子字符串
Pattern p2=Pattern.compile("[/]+");
System.out.println("将字符串张三/李四//王五///赵六中的/全部替换为|:"
+m5.replaceAll("|"));
System.out.println("将字符串张三/李四//王五///赵六中的首个/替换为|:"
+m5.replaceFirst("|"));
}
}
3.String类对正则表达式的支持
String类提供了3个支持正则表达式的方法。
public class test{
public static void main(String[] args){
String str="A1B22DDS34DSJ9D".replaceAll("\\d+","_");
System.out.println("字符串替换后为:"+str);
boolean te="321123as1".matches("\\d+");
System.out.println("字符串是否匹配:"+te);
String[] s="SDS45d4DD4dDS88D".split("\\d+");
System.out.println("字符串拆分后为:");
for(int i=0;i<s.length;i++){
System.out.println(s[i]+" ");
}
}
}
需要注意的是,String类的matches(String regex)方法的调用与Pattern类和Matcher类中的同名方法一样,必须匹配所有字符才返回true,否则返回false。
课后题
1.编写程序统计一个字符子串在一个字符串中出现的次数和位置。如子字符串“nba”在字符串”asfasfnabaasdfnbasdnbasnbasdnbadfasdf”中出现的次数和出现的位置。
class b1{
public static void main(String[] args){
String str="asfasfnabaasdfnbasdnbasnbasdnbadfasdf";
String key="nba";
int count= getKeyStringCount(str,key);
System.out.print("count="+count);
}
public static int getKeyStringCount(String str,String key){
int count = 0,a=0;
if(!str.contains(key)){
return count;
}
int index = 0;
while((index=str.indexOf(key))!=-1){
str=str.substring(index+key.length());
a=a+index;
System.out.print(" "+(a+1));
a=a+3;
count++;
}
return count;
}
}
2.对字符串“23 10 -8 0 3 7 108”中的数值进行升序排序,生成一个数值有序的字符串 “-8 0 3 7 10 23 208”。
import java.util.Arrays;
class b2{
public static void main(String[] args){
String str1="23 10 -8 0 3 7 108";
int c=0;
String[] strA=str1.split(" ");
/*int[] nums=new int[strA.length];
for (int i = 0; i < strA.length; i++) {
nums[i] = Integer.parseInt(strA[i]);
}*/
int[] nums = Arrays.stream(strA).mapToInt(Integer::parseInt).toArray();
Arrays.sort(nums);
/*while(c<strA.length){
System.out.print(nums[c]+" ");
c++;
}*/
System.out.print(Arrays.toString(nums));
}
}
3.编写一个简单的程序,完成如图下图所示的功能
import java.util.Scanner;
public class b3 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Runtime rt = Runtime.getRuntime();
System.out.println(" A:Notepad");
System.out.println(" B:Restart");
System.out.println(" C:Start qq");
System.out.println(" D:System Stats");
System.out.println(" G:Exit");
while (true) {
System.out.println("please input D:");
String command = scanner.nextLine();
switch (command) {
case "A":
try {
rt.exec("notepad.exe");
} catch (Exception e) {
e.printStackTrace();
}
break;
case "B":
try {
rt.exec("shutdown /r /t 0");
} catch (Exception e) {
e.printStackTrace();
}
break;
case "C":
try {
rt.exec("D:\\qq\\Bin\\QQScLauncher.exe");
} catch (Exception e) {
e.printStackTrace();
}
break;
case "D":
try {
String javaVersion = System.getProperty("java.version");
System.out.println("Java version: " + javaVersion);
} catch (Exception e) {
e.printStackTrace();
}
break;
case "G":
try {
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
}
break;
default:
System.out.println("no");
break;
}
}
}
}
八、集合
数组可以存储多个对象,但是数组只能存储相同类型的对象,如果要存储一批不同类型的对象,数组便无法满足需求了。为此,Java提供了集合,集合可以存储不同类型的对象。
7.1 集合概述
为了存储不同类型的多个对象,Java提供了一系列特殊的类,这些类可以存储任意类型的对象,并且存储的长度可变,这些类统称为集合。集合可以简单地理解为一个长度可变,可以存储不同数据类型的动态数组。集合都位于java.util包中,使用时需导入。
虚线框里的是接口类型,实线框里的是具体的实现类。
7.2 Collection接口
该接口是Java单列集合中的根接口,它定义了各种具体单列集合的共性,其他单列集合大多直接或间接继承该接口。
//Collection接口定义:
public interface Collection<E> extends Iterable<E>{
//Query Operations
}
//Collection是Iterable的子接口,Collection和Iterable后面的<E>表示它们都使用了泛型
在开发中往往很少直接使用Collection接口,基本上都是使用其子接口,Collection接口的子接口主要有List、Set、Queue和SortedSet。
7.3 List接口
1.List接口简介
List接口继承自Collection接口,List接口实例中允许存储重复的元素,所有元素以线性方式存储。在程序中可以通过索引访问List接口实例中存储的元素。另外,List接口实例中存储的元素是有序的,即元素的存入顺序与取出顺序一致。
2.ArrayList
ArrayList内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数值来存储这些元素,因此可以将ArrayList看作一个长度可变的数组。
//ArrayList的大部分方法是从父类Collection和List继承的,其中add()方法和get()方法分别用于
//实现元素的存入和取出。
import java.util.*;
public class test{
public static void main(String[] args){
ArrayList list=new ArrayList();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
//获取集合中的元素个数
System.out.println("集合的长度:"+list.size());
//取出并打印指定位置的元素
System.out.println("第二个元素是:"+list.get(1));
//删除索引为3的元素
list.remove(3);
System.out.println("删除索引为3的元素:"+list);
//替换索引为1的元素为李四2
list.set(1,"李四2");
System.out.println("替换索引为1的元素为李四2:"+list);
}
}
//索引为1的元素是集合中的第2个元素,这说明集合和数组一样,索引的取值范围是从0开始的,最后一个索引
//是集合大小减1.在访问元素时一定要注意索引不可超出此范围,否则程序会抛出索引越界异常。
由于ArrayList的底层是使用一个数组存储元素,在增加或删除指定位置的元素时,会创建新的数组,效率较低,因此,该集合不适合做大量的增删操作。而适合元素查找。
3.LinkedList
ArrayList在查询元素时速度很快,但在增删操作时效率较低。为了克服这种局限性,可以使用List接口的另一个实现类——LinkedList。
LinkedList内部维护了一个双向循环链表,链表中的每一个元素都使用引用的方式记录它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来。当插入元素时,只需要修改元素之间的引用关系即可:删除一个节点也是如此。
实线箭头表示建立新的引用关系,虚线箭头表示删除引用关系。
import java.util.*;
public class test{
public static void main(String[] args){
LinkedList link=new LinkedList(); //创建集合
link.add("张三");
link.add("李四");
link.add("王五");
link.add("赵六");
System.out.println(link.toString()); //获取并打印集合中的元素
link.add(3,"Student"); //向集合中的索引为3处插入元素Student
link.addFirst("First"); //向集合的第一个位置插入元素First
System.out.println(link);
System.out.println(link.getFirst()); //取出集合中第一个元素
link.remove(3); //移除集合中索引为3的元素
link.removeFirst(); //移除集合中第一个元素
System.out.println(link);
}
}
7.4 集合遍历
针对这种需求,JDK提供了Iterator接口和foreach循环。
1. Iterator接口
Collection接口和Map接口主要用于存储元素,而Iterator接口主要用于迭代访问(遍历)集合中的元素,通常情况下Iterator对象也称为迭代器。
import java.util.*;
public class test{
public static void main(String[] args){
ArrayList list=new ArrayList(); //创建集合
list.add("张三"); //向集合中添加字符串
list.add("李四");
list.add("王五");
list.add("赵六");
Iterator it=list.iterator(); //获取Iterator对象
while(it.hasNext()){ //判断集合是否存在下一个元素
Object obj=it.next(); //取出集合中的元素
System.out.println(obj);
}
}
}
Iterator对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。
在调用Iterator的next()方法之前,Iterator的指针位于第一个元素之前,不指向任何元素;在第一次调用Iterator的next()方法时,Iterator的指针会向后移动一位,指向第一个元素并将该元素返回;当第二次调用next()方法时,Iterator的指针会指向第二个元素并将该元素返回;以此类推,直到hasNext()方法返回false,表示已经遍历集合中所有的元素,终止对元素的遍历。
需要注意的时,通过Iterator获取集合中的元素时,这些元素类型都是Object类型。如果想获取特定类型的元素,则需要对数据进行强制转换。
脚下留下:并发修改异常
在使用Iterator对集合的元素进行遍历时,如果调用了集合对象的remove()方法删除元素,然后继续使用Iterator遍历元素,会出现异常。
//错误代码
import java.util.*;
public class test{
public static void main(String[] args){
ArrayList list=new ArrayList(); //创建集合
list.add("张三"); //向集合中添加字符串
list.add("李四");
list.add("王五");
list.add("赵六");
Iterator it=list.iterator(); //获取Iterator对象
while(it.hasNext()){ //判断集合是否存在下一个元素
Object obj=it.next(); //获取集合中的元素
if("张三".equals(obj)){
list.remove(obj); //删除张三
}
}
System.out.println(list);
}
}
//抛出了并发异常(ConcurrentModificationException)
//这个异常是Iterator对象抛出的,出现异常的原因是集合在Iterator运行期间删除了元素,会导致Iterator
//预期的迭代次数发生改变,Iterator的迭代结果不准确。
解决上述问题,可采用以下两种方法:
1)只需找到该学生后跳出循环即可,不再迭代即可
if("张三".equals(obj)){
list.remove(obj); //删除张三
break;
}
在使用break语句跳出循环以后,由于不再使用Iterator对集合中的元素进行遍历,所以在集合中删除元素对程序没有任何影响,就不会再出现异常。
2)需要在集合的迭代期间对集合中的元素进行删除,可以使用Iterator本身的删除方法
if("张三".equals(obj)){
it.remove();
}
调用Iterator对象的remove()方法删除元素所导致的迭代次数变化对于Iterator对象本身来讲是可预知的。
2. foreach循环
虽然Iterator可以用来遍历集合中的元素,但在写法上比较繁琐。为了简化书写,从JDK5开始,JDK提供了foreach循环,它是一种更加简洁的for循环,主要用于遍历数组或集合中的元素。
for(容器中元素类型 临时变量:容器变量){
执行语句
}
与for循环相比,foreach循环不需要获取集合的长度,也不需要根据索引访问集合中的元素,就能够自动遍历集合中的元素。
import java.util.*;
public class test{
public static void main(String[] args){
ArrayList list=new ArrayList(); //创建集合
list.add("张三"); //向集合中添加字符串
list.add("李四");
list.add("王五");
for(Object obj:list){ //使用foreach循环遍历集合
System.out.println(obj); //取出并打印集合中的元素
}
}
}
foreach循环在遍历集合时语法非常简洁,没有循环条件,也没有迭代语句,所有这些工作都交给Java虚拟机执行了。foreach循环的次数由集合中元素的个数决定的,每次循环时,foreach都通过临时变量将当前循环的元素记住,从而将集合中的元素分别打印出来。
脚下留下:foreach循环缺陷
foreach循环虽然书写起来很简洁,但在使用时也存在一定的局限性。当使用foreach循环遍历集合和数组时,只能访问其中的元素,不能对其中元素进行修改。
public class test{
static String[] strs={"aaa","bbb","ccc"};
public static void main(String[] args){
//foreach循环遍历数组
for(String str:strs){
str="ddd";
}
System.out.println("foreach循环修改后的数组:"+strs[0]+","+strs[1]+","+strs[2]);
//for循环遍历数组
for(int i=0;i<strs.length;i++){
strs[i]="ddd";
}
System.out.println("普通for循环修改后的数组:"+strs[0]+","+strs[1]+","+strs[2]);
}
}
//其原因是 str="ddd"只是将临时变量str赋值为一个新的字符串,这和数组中的元素没有任何关系
//而在普通for循环中,可以通过索引的方式引用数组中的元素并修改其值。
7.5 Set接口
1.Set接口简介
Set接口也继承自Collection接口,它的方法与Collection接口的方法基本一致,并没有对Collection接口进行功能上的补充。与List接口不同的是,Set接口中的元素是无序的,并且都会以某种规则保证存入元素不出现重复。
Set接口常见的实现类有3个,分别是HashSet、LinkedHashSet、TreeSet。
其中HashSet根据对象的哈希值确定元素在集合中的存储位置,具有良好的存取和查找性能;LinkedHashSet是链表和哈希表组合的一个数据存储结构;TreeSet则以二叉树的方式存储元素,它可以对集合中的元素进行排序。
2.HashSet
HashSet是Set接口的一个实现类,它存储的元素是不可重复的。当向HashSet中添加一个元素时,首先会调用hashCode()方法确定元素的存储位置,然后再调用equals()方法确保该位置没有重复元素。Set接口与List接口存取元素的方式是一样的,但是Set集合中的元素是无序的。
import java.util.*;
public class test{
public static void main(String[] args){
HashSet hset=new HashSet(); //创建集合
hset.add("张三"); //向集合中添加字符串
hset.add("李四");
hset.add("王五");
hset.add("李四"); //向集合中添加重复元素
Iterator it=hset.iterator(); //获取Iterator对象
while(it.hasNext()){ //通过while循环判断集合中是否有元素
Object obj=it.next(); //调用Iterator的next()方法获取元素
System.out.println(obj);
}
}
}
HashSet之所以能确保不出现重复的元素,是因为它存入元素时做了很多工作。当调用HashSet的add()方法存入元素时,首先调用hashCode()方法获得该元素的哈希值,然后根据哈希值计算存储位置。如果该位置上没有元素,则直接将元素存入;如果该位置上有元素,则调用equals()方法将要存入的元素和该位置上的元素进行比较,根据返回结果确定是否存入元素。如果返回结果为false,就将该元素存入集合;如果返回结果为true则说明有重复元素,就将要存入的重复元素舍去。
根据前面的分析不难看出,当向集合中存入元素时,为了保证集合正常工作,要求在存入元素时重写Object类中的hashCode()和equals()方法。
import java.util.*;
class Student{
String id;
String name;
public Student(String id,String name){ //创建构造方法
this.id=id;
this.name=name;
}
public String toString(){ //重写toString()方法
return id+":"+name;
}
}
public class test{
public static void main(String[] args){
HashSet hs=new HashSet(); //创建集合
Student stu1=new Student("1","张三"); //创建Student对象
Student stu1=new Student("2","李四");
Student stu1=new Student("3","王五");
hs.add(stu1);
hs.add(stu2);
hs.add(stu3);
System.out.println(hs);
}
}
//运行结果出现两个相同的信息,之所以没有去掉这样的重复元素,是因为在定义Student类时
//没有重写hashCode()和equals()方法
重写后:
import java.util.*;
class Student{
private String id;
private String name;
public Student(String id,String name){ //创建构造方法
this.id=id;
this.name=name;
}
//重写toString()方法
public String toString(){
return id+":"+name;
}
//重写hashCode()方法
public int hashCode(){
return id.hashCode(); //返回id属性的哈希值
}
//重写equals()方法
public boolean equals(Object obj){
if(this==obj){ //判断是否同一个对象
return true; //如果是返回true
}
if(!(obj instanceof Student)){ //判断对象是否为Student类型
return false;
}
Student stu=(Student)obj; //将对象转换为Student类型
boolean b=this.id.equals(stu.id); //判断id值是否相同
return b; //返回判断结果
}
}
public class test{
public static void main(String[] args){
HashSet hs=new HashSet(); //创建集合
Student stu1=new Student("1","张三"); //创建Student对象
Student stu1=new Student("2","李四");
Student stu1=new Student("3","王五");
hs.add(stu1);
hs.add(stu2);
hs.add(stu3);
System.out.println(hs);
}
}
//去除成功
3. LinkedHashSet
HashSet存储的元素是无序的,如果想让元素的存取顺序一致,可以使用Java提供的LinkedHashSet类,LinkedHashSet类是HashSet的子类,与LinkedList一样,它也使用双向链表来维护内部元素的关系。
import java.util.Iterator;
import java.util.LinkedHashSet;
public class test{
public static void main(String[] args){
LinkedHashSet set=new LinkedHashSet();
set.add("张三"); //向集合中添加字符串
set.add("李四");
set.add("王五");
Iterator it=set.iterator(); //获取Iterator对象
while(it.hasNext()){ //通过while循环判断集合中是否有元素
Object obj=it.next();
System.out.println(obj);
}
}
}
//元素的遍历顺序和存入的顺序是一样的
4. TreeSet
它内部采用二叉树存储元素,这样的结构可以保证集合中没有重复的元素,并且可以对元素进行排序。所谓二叉树就是每个节点最多有两个子节点的有序树。
一个节点及其子节点组成的树称为子树,通常左侧的子树称为左子树,右侧的子树称为右子树,其中左子树的元素都小于它的根节点,而右子树上的元素都大于根节点。
在向集合中依次存入元素时,首先将第一个元素放在二叉树的最顶端。随后存入的元素与第一个元素进行比较。如果小于第一个元素,就将该元素放在左子树上;如果大于第一个元素就将该元素放在右子树上,以此类推。
import java.util.TreeSet;
public class test{
public static void main(String[] args){
//创建集合
TreeSet ts=new TreeSet();
//向集合中添加元素
ts.add(3);
ts.add(29);
ts.add(101);
ts.add(21);
System.out.println("创建的TreeSet集合为:"+ts);
//获取首尾元素
System.out.println("TreeSet集合的首元素为:"+ts.first());
System.out.println("TreeSet集合尾部元素为:"+ts.last());
//比较并获取元素
System.out.println("集合中小于或大于9的最大的元素为:"+ts.floor(9));
System.out.println("集合中大于10的最小元素为:"+ts.higher(10));
System.out.println("集合中大于100的最小元素为:"+ts.higher(100));
//删除元素
Object first=ts.pollFirst();
System.out.println("删除的第一个元素为:"+first);
System.out.println("删除第一个元素后TreeSet集合变为:"+ts);
}
}
//向TreeSet集合添加元素时,不论元素的添加顺序如何,这些元素都能够按照一定的顺序排列。其原因是:
//每次向TreeSet集合中存入一个元素时,就会将该元素与其他元素进行比较,最后将它插入有序的对象序列中。
集合中的元素在进行比较时都会调用compareTo()方法,该方法是在Comparable接口中定义的,因此要想对集合中的元素进行排序,就必须实现Comparable接口。Java中大部分的类实现了Comparable接口,并默认实现了该接口中的CompareTo()方法,如Integer、Double、String等。
在实际开发中,除了会向TreeSet集合中存储一些Java中默认类型的数据外,还会存储一些用户自定义的类型的数据,如Student类型的数据、Teacher类型的数据等。由于这些自定义类型的数据没有实现Comparable接口,因此也就无法直接在TreeSet集合中进行排序操作。为此,Java提供了两种TreeSet集合的排序规则,分别为自然排序和自定义排序。默认情况下,TreeSet都采用自然排序。
1)自然排序
自然排序要求向TreeSet集合中存储的元素所在类必须实现Comparable接口,并重写compareTo()方法,然后TreeSet集合就会对该类型的元素使用compareTo()方法进行比较。compareTo()方法将当前对象与指定对象按顺序进行比较,返回值为一个整数,其中,返回负整数、零或正整数分别代表当前对象小于、等于或大于指定对象,默认根据比较结果顺序排列。
import java.util.TreeSet;
class Student implements Comparable{
private String name;
private int age;
public Student(String name,int age){
this.name=name;
this.age=age;
}
//重写toString()方法
public String toString(){
return name+":"+age;
}
//重写Comparable接口的comapreTo()方法
public int compareTo(object obj){
Student stu=(Student)obj;
//定义比较方式,先比较age,再比较name
if(this.age-stu.age>0){
return 1;
}
if(this.age-stu.age==0){
return this.name.compareTo(stu.name);
}
return -1;
}
}
public class test{
public static void main(String[] args){
TreeSet ts=new TreeSet();
ts.add(new Student("Lucy",18));
ts.add(new Student("Tom",20));
ts.add(new Student("Bob",20));
ts.add(new Student("Tom",20));
System.out.println(ts);
}
}
//
欢迎交流~