1.注释,标识符,关键字
1.1注释
单行注释:// 注释内容(用的最多)
多行注释:/* 注释内容*/(不推荐)
文档注释: /** 文档注释 */(常见于方法和类之上描述方法和类的作用),可以被javadoc工具解析,生成一套以网页文件形式体现的程序说明文档
注释快捷键:
单行注释:Ctrl+/
多行注释:Ctrl+Shift+/
文档注释:/**+回车
取消注释快捷键:单行注释和多行注释与注释快捷键相同,文档注释选中删除就行
注意:
1. 多行注释不能嵌套使用
2. 不论是单行还是多行注释,都不参与编译,即编译之后生成的.class文件中不包含注释信息。
注释规范:
1. 内容准确: 注释内容要和代码一致, 匹配, 并在代码修改时及时更新.
2. 篇幅合理: 注释既不应该太精简, 也不应该长篇大论.
3. 使用中文: 一般中国公司都要求使用中文写注释, 外企另当别论.
4. 积极向上: 注释中不要包含负能量(例如 领导 SB 等).
1.2标识符
硬性规则:
标识符中可以包含:字母、数字以及 下划线和 $ 符号等等。 注意:标识符不能以数字开头,也不能是关键字,且严格区分大小写。
软性建议:
类名:每个单词的首字母大写(大驼峰)
方法名:首字母小写,后面每个单词的首字母大写(小驼峰)
变量名:与方法名规则相同
定义标识符时尽量少用$符,能不用就不用,避免不必要的麻烦
1.3关键字
2.数据类型与变量
数据类型分为8种基本数据类型和引用数据类型
8种基本数据类型:
注意:
1.不论是在16位系统还是32位系统,int都占用4个字节,long都占8个字节
2.整形和浮点型都是带有符号的 整型默认为int型,浮点型默认为double
3.字符串属于引用类型
变量:
语法格式:
数据类型 变量名 = 初始值;
注意事项:
1. int不论在何种系统下都是4个字节
2. 如果没有合适的初始值,可以设置为0
3. 在给变量设置初始值时,值不能超过基本数据类型的表示范围,否则会导致溢出
4. 变量在使用之前必须要赋初值,否则编译报错
5. int的包装类型为 Integer,char的包装类型为Character,其余的包装类都是首字母大写
6.float类型的数据,初始化时,需要加上f,与double类型的数据区分
7.boolean类型的数据只能是true或false,不存在0表示假,非0表示真
8. 不同数字类型的变量之间赋值, 表示范围更小的类型能隐式转换成范围较大的类型
9. 如果需要把范围大的类型赋值给范围小的, 需要强制类型转换, 但是可能精度丢失
10. 将一个字面值常量进行赋值的时候, Java 会自动针对数字范围进行检查
11. 强制类型转换不一定能成功,不相干的类型不能互相转换
12.a,b,c 都是 byte, 但是计算 a + b 会先将 a 和 b 都提升成 int, 再进行计算, 得到的结果也是 int, 将结果赋给c会报错
13.byte 和 short 这种低于 4 个字节的类型, 会先提升成 int, 再参与计算.
3.运算符
1.基本四则运算符:+ - * / %
做除法和取模时,右操作数不能为0
% 不仅可以对整型取模,也可以对double类型取模,但是没有意义,一般都是对整型取模的
两侧操作数类型不一致时,向类型大的提升
2.增量运算符 += -= *= /= %=
只有变量才能使用该运算符,常量不能使用。
3. 自增/自减运算符 ++ --
注意:
如果单独使用,【前置++】和【后置++】没有任何区别 如果混合使用,【前置++】先+1,然后使用变量+1之后的值,【后置++】先使用变量原来的值,表达式 结束时给变量+1 只有变量才能使用自增/自减运算符,常量不能使用,因为常量不允许被修改
4. 关系运算符
关系运算符主要有六个: == != < > = ,其计算结果是 true 或者 false 。
5. 逻辑运算符
逻辑与 &&:一假则假
逻辑或 ||:一真则真
逻辑非 !:真假调换
短路求值:
System.out.println(10 > 20 && 10 / 0 == 0); // 打印 false System.out.println(10 < 20 || 10 / 0 == 0); // 打印 true
对于 && , 如果左侧表达式值为 false, 则表达式结果一定是 false, 无需计算右侧表达式.
对于 ||, 如果左侧表达式值为 true, 则表达式结果一定是 true, 无需计算右侧表达式.
& 和 | 如果表达式结果为 boolean 时, 也表示逻辑运算. 但与 && || 相比, 它们不支持短路求值.
6.位运算符
按位与 &: 如果两个二进制位都是 1, 则结果为 1, 否则结果为 0.
按位或 |: 如果两个二进制位都是 0, 则结果为 0, 否则结果为 1.
按位取反 ~: 如果该位为 0 则转为 1, 如果该位为 1 则转为 0.
按位异或 ^: 如果两个数字的二进制位相同, 则结果为 0, 相异则结果为 1.
7. 移位运算
左移<<: 最左侧位不要了, 最右侧补 0.
右移 >>: 最右侧位不要了, 最左侧补符号位(正数补0, 负数补1)
无符号右移 >>>: 最右侧位不要了, 最左侧补 0.
注意:
1. 左移 1 位, 相当于原数字 * 2. 左移 N 位, 相当于原数字 * 2 的N次方.
2. 右移 1 位, 相当于原数字 / 2. 右移 N 位, 相当于原数字 / 2 的N次方.
3. 由于计算机计算移位效率高于计算乘除, 当某个代码正好乘除 2 的N次方的时候可以用移位运算代替.
4. 移动负数位或者移位位数过大都没有意义.
8.条件运算符
表达式1 ? 表达式2 : 表达式3
当 表达式1 的值为 true 时, 整个表达式的值为 表达式2 的值; 当 表达式1 的值为 false 时, 整个表达式的值为 表达式3 的值.
4.逻辑控制
顺序结构:顺序结构比较简单,按照代码书写的顺序一行一行执行。
分支结构:
if-else语句:
if(布尔表达式){ //语句 }else if(布尔表达式){ //语句 }else{ //语句 }
1.如果布尔表达式结果为true,则执行if中语句,否则执行else中语句。
2.if可以单独使用,但else if与else必须与if连用(可以是if -else,if-else if,if-else if- else)
3.if / else 语句中可以不加 大括号 . 但是也可以写语句(只能写一条语句). 此时 else 是和最接近的 if 匹配. 但是实际开发中我们 不建议 这么写. 最好加上大括号.
switch 语句:
switch(表达式){case 常量值1:{语句1;[break;]}case 常量值2:{语句2;[break;] }default:{内容都不满足时执行语句;[break;]} }
执行流程:
1. 先计算表达式的值
2. 和case依次比较,一旦有响应的匹配就执行该项下的语句,直到遇到break时结束
3. 当表达式的值没有与所列项匹配时,执行default
【注意事项】
多个case后的常量值不可以重复 switch的括号内只能是以下类型的表达式:
基本类型:byte、char、short、int,注意不能是long类型
引用类型:String常量串、枚举类型
循环结构:
while循环:
while(循环条件){循环语句; }
循环条件为 true, 则执行循环语句; 否则结束循环.
注意事项
1. 和 if 类似, while 下面的语句可以不写 { } , 但是不写的时候只能支持一条语句. 建议还是加上 { }
2. 和 if 类似, while 后面的 { 建议和 while 写在同一行.
3. 和 if 类似, while 后面不要多写 分号, 否则可能导致循环不能正确执行.
break:
break的作用是提前结束循环,但程序执行到break,跳出当前循环,且一次只能跳出一个循环
continue:
continue的作用是不执行本次循环后面的程序,开始执行下一次循环的程序
for循环:
for(表达式①;布尔表达式②;表达式③){表达式④; }
表达式1: 用于初始化循环变量初始值设置,在循环最开始时执行,且只执行一次
表达式2: 循环条件,满则循环继续,否则循环结束
表达式3: 循环变量更新方式
【注意事项】 (和while循环类似)
1. 和 if 类似, for 下面的语句可以不写 { } , 但是不写的时候只能支持一条语句. 建议还是加上 { }
2. 和 if 类似, for 后面的 { 建议和 while 写在同一行.
3. 和 if 类似, for 后面不要多写 分号, 否则可能导致循环不能正确执行.
4. 和while循环一样,结束单趟循环用continue,结束整个循环用break
do-while()循环:
do{循环语句; }while(循环条件);
先执行循环语句, 再判定循环条件,循环条件成立则继续执行,否则循环结束。
5.输入输出
输出:
System.out.println(msg); // 输出一个字符串, 带换行 System.out.print(msg); // 输出一个字符串, 不带换行 System.out.printf(format, msg); // 格式化输出
println 输出的内容自带 \n, print 不带 \n
printf 的格式化输出方式和 C 语言的 printf 是基本一致的.
输入:
使用 Scanner 读取字符串/整数/浮点数
import java.util.Scanner; // 需要导入 util 包Scanner sc = new Scanner(System.in); System.out.println("请输入你的姓名:"); String name = sc.nextLine(); System.out.println("请输入你的年龄:"); int age = sc.nextInt(); System.out.println("请输入你的工资:"); float salary = sc.nextFloat(); System.out.println("你的信息如下:"); System.out.println("姓名: "+name+"\n"+"年龄:"+age+"\n"+"工资:"+salary); sc.close(); // 注意, 要记得调用关闭方法
使用 Scanner 循环读取 N 个数字,并求取其平均值
Scanner sc = new Scanner(System.in); while (sc.hasNextInt()) {//语句 }
注意事项: 当循环输入多个数据的时候, 使用 ctrl + z 来结束输入 (Windows 上使用 ctrl + z, Linux / Mac 上使用 ctrl + d).
6.猜数字小游戏
猜数字游戏的核心是利用Random类中的nextInt方法生成随机数
Random random=new Random();//创建一个Randow类型的对象int guess= random.nextInt(100)+1;//调用nextInt方法生成随机数
//生成的随机数是从0开始的100个数,所以+1表示从1到100
整体游戏框架:
全部代码放置在码云:猜数字游戏 · 6e5cae8 · 鱼骨不是鱼翅/java学习 - Gitee.com
7.方法的使用
7.1方法的定义
// 方法定义
修饰符 返回值类型 方法名称([参数类型 形参 ...]){方法体代码;[return 返回值];
}
【注意事项】
1. 修饰符:现阶段直接使用public static 固定搭配
2. 返回值类型:如果方法有返回值,返回值类型必须要与返回的实体类型一致,如果没有返回值,必须写成 void
3. 方法名字:采用小驼峰命名
4. 参数列表:如果方法没有参数,()中什么都不写,如果有参数,需指定参数类型,多个参数之间使用逗号隔开
5. 方法体:方法内部要执行的语句
6. 在java当中,方法必须写在类当中
7. 在java当中,方法不能嵌套定义
8. 在java当中,没有方法声明一说
7.2实参和形参
1.形参的名字可以随意取,对方法都没有任何影响,形参只是方法在定义时需要借助的一个变量,用来保存方法在调用时传递过来的值。
2.在Java中,实参的值永远都是拷贝到形参中,形参和实参本质是两个实体
3.对于基础类型来说, 形参相当于实参的拷贝. 即传值调用
7.3方法重载
在Java中,如果多个方法的名字相同,参数列表不同,则称该几种方法被重载了。
注意:
1. 方法名必须相同
2. 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序必须不同)
3. 与返回值类型是否相同无关
7.4递归
一个方法在执行过程中调用自身, 就称为 "递归".
递归的必要条件:
1. 将原问题划分成其子问题,注意:子问题必须要与原问题的解法相同
2. 递归出口
代码示例 :求N的阶乘
public static void main(String[] args) {int n = 5;int ret = factor(n);System.out.println("ret = " + ret);
}public static int factor(int n) {if (n == 1) {return 1;}return n * factor(n - 1); // factor 调用函数自身
}
8.数组
8.1数组的创建及初始化
数组的创建:
T[] 数组名 = new T[N];
T:表示数组中存放元素的类型
T[]:表示数组的类型
N:表示数组的长度
数组的初始化:
动态初始化:在创建数组时,直接指定数组中元素的个数
int[] array = new int[10];
静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
语法示例:
语法格式: T[] 数组名称 = {data1, data2, data3, ..., datan};int[] array1 = new int[]{0,1,2,3,4,5,6,7,8,9};
【注意事项】
静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
静态初始化时, {}中数据类型必须与[]前数据类型一致。
静态初始化可以简写,省去后面的new T[]。
如果没有对数组进行初始化,数组中元素有其默认值 如果数组中存储元素类型为基类类型,默认值为基类类型对应的默认值,比如:
如果数组中存储元素类型为引用类型,默认值为null
8.2数组的使用
【注意事项】
1. 数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素
2. 下标从0开始,介于[0, N)之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常。
3.在数组中可以通过 数组对象.length 来获取数组的长度
8.3认识null
null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操 作. 一旦尝试读写, 就会抛出 NullPointerException.
8.4常见的数组方法
数组转字符串:Arrays.toString()
数组拷贝:Arrays.copyOf()
拷贝指定区间:Arrays.copyOfRange()
8.5二维数组:
语法格式:
数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };
二维数组本质上也就是一维数组, 只不过每个元素又是一个一维数组.
9.类和对象
类的创建格式:
// 创建类
class ClassName{ field; // 字段(属性) 或者 成员变量method; // 行为 或者 成员方法
}
1.class为定义类的关键字,ClassName为类的名字,{}中为类的主体。
2.类中包含的内容称为类的成员。属性主要是用来描述类的,称之为类的成员属性或者类成员变量。方法主要说明类 具有哪些功能,称为类的成员方法。
3.类名注意采用大驼峰定义
注意事项:
1. 一般一个文件当中只定义一个类
2. main方法所在的类一般要使用public修饰(注意:Eclipse默认会在public修饰的类中找main方法)
3. public修饰的类必须要和文件名相同
4. 不要轻易去修改public修饰的类的名称
类的实例化:
new 关键字用于创建一个对象的实例.
使用 . 来访问对象中的属性和方法. 同一个类可以创建对个实例.
类和对象说明:
1. 类只是一个模型一样的东西,用来对一个实体进行描述,限定了类有哪些成员.
2. 类是一种自定义的类型,可以用来定义变量.
3. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
4. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东 西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间
9.1this关键字
1. this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
2. this只能在"成员方法"中使用
3. 在"成员方法"中,this只能引用当前对象,不能再引用其他对象
4. this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法 对象的引用传递给该成员方法,this负责来接收
9.2构造方法
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且 在整个对象的生命周期内只调用一次。
代码示例:
public class Date {public int year;public int month;public int day;// 构造方法:// 名字与类名相同,没有返回值类型,设置为void也不行// 一般情况下使用public修饰// 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次public Date(int year, int month, int day){this.year = year;this.month = month;this.day = day;System.out.println("Date(int,int,int)方法被调用了");}
}
注意:
1.构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间。
2. 名字必须与类名相同
3. 没有返回值类型,设置为void也不行
4. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
5. 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)6.没有定义任何构造方法,编译器会默认生成一个不带参数的构造方法,一旦用户定义,编译器则不再生成。
7. 构造方法中,可以通过this调用其他构造方法来简化代码
public class Date {public int year;public int month;public int day;// 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复// 此处可以在无参构造方法中通过this调用带有三个参数的构造方法// 但是this(1900,1,1);必须是构造方法中第一条语句public Date(){//System.out.println(year); 注释取消掉,编译会失败this(1900, 1, 1);//this.year = 1900;//this.month = 1;//this.day = 1;} }
8.不能成环
public Date(){this(1900,1,1); }public Date(int year, int month, int day) {this(); }/* 无参构造器调用三个参数的构造器,而三个参数构造器有调用无参的构造器,形成构造器的递归调用 编译报错:Error:(19, 12) java: 递归构造器调用 */
9.3new关键字
Date d = new Date(2021,6,9);
在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单介绍下:
1. 检测对象对应的类是否加载了,如果没有加载则加载
2. 为对象分配内存空间
3. 处理并发安全问题 比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突
4. 初始化所分配的空间 即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值
5. 设置对象头信息
6. 调用构造方法,给对象中各个成员赋值
10.封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
简而言之就是private修饰成员变量,通过getter和setter方法访问成员变量
10.1static成员
static修饰成员变量
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。
【静态成员变量特性】
1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
3. 类变量存储在方法区当中
4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
static修饰成员方法
Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。
public class Student{// ...private static String classRoom = "Bit306";// ...public static String getClassRoom(){return classRoom;}
}public class TestStudent {public static void main(String[] args) {System.out.println(Student.getClassRoom());}
}
【静态方法特性】
1. 不属于某个具体的对象,是类方法
2. 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者
3. 不能在静态方法中访问任何非静态成员变量
4. 静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用
10.2代码块
普通代码块:定义在方法中的代码块.
public class Main{public static void main(String[] args) {{ //直接使用{}定义,普通方法块int x = 10 ;System.out.println("x1 = " +x);}int x = 100 ;System.out.println("x2 = " +x);}
}
构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。
class Student{
private String name;
private int age;
//实例代码块{this.name="张三";this.age=10;}
}
静态代码块 :使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量
class Student{
private static String name;
private static int age;
//静态代码块static {name="张三";age=10;}
}
注意事项:
1.静态代码块不管生成多少个对象,其只会执行一次
2.静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
3.如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
4.实例代码块只有在创建对象时才会执行
10.3内部类
内部类大体结构
public class OutClass {// 成员位置定义:未被static修饰 --->实例内部类public class InnerClass1{}// 成员位置定义:被static修饰 ---> 静态内部类static class InnerClass2{}public void method(){// 方法中也可以定义内部类 ---> 局部内部类:几乎不用class InnerClass5{}}
}
根据内部类定义的位置不同,一般可以分为以下几种形式:
1. 成员内部类(普通内部类:未被static修饰的成员内部类 和 静态内部类:被static修饰的成员内部类)
2. 局部内部类(不谈修饰符)、匿名内部类 注意:内部类其实日常开发中使用并不是非常多,大家在看一些库中的代码时候可能会遇到的比较多,日常开始中 使用最多的是匿名内部类。
成员内部类:
public class OutClass {private int a;static int b;int c;public void methodA(){a = 10;System.out.println(a);}public static void methodB(){System.out.println(b);}// 实例内部类:未被static修饰class InnerClass{int c;public void methodInner(){// 在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员a = 100;b =200;methodA();methodB();// 如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类自己的c = 300;System.out.println(c);// 如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字OutClass.this.c = 400;System.out.println(OutClass.this.c);}}public static void main(String[] args) {// 外部类:对象创建 以及 成员访问OutClass outClass = new OutClass();System.out.println(outClass.a);System.out.println(OutClass.b);System.out.println(outClass.c);outClass.methodA();outClass.methodB();System.out.println("=============实例内部类的访问=============");// 要访问实例内部类中成员,必须要创建实例内部类的对象// 而普通内部类定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类// 创建实例内部类对象OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();// 上述语法比较怪异,也可以先将外部类对象先创建出来,然后再创建实例内部类对象OutClass.InnerClass innerClass2 = outClass.new InnerClass();innerClass2.methodInner();}
}
【注意事项】
1. 外部类中的任何成员都可以在实例内部类方法中直接访问
2. 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束
3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名 称.this.同名成员 来访问
4. 实例内部类对象必须在先有外部类对象前提下才能创建
5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用
6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
静态内部类:
public class OutClass {private int a;static int b;public void methodA(){a = 10;System.out.println(a);}public static void methodB(){System.out.println(b);}// 静态内部类:被static修饰的成员内部类static class InnerClass{public void methodInner(){// 在内部类中只能访问外部类的静态成员// a = 100; // 编译失败,因为a不是类成员变量b =200;// methodA(); // 编译失败,因为methodB()不是类成员方法methodB();}}public static void main(String[] args) {// 静态内部类对象创建 & 成员访问OutClass.InnerClass innerClass = new OutClass.InnerClass();innerClass.methodInner();}
}
【注意事项】
1. 在静态内部类中只能访问外部类中的静态成员
2. 创建静态内部类对象时,不需要先创建外部类对象
局部内部类:
public class OutClass {int a = 10;public void method(){int b = 10;// 局部内部类:定义在方法体内部// 不能被public、static等访问限定符修饰class InnerClass{public void methodInnerClass(){System.out.println(a);System.out.println(b);}}// 只能在该方法体内部使用,其他位置都不能用InnerClass innerClass = new InnerClass();innerClass.methodInnerClass();}public static void main(String[] args) {// OutClass.InnerClass innerClass = null; 编译失败}
}
【注意事项】
1. 局部内部类只能在所定义的方法体内部使用
2. 不能被public、static等修饰符修饰
3. 编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class
4. 几乎不会使用
匿名内部类(重点):
匿名内部类本质上就是隐藏了名字的内部类
格式:
new 类名或接口名(){
重写方法;
}举例:
new Inter(){
public void show(){}
}
遵循编译看左边,运行看右边的原则
匿名内部类是一个对象,可以直接点方法调用自己类里面的方法
new Swin(){
public void swin(){
sout("重写接口里面swin的方法");
}
}.swin();
注意:匿名内部类可以写在局部位置也可以写在成员位置
11.继承和多态
11.1继承
在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:
修饰符 class 子类 extends 父类 {// ...
}
注意:
1. 子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
子类中访问父类的成员变量:
在子类方法中 或者 通过子类对象访问成员时:
1.如果访问的成员变量子类中有,优先访问自己的成员变量。
2.如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
3.如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
4.成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
子类中访问父类的成员方法:
1.通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到 则访问,否则编译报错。
2.通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错;
super关键字
java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员。
【注意事项】
1. 只能在非静态方法中使用。
2. 在子类方法中,访问父类的成员变量和方法。
子类构造方法
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执 行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
注意:
1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构 造方法
2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。
3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现
super和this
super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语 句,那他们之间有什么区别呢?
【相同点】
1. 都是Java中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
【不同点】
1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造 方法中出现
4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
final关键字
1. 修饰变量或字段,表示常量(即不能修改)
final int a = 10; a = 20; // 编译出错
2. 修饰类:表示此类不能被继承
final public class Animal {... }public class Bird extends Animal {... }// 编译出错 Error:(3, 27) java: 无法从最终com.bit.Animal进行继
3. 修饰方法:表示该方法不能被重写
11.2多态
在java中要实现多态 ,必须要满足如下几个条件 ,缺一不可:
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法多态体现 : 在代码运行时,当传递不同类对象时,会调用对应 类中的方法 。
class Animal{String name;int age;public Animal(String name, int age) {this.name = name;this.age = age;}public void show(){System.out.println("我是动物,我叫"+name+"今年+"+age+"岁了");}
}
class Dog extends Animal{public Dog(String name, int age) {super(name, age);}@Overridepublic void show() {System.out.println("我是狗,我叫"+name+"今年"+age+"岁了");}
}
class Cat extends Animal{public Cat(String name, int age) {super(name, age);}@Overridepublic void show() {System.out.println("我是猫,我叫"+name+"今年"+age+"岁了");}
}
public class T2 {public static void main(String[] args) {
Cat cat=new Cat("花花",2);
Dog dog=new Dog("旺财",4);
show(dog);
show(cat);}
/ / 编译器在编译代码时,并不知道要调用Dog还是Cat中show的方法 / / 等程序运行起来后,形参animal引用的具体对象确定后,才知道调用那个方法/ /注意:此处的形参类型必须是父类类型才可以public static void show(Animal animal){animal.show();}
}
当类的调用者在编写show这个方法的时候, 参数类型为 Animal (父类), 此时在该方法内部并不知道, 也不关注当前的 animal引用指向的是哪个类型(哪个子类)的实例. 此时animal这个引用调用show方法可能会有多种不同的表现(和 animal 引用的实例相关), 这种行为就称为多态.
重写
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定 于自己的行为。 也就是说子类能够根据需要实现父类的方法。
方法重写规则:
1.子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
2.被重写的方法返回值类型可以不同,但是必须是具有父子关系的
3.访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方 法就不能声明为 protected
4.父类被static、private修饰的方法、构造方法都不能被重写。
5.重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法 构成重写.
向上转型和向下转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()
Animal animal = new Cat("元宝",2);
animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
向下转型:将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的 方法,此时:将父类引用再还原为子类对象即可,即向下转换。
代码示例:
dog = (Dog)animal;
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入 了 instanceof ,如果该表达式为true,则可以安全转换。
12.抽象类和接口
抽象类
在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用 给出具体的实现体。
// 抽象类:被abstract修饰的类
public abstract class Shape {// 抽象方法:被abstract修饰的方法,没有方法体abstract public void draw();abstract void calcArea();// 抽象类也是类,也可以增加普通方法和属性public double getArea(){return area;}protected double area; // 面积
}
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
抽象类的特性:
1. 抽象类不能直接实例化对象
2. 抽象方法不能是 private 的
3. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
4. 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
5. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
6. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
接口
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
接口的定义格式:
将class关键字换成 interface 关键字,就定义了一个接口。
public interface 接口名称{// 抽象方法public abstract void method1(); // public abstract 是固定搭配,可以不写public void method2();abstract void method3();void method4();// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}
提示:
1. 创建接口时, 接口的命名一般以大写字母 I 开头.
2. 接口的命名一般使用 "形容词" 词性的单词.
3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
接口的使用:
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
public class 类名称 implements 接口名称{// ...
}
注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。
接口的特性:
1. 接口类型是一种引用类型,但是不能直接new接口的对象
2. 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)
3. 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
4. 重写接口中方法时,不能使用默认的访问权限
5. 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
6. 接口中不能有静态代码块和构造方法
7. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
8. 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
9. jdk8中:接口中还可以包含default方法。
10.在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。
11.一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。
接口间的继承:
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到 多继承的目的。
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字.
interface IAmphibious extends IRunning, ISwimming {}
//接口间的继承相当于把多个接口合并在一起.
更多接口扩展内容,可参考:Java——接口后续-CSDN博客
13.String
常用方法:
1.字符串构造的常用三种方法
// 使用常量串构造String s1 = "hello bit";System.out.println(s1);// 直接newString对象String s2 = new String("hello bit");System.out.println(s1);// 使用字符数组进行构造char[] array = {'h','e','l','l','o','b','i','t'};String s3 = new String(array);
2.String对象的比较
1. ==比较是否引用同一个对象
注意:对于内置类型,==比较的是变量中的值;
对于引用类型==比较的是引用中的地址。
2. boolean equals(Object anObject) 方法:按照字典序比较 字典序:字符大小的顺序 String类重写了父类Object中equals方法,Object中equals默认按照==比较,String重写equals方法后,按照 如下规则进行比较,比如: s1.equals(s2)
public boolean equals(Object anObject) {// 1. 先检测this 和 anObject 是否为同一个对象比较,如果是返回trueif (this == anObject) {return true;}// 2. 检测anObject是否为String类型的对象,如果是继续比较,否则返回falseif (anObject instanceof String) {// 将anObject向下转型为String类型对象String anotherString = (String)anObject;int n = value.length;// 3. this和anObject两个字符串的长度是否相同,是继续比较,否则返回falseif (n == anotherString.value.length) {char v1[] = value;char v2[] = anotherString.value;int i = 0;// 4. 按照字典序,从前往后逐个字符进行比较while (n-- != 0) {if (v1[i] != v2[i])return false;i++;}return true;}}return false; }
3. int compareTo(String s) 方法:
按照字典序进行比较 与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型。
具体比较方式:
1. 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
2. 如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值
public static void main(String[] args) {String s1 = new String("abc");String s2 = new String("ac");String s3 = new String("abc");String s4 = new String("abcdef");System.out.println(s1.compareTo(s2)); // 不同输出字符差值-1System.out.println(s1.compareTo(s3)); // 相同输出 0System.out.println(s1.compareTo(s4)); // 前k个字符完全相同,输出长度差值 -3 }
4. int compareToIgnoreCase(String str) 方法:与compareTo方式相同,但是忽略大小写比较
public static void main(String[] args) {String s1 = new String("abc");String s2 = new String("ac");String s3 = new String("ABc");String s4 = new String("abcdef");System.out.println(s1.compareToIgnoreCase(s2)); // 不同输出字符差值-1System.out.println(s1.compareToIgnoreCase(s3)); // 相同输出 0System.out.println(s1.compareToIgnoreCase(s4)); // 前k个字符完全相同,输出长度差值 -3 }
3.字符串查找
char charAt(int index):返回index位置上字符,如果index为负数或者越界,抛出 IndexOutOfBoundsException异常
int indexOf():从前往后查找,返回第一次出现的位置,没有返回-1
int lastIndexOf():从后往前查找,返回第一次出现的位置,没有返回-1
4.转化
1. 数值和字符串转化
String s1 = String.valueOf(1234);
2. 大小写转换
String s1 = "hello"; String s2 = "HELLO"; // 小写转大写 System.out.println(s1.toUpperCase()); // 大写转小写 System.out.println(s2.toLowerCase());
3. 字符串转数组
String s = "hello"; // 字符串转数组 char[] ch = s.toCharArray(); // 数组转字符串 String s2 = new String(ch);
4. 格式化
String s = String.format("%d-%d-%d", 2019, 9,14);
5.字符串替换
String str = "helloworld" ; System.out.println(str.replaceAll("l", "_")); System.out.println(str.replaceFirst("l", "_"));
6 .字符串拆分
String str = "hello world hello bit" ; String[] result = str.split(" ") ; // 按照空格拆分
注意事项:
1. 字符"|","*","+"都得加上转义字符,前面加上 "\\" .
2. 而如果是 "\" ,那么就得写成 "\\\\" .
3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符.
多次拆分:
String str = "name=zhangsan&age=18" ; String[] result = str.split("&") ; for (int i = 0; i < result.length; i++) {String[] temp = result[i].split("=") ; System.out.println(temp[0]+" = "+temp[1]); }
7 .字符串截取
String str = "helloworld" ; System.out.println(str.substring(5));//从指定索引截取到结尾System.out.println(str.substring(0, 5));//截取部分内容
注意事项:
1. 索引从0开始
2. 注意前闭后开区间的写法, substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标
8.去除字符串两边的空格
String trim():去掉字符串中的左右空格,保留中间空格
String str = " hello world " ; System.out.println("["+str+"]"); System.out.println("["+str.trim()+"]");
14.异常
异常的分类
1. 编译时异常 在程序编译期间发生的异常,称为编译时异常,也称为受检查异常
2. 运行时异常 在程序执行期间发生的异常,称为运行时异常,也称为非受检查异常
注意:
编译时出现的语法性错误,不能称之为异常。例如将 System.out.println 拼写错了, 写成了 system.out.println. 此时编译过程中就会出错, 这是 "编译期" 出错。而运行时指的是程序已经编译通过得到 class 文件了, 再由 JVM 执行过程中出现的错误。
异常的处理
事前防御型:
boolean ret = false; ret = 登陆游戏(); if (!ret) {处理登陆游戏错误;return; } ret = 开始匹配(); if (!ret) {处理匹配错误;return; } ret = 游戏确认(); if (!ret) {处理游戏确认错误;return; } ret = 选择英雄(); if (!ret) {处理选择英雄错误;return; } ret = 载入游戏画面(); if (!ret) {处理载入游戏错误;return; }
缺陷:正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱。
事后认错型:
try {登陆游戏();开始匹配();游戏确认();选择英雄();载入游戏画面();... } catch (登陆游戏异常) {处理登陆游戏异常; } catch (开始匹配异常) {处理开始匹配异常; } catch (游戏确认异常) {处理游戏确认异常; } catch (选择英雄异常) {处理选择英雄异常; } catch (载入游戏画面异常) {处理载入游戏画面异常; }
优势:正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码 异常处理的核心思想就是 EAFP。
在Java中,异常处理主要的5个关键字:throw、try、catch、final、throws。
异常的抛出
在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下:
throw new XXXException("异常产生的原因");
【注意事项】
1. throw必须写在方法体内部
2. 抛出的对象必须是Exception 或者 Exception 的子类对象
3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理
4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
5. 异常一旦抛出,其后的代码就不会执行
异常的捕获
异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明throws 以及 try-catch捕获处理。
throws:
语法格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{}
1. throws必须跟在方法的参数列表之后
2. 声明的异常必须是 Exception 或者 Exception 的子类
3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型 具有父子关系,直接声明父类即可
4. 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出
try-catch捕获并处理:
throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行 处理,就需要try-catch。
语法格式:
try{// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类
时,就会被捕获到// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
}[catch(异常类型 e){// 对异常进行处理
}finally{// 此处代码一定会被执行到
}]// 后序代码
// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行
// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行注意:1. []中表示可选项,可以添加,也可以不用添加2. try中的代码可能会抛出异常,也可能不会
【注意事项】
1. try块内抛出异常位置之后的代码将不会被执行
2. 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到 JVM收到后中断程序----异常是按照类型来捕获的
3. try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获
如果多个异常的处理方式是完全相同, 也可以写成这样:
catch (ArrayIndexOutOfBoundsException | NullPointerException e) {... }
如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch
4. 可以通过一个catch捕获所有的异常,即多个异常,一次捕获(不推荐)
finally
在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库 连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能 导致有些语句执行不到,finally就是用来解决这个问题的。
语法格式:
try{// 可能会发生异常的代码
}catch(异常类型 e){// 对捕获到的异常进行处理
}finally{// 此处的语句无论是否发生异常,都会被执行到
}// 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行
注意:finally中的代码一定会执行的,一般在finally中进行一些资源清理的扫尾工作。
【异常处理流程总结】
程序先执行 try 中的代码 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
如果找到匹配的异常类型, 就会执行 catch 中的代码 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
如果上层调用者也没有处理的了异常, 就继续向上传递.
一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
自定义异常类
具体方式:
1. 自定义异常类,然后继承自Exception 或者 RunTimeException
2. 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因
用户登录功能:
public class LogIn {private String userName = "admin";private String password = "123456";public static void loginInfo(String userName, String password)throws UserNameException,PasswordException{if (!userName.equals(userName)) {throw new UserNameException("用户名错误!");}if (!password.equals(password)) {throw new PasswordException("用户名错误!");}System.out.println("登陆成功");}public static void main(String[] args) {try {loginInfo("admin", "123456");} catch (UserNameException e) {e.printStackTrace();} catch (PasswordException e) {e.printStackTrace();}
}
}