初识Java 超详细的万字入门介绍

目录

-------------初识Java

--------数据类型与变量

1. 字面常量

2. 数据类型

3. 变量

3.1.整型变量

3.2.长整型变量

3.3. 短整型变量

3.4. 字节型变量

3.5. 双精度浮点型

3.6. 单精度浮点型

3.7. 字符型变量

3.8. 布尔型变量

3.9. 类型转换

3.10. 类型提升

4. 字符串类型

-------- 运算符

1.算术运算符

1. 基本四则运算符:加减乘除模(+ - * / %)

2. 增量运算符 += -= *= %=

3. 自增/自减运算符 ++ --

2. 关系运算符

3. 逻辑运算符(重点)

1. 逻辑与 &&

2. 逻辑 ||

3. 逻辑非 !

4. 短路求值

4. 位运算符

5. 移位运算

6. 条件运算符

7. 运算符的优先级

--------逻辑控制

1.顺序结构

2.分支结构

2.1 if 语句

2.2 switch 语句

3. 循环结构

3.1 while 循环

3.2 break

3.3 continue

3.4 for 循环

3.5 do while 循环

4. 输入输出

4.1 输出到控制台

4.2 从键盘输入

5. 猜数字游戏

-------方法的使用

1. 方法概念及使用

1.1 什么是方法(method)

1.2 方法定义

1.3 方法调用的执行过程

1.4 实参和形参的关系(重要)

1.5 没有返回值的方法

2. 方法重载

2.1 方法重载概念

2.3 方法签名

3. 递归

3.1 递归的概念

3.2 递归执行过程分析

3.3 递归练习

------数组的定义与使用

1. 数组的基本概念

1.1 什么是数组

1.2 数组的创建及初始化

1.3 数组的使用

2. 数组是引用类型

2.1 初始JVM的内存分布

2.2 基本类型变量与引用类型变量的区别

2.3 再谈引用变量

2.4 认识 null

3. 数组的应用场景

3.1 保存数据

3.2 作为函数的参数

3.3 作为函数的返回值

4. 数组练习

4.1 数组转字符串

4.2 数组拷贝

4.3 求数组中元素的平均值

4.4 查找数组中指定元素(顺序查找)

4.5 查找数组中指定元素(二分查找)

4.6 数组排序(冒泡排序)

4.7 数组逆序

5. 二维数组

-----------类和对象

1. 面向对象的初步认知

1.1 什么是面向对象

1.2 面向对象与面向过程

2. 类定义和使用

2.1 简单认识类

2.2 类的定义格式

2.3 课堂练习

3. 类的实例化

3.1 什么是实例化

3.2 类和对象的说明

4. this引用

4.1 为什么要有this引用

4.2 什么是this引用

4.3 this引用的特性

5. 对象的构造及初始化

5.1 如何初始化对象

5.2 构造方法

5.3 默认初始化

5.4 就地初始化

6. 封装

6.1 封装的概念

6.2 访问限定符

6.3 封装扩展之包

7. static成员

7.1 static修饰成员变量

7.2 static修饰成员方法

7.3 static成员变量初始化

8. 代码块

8.1 代码块概念以及分类

8.2 普通代码块

8.3 构造代码块

8.4 静态代码块

9. 内部类

9.1 内部类

9.2 局部内部类

9.3 匿名内部类

10. 对象的打印

----------继承和多态

1. 继承

1.1 为什么需要继承

1.2 继承概念

1.3 继承的语法

1.4 父类成员访问

1.5 super关键字

1.6 子类构造方法

1.7 super和this

1.8 再谈初始化

1.9 protected 关键字

1.10 继承方式

1.11 final 关键字

1.12 继承与组合

2.多态

2.1 多态的概念

2.2 多态实现条件

2.3 重写

2.4 向上转移和向下转型

2.5 多态的优缺点

2.6 避免在构造方法中调用重写的方法

-------抽象类和接口

1. 抽象类

1.1 抽象类概念

1.2 抽象类语法

1.3 抽象类特性

1.4 抽象类的作用

2. 接口

2.1 接口的概念

2.2 语法规则

2.3 接口使用

2.4 接口特性

2.5 实现多个接口

2.6 接口间的继承

2.7 接口使用实例

2.8 Clonable 接口和深拷贝

2.9 抽象类和接口的区别

3. Object类

 3.1 获取对象信息

3.2 对象比较equals方法

3.3 hashcode方法

-------String类

1. 常用方法

1.1 字符串构造

1.2 String对象的比较

1.3 字符串查找

1.4 转化

1.5 字符串替换

1.6 字符串拆分

1.7 字符串截取

1.8 其他操作方法

1.9 字符串的不可变性

1.10 字符串修改

2. StringBuilder和StringBuffer

2.1 StringBuffer和StringBuilder的介绍

-------认识异常

1. 异常的概念与体系结构

1.1 异常的概念

1.2 异常的体系结构

1.3 异常的分类

2. 异常的处理

2.1 防御式编程

2.2 异常的抛出

2.3 异常的捕获

3. 自定义异常类


-------------初识Java

使用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语言提前定义好的,有特殊含义的标识符,或者保留字。

--------数据类型与变量

1. 字面常量

常量即程序运行期间,固定不变的量称为常量,和C语言的常量类似。

字面常量的分类:

1. 字符串常量:由""括起来的,比如“12345”、“hello”、“你好”。

2. 整形常量:程序中直接写的数字(注意没有小数点),比如:100、1000

3. 浮点数常量:程序中直接写的小数,比如:3.14、0.49

4. 字符常量:由 单引号 括起来的当个字符,比如:‘A’、‘1’

5. 布尔常量:只有两种true和false

6. 空常量:null

2. 数据类型

在Java中数据类型主要分为两类:基本数据类型和引用数据类型(数组,类,接口,枚举类型)。

基本数据类型有四类八种:

1. 四类:整型、浮点型、字符型以及布尔型

2. 八种:

注意:

  • 不论是在16位系统还是32位系统,int都占用4个字节,long都占8个字节
  • 整型和浮点型都是带有符号的
  • 整型默认为int型,浮点型默认为double
  • 字符串属于引用类型

3. 变量

能够被改变的量。都和C语言类似,但是基本数据类型多了个包装类

包装类只是针对基本数据类型 对应的类 类型

数据类型就是用来定义不同种类变量的

和C语言定义变量相同

比如 int a = 10; 等号左边的叫左值(空间),右边叫右值(数据)

3.1.整型变量

int a = 10;

整型变量在使用前一定得赋初始值,不然编译期间会报错

1. int不论在何种系统下都是4个字节

2. 推荐使用方式一定义,如果没有合适的初始值,可以设置为0

3. 在给变量设置初始值时,值不能超过int的表示范围,否则会导致溢出

4. 变量在使用之前必须要赋初值,否则编译报错

5. int的包装类型为 Integer

3.2.长整型变量

Long 是 long 的包装类型,为了区分,所以定义变量数值时要加L

注意事项:

1. 长整型变量的初始值后加L或者l,推荐加L

2. 长整型不论在那个系统下都占8个字节

3. 长整型的表示范围为:- 2^63 ~ 2^63 - 1

4. long的包装类型为Long

3.3. 短整型变量

注意事项:

1. short在任何系统下都占2个字节

2. short的表示范围为:-32768 ~ 32767

3. 使用时注意不要超过范围(一般使用比较少)

4. short的包装类型为Short

3.4. 字节型变量

注意事项:

1. byte在任何系统下都占1个字节

2. byte的范围是:-128 ~ 127

3. 字节的包装类型为Byte

3.5. 双精度浮点型

在 Java 中, int 除以 int 的值仍然是 int(会直接舍弃小数部分)。

注意事项:

1. double在任何系统下都占8个字节

2. 浮点数与整数在内存中的存储方式不同,不能单纯使用 的形式来计算

3. double的包装类型为Double

4. double 类型的内存布局遵守 IEEE 754 标准(和C语言一样), 尝试使用有限的内存空间表示可能无限的小数, 势必会存在一定的精度误差,因此浮点数是个近似值,并不是精确值。

3.6. 单精度浮点型

占4个字节

3.7. 字符型变量

注意事项:

1. Java 中使用 单引号 + 单个字母 的形式表示字符字面值.

2. 计算机中的字符本质上是一个整数. 在 C 语言中使用 ASCII 表示字符, 而 Java 中使用 Unicode 表示字符. 因此一个字符占用两个字节, 表示的字符种类更多, 包括中文

3.取值范围:0 ~ 2^16 -1

3.8. 布尔型变量

布尔类型常用来表示真假,只有两种值,true和false

它没有确定大小,也不能进行类型转换,强制转换也不行,

不能进行+-整数

Java虚拟机规范中,并没有明确规定boolean占几个字节,也没有专门用来处理boolean的字节码指令,在Oracle公司的虚拟机实现中,boolean占1个字节。

boolean的包装类型为Boolean

3.9. 类型转换

在Java中,当参与运算数据类型不一致时,就会进行类型转换。Java中类型转换主要分为两类:自动类型转换(隐式) 和 强制类型转换(显式)。

和C语言类似。

3.9.1 自动类型转换(隐式)

自动类型转换即:代码不需要经过任何处理,在代码编译时,编译器会自动进行处理。

特点:数据范围小的转为数据范围大的时会自动进行。

小转大

Java比较严,这样做会报错

3.9.2 强制类型转换(显式)

强制类型转换:当进行操作时,代码需要经过一定的格式处理,不能自动完成。

特点:数据范围大的到数据范围小的。

注意事项:

1. 不同数字类型的变量之间赋值, 表示范围更小的类型能隐式转换成范围较大的类型

2. 如果需要把范围大的类型赋值给范围小的, 需要强制类型转换, 但是可能精度丢失

3. 将一个字面值常量进行赋值的时候, Java 会自动针对数字范围进行检查

4. 强制类型转换不一定能成功,不相干的类型不能互相转换

3.10. 类型提升

和C语言差不多

分为整型提升和算术转换

小于int的就转int

其他的: int -> float -> long -> double      按照这个来转换

4. 字符串类型

输出

1.int 转成 String

问:输出什么?

答案:

2. String 转成 int

String str = "109" int ret = Integer.valueOf(str);

-------- 运算符

1.算术运算符

1. 基本四则运算符:加减乘除模(+ - * / %)

和C语言类似

int / int =int

小数也能进行取模了,但是意义不大

做除法和取模时,右操作数不能为0,不然抛异常

2. 增量运算符 += -= *= %=

注意:只有变量才能使用该运算符,常量不能使用。

会自动进行类型转换

和C语言相同

比如 a = 10; a += 10;//相当于 a = a + 10

其他同理

3. 自增/自减运算符 ++ --

和C语言相同

前置++:先++,再使用

后置++:先使用,后++

--同理

2. 关系运算符

关系运算符主要有六个: == != < > = ,其计算结果是 true 或者 false 。

和C语言类似

3. 逻辑运算符(重点)

逻辑运算符主要有三个: && || ! ,运算结果都是 boolean类型。

1. 逻辑与 &&

布尔表达式1 && 布尔表达式2

有假就是假

2. 逻辑 ||

布尔表达式1 || 布尔表达式2

有真就是真

3. 逻辑非 !

假变真,真变假

4. 短路求值

和C语言相同

对于 && , 如果左侧表达式值为 false, 则表达式结果一定是 false, 无需计算右侧表达式.

对于 ||, 如果左侧表达式值为 true, 则表达式结果一定是 true, 无需计算右侧表达式. 

& 和 | 如果表达式结果为 boolean 时, 也表示逻辑运算. 但与 && || 相比, 它们不支持短路求值.

4. 位运算符

位运算符主要有四个: & | ~ ^ ,和C语言相同

按位与 &:有0则0

按位或 |:有1则1

注意: 当 & 和 | 的操作数为整数(int, short, long, byte) 的时候, 表示按位运算, 当操作数为 boolean 的时候, 表示逻辑运算.

按位取反 ~: 0变1,1变0

按位异或 ^:相同为 0,相异为1

%x 打印16进制

5. 移位运算

移位运算符有三个: > >>> ,都是二元运算符,且都是按照二进制比特位来运算的

1. 左移 : 左边丢弃,右边补0,相当于乘2.

注意:向左移位时,丢弃的是符号位,因此正数左移可能会变成负数

2. 右移 >>: 最右丢弃, 左边补符号位(正数补0, 负数补1),相当于除2

3. 无符号右移 >>>: 最右侧位不要了, 最左侧补 0

6. 条件运算符

和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: 不是语句

7. 运算符的优先级

和C语言相同,不用记忆,做题的时候直接加括号

--------逻辑控制

1.顺序结构

顺序结构:从上到下一行一行的执行你写的代码。

2.分支结构

2.1 if 语句

语法:和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匹配

2.2 switch 语句

和C语言相同

为了达成分支效果,case 和break 必须一起使用

不能左switch语句参数的基本数据类型有:

float double long boolean

switch 不能表达复杂的条件。

3. 循环结构

和C语言相同

3.1 while 循环

布尔表达式为真,就执行循环语句

此时 ; 为 while 的语句体(这是一个空语句), 实际的 { } 部分和循环无关. 此时循环条件 num <= 10恒成立,导致代码死循环了

3.2 break

break 跳出循环

3.3 continue

continue :跳过continue后面的语句,进入下一次循环

死循环了,因为num=2时永远continue,num无法自增,所以死循环

3.4 for 循环

fori + 回车 快速写for循环

3.5 do while 循环

特点:至少执行一次

4. 输入输出

4.1 输出到控制台

println 输出的内容自带 \n, print 不带 \n

printf 的格式化输出方式和 C 语言的 printf 是基本一致的.

4.2 从键盘输入

使用前得导入包

import java.util.Scanner;

Scanner scan = new Scanner(System.in);

//这个也要写在前面,否则没法输入

.nextLine()就相当于C语言的gets

字符串的输入

整型的输入:

浮点数输入:

多组数据输入:

刷题的时候会用到,猛记

按Ctrl + D 结束输入

5. 猜数字游戏

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("猜小了");
            }
        }
    }
}

-------方法的使用

1. 方法概念及使用

1.1 什么是方法(method)

和C语言的函数类似。

使用方法能让代码的可读性更高,提升效率。

1.2 方法定义

和C语言类似

public static 返回类型 方法名(参数)

比如main方法

例如:判断闰年

例如:两个整数相加

1. 修饰符:现阶段直接使用public static 固定搭配

2. 返回值类型:如果方法有返回值,返回值类型必须要与返回的实体类型一致,如果没有返回值,必须写成void

3. 方法名字:采用小驼峰命名

4. 参数列表:如果方法没有参数,()中什么都不写,如果有参数,需指定参数类型,多个参数之间使用逗号隔开

5. 方法体:方法内部要执行的语句

6. 在java当中,方法必须写在类当中

7. 在java当中,方法不能嵌套定义

8. 在java当中,没有方法声明一说

1.3 方法调用的执行过程

调用方法--->传递参数--->找到方法地址--->执行被调方法的方法体--->被调方法结束返回--->回到主调方法继续往下执行

在方法执行中,如果遇到了return 那么方法就结束了,意味着该方法在栈上开辟的内存就没有了

方法在编译完成之后,会保存在方法区,方法被调用时会在栈区上开辟空间。

定义方法的时候, 不会执行方法的代码. 只有调用的时候才会执行.

一个方法可以被多次调用。

1.4 实参和形参的关系(重要)

和C语言相同,形参是实参的一份临时拷贝,改变形参不会影响实参(传值调用)

想要写一个函数交换两个整型变量,只能传引用类型的变量,引用就是引用变量,引用变量存的是“地址”(哈希值),但它是唯一的。

这么写才能交换,数组变量名其实就是一个引用

1.5 没有返回值的方法

没有返回值的方法必须返回类型必须写void

2. 方法重载

2.1 方法重载概念

在java中,方法可以同名,但是 同名方法的 参数列表不能相同,

这种同名的方法就被叫做方法重载

比如:

注意:

1. 方法名必须相同

2. 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序必须不同)

3. 与返回值类型是否相同无关

2.3 方法签名

每个方法都会有一个方法签名

方法签名即:经过编译器编译修改过之后方法最终的名字。具体方式:方法全路径名+参数列表+返回值类型,构成方法完整的名字。

输入:javap -v 字节码文件名字即可,就能查看方法签名(能看到类文件大小、类文件的MD5、JDK版本等)

输入:javap -c 字节码文件名字即可,就能查看方法签名(查看相关反编译指令)

比如判断闰年,参数时int,返回类型是boolean

[ 是数组

J 是long

L 是引用类型

Z 是boolean类型

其他基本数据类型都是以其大写字母开头来表示

3. 递归

3.1 递归的概念

和C语言相同,就是方法自己调用自己,被称为递归。

递归必须有条件限制,每次递归后会更接近这个条件,不然的话就会死递归

例如, 求 N!

起始条件: N = 1 的时候, N! 为 1. 这个起始条件相当于递归的结束条件.

递归公式: 求 N! , 直接不好求, 可以把问题转换成 N! => N * (N-1)!

3.2 递归执行过程分析

绿色部分是递

红色部分是归

关于 "调用栈"

方法调用的时候, 会有一个 "栈" 这样的内存空间描述当前的调用关系. 称为调用栈.

每一次的方法调用就称为一个 "栈帧", 每个栈帧中包含了这次调用的参数是哪些, 返回到哪里继续执行等信息.

后面我们借助 IDEA 很容易看到调用栈的内容.

3.3 递归练习

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);
    }
}

------数组的定义与使用

1. 数组的基本概念

引用变量:存地址的变量(相当于C语言的指针)

1.1 什么是数组

全和C语言一样

数组:相同类型元素组成的一个集合。在内存中是一段连续的空间。

数组就是引用(引用变量),里面存的是地址,地址指向了数组(在堆区存放)

1. 数组中存放的元素其类型相同

2. 数组的空间是连在一起的

3. 每个空间有自己的编号,其实位置的编号为0,即数组的下标

1.2 数组的创建及初始化

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.3.1 数组中元素访问

和C语言一样

数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过下标访问其任意位置的元素

用下标访问数组

下标从0开始,每次递增1,有n个元素,下标范围就是[0,n),这个和C是一样的

1.3.2 遍历数组

"遍历" 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作

数组元素个数可以通过数组名.length求得,非常方便

for each循环,专门用来访问数组的

两种方法都能遍历数组,区别是for依赖下标,而for each循环不依赖下标

2. 数组是引用类型

引用类型:变量存的是地址

2.1 初始JVM的内存分布

内存是一段连续的存储空间,主要用来存储程序运行时数据的。

程序计数器: 只是一个很小的空间, 保存下一条执行的指令的地址

虚拟机栈: 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。

本地方法栈: 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的。

: JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。

方法区: 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域。

重点:局部变量存在栈里,堆区存的是对象。

2.2 基本类型变量与引用类型变量的区别

基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。

引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象。

2.3 再谈引用变量

修改了形参所指向的对象。

如果两个引用指向同一个对象,那么既可以通过引用1修改对象,

也可以通过引用2修改对象

局部变量的生命周期和作用域是进入方法创建,出方法销毁。

当对象没有人引用时,对象就会自动被回收

2.4 认识 null

null 在 Java 中表示 "空引用" , 也就是该引用没有指向对象.

null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操作. 一旦尝试读写, 就会抛出 NullPointerException.

注意: Java 中并没有约定 null 和 0 号地址的内存有任何关联

3. 数组的应用场景

3.1 保存数据

public static void main(String[] args) {
    int[] array = {1, 2, 3};
    for(int i = 0; i < array.length; ++i){
        System.out.println(array[i] + " ");
    }
}

3.2 作为函数的参数

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

通过形参改变对象对进而实参产生影响

3.3 作为函数的返回值

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]);
        }
    }
}

对象一定是在堆上的,而引用不一定在栈上

4. 数组练习

4.1 数组转字符串

Arrays.toString(数组名),将数组转化成字符串

使用前要导入包 java.util.Arrays

模拟实现toString

ctrl + 鼠标点击 能查看java源码

4.2 数组拷贝

1.

2.

3.

4.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

4.4 查找数组中指定元素(顺序查找)

public static int findVal(int[] arr, int k) {
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] == k) {
            return i;
        }
    }
    return -1;
}

4.5 查找数组中指定元素(二分查找)

有序数组才能二分查找,排序就用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()

4.6 数组排序(冒泡排序)

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;
        }
    }
}

4.7 数组逆序

给定一个数组, 将里面的元素逆序排列

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;
    }
}

5. 二维数组

二维数组就是一维数组的集合

二维数组列可以省略,行不可以省略

数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };

 

所以arr由2(行)个一维数组组成,每个一维数组含有3(列)个元素。

打印二维数组用Arrays.deepToString

会抛异常,因为没有列,所以二维数组中的一维数组(引用)没有指向对象,所以是null

-----------类和对象

1. 面向对象的初步认知

1.1 什么是面向对象

面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情

1.2 面向对象与面向过程

面向过程:亲力亲为,注重过程

面向对象:通过对象交互来完成一件事

对象的产生依赖于类

2. 类定义和使用

2.1 简单认识类

类:自定义类型,和C语言的结构体类似

类是用来对一个实体(对象)来进行描述的

2.2 类的定义格式

通过class定义一个类

成员变量是定义在类里边,方法外边

成员变量和成员方法都得用public修饰

类名一定要采用大驼峰

此处写的方法不带 static 关键字

2.3 课堂练习

2.3.1 定义一个狗类

2.3.2 定义一个学生类

1. 一般一个文件当中只定义一个类

2. main方法所在的类一般要使用public修饰(注意:Eclipse默认会在public修饰的类中找main方法)

3. public修饰的类必须要和文件名相同

4. 不要轻易去修改public修饰的类的名称,如果要修改,通过开发工具修改

修改类名:

只适用于一个java文件只有一个类的情况

3. 类的实例化

3.1 什么是实例化

用类类型创建对象的过程,称为类的实例化

在java中采用new关键字,配合类名来实例化对象

如何访问对象的属性?

通过对象的引用访问对象的属性。

  • new 关键字用于创建一个对象的实例.
  • 使用 . 来访问对象中的属性和方法.
  • 同一个类可以创建对个实例.

对象的成员属性在没有赋值时,引用类型的默认值是null,简单类型是对应的0

3.2 类和对象的说明

1. 类只是一个模型一样的东西,用来对一个实体进行描述,限定了类有哪些成员.

2. 类是一种自定义的类型,可以用来定义变量.

3. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量

4. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间

4. this引用

4.1 为什么要有this引用

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

4.2 什么是this引用

this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。

this就代表当前对象的引用(地址),谁调用,谁就是this

this引用的是调用成员方法的对象

通过this可以访问当前对象的成员属性(静态的成员变量不支持)

4.3 this引用的特性

1. this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型

2. this只能在"成员方法"中使用

3. 在"成员方法"中,this只能引用当前对象,不能再引用其他对象

4. this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收

5. 对象的构造及初始化

5.1 如何初始化对象

局部变量必须要初始化才能使用,但是对象不用

当成员变量没有被初始化时,引用类型默认是null,基本类型默认是对应的0值,比如boolean是false,char是'\u0000',int是0

5.2 构造方法

1.方法名必须和类名相同

2.构造方法没有返回值,写成void也不行

Student就是一个构造方法,带参数的构造方法可以对成员变量进行初始化

当一个类中存在一个及以上的构造方法时,java就不会再给你提供构造方法了

构造方法之间是可以构成方法重载的

5.2.1 概念

用来初始化对象的方法,得加public修饰

构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次(一次性用品)

先调用完成构造方法,才会产生对象

构造方法并不负责给对象开辟空间。

完成一个对象的构造,分两步:

1.开辟内存

2.调用合适的构造方法

5.2.2 特性

1. 名字必须与类名相同

2. 没有返回值类型,设置为void也不行

3. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)

4. 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)

5. 如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的

6. 构造方法中,可以通过this调用其他构造方法来简化代码

注意:

  • this(...)必须是构造方法中第一条语句
  • this( ) :调用其他构造方法 ,只能在当前构造方法的内部使用

this本身 代表当前对象的引用,有三个作用:

  • 不能形成环

代码错误,不可以形成环

7. 绝大多数情况下使用public来修饰,特殊场景下会被private修饰

5.3 默认初始化

new对象时,先分配内存,再调用构造方法

当成员变量没有被初始化时,引用类型默认为null,其他类型默认为对应的0值

5.4 就地初始化

初始化类时,直接给成员变量赋值

6. 封装

面向对象程序三大特性:封装、继承、多态。

6.1 封装的概念

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互

封装:对类的成员进行隐藏,通过关键字private,只是对类外提供公开的接口。

可以隐藏类的实现细节,从而达到安全性

6.2 访问限定符

 

快速写set和get方法:

Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用

Java中提供了四种访问限定符:

default是默认(包访问权限),比如:

啥也不加,就是默认

public:可以理解为一个人的外貌特征,谁都可以看得到

default: 对于自己家族中(同一个包中)不是什么秘密,对于其他人来说就是隐私了

private:只有自己知道,其他人都不知道

6.3 封装扩展之包

6.3.1 包的概念

为了更好的管理类,把多个类收集在一起成为一组,称为软件

包。有点类似于目录。包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。

6.3.2 导入包中的类

使用 import语句导入包中的类

声明当前java文件是在哪个包底下,用package

import java.util.*;//导入所有类

更建议显式的指定要导入的类名. 否则还是容易出现冲突

可以使用import static导入包中静态的方法和字段

 

6.3.3 自定义包

基本规则

  • 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
  • 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ).
  • 包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码.
  • 如果一个类没有 package 语句, 则该类被放到一个默认包中(src).

操作步骤

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编程开发包。

7. static成员

7.1 static修饰成员变量

被static修饰的成员变量,称为静态成员变量,

static只能修饰成员变量和成员方法,不能修饰局部变量

静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。也就是static修饰的变量或方法是不依赖于对象的,所以static修饰的变量或方法中不能出现this和super,存储于方法区。

【静态成员变量特性】

1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中

2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问

3. 类变量存储在方法区当中

4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)

非静态成员变量都是属于对象的

静态成员变量的使用不依赖于对象

7.2 static修饰成员方法

 Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的

【静态方法特性】

1. 不属于某个具体的对象,是类方法

2. 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者

3. 不能在静态方法中直接访问任何非静态成员变量,但是可以通过对象的引用来访问普通成员方法

4. 静态方法中不能直接调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用

static方法里面不能使用this

普通成员方法通过对象的引用调用

静态成员方法通过类名来调用

7.3 static成员变量初始化

注意:静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性。

1. 就地初始化

就地初始化指的是:在定义时直接给出初始值

2. 静态代码块初始化

8. 代码块

8.1 代码块概念以及分类

使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:

  • 普通代码块
  • 构造块
  • 静态块
  • 同步代码块

8.2 普通代码块

普通代码块:定义在方法中的代码块

8.3 构造代码块

构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。

实例代码块先于构造方法执行

8.4 静态代码块

使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量

注意事项

  • 静态代码块不管生成多少个对象,其只会执行一次
  • 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
  • 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
  • 实例代码块只有在创建对象时才会执行

先执行静态代码块,再执行构造代码块,最后执行构造方法

如果都是静态的,那么就看它们的定义顺序

只要类被加载,静态代码块就一定会被执行

内部类还没学,学了之后再补上来

9. 内部类

内部类的分类:实例内部类,静态内部类,局部内部类,匿名内部类

9.1 内部类

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. 创建静态内部类对象时,不需要先创建外部类对象

9.2 局部内部类

定义在外部类的方法内部的类叫做局部内部类,该种内部类只能在其定义的位置使用

【注意事项】

1. 局部内部类只能在所定义的方法体内部使用

2. 不能被public、static等修饰符修饰

3. 编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class

4. 几乎不会使用

9.3 匿名内部类

调用testA方法1:

调用testA方法2:

在匿名内部类中,能够访问的是 没有被修改过的数据(常量,被final修饰的) -> 变量的捕获

匿名内部类字节码文件名:外部类类名$数字.class

10. 对象的打印

----------继承和多态

1. 继承

1.1 为什么需要继承

为了简化代码,实现代码的复用,跟C语言的函数类似,只用写一份代码,有需要就调用(Java是继承父类)这个代码,不用写重复的多份代码

进行共性抽取,实现代码复用。

1.2 继承概念

继承:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类(被叫做父类,基类,超类)特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类,也叫子类。

举个例子:比如香蕉和水果,它们的共性就是都有形状,大小,颜色,香蕉是水果的一种,那么就可以让香蕉(子类)继承水果(父类),只写一份父类的代码,让子类继承,实现代码复用。

例子2:狗和猫 动物

继承意义:对共性抽取,实现代码复用

抽取到父类里面的一定是子类的共性

如果子类和父类是 is- a(是一种)的关系,就要继承

1.3 继承的语法

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.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 子类中访问父类的成员方法

成员方法也同理,先去子类找,再去父类找,找不到就报错

  • 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
  • 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错。

1.5 super关键字

和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(...)来调用父类构造方法

1.6 子类构造方法

父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。

子类要先帮助父类构造,然后子类自己再来构造。

只能在子类中调用父类的构造方法

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.7 super和this

【相同点】

1. 都是Java中的关键字

2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段他。

3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

【不同点】

1. this是当前对象的引用,当前对象即调用实例方法的对象,super只是一个关键字让人一看到就知道是调用父类成员的。

2. 在非静态成员方法中,this用来访问本类(父类和子类)的方法和属性,super用来访问父类继承下来的方法和属性。

3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现。

4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有。

1.8 再谈初始化

先是执行静态代码块,再执行实例代码块,最后执行构造方法。

1. 静态代码块先执行,并且只执行一次,在类加载阶段执行

2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行。

【继承关系上的执行顺序】

一直遵循着先父再子,子类会帮助父类完成构造之后,子类自己再构造。

静态代码块一定是最早执行的!且只执行一次!

根据以上,不难得出:

执行顺序:

父类静态代码块>子类静态代码块>父类实例代码块>父类构造方法>子类实例代码块>子类构造方法

父静子静,父实父构造,子实子构造

1.9 protected 关键字

 protected关键字主要解决的就是继承在不同包里的子类问题。

1.10 继承方式

 注意:Java中不支持多继承。

1.11 final 关键字

final关键可以用来修饰变量、成员方法以及类。

final,最终的意思,让修饰的变量不能被修改

如果一个类不想被继承的话,就得使用final关键字进行修饰

1. 修饰变量或字段,表示常量(即不能修改)

final修饰引用变量,引用变量里面存的是地址,被final修饰之后,将不能修改地址,也就是不能让arr指向一个新的对象

2. 修饰类:表示此类不能被继承

被final修饰的类叫密封类

3. 修饰方法:表示该方法不能被重写

1.12 继承与组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。

继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物

合表示对象之间是has-a/a part of 的关系,比如:

学生和老师都是学校的一部分

用别的对象作为成员。组合就是代码的实现方式。

组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。

2.多态

2.1 多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。

2.2 多态实现条件

1. 必须在继承体系下

2. 子类必须要对父类中方法进行重写

3. 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法

总结:

条件:

1.向上转型

2.重写方法

3.通过父类引用调用子类对象重写的方法

多态就是不同对象调用重写的方法表现行为会不相同,多态是一种思想。

动态绑定是多态的基础。

动态绑定:1.向上转型 2.通过父类引用调用方法重写 结果是调用子类重写的方法,这个过程就叫做运行时绑定,也叫动态绑定。

2.3 重写

重写:也称为覆盖。重写是子类对父类非静态、非private修饰,非fifinal修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

条件:

1.方法名相同

2.参数列表(类型,个数,顺序)相同

3.返回值相同,返回值也可以是父子关系的

重写需要注意事项:

被static,privat,final修饰的方法不能重写

构造方法也不能重写

重写方法时,子类的重写方法访问权限必须大于父类的重写方法访问权限,private < 默认权限 < protected < public

方法的返回值可以不同,但必须是父子类关系

重写的方法可以用@override来标注,有利于编译器检查

静态绑定:方法重载

通过方法的参数等,在编译时就能知道调用的是哪个方法

动态绑定:方法重写

动态绑定就是编译前看不出调用哪个方法

静态绑定就是编译前就能看出调用的方法

2.4 向上转移和向下转型

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

2.5 多态的优缺点

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. 构造方法没有多态性

2.6 避免在构造方法中调用重写的方法

下面代码运行结果是什么?

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方法

-------抽象类和接口

1. 抽象类

1.1 抽象类概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

1.2 抽象类语法

在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.3 抽象类特性

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. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

1.4 抽象类的作用

抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法。

2. 接口

2.1 接口的概念

接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。

在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

2.2 语法规则

接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。

提示:

1. 创建接口时, 接口的命名一般以大写字母 I 开头.

2. 接口的命名一般使用 "形容词" 词性的单词.

3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.

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());
    }
}

2.4 接口特性

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.如果类没有实现接口中所有的抽象方法,那么该类要设置为抽象类

2.5 实现多个接口

在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。

父类里放的是共性,而接口中放的是特性

一定是先继承(extends)再实现(implements)

在java中,一个类只能继承一个类,实现多个接口

Alt + 回车,能快速重写方法

继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 .

注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。

2.6 接口间的继承

接口可以继承多个接口, 达到复用的效果. 使用 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()");
    }
}

2.7 接口使用实例

自定义类型要想比较大小,就得实现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));
    }
}

2.8 Clonable 接口和深拷贝

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);
    }
}

2.9 抽象类和接口的区别

核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法

3. Object类

Object类是所有类的父类

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。

所以在开发之中,Object类是参数的最高统一类型

我们主要来熟悉这几个方法:toString()方法,equals()方法,hashcode()方法

 3.1 获取对象信息

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);
    }
}

3.2 对象比较equals方法

如果是以后 自定义的类型,那么一定记住重写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方法 

3.3 hashcode方法

两个一样的对象,想放在同一个位置,就可以利用重写hashCode方法来做。

结论:

1、hashcode方法用来确定对象在内存中存储的位置是否相同

2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

-------String类

在Java当中,String是不可变的,其所有的方法都不是在原来的字符串上转换,而是会生成新的对象。

1. 常用方法

1.1 字符串构造

区别于C语言,在Java中 字符串没有以\0结尾的说法

【注意】

1. String是引用类型,内部并不存储字符串本身,在String类的实现源码中,String类实例变量如下:

存的是字符数组和哈希

2. 在Java中“”引起来的就是String类型对象。

.length()求字符串长度

.isEmpty()判断字符串是否为空

1.2 String对象的比较

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方式相同,但是忽略大小写比较

1.3 字符串查找

.charAt():访问对应下标的字符

indexOf(int ch):返回字符ch第一次出现的下标

lastIndexOf():从后面开始往前找,跟indexOf类似

1.4 转化

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有点像

1.5 字符串替换

注意事项: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串

1.6 字符串拆分

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. 如果一个字符串中有多个分隔符,可以用"|"作为连字符.

1.7 字符串截取

String substring(int beginIndex) :从指定索引截取到结尾

如果传入0下标,默认返回原来的对象,否则,返回的是新的对象

String substring(int beginIndex, int endIndex) :截取部分内容(左闭右开)

1.8 其他操作方法

String trim() :去掉字符串中的左右两边的空格,保留中间空格

1.9 字符串的不可变性

1. String类在设计时就是不可改变的,String类实现描述中已经说明了

1. String类被final修饰,表明该类不能被继承

2. value被修饰被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。

因为private修饰了value,而在类外又拿不到value,所以就修改不了value

final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。

1.10 字符串修改

注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下。

尽量避免对String的直接需要,如果要修改建议尽量使用StringBuffer或者StringBuilder。

2. StringBuilder和StringBuffer

由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffffer类。这两个类大部分功能是相同的。

StringBuilder和StringBuffer不能直接赋值,必须得通过new来创建对象。

2.1 StringBuffer和StringBuilder的介绍

创建一个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的区别

  • String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
  • StringBuffer与StringBuilder大部分功能是相似的
  • 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;
    }
}

-------认识异常

1. 异常的概念与体系结构

1.1 异常的概念

在Java中,将程序执行过程中发生的不正常行为称为异常。

比如:

1. 算术异常

2. 数组越界异常

3. 空指针异常

java中不同类型的异常,都有与其对应的类来进行描述。

1.2 异常的体系结构

异常种类繁多,为了对不同异常或者错误进行很好的分类管理,Java内部维护了一个异常的体系结构:

1. Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception

2. Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,

典型代表:StackOverflflowError和OutOfMemoryError,一旦发生回力乏术。

3. Exception异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说的异常就是Exception。

1.3 异常的分类

异常可能在编译时发生,也可能在程序运行时发生,根据发生的时机不同,可以将异常分为:编译时异常和运行时异常。

1. 编译时异常

在程序编译期间发生的异常,称为编译时异常,也称为受查异常。默认继承Exception的异常就是编译时异常,

比如:CloneNotSupportedException

2. 运行时异常

在程序执行期间发生的异常,称为运行时异常,也称为非受查异常。RunTimeException以及其子类对应的异常,都称为运行时异常。如:空指针异常,算术异常,数组越界异常等。

注意:编译时出现的语法性错误,不能称之为异常

2. 异常的处理

2.1 防御式编程

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。

2.2 异常的抛出

在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下:

【注意事项】

1. throw必须写在方法体内部

2. 抛出的对象必须是Exception 或者 Exception 的子类对象

3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理

4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译

5. 异常一旦抛出,其后的代码就不会执行

2.3 异常的捕获

异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明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 异常的处理流程

3. 自定义异常类

具体方式:

1. 自定义异常类,然后继承自Exception 或者 RunTimeException

2. 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因

自定义异常通过throw抛出

注意事项

  • 自定义异常通常会继承自 Exception 或者 RuntimeException
  • 继承自 Exception 的异常默认是受查异常
  • 继承自 RuntimeException 的异常默认是非受查异常

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 21
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值