Java SE笔记
一、注释
1.1注释的作用
注释是对代码的解释和说明,其目的是让程序员更快的理解代码。
注释中的字符将被 Java 编译器忽略。
在编译时,注释不会生成到class字节码文件中,只在Java源文件中保留。
1.2 书写注释的三种方式
1.单行注释,格式如下:
//这是单行注释
2.多行注释,格式如下:
/*
*这是第一行多行注释
*这是第二行注释
*/
3.javadoc注释,格式如下:
/**
*这里的信息是javadoc注释
*@author 作者名字
*@version 版本号
*@since 从哪个版本开始存在的
*javadoc的注释会被JDK解析并生成帮助文档
*/
二、标识符和关键字
2.1 标识符
标识符是指程序员自己规定的代表一定含义的单词。例如:类名、接口名、变量名、方法名、常量名等。
2.1.1 标识符命名规则
命名规则是必须遵守的,否则会报错。
标识符命名规则如下:
标识符只能由数字、字母、下划线(_)、美元符号($)组成
标识符不能以数字开始
标识符严格区分大小写
Java关键字和保留字不能作为标识符
标识符理论上没有长度限制
示例程序:
public class Student {//Student是类名
int age;//age是变量名
public void setAge(int a){//setAge是方法名,a是变量名
age = a;
}
}
2.1.2 标识符命名规范
命名规范是一种书写代码的通用习惯,不遵守不会报错。
标识符命名规范如下:
见名知意:增强可读性
驼峰命名:利用首字母大小写分隔单词,每个单词之间会划清界限
类名、接口名首字母大写,后面每个单词首字母大写,例如:HelloWorld
变量名、方法名首字母小写,后面每个单词首字母大写,例如:getName
常量名全部大写,单词和单词之间使用下划线连接
2.2 关键字
Java关键字是编译语言事先定义的,有特殊意义的单词。
Java中所有关键字都是小写的。
常见关键字如下:
三、变量
3.1 字面量
字面量就是数据、数值。
在编程语言中数据被分为:
整数型:1、2、-1
浮点型:1.0、3.14
字符型:'a'、'中'
布尔型:true、false
字符串型:"abc"、"好的"
3.2 变量
变量是内存中存储数据最基本的单元。
变量的三要素是数据类型、变量名、字面量。
3.2.1 变量的声明及赋值
在使用变量前必须先进行变量的声明。然后进行变量的赋值。
在同一个域中,变量不能重名。
变量声明的语法格式:
数据类型 变量名;
变量名 = 值;
例如:
int i;
i = 1;
int count = 100;
int a = 200,b = 300,c = 400
int a,b,c=20;//这种声明赋值方式是错的。但是在C++中是对的。
3.2.2 变量的分类
变量根据声明位置分为两大类四种:
局部变量:在方法体中声明的变量以及方法的每一个参数都是局部变量
成员变量:在方法体外,类体内声明的变量是成员变量
成员变量又分为两种:
静态(成员)变量:成员变量声明时使用static关键词修饰的称为静态变量
实例(成员)变量:没有static关键词修饰的称为实例变量
例如:
public class Test{
int x=20;//实例变量
static int y=200;//静态变量
public static void sum(int a,int b){//局部变量a和b
int firstNum=100;//局部变量
}
}
3.2.3 变量的作用域
变量的作用域就是变量的使用范围。
不同类型的变量存储在java虚拟机上的不同内存区域,所以变量是有作用域的。
简单理解为:出大括号就不认识。
小例子:
{
int i = 100;
{
在这里可以访问i
}
}
{
在这里是无法访问i。
}
四、数据类型
数据类型的作用是决定程序运行阶段给该变量分配多大的内存空间。
数据类型主要分为两大类:
基本数据类型:分为四类八种:
1.整数型:byte、short、int、long
2.浮点型:float、double
3.字符型:char
4.布尔型:boolean
布尔类型在java中只有true和false,没有0和1
引用数据类型:除基本数据类型其他均为引用数据类型
字符串型String属于引用数据类型
基础数据类型的详细信息如下:
关于计算机存储单位
1bit就是一个1或0
1字节=8bit
1byte=8bit
1KB=1024byte
1MB=1024KB
1GB=1024MB
1TB=1024GB
4.1 字符型数据类型
char类型的自变量用单引号括起来,只能存储一个字符。
char的取值范围为[0-65535]
char采用unicode编码方式
char可以存储一个汉字
示例程序:
public class Test {
public static void main(String[] args) {
char c1 = '中';
System.out.println(c1);
char c2 = 'a';
System.out.println(c2);
char c3 = '0';
System.out.println(c3);
// char c4 = "a"; String类型不能转换为char类型
// System.out.println(c4);
// char c5 = 'ab'; char类型只能容纳一个字符
// System.out.println(c5);
// char c6 = 'a; 未结束的字符文字
// System.out.println(c6);
}
}
运行结果:
中
a
0
4.2转义字符
转义字符是一种特殊的字符常量。
转义字符以反斜线" \ "开头,后跟一个或几个字符。
转义字符具有特定的含义,不同于字符原有的意义,故称“转义”字符。
常用转义字符入选如下: 示例程序:
public class Test {
public static void main(String[] args) {
char c1 = 't';
System.out.println(c1);
char c2 = '\t';// \t是一个字符,表示“制表符tab”
System.out.println(c2);
System.out.println("abc\tdef");
System.out.print("123\n456");
System.out.println("\\");//输出一个"\"
char c3 ='\u4e2d';//unicode编码表示"中"
System.out.println(c3);
}
}
运算结果:
t
abc def
123
456\
中
4.3 整数型
整数型:byte、short、int、long。
一般直接使用int即可。
在Java中,整数型的字面量默认被当作int类型处理。
如果希望整数型被当作long类型处理,需要在自变量后加“L / l”。
4.4浮点型数据类型
浮点型包括:float,double。
在Java中,浮点型的字面量默认被当作int类型处理。想被按float处理,需在字面量后加“F/f”。
浮点型数据在计算机中存储的都是近似值。
在银行方面double精度也是远远不够的,一般使用java.math.BigDecimal,属于引用数据类型。
long类型8个字节,float4个字节,但是float容量更大。记住即可
4.5布尔型数据类型
布尔类型包括:boolean只有两个值true,false
布尔类型不能和其它类型转换。
使用在逻辑判断中,一般用在条件判断位置上。充要条件
4.6 类型转换
小容量转大容量称为自动类型转换。(byte<short(char)<int<long<float<double)
大容量转小容量称为强制类型转换,必须加强制类型转换符,超范围会有精度损失。
浮点数类型强转时,只留下整数位。
byte、short、char做混合运算时,先各自转换为int再做运算。
char c1='a';
byte b=1;
short s=c1+b;当有变量时,需要加强转才行
short k=98;当是具体数值时可以自动转
多种类型做混合运算时,各自先转换成容量最大的那种再运算。
char,short,byte运算除外,因为会先转成int。
示例程序:
public class Test {
public static void main(String[] args) {//
long a = 10L;
char c = 'a';
short s = 100;
int i = 30;
System.out.println(a+c+s+i);//237
//int x = a+c+s+i;
//System.out.println(x);
//报错!java: 不兼容的类型: 从long转换到int可能会有损失
//说明(a+c+s+i)是long类型
int x = a+c+s+i;
System.out.println(x);
//结果为:227未报错
}
}
当整数型没有超出byte,short,char范围时,可以直接赋值。
int a = 100;没有类型转换
long b = 100;自动类型转换
long c = 100L;没有类型转换
long d = 2147483648;会报错,int值超范围,此时必须加“L”变为long类型
long e =2147483648L;
int f = (int)e;强制类型转换,超int范围会损失精度
计算机存储数据是以二进制补码形式存储的,正数的原码反码补码是一样的。
long类型100L:00000000 00000000 00000000 00000000 00000000 00000000 00000000 01100100
强转为int类型:00000000 00000000 00000000 01100100
强转自动将!前面!4个字节砍掉
byte g = (byte)300; g的值为44
int类型:00000000 00000000 00000001 00101100 为300
byte类型:00101100 为44
byte x=1;
byte y=127;
示例程序:
public class Test {
public static void main(String[] args) {
char c1 = 97;
System.out.println(c1);
char c2 = 'a'+1;//acsll码97+1=98,98代表b
System.out.println(c2);
char c3 = '0'+'1';//ascll码48+49=97,97代表a
System.out.println(c3);
}
}
运行结果:
a
b
a
五、运算符
运算符是对操作数的运算方式。
按照操作数分类有单目运算符、双目运算符、三目运算符。
按照功能来分有:
算术运算符:+、-、*、/、%、++、 --
关系运算符:>、>=、<、<=、==、!=
逻辑运算符:&、|、!、&&、||
赋值运算符:=、+=、-=、*=、/=、%=、^=、&=、|=、<<=、>>=
位运算符:&、|、^、~、<<、>>、>>>
条件运算符:布尔表达式?表达式 1:表达式 2
字符连接运算符:+
其他运算符:instance、new
5.1 算术运算符
算术运算符:
+ 加
- 减
* 乘
/ 除
% 求余取模
++ 自增
-- 自减
自增表达式:前缀a++,后缀++a
自减表达式:前缀b- -,后缀- -b
前缀表达式:先赋值,再自增减。
例如:
int a=1;
int b=a++;
先做b=a,再做a=a+1
b=1,a=2
后缀表达式:先自增减,再赋值
例如:
int a=1;
int b=++a;
先做a=a+1,再做b=a
b=2,a=2
程序示例:
public class Test {
public static void main(String[] args) {
int a = 7;
int b = 3;
System.out.println(a+b);
System.out.println(a-b);
System.out.println(a*b);
System.out.println(a/b);
System.out.println(a%b);
int x = 10;
System.out.println(x++);//先赋值,再自加
System.out.println(x);
int y = 5;
System.out.println(++x);//先自加,再赋值
System.out.println(x);
}
}
运算结果:
10
4
21
2
1
10
11
12
12
5.2 关系运算符
关系运算符:
>
>=
<
<=
==
!=
关系运算符的运算结果都是布尔类型
关系运算符中两个符号的中间不能有空格
示例程序:
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 5;
System.out.println(a>b);
System.out.println(a>=b);
System.out.println(a<b);
System.out.println(a<=b);
System.out.println(a==b);
System.out.println(a!=b);
}
}
运算结果:
true
true
false
false
false
true
5.3 逻辑运算符
逻辑运算符:
& 逻辑与:当且仅当两个操作数都为真,条件才为真
| 逻辑或:如果任何两个操作数任何一个为真,条件为真
! 逻辑非:如果条件为true,则逻辑非运算符将得到false
&& 短路与:当得到第一个操作为false时,结果必false,就不再判断第二个操作了
|| 短路或:当得到第一个操作为true时,结果必true,就不再判断第二个操作了
逻辑运算符两边必须为布尔类型,并且结果也是布尔类型
示例程序:
public class Test {
public static void main(String[] args) {
boolean a = true;
boolean b = false;
System.out.println(a&b);//如果把操作数改为int类型,那么&、|会被认为是位运算符
System.out.println(a|b);
System.out.println(!a);
System.out.println(a&&b);
System.out.println(a||b);
}
}
运算结果:
false
true
false
false
true
5.4 赋值运算符
赋值运算符:
基本赋值运算符:=
扩展赋值运算符:+=、-=、*=、/=、%=
扩展赋值运算符书写时两个符号间不能有空格
赋值运算符“=”:将右操作数的值赋给左侧操作数
扩展表达式“+=”:a+=b相当于a=a+b
x = x +1和x+=1相似,但是本质不同,不能等价。
示例程序:
public class Test {
public static void main(String[] args) {
byte x=100;
x = x + 1;//报错!x+1默认为int类型,转换为byte会有损失
System.out.println(x);
x+=1;//等价于x=(byte)(x+1)
System.out.println(x);
}
}
使用扩展赋值运算符时,不会改变运算结果数据类型。上文的x+=1中的x生来就是byte类型。
5.5 条件运算符
条件运算符:布尔表达式 ? 表达式1 : 表达式2
如果为true,执行表达式1,否则执行表达式2
条件运算符不是语句!
sex?'男':'女';//是错误的,不是Java语句
char c = sex ? '男' : '女';//这样是对的,前面的类型要对应,但是实际开发不这么写
System.out.println(sex?'男':'女');//这样也是对的,在这里的括号里什么类型都能放
示例程序:
public class Test {
public static void main(String[] args) {
boolean sex=true;
System.out.println(sex?'男':'女');
}
}
运算结果:
男
5.6 字符串连接运算符
字符串连接运算符:+
当+两边都是数字类型,做加法运算
当+两边有一边为字符型,做拼接操作。
遵循自左向右的顺序执行.
示例程序:
public class Test {
public static void main(String[] args) {
System.out.println(100+200+"200");
System.out.println(300+200);
System.out.println(300+(100+"200"));
System.out.println(100+"+"+200+"="+100+200);//遵循自左向右的顺序,除非有小括号
System.out.println(100+"+"+200+"="+(100+200));
}
}
运算结果:
300200
500
300100200
100+200=100200
100+200=300
5.7 接受键盘输入
public class Test {
public static void main(String[] args) {
//创建一个键盘扫描器
java.util.Scanner s = new java.util.Scanner(System.in);
//从键盘接收一个int类型的数据
int a = s.nextInt();
System.out.println(a);
//从键盘接收一个字符串类型的数据
String b = s.next();
System.out.println(b);
}
}
运行结果:
10
10
你好
你好
导包形式接收键盘数据
示例程序:
import java.util.Scanner;
public class Test1 {
public static void main(String[] args) {
//创建键盘扫描器对象
Scanner s = new Scanner(System.in);
//从键盘接收一个int类型的数据
int a = s.nextInt();
System.out.println(a);
}
}
运算结果:
1
1
六、控制语句
控制语句是用来对程序流程的选择、循环、转向和返回等进行控制的。
Java中共有四类8种控制语句:
选择语句:if、switch
循环语句:for、while、do...while
转向语句:break、continue
返回语句:return
6.1 选择语句
选择语句又称分支语句,它用来对给定的条件进行判断,从而决定执行两个或多个分支中的哪一支。
6.1.1 if语句
if语句的语法格式总结为四种:
//第一种
if(布尔表达式){
java语句;
}
//第二种
if(布尔表达式){
java语句;
} else {
java语句;
}
//第三种
if(布尔表达式){
java语句;
}else if(布尔表达式){
java语句;
} else {
java语句;
}
//第四种
if(布尔表达式){
java语句;
}else if(布尔表达式){
java语句;
} else if(布尔表达式){
java语句;
}
if语句在任何情况下都只有一个分支进行。
示例程序:
public class Test {
public static void main(String[] args) {
boolean a = true;
String b = "";
if(a){
b = "男";
} else {
b = "女";
}
System.out.println(b);
}
}
运行结果:
男
6.1.2 switch语句
switch的语法格式为:
switch(int/String类型){
case int/String类型:
java语句;
break;//非必须
case int/String类型:
java语句;
break;//非必须
default://非必须
java语句;
}
switch 虽然只能探测 int/String 类型,但是也可以将 byte,short,char 类型放到小括号当中,因为这些类型会自动转换成 int 类型
switch 语句当中 case 是可以进行合并的。
示例程序:
import java.util.Scanner;
public class Test {
public static void main(String[] args) {//
Scanner scan = new Scanner(System.in);
System.out.print("请输入[1-7]的整数数字:");
int dayOfWeek = scan.nextInt();
switch(dayOfWeek){
case 1:
case 2:
case 3:
case 4:
case 5:
System.out.println("今天是工作日哦!");
break;
case 6:
case 7:
System.out.println("今天是休息日哦!");
break;
default :
System.out.println("对不起,您的输入有误");
}
}
}
运算结果:
请输入[1-7]的整数数字:4
今天是工作日哦!
6.2 循环语句
6.2.1 for语句
for语句的语法结构:
for(初始循环表达式;布尔表达式;更新表达式){
循环体;
}
for语句的执行顺序是:
1.初始表达式
2.布尔表达式
3.循环体
4.更新表达式
示例程序:
public class Test {
public static void main(String[] args) {//计算1~100所有奇数之和
int sum = 0;
for(int i = 1; i <= 100; i++){
if(i % 2 != 0){
sum += i;
}
}
System.out.println("sum = " + sum);
}
}
运行结果:
sum = 2500
6.2.2 while语句
while语句的语法结构:
while(布尔表达式){
循环体;
}
示例程序:
public class Test {
public static void main(String[] args) {//计算1~100所有偶数之和
int sum = 0;
int i = 0;
while(i <= 100){
if(i % 2 == 0){
sum += i;
}
i++;
}
System.out.println("sum = " + sum);
}
}
运算结果:
sum = 2550
6.2.3 do…while语句
do…while语句的语法结构:
do{
循环体;
}while(布尔表达式);
do…while 循环至少会执行一次。
示例程序:
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String username;
String password;
do{
System.out.print("用户名:");
username = scanner.next();
System.out.print("密码:");
password = scanner.next();
}while(!username.equals("admin") || !password.equals("123"));
System.out.println("登录成功,欢迎" + username + "回来!");
}
}
运行结果:
用户名:1
密码:1
用户名:admin
密码:123
登录成功,欢迎admin回来!
6.3 转向语句
转向语句用于实现循环执行过程中程序流程的流转。在Java中转向语句有break和continue。
6.3.1 break语句
break语句可以用于switch中,在循环中,break用来终止跳出循环。
示例程序:
public class Test {
public static void main(String[] args) {
for(int i = 1; i <= 10; i++){
if(i == 5){
break;
}
System.out.println("break : i = " + i);
}
}
}
运行结果:
break : i = 1
break : i = 2
break : i = 3
break : i = 4
6.3.2 continue语句
continue 适用于任何循环控制结构中。作用是让程序立刻跳到下一次循环。
在 for 循环中,continue 语句使程序跳到更新语句。
在 while 或者 do…while 循环中,程序立即跳转到布尔表达式的判断语句。
示例程序:
public class Test1 {
public static void main(String[] args) {
for(int i = 1; i <= 10; i++){
if(i == 5){
continue;
}
System.out.println("continue : i = " + i);
}
}
}
运行结果:
continue : i = 1
continue : i = 2
continue : i = 3
continue : i = 4
continue : i = 6
continue : i = 7
continue : i = 8
continue : i = 9
continue : i = 10
七、方法
方法是指一段可以完成某个特定功能的并且可以被重复使用的代码片段。
7.1 方法的声明
在使用方法前必须对其声明。
方法的声明:
[修饰符列表] 返回值列表 方法名(形式参数列表){
方法体;
}
例如:
public static void sumInt(int a,int b){
int c=a+b;
System.out.println(a+"+"+b+"="+c);
}
/*
*public static 是修饰符列表
*void 是返回值
*sumInt 是方法名
*(int a,int b)是形式参数,每一个形参都是局部变量
*一对大括号括起来的是方法体
*/
相关规则:
[修饰符列表],此选项是可选项,非必须的。
返回值,类型可以是Java中任意一种数据类型。
如果方法返回类型不为空,那必须在方法体最后加“return 值”,且要考虑到所有程序出口的return 值!
返回值类型为void,在方法体最后不能加“return 值”,但是可以加“return;”。
return表示结束当前方法。执行return后的本方法内语句是无法执行的。
方法名,必须为合法的标识符,遵守驼峰命名,例:getName。
形式参数,个数为0~N。多个参数用逗号隔开。
7.2 方法的调用
方法声明完后,需要手动调用。
调用方法的语法格式(前提:方法被static修饰)是“类名.方法名(实际参数列表);”。
例如:
public class MethodTest{
public static void main(String[] args){
sumInt(100,200);//100和200为实际参数,类名可省略
Other.doOther();//类名不可省略!
}
public static void sumInt(int x,int y){//int x和int y为形式参数
System.out.println(x+"+"+y+"="+(x+y));
}
}
public class Other{
public static void doOther(){
System.out.println("Other");
}
}
相关规则:
实际参数和形式参数的个数和类型必须一一对应。(也可能存在自动类型转换,小转大可以)
在同一个类中的方法调用时,可以省略类名。例如:MethodTest(100,200);
7.3 方法的重载
方法重载是指同一个类中定义多个同名的方法,但是每个方法具有不同的个数、类型或顺序的参数。
继承父类的子类中有和父类中方法同名但参数不同的也是方法重载。
方法重载常用于创建完成一组任务相似,但参数个数、类型或顺序不同的方法。
例如:
public class U {//以下均为方法p的方法重载
public static void p(){
System.out.println();
}
public static void p(int x){
System.out.println();
}
public static void p(int x,long y){
System.out.println();
}
public static void p(long x,int y){
System.out.println();
}
}
相关规则:
在调用时通过传递给他们不同个数、类型或顺序的实参来判断使用哪个方法。
方法重载与修饰词列表和返回值类型无关。
构成重载的条件:
在同一个类中
方法名相同
参数列表不同:个数不同 或 类型不同 或 顺序不同
7.4 方法的递归
方法调用自身叫做方法的递归调用。
使用递归的方式计算5的阶乘:
public class Test {
public static void main(String []args){
int n = 5;
int result = f(n);
System.out.println(result);
}
public static int f(int n){
if (n == 1){
return 1;
}
return n*f(n-1);
}
}
八、面向对象编程
8.1类和对象
类是现实世界中具有共同特征的事物进行抽象形成的模板或概念。
对象是实际存在的个体。
通过类可以创建对象,对象也被称为实例,创建对象的过程也可以叫实例化。
对对象的特征进行抽取形成类,这个过程叫抽象。
8.1.1 类的定义
类由属性和方法构成。属性描述的是状态,方法描述的是行为动作。
对象的属性以变量的形式存在,这里的变量就是成员变量中的实例(成员)变量。
在Java中,凡是使用class创建的类,都属于引用数据类型。
定义类的语法格式:
[修饰符] class 类名 {
类体;//类体=属性+方法
}
例如:
public class Student{//定义一个学生类
//学号
int no;
//姓名
String name;
//年龄
int age;
//性别
boolean sex;
}
8.1.2 对象的创建和使用
8.1.2.1对象的创建
类定义后,就可以使用类来创建对象了。一个类可以创建多个对象。
创建对象的语法格式:
new 类名();
例如:
public class StudentTest{
public static void main(String[] args){
//创建一个学生对象
new Student();
//再创建两个学生对象,用变量接收一下
Student s1 = new Student();
Student s2 = new Student();
}
}
8.1.2.2 对象的使用
public class StudentTest{
public static void main(String[] args){
//创建一个学生对象
Student s = new Student();
System.out.println("学号:"+s.no);
System.out.println("姓名:"+s.name);
System.out.println("年龄:"+s.age);
System.out.println("性别:"+s.sex);
}
}
运行结果:
学号:0
姓名:null
年龄:0
性别:false
上面的结果表明:如果实例变量没有被手动赋值,在创建对象时,程序会给实例变量默认赋值。
默认值参考如下:
8.1.2.3 构造方法
构造方法是类中特殊的方法。
通过调用构造方法来完成对象的创建以及对象属性的初始化操作
构造方法定义的语法格式:
[修饰符列表] 构造方法名(形式参数列表){
构造方法体;
}
相关规则:
构造方法名和类名一致
构造方法不需要写返回值类型(包括void)
一个类中可以定义多个构造方法,即允许方法重载
构造方法的返回值是当前类的类型
当一个类没有显示定义任何构造方法时,程序会默认提供无参构造方法
一旦有程序员自己定义的构造方法后,程序将不再默认提供无参构造方法
例如:
public class Data {
int year;
int month;
int day;
public Data(){
System.out.println(year+","+month+","+day+" 无参构造");
}
public Data(int year){
System.out.println(year+","+month+","+day+" 带year构造");
}
public Data(int year,int month){
System.out.println(year+","+month+","+day+" 带year,month构造");
}
public Data(int year,int month,int day){
System.out.println(year+","+month+","+day+" 带year,month,day构造");
}
}
public class DataTest {
public static void main(String[] args){
new Data();
new Data(2020);//没有手动赋值的属性,程序依然会自动默认
new Data(2020,8);
new Data(2020,8,23);
}
}
运行结果:
0,0,0 无参构造
2020,0,0 带year构造
2020,8,0 带year,month构造
2020,8,23 带year,month,day构造
8.1.2.4 空指针异常
例如:
public class Pointer{
int i;
}
public class PointerTest{
public static void main(String[] args){
Pointer p=new Pointer();
System.out.println(p.i);
p=Null;//造成空指针异常,报错!
System.out.println(p.i);
}
}
运行结果:
0
Exception in thread "main" java.lang.NullPointerException
at PointerTest.main(PointerTest.java:6)
java.lang.NullPointerException称为空指针异常。
当p=null时,p引用就无法找到堆内存中的Java对象了,程序就无法正常访问。
8.1.2.5 方法调用时参数的传递问题
当参数为基本数据类型时,参数传递的是数据10:
public class Test1 {
public static void main(String[] args){
int x = 100;
int y = x;
int z = 10;
add(z);
System.out.println("main:"+z);
}
public static void add(int i){
i++;
System.out.println("add:"+i);
}
}
运行结果:
add:11
main:10
当参数为引用数据类型时,参数传递的是内存地址:
public class Test2 {
public static void main(String[] args){
Person p = new Person();
Person p1 = new Person();
p.age = 10;
p1 = p;
add(p1);
System.out.println("main:"+p.age);
}
public static void add(Person p2){
p2.age++;
System.out.println("add:"+p2.age);
}
}
class Person{
int age;
}
运行结果:
add:11
main:11
两个程序的区别就是参数的数据类型不同,本质上10和p代表的内存地址都是值。
前者把x的值“10”复制一份给了y,而后者把p的内存地址复制了一份给了p1,因此导致p和p1指向的地址是同一个Java对象,通过任何一个引用去访问堆内存中的对象,对象内存都会受到影响。
九、封装
封装是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能的隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。
封装降低了程序的耦合度,提高了系统的扩展性以及重用性,可以隐藏内部实现细节,外部看不到内部复杂结构,对外只提供简单的安全的操作入口,更安全。
9.1如何封装
可以使用Java语言中的private修饰符,private表示私有的,私有的数据只能在本类中访问。
例如:
public class MobilePhone {
private double voltage;
}
public class MobilePhoneTest {
public static void main(String[] args){
MobilePhone phone = new MobilePhone();
phone.voltage = 3.7;
System.out.println(phone.voltage);
phone.voltage = 100;
System.out.println(phone.voltage);
}
}
运行结果:
java: voltage 在 MobilePhone 中是 private 访问控制
private修饰的数据无法在外部程序直接访问。
9.2 set和get方法
set和get方法是用来提供访问入口的。
set和get方法访问的都是某个具体对象的属性,不同对象调用set和get方法获取的属性值不同,set和get方法必须有对象的存在才能调用。
set方法是用来修改属性值的。一般在set方法内部编写安全控制程序。
get方法是用来读取属性值的。不需要参数。返回值类型是该属性所属类型。
两者都不能static修饰词修饰,这样的方法称为实例方法。实例方法必须用引用的方式调用。
被static关键字修饰的方法直接采用“类名.方法”来调用。
set方法定义的语法格式:
public void set+属性名首字母大写(一个参数){
xxx = 参数;
}
get方法定义的语法格式:
public 返回值类型 get+属性名首字母大写(无参数){
return xxx;
}
例如:
public class MobilePhone {
private double voltage;
public MobilePhone(){
}
public void setVoltage(double _voltage) {//set方法,只能用来修改属性值
if(_voltage<3||_voltage>5){//用来设置门槛,保证修改安全
throw new RuntimeException("电压输入错误");
}
voltage=_voltage;
}
public double getVoltage() {//get方法,只能用来读取数据
return voltage;
}
}
public class MobilePhoneTest {
public static void main(String[] args){
MobilePhone phone = new MobilePhone();
phone.setVoltage(3.7);
System.out.println(phone.getVoltage());
phone.setVoltage(100);
System.out.println(phone.getVoltage());
}
}
运行结果:
3.7
Exception in thread "main" java.lang.RuntimeException: 电压输入错误
at MobilePhone.setVoltage(MobilePhone.java:10)
at MobilePhoneTest.main(MobilePhoneTest.java:6)
十、this和static
10.1 this
this是Java中的关键字,指“当前对象”。this这个引用保存了当前对象的内存地址指向自身。
this在实例方法以及构造方法中,语法格式分别为“this.”和“this(实际参数列表)”。
this不能出现在有静态方法中,只能出现在实例方法中。
例如:
public class ThisTest01 {
public static void main(String[] args){
Customer c1 = new Customer("张三");
c1.shopping();
Customer c2 = new Customer("李四");
c2.shopping();
}
}
class Customer{
String name;
public Customer(){
}
public Customer(String s){
name=s;
}
public void shopping(){
System.out.println(this.name + "正在购物!");//name前面可以隐藏(this.)
}
}
this有一种情况是不能省略的。
例如:
class Customer{
String name;
public Customer(){
}
public Customer(String name){
//name = name;//如果形参也命名为name,系统会遵循就近原则,所以name = name中,两个name都为形参name
this.name = name;//这是this的第二个用法,前者是本类中实例变量name,后者是形参name,此处不能省略this
}
this(实际参数列表)还可以用在构造方法里
例如:
public class Date {
private int year;
private int month;
private int day;
public Date(){
this.year = 1970;
this.month = 1;
this.day = 1;
}
public Date(int year,int month,int day){
this.year = year;
this.month = month;
this.day = day;
}
public int getYear(){
return year;
}
public void setYear(int year){
this.year = year;
}
public int getMonth(){
return month;
}
public void setMonth(int month){
this.month = month;
}
public int getDay(){
return day;
}
public void setDay(int day){
this.day = day;
}
}
public class DateTest {
public static void main(String[] args){
Date d1 = new Date();
System.out.println(d1.getYear() + "年 " + d1.getMonth() + "月 " + d1.getDay() + "日");
Date d2 = new Date(2008,8,8);
System.out.println(d2.getYear() + "年 " + d2.getMonth() + "月 " + d2.getDay() + "日");
}
}
在上面的无参构造方法和有参构造方法中,代码基本相同,没有复用,这时可以采用this()的方式。但是!this()语法只能出现在构造方法的第一行,因此在一个构造方法中this()只能出现一次。
例如:
public Date(){
this(1970,1,1);
}
public Date(int year,int month,int day){
this.year = year;
this.month = month;
this.day = day;
}
10.2 static
static是Java中的关键字,表示“静态的”,可以用来修饰变量、方法、代码块。
在Java中凡是用static修饰的都是类相关的。
实例变量必须先创建对象才能访问,通过“引用.”访问。实例变量在构造方法执行时才初始化。
静态变量访问时不需要创建对象,直接通过“类名.”访问。静态变量在类加载时就初始化。
10.2.1 static变量
当一个类中所有对象的某个属性值不会随着对象的变化而变化时,我们可以把这个属性定义为静态属性。
静态属性不需要创建对象,在类加载时初始化,直接通过“类名.”来访问。引用.的形式也可以,但是不建议!!!
用"引用."的形式去访问,实际上此程序还是通过“类.”的方式访问的,不会出现空指针异常。
例如:
public class Man{
int idCard;
static boolean sex = true;
}
public class ManTest{
public static void main(String[] args){
System.out.println(Man.sex);
}
}
10.2.2 static方法
static方法一般用在工具类。
静态方法不需要创建对象,直接通过“类名.”访问。也可以用使用“引用.”来访问,但是不建议!!!
方法描述的是行为动作,当某个行为动作需要对象的参与,这个方法应该定义为实例方法。
当方法体中直接访问了实例变量,那这个方法一定是实例方法。
为了简化输出语句编写的工具类
例如:
public class U {
public static void p(int data){
System.out.println(data);
}
public static void p(long data){
System.out.println(data);
}
public static void p(float data){
System.out.println(data);
}
public static void p(double data){
System.out.println(data);
}
public static void p(boolean data){
System.out.println(data);
}
public static void p(char data){
System.out.println(data);
}
public static void p(String data){
System.out.println(data);
}
}
public class HelloWorld {
public static void main(String[] args) {
U.p("Hello World!");
U.p(true);
U.p(3.14);
U.p('A');
}
}
10.2.3 静态代码块
静态代码块在类加载时执行,在main方法执行前,并且只执行一次。
静态代码块的语法格式:
类{
static{
java语句;
}
}
静态代码块遵循自上而下的顺序执行,有时类体内代码是有执行顺序的。
例如:
public static StaticTest01{
static int i = 100;//放在此处正确
static{
System.out.println(i);
}
static int i = 100;//放在此处访问不到
}
静态代码块是访问不到后面的i变量的,必须将变量放在前面。
10.2.4 实例代码块
实例语句块在构造方法执行前自动执行。
实例代码块的语法格式:
类{
{
java语句;
}
}
静态代码块和实例代码块的执行顺序
例如:
public class CodeOrder {
static{
System.out.println("A");
}
public static void main(String[] args){
System.out.println("Y");
new CodeOrder();
System.out.println("Z");
}
public CodeOrder(){
System.out.println("B");
}
{
System.out.println("C");
}
static{
System.out.println("X");
}
}
运行结果:
A
X
Y
C
B
Z
十一、继承
子类继承父类的特征和行为,使子类对象(实例)具有父类的属性。
继承可以看作将父类中的属性方法(除构造方法外)直接复制到子类中。
在不同的类中也可能有共同的特征和动作,可以把这些共同的特征和动作放在一个通用类中,让其他类共享。
Java中继承的语法格式:
class 类名 extends 父类名{
类体;
}
例如:
public class Account { //银行账户类
//账号
private String actno; //余额
private double balance;
//账号和余额的 set 和 get 方法
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
public class CreditAccount extends Account{ //信用账户类
//信誉度(特有属性)
private double credit;
//信誉度的 set 和 get 方法
public double getCredit() {
return credit;
}
public void setCredit(double credit) {
this.credit = credit;
}
}
public class AccountTest {
public static void main(String[] args) {
CreditAccount act = new CreditAccount(); act.setActno("111111111");
act.setBalance(9000.0);
System.out.println(act.getActno()+"信用账户,余额"+act.getBanlance()+"元");
}
}
通过继承解决了代码复用的问题。
继承的相关特性:
B 类继承 A类,则称 A类为超类(superclass)、父类、基类,B 类则称为子类(subclass)、 派生类、扩展类。
java 中的继承只支持单继承,不支持多继承。但可以通过间接继承,达到多继承的效果。
子类继承父类,构造方法和被 private 修饰的数据不能继承。
java 中的类没有显示的继承任何类,则默认继承 根类Object 。
继承会导致耦合度高。
十二、方法的覆盖和多态
12.1 方法覆盖
当从父类继承过来的方法无法满足当前子类业务需求时,需要将父类中继承过来的方法进行覆盖。
当该方法被重写后,子类对象一定会调用重写后的方法。
例如:
public class Animal {
public void move(){
System.out.println("动物在移动!");
}
}
public class Cat extends Animal{
public void move(){
System.out.println("猫在走猫步!");
}
public void catchMouse(){
System.out.println("猫在抓老鼠");
}
}
public class Bird extends Animal{
public void move(){
System.out.println("鸟儿在飞翔!");
}
}
public class Test {
public static void main(String[] args){
Cat cat = new Cat();
cat.move();
cat.catchMouse();
Bird bird = new Bird();
bird.move();
}
}
运行结果:
猫在走猫步!
猫在抓老鼠
鸟儿在飞翔!
相关规则:
方法覆盖只能用在具有继承关系的父子类之间。
覆盖后的方法与原方法具有相同的返回值,方法名和相同的形式参数列表(直接复制到子类就行)。
私有的方法不能被继承,所以不能被覆盖。
构造方法不能被继承,所以不能被覆盖。
覆盖后的方法不能比原方法有更低的访问权限,只能更高(封闭)。
覆盖后的方法不能比原方法抛出更多的异常,可以相同或少。
方法覆盖与属性无关。
静态方法不存在覆盖。(可以但意义不大)
12.2 多态
多态就是同一个行为发生在不同的对象上会产生不同的效果。
Java中允许两种语法出现:向上转型和向下转型。
无论是向上转型还是向下转型,两个类必须有继承关系。
12.2.1 向上转型
向上转型是子类型转为父类型。又称为自动转换类型。
例如:
public class PolyTest01 {
public static void main(String[] args) { //创建 Animal 对象
Animal a1 = new Cat();
a1.move();
Animal a2 = new Bird();
a2.move();
}
}
class Animal {
public void move(){
System.out.println("Animal move!");
}
}
class Cat extends Animal{
//方法覆盖
public void move(){
System.out.println("走猫步!");
}
//子类特有
public void catchMouse(){
System.out.println("抓老鼠!");
}
}
class Bird extends Animal { //方法覆盖
public void move() {
System.out.println("鸟儿在飞翔!");
}
//子类特有
public void sing() {
System.out.println("鸟儿在歌唱!");
}
}
运行结果:
走猫步!
鸟儿在飞翔!
Java程序运行包括编译和运行:
在编译阶段编译器只知道a1的数据类型是Animal,编译器会去Animal.class字节码中查找move()方法,发现方法后,将move()方法绑定到a1引用上,编译通过。这叫静态绑定。
在运行阶段实际上在堆内存中存的是Cat类型,所以运行时会自动执行Cat类中的move()方法。这叫动态绑定。
12.2.2 向下转型
向下转型是父类型转为子类型。又称为强制转换类型。
当需要访问子类对象中的特有方法时,需要向下转型。
例如:
public class PolyTest02 {
public static void main(String[] args) {
Animal a = new Cat();
a.catchMouse();
}
}
运行结果:
java: 找不到符号
符号: 方法 catchMouse()
位置: 类型为Animal的变量 a
程序报错,在编译时编译器只知道a变量的数据类型是Animal,只会去Animal.class字节码文件中寻找catchMouse()方法,但是没找到,编译错误。
代码应改为:
public class PolyTest03 {
public static void main(String[] args) {
Animal a = new Cat();
Cat c = (Cat)a;//强制转换类型
c.catchMouse();
}
}
向下转型是有风险的例如:
public class PolyTest04 {
public static void main(String[] args) {
Animal a = new Bird();
Cat c = (Cat)a;
}
}
运行结果:
Exception in thread "main" java.lang.ClassCastException: class Bird cannot be cast to class Cat (Bird and Cat are in unnamed module of loader 'app')
at PolyTest04.main(PolyTest04.java:4)
编译可以通过,因为编译器只知道在a变量是Animal且存在继承关系,语法上没有错误,i编译通过。运行没有通过,因为在运行时堆内存中创建了一个Bird对象,Cat和Bird是没有继承关系的,所以强转失败。
12.2.3 instanceof运算符
instanceof运算符的结果是布尔类型。
例如:
(c instanceof Cat)结果是true,说明在运行阶段c引用指向的对象是Cat类型
在向下转型时,需要用instanceof来判断
例如:
public class Test05 {
public static void main(String[] args) {
Animal a = new Bird();
if(a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse();
}
}
}
12.2.4多态在开发中的应用
例如:
public class PolyTest {
public static void main(String[] args){
Dog dog = new Dog("二哈");
Bird bird = new Bird("小鸟儿");
Master master = new Master();
master.feed(dog);
master.feed(bird);
}
}
class Master{
public void feed(Pet pet){
System.out.println("主人开始喂食");
pet.eat();
System.out.println("主人喂食完毕");
}
}
class Pet{
String name;
public void eat(){
}
}
class Dog extends Pet{
public Dog(String name){
this.name = name;
}
public void eat(){
System.out.println(this.name+"在吃骨头");
}
}
class Bird extends Pet{
public Bird(String name){
this.name = name;
}
public void eat(){
System.out.println(this.name+"在唱歌");
}
}
运行结果:
主人开始喂食
二哈在吃骨头
主人喂食完毕
主人开始喂食
小鸟儿在唱歌
主人喂食完毕
十三、super关键字
super代表当前对象中从父类继承过来的那部分特征。
super可以出现在实例方法和构造方法中。
super的语法为:“super”、“super()”。
super不能使用在静态方法中。
super. 大部分情况下不能省略,例如:父类子类用同名属性,或同名方法。想在子类中访问父类的不能省略
super()只能从出现在构造方法第一行,通过当前方法去调用父类中的构造方法,目的是创建子类对象时,先初始化父类特征。
super和this不能共存共存。
当一个构造方法第一行:
既没有this()有没有super(),默认会有一个super();
表示通过当前子类构造方法调用父类的无参构造方法
super不是引用,也不保存内存地址,也不指向任何对象。
例如:
public class SuperTest01 {
public static void main(String[] args){
new B();
}
}
class A{
public A(){
System.out.println("A类型的无参构造方法");
}
public A(int i){
System.out.println("A类型的有参构造方法");
}
}
class B extends A{
public B(){
//super();如果此处没写任何东西,默认会有一个super();去调用父类的无参构造方法
//super(100);如果此处写了super(100);那只会调用父类的有参构造方法
System.out.println("B类型的无参构造方法");
}
}
运行结果:
A类型的无参构造方法
B类型的无参构造方法
例题:
public class SuperTest02 {
public static void main(String[] args){
new C();
}
}
class A{
public A(){
System.out.println("1");
}
}
class B extends A{
public B(){
System.out.println("2");
}
public B(String name){
System.out.println("3");
}
}
class C extends B{
public C(){
this("zhangsan");
System.out.println("4");
}
public C(String name){
this(name,20);
System.out.println("5");
}
public C(String name,int age){
super(name);
System.out.println("6");
}
}
运行结果:
1
3
6
5
4
super(实参列表)的适当使用:
public class SuperTest03 {
public static void main(String[] args){
CreditAccount ca1 = new CreditAccount();
System.out.println(ca1.getActno()+","+ca1.getBalance()+","+ca1.getCredit());
CreditAccount ca2 = new CreditAccount("1111",1000.0,0.99);
System.out.println(ca2.getActno()+","+ca2.getBalance()+","+ca2.getCredit());
}
}
class Account{
//账户号
private String actno;
//余额
private double balance;
public Account(){
}
public Account(String actno,double balance) {
this.actno = actno;
this.balance = balance;
}
public void setActno(){
this.actno = actno;
}
public String getActno(){
return actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public double getBalance(){
return balance;
}
}
class CreditAccount extends Account{
//属性:信誉度,子类特有的特征
private double credit;
public CreditAccount(){
}
public CreditAccount(String actno,double balance,double credit){
//this.actno = actno;
//this.balance = balance;
// 这两条的属性是父类中的私有属性,无法访问
//可以通过super()去调用父类中的有参构造方法
super(actno,balance);
this.credit = credit;
}
public void setCredit(double credit){
this.credit = credit;
}
public double getCredit(){
return credit;
}
}
运行结果:
null,0.0,0.0
1111,1000.0,0.99
super什么时候不能省略:当父类子类有同名属性时
public class SuperTest04 {
public static void main(String[] args){
Vip v = new Vip("张三");
v.shopping();
}
}
class Customer{
String name;
public Customer(){
}
public Customer(String name){
super();
this.name = name;
}
}
class Vip extends Customer{
//和父类有同名属性
String name;
public Vip(){
}
public Vip(String name){
super(name);
}
public void shopping(){
System.out.println(this.name + "正在购物");
System.out.println(super.name + "正在购物");
System.out.println(name + "正在购物");
}
}
运行结果:
null正在购物
张三正在购物
null正在购物
super不仅可以访问父类属性,也可以访问父类方法:
public class SuperTest05 {
public static void main(){
Cat c = new Cat();
c.yiDong();
}
}
class Animal{
public void move(){
System.out.println("Animal move");
}
}
class Cat extends Animal{
public void move(){
System.out.println("CAt move");
}
public void yiDong(){
this.move();
move();
super.move();
}
}
运行结果:
Cat move
Cat move
Animal move