赞
踩
目录
2.1 StringBuffer和StringBuilder的介绍
使用java写的程序,如果在cmd上运行的话,
要先使用 javac xxxxx.java 编译此程序
然后再使用 java xxxxx 运行这个程序(在JVM上运行)java虚拟机 JVM
.class文件就是字节码文件
Java程序经编译后会产生byte code文件(字节码文件)
类:类型 方法:和C语言的函数差不多 public:访问修饰限定符 static:静态的
ctrl + shift +F10 运行程序,main + 回车 能够自动打出main方法
sout + 回车
System.out.println是Java中标准输出,会将内容输出到控制台,打印并且换行
1.源文件(扩展名为*.java):源文件带有类的定义。类用来表示程序的一个组件,小程序或许只会有一个类。类的内容必须包含在花括号里面。
2.类:类中带有一个或多个方法。方法必须在类的内部声明。
3.方法:在方法的花括号中编写方法应该执行的语句。
总结一下:类存在于源文件里面;方法存在于类中;语句存在于方法中
在一个源文件中只能有一个public修饰的类,而且源文件名字必须与public修饰的类名字相同。
注释:分为三种
1.单行注释 \\ 快捷键 ctrl + \
2.块注释 \**\ 不支持嵌套使用
3.文本注释 \** *\ (常见于方法和类之上描述方法和类的作用),可以被javadoc工具解析,生成一套以网页文件形式体现的程序说明文档
注释是不会参与编译的,即编译之后生成的.class文件中不包含注释信息。
1. main方法是Java程序的入口方法
2. main方法的格式是固定的,必须为public static void main(String[] args)
-----------------------------------------------------------------------------------------------------------------------
- /**
- 文档注释:
- @version v1.0.0
- @author will
- 作用HelloWorld类,入门第一个程序练习
- */
- public class HelloWorld{
- /*多行注释:
- 1. main方法是Java程序的入口方法
- 2. main函数的格式是固定的,必须为public static void main(String[] args)
- */
- /**
- main方法是程序的入口函数
- @param args 命令行参数。
- */
- public static void main(String[] args){
- // 单行注释:System.out.println是Java中标准输出,会将内容输出到控制台
- System.out.println("Hello World");
- }
// 在cmd中,使用javadoc工具从Java源码中抽离出注释 |
// -d 创建目录 myHello为目录名 |
// -author 显示作者 |
// -version 显示版本号 |
// -encoding UTF-8 -charset UTF-8 字符集修改为UTF-8 |
javadoc -d myHello -author -version -encoding UTF-8 -charset UTF-8 HelloWorld.java
运行上面这些东西,会将注释内容生成一个离线的网页,方便别人学习
---------------------------------------------------------------------------------------------------------------------------------
Test称为类名,main称为方法名,也可以将其称为标识符,即:在程序中由用户给类名、方法名或者变量所取的名字。
【硬性规则】
标识符中可以包含:字母、数字以及 下划线和 $ 符号。
注意:标识符不能以数字开头,也不能是关键字,且严格区分大小写。
关键字是由Java语言提前定义好的,有特殊含义的标识符,或者保留字。
常量即程序运行期间,固定不变的量称为常量,和C语言的常量类似。
字面常量的分类:
1. 字符串常量:由""括起来的,比如“12345”、“hello”、“你好”。
2. 整形常量:程序中直接写的数字(注意没有小数点),比如:100、1000
3. 浮点数常量:程序中直接写的小数,比如:3.14、0.49
4. 字符常量:由 单引号 括起来的当个字符,比如:‘A’、‘1’
5. 布尔常量:只有两种true和false
6. 空常量:null
在Java中数据类型主要分为两类:基本数据类型和引用数据类型(数组,类,接口,枚举类型)。
基本数据类型有四类八种:
1. 四类:整型、浮点型、字符型以及布尔型
2. 八种:
注意:
- 不论是在16位系统还是32位系统,int都占用4个字节,long都占8个字节
- 整型和浮点型都是带有符号的
- 整型默认为int型,浮点型默认为double
- 字符串属于引用类型
能够被改变的量。都和C语言类似,但是基本数据类型多了个包装类
包装类只是针对基本数据类型 对应的类 类型
数据类型就是用来定义不同种类变量的
和C语言定义变量相同
比如 int a = 10; 等号左边的叫左值(空间),右边叫右值(数据)
int a = 10;
整型变量在使用前一定得赋初始值,不然编译期间会报错
1. int不论在何种系统下都是4个字节
2. 推荐使用方式一定义,如果没有合适的初始值,可以设置为0
3. 在给变量设置初始值时,值不能超过int的表示范围,否则会导致溢出
4. 变量在使用之前必须要赋初值,否则编译报错
5. int的包装类型为 Integer
Long 是 long 的包装类型,为了区分,所以定义变量数值时要加L
注意事项:
1. 长整型变量的初始值后加L或者l,推荐加L
2. 长整型不论在那个系统下都占8个字节
3. 长整型的表示范围为:- 2^63 ~ 2^63 - 1
4. long的包装类型为Long
注意事项:
1. short在任何系统下都占2个字节
2. short的表示范围为:-32768 ~ 32767
3. 使用时注意不要超过范围(一般使用比较少)
4. short的包装类型为Short
注意事项:
1. byte在任何系统下都占1个字节
2. byte的范围是:-128 ~ 127
3. 字节的包装类型为Byte
在 Java 中, int 除以 int 的值仍然是 int(会直接舍弃小数部分)。
注意事项:
1. double在任何系统下都占8个字节
2. 浮点数与整数在内存中的存储方式不同,不能单纯使用 的形式来计算
3. double的包装类型为Double
4. double 类型的内存布局遵守 IEEE 754 标准(和C语言一样), 尝试使用有限的内存空间表示可能无限的小数, 势必会存在一定的精度误差,因此浮点数是个近似值,并不是精确值。
占4个字节
注意事项:
1. Java 中使用 单引号 + 单个字母 的形式表示字符字面值.
2. 计算机中的字符本质上是一个整数. 在 C 语言中使用 ASCII 表示字符, 而 Java 中使用 Unicode 表示字符. 因此一个字符占用两个字节, 表示的字符种类更多, 包括中文
3.取值范围:0 ~ 2^16 -1
布尔类型常用来表示真假,只有两种值,true和false
它没有确定大小,也不能进行类型转换,强制转换也不行,
不能进行+-整数
Java虚拟机规范中,并没有明确规定boolean占几个字节,也没有专门用来处理boolean的字节码指令,在Oracle公司的虚拟机实现中,boolean占1个字节。
boolean的包装类型为Boolean
在Java中,当参与运算数据类型不一致时,就会进行类型转换。Java中类型转换主要分为两类:自动类型转换(隐式) 和 强制类型转换(显式)。
和C语言类似。
3.9.1 自动类型转换(隐式)
自动类型转换即:代码不需要经过任何处理,在代码编译时,编译器会自动进行处理。
特点:数据范围小的转为数据范围大的时会自动进行。
小转大
Java比较严,这样做会报错
3.9.2 强制类型转换(显式)
强制类型转换:当进行操作时,代码需要经过一定的格式处理,不能自动完成。
特点:数据范围大的到数据范围小的。
注意事项:
1. 不同数字类型的变量之间赋值, 表示范围更小的类型能隐式转换成范围较大的类型
2. 如果需要把范围大的类型赋值给范围小的, 需要强制类型转换, 但是可能精度丢失
3. 将一个字面值常量进行赋值的时候, Java 会自动针对数字范围进行检查
4. 强制类型转换不一定能成功,不相干的类型不能互相转换
和C语言差不多
分为整型提升和算术转换
小于int的就转int
其他的: int -> float -> long -> double 按照这个来转换
输出
1.int 转成 String
问:输出什么?
答案:
2. String 转成 int
String str = "109" int ret = Integer.valueOf(str);
和C语言类似
int / int =int
小数也能进行取模了,但是意义不大
做除法和取模时,右操作数不能为0,不然抛异常
注意:只有变量才能使用该运算符,常量不能使用。
会自动进行类型转换
和C语言相同
比如 a = 10; a += 10;//相当于 a = a + 10
其他同理
和C语言相同
前置++:先++,再使用
后置++:先使用,后++
--同理
关系运算符主要有六个: == != < > = ,其计算结果是 true 或者 false 。
和C语言类似
逻辑运算符主要有三个: && || ! ,运算结果都是 boolean类型。
布尔表达式1 && 布尔表达式2
有假就是假
布尔表达式1 || 布尔表达式2
有真就是真
假变真,真变假
和C语言相同
对于 && , 如果左侧表达式值为 false, 则表达式结果一定是 false, 无需计算右侧表达式.
对于 ||, 如果左侧表达式值为 true, 则表达式结果一定是 true, 无需计算右侧表达式.
& 和 | 如果表达式结果为 boolean 时, 也表示逻辑运算. 但与 && || 相比, 它们不支持短路求值.
位运算符主要有四个: & | ~ ^ ,和C语言相同
按位与 &:有0则0
按位或 |:有1则1
注意: 当 & 和 | 的操作数为整数(int, short, long, byte) 的时候, 表示按位运算, 当操作数为 boolean 的时候, 表示逻辑运算.
按位取反 ~: 0变1,1变0
按位异或 ^:相同为 0,相异为1
%x 打印16进制
移位运算符有三个: > >>> ,都是二元运算符,且都是按照二进制比特位来运算的
1. 左移 : 左边丢弃,右边补0,相当于乘2.
注意:向左移位时,丢弃的是符号位,因此正数左移可能会变成负数
2. 右移 >>: 最右丢弃, 左边补符号位(正数补0, 负数补1),相当于除2
3. 无符号右移 >>>: 最右侧位不要了, 最左侧补 0
和C语言相同
条件运算符只有一个:
布尔表达式1 ? 表达式2 : 表达式3
当 表达式1 的值为 true 时, 执行表达式2,整个表达式的值为 表达式2 的值;
当 表达式1 的值为 false 时,执行表达式3, 整个表达式的值为 表达式3 的值.
也是 Java 中唯一的一个 三目运算符, 是条件判断语句的简化写法
注意:
1. 表达式2和表达式3的结果要是同类型的,除非能发生类型隐式类型转换
2. 表达式不能单独存在,其产生的结果必须要被使用
int a = 10;
int b = 20;
a > b? a : b; // 报错:Error:(15, 14) java: 不是语句
和C语言相同,不用记忆,做题的时候直接加括号
顺序结构:从上到下一行一行的执行你写的代码。
语法:和C语言相同
格式1:
if(布尔表达式){
语句;
}
上面代码意思是:如果score大于等于85(表达式为真),就打印优秀。
格式2:
if(布尔表达式){
语句;
}else{
语句;
}
上面代码意思是:如果score大于等于85(表达式为真),就打印优秀。否则打印普通。
格式3:
if(布尔表达式1){
语句;
}else if(布尔表达式2){
语句;
}else{
语句;
}
上面代码意思是:如果score大于等于85(表达式为真),就打印优秀。其他如果score在70到85,则打印良好,否则就打印普通。
1. 判断一个数字是奇数还是偶数
2. 判断一个年份是否为闰年
不要手贱在if语句后面加 ; 不然后果自负
和C语言一样,else和距离最近的未匹配的if匹配
和C语言相同
为了达成分支效果,case 和break 必须一起使用
不能左switch语句参数的基本数据类型有:
float double long boolean
switch 不能表达复杂的条件。
和C语言相同
布尔表达式为真,就执行循环语句
此时 ; 为 while 的语句体(这是一个空语句), 实际的 { } 部分和循环无关. 此时循环条件 num <= 10恒成立,导致代码死循环了
break 跳出循环
continue :跳过continue后面的语句,进入下一次循环
死循环了,因为num=2时永远continue,num无法自增,所以死循环
fori + 回车 快速写for循环
特点:至少执行一次
println 输出的内容自带 \n, print 不带 \n
printf 的格式化输出方式和 C 语言的 printf 是基本一致的.
使用前得导入包
import java.util.Scanner;
Scanner scan = new Scanner(System.in);
//这个也要写在前面,否则没法输入
.nextLine()就相当于C语言的gets
字符串的输入
整型的输入:
浮点数输入:
多组数据输入:
刷题的时候会用到,猛记
按Ctrl + D 结束输入
- import java.util.Random;
- import java.util.Scanner;
-
- public class Test {
- public static void main(String[] args) {
- Scanner scan = new Scanner(System.in);
- Random random = new Random();//设置随机数
- int randomNum = random.nextInt(100) + 1;//生成随机数
- while (true) {
- System.out.println("请输入要猜的数字");
- int guess = scan.nextInt();
- if (guess > randomNum) {
- System.out.println("猜大了");
- } else if (guess == randomNum) {
- System.out.println("猜对了");
- break;
- } else {
- System.out.println("猜小了");
- }
- }
- }
- }
和C语言的函数类似。
使用方法能让代码的可读性更高,提升效率。
和C语言类似
public static 返回类型 方法名(参数)
比如main方法
例如:判断闰年
例如:两个整数相加
1. 修饰符:现阶段直接使用public static 固定搭配
2. 返回值类型:如果方法有返回值,返回值类型必须要与返回的实体类型一致,如果没有返回值,必须写成void
3. 方法名字:采用小驼峰命名
4. 参数列表:如果方法没有参数,()中什么都不写,如果有参数,需指定参数类型,多个参数之间使用逗号隔开
5. 方法体:方法内部要执行的语句
6. 在java当中,方法必须写在类当中
7. 在java当中,方法不能嵌套定义
8. 在java当中,没有方法声明一说
调用方法--->传递参数--->找到方法地址--->执行被调方法的方法体--->被调方法结束返回--->回到主调方法继续往下执行
在方法执行中,如果遇到了return 那么方法就结束了,意味着该方法在栈上开辟的内存就没有了
方法在编译完成之后,会保存在方法区,方法被调用时会在栈区上开辟空间。
定义方法的时候, 不会执行方法的代码. 只有调用的时候才会执行.
一个方法可以被多次调用。
和C语言相同,形参是实参的一份临时拷贝,改变形参不会影响实参(传值调用)
想要写一个函数交换两个整型变量,只能传引用类型的变量,引用就是引用变量,引用变量存的是“地址”(哈希值),但它是唯一的。
这么写才能交换,数组变量名其实就是一个引用
没有返回值的方法必须返回类型必须写void
在java中,方法可以同名,但是 同名方法的 参数列表不能相同,
这种同名的方法就被叫做方法重载
比如:
注意:
1. 方法名必须相同
2. 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序必须不同)
3. 与返回值类型是否相同无关
每个方法都会有一个方法签名
方法签名即:经过编译器编译修改过之后方法最终的名字。具体方式:方法全路径名+参数列表+返回值类型,构成方法完整的名字。
输入:javap -v 字节码文件名字即可,就能查看方法签名(能看到类文件大小、类文件的MD5、JDK版本等)
输入:javap -c 字节码文件名字即可,就能查看方法签名(查看相关反编译指令)
比如判断闰年,参数时int,返回类型是boolean
[ 是数组
J 是long
L 是引用类型
Z 是boolean类型
其他基本数据类型都是以其大写字母开头来表示
和C语言相同,就是方法自己调用自己,被称为递归。
递归必须有条件限制,每次递归后会更接近这个条件,不然的话就会死递归
例如, 求 N!
起始条件: N = 1 的时候, N! 为 1. 这个起始条件相当于递归的结束条件.
递归公式: 求 N! , 直接不好求, 可以把问题转换成 N! => N * (N-1)!
绿色部分是递
红色部分是归
关于 "调用栈"
方法调用的时候, 会有一个 "栈" 这样的内存空间描述当前的调用关系. 称为调用栈.
每一次的方法调用就称为一个 "栈帧", 每个栈帧中包含了这次调用的参数是哪些, 返回到哪里继续执行等信息.
后面我们借助 IDEA 很容易看到调用栈的内容.
1.按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4)
- public static void print(int n) {
- if (n <= 9) {
- System.out.println(n);
- } else {
- print(n / 10);
- System.out.println(n % 10);
- }
- }
2.递归求 1 + 2 + 3 + ... + 10
- public static int sum(int n) {
- if (n == 1) {
- return 1;
- } else {
- return n + sum(n - 1);
- }
- }
3.写一个递归方法,输入一个非负整数,返回组成它的数字之和. 例如,输入 1729, 则应该返回1+7+2+9,它的和是19
- public static int sum(int n) {
- if (n <= 9) {
- return n;
- } else {
- return n % 10 + sum(n / 10);
- }
- }
4.求斐波那契数列的第 N 项
- public static int fib(int n) {
- if (n == 1) {
- return 0;
- }
- if (n == 2) {
- return 1;
- }
- return fib(n - 1) + fib(n - 2);
- }
5.实现汉诺塔
思路:
将汉诺塔的n个盘子分为两部分,最底下的盘子和其他盘子,
先让其他盘子(n-1)从A借助C挪到B,
再让最底下的盘子从A直接挪到C,
最后再把其他盘子(n-1)从B借助A挪到C
- public static void hanoi(char a, char b, char c, int cnt) {
- if (cnt == 1) {
- System.out.println(a + "->" + c);
- } else {
- hanoi(a, c, b, cnt - 1);
- System.out.println(a + "->" + c);
- hanoi(b, a, c, cnt - 1);
- }
- }
引用变量:存地址的变量(相当于C语言的指针)
全和C语言一样
数组:相同类型元素组成的一个集合。在内存中是一段连续的空间。
数组就是引用(引用变量),里面存的是地址,地址指向了数组(在堆区存放)
1. 数组中存放的元素其类型相同
2. 数组的空间是连在一起的
3. 每个空间有自己的编号,其实位置的编号为0,即数组的下标
1.2.1 数组的创建
方法:T是类型
1. T[ ] 数组名 ={ };
2.
int[] array1 = new int[10]; // 创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5]; // 创建一个可以容纳5个double类型元素的数组
String[] array3 = new double[3]; // 创建一个可以容纳3个字符串元素的数组
1.2.2 数组的初始化
以下初始化内容都不重要,只要知道这么用即可
数组的初始化主要分为动态初始化以及静态初始化
1. 动态初始化:在创建数组时,直接指定数组中元素的个数
int[] array = new int[10];
2. 静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
语法格式: T[] 数组名称 = {data1, data2, data3, ..., datan};
int[] array1 = new int[]{0,1,2,3,4,5,6,7,8,9};
double[] array2 = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = new String[]{"hell", "Java", "!!!"};
静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
静态初始化时, {}中数据类型必须与[]前数据类型一致。
静态初始化可以简写,省去后面的new T[]。
如果没有对数组进行初始化,数组中元素有其默认值。全是0,boolean就是false
如果数组中存储元素类型为引用类型,默认值为null
1.3.1 数组中元素访问
和C语言一样
数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过下标访问其任意位置的元素
用下标访问数组
下标从0开始,每次递增1,有n个元素,下标范围就是[0,n),这个和C是一样的
1.3.2 遍历数组
"遍历" 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作
数组元素个数可以通过数组名.length求得,非常方便
for each循环,专门用来访问数组的
两种方法都能遍历数组,区别是for依赖下标,而for each循环不依赖下标
引用类型:变量存的是地址
内存是一段连续的存储空间,主要用来存储程序运行时数据的。
程序计数器: 只是一个很小的空间, 保存下一条执行的指令的地址
虚拟机栈: 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
本地方法栈: 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的。
堆: JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。
方法区: 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域。
重点:局部变量存在栈里,堆区存的是对象。
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。
引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象。
修改了形参所指向的对象。
如果两个引用指向同一个对象,那么既可以通过引用1修改对象,
也可以通过引用2修改对象
局部变量的生命周期和作用域是进入方法创建,出方法销毁。
当对象没有人引用时,对象就会自动被回收
null 在 Java 中表示 "空引用" , 也就是该引用没有指向对象.
null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操作. 一旦尝试读写, 就会抛出 NullPointerException.
注意: Java 中并没有约定 null 和 0 号地址的内存有任何关联
- public static void main(String[] args) {
- int[] array = {1, 2, 3};
- for(int i = 0; i < array.length; ++i){
- System.out.println(array[i] + " ");
- }
- }
1. 参数传基本数据类型
- public static void main(String[] args) {
- int num = 0;
- func(num);
- System.out.println("num = " + num);
- }
-
- public static void func(int x) {
- x = 10;
- System.out.println("x = " + x);
- }
- // 执行结果
- x =10
- num =0
改变形参不影响实参
2. 参数传数组类型(引用数据类型)
- public static void main(String[] args) {
- int[] arr = {1, 2, 3};
- func(arr);
- System.out.println("arr[0] = " + arr[0]);
- }
-
- public static void func(int[] a) {
- a[0] = 10;
- System.out.println("a[0] = " + a[0]);
- }
- // 执行结果
- a[0]=10
- arr[0]=10
通过形参改变对象对进而实参产生影响
- public class TestArray {
- public static int[] fib(int n) {
- if (n <= 0) {
- return null;
- }
- int[] array = new int[n];
- array[0] = array[1] = 1;
- for (int i = 2; i < n; ++i) {
- array[i] = array[i - 1] + array[i - 2];
- }
- return array;
- }
-
- public static void main(String[] args) {
- int[] array = fib(10);
- for (int i = 0; i < array.length; i++) {
- System.out.println(array[i]);
- }
- }
- }
对象一定是在堆上的,而引用不一定在栈上
Arrays.toString(数组名),将数组转化成字符串
使用前要导入包 java.util.Arrays
模拟实现toString
ctrl + 鼠标点击 能查看java源码
1.
2.
3.
- public static void main(String[] args) {
- int[] arr = {1,2,3,4,5,6};
- System.out.println(avg(arr));
- }
- public static double avg(int[] arr) {
- int sum = 0;
- for (int x : arr) {
- sum += x;
- }
- return (double)sum / (double)arr.length;
- }
- // 执行结果
- 3.5
- public static int findVal(int[] arr, int k) {
- for (int i = 0; i < arr.length; i++) {
- if (arr[i] == k) {
- return i;
- }
- }
- return -1;
- }
有序数组才能二分查找,排序就用Arrays.sort
- public static int binarySearch(int[] arr, int k) {
- int left = 0;
- int right = arr.length - 1;
- while (left <= right) {
- int mid = (left + right) / 2;
- if (arr[mid] > k) {
- right = mid - 1;
- } else if (arr[mid] < k) {
- left = mid + 1;
- } else {
- return mid;
- }
- }
- return -1;
- }
通过Arrays.binarySearch也能查找指定元素
使用Arrays.equals()能够比较两个数组对应位置的数据是否一样
填充数组用Arrays.fill()
- public static void bubbleSort(int[] arr) {
- //冒泡排序趟数
- for (int i = 0; i < arr.length - 1; i++) {
- boolean flag = true;
- //一趟冒泡排序
- for (int j = 0; j < arr.length - 1 - i; j++) {
- if (arr[j] > arr[j + 1]) {
- int tmp = arr[j];
- arr[j] = arr[j + 1];
- arr[j + 1] = tmp;
- flag = false;
- }
- }
- if (flag == true) {
- break;
- }
- }
- }
给定一个数组, 将里面的元素逆序排列
- public static void reverse(int[] arr) {
- int left = 0;
- int right = arr.length - 1;
- while (left < right) {
- int tmp = arr[left];
- arr[left++] = arr[right];
- arr[right--] = tmp;
- }
- }
二维数组就是一维数组的集合
二维数组列可以省略,行不可以省略
数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };
所以arr由2(行)个一维数组组成,每个一维数组含有3(列)个元素。
打印二维数组用Arrays.deepToString
会抛异常,因为没有列,所以二维数组中的一维数组(引用)没有指向对象,所以是null
面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情
面向过程:亲力亲为,注重过程
面向对象:通过对象交互来完成一件事
对象的产生依赖于类
类:自定义类型,和C语言的结构体类似
类是用来对一个实体(对象)来进行描述的
通过class定义一个类
成员变量是定义在类里边,方法外边
成员变量和成员方法都得用public修饰
类名一定要采用大驼峰
此处写的方法不带 static 关键字
2.3.1 定义一个狗类
2.3.2 定义一个学生类
1. 一般一个文件当中只定义一个类
2. main方法所在的类一般要使用public修饰(注意:Eclipse默认会在public修饰的类中找main方法)
3. public修饰的类必须要和文件名相同
4. 不要轻易去修改public修饰的类的名称,如果要修改,通过开发工具修改
修改类名:
只适用于一个java文件只有一个类的情况
用类类型创建对象的过程,称为类的实例化
在java中采用new关键字,配合类名来实例化对象
如何访问对象的属性?
通过对象的引用访问对象的属性。
对象的成员属性在没有赋值时,引用类型的默认值是null,简单类型是对应的0
1. 类只是一个模型一样的东西,用来对一个实体进行描述,限定了类有哪些成员.
2. 类是一种自定义的类型,可以用来定义变量.
3. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
4. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间
- public class Date {
- public int year;
- public int month;
- public int day;
-
- public void setDay(int y, int m, int d) {
- year = y;
- month = m;
- day = d;
- }
-
- public void printDate() {
- System.out.println(year + "/" + month + "/" + day);
- }
-
- public static void main(String[] args) {
- // 构造三个日期类型的对象 d1 d2 d3
- Date d1 = new Date();
- Date d2 = new Date();
- Date d3 = new Date();
- // 对d1,d2,d3的日期设置
- d1.setDay(2020, 9, 15);
- d2.setDay(2020, 9, 16);
- d3.setDay(2020, 9, 17);
- // 打印日期中的内容
- d1.printDate();
- d2.printDate();
- d3.printDate();
- }
- }
形参名不小心与成员变量名相同
- public void setDay(int year, int month, int day) {
- year = year;
- month = month;
- day = day;
- }
再传值,打印后发现并没有给year,month,day赋值
因为局部变量优先的原则,导致了赋值的失败,
但是在前面加上this,就不会发生这种事情。
- public void setDay(int year, int month, int day) {
- this.year = year;
- this.month = month;
- this.day = day;
- }
所以,为了区分成员变量,就得使用this(平常也建议使用this)
this就代表当前对象的引用(地址),谁调用,谁就是this
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。
this就代表当前对象的引用(地址),谁调用,谁就是this
this引用的是调用成员方法的对象
通过this可以访问当前对象的成员属性(静态的成员变量不支持)
1. this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
2. this只能在"成员方法"中使用
3. 在"成员方法"中,this只能引用当前对象,不能再引用其他对象
4. this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收
局部变量必须要初始化才能使用,但是对象不用
当成员变量没有被初始化时,引用类型默认是null,基本类型默认是对应的0值,比如boolean是false,char是'\u0000',int是0
1.方法名必须和类名相同
2.构造方法没有返回值,写成void也不行
Student就是一个构造方法,带参数的构造方法可以对成员变量进行初始化
当一个类中存在一个及以上的构造方法时,java就不会再给你提供构造方法了
构造方法之间是可以构成方法重载的
5.2.1 概念
用来初始化对象的方法,得加public修饰
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次(一次性用品)
先调用完成构造方法,才会产生对象
构造方法并不负责给对象开辟空间。
完成一个对象的构造,分两步:
1.开辟内存
2.调用合适的构造方法
5.2.2 特性
1. 名字必须与类名相同
2. 没有返回值类型,设置为void也不行
3. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
4. 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
5. 如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的
6. 构造方法中,可以通过this调用其他构造方法来简化代码
注意:
this本身 代表当前对象的引用,有三个作用:
代码错误,不可以形成环
7. 绝大多数情况下使用public来修饰,特殊场景下会被private修饰
new对象时,先分配内存,再调用构造方法
当成员变量没有被初始化时,引用类型默认为null,其他类型默认为对应的0值
初始化类时,直接给成员变量赋值
面向对象程序三大特性:封装、继承、多态。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互
封装:对类的成员进行隐藏,通过关键字private,只是对类外提供公开的接口。
可以隐藏类的实现细节,从而达到安全性
快速写set和get方法:
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。
Java中提供了四种访问限定符:
default是默认(包访问权限),比如:
啥也不加,就是默认
public:可以理解为一个人的外貌特征,谁都可以看得到
default: 对于自己家族中(同一个包中)不是什么秘密,对于其他人来说就是隐私了
private:只有自己知道,其他人都不知道
6.3.1 包的概念
为了更好的管理类,把多个类收集在一起成为一组,称为软件
包。有点类似于目录。包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
6.3.2 导入包中的类
使用 import语句导入包中的类
声明当前java文件是在哪个包底下,用package
import java.util.*;//导入所有类
更建议显式的指定要导入的类名. 否则还是容易出现冲突
可以使用import static导入包中静态的方法和字段
6.3.3 自定义包
基本规则
操作步骤
1. 在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包
2. 在弹出的对话框中输入包名, 例如 com.bit.demo1
3. 在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可
6.3.4 包的访问权限控制举例
6.3.5 常见的包
1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
2. java.lang.reflflect:java 反射编程包;
3. java.net:进行网络编程开发包。
4. java.sql:进行数据库开发的支持包。
5. java.util:是java提供的工具程序包。(集合类等) 非常重要
6. java.io:I/O编程开发包。
被static修饰的成员变量,称为静态成员变量,
static只能修饰成员变量和成员方法,不能修饰局部变量
静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。也就是static修饰的变量或方法是不依赖于对象的,所以static修饰的变量或方法中不能出现this和super,存储于方法区。
【静态成员变量特性】
1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
3. 类变量存储在方法区当中
4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
非静态成员变量都是属于对象的
静态成员变量的使用不依赖于对象
Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的
【静态方法特性】
1. 不属于某个具体的对象,是类方法
2. 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者
3. 不能在静态方法中直接访问任何非静态成员变量,但是可以通过对象的引用来访问普通成员方法
4. 静态方法中不能直接调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用
static方法里面不能使用this
普通成员方法通过对象的引用调用
静态成员方法通过类名来调用
注意:静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性。
1. 就地初始化
就地初始化指的是:在定义时直接给出初始值
2. 静态代码块初始化
使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:
普通代码块:定义在方法中的代码块
构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。
实例代码块先于构造方法执行
使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量
注意事项
先执行静态代码块,再执行构造代码块,最后执行构造方法
如果都是静态的,那么就看它们的定义顺序
只要类被加载,静态代码块就一定会被执行
内部类还没学,学了之后再补上来
内部类的分类:实例内部类,静态内部类,局部内部类,匿名内部类
9.1.1 实例内部类
没被static修饰的内部类就是实例内部类
先有外部类对象,再有实例内部类对象
创建实例内部类对象,依赖于外部类对象
在实例内部类中可以定义static成员,但是该成员必须由static final修饰
加了final之后就成了常量,不需要类加载,编译时就可得知大小
当外部类的成员变量与内部类成员变量同名时,
想要调用外部类的就得通过外部类类名.this.同名的变量
实例内部类中是 包含 外部类的this的
【注意事项】
1. 外部类中的任何成员都可以在实例内部类方法中直接访问
2. 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束
3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问
4. 实例内部类对象必须在先有外部类对象前提下才能创建
5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用
6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
- class OuterClass {
- public int data1;
- private int data2;
- public static int data3;
-
- class InnerClass {
- public int data4 = 4;
- private int data5 = 5;
- //public static int data6 = 6;//err
-
- public void test() {
- System.out.println(data4);
- System.out.println(data5);
- System.out.println("实例内部类的test方法");
- }
- }
-
- public void test() {
- System.out.println("外部类的test方法");
- }
- }
-
- public class Test2 {
- public static void main(String[] args) {
-
- //创建实例内部类对象,依赖于外部类对象
- OuterClass outerClass = new OuterClass();
- OuterClass.InnerClass innerClass = outerClass.new InnerClass();//方法1
- OuterClass.InnerClass innerClass2 = new OuterClass().new InnerClass();//方法2
- }
-
- }
9.1.2 静态内部类
被static修饰的类就是静态内部类
初始化静态内部类对象: new 外部类类名.静态内部类类名();
【注意事项】
1. 在静态内部类中只能访问外部类中的静态成员
如果确实想访问,通过外部类对象的引用
2. 创建静态内部类对象时,不需要先创建外部类对象
定义在外部类的方法内部的类叫做局部内部类,该种内部类只能在其定义的位置使用
【注意事项】
1. 局部内部类只能在所定义的方法体内部使用
2. 不能被public、static等修饰符修饰
3. 编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class
4. 几乎不会使用
调用testA方法1:
调用testA方法2:
在匿名内部类中,能够访问的是 没有被修改过的数据(常量,被final修饰的) -> 变量的捕获
匿名内部类字节码文件名:外部类类名$数字.class
为了简化代码,实现代码的复用,跟C语言的函数类似,只用写一份代码,有需要就调用(Java是继承父类)这个代码,不用写重复的多份代码
进行共性抽取,实现代码复用。
继承:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类(被叫做父类,基类,超类)特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类,也叫子类。
举个例子:比如香蕉和水果,它们的共性就是都有形状,大小,颜色,香蕉是水果的一种,那么就可以让香蕉(子类)继承水果(父类),只写一份父类的代码,让子类继承,实现代码复用。
例子2:狗和猫 动物
继承意义:对共性抽取,实现代码复用
抽取到父类里面的一定是子类的共性
如果子类和父类是 is- a(是一种)的关系,就要继承
extends是继承的意思
修饰符 class 子类 extends 父类 {
// ...
}
- class Fruit {
- String shape;
- String size;
- String color;
- }
-
- //修饰符 class 子类 extends 父类
- class Banana extends Fruit {
- String type;
- int price;
- }
这样,子类就继承了父类的非静态的成员变量和非静态成员方法,就可以通过子类调用父类的成员方法和成员变量了。
再比如:狗和猫,
- class Animal {
- String name;
- String color;
- int age;
-
- public void eat() {
- System.out.println(this.name + " 正在吃饭!");
- }
- }
-
- class Dog extends Animal {
-
- public void bark() {
- System.out.println(this.name + " 正在汪汪叫!");
- }
- }
-
- class Cat extends Animal {
-
- public void mew() {
- System.out.println(this.name + " 正在喵喵叫!");
- }
- }
-
- public class Test {
- public static void main(String[] args) {
- Dog dog = new Dog();
- dog.name = "旺财";
- dog.eat();
- dog.bark();
- System.out.println("=========");
- Cat cat = new Cat();
- cat.name = "咪咪";
- cat.eat();
- cat.mew();
- }
- }
注意:
1. 子类会将父类中的非静态的 成员变量或者成员方法 继承到子类中了。
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了。
1.4.1 子类中访问父类的成员变量
优先在子类找,子类没有的话就去父类找,如果父类也没有,就会报错。也就是遵循就近原则
1. 子类和父类不存在同名成员变量
2. 子类和父类成员变量同名
子类和父类成员变量同名时,在子类访问这个同名的成员变量,访问的是子类的。优先访问子类自己的。
- class Base {
- int a;
- int b;
- int c;
- }
-
- class Derived extends Base {
- int a; // 与父类中成员a同名,且类型相同
- char b; // 与父类中成员b同名,但类型不同
-
- public void method() {
- a = 100; // 访问子类自己的a
- b = 101; // 访问子类自己的b,存的是ASCII码值
- c = 102; // 子类没有c,访问的肯定是从父类继承下来的c
- // d = 103; // 编译失败,因为父类和子类都没有定义成员变量d
- System.out.println(b);
- }
- }
-
- public class Test {
- public static void main(String[] args) {
- Derived derived = new Derived();
- derived.method();
- }
- }
总结:先去子类找,没有就去父类找,父类还找不到就直接报错
如果一定要访问父类的a,就要用到关键字super
1.4.2 子类中访问父类的成员方法
成员方法也同理,先去子类找,再去父类找,找不到就报错
和this用法非常类似。super就是一个关键字,让人一看到就知道是调用父类成员的。
super作用:在子类方法中访问父类的成员。
super在当前类中使用,那么当前类一定是子类。
- class Base {
- public int a = 1;
- public int b = 2;
- }
-
- class Derived extends Base {
- public int c = 3;
-
- public void func() {
- /*super.a = 10;//这里的a就是父类的a
- b = 20;
- c = 30;*/
- System.out.println(super.a);
- }
- }
-
- public class Test2 {
- public static void main(String[] args) {
- Derived derived = new Derived();
- derived.func();
- }
- }
this能访问父类和子类的成员变量和成员方法,super只能访问父类的
当使用this访问父类和子类同名的变量时,子类优先访问
【注意事项】
1. 只能在非静态方法中使用
2. 在子类方法中,访问父类的成员变量和方法。
3. 在子类构造方法中,super(...)来调用父类构造方法
父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
子类要先帮助父类构造,然后子类自己再来构造。
只能在子类中调用父类的构造方法。
- class Animal {
- String name;
- String color;
- int age;
-
- public Animal(String name) {
- this.name = name;
- }
-
- public void eat() {
- System.out.println(this.name + " 正在吃饭!");
- }
- }
-
- class Dog extends Animal {
-
- public Dog(String name) {
- super(name);
- }
-
- public void bark() {
- System.out.println(this.name + " 正在汪汪叫!");
- }
- }
-
- class Cat extends Animal {
-
- public Cat(String name) {
- super(name);
- }
-
- public void mew() {
- System.out.println(this.name + " 正在喵喵叫!");
- }
- }
子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
注意:
1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法。
2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现。
【相同点】
1. 都是Java中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段他。
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在。
【不同点】
1. this是当前对象的引用,当前对象即调用实例方法的对象,super只是一个关键字,让人一看到就知道是调用父类成员的。
2. 在非静态成员方法中,this用来访问本类(父类和子类)的方法和属性,super用来访问父类继承下来的方法和属性。
3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现。
4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有。
先是执行静态代码块,再执行实例代码块,最后执行构造方法。
1. 静态代码块先执行,并且只执行一次,在类加载阶段执行
2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行。
【继承关系上的执行顺序】
一直遵循着先父再子,子类会帮助父类完成构造之后,子类自己再构造。
静态代码块一定是最早执行的!且只执行一次!
根据以上,不难得出:
执行顺序:
父类静态代码块>子类静态代码块>父类实例代码块>父类构造方法>子类实例代码块>子类构造方法
父静子静,父实父构造,子实子构造
protected关键字主要解决的就是继承在不同包里的子类问题。
注意:Java中不支持多继承。
final关键可以用来修饰变量、成员方法以及类。
final,最终的意思,让修饰的变量不能被修改
如果一个类不想被继承的话,就得使用final关键字进行修饰
1. 修饰变量或字段,表示常量(即不能修改)
final修饰引用变量,引用变量里面存的是地址,被final修饰之后,将不能修改地址,也就是不能让arr指向一个新的对象。
2. 修饰类:表示此类不能被继承
被final修饰的类叫密封类
3. 修饰方法:表示该方法不能被重写
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a/a part of 的关系,比如:
学生和老师都是学校的一部分
用别的对象作为成员。组合就是代码的实现方式。
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
总结:
条件:
1.向上转型
2.重写方法
3.通过父类引用调用子类对象重写的方法
多态就是不同对象调用重写的方法表现行为会不相同,多态是一种思想。
动态绑定是多态的基础。
动态绑定:1.向上转型 2.通过父类引用调用方法重写 结果是调用子类重写的方法,这个过程就叫做运行时绑定,也叫动态绑定。
重写:也称为覆盖。重写是子类对父类非静态、非private修饰,非fifinal修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
条件:
1.方法名相同
2.参数列表(类型,个数,顺序)相同
3.返回值相同,返回值也可以是父子关系的
重写需要注意事项:
被static,privat,final修饰的方法不能重写
构造方法也不能重写
重写方法时,子类的重写方法访问权限必须大于父类的重写方法访问权限,private < 默认权限 < protected < public
方法的返回值可以不同,但必须是父子类关系
重写的方法可以用@override来标注,有利于编译器检查
静态绑定:方法重载
通过方法的参数等,在编译时就能知道调用的是哪个方法
动态绑定:方法重写
动态绑定就是编译前看不出调用哪个方法
静态绑定就是编译前就能看出调用的方法
2.4.1 向上转型
向上转型就是:父类引用 引用了 子类对象
语法格式:父类类型 对象名 = new 子类类型()
【有三种方式可以方式向上转型】
1. 直接赋值
2. 方法传参
3. 方法返回
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法,只能调用父类自己的方法。
- class Animal {
- public String name;
- public int age;
-
- public Animal(String name, int age) {
- this.name = name;
- this.age = age;
- }
-
- public void eat() {
- System.out.println(this.name + " 正在吃...");
- }
- }
-
- class Dog extends Animal {
- public Dog(String name, int age) {
- super(name, age);
- }
-
- public void wangWang() {
- System.out.println(this.name + " 正在汪汪!");
- }
-
- }
-
- class Bird extends Animal {
- public Bird(String name, int age) {
- super(name, age);
- }
-
- public void fly() {
- System.out.println(this.name + " 正在飞!");
- }
-
- }
-
- public class Test {
-
- public static void main(String[] args) {
- Animal animal = new Dog("旺财", 10);
- animal.wangWang();
-
- }
- }
2.4.2 向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转型。
向下转型非常不安全!
所以向下转型必须做检查,通过instanceof
- class Shape {
- public void draw() {
- System.out.println("画图形...");
- }
- }
-
- class Rect extends Shape {
- @Override
- public void draw() {
- System.out.println("矩形");
- }
- }
-
- class Circle extends Shape {
- @Override
- public void draw() {
- System.out.println("圆");
- }
- }
-
- class Flower extends Shape {
- @Override
- public void draw() {
- System.out.println("❀");
- }
- }
-
- public class Test {
- public static void drawMap(Shape shape) {
- shape.draw();
- }
-
- public static void main(String[] args) {
- Circle circle = new Circle();
- Rect rect = new Rect();
- Flower flower = new Flower();
- drawMap(circle);
- drawMap(rect);
- drawMap(flower);
- }
- }
【使用多态的好处】
1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
- class Shape {
- public void draw() {
- System.out.println("画图形...");
- }
- }
-
- class Rect extends Shape {
- @Override
- public void draw() {
- System.out.println("矩形");
- }
- }
-
- class Circle extends Shape {
- @Override
- public void draw() {
- System.out.println("圆");
- }
- }
-
- class Flower extends Shape {
- @Override
- public void draw() {
- System.out.println("❀");
- }
- }
- public class Test {
- public static void drawMaps1() {
- Circle circle = new Circle();
- Rect rect = new Rect();
- Flower flower = new Flower();
- String[] maps = {"Circle", "Rect", "Circle", "Rect", "Flower"};
- for (int i = 0; i < maps.length; i++) {
- if (maps[i].equals("Circle")) {
- circle.draw();
- } else if (maps[i].equals("Rect")) {
- rect.draw();
- } else {
- flower.draw();
- }
- }
- }
- }
- class Shape {
- public void draw() {
- System.out.println("画图形...");
- }
- }
-
- class Rect extends Shape {
- @Override
- public void draw() {
- System.out.println("矩形");
- }
- }
-
- class Circle extends Shape {
- @Override
- public void draw() {
- System.out.println("圆");
- }
- }
-
- class Flower extends Shape {
- @Override
- public void draw() {
- System.out.println("❀");
- }
- }
-
- public class Test {
-
- public static void drawMaps2() {
- Shape[] shape = {new Circle(), new Rect(), new Circle(),
- new Rect(), new Flower()};
- for (int i = 0; i < shape.length; i++) {
- shape[i].draw();
- }
- }
-
- public static void main(String[] args) {
- drawMaps2();
- }
- }
2. 可扩展能力更强
多态缺陷:代码的运行效率降低。
1. 属性没有多态性
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
2. 构造方法没有多态性
下面代码运行结果是什么?
- class B {
- public B() {
- // do nothing
- func();
- }
-
- public void func() {
- System.out.println("B.func()");
- }
- }
-
- class D extends B {
- private int num = 1;
-
- @Override
- public void func() {
- System.out.println("D.func() " + num);
- }
- }
-
- public class Test {
- public static void main(String[] args) {
- D d = new D();
- }
- }
结果:D.func() 0
因为new D ,D 又继承了 B,所以会先调用完父类B的构造方法之后,子类D才开始构造,又因为父类B的func被子类D的func重写,所以会调用子类D的func方法,但是父类的构造方法还没执行完,因此子类的num还没有初始化,所以默认是0,所以打印D.func 0
所以在构造函数内,尽量避免使用实例方法,除了fifinal和private方法
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。
- abstract class Shape {
- public abstract void draw();
- //被abstract修饰的方法叫做抽象方法,抽象方法可以没有具体实现
- }
-
- class Rect extends Shape {
- @Override
- public void draw() {
- System.out.println("矩形");
- }
- }
-
- class Flower extends Shape {
- @Override
- public void draw() {
- System.out.println("❀");
- }
- }
-
- class Circle extends Shape {
- @Override
- public void draw() {
- System.out.println("圆");
- }
- }
-
- public class Test {
- public static void drawMap(Shape shape) {
- shape.draw();
- }
-
- public static void main(String[] args) {
- drawMap(new Rect());
- //new Rect() -> 匿名对象 没有名字的对象
- //匿名对象的缺点是:每次使用都得重新实例化
- }
- }
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
1.抽象类是被abstract修饰的
2.被abstract修饰的方法叫抽象方法,该方法可以没有具体实现
3.当一个类含有抽象方法的时候,该类必须使用abstract修饰
4.抽象类中可以有和普通类一样的成员变量和成员方法
5.抽象类不能被实例化
6.抽象类的存在意义是:为了被继承
7.当普通类继承抽象类之后,普通类必须重写抽象类中所有的抽象方法
8.final / private / static 和abstract是不能同时存在的
9.当一个抽象类A不想被普通类B继承,可以把B变成抽象类,那么当一个普通类C继承抽象类B时,普通类C要重写抽象类A和抽象类B里面所有的抽象方法
10.抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
11. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法。
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。
提示:
1. 创建接口时, 接口的命名一般以大写字母 I 开头.
2. 接口的命名一般使用 "形容词" 词性的单词.
3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
- interface IShape {
- int a = 10;
-
- void draw();
-
- default void test() {
- System.out.println("test");
- }
-
- public static void func() {
- System.out.println("static");
- }
- }
-
- class Rect implements IShape {
- @Override
- public void draw() {
- System.out.println("矩形");
- }
- }
-
- class Flower implements IShape {
- @Override
- public void draw() {
- System.out.println("❀");
- }
- }
-
- public class Test {
- public static void drawMap(IShape ishape) {
- ishape.draw();
- }
-
- public static void main(String[] args) {
- drawMap(new Rect());
- drawMap(new Flower());
- }
- }
1.使用interface来定义一个接口
2.接口当中的成员变量默认是public static final的,一般情况下我们不写
3.接口当中的成员方法默认是public abstract的,一般情况下我们不写
4.接口当中 不可以有普通的方法
5.Java8开始,允许定义一个default方法,default方法可以有具体实现
6.接口当中被static修饰的方法,是可以有具体的实现的
7.接口不能通过new关键字进行实例化
8.类和接口之间 可以通过关键字implements来实现接口,要重写接口中的抽象方法
9.接口也可以发生向上转型和动态绑定
10.当一个类 实现接口当中的方法之后,当前类当中的方法必须加public
11.接口当中不能有构造方法和代码块
12.一个接口也会产生独立的字节码文件
13.如果类没有实现接口中所有的抽象方法,那么该类要设置为抽象类
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。
父类里放的是共性,而接口中放的是特性
一定是先继承(extends)再实现(implements)
在java中,一个类只能继承一个类,实现多个接口
Alt + 回车,能快速重写方法
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 .
注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。
接口可以继承多个接口, 达到复用的效果. 使用 extends 关键字.接口间的继承相当于把多个接口合并在一起。
- interface A {
- void testA();
- }
-
- interface B {
- void testB();
- }
-
- //有一个接口C,同时具备A和B的功能,extends 拓展
- interface C extends A, B {
- void testC();
- }
-
-
- public class Test implements C {
- @Override
- public void testA() {
- System.out.println("testA()");
- }
-
- @Override
- public void testB() {
- System.out.println("testB()");
- }
-
- @Override
- public void testC() {
- System.out.println("testC()");
- }
- }
自定义类型要想比较大小,就得实现Comparable接口,
并且要重写compareTo方法,来实现比较的逻辑
例如:
给对象数组排序
同样的,也要先实现Comparable接口,重写compareTo方法
再来通过Arrays.sort(数组名)来排序。
- import java.util.Arrays;
-
- class Student implements Comparable<Student> {
- public String name;
- public int age;
-
- public Student(String name, int age) {
- this.name = name;
- this.age = age;
- }
-
- @Override
- public String toString() {
- return "Student{" +
- "name='" + name + '\'' +
- ", age=" + age +
- '}';
- }
-
- @Override
- public int compareTo(Student o) {
- return this.age - o.age;
- }
- }
-
- public class Test {
- public static void main(String[] args) {
- /*Student student1 = new Student("张三", 10);
- Student student2 = new Student("李四", 15);
- //Student student3 = new Student("王五", 18);
- System.out.println(student1.compareTo(student2));*/
-
- Student[] students = {new Student("张三", 12),
- new Student("李四", 10),
- new Student("王五", 18)};
- Arrays.sort(students);
- System.out.println(Arrays.toString(students));
- }
-
- }
用冒泡排序来排序数组:
- import java.util.Arrays;
-
- class Student implements Comparable<Student> {
- public String name;
- public int age;
-
- public Student(String name, int age) {
- this.name = name;
- this.age = age;
- }
-
- @Override
- public String toString() {
- return "Student{" +
- "name='" + name + '\'' +
- ", age=" + age +
- '}';
- }
-
- @Override
- public int compareTo(Student o) {
- return this.age - o.age;
- }
- }
-
- public class Test {
-
- public static void bubbleSort(Comparable[] comparable) {
- for (int i = 0; i < comparable.length - 1; i++) {
- for (int j = 0; j < comparable.length - 1 - i; j++) {
- if (comparable[j].compareTo(comparable[j + 1]) > 0) {
- Comparable tmp = comparable[j];
- comparable[j] = comparable[j + 1];
- comparable[j + 1] = tmp;
- }
- }
- }
- }
-
- public static void main(String[] args) {
- /*Student student1 = new Student("张三", 10);
- Student student2 = new Student("李四", 15);
- //Student student3 = new Student("王五", 18);
- System.out.println(student1.compareTo(student2));*/
-
- Student[] students = {new Student("张三", 12),
- new Student("李四", 10),
- new Student("王五", 18)};
- bubbleSort(students);
- //Arrays.sort(students);
- System.out.println(Arrays.toString(students));
- }
-
- }
但是Comparable接口对类的侵入性比较强。
- import java.util.Comparator;
- class Student {
- public String name;
- public int age;
-
- public Student(String name, int age) {
- this.name = name;
- this.age = age;
- }
-
- @Override
- public String toString() {
- return "Student{" +
- "name='" + name + '\'' +
- ", age=" + age +
- '}';
- }
-
- }
-
- //比较器,优点:对类的侵入性不强
- class AgeComparator implements Comparator<Student> {
- @Override
- public int compare(Student o1, Student o2) {
- return o1.age - o2.age;
- }
- }
-
- class NameComparator implements Comparator<Student> {
- @Override
- public int compare(Student o1, Student o2) {
- return o1.name.compareTo(o2.name);
- }
- }
-
- public class Test {
-
- public static void main(String[] args) {
- Student student1 = new Student("张三", 10);
- Student student2 = new Student("李四", 15);
-
- AgeComparator ageComparator = new AgeComparator();
- System.out.println(ageComparator.compare(student1, student2));
- System.out.println("================");
-
- NameComparator nameComparator = new NameComparator();
- System.out.println(nameComparator.compare(student1, student2));
- }
- }
Java 中内置了一些很有用的接口, Clonable 就是其中之一.
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 "拷贝". 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛CloneNotSupportedException 异常.
浅拷贝不会将对象里的对象进行克隆
深拷贝会拷贝对象里所有的对象
深拷贝和浅拷贝看的是代码的实现过程
浅拷贝例子:
- class Money {
- public double money = 19.9;
- }
-
- class Person implements Cloneable {
- public int age;
- public Money m;
-
- public Person(int age) {
- this.age = age;
- this.m = new Money();
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public String toString() {
- return "Person{" +
- " age=" + age +
- '}';
- }
- }
-
- public class Test2 {
- public static void main(String[] args) throws CloneNotSupportedException {
- Person person1 = new Person(10);
- Person person2 = (Person) person1.clone();
- System.out.println(person1.m.money);
- System.out.println(person2.m.money);
- System.out.println("=============");
- person2.m.money = 99.99;
- System.out.println(person1.m.money);
- System.out.println(person2.m.money);
- }
- }
深拷贝例子:
- class Money implements Cloneable {
- public double money = 19.9;
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
- }
-
- class Person implements Cloneable {
- public int age;
- public Money m;
-
- public Person(int age) {
- this.age = age;
- this.m = new Money();
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- Person tmp = (Person) super.clone();
- tmp.m = (Money) this.m.clone();
- return tmp;
- }
-
- @Override
- public String toString() {
- return "Person{" +
- " age=" + age +
- '}';
- }
- }
-
- public class Test2 {
- public static void main(String[] args) throws CloneNotSupportedException {
- Person person1 = new Person(10);
- Person person2 = (Person) person1.clone();
- System.out.println(person1.m.money);
- System.out.println(person2.m.money);
- System.out.println("=============");
- person2.m.money = 99.99;
- System.out.println(person1.m.money);
- System.out.println(person2.m.money);
- }
- }
核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法
Object类是所有类的父类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。
所以在开发之中,Object类是参数的最高统一类型
我们主要来熟悉这几个方法:toString()方法,equals()方法,hashcode()方法
- class Person {
- public String name;
- public int age;
-
- public Person(String name, int age) {
- this.name = name;
- this.age = age;
- }
-
- @Override
- public String toString() {
- return "Person{" +
- "name='" + name + '\'' +
- ", age=" + age +
- '}';
- }
- }
-
- public class Test {
- public static void main(String[] args) {
- Person person1 = new Person("张三", 10);
- System.out.println(person1);
- }
- }
如果是以后 自定义的类型,那么一定记住重写equals方法
- import java.util.Objects;
-
- class Person {
- public String name;
- public int age;
-
- public Person(String name, int age) {
- this.name = name;
- this.age = age;
- }
-
- @Override
- public String toString() {
- return "Person{" +
- "name='" + name + '\'' +
- ", age=" + age +
- '}';
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Person person = (Person) o;
- return age == person.age && Objects.equals(name, person.name);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(name, age);
- }
- }
-
- public class Test {
- public static void main(String[] args) {
- Person person1 = new Person("张三", 10);
- System.out.println(person1);
- Person person2 = new Person("张三", 10);
- System.out.println(person2);
- System.out.println("=============");
-
- //此时比较的是变量中的值(地址)
- System.out.println(person1 == person2);
-
- System.out.println(person1.equals(person2));
-
- }
- }
快速重写equals方法:
1.鼠标右键,点Generate
2.点这个
3.一路点Next,最后就重写了equals和hashCode
结论:比较对象中内容是否相同的时候,一定要重写equals方法
两个一样的对象,想放在同一个位置,就可以利用重写hashCode方法来做。
结论:
1、hashcode方法用来确定对象在内存中存储的位置是否相同
2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
在Java当中,String是不可变的,其所有的方法都不是在原来的字符串上转换,而是会生成新的对象。
区别于C语言,在Java中 字符串没有以\0结尾的说法
【注意】
1. String是引用类型,内部并不存储字符串本身,在String类的实现源码中,String类实例变量如下:
存的是字符数组和哈希
2. 在Java中“”引起来的就是String类型对象。
.length()求字符串长度
.isEmpty()判断字符串是否为空
1. ==比较是否引用同一个对象
注意:对于内置类型,==比较的是变量中的值;对于引用类型==比较的是引用中的地址。
这种形式就相当于C语言的字符数组,当然是不一样的。
因为str1是个引用,str2也是个引用,每次new都会开辟新的内存,地址也就不一样。
这种形式就相当于C语言的常量字符串,因为是相同的东西所以只存一份来节省内存,提高存储效率。在java的堆中有个字符串常量池,用来存放字符串常量(直接被""引起来的字符串)存的是字符串的常量值 所以str1和str2是相等的。
如果要比较两个引用的内容是否一样的话,一定要重写equals方法
建议自定义类型一定要重写hashcode和equals方法
2. boolean equals(Object anObject) 方法:按照字典序比较
判断字符串的内容是否相同的话要用equals
3. int compareTo(String s) 方法: 按照字典序进行比较
和C语言的strcmp类似,比较的是对应字符的ASCII码值,谁的大,谁就大。
str1>str2,返回正数,等于返回0,小于返回负数。
4. int compareToIgnoreCase(String str) 方法:
与compareTo方式相同,但是忽略大小写比较
.charAt():访问对应下标的字符
indexOf(int ch):返回字符ch第一次出现的下标
lastIndexOf():从后面开始往前找,跟indexOf类似
1. 数值和字符串转化
调用valueOf()
- // 字符串转数字
- // 注意:Integer、Double等是Java中的包装类型
- int data1 = Integer.parseInt("1234");
- double data2 = Double.parseDouble("12.34");
- System.out.println(data1);
- System.out.println(data2);
2. 大小写转换
- public static void main(String[] args) {
- String s1 = "hello";
- String s2 = "HELLO";
- // 小写转大写
- System.out.println(s1.toUpperCase());
- // 大写转小写
- System.out.println(s2.toLowerCase());
- }
3. 字符串转数组
toCharArray():将字符串转化为字符数组
4. 格式化
跟printf有点像
注意事项: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串
String[] split(String regex) :将字符串全部拆分
分割之后的结果得存储到数组当中
与C语言的strtok类似
String[] split(String regex, int limit) :将字符串以指定的格式,拆分为limit组
- String str = "192.168.1.1" ;
- String[] result = str.split("\\.") ;
- for(String s: result) {
- System.out.println(s);
- }
注意事项:
1. 字符"|","*","+"都得加上转义字符,前面加上 "\\" .
2. 而如果是 "\" ,那么就得写成 "\\\\" .
3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符.
String substring(int beginIndex) :从指定索引截取到结尾
如果传入0下标,默认返回原来的对象,否则,返回的是新的对象
String substring(int beginIndex, int endIndex) :截取部分内容(左闭右开)
String trim() :去掉字符串中的左右两边的空格,保留中间空格
1. String类在设计时就是不可改变的,String类实现描述中已经说明了
1. String类被final修饰,表明该类不能被继承
2. value被修饰被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。
因为private修饰了value,而在类外又拿不到value,所以就修改不了value
final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。
注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下。
尽量避免对String的直接需要,如果要修改建议尽量使用StringBuffer或者StringBuilder。
由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffffer类。这两个类大部分功能是相同的。
StringBuilder和StringBuffer不能直接赋值,必须得通过new来创建对象。
创建一个stringBuilder对象
两个类的方法基本一样。
append方法:相当于+=
setCharAt方法:将index下标的值改为对应字符
insert方法:在index下标处插入
reverse方法:反转字符串
toString:转换成String类的对象
StringBuilder和StringBuffer的区别:
StringBuffer的某些方法会有synchronized修饰。
String和StringBuffer最大的区别在于String的内容无法修改,而StringBuffer和StringBuilder的内容可以修改。频繁修改字符串的情况考虑使用StringBuffer或者StringBuilder。
注意:
String和StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:
String变为StringBuffer: 利用StringBuffer的构造方法或append()方法。
StringBuffer变为String: 调用toString()方法。
StringBuilder同理。
1. String、StringBuffer、StringBuilder的区别
2. 以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】
不知道,没学常量池和哈希表,学完后回来补
练习:
- class Solution {
- public int firstUniqChar(String str) {
- int[] arr = new int[26];//存每个字符出现的次数
- for (int i = 0; i < str.length(); i++) {
- char ch = str.charAt(i);
- arr[ch - 'a']++;
- }
- for (int i = 0; i < str.length(); i++) {
- char ch = str.charAt(i);//从str的第一个字符开始找
- if (arr[ch - 'a'] == 1) {
- return i;//顺着找直到找到第一次出现的字符
- }
- }
- return -1;//找不到返回-1
- }
- }
2.
- class Solution {
- public static boolean isLegal(char ch) {
- if (ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9') {
- return true;
- } else {
- return false;
- }
- }
-
- public boolean isPalindrome(String str) {
- str = str.toLowerCase();
- char[] arr = str.toCharArray();
- int left = 0;
- int right = arr.length - 1;
- //定义双下标来遍历数组
- //如果是回文字符串,那除去标点空格一定是对称的
- while (left < right) {
- //不合法就跳过
- while (left < right && !isLegal(arr[left])) {
- left++;
- }
- while (left < right && !isLegal(arr[right])) {
- right--;
- }
- //合法才停下来比较
- if (arr[left] != arr[right]) {
- return false;
- } else {
- left++;
- right--;
- }
- }
- return true;
- }
- }
在Java中,将程序执行过程中发生的不正常行为称为异常。
比如:
1. 算术异常
2. 数组越界异常
3. 空指针异常
java中不同类型的异常,都有与其对应的类来进行描述。
异常种类繁多,为了对不同异常或者错误进行很好的分类管理,Java内部维护了一个异常的体系结构:
1. Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception
2. Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,
典型代表:StackOverflflowError和OutOfMemoryError,一旦发生回力乏术。
3. Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说的异常就是Exception。
异常可能在编译时发生,也可能在程序运行时发生,根据发生的时机不同,可以将异常分为:编译时异常和运行时异常。
1. 编译时异常
在程序编译期间发生的异常,称为编译时异常,也称为受查异常。默认继承Exception的异常就是编译时异常,
比如:CloneNotSupportedException
2. 运行时异常
在程序执行期间发生的异常,称为运行时异常,也称为非受查异常。RunTimeException以及其子类对应的异常,都称为运行时异常。如:空指针异常,算术异常,数组越界异常等。
注意:编译时出现的语法性错误,不能称之为异常
1. LBYL: Look Before You Leap. 在操作之前就做充分的检查即:事前防御型
- boolean ret = false;
- ret = 登陆游戏();
- if (!ret) {
- 处理登陆游戏错误;
- return;
- }
- ret = 开始匹配();
- if (!ret) {
- 处理匹配错误;
- return;
- }
- ret = 游戏确认();
- if (!ret) {
- 处理游戏确认错误;
- return;
- }
- ret = 选择英雄();
- if (!ret) {
- 处理选择英雄错误;
- return;
- }
- ret = 载入游戏画面();
- if (!ret) {
- 处理载入游戏错误;
- return;
- }
- ......
缺陷:正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱。
2. EAFP: It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易". 也就是先操作, 遇到问题再处理. 即:事后认错型
- try {
- 登陆游戏();
- 开始匹配();
- 游戏确认();
- 选择英雄();
- 载入游戏画面();
- ...
- } catch (登陆游戏异常) {
- 处理登陆游戏异常;
- } catch (开始匹配异常) {
- 处理开始匹配异常;
- } catch (游戏确认异常) {
- 处理游戏确认异常;
- } catch (选择英雄异常) {
- 处理选择英雄异常;
- } catch (载入游戏画面异常) {
- 处理载入游戏画面异常;
- }
- ......
优势:正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码,异常处理的核心思想就是 EAFP。
异常处理主要的5个关键字:throw、try、catch、final、throws。
在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下:
【注意事项】
1. throw必须写在方法体内部
2. 抛出的对象必须是Exception 或者 Exception 的子类对象
3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理
4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
5. 异常一旦抛出,其后的代码就不会执行
异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明throws 以及 try-catch捕获处理。
2.3.1 异常声明throws
当前方法不处理异常,提醒方法的调用者处理异常。
抛出一个运行时异常是不需要处理的,但如果是一个编译时异常,我们就需要处理这个异常,最简单的方式是通过throws处理。
在Java中,可以借助throw关键字,抛出一个指定的异常对象
【注意事项】
1. throws一般放在方法声明的地方
2. 声明的异常必须是 Exception 或者 Exception 的子类
3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。
4. 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出
2.3.2 try-catch捕获并处理
如果真正要对异常进行处理,就需要try-catch。
关于异常的处理方式
异常的种类有很多, 我们要根据不同的业务场景来决定.
对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.
在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息.
【注意事项】
1. 在try块内抛出异常位置之后的代码将不会被执行
2. 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序----异常是按照类型来捕获的,所以catch一定要捕获一个对应的异常
3. try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获
如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误:
4. 可以通过一个catch捕获所有的异常,即多个异常,一次捕获(不推荐),由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常.
程序只会同时抛出一个异常,不会同时抛出多个异常
可以通过|连接异常
2.3.3 finally
在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这个问题的。
注意:finally中的代码一定会执行的,一般在finally中进行一些资源清理的扫尾工作。
打印100,因为finally的代码一定会被执行,不建议在finally当中return
2.4 异常的处理流程
具体方式:
1. 自定义异常类,然后继承自Exception 或者 RunTimeException
2. 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因
自定义异常通过throw抛出
注意事项
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。