JAVA
一、基础
1.java运行编译与平台无关:
平台包括:OS和CPU;
原因:每个平台有自己机器码识别规则,其他语言的程序源文件是直接编译成机器码进行运行,跨平台无法运行
原理:JAVA只需要在平台上安装运行环境jre,jre中包含:JVM(JAVA虚拟机)、类库以及一些核心文件组成,javac.exe将源程序编译成字节码,JVM将字节码翻译成虚拟机所识别的机器码(java.exe),由于有字节码这个中间过程,所以java还可以通过javap.exe进行反编译:将字节码反编译成源文件
2.java开发环境JDK和IDE:
(1)JDK:是java自带的软件开发工具箱
(2)IDE:以JDK为系统核心,便于更好、更快开发程序,界面友好
3.配置环境变量:
(1)设置环境变量JAVA_HOME:
值为JDK目录结构的根目录,即:JDK的安装目录 e.g.C:\jdk-14.0.1
(2)设置系统变量Path:
原因:javac.exe和java.exe位于JDK根目录下bin文件中,为了能在任何目录中使用编译器和解释器
值为%JAVA_HOME%\bin(引用JAVA_HOME的地址有助于后期维护,若jdk改变,只需要更改JAVA_HOME的值即可)
二、基本数据类型/数组
1.标识符:
(1)由字母、下划线、数字、美元符——长度不限
(2)首字符不能是数字
(3)不能是关键字和bool值(关键字无大写,所以见到大写不是关键字)
2.基本数据类型:
(1)逻辑类型:boolean;
值:true、false;
(2)整数类型:byte、short、int、long;
int型常量表示:077(八进制以0开头)、0x3ABC(十六进制以0x开头)、0b111(二进制以0b开头)
(3)字符类型:char;
转义字符:\n、\b、\t、\单引号、\双引号......
(4)浮点类型:float、double;
float型常量表示:453.5439F(小数表示法以F/f结尾)、2e40f(指数表示法:2乘10的40次方)
double型常量表示:453.5439D
3.类型转换运算:
(1)精度从小—>大:
byte(8bit)、short(2byte=16bit)、char(2byte=16bit)、int(4byte=32bit)、long(8byte=64bit)、float(4byte=32bit)、double(8byte=64bit)
(2)类型转换:
系统自动转换:精度从小—>大
强制类型转换:精度从大—>小
类型转换运算:类型转换过程中,超出转换类型的取值范围:-2^bit-1~(2^bit-1)-1 e.g.byte的取值范围:-2^7~2^7-1=-128~127
(3)形式:
(类型名)要转换的值;
e.g.int x = (int)34.89、long y = (long)56.89F
4.输入与输出数据:
4.1输入基本型数据:
使用Scanner类创建一个对象:
Scanner reader = new Scanner(System.in);
该对象名是reader,reader可以调用nextBoolean、nextByte、nextShort、nextInt、nextLong、nextFloat、nextDouble
e.g.
Scanner reader = new Scanner(System.in);
int x = reader.nextInt();
4.2输出基本型数据:
System.out.println();//输出后换行
System.out.print();//输出后不换行
ps:输出字符串常量时不可以在内容里回车,若字符串过长,可以拆分成几个部分,通过并置符号+首尾连接!
对象out可以调用printf()方法,具体格式:
System.out.printf("格式控制部分",表达式1,表达式2...)
格式控制部分:
%d输出int类型、%c输出char类型、%f输出浮点型数据,小数部分最多保留6位、%s输出字符串数据
%md输出的int型数据占m列、%m.nf输出的浮点型数据占m列小数部分保留n位
5.数组:
(1)声明数组:
一维数组:
数组的元素类型 数组名[ ];√常用 e.g.
int boy[];
数组的元素类型 [ ]数组名;
二维数组:
数组的元素类型 数组名[ ] [ ];√常用
数组的元素类型 [ ] [ ]数组名;
ps:不允许在声明数组中的方括号内定义数组元素个数!!!
(2)为数组分配元素
声明数组仅给出了数组变量的名字和元素的数据类型,要使用数组还需要创建数组。
为数组分配元素形式:
数组名 = new 数组元素类型[元素个数] e.g.
boy = new int[4]; //数组boy获得4个int类型的元素,变量boy中存放这些变量的首地址
(3)初始化数组:
为分配好空间的数组赋初始值 e.g.
boy[0]=1;boy[1]=2....
ps:
声明和创建数组可以同时进行:
int boy[] = new int[4];
声明数组的同时可以初始化数组(跳过创建):
int boy[] = {1,2,3,4};
相当于先声明和创建数组,再初始化
(4)数组的引用:
数组的引用是首元素的地址值,若两数组的引用完全相同,则两数组的内容元素完全相同。
通过
System.out.println(数组名);
可以输出带前缀信息I@的引用地址(元素的首地址)
ps:char型数组不会输出引用地址,会直接输出数组内容!!!
三、运算符、表达式和语句
1.运算符与表达式:
(1)算数运算符和算数表达式:略
(2)自增、自减运算符:
++x或--x:先自+1或自-1再调用x;
x++或x--:先调用x再自+1或自-1;
(3)算数混合运算的精度:按运算中最高精度的类型运算,最低精度为int类型(int精度一下的类型均以int精度运算)!!!
(4)关系运算符与关系表达式:略
(5)逻辑运算符与逻辑表达式:
&&:均为ture,结果才为ture;
||:只要有一个为ture,结果就为ture;
!:取反;
ps:&&和||运算符也称短路运算符:当op1为false,&&运算op2恒为false;当op1为ture,||运算op2恒为ture;
(6)赋值运算符与赋值表达式:注意区分=为赋值,==为等于;
(7)位运算符:
&:按位与运算 |:按位或运算 ~:按位非运算
^:按位异或:对应位置相同为0,不同为1;
用途:通常用来加密信息:(a^b)^b=a;\交换两变量的数值:a=a^b;b=a^b;a=a^b;
(8)&&、||和!与&、|、~的区别:
位运算符要在计算完X,Y的值后给出运算结果。
int x = 1;
((y=1)==0)&&((x=6)==6);//x的值仍然是1;
((y=1)==0)&((x=6)==6);//x的值是6;
(9)instanceof运算符:
是java特有的运算符,形式如下:
对象名instanceof类名;若该对象是该类或该类的子类创建的,则结果为ture,否则为false;
2.语句概述:
(1)方法调用语句:略
(2)表达式语句:略
(3)复合语句:略
(4)空语句:一个分号也是一个语句
(5)控制语句:条件分支语句:if、开关语句:switch、循环语句:for/while/do-while
(6)package语句和import语句:与类和对象有关
3.if条件分支语句:
略
4.switch语句:
switch(表达式){
case 常量值1:
若干语句
break;
case 常量值1:
若干语句
break;
case 常量值2:
若干语句
break;
case 常量值3:
若干语句
break;
...
default:
若干语句
}
ps:
一定要写break;,否则符合case值的语句执行后,将执行剩下的全部语句,直到遇到break;才停止。
default可有可无。
5.循环语句:
(1)for循环语句:略
(2)while循环语句:略
(3)do-while循环语句:形式如下:
do{
若干语句
}while(表达式);
(4)while和do-while的区别:
do-while先执行do的循环体,所以do-while至少被执行一次,而while若不满足表达式,可以一次也不执行直接跳出循环
6.break和continue语句:
break;语句是直接结束循环
continue;语句是结束本次循环,进行下次循环
7.for语句遍历数组:
JDK5新增for语句遍历数组的功能:
for(声明循环变量:数组名){
...
}
/*
for(int n:a){ 将每个数组a中的数赋值给n,再将n输出;
System.out.println(a[n])
}
相当于:
for(int n=0;n<a.length;n++){
System.out.println(a[n])
}
*/
四、类
1.面向对象语言:
特点:封装性、继承性、多态性
2.构造类:
(1)类定义:
class 类名{ //类声明
类体的内容 //类体
}
(2)类体:
定义类的目的:
是抽象出一类事物共有的属性和行为,用一定语法去描述出属性和行为,所以类是一种用于创建具体实例(对象)的数据类型,相当于int、float...;抽象的关键是抓住事物的两个方面:属性(数据)、行为(对数据的操作);
因此类体的内容:变量的声明(体现对象的属性);方法的定义(体现对象所具有的行为);
3.成员变量:
(1)定义:在类体内对变量的声明,也叫域变量;
(2)成员变量的默认值:
若声明成员变量时未赋初始值,则编译器会指定,默认值—boolean:false、char:'\0'(空字符)、数组等引用型变量:null、其他为0;
(3)成员变量的有效范围:
在整个类的所有方法里均有效,与声明位置无关:可以先使用,后声明,只要在类体里有定义就行
ps:若定义的变量值与其他成员变量有关,e,g,int a = b+1;则b要先声明!
3.1.实例变量和类变量:
成员变量包括:实例变量、类变量
(1)类变量:用关键字static修饰的(静态变量)
(2)实例变量:成员变量中剩下的变量
//成员变量:
class Dog{
float x; //实例变量
static int y; //类变量或静态变量
}
3.2.实例变量与类变量的区别:
(1)不同对象的实例变量互不相同;
(2)所有对象共享类变量;
(3)可以通过类名直接访问类变量;
ps:正常情况下,调用类中的变量需要先声明一个对象,通过对象名.变量进行赋值等操作;若声明为类变量,可以直接用类名.变量进行赋值等操作。e.g.所有对象通用的数据可以设置为类变量:民族,国籍...
4.方法:
(1)格式:
方法头{
方法体的内容
}
(2)方法头:
int speak(){
return 23; //无参数的方法头
}
int add(int x,int y,int z){
return x+y+z; //有参数的方法头
}
(3)方法体:
局部变量:在方法体内声明的变量,只在该方法体中有效,而且与声明的位置有关,需要先声明后使用!!!
方法的参数在真个方法体内有效,局部变量从声明后有效。
方法重载:
满足条件:参数个数或参数类型不同(满足其一即可);
5.static关键字:
静态变量:类中有static修饰变量
实例变量:类中无static修饰的变量
对于静态变量,在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)
对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
静态方法:有static修饰的方法
可不创建对象直接对其方法进行调用,任何的实例也都可以调用——形式:类名.方法名
ps:(1)构造方法不可用static修饰,即构造方法无静态方法;
(2)static修饰要放在方法类型的前面;
(3)静态方法不能使用this和super关键字,不能直接访问所属类的实例变量和实例方法(实例变量只声明未分配空间,还不能使用),只能访问所属类的静态变量和静态方法。
(4)static不能修饰abstract方法:因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract
(5)main方法是static静态的,这样JVM在运行main方法的时候可以直接调用而不用创建实例。
static代码块:
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体
内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它
们,每个代码块只会被执行一次,利用静态代码块可以对一些static变量进行赋值
设计static方法的情况:
(1)若一个方法不需要操作实例变量或调用类中的实例方法,即可设计为static方法;
(2)例如java核心类库中提供的工具类等:Math、Data...其中的方法都是static方法(静态方法)
用Math.max(40,399)的值为:399;
ps:在JSP中相当于内置对象:person...
6.区分成员变量和局部变量:
(1)若成员变量和局部变量名相同,则成员变量被隐藏!!!,即成员变量在该方法内暂时失效;
若想使用被隐藏的成员变量,则需要使用关键字this
this.成员变量
(2)局部变量无默认值,在使用局部变量时必须先赋值!!!
7.构建类时注意的问题:
类体由变量的声明和方法的定义构成,对变量的操作只能定义在方法体中,不能在类体中进行操作
e.g.
class A{
int a;
a=12 ; //报错,"="是赋值语句,应写在方法体中
}
//修改:
class A{
int a =12; //可以声明的同时指定数值
}
8.类的UML图:
具体类在类图中用矩形框表示,矩形框分为三层:第一层是类名字。第二层是类的成员变量;第三层是类的方法。成员变量以及方法前的访问修饰符,用符号来表示:
- “+”表示 public;
- “-”表示 private;
- “#”表示 protected;
- 不带符号表示 default。
画图软件:Visio(微软的)、亿图图示(轻量级)
9.类与程序的基本结构:
(1)一个Java程序——一个工程;
(2)一个Java程序——若干的类构成——储存在一个或若干的源文件中;
ps:提倡在一个源文件中只编写一个类
(3)Java应用程序有一个主类(含有main方法的类),Java应用程序从主类的main()方法开始执行。
ps:需要将若干的源文件和主类的源文件放在同一个文件夹中——由Java虚拟机同时编译;
若不放在同一文件夹中,可以只执行主类 的源程序,编译器将自动地先编译所需要的其他源文件;
10.传参形式:
(1)基本数据类型参数的传值:略;
(2)引用类型数据的传值:数组、类、接口(传的是地址值);
(3)可变参数:在类中声明方法时,不给出参数列表中从某项开始直至最后一项参数的名字和个数。
ps:如果一个方法有多个参数,包含可变参数,可变参数要放在最后。
pubilc void f(int...x) //从第一个至最后一个参数都是int型
public void g(double a,int...x) //第一个参数是double型,从第二个至最后一个参数都是int型
public void a(int...x,int y) //错误使用!!!int...x可变参数x必须表示最后一个参数
(4)可变参数的使用:
public int getSum(int...x){
int sum=0;
for(int i=0;i<x.length;i++){
sum=sum+x[i]; //使用可变参数,类似数组
}
return sum;
}
11.this指针:
this关键字代表一个对象;
class student{
int a;
void getName(int a){
this.a=a;
}
}
student A = new student();
A.getName("张三");
this.变量名是调用实例变量,所以只能出现在实例方法或构造方法中;
在实例方法或构造方法里,若无this修饰的变量为局部变量:
class student{
int a;
void getSum(int a){
a=a; //非法语句,a相同,均为局部变量;
}
}
//修改:
class student{
int a;
void getSum(int a){
this.a=a; //将方法中的局部变量a赋值给类中的实例变量a;
}
}
12.package包:
package语句放在源文件的第一句;(打包方法,架包),便于以后调用不在同一文件夹内的类;
若一个类有包名,则不能随意存放,将该类编译的字节码文件必须放在包名对用的地址文件夹中;
ps:类所在的源文件可以不在该文件夹内,所以通常将源文件放在上一层目录中,这样源文件编译后,字节码文件直接存放在包名所对应的目录中;
13.import语句:
若一个类需要另一个类声明的对象作为自己的成员或方法中的局部变量,且这两个类不在同一个包内,需要用import语句引入相关的类;
(1)引用类库中的类:
用户创建的类一定不与java核心类库在同一包内,使用import语句可以引入包中的类和接口;
java核心类库提供130多个包:e.g.
(1)java.lang包含所有的基本语言类;
(2)javax.swing包含抽象窗口工具集中的图形、文本、窗口GUI类;
(3)java.io包含所有的输入/输出类;
(4)java.util包含实用类;
(5)java.sql包含操作数据库的类;
(6)java.net包含所有实现网络功能的类
形式:e.g.
import java.util.*; //通配符*引入包中的所有类
import java.util.Date; //引入包中的Data类
引用自定义包中的类:
包名路径左对齐:让源文件的包名与想引入类所在的包名,两者对应的路径的父目录相同;
若源文件没有包名,则使包名路径和源文件左对齐:想引入类所在的包与源文件在同一文件夹内;
ps:
(1)java.lang是Java语言核心类库,包含了必不可少的系统类,系统自动的为程序引入java.lang,包中包含System类、Math类等
(2)用import语句引入整个包中的类时,会增加编译时间,但不会影响程序运行的性能(只编译需要用到的类)
(3)若未用import语句,也可以直接带着包名使用该类:e.g.
java.util.Date date = new java.util.Date();
13.1Jar文件:
可以将有包名的类的字节码文件压缩成一个jar文件,供其他源文件使用import语句进行导入jar文件中的类;
步骤:
(1)将两包的.java源文件用javac进行编码,生成.class字节码文件;
(2)编写清单文件:qingdan.mf(Manifestfiles)
Manifest-Version: 1.0
Class: 目录.源文件1名 目录.源文件2名
Created-By: 14
ps:冒号后都要用空格分离;
(3)jar命令:
在两包的父目录> jar cfm Jerry.jar qingdan.mf 字节码1所在目录\.java 字节码2所在目录\.java
(4)使用jar文件中的类:
在编写的源文件开头写import语句进行导入,
14.访问权限:
定义:对象是否可以通过“.”来访问类中的方法或操作自己的变量;
private<default<protected<public(访问权限范围排序)
private修饰的变量/方法:不能直接用对象.变量/方法进行赋值等操作,需要调用方法该改变自己变量的属性;
protected修饰的变量/方法:要求必须在同一包内,并且为子类的关系可以调用访问;
public修饰的变量/方法:无论是否在同一包内,均可以调用;
default不是修饰变量/方法的:若变量/方法前无private/protected/public修饰,则为友好变量/方法(defualt);要求必须在同一包内;
ps:defaul(友好类)和protected(保护类)的区别:在子类/父类的继承关系中有体现;子类继承父类,若两者在同一包内,private(私有类)不被继承,若两者不在同一包内,private(私有类)和defaul(友好类)均不被继承。
15.基本类型的类封装:
java中的基本数据类型:boolean、byte、int、short、long、float、double、char;
java中还提供了基本数据类型的相关类:Boolean、Byte、Integer、Short、Long、Float、Double、Character(在java.long包中);
一些常用的类方法:
(1)byteValue()、intValue()、shortValue()、longValue()、floatValue()、doubleValue():返回相应基本类型的数据;
(2)Character类有:isDigit()、isLetter()(判断是否为数字/字母);
toUpperCase()、toLowerCase()、isUpperCase()、isLowerCase()(大小写判断及相互转换);
可以直接用Character.isDigit()/Character.isLetter()...进行调用,属于static静态方法;
16.对象数组:
Student [] stu = new Student[10]//仅声明了一个对象数组,其含有10个对象元素,此时每个对象还是空对象,不能直接调用
for(i=0;i<stu.length;i++)
{
stu[i]= new Student(); //用for循环对每个对象进行创建。
}
五、对象
1.构造方法:
(1)定义:是类中的特殊方法,当程序用类创建对象时,需要使用类的构造方法。
(2)系统默认构造方法:若类中没有编写构造方法,系统默认该类只有一个默认方法,该默认构造方法是无参的,且方法体中没有语句;e.g.
class A{
A(){
}
}
(3)自定义格式:方法名必须与类名一致且没有类型。允许在一个类中编写若干构造方法,但必须参数不同:参数个数不同或参数类型不同;e.g.
class A{
int x,y;
A(){
x=1; //无参数的构造方法
y=1;
}
A(int n,int i){
x=n; //有参数的构造方法
y=i;
}
}
//A类有两个构造方法,参数个数不同
2.创建对象:
(1)步骤:声明、分配变量;
(2)声明格式:
类名 对象名;
(3)分配变量格式:使用new和类体中的构造方法
对象名 = new 类名();
3.对象的内存模型:
在声明对象后,该对象的内存还没有任何东西(null),成为空对象,空对象还未得到任何“实体”,不能使用。
使用new 类名();时,系统会做两件事:
(1)为类体中的变量分配内存
(2)new运算符在为变量分配内存后计算出一个引用值(该值包含变量的地址和其他重要信息),即
new 类名();是一个值,将这个值赋值给对象。所谓创建对象,就是为对象分配变量,让对象获得一个引用值,以确保这些变量由该对象操作管理
一个对象—>通过引用值控制若干变量(该对象的属性)
创建多个对象,就是为类的每个变量多次分配不同的内存,赋值给不同的对象单独管理,互不影响;
总结:
new标识符只能和类的构造方法进行运算,运算的结果是十六进制数——这个数称作对象的引用
4.使用对象:
(1)形式:对象.变量; 对象.方法();
(2)封装性:类中的方法可以操作成员变量,当对象调用方法时,方法中出现的成员变量,就是分配给该对象的变量;
5.对象的引用和实体:
(1)避免使用空对象:(只声明对象)
类名 对象;
解决:需要用类的构造方法对空对象进行初始化:
对象名 = new 类名()
(2)引用数据类型:数组、类、接口(他们传的数据是地址值)
重要结论:一个类的两个对象如果具有相同的引用,两者就具有完全相同的变量(实体)
(数组也适应!!!)
(3)垃圾收集:这种机制周期性的检测某个变量(实体)是否已不再被任何对象所引用,若有则释放实体占用的内存;
ps:若希望Java虚拟机立刻进行“垃圾收集”操作,可以让System类调用gc()方法。
6.对象的组合:
(1)若对象a组合了对象b,对象a就可以委托对象b调用其方法——对象a以组合的方式复用对象b的方法
特点:
- 通过组合对象来复用方法——“黑盒”复用(因为不知道复用方法的结构细节)
- 当前对象随时可以更换所包含的对象——弱耦合关系
- 当前对象随时不可以更换所包含的对象——强耦合关系 e.g.圆锥对象包含圆对象(不可缺少)
六、子类与继承
1.子类与父类:
面向对象编程:OOP、面向对象建模:OOM;
面向对象基础设计原则:开闭原则:对扩展开放,对修改关闭;
(1)定义:满足子类 is a 父类;满足is-a的关系;
(2)定义子类:
class 子类名 extends 父类名{
...
}
//e.g.
class Student extends People{
...
}
(3)类的树形结构:
类按继承关系形成树形结构,将类看成树上的节点,根节点是Object类——java.lang包中的类;
Object类是所有类的祖先类,声明类时没有使用extends关键字,这个类默认是Object的子类;
2.子类的继承性:
(1)子类与父类的关系:n:1 一个子类只有一个父类,一个父类可对应多个子类;
即:不支持多重继承(在python中允许)
(2)子类和父类在同一包内继承:
继承public类、protected类、友好类
不继承private
(3)子类和父类不在同一包内继承:
继承public类、protected类
不继承private类、友好类
(4)总结protected类:
A类在自己类中创建对象,可以通过"."运算符访问自己的protected类
在其他类中,用A类创建的对象:
对于A类自己声明的protected类,只要与A类在同一包内,可以通过"."运算符访问A类的protected类;
对于A类从父类中继承的protected类,需要追溯到祖先类,需要与祖先类在同一包内才能通过"."运算符访问;
3.子类与对象:
(1)子类对象的特点:
当用子类的构造方法创建一个子类的对象时,不仅子类声明的成员变量分配空间,父类的成员变量也被分配空间,但只将一部分子类继承的成员变量分配给子类。(private/default不被分配)
(2)关于instanceof运算符
java独有的双目运算符,其左侧操作元素是对象,右边是类/接口;
当左侧的对象是右侧类的子类时,结果为true,否则为flase;
4.成员变量的隐藏和方法重写:
(1)成员变量的隐藏:
当子类从父类继承的成员变量与自己所声明的成员变量名相同时,所继承的成员变量会被隐藏
特点:
子类自己声明的方法是操作自己声明的成员变量,即使与父类重名;
子类从父类继承的方法是操作父类声明的成员变量,即使与子类重名;
ps:可以通过super关键字操作子类隐藏的成员变量;
(2)方法重写:
只要子类可以继承父类的某个方法,就有权对此方法进行重写,之后子类调用此方法,是重写后的方法;
5.super关键字:
子类一旦隐藏了继承的成员变量,那子类创建的对象便不再拥有该变量,该变量归关键字super拥有(方法同理)
ps:当用super调用被隐藏的方法时,该方法调用的是被子类隐藏的成员变量或继承的成员变量。
打印输出:先父后子——先调用父类的构造函数,再调用子类的构造函数。
ps:若子类的构造方法没有明显的指出使用父类哪个构造方法,默认使用父类无参的构造方法。
子类不继承父类的构造方法,因此,子类如果想使用父类的构造方法,必须在子类的构造方法中使用,并且必须使用关键字super来表示。
ps:super必须是子类构造方法中的头一条语句。习惯上若在父类中定义了构造方法,也要写一个无参的构造方法,否则当子类无super来调用父类的有参构造方法时,系统会报错!
6.final关键字:
final关键字可以修饰类、成员变量、局部变量。
final修饰的类不能被继承——无子类;
final修饰的方法不能被子类继承;
final修饰的变量不能修改值——是常量;
ps:常量没有默认值,因此用final修饰的变量初始化时必须给出值;
7.对象的向上转型:
(1)上转型对象不能操作子类新增的成员变量;不能调用子类新增的方法。
(2)上转型对象可以访问子类继承或隐藏的成员变量,也可以调用子类继承的方法或子类重写的实例方法。
(3)如果子类重写了父类的某个实例方法后,当用上转型对象调用这个实例方法时一定是调用了子类重写的实例方法。
上转型对象相当与创建了一个“简化”的对象——上转型对象会失去原对象的一些属性和功能
Animal animal = new Tiger(); //称对象animal是对象tiger的上转型对象,相当于老虎是动物。
(4)上转型后,该对象拥有的是父类的成员变量、静态变量、子类重写的方法
ps:
父类创建的对象不等于其子类上转型的对象
可以将上转型对象再强制转换为一个子类的对象
Animal{
***
}
People extends Animal{
***
}
Animal animal = new People(); //将People类创建的对象上转型为Animal类;
People people = (People)animal; //animal强制下转型为People类;
7.1上转型的应用——继承与多态:
Animal animal = new Dog();
Animal animal = new Cat(); ...
// (父类变量) 指向 (子类对象)
这样animal的方法就具有多态性
8.编译看左边,运行看右边:
class Animal{
public void eat(){
System.out.println("eat");
}
public static void sleep(){
System.out.println("sleep");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("Cat eat");
}
public static void sleep(){
System.out.println("Cat sleep");
}
public void miao(){
System.out.println("miao");
}
}
public class polyTest {
public static void main(String[] args) {
Animal animal = new Cat(); //编译看左边,运行看右边
animal.eat();
animal.miao(); //报错!!!
animal.sleep();
}
}
/*
对于非静态方法来讲,编译看左,运行看右:
animal.eat()在编译时看左边的类Animal中已存在eat()方法,因此编译正常;
在运行时看右边的类Cat(),因此程序在真正运行时,执行的实际上是Cat类中的eat()方法
animal.miao()在编译时看左边的类Animal中并不存在miao()方法,因此编译报错;
对于静态方法来讲,编译和运行都是看左边:
animal.sleep()在编译时看左边的Animal中已存在sleep()方法,因此编译正常;
在运行时看左边的类Animal(),因此程序在真正运行时,执行的实际上是Animal类中的sleep()方法
ps:因为Cat上转型animal后,该对象拥有的是父类的成员变量、父类的静态方法、子类重写的非静态方法
*/
9.抽象类/抽象方法:
abstract关键字修饰的类/方法为抽象类/抽象方法;
特点:
(1)抽象类/抽象方法只允许声明,不允许实现——没有方法体;
(2)不允许final和abstract同时修饰一个类/方法——必须要在继承的子类中重写abstract方法,final就不可改了(矛盾);
(3)不允许static和private修饰abstract方法——abstract方法必须是非private的实例方法;
ps:1.访问权限必须高于private 2.只能是非static方法(只能通过抽象类被继承后创建对象来调用)
(4)只有抽象类中才可以有抽象方法——普通类中不可声明抽象方法;
(5)抽象类不能通过new关键字创建对象——必须通过子类继承来创建对象;
ps:虽然不可创建对象,可以声明对象,使其成为子类对象的上转型对象,则该声明的对象就可以调用子类重写的方法
(6)若抽象类的子类是抽象类——可以继承也可以重写父类的抽象方法;
若抽象类的子类是非抽象类——该子类必须重写抽象类中的所有抽象方法;
理解abstract类:
(1)abstract类封装了其子类必须要有的行为标准;
(2)abstract类声明的对象可以成为其子类的对象的上转型对象,调用子类重写的方法;
七、接口与实现
1.接口:
(1)定义接口:interface关键字;
interface Com{
...
}
(2)在JDK8之前:只允许接口体中有抽象类和常量:
在接口体中定义的变量,默认为静态常量;定义的方法,默认为抽象方法;
int a = 100; //相当于:public static final int a = 100;
void add(); //相当于:public abstract void add();
在JDK8之后:
1.1接口中的实例方法:
interface Com{
public abstract void add();
public default int max(int a,int b){ //这里default不是权限修饰符,是修饰实例方法的;
return a>b?a:b;
}
}
在实例方法中,default不可省略:
interface Com{
public int max(int a,int b){ //是错误的,接口体中不允许定义带方法体的public实例方法;
return a>b?a:b;
}
}
所以若不想构造抽象方法在子类中重写,可以用default方法(默认实现方式)
1.2接口中的static方法:
interface Com{
public static void f(){ //可以直接用接口名.方法名()进行调用;
...
}
}
JDK9之后:
允许定义private方法,在接口体内进行方法的封装,供default实例方法在接口体内部调用private方法,实现算法复用;
2.实现接口:
通过类来实现接口,以便使用接口中的方法;
关键字:implements;实现多个接口,接口名用逗号隔开;
继承加实现接口的格式:
class Dog extends Animal implements Eatable,Sleepable{
...
}
2.1接口的细节说明:
(1)一个类实现了某个接口,就自然拥有了接口的常量,default方法(去掉default关键字),该类可以重写default方法;
(2)非abstract类实现了某个接口,必须重写接口中的abstract方法——即去掉abstract修饰给出方法体;
ps:类实现了接口,不拥有接口中的private方法和static方法;
类拥有的接口中的方法都是public方法,在重写时不可省略public修饰符(不可降低访问权限);
(3)若父类实现了某个接口,子类也就自然实现了该接口——子类不用显式的使用关键字implements声明;
(4)接口也可被继承,通过关键字extends声明一个接口是另一个接口的子接口;
3.接口的回调:
接口变量 = 实现该接口类的对象;
//接口变量不能为空,否则为空指针异常;
(1)接口也是一个重要的数据类型:用接口声明的变量成为接口变量;
(2)接口变量是一个引用型变量,与数组是一样的——存放类实现的接口中方法的地址;
ps:实现该接口的类中有两种方法:(1)类原有的方法(2)类实现的接口中的方法(可以用接口变量存放)
接口回调无法调用类中其他非接口方法;
(3)接口回调类似于C语言中的指针回调——表示一个变量的地址存放在一个指针变量中,该指针变量可以间接操作该变变量中存放的数据;
在Java中指可以把实现某一接口的类所创建的对象的引用赋值给接口变量,那么该接口变量就可以调用被该类实现的接口方法——类似于上转型;
4.函数接口与Lambda表达式:
JDK8开始:Java使用Lambda表达式;
单接口=函数接口:接口体中有且只有一个abstract方法;
接口变量存放Lambda表达式的值:
Lambda表达式可以写在主函数中,简化接口的回调——一般情况下接口回调:要通过接口变量=实现该接口类的对象,这样需要先构造实现接口的类;而Lambda表达式写在主函数里,在主函数内直接用接口变量存放Lambda表达式的值,即可实现该接口回调
interface Com{
public abstract int computeSum(int a,int b);
}
public class text{
public static void main(String[] args){
Com com;
com=(a,b)->{
return a+b;
}
com.computeSum(2,5)
}
}
5.abstract类和接口的比较:
(1)abstract类和接口都可以有abstract方法;
(2)接口只有常量,无变量;而abstract类都可以有;
(3)接口不可以有非default实例方法;
应用场景:
若子类除了需要继承父类的abstract方法,还需要从父类继承一些变量或非abstract方法——用abstract类;
若某个问题不需要继承,只是需要若干类给出某些重要的abstract方法的实现细节——用接口;
八、内部类与异常类
1.内部类:
(1)外嵌类:一个类除可以定义成员变量和方法,还允许类中定义类(内部类),而包含内部类的类(外嵌类);
1.1.内部类和外嵌类的关系:
(1)外嵌类声明的变量在内部类中仍然有效,内部类声明的方法可以调用外嵌类中声明的变量;
(2)内部类中不能声明类方法和类变量,可以在外嵌类中用内部类声明变量,称为外嵌类的成员;
(3)内部类尽供它的外嵌类使用,其他类不可以用某个类的内部类声明对象;所以只能在外嵌类中创建/实例化内部类对象
ps:static内部类除外,下面内部类特点中有描述;
1.2.内部类的特点:
(1)内部类可以用private和protected和static修饰 ps:非内部类只能用public和无修饰符(default)修饰;
(2)内部类可以被修饰为static内部类:类是一种数据类型,那么static内部类就是外嵌类中的一种静态数据类型,这样就可以在其他类中使用static内部类来创建对象
//在mian()所在的主类中使用static内部类创建和实名对象;
public static void main(String args[]}){
RedCowFarm.Redcow redcow = new RedCowFarm.Redcow(180,119,6000)
}
//构造RedCowFarm类:
static String farmName{
RedCow redcow;
RedCowFarm(){
}
RedCowFarm(String s){
cow = new RedCow(150,112,5000) //在外嵌类中调用内部类实例化对象;
}
//声明构造内部类:
static class RedCow{
String cowName="红牛";
int height,weight,price;
RedCow(int h,int w,int p){
height =h;
weight =w;
price = p;
}
}
}
2.匿名类:
不显示的声明一个类的子类,但用子类创建对象;
解释:用父类创建对象(父类可以是不允许直接创建对象的类,e.g.抽象类),该对象当子类用:
抽象类
abstract class A{
public abstract b();
}
//主类
public class C{
public static void mai(String args[]){
//用抽象类的构造方法类似于创建一个对象,但该对象有类体,内部具体实现抽象类中的所有抽象方法;
new A(){
b(){
System.out.printf("Hello")
}
}
}
}
2.1.和接口有关的内部类:
形式:
new 接口名(){
实现接口的匿名类的类体;
}
ps:若一个方法的参数是接口型变量,可以用接口名和类体的组合创建一个接口的匿名类对象递给方法和参数,类体必须重写接口中的所有方法;
2.2用Lambda表达式代替匿名类:
也是若一个方法的参数是接口型变量,就可以用Lambda表达式的值传递给参数;
//()->代替接口变量;
()->{
}
3.异常类:
异常就是程序运行时可能出现的一些错误,异常处理将会改变程序的控制流程,让程序有机会对错误做出处理;
程序运行出现异常,实际上是程序在执行过程中通过throw关键字抛出异常对象;
Java允许在定义方法时声明该方法在被调用过程中可能出现的异常——即允许方法在调用过程中抛出异常对象,终止当前方法的继续执行;
//在Throwable类中为我们提供了很多操作异常对象的方法:
public String getMessage();
public void PrintStackTrace();
public String toString();
3.1try-catch语句:
try-catch语句是处理异常的语句;
try部分存放可能抛出异常的语句——被检查的语句;若抛出异常,会立即终止剩余语句的执行,转到执行相应的catch部分;
catch部分存放相应异常的处理语句;过程中可能出现多种异常——因此可以由多个catch语句构成,分别处理相应的异常;
(若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理)
try{
}
//多个catch语句的一种形式:
catch(NumberFormatException e){
}
catch(ArithmeticException e){
}
//多个catch语句的另一种形式:
catch(NumberFormatException|ArithmeticException e){
}
ps:若try中语句无异常抛出,catch语句不执行,只执行finally块语句;
try {
被捕获的代码
}
catch(异常类型 e) {
对异常的处理
}
finally{
最后一定会执行的代码
}
3.2finally块:
finally块一般都会执行,相当于一个万能的保险,但有4种特殊情况finally将不会执行
1.finally块中发生异常
2.在前面的代码中使用了System.exit(0);
3.程序所在线程死亡
4.关闭CPU
finally块语句的应用场景:
1.资源整合;2.将文件输入/输出流关闭;3.关闭数据库连接;
try-catch-finally中存在return时,执行的先后顺序:
首先执行try-catch部分,若有rerurn 则先不返回,将return值存入临时栈中,无论有无异常,都执行finally块,若finally块中有return,则代替临时栈中的原先的return值并在执行完finally块后直接返回此return值,原先try-catch部分中的return被代替,不再返回;
ps:此三部分只要有一个部分含有return,finally块之后的return不再执行——程序只允许有一个return;
3.3自定义异常类:
自定义异常类声明格式:
public class 自定义异常类名 extends Exception{
public 自定义异常类名(){
//自定义异常类名的构造函数,在后面throw抛出异常,创建异常对象时用到。
}
//常用的是父类Exception的构造函数:
public 自定义异常类名(String ErrorMessage){
super(ErrorMessage);
}
//其次是要写自定义异常类对异常的处理方法,其中部分是继承的Exception的一些方法:e.g.
public String getMessage(); //返回该异常的详细信息字符串,即异常提示信息
public void PrintStackTrace(); //在控制台输出该异常的名称与详细信息字符串、异常出现的代码位置
public String toString(); //返回该异常的名称与详细信息字符串
public String warnMess(){ //自定义的异常类方法
...
}
}
自定义异常类通过throws关键字应用:
//允许在声明方法时通过throws关键字声明要产生的若干异常类(可以是原有的异常类,也可是自定义异常类)
class A{
public void method() throws 异常类名1,异常类名2,...{
//需要在throws声明的方法体内通过throw关键字抛出创建的异常对象:
throw new 异常类的构造函数();
}
}
public class 主类{
public static void main(String arg[]){
A a = new A();
try{
a.method(); //检查method是否抛出异常对象;
}catch(异常类名1|异常类名2|... e){
//对异常的处理(调用异常对象的方法)
e.PrintStrackTrace();//Exception父类原本含有的方法——打印错误信息(必写)
e.在自定义异常类中拓写的方法;
}
}
}
4.断言:
断言语句一般用于程序不准备通过捕获异常来处理的错误,例如:当发生某个错误时要求程序必须立即停止执行。
4.1.启动和关闭断言:
当使用Java解释器直接运行应用程序时,默认关闭断言语句
在终端使用:java -ea mainClass 来启动断言
4.2.断言语句格式:
assert boolean型的表达式;
//或者
assert boolean型表达式:字符串表达式;
// boolean表达式的值为true,不终止程序,继续执行;
// boolean表达式的值为flase,立即终止程序,并且显示字符串表达式;
ps:可用在for循环中判断每个语句;
九、常用实用类
1.String类
1.1构造String对象:
//1.常量对象:"字符串" :
"hello" "你好"
//2.String对象:
//方法一:
String s = new String("we are students");
String t = new String("we are students");
//方法二:
String s,t;
s="we are students";
t="we are students";
/*
方法一:对象变量s和t中存放的是new运算符开辟的内存空间的引用,s和t虽然实体相同(都是相同字符串),但是两者的引用不同,
即表达式s==t的值是flase(new运算符,每次都要开辟新天地)
方法二:对象变量s和t中存放的是常量池中"we are students"的引用,所以s和t的值是相同的(没有用new关键字开辟新空间),s和t都指向同一个常量池中的"we are students"。
*/
//3.可以通过已将创建的String对象创建另一个String对象:
String s = new String("hello");
String t = new String(s); //用s对象的实体创建t对象的实体;
//4.用字符数组创建String对象:
char a[] = {'J','a','v','a'};
String s = new String(a);
//5.提取字符数组a中的一部分字符创建String对象:
String(char a[],int startIndex,int count) //startIndex表示起始位置,count表示截取的字符个数 e.g.
char a[] = {'J','a','v','a'};
String s = new String(a,1,3); //结果为:ava
2.2理解用户无法输出String对象的引用!!!
String s = new String("hello");
System.out.println(s); //打印的String对象的实体
解释:
System.out.println(s) 实际上等于 System.out.println(s.toString());
/*
println()方法在打印时,首先调用的是对象的toString()方法(任何一个对象都有toString()方法),
因为任何一个类都是Object类的子类,Object类中声明了toString()方法,返回值是:创建该对象的类的名字@对象的引用;
但是此处,String类是Java本身提供的类,String类中重写了toString()方法
当println()方法在打印时,调用String类中的toString()方法,此方法返回的是String对象的value值
运行期会将String修饰的对象的value全部转化为常量池中的数据,用常量池表来维护
*/
ps:
输出String对象的引用的方法:让System类调用静态identityHashCode(Object object)方法
int address = System.identityHashCode(Object object);
3.String对象是不可变对象:
定义:对象的变量中存储的值不能发生变化。即String对象的实体(String对象封装的字符序列)无法再发生变化,只能改便String对象中的引用,使其改变所指向的实体,但每个实体的内容不能再改变。
why:String类是final类,String类也没有给其对象提供修改实体的方法。
4.String对象的并置:
用“+”符号进行并置拼接;
ps:
//String常量的并置:
String hello = "你好";
String s = "你"+"好";
System.out.printf(s==hello)
System.out.printf("你好"==hello)
System.out.printf("你好"==s)
/*
三个输出都为ture,因为:
用常量池中的"你好"为hello赋值,则常量池中"你好"的引用会赋值给hello
用常量池中的"你","好"为s赋值并拼接,会转化成常量池中的"你好",将常量池中"你好"的引用赋值给s
(这是编译原理中,常量优化技术,常量折叠式一种Java编译器使用的优化技术,还有int x =1+2;会被优化成int x =3;)
*/
//String变量的并置:
String hello = "你好";
String t = "你";
String s = "好";
String hi = t+s;
System.out.printf(hello==hi) //输出的结果为flase
//因为两个String对象t+s,相当于new String("你好"),就不是取常量池中的引用,而是在动态区产生了新对象存放引用;
5.String类的常用方法:
//(1)length()
String对象.length()来获取字符串的长度;
//(2)equals(String s)
String对象1.equals(String对象2)来比较两个对象的实体(字符序列)是否相同
ps:比较时忽略大小写
//(3)startsWith(String s)/endsWith(String s)
String对象1.startsWith(String对象2)判断String对象1的字符序列前缀是否为String对象2
String对象1.endsWith(String对象2)判断String对象1的字符序列后缀是否为String对象2
//(4)compareTo(String s)
String对象1.compareTo(String对象2)按字符串在Unicode中的排序位置比较String对象1和String对象2,结果为0/正值/负值
//(5)contains(String s)
String对象1.contains(String对象2)判断String对象1是否包含String对象2
//(6)indexOf(String s)/lastIndexOf(String s)
String对象1.indexOf(String对象2)从索引0检索对象1首次出现String对象2的位置,并返回该位置,若无则返回-1
String对象1.lastIndexOf(String对象2)从索引0检索对象1最后一次出现String对象2的位置,并返回该位置,若无则返回-1
ps:indexOf(String s,int startpoint)是一个重载方法,startpoint来指定检索的开始位置——从索引startpoint检索;
//(7)substring(int startpoint)
String对象1.substring(int startpoint)获得一个新对象,该对象的实体是从String对象1的索引startpoint位置至最后位置上的字符序列
ps:String对象1.substring(int start,int end)是一个重载方法,获取一个新对象,该对象的实体是从String对象1的索引start位置至end-1位置上的字符序列
//(8)trim()
String对象.trim()获得一个新对象,该对象的实体是原对象实体去掉前后空格的字符序列
//(9)toLowerCase()/toUpperCase();
String对象.toLowerCase()可以将字符串中的所有字符从大写字母改写到小写字母;
String对象.toUpperCase()可以将字符串中的所有字符从小写字母改写到大写字母;
//(10)replace()
String对象.replace(char oldChar, char newChar)方法可实现将指定的字符或字符串替换成新的字符或字符串。
oldChar:要替换的字符或字符串。newChar:用于替换原来字符串的内容。
ps:
String对象.replace(String regex,String replacement)将符号regex(正则表达式)匹配的字符换成relpacement
6.String对象与基本数据的互相转换:
6.1将String对象转换为基本型:
调用Integer.parseInt(String对象)、byte.parseInt(String对象)、short.parseInt(String对象)、long.parseInt(String对象)、float.parseInt(String对象)、double.parseInt(String对象)——将String对象转换成相应的基本型;
6.2将基本型转换为String对象:
//方法一:调用String类的静态方法valueOf(基本型变量): e.g.
String str = String.valueOf(12313.98);
//方法二:基本型数据和不含任何字符的String对象进行并置运算 e.g.
String str = ""+12313.98;
6.3基本型数据的进制表示:
//可以将int/long型数据的二进制、八进制、十六进制的表示作为String对象封装的字符序列:
String.toBinaryString(int/long n); //二进制,负数转换成二进制补码
String.toOctalString(int/long n); //八进制
String.toHexString(int/long n); //十六进制
7.String对象与 字符数组、字节数组:
7.1String对象与字符数组:
//用字符数组创建String对象:
//分别用数组a中的全部字符和部分字符创建String对象:
String(char a[])和String(char a[],int offset,int length);//通过String()构造方法来实现
//用String对象创建字符数组:
//(1)在String对象的字符序列中从位置start到end-1位置的字符复制到从字符数组的offset处及之后 ps:必须保证数组能容纳被复制的字符序列
String对象.getChars(int start,int end,char a[],int offset);
//(2)将String对象的字符序列全部存放在一个字符数组中:
String对象.toCharArray(); //返回的数组长度与String对象的字符序列长度相同
7.2String对象与字节数组:
//用字节数组创建String对象:
//分别用指定的字节数组中的全部字符和部分字符创建String对象:
String(byte [])和String(byte[],int offset,int length);//通过String()构造方法来实现
//用String对象创建字节数组:
//使用平台默认的字符编码,将当前String对象的字符序列存放到字节数组中,并返回数组的引用:
String对象.getBytes()
//使用指定的字符编码,将当前String对象的字符序列存放到字节数组中,并返回数组的引用:
String对象.getBytes(String charsetName)
//ps:getBytes(String charsetName)会抛出异常,因此必须在try-catch语句中调用!!!
7.3字符序列的加密算法:
/*
(1)创建一个String类的password字符串,将password对象存放在数组中:
(2)假设password的长度为n,则将待加密的sourceString的字符序列以n个字符分组(最后一组的字符个数可以小于n),对每一组的字符用数组p的对应字符做加法运算
(3)上述加密算法的解密算法是对密文做减法运算
*/
//加密:
char p = password.tocharArray();
int n =p.length;
char c = sourceString.tocharArray();
int m = c.length;
for(int k =0,k<=m,k++){
int mima =c[k]+p[k%n]
c[k] = (char)mima //将相加后的整型变量mima转换为字符型变量赋值为原来的c[k]
}
//解密:
char p = password.tocharArray();
int n =p.length;
char c = sourceString.tocharArray();
int m = c.length;
for(int k =0,k<=m,k++){
int mima =c[k]-p[k%n]
c[k] = (char)mima //将相减后的整型变量mima转换为字符型变量赋值为原来的c[k]
}
8.字符串生成器(StringBuffer):
8.1定义:
StringBuffer类,即字符串生成器,新创建的StringBuffer对象初始容量是16个字符,可以自行指定初始长度,也可以动态地执行添加、删除和插入等字符串的编辑操作,大大提高了频繁增加字符串的效率。如果附加的字符超过可容纳的长度,则StringBuffer对象将自动增加长度以容纳被附加的字符。
8.2StringBuffer类的方法:
//StringBuffer类的构造方法有很多,主要是参数上的区别,这里我们主要介绍几种在编程中经常会用到的方法。
(1) StringBuffer append(String str)方法
/*StringBuffer拼接字符串,时间开销远小于字符串拼接符+。此外,由于String对象是不可变对象,所以每轮循环拼接都会生成新对象,而StringBuffer则是修改底层封装的动态数组,StringBuffer消耗的内存空间也更少。所以,当业务中需要频繁增删字符串时,推荐使用StringBuffer。
*/
StringBuffer append(StringBuffer sb)方法
StringBuffer insert(int offset, String str) 方法
StringBuffer delete(int start, String end)方法
String toString() 方法
9.正则表达式:
9.1正则表达式与元字符:
正则表达式是一个String对象的字符序列,该字符序列中含有具有特殊意义 的字符(元字符);
e.g.
\\dcat //其中的\\d就是一个元字符,该序列代表0-9中的任意一个"0cat、1cat、2cat..."
正则表达式通常被用于判断语句中,用来检查某一字符串是否满足某一格式。通过调用String类中的matches(String regex)方法,判断字符串是否匹配给定的正则表达式,返回布尔值。
正则表达式元字符使用方法:
在正则表达式中可以使用方括号括起来若干个字符表示一个元字符,该元字符可代表方括号中的任意字符,例:
[^abc]:代表abc以外的任意字符;
[a-r]:代表从a到r的任意字母;
[a-zA-Z]:可表示任意一个英文字母;
[a-e[g-z]]:表示a到e,或g到z中的任意字母(并运算)
ps:由于“.”代表任何一个字符,所以在正则表达式中如果想使用普通意义的点字符,必须使用[.]或者\\.表示;
正则表达式限定符:
限定修饰符 | 意义 | 示例 |
---|---|---|
? | 0次或1次 | A? |
* | 0次或多次 | A* |
+ | 1次或多次 | A+ |
{n} | 正好出现n次 | A{2} |
{n,} | 至少出现n次 | A{2,} |
{n, m} | 出现n到m次 | A{2, 6} |
9.2常用的正则表达式:
//1.匹配整数的正则表达式
String regex = "-?[1-9]\\d"; //任何形如整数的字符序列都与regex相匹配
//2.匹配浮点数的正则表达式
String regex = "-?[0-9][0-9]*[.][0-9]+";
//3.匹配Email的正则表达式
String regex = "\\w+@\\w+\\.[a-z]+(\\.[a-z]+)?";
//或者 ()内的是可有可无的,若有,则需要符合()内的正则表达式
String regex = "\\w+@\\w+(\\.\\w{2,3})*\\.\\w{2,3})";
//4.匹配身份证号码的正则表达式
String regex = "[1-9][0-9]{16}[a-zA-Z0-9]{1}";
//5.匹配日期的正则表达式
String year = "[1-9][0-9]{3}";
String month = "((0?[1-9])|(1[012]))";
String day = "((0?[1-9])|([12][0-9])|(3?[01]))";
String regex = year+"[-./]"+month+"[-./]"+day;
9.3字符序列的替换:
写在String类的常用方法中:
9.4字符序列的分解:
String 数组名[] = String对象.split(regex);
/*String对象调用该方法的时候,使用参数指定的regex(正则表达式)所匹配的字符作为分隔标记,分解出当前String对象的字符序列中的多段字符,并存放在String数组中。
ps:
split()方法认为分隔标记的左右是单词,若左边无字符,则传入数组一个空字符(""),右边无所谓——只限制左边!!!
e.g.一个字符串的首字符是匹配的分隔标记,该字符已经是首字符,左边无字符,则数组[0]中存入空字符;
一个字符串的首字符不是匹配的分隔字符,该字符左边有字符,则正常存入;
*/
10.StringTokenizer类
与前面将的String类中的split(regex)方法的用途一样,是将字符串分解成若干字符串,但是与split(regex)方法不同是的,该类不用正则表达式做分隔标记;
//StringTokenizer类有两个常用构造方法
StringTokenizer(String s); //为s构造一个分析器,使用默认的分隔标记:空格符、换行、回车符、制表符、进纸符;
StringTokenizer(String s,String delim) ////为s构造一个分析器,delim的字符序列中的字符任意排列作为分隔标记
称一个StringTokenizer对象为一个字符序列分析器,分析器中封装的是若干的单词,一个分析器可以通过nextToken()方法逐个获取分析器中的单词——一般用while循环,为了控制循环,可以类调用hasMoreToken()方法,只要分析器中还有单词,返回值就是ture——每当nextToken()获取一个单词,该单词就在原的分析器中删除。另外,还可以随时让分析器调用countTokens()方法返回当前分析器中的单词的个数。
11.Scanner类
十三、多线程机制
1.进程与线程:
1.1操作系统与进程:
程序是一段静态的代码——应用软件执行的蓝本。
进程是一个程序的动态执行过程,它对应了从代码加载、执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到消亡的过程。
现代操作系统可以同时管理计算机系统的多个进程——即让多个进程轮流使用CPU资源。
甚至让多个进程共享操作系统所管理的资源:e.g. word进程与其他文本编辑器进程同时共享系统的剪切板。
1.2进程与线程概述:
线程不是进程,线程是比进程更小的执行单位,一个进程在执行的过程中可以产生多个线程,每个线程有它自身的产生、存在、和消亡的过程。进程中的多个线程可以共享进程中的某些内存单元(代码与数据),实现必要的通信和同步操作。
没有进程就没有线程,就像没有操作系统就没有进程一样。通俗的讲,线程是运行在进程中的“小程序”。
2.Java中的线程:
2.1java的多线程机制:
执行线程给人一种几件事件同时发生的感觉——错觉,因为计算机在任何给定的时刻只能执行线程中的一个
产生错觉的原因:CPU时间片短,切换线程快。
多线程轮流执行,获得真实的线程并发执行效果。
2.2主线程:
定义:
主线程:每个java应用程序都有一个默认的主线程——main()线程,当JVM加载代码,发现main()方法后就会启动main()线程
其他线程:在main()线程中再创建的线程
若未在main()方法中创建其他线程,执行完main()方法就会结束;若创建了其他线程,就要在主线程和其他线程之间轮流切换
2.3线程的状态和生命周期:
(1)Thread类及其子类的对象来表示线程 (2)runnable接口
Thread.getState()方法返回枚举类型Thread.State的枚举常量:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
1.新建状态:NEW状态
当一个Thread类或其子类的对象被声明时,新生的线程对象处于NEW状态——此时已经有了相应的内存空间和其他资源——尚未启动(没有调用start()方法)的线程处于此状态
2.可运行状态:RUNNABLE
处于NEW状态的线程仅仅占有内存空间,JVM管理的线程中还没有这个线程——调用start()方法,进入RUNNABLE状态,这样JVM就知道有个新线程在排队等候切换了。
当JVM分配给RUNNABLE状态的线程使用CPU时,若该线程是Thread的子类创建的,就会调用该类的run()方法,所以必须重写父类中的run()方法——规定了线程的具体使命。
3.中断状态:BLOCKED、WAITING、TIMED_WAITING
当中断状态进入可运行状态时,Jun()方法从中断处继续执行。
(1)当JVM将CPU资源从当前线程切换给其他线程,该线程是BLOCKED状态,必须等待JVM解除状态才能进入RUNNABLE状态。
(2)线程在使用CPU资源期间执行了Thread类中的sleep(int millsecond)方法,使当前线程进入休眠状态——TIMED_WAITING,立刻让出CPU使用权,等待millsecond秒后再进入RUNNABLE状态。
(3)线程在使用CPU资源期间执行了Thread类中的wati()方法,使当前线程进入WAITING状态,该状态不会自动进入RUNNABLE状态,需要其他线程调用notify()方法通知。wait()——notify()
(4))线程在使用CPU资源期间执行了某些操作,如等待用户输入数据(Scanner(System.in)),导致线程进入WAITING状态,那么只有当引起WAITING的原因消除时,线程才重新进入RUNNABLE状态。
4.死亡状态:TERMINATED
线程执行完run()方法,进入TERMINATED状态,处于死亡状态的线程不具备继续运行的能力
ps:只有处于NEW状态的线程才能调用start()方法,其他状态的线程调用start()方法会触发Illegal ThreadStateException异常。
2.4线程调度与优先级:
(1)JVM中的线程调度器将处于就绪的线程分为10个优先级,分别用Thread类中的常量表示。
每个Java线程的优先级都在常数1-10之间,即Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之间。
若没有明确设置线程的优先级,则每个线程都为5,即Thread.NORM_PRIORITY
(2)线程的优先级可以用setPriority(int grade)方法调整,参数必须在1-10之间;getPriority()返回线程的优先级。
ps:有些系统只识别3个级别——1,5,10
(3)JVM的线程调度器保证高优先级的线程始终运行,CPU时间片一旦有空闲,则使具有同等优先级的线程以轮流的方式顺序使用时间片。
ps:在实际编程中不提倡使用线程的优先级来保证算法的正确执行,如果要编写正确、跨平台的多线程代码,必须假设每个线程在任何时刻都有可能被剥夺CPU资源的使用权——见12.6协调同步线程
3.Thread类与线程的创建:
3.1使用Thread子类:
创建类继承Thread类,在创建的类中重写父类中的run()方法
优点:
可以在子类中增加新的成员变量——使线程具有某种属性;
可以在子类中增加新的方法——使线程具有某种功能;
3.2使用Thread类:
用Thread类直接创建对象用的构造方法是Thread(Runnable target),参数是Runnable接口类的实例对象——该线程的目标对象,通过目标对象自动调用接口中的run()方法(接口回调)
目标对象:用实现Runnable接口的类创建的对象
简单来说:创建一个类实现Runnable接口,用该类创建的对象传入Threadl类的Thread(Runnable target)构造函数来创建线程。
优点:
一个目标对象可以当作多个Thread类构造方法的参数,传入多个线程,实现多个线程共享一个目标对象,该目标对象的属性和方法供多个线程使用。
3.3目标对象与线程的关系:
(1)目标对象和线程完全解耦
实现Runnable接口的类,其没有组合其他线程对象——所创建的目标对象不含其他线程对象引用(完全解耦)
这种情况下,在该类中经常需要获得线程对象的名字:
String name = Thread.currentThread.getName(); //正在运行的线程对象的名字
(2)目标对象组合线程(弱耦合)
实现Runnable接口的类,允许将线程对象当作其成员
这种情况下,在该类中获得线程对象:
Thread t = Thread.currentThread(); //正在运行的线程对象
e.g.
// (1)目标对象和线程完全解耦
public class main{
TEXT text = new text(); //创建目标对象
Thread A,B; //声明A,B线程
A = new Thread(text); //创建A线程
B = new Thread(text); //创建B线程
}
class TEXT implements Runnable{
...
}
//(2)目标对象组合线程(弱耦合)
public class main{
TEXT text = new TEXT();
}
class TEXT implements Runnable{
Thread A,B;
TEXT(){
A = Thread(this); //this代表目标对象)——text
A = Thread(this); //this代表目标对象)——text
}
}
//两者等价,区别在于在哪个位置创建Thread线程!!!
4.线程的常用方法:
(1)start()
只有处于NEW状态的线程才能调用一次start()方法;
(2)run()
必须重写父类的run()方法——自动调用
(3)sleep(int millsecond)
在run()方法中调用,进入休眠状态
millsecond设置休眠时间,以毫秒为单位
必须在try-catch语句中调用——睡眠状态被打断会抛出异常
(4)isAlive()
线程在NEW状态——返回值:flase
线程调用start()方法后,没有进入死亡状态前——返回值:ture
线程在死亡状态——返回值:flase
ps:线程只要调用start()后进入运行状态,在没有进入死亡状态之前,不要再给其分配实体!否则线程只会引用最后分配的实体,先前的实体会称为“垃圾”且不会被垃圾收集器收集掉——JVM认为“垃圾”实体正在运行
Thread thread = new Thread(target);
thread.start();
thread = new Thread(target);
thread.start();
//此时有两个线程在执行,原先的线程不会停止!!!
(5)currentThread()
静态方法,可以直接用类名调用,返回正在使用CPU资源的线程
(6)interrupt()
与sleep()方法配合使用,吵醒正在休眠的线程——在正在运行的线程中用休眠线程.interrupt()唤醒。
5.线程的同步方法:
当两个或多个线程同时访问和修改同一个变量,应确保数据的正确性——若干个线程都需要使用synchronized(同步)
修饰的方法,即若干线程都需要使用一个方法,该方法用synchronized修饰,多个线程调用synchronized()方法必须遵守同步机制
同步机制:当线程A使用synchronized方法时,其他线程必须等待,直到线程A使用完该synchronized方法——线程A一直占有该方法。
6.协调同步方法
如果当一个线程使用synchronized方法时,用到的某个变量又需要其他线程修改后继续使用,可以在synchronized方法中使用wait()方法——中断执行、暂时让出CPU使用权,让其他线程使用这个synchronized方法,其他线程使用完,调用notifyALL()方法,让所有因为使用这个synchronized方法的线程结束等待,从中断处继续执行。
notifyALL()唤醒时,遵循先中断先执行。
notify()是唤醒某一个等待的线程。
ps:在许多实际问题中,wait()方法应放在一个“while(等待条件){}”的语句中,而不是放在“if(等待条件){}”的语句中。
7.线程联合:
一个线程A在占有CPU资源期间可以让其他线程调用join()方法和本线程联合,原线程会暂停,先执行join()的线程
B.join(); //加入B线程
B.join(时间); //A线程暂停时间
8.GUI线程:
9.计时器线程:
10.守护线程:
十六、泛型与集合框架
从JDK1.5以后,有自动装箱,自动拆箱的实现。
e.g.
Integer i = new Integer(5) //类中有多个构造函数,自动调用符合的构造函数
Integer i = 5; //自动装箱:类不用new关键字调用构造函数
int x = i; //自动拆箱:将对象封装的value取出
1.泛型:
泛型是在JDK1.5中推出的,为了建立具有类型安全的集合框架。
这些类型参数在声明时并不指定具体的类型,而是在使用时(例如实例化类、调用方法等)才指定具体的类型。
泛型的主要目的:提供编译时的类型安全,并减少类型转换和代码重复。
1.1泛型类的声明:
class People<E>{
... //泛型类
}
E是泛型,也就是说并没有指定E是何种类型的数据,可以是任何对象或接口,但不能是基本类型数据——可以是他们的封装类:Byte、Long、Short、Integer......
1.2使用泛型类声明对象:
泛型实例化时,应具体某一类型:
People<Float> zhang = new People<2.0>();
People<Integer> zhang = new People<3>();
//可以多次用同一泛型类指定不同具体类型
1.3实现泛型接口:
在使用一个非泛型类实现泛型接口时,必须指定泛型接口中泛型的具体类型
ps:不是在该类实例化时,而是在类实现泛型接口时——编写该类的时候
e.g.
//java.lang包中的泛型接口:
public interface Comparable<T>{
public int compareTo(T m);
}
public class Dog implements Comparable<Dog>{ //将泛型T指定为Dog类型
...
}
2.集合类继承关系:
2.Collection接口:
Collection接口是层次结构中的根接口,构成Collection的单位称为元素;
3.List接口:
(1)特点:
List 接口定义了一个有序的集合,其中的元素可以通过其索引(位置)进行访问。
List 允许存储重复的元素,并且每个元素都有一个特定的索引。
(2)常用方法:
boolean add(E element);
将指定的元素追加到此列表的末尾。
void add(int index, E element);
将指定的元素插入此列表中的指定位置
E get(int index);
返回此列表中指定位置的元素。
int size()
返回长度
e.g.
//一般用接口回调:
List<String> fruits = new ArrayList<>();
fruits.add("apple");
fruits.add("orange");
fruits.add("banana");
System.out.println(fruits.size());
System.out.println(fruits.get(0));
(3)List接口常见实现类:
ArrayList :基于动态数组的实现,提供了对元素的快速访问。 内存空间连续
LinkedList :基于双向链表的实现,提供了对列表元素的快速插入和删除操作。 内存空间可以不连续
ArrayList和LinkedList比较:
ArrayList | LinkedList | |
---|---|---|
优点 | 适合查询 | 适合新增删除元素 |
缺点 | 不适合增删(尾新增和尾删除除外) | 不适合查询 |
1.链表:
链表是由若干个称作结点的对象组成的一种数据结构;
单链表:每个结点含有一个数据和下一结点的引用;
双链表:每个结点含有一个数据并含有上一结点的引用和下一结点的引用;
2.双链表——LinkedList\泛型类:
常用方法:
3.遍历链表:
4.排序与查找:
5.洗牌与旋转:
4.Map接口:
Map是Java中用于存储键值对的数据结构。在 Map中,每一个元素都包含一个键和一个值
ps:键是唯一的,而值可以重复
常用方法:
V put(K key, V value);
将指定的值与此映射中的指定键关联。如果映射以前包含了一个该键的映射关系,则旧值被替换。
V get(K key);
返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
//一般用接口回调:
Map<String,String>map = new HashMap<>();
map.put("001","张三");
map.put("002","李四");
System.out.println(map.get("001"));
map.put("001","Jerry"); //存在重复的Key值,新的value将代替旧的value;
System.out.println(map.get("001"));
Map接口常见实现类:
HashMap : 基于哈希表的 Map 接口的实现(重点)
TreeMap : 基于红黑树实现
4.1散列映射——HashMap<K,V>泛型类:
常用方法
4.2遍历散列映射:
4.3基于散列映射的查询:
4.4树映射——TreeMap<K,V>泛型类:
5.Set接口:
5.1树集——TreeSet\泛型类:
常用方法:
5.2结点的大小关系:
5.3集合——HashSet\泛型类:
常用方法:
5.4集合的交、并、差:
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:http://seplibrah.cn/hello-world/