Java

Java编程知识

1. 基本背景与流程

1.1 JAVA技术体系平台

  • JavaSE(Java Standard Edition标准版)
  • JavaEE(Java Enterprise Edition企业版)
  • JavaME(Java Micro Edition小型版)

Java语言的8大特性:

  1. 简单:比C/C++简单
  2. 面向对象:关注的是有功能的对象。
  3. 分布式:基于网络的多主机协作。
  4. 健壮性:强类型(所有数据都有类型),异常处理,GC(垃圾自动收集),指针(Pointer)的安全化:引用(Reference)。
  5. 安全:所有程序.class必须由ClassLoader类加载器加载。
  6. 跨平台:不同平台有不同的JVM。
  7. 性能好:Java是编译程序,比解释程序好。
  8. 多线程

1.2 JVM、JRE、JDK

  • JVM(Java Virtual Machine ):Java虚拟机,是运行所有Java程序的假想计算机,是Java程序的运行环境之一,也是Java 最具吸引力的特性之一。我们编写的Java代码,都运行在JVM 之上。
  • **JRE ** (Java Runtime Environment) :是Java程序的运行时环境,包含JVM 和运行时所需要的核心类库
  • JDK (Java Development’s Kit):是Java程序开发工具包,包含JRE 和开发人员使用的工具。

想要运行一个已有的Java程序,那么只需安装JRE 即可。

想要开发一个全新的Java程序,那么必须安装JDK ,其内部包含JRE

1561383524152

1.3 环境搭建

1、JDK下载

​ 官方网站www.oracle.com下载JDK8。

2、JDK安装

​ 安装到纯英文路径并复制路径。

3、配置环境变量

​ 环境变量->系统变量->编辑Path->在最前面键入路径;

1.4 Java程序结构

1
2
3
4
5
类{
方法{
语句;
}
}
  • 类>多个方法>多个语句
  • 类中包含方法,方法中也可以有内部类
  • 是java程序的基本单位
  • 方法是一个类最基本的功能单位,表达类或对象的功能,方法必须隶属于类
  • 语句是最小执行单位,必须隶属于方法
  • 主类是包含主方法(main)的类,可以有多个,只有主类可以当作程序运行
  • 公共类是有public关键字的类,公共类要与源文件同名,公共类只能有一个

1.5 运行案例(经典HelloWorld案例)

JAVA开发三步骤:编写、编译、运行。

开发步骤

1、编写Java文件

1
2
3
4
5
public class HelloWorld {
public static void main(String[] args) {
System.out.println("HelloWorld");
}
}

2、编译Java源文件生成.class字节码文件

​ 进入Java文件所在目录,在地址中输入cmd即可定位在该目录使用DOS命令行。

1
javac HelloWorld.java

​ 可以发现在该目录生成了HelloWorld.class字节码文件。

3、运行Java程序

1
java HelloWorld

1.6 注意事项

  1. Java语言是一门严格区分大小写的语言

  2. 字符编码问题

下·

1
javac -encoding utf-8 Review01.java
  1. 当类是public时,源文件名必须与类名一致。无论是否public都尽量保持同名。
  2. 一个源文件可以有多个类,编译后生成多个class文件。但是一个源文件只有一个public类。
  3. main方法不是必须在public类中,一般是public。

1.7 注释

​ 单行注释 //

​ 多行注释 /* */

​ 文档注释(java特有)/** */

1.8 DOS指令

1681909436972

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//切换到目标目录-绝对
cd E:\Atguigu\05_code\JavaSE\day01

//切换到目标目录-相对
cd .\Atguigu\05_code

//需要切换盘符情况下的绝对路径,例如当前在C盘需要切换到E盘
cd /d E:\Atguigu\05_code\JavaSE\day01

//回到根目录
cd \

//退回到上一级
cd ..

//多次退回
cd ..\..

//以当前窗口启动资源管理器
start .

1.9 Java字符

1、特殊的转义字符

1
2
3
4
5
6
7
\n:换行
\r:回车
\t:Tab键
\\:\
\":"
\':'
\b:删除键Backspace

2、 Unicode编码值

在JVM内存中,一个字符占2个字节,Java使用Unicode字符集来表示每一个字符,即每一个字符对应一个唯一的Unicode编码值。

范围是0~65535.

字符 Unicode编码值
‘0’ 48
‘1’ 49
‘A’ 65
‘B’ 66
‘a’ 97
1
2
3
4
5
6
7
8
char c2 = 97;
System.out.println(c2);//a

//查看某个字符的Unicode编码
int codeOfA = 'A';
System.out.println(codeOfA);
int codeOfTab = '\t';
System.out.println(codeOfTab);

\u字符的Unicode编码值的十六进制型

1
2
char c = '\u0041'; //十进制Unicode值65,对应十六进制是41,但是\u后面必须写4位
char c = '\u5c1a'; //十进制Unicode值23578,对应十六进制是5c1a

1.10 快捷键

  • Ctrl+D 复制一行
  • Ctrl+Y 删除一行
  • Alt+回车 快速修正常见问题
  • 锁定小键盘后,按1到行尾,按7到行首。
  • Ctrl+P 显示提醒方法的参数列表
  • alt+insert 快速构造 构造器 get/set —–> alt+n 不选
  • ctrl+alt+insert 新建类
  • 快速创建数组 arr.fori
  • 重写方法快捷键:Ctrl+O
  • shift+tab 切换类型
  • 按两次shift , 勾上include non- .. ,可以搜索.
  • 生成构造器 Alt+ Insert
  • 查看构造器和方法形参列表 Ctrl+P

Debug模式:

  • f7 进入语句内部(如果有方法调用,会进入)
  • alt+shift+f7 强行进入最细节
  • f8 一行一行地执行
  • shift+f8 直接结束掉当前方法
  • f9 直接执行

1.11 经典报错

  1. 0 / 0 报错ArithmeticError
  2. NullPointerException 空指针异常,
  3. 0.0 / 0 不报错 显示NaN —>Not a number
  4. 1.0 / 0 不报错 显示Infinity
  5. NumberFormatException 数字格式化异常
  6. ArrayIndexOutOfBoundsException 数组下标越界异常
  7. ClassCastException 类型转换异常
  8. RuntimeException 运行时异常

2. Java基础语法

2.1 关键字(keyword)

关键字:是指在程序中,Java已经定义好的单词,具有特殊含义。全部是小写字母。

保留字:是指在现阶段还不是关键字, 但是将来有可能会成为关键字。

1555209180504

2.2 标识符(identifier)

程序员自己命名的部分可以称为标识符。

即给类、变量、方法、包等命名的字符序列,称为标识符。

1、标识符的命名规则

  1. 标识符只能使用26个英文字母、数字、_和$。
  2. 不能使用关键字。
  3. 数字不能开头。
  4. 不能包含空格,长度无限制。
  5. 严格区分大小写。

2、标识符的命名规范

  1. 包名:全部小写,单词间.分割。

例:java.lang

  1. 类名、接口名:每个单词首字母大写。

例:HelloWorld,String

  1. 变量、方法名:第二个单词开始首字母大写。

例:name,bookName

  1. 常量名:全部大写

例:MAX_VALUE

2.3 数据类型(data type)

Java的数据类型分为两大类:

  • 基本数据类型:包括 整数浮点数字符布尔。 空间中保存数据本身.—>存放在栈中(寿命短)
  • 引用数据类型:包括数组接口枚举注解。空间中保存其他数据的地址. —->存放在堆中(寿命长)
image-20210628142322228

1、基本数据类型存储范围

1681909679895

image-20240223100812343

min max
byte 0x80 0x7F
char 0x0000 0xFFFF
short 0x8000 0x7FFF
int 0x8000 0000 0x7FFF FFFF
long 0x8000 0000 0000 0000 0x7FFF FFFF FFFF FFFF

2、数据类型的作用

1.决定空间大小

2.决定空间中可以保存什么数据

3.决定数据能做什么

2.4 变量(variable)

变量是什么?

在内存中的一块固定类型的空间,此空间可以保存一个数据,且此空间数据在其数据范围内可随意变化.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
数据类型  变量名;
例如:
//存储一个整数类型的年龄
int age;

//存储一个小数类型的体重
double weight;

//存储一个单字符类型的性别
char gender;

//存储一个布尔类型的婚姻状态
boolean marry;

//存储一个字符串类型的姓名
String name;

//声明多个同类型的变量
int a,b,c; //表示a,b,c三个变量都是int类型。

注意:变量的数据类型可以是基本数据类型,也可以是引用数据类型。

1、变量赋值

1
2
3
4
5
int age = 18;
double weight = 84.4;
char gender = '男';
boolean marry = true;
String name = "佟刚";
  • long类型:如果赋值的常量整数超过int范围,那么需要在数字后面加L。
  • float类型:如果赋值为常量小数,那么需要在小数后面加F。
  • char类型:使用单引号’’
  • String类型:使用双引号””
1
2
3
4
5
char c = '尚';//使用单引号
String s = '尚';//错误的,哪怕是一个字符,也要使用双引号

char kongChar = '';//错误,单引号中必须有且只能有一个字符
String kongStr = "";//可以,双引号中可以没有其他字符,表示是空字符串

2、变量输出

1
2
3
4
5
//输出变量的值
System.out.println(age);

//输出变量的值
System.out.println("name" + name + ",age = " + age + ",gender = " + gender + ",weight = " + weight + ",marry = " + marry);

3、变量注意事项

  1. 先声明后使用,只有声明好变量才有空间可用。
  2. 必须初始化再使用。
  3. 必须要有数据类型和变量名,数据类型的作用是3个。变量名的作用是定位空间。
  4. 同一作用域内变量不可以重复声明。
  5. 变量不可以超出作用范围使用,由其声明语句所隶属的{}范围决定。
  6. 变量有其数据范围。

4、变量分类

1)按数据类型分:

基本数据类型(primitive):空间中保存数据本身(itself)。

  • 整数 byte short char int long
  • 浮点数 float double
  • 布尔型 boolean 占用1字节

引用数据类型(reference):空间中保存对象地址(address)。地址:某个字节的编号,占用8字节,编号为0的内存地址为null。

  • 类 class
  • 接口 interface
  • 枚举 enum
  • 注解 @interface
  • 数组 []

2)按声明语句位置分:

  • 局部变量(local):声明在方法中的变量,范围小,寿命短。
  • 成员变量(member):声明在类中方法外的变量。此时变量与方法平级,都是类成员。范围大(全局),寿命长。

2.5 常量(literal value)

常量也有内存空间和数据类型,但是空间中的数据不能改变.常量通常有2种,一种是字面量,另一种是final修饰的量.

常见常量:

类型 举例
整数字面量 12,-23, 1567844444557L
浮点字面量 12.34F,12.34
字符字面量 ‘a’,’0’,‘尚’
布尔字面量 true,false
字符串字面量 ”HelloWorld“
  • 整数常量,超过int范围的必须加L
  • 小数常量,不加F就是默认double类型,如果数据类型是float,在初始化时一定要加F。
  • char常量,使用’’
  • String字符串常量,使用””
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ConstantDemo {
public static void main(String[] args) {
//输出整数常量值
System.out.println(12);
System.out.println(-23);
System.out.println(2352654566L);

//输出小数常量值
System.out.println(12.34F);
System.out.println(12.34);

//输出字符常量值
System.out.println('a');
System.out.println('0');

//输出布尔常量值
System.out.println(true);
System.out.println(false);

//输出字符串常量值
System.out.println("HelloWorld");
}
}

2.6 最终变量(final)

最终变量习惯上也称为常量,常量名通常所有字母都大写,每一个单词之间使用下划线分割,从命名上和变量名区分开来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class FinalVariableDemo {
public static void main(String[] args){
//定义常量
final int FULL_MARK = 100;//满分
// FULL_MARK = 150;//错误,final修饰的变量,是常量,不能重新赋值

//输出常量值
System.out.println("满分:" + FULL_MARK);

//小王的成绩比满分少1分
int wang = FULL_MARK - 1;
//小尚得了满分
int shang = FULL_MARK;
//小刘得了一半分
int liu = FULL_MARK/2;

//输出变量值
System.out.println("小王成绩:" + wang);
System.out.println("小尚成绩:" + shang);
System.out.println("小刘成绩:" + liu);
}

2.7 基本类型转换(Conversion)

转换方式:自动类型转换和强制类型转换

1、自动类型转换

  1. 当把存储范围小的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围大的变量时。
1
2
3
4
5
6
7
int i = 'A';//char自动升级为int,其实就是把字符的编码值赋值给i变量了
double d = 10;//int自动升级为double

byte b = 127; //右边的整数常量值必须在-128~127范围内
//byte bigB = 130;//错误,右边的整数常量值超过byte范围
long num = 1234567; //右边的整数常量值如果在int范围呢,编译和运行都可以通过,这里涉及到数据类型转换
long bigNum = 12345678912L;//右边的整数常量值如果超过int范围,必须加L,否则编译不通过
  1. 小范围与大范围混合运算,按最大类型运算。
1
2
3
4
5
int i = 1;
byte b = 1;
double d = 1.0;

double sum = i + b + d;//混合运算,升级为double
  1. 当byte,short,char数据类型进行算术运算时,按照int类型处理。
1
2
3
4
5
6
7
byte b1 = 1;
byte b2 = 2;
byte b3 = b1 + b2;//编译报错,b1 + b2自动升级为int

char c1 = '0';
char c2 = 'A';
System.out.println(c1 + c2);//113
  1. 在java中,boolean只有true和false的表现形式,没有1或者0,也不能用1或0转换为Boolean。

2、强制类型转换

取值范围大的转换成范围小的类型。

转换格式:

1
数据类型 变量名 = (数据类型)被强转数据值;  //()中的数据类型必须<=变量的数据类型,一般都是=

(1)当把存储范围大的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围小的变量时,需要强制类型转换,提示:有风险,可能会损失精度或溢出

1
2
3
4
5
6
7
int i = (int)3.14;//强制类型转换,损失精度

double d = 1.2;
int num = (int)d;//损失精度

int i = 200;
byte b = (byte)i;//溢出

(2)当某个值想要提升数据类型时,也可以使用强制类型转换

1
2
3
int i = 1;
int j = 2;
double shang = (double)i/j;

提示:这个情况的强制类型转换是没有风险的。

3、基本数据类型与字符串类型的转换

  1. 任何类型与String类型进行”+”运算时,结果一定是String类型。
1
System.out.println("" + 1 + 2);//12
  1. String类型不能进行强制类型转换。
1
2
3
String str = "123";
int num = (int)str;//错误
int num = Integer.parseInt(str);//该方法可以把str转换为int

练习:

1
2
3
4
5
6
7
String str1 = 4;        				//报错
String str2 = 3.5f + ""; //
System.out.println(str2); //输出:3.5
System.out.println(3+4+"Hello!"); //输出:7Hello
System.out.println("Hello!"+3+4); //输出:Hello!34
System.out.println('a'+1+"Hello!"); //输出:98Hello!
System.out.println("Hello"+'a'+1); //输出:Helloa1

2.8 进制

四种类型的进制来表示10。

(1)十进制:正常表示

System.out.println(10); // 10

(2)二进制:0b或0B开头

System.out.println(0B10); // 2

(3)八进制:0开头

System.out.println(010); // 8

(4)十六进制:0x或0X开头

System.out.println(0X10); // 16

2.9 运算符和标点符号

分类 运算符
算术运算符(7个) +、-、*、/、%、++、–
赋值运算符(12个) =、+=、-=、*=、/=、%=、>>=、<<=、>>>=、&=、|=、^=等
关系运算符(6个) >、>=、<、<=、==、!=
逻辑运算符(6个) &、|、^、!、&&、||
条件运算符(2个) (条件表达式)?结果1:结果2
位运算符(7个) <<, >>, >>>, &, |, ^, ~
Lambda运算符(1个) ->(后面学)

1、位运算符

位运算符 符号解释
& 按位与,当两位相同时为1时才返回1
` `
~ 按位非,将操作数的每个位(包括符号位)全部取反
^ 按位异或。当两位相同时返回0,不同时返回1
<< 左移运算符
>> 右移运算符
>>> 无符号右移运算符
  • byte,short,char在计算时按照int类型处理

如何区分&,|,^是逻辑运算符还是位运算符?

如果操作数是boolean类型,就是逻辑运算符,如果操作数是整数,那么就位运算符。

2、算术运算符

算术运算符 符号解释
+ 取自己int a = 5; +a => 5, int b = -5; +b => -5
- 取相反数 int a = 5; -n => -5, int b = -8, -b => 8
+ 加法运算,字符串连接运算,正号
- 减法运算,负号
* 乘法运算
/ 除法运算,整数/整数结果还是整数, 5 / 3 => 1
% 求余运算,余数的符号只看被除数, 13 % 5 => 3
++-- 自增自减运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int a = 10;
a++; // 后加加

a = 10;
++a; // 前加加

int x = 20;
int y = ++x; // 先加后用, x : 21, y : 21

x = 20;
y = x++; // 先用后加, x : 21, y : 20 先把a中的老值放入临时空间t,再把a++=21,然后把临时空间t的值赋值给b。

a = 10;
a = a++;//此时a的打印是10,原理见上。***

10++;//错误,10是常量,++只能针对变量。

//for循环的优化-优化前
for(int i = 0;i < arr.length;i++){

}
//for循环的优化-优化后
for(int i = 0;i != arr.length;++i){//++i不需要临时内存,'!='的效率优于'-'

}
  • M%N,如果N为负数,忽略负号,如果M是负数,结果也是负数。

3、关系运算符/比较运算符

关系运算符 符号解释
< 比较符号左边的数据是否小于右边的数据,如果小于结果是true。
> 比较符号左边的数据是否大于右边的数据,如果大于结果是true。
<= 比较符号左边的数据是否小于或者等于右边的数据,如果大于结果是false。
>= 比较符号左边的数据是否大于或者等于右边的数据,如果小于结果是false。
== 比较符号两边数据是否相等,相等结果是true。
!= 不等于符号 ,如果符号两边的数据不相等,结果是true。
  • 比较大小的运算只能适用于基本数据类型中的数值型之间。
  • ==和!=可以适用于所有数据类型之间。包括对象和布尔。
1
2
3
boolean b1 = 1 <= a < 10;//错误,自左向右依次判断, 1<=a判断完成后,转为true即boolean类型,而boolean型不能参与比较大小运算。
boolean b1 = 1 <= a && a < 10;//正确
boolean b1 = 1 <= a == true;//正确
  • 累操作比常规操作更方便。累操作不会改变类型。
1
2
3
4
5
6
byte b1 = 50;
b1 = b1 + 10;//报错,右边会自动升级成int型。
b1 += 10;//不会报错

int n = 200;
n *= 3.9;

4、逻辑运算符

逻辑运算符,是用来连接两个布尔类型值的运算符(!除外),运算结果也是boolean值true或者false

逻辑运算符 符号解释 符号特点
& 逻辑与,且 falsefalse
` ` 逻辑或
^ 逻辑异或 相同为false,不同为true
! falsetrue,非truefalse
&& 双与,短路与 左边为false,则右边就不看
` `

&&和&区别,||和|区别:

  • **&&&**区别:

    • &&&结果一样,&&有短路效果,左边为false,右边不执行;&左边无论是什么,右边都会执行。
  • **|||**区别:

    • |||结果一样,||有短路效果,左边为true,右边不执行;|左边无论是什么,右边都会执行。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class LogicOperator{
    public static void main(String[] args){
    /*
    表示条件,成绩必须在[0,100]之间
    成绩是int类型变量score
    */
    int score = 56;
    System.out.println(0<=score && score<=100);
    /*
    错误:
    System.out.println(0<=score<=100);
    */

5、条件运算符

格式:

1
条件表达式?表达式1:表达式2
  • 表达式1和表达式2必须是一样的数据类型
1
2
3
4
5
6
//找到两者的最大值
public static void main(String[] args){
int n1 = ?;
int n2 = ?;
int max = n1 > n2 ? n1 : n2;
}

6、赋值运算符

运算符 符号解释
= 将右边的常量值/变量值/表达式的值,赋值给左边的变量,. 左面必须是变量
+= 相加,最后将结果赋值给左边的变量
-= 相减,最后将结果赋值给左边的变量
*= 相乘,最后将结果赋值给左边的变量
/= 相除,最后将结果赋值给左边的变量
%= 相模,最后将结果赋值给左边的变量
<<= 将左边变量的值左移右边常量/变量值/表达式的值的相应位,最后将结果赋值给左边的变量
>>= 将左边变量的值右移右边常量/变量值/表达式的值的相应位,最后将结果赋值给左边的变量
>>>= 将左边变量的值无符号右移右边常量/变量值/表达式的值的相应位,最后将结果赋值给左边的变量
&= 将左边变量的值和右边的常量值/变量值/表达式的值进行按位与,最后将结果赋值给左边的变量
|= 将左边变量的值和右边的常量值/变量值/表达式的值进行按位或,最后将结果赋值给左边的变量
^= 将左边变量的值和右边的常量值/变量值/表达式的值进行按位异或,最后将结果赋值给左边的变量
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args){
int a;
a = 10;
System.out.println(a);//打印的是a中的副本
System.out.println(a = 10);//打印表达式本身
//赋值表达式的结果值就是它的最右值。

int n1,n2,n3,n4;
n1 = n2 = n3 = n4 = 100;//从右向左赋值,n3被赋予的是'n4 = 100'的表达式本身的值。
}

7、运算符优先级

1681909834136

口诀:

单目运算排第一;

乘除余二加减三;

移位四,关系五;

等和不等排第六;

位与、异或和位或;

短路与和短路或;

依次从七到十一;

条件排在第十二;

赋值一定是最后;

8、标点符号

image-20210701170438577

  • 小括号()用于强制类型转换、表示优先运算表达式、方法参数列表
  • 大括号{}用于数组元素列表、类体、方法体、复合语句代码块边界符
  • 中括号[]用于数组
  • 分号;用于结束语句
  • 逗号,用于多个赋值表达式的分隔符和方法参数列表分隔符
  • 英文句号.用于成员访问和包目录结构分隔符
  • 英文省略号…用于可变参数
  • @用于注解
  • 双冒号::用于方法引用

3. 流程控制语句

3.1 顺序结构

顺序结构就是程序从上到下逐行地执行。表达式语句都是顺序执行的。并且上一行对某个变量的修改对下一行会产生影响。

3.2 分支语句

1、条件判断:if

1
2
3
if (条件表达式){
语句体;

2、条件判断:if..else

1
2
3
4
5
if(关系表达式) { 
语句体1;
}else {
语句体2;
}

练习:接收一个整数,判断能否被7整除。

1
2
3
4
5
6
7
8
9
10
11
12
public class TestIfElse {
public static void main(String[] args){
int inputNum = Integer.parseInt(args[0]);

if(inputNum % 7 == 0) {
System.out.println(inputNum + "可以被7整除");
} else{
System.out.println(inputNum + "不可以被7整除");
}

}
}

3、多分支条件判断

1
2
3
4
5
6
7
8
9
10
11
if (判断条件1) {
执行语句1;
} else if (判断条件2) {
执行语句2;
}
...
}else if (判断条件n) {
执行语句n;
} else {
执行语句n+1;
}

4、if..else嵌套

1
2
3
4
5
6
7
8
9
if(){
if(){

}else{

}
}else{

}

5、switch..case多分支选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//如果case中没有break,会形成fall through.穿透
//如果某个case一旦进入,后面的所有case形同虚设,直到遇到break中switch结束。
switch(表达式){//必须是非long整数的变量/字符串/枚举
case 常量值1: // if (表达式==常量值1),必须是常量,因为它的底层是一个表格。
语句块1;
break;】
case 常量值2:
语句块2;
break;】
。。。
default: // else
语句块n+1;
break;】

}
  • swtich表达式值的类型只能是4种基本数据类型(byte,short,int,char)、2种引用数据类型(JDK1.5之后的枚举,JDK1.7之后的String)
  • case后面必须是常量且不能重复,多个case绝不允许出现相同的常量。

switch语句具有穿透性。一旦匹配成功,不会在判断下一个case的值,直接向后运行,直到遇到break或者整个switch语句结束,switch语句执行终止。

switch case的效率比for循环高,因为它的底层是表格,里面存储常量,不需要判断,直接跳转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import java.util.Scanner;

/*
* 需求:指定一个月份,输出该月份对应的季节。
* 一年有四季
* 3,4,5 春季
* 6,7,8 夏季
* 9,10,11 秋季
* 12,1,2 冬季
*/
public class Test14SwitchDemo2 {
public static void main(String[] args) {
int month = Integer.parseInt(args[0])

/*
switch(month) {
case 1:
System.out.println("冬季");
break;
case 2:
System.out.println("冬季");
break;
case 3:
System.out.println("春季");
break;
case 4:
System.out.println("春季");
break;
case 5:
System.out.println("春季");
break;
case 6:
System.out.println("夏季");
break;
case 7:
System.out.println("夏季");
break;
case 8:
System.out.println("夏季");
break;
case 9:
System.out.println("秋季");
break;
case 10:
System.out.println("秋季");
break;
case 11:
System.out.println("秋季");
break;
case 12:
System.out.println("冬季");
break;
default:
System.out.println("你输入的月份有误");
break;
}
*/

// 改进版
switch(month) {
case 1:
case 2:
case 12:
System.out.println("冬季");
break;
case 3:
case 4:
case 5:
System.out.println("春季");
break;
case 6:
case 7:
case 8:
System.out.println("夏季");
break;
case 9:
case 10:
case 11:
System.out.println("秋季");
break;
default:
System.out.println("你输入的月份有误");
break;
}
}
}

3.3 循环语句

for适用于循环次数确定的循环。

while和do while适用于次数不确定。

组成部分

      1. 初始化语句(init_statement)
      2. 循环条件语句(test_statement)
      3. 循环体(body)
      4. 迭代语句(alter_statement)

1、while循环

1
2
3
4
5
[初始化语句;]
while (循环条件语句) {
循环体语句;
[迭代语句;]
}

while中循环条件必须是boolean类。

1
2
3
4
while(1 == 1) //报错,不可能到达。死循环

boolean flag = true;
while(flag) //不会报错,因为flag是变量,java认为有可能退出循环。称为无限循环。无限循环的存在是好的,相当于程序可以一直运行。

2、do…while循环

1
2
3
4
5
[初始化语句;]
do {
循环体语句;
[迭代语句;]
} while (循环条件语句);

注意:

  • while(循环条件)中循环条件必须是boolean类型

  • do{}while();最后有一个分号

  • do…while结构的循环体语句是至少会执行一次,这个和for和while是不一样的

3、for循环

1
2
3
for (初始化语句A; 循环条件语句B; 迭代语句C) { /
循环体语句D;
}

注意:

  • 循环条件必须是boolean类型

死循环:

1
2
3
4
5
6
7
8
9
10
11
while (true) {
语句块;
}

do {
语句块;
} while (true);

for(;;){
循环体语句块;//如果循环体中没有跳出循环体的语句,那么就是死循环
}
增强型for循环
1
2
3
4
5
6
7
8
9
//增强for循环,利用tmp让修改内容的位置在栈中而不是直接修改堆中的内容,更安全适用于只读访问.
for(int tmp : arr){
System.out.println(tmp + " ");
}

//经典for循环,适用于写入操作或和下标密切相关的操作
for(int i = 0;i< arr.length;++i){

}

4、循环语句的区别

  • 从循环次数角度分析
    • do…while循环至少执行一次循环体语句
    • for和while循环先循环条件语句是否成立,然后决定是否执行循环体,至少执行零次循环体语句

5、循环嵌套

1
2
3
4
5
6
for(初始化语句A; 循环条件语句B; 迭代语句C) {//外循环控制行
for(初始化语句D; 循环条件语句E; 迭代语句F) {//内循环控制列
循环体语句G;
}
循环体语句H;
}

6、特殊流程控制break

使用场景:终止switch/当前循环/指定标签

1
2
3
4
for (int i = 0; i < 10; i++) {
if (i == 3) {
break;
}

使用标签中断指定循环

1
2
3
4
5
6
7
8
l1: for(int i = 0;i<5;i++){
l2: for(int j = 0;j<3;j++){
System.out.println("j:" + j)
if(j == 1){
break l1;//中断标签指示的循环
}
}
}

命名的标签可以中断

1
2
3
4
5
6
7
8
boolean flag = true;
l3: {
System.out.println("这是l3的语句");
if(flag == true){//如果不在这里添加flag == true的判断而直接写break l3 后面的输出语句会报错。
break l3;
}
System.out.println("这是break后的语句,如果显示那么就是出错了");
}

7、特殊流程控制continue

使用场景:提前结束本次循环,继续下一次的循环

1
2
3
4
5
6
for (int i = 0; i < 100; i++){
if (i % 10 == 0) {
continue;
}
System.out.println("i : " + i);
}

8、特殊流程控制return

return 并不是用于循环的, 它在方法中表示方法的结束, 而不管此时语句正在循环中还是哪里, 都会直接结束.

9、死循环与无限循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while(true){};//死循环
while(true);//死循环

boolean flag = true;
while(flag);//无限循环,flag是变量

do{} while(true);//死循环
do{} while(flag);//无限循环

for(;;);//死循环
for(int i = 0; ; i++);//死循环
for(int i = 0;true;i++);//死循环

for(int i = 0;flag;i++);//无限循环
for(int i = 0;i<10;);//无限循环
for(int i = 0;i<10;i--);//有限循环

3.4 方法(Method)

方法也叫函数(function),是java程序中的独立的功能单位,方法是一个类中最基本的功能单元。

1、方法的特点

(1)必须先声明后使用

类,变量,方法等都要先声明后使用

(2)不调用不执行,调用一次执行一次。

2、方法的声明

声明方法的位置==必须在类中方法外==,方法不可以嵌套。

声明方法的位置:

1
2
3
4
5
6
7
8
类{
方法1(){

}
方法2(){

}
}

声明方法的格式:

1
2
3
4
5
【修饰符】 返回值类型 方法名(数据类型1 形参1, 数据类型2 形参2 ....){
语句1;
语句2;
return 返回值;
}

一个完整的方法 = 方法头 + 方法体。

方法头就是 【修饰符】 返回值类型 方法名(【形参列表 】),也称为方法签名,通常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。

方法头可能包含5个部分,但是有些部分是可能缺省的:

  • 修饰符:可选的。方法的修饰符也有很多,例如:public、protected、private、static、abstract、native、final、synchronized等。其中根据是否有static,可以将方法分为静态方法和非静态方法。其中静态方法又称为类方法,非静态方法又称为实例方法.
  • 返回值类型: 表示方法运行的结果的数据类型,方法执行后将结果返回到调用者
    • 基本数据类型
    • 引用数据类型
    • 无返回值类型:void
  • 方法名:标识符,给方法起一个名字,见名知意,能准确代表该方法功能的名字
  • 参数列表:表示完成方法体功能时需要外部提供的数据列表
  • throws异常列表

关于方法体中return语句的说明:

  • return语句的作用是结束方法的执行,并将方法的结果返回去
  • 如果返回值类型不是void,方法体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。
  • 如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执行,那么return后面不能跟返回值,直接写return ; 就可以。
  • return语句后面就不能再写其他代码了,否则会报错:Unreachable code

3、形参和实参

形参:在定义方法时方法名后面括号中声明的变量称为形式参数

实参:在调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数

4.值传递

1、对于基本类型参数,在方法体内对参数进行重新赋值,并不会改变原有变量的值。

2、对于引用类型参数,在方法体内对参数进行重新赋予引用,并不会改变原有变量所持有的引用。

3、方法体内对参数进行运算,不影响原有变量的值。

4、方法体内对参数所指向对象的属性进行操作,将改变原有变量所指向对象的属性值。

3.5 方法的重载

  • 方法重载(overload):指在同一个类中,允许存在一个以上的同名方法只要它们的参数列表不同即可,与修饰符和返回值类型无关。

  • 参数列表:数据类型个数不同,数据类型不同(按理来说数据类型顺序不同也可以,但是很少见,也不推荐,逻辑上容易有歧义)。

  • 重载方法调用:JVM通过方法的参数列表,调用匹配的方法。

    • 先找个数、类型最匹配的
  • 再找个数和类型可以兼容的,如果同时多个方法可以兼容将会报错

  • 连环调用,对方法重载时调用重载前方法,便于维护。能连环调用绝不单独调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class OverLoad_2 {

//打印一个n*m的矩形
public static void method1(int n, int m) {
System.out.println("打印" + n + "*" + m + "的矩形");
System.out.println("----------------");
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
System.out.print("*");
}
System.out.println();
}
System.out.println("----------------");
}

//打印一个边长为n的正方形并返回周长
public static int method1(int n) {
method1(n, n);//连环调用,能连环调用绝不单独写,便于维护。
return n * 4;
}


public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
int m = Integer.parseInt(args[1]);

method1(n, m);
method1(n);
}
}

3.6 类限定

限定使用某个类的方法。类名.方法名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class JavaBeanTest {
public static void main(String[] args) {
int n = 100;
int m = 20;

System.out.println(Another.add(n,m));//使用Another类中的add方法。
}
}

class Another {
public static int add(int a, int b) {
return a + b;
}
}

3.7 方法的递归调用

递归调用:方法自己调用自己的现象就称为递归。

递归的分类:

  • 递归分为两种,直接递归和间接递归。
  • 直接递归称为方法自身调用自己。
  • 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。

注意事项

  • 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
  • 在递归中虽然有限定条件,但是递归深度不能太深,否则效率低下,或者也会发生栈内存溢出。
    • 能够使用循环代替的,尽量使用循环代替递归
  • 递归一定有参数,参数控制就类似于迭代语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FibonacciTest {

public static void main(String[] args) {
System.out.println(f(10));//6765
}

//使用递归
public static int f(int n){//计算斐波那契数列第n个值是多少
if(n<1){//负数是返回特殊值1,表示不计算负数情况
return 1;
}
if(n==1 || n==2){
return 1;
}
return f(n-2) + f(n-1);
}
}

4. 数组Array

4.1 数组的特点

1、数组的长度一旦确定就不能修改

2、创建数组时会在内存中开辟一整块连续的空间。

3、存取元素的速度快,因为可以通过[下标],直接定位到任意一个元素。

4.2 数组的分类

按照维度分:

  • 一维数组
  • 二维数组

按照元素类型分:

  • 基本数据类型的元素:存储数据值
  • 引用数据类型的元素:存储对象(本质上存储对象的首地址)

无论数组的元素是基本数据类型还是引用数据类型,数组本身都是引用数据类型

4.3 数组的声明

1
2
3
4
5
//推荐
元素的数据类型[] 数组的名称;

//不推荐
元素的数据类型 二维数组名[];
  • 数组的声明,就是要确定:

(1)数组的维度:在Java中数组的标点符号是[],[]表示一维,[][]表示二维

(2)数组的元素类型:即创建的数组容器可以存储什么数据类型的数据。元素的类型可以是任意的Java的数据类型。例如:int, String, Student等

(3)数组名:就是代表某个数组的标识符,数组名其实也是变量名,按照变量的命名规范来命名。数组名是个引用数据类型的变量,因为它代表一组数据。

4.4 一维数组

1、一维数组的静态初始化

数组初始化

初始化就是确定数组元素的个数和元素的值

静态数组初始化

静态初始化就是用静态数据为数组初始化,此时数组的长度由静态数据的个数决定。

静态初始化格式1:

1
数据类型[] 数组名 = {元素1,元素2,元素3...};//必须在一个语句中,不能分开两个语句写
1
2
3
4
int[] arr = {1,2,3,4,5};//正确

int[] arr;
arr = {1,2,3,4,5};//错误

静态初始化格式2:

1
2
3
4
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...};

数据类型[] 数组名;
数组名 = new 数据类型[]{元素1,元素2,元素3...};
1
2
3
4
int[] arr = new int[]{1,2,3,4,5};//正确

int[] arr;
arr = new int[]{1,2,3,4,5};//正确

2、一维数组的使用

  1. 数组长度
1
数组名.length
  1. 数组索引
1
数组名[下标];//从0开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test03ArrayUse {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};

System.out.println("arr数组的长度:" + arr.length);
System.out.println("arr数组的第1个元素:" + arr[0]);//下标从0开始
System.out.println("arr数组的第2个元素:" + arr[1]);
System.out.println("arr数组的第3个元素:" + arr[2]);
System.out.println("arr数组的第4个元素:" + arr[3]);
System.out.println("arr数组的第5个元素:" + arr[4]);

//修改第1个元素的值
//此处arr[0]相当于一个int类型的变量
arr[0] = 100;
System.out.println("arr数组的第1个元素:" + arr[0]);
}
}

3、越界异常

1
2
3
4
5
6
7
8
public class Test04ArrayIndexOutOfBoundsException {
public static void main(String[] args) {
int[] arr = {1,2,3};
// System.out.println("最后一个元素:" + arr[3]);//错误,下标越界ArrayIndexOutOfBoundsException
// System.out.println("最后一个元素:" + arr[arr.length]);//错误,下标越界ArrayIndexOutOfBoundsException
System.out.println("最后一个元素:" + arr[arr.length-1]);//对
}
}

4、一维数组的遍历

1
2
3
for(int i=0; i<arr.length; i++){
System.out.println(arr[i]);
}

5、一堆数组动态初始化

先确定元素的个数(即数组的长度),而元素此时只是默认值,并不是真正的数据。元素真正的数据需要后续单独一个一个赋值。

1
2
3
4
5
6
//第一种
数组存储的元素的数据类型[] 数组名字 = new 数组存储的元素的数据类型[长度];

//第二种
数组存储的元素的数据类型[] 数组名字;
数组名字 = new 数组存储的元素的数据类型[长度];
  • new:关键字,创建数组使用的关键字。因为数组本身是引用数据类型,所以要用new创建数组对象。
  • 注意:数组有定长特性,长度一旦指定,不可更改。
1
2
3
4
5
6
int[] arr = new int[5];

int[] arr;//在栈中开辟一个引用变量的空间,用于保存一个数组对象,此时并没有地址。
arr = new int[5];//new操作会在堆内存中创建一个真实的数组对象。占用20字节,并且所有数据都是0。

int[] arr = new int[5]{1,2,3,4,5};//错误的,后面有{}指定元素列表,就不需要在[]中指定元素个数了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ArrayInitTest {
public static void main(String[] args) {
int[] arr = new int[5];

System.out.println("arr数组的长度:" + arr.length);
System.out.print("存储数据到arr数组之前:[");
for (int i = 0; i < arr.length; i++) {
if(i==0){
System.out.print(arr[i]);
}else{
System.out.print("," + arr[i]);
}
}
System.out.println("]");

//初始化
/* arr[0] = 2;
arr[1] = 4;
arr[2] = 6;
arr[3] = 8;
arr[4] = 10;*/

for (int i = 0; i < arr.length; i++) {
arr[i] = (i+1) * 2;
}

System.out.print("存储数据到arr数组之后:[");
for (int i = 0; i < arr.length; i++) {
if(i==0){
System.out.print(arr[i]);
}else{
System.out.print("," + arr[i]);
}
}
System.out.println("]");
}
}

6、数组元素的默认值

当我们使用动态初始化方式创建数组时,元素只是默认值。

7、一维数组内存分析

Java虚拟机要运行程序,必须要对内存进行空间的分配和管理。

Java虚拟机的内存划分:

1561465258546

区域名称 作用
程序计数器 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址
本地方法栈 当程序中调用了native的本地方法时,本地方法执行期间的内存区域
方法区 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
堆内存 存储对象(包括数组对象),new来创建的,都存储在堆内存。
虚拟机栈 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。

1. 一维数组在内存中的存储

1
2
3
4
public static void main(String[] args){
int[] arr = new int[3];
System.out.println(arr);//[I@5f150435
}

程序执行流程:

1.main方法进入方法栈执行。

2.创建数组,JVM会在堆内存中开辟空间,存储数组。

3.数组在内存中会有自己的内存地址,以十六进制表示。

4.数组中有3个元素,默认值0。

5.JVM将数组的内存首地址赋值给引用类型变量arr。

6.变量arr中保存的是数组内存中的地址,而不是一个具体是数值,因此是引用数据类型。

思考:打印arr为什么是[I@5f150435,它是数组的地址吗?

答:它不是数组的地址。

问?不是说arr中存储的是数组对象的首地址吗?

答:arr中存储的是数组的首地址,但是因为数组是引用数据类型,打印arr时,会自动调用arr数组对象的toString()方法,该方法默认实现的是对象类型名@该对象的hashCode()值的十六进制值。

问?对象的hashCode值是否就是对象内存地址?

答:不一定,因为这个和不同品牌的JVM产品的具体实现有关。例如:Oracle的OpenJDK中给出了5种实现,其中有一种是直接返回对象的内存地址,但是OpenJDK默认没有选择这种方式。

两个变量指向同一个数组,本质上代表同一个数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
// 定义数组,存储3个元素
int[] arr = new int[3];
//数组索引进行赋值
arr[0] = 5;
arr[1] = 6;
arr[2] = 7;
//输出3个索引上的元素值
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
//定义数组变量arr2,将arr的地址赋值给arr2
int[] arr2 = arr;
arr2[1] = 9;
System.out.println(arr[1]);
}

4.5 数组的常见算法

1、数组统计

​ 求总和、均值、统计偶数等。

2、数组找最值

1、找最值并记录下标

思路1:假设第一个最大/最小,然后用max/min与后面元素依次比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ArrayFindMaxTest{
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int max = array[0];
int maxIndex = 0;
for(int i = 1;i<array.length;i++){
if(array[i] > max){
max = array[i];
index = i;
}
}
System.out.println("最大值为:" + max);
System.out.println("最大值的下标是:" + maxIndex);
}
}

思路2:用maxIndex时刻记录目前比对的最大/小的下标,那么arr[maxIndex]就是目前的最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
class ArrayFindMaxTest2{
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int maxIndex = 0;
for(int i = 1;i< array.length;i++){
if(array[i] > array[maxIndex]){
maxIndex = i;
}
}
System.out.println("最大值为:" + array[maxIndex]);
System.out.println("最大值的下标是:" + maxIndex);
}
}

3、数组查找

1、顺序查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.Scanner;

public class ArrayOrderSearch {
public static void main(String[] args) {
//找到查找到的第一个数字的位置.
int[] array = {1, 2, 3, 4, 5, 5, 6, 7, 5};

//输入要查找的数字
Scanner input = new Scanner(System.in);
System.out.println("请输入您要查找的数字:");
int searchNum = input.nextInt();

//查找
for (int i = 0; i < array.length; ++i) {
if (array[i] == searchNum) {
System.out.println("数字的下标是: " + i);
break;
}
}
}
}

2、二分查找

不考虑重复元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.Scanner;

public class BinarySearch {
public static void main(String[] args) {
/*
二分查找的前提是数组是整体有序的。数组默认是递增的。
首先选择数组中间的数字和目标数字对比。
如果相等则直接返回答案。
如果不相等-中间的数字大于目标值,则中间向右的均大于目标值,全部排除,right = mid - 1。
-中间的数字小于目标值,则中间向左的均小于目标值,全部排除,left = mid + 1。
进行下一次对比。
*/

//数组要有序,默认是递增。
int[] array = {8, 19, 21, 34, 39, 40};

//输入要查找的整数。
Scanner input = new Scanner(System.in);
System.out.println("请输入您要查找的值: ");
int target = input.nextInt();

//二分查找
int index = -1;//默认-1,找不到就输出-1.
for (int left = 0, right = array.length - 1; left <= right; ) {
int mid = left + (right - left) / 2;

if (array[mid] == target) {
index = mid;
break;
} else if (target > array[mid]) {
left = mid + 1;//目标大于中间值,说明目标左侧值都比目标小,left移动到mid+1位置。
} else if (target < array[mid]) {
right = mid - 1;//目标小于中间值,说明目标右侧值都比目标大,right移动到mid-1位置。
}
}

if(index!=-1){
System.out.println("找到了,目标位置是" + index);
}else {
System.out.println("没有找到。");
}
}
}

考虑重复元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import java.util.Scanner;

public class BinarySearch {
public static void main(String[] args) {
/*
二分查找的前提是数组是整体有序的。数组默认是递增的。
首先选择数组中间的数字和目标数字对比。
如果相等则直接返回答案。
如果不相等-中间的数字大于目标值,则中间向右的均大于目标值,全部排除,right = mid - 1。
-中间的数字小于目标值,则中间向左的均小于目标值,全部排除,left = mid + 1。
进行下一次对比。
*/

//数组要有序,默认是递增。
int[] array = {8, 19, 21, 34, 39, 40};

//输入要查找的整数。
Scanner input = new Scanner(System.in);
System.out.println("请输入您要查找的值: ");
int target = input.nextInt();

//二分查找
int index = -1;//默认-1,找不到就输出-1.
for (int left = 0, right = array.length - 1; left <= right; ) {
int mid = left + (right - left) / 2;

if (array[mid] == target) {
index = mid;
break;
} else if (target > array[mid]) {
left = mid + 1;//目标大于中间值,说明目标左侧值都比目标小,left移动到mid+1位置。
} else if (target < array[mid]) {
right = mid - 1;//目标小于中间值,说明目标右侧值都比目标大,right移动到mid-1位置。
}
}

if (index != -1) {
System.out.println("找到了,目标位置是" + index);
} else {
System.out.println("没有找到。");
}
}
}

class BinarySearch_2 {
public static void main(String[] args) {
/*
如果数组中有重复元素,需要考虑重复元素时,二分查找应该怎么做。
*/

//数组必须有序。
int[] array = {1, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10};

//输入需要查找的数
Scanner input = new Scanner(System.in);
System.out.println("请输入要查找的数:");
int searchNum = input.nextInt();

//二分查找
int index = -1;//如果查找不到,返回-1.
for (int left = 0, right = array.length - 1; left <= right; ) {
int mid = left + (right - left) / 2;
System.out.println("mid = " + mid);

if (array[mid] == searchNum) {
index = mid;
System.out.print(searchNum + "的下标有:" + index);

//使用二分查找的数是"有序",如果有重复元素,那么他们是在一起的。
//继续比较mid左边和右边的元素,直到元素与target不相等为止。

for (int i = mid - 1; i >= left; i--) {
if (array[i] == searchNum) {
System.out.print("," + i);
} else {
break;
}
}
for (int j = mid + 1; j <= right; j++) {
if (array[j] == searchNum) {
System.out.print("," + j);
} else {
break;
}
}
System.out.println();
break;
} else if (searchNum > array[mid]) {
//说明target在mid右边
left = mid + 1;

//如果array右边的元素和array[mid]一样,可以把左边界向右侧移动。
while (array[mid] == array[left]) {
System.out.println("left = " + left);
left++;
}
} else {
//说明target在mid左边
right = mid - 1;

//如果array左边的元素和array[mid]一样,可以把右边界向左侧移动。
while (array[mid] == array[right]) {
System.out.println("right = " + right);
right--;
}
}
}
if (index == -1) {
System.out.println("未找到目标元素。");
}
}
}

4、数组反转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//数组对称位置的元素互换
public class ArrayReverse {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5};//设置数组

//循环遍历打印反转前数组
System.out.print("反转前:");
System.out.print(array[0]);
for (int i = 1; i < array.length; i++) {
System.out.print("," + array[i]);
}
System.out.println();

//反转数组
for (int i = 0; i < array.length / 2; i++) {
int temp = array[i];
array[i] = array[array.length - 1 - i];
array[array.length - 1 - i] = temp;
}

//循环遍历打印反转后数组
System.out.print("反转后:");
System.out.print(array[0]);
for (int i = 1; i < array.length; i++) {
System.out.print("," + array[i]);
}


}
}

5、数组排序

1、排序算法

image-20211222111142684

2、直接选择排序

又称为简单选择排序,从待排序序列中选出最小/最大元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找最小/最大元素,放到已排序序列的末尾。直到全部待排序的数据元素的个数为0。选择排序不稳定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//直接选择排序
//思想:每一轮找出本轮的最大值/最小值,然后看他是否在正确的位置,如果不在就与正确的位置交换。
public static void selectSort(int[] arr) {
//每次找到最小值放入到i的位置,然后i++,在下一个位置放入最小值.
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;//记录最小值的下标
for (int j = i; j < arr.length; j++) {
if(arr[j] < arr[minIndex]){
minIndex = j;
}
}
int tmp = arr[i];//交换最小值和i位置的元素
arr[i] = arr[minIndex];
arr[minIndex] = tmp;
}
}
3、冒泡排序

原理:比较两个相邻的元素,将值大的元素交换至右端。

思路:依次比较相邻的两个数,将小数放到前面,大数放到后面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//冒泡排序 BubbleSort

public class BubbleSort {
public static void main(String[] args) {
int[] array = {1, 4, 2, 3, 6, 7, 8, 9, 4, 5, 6};

//从左到右依次比较,将最大值移动到待比较队列最右。
for (int i = array.length - 1; i > 0; i--) {//用i控制待排序队列的最右侧位置
for (int j = 0; j < i; j++) {
if (array[j] > array[j + 1]) {//相邻之间如果左侧大于右侧,则把左侧值交换到右侧。
int temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
}
}
}

//输出
System.out.print(array[0]);
for (int i = 1; i < array.length; i++) {
System.out.print("," + array[i]);
}
System.out.println();
}
}
4、冒泡排序优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//多加一个flag来控制比较次数,如果某一次排序过程中,整个数组顺序都无变动,说明已经是有序状态。

//冒泡排序 BubbleSort

public class BubbleSort {
public static void main(String[] args) {
int[] array = {1, 4, 2, 3, 6, 7, 8, 9, 4, 5, 6};

//从左到右依次比较,将最大值移动到待比较队列最右。
for (int i = array.length - 1; i > 0; i--) {//用i控制待排序队列的最右侧位置
for (int j = 0; j < i; j++) {
if (array[j] > array[j + 1]) {//相邻之间如果左侧大于右侧,则把左侧值交换到右侧。
int temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
}
}
}

//输出
System.out.print(array[0]);
for (int i = 1; i < array.length; i++) {
System.out.print("," + array[i]);
}
System.out.println();
}
}

class BubbleSort2 {
public static void main(String[] args) {
int[] array = {1, 4, 2, 3, 6, 7, 8, 9, 4, 5, 6};

for (int i = array.length - 1; i > 0; i--) {
boolean flag = true;
for (int j = 0; j < i; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
flag = false;
}
}

if(flag){
break;
}
}

//输出排序后数组
System.out.print(array[0]);
for (int i = 1; i < array.length; i++) {
System.out.print("," + array[i]);
}
System.out.println();
}
}
5、快速排序

分区\递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void QuickSort(int left, int right, int... arr) {

//判定递归结束条件,right == left
if (right <= left) {
return;
}

//需要定义pivot,first,last.为了方便起见,pivot直接就定义成first位置的元素
int first = left;//数组最左侧元素下标
int last = right;//数组最右侧元素下标
int pivot = arr[left];//pivot默认为下标为left的元素,在一趟快速排序后,应当实现的效果是:pivot左侧都比pivot位置元素小,pivot右侧都比pivot位置元素大.

//left < right的情况下就一直进行循环,直到left == right
while (first < last) {
//从right指针开始,将right位置与基准值进行比较
while (first < last && pivot <= arr[last]) {//如果left < right 且 right位置的元素比基准值大 那就right--,继续向左探寻
last--;
}
arr[first] = arr[last];//如果left < right 且 right位置的元素比基准值小 那就把right指针的值放入left指针位置

//right探测完毕后,将left位置与基准值进行比较
while (first < last && pivot >= arr[first]) {//如果left < right 且 left位置的元素比基准值小,那就left++,继续向右探寻
first++;
}
arr[last] = arr[first];//如果left < right 且 left位置的元素比基准值大,那就把left指针的元素放入到right指针元素.
}

arr[first] = pivot;

//分别递归左右子数列,直到数列中只剩下一个元素
QuickSort(left, last - 1, arr);
QuickSort(first + 1, right, arr);
}
6、idea中的排序调用
1
arrays.

6、对象数组

数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组。

注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。

1、对象数组的声明和使用

案例:

(1)定义矩形类,包含长、宽属性,area()求面积方法,perimeter()求周长方法,String getInfo()返回圆对象的详细信息的方法

(2)在测试类中创建长度为5的Rectangle[]数组,用来装3个矩形对象,并给3个矩形对象的长分别赋值为10,20,30,宽分别赋值为5,15,25,遍历输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Rectangle {
double height;
double width;

double area() {//面积
return height * width;
}

double perimeter() {//周长
return 2 * (height + width);
}

String getInfo() {
return "长:" + height +
",宽:" + width +
",面积:" + area() +
",周长:" + perimeter();
}
}

public class ObjectArrayTest {
public static void main(String[] args) {
//声明并创建一个长度为3的矩形对象数组
Rectangle[] rect = new Rectangle[3];

//创建3个矩形对象,并为3个对象的实例变量赋值
//3个矩形对象的长分别是10,20,30
//3个矩形对象的宽分别是5,15,25
//调用矩形对象的getInfo()返回对象信息后输出
for (int i = 0; i < rect.length; i++) {
//创建矩形对象
rect[i] = new Rectangle();

//为矩形对象的成员变量赋值
rect[i].height = (i + 1) * 10;
rect[i].width = (2 * i + 1) * 5;

//获取并输出对象的信息
System.out.println(rect[i].getInfo());
}
}
}

4.6 二维数组

1. 二维数组的概念

如果需要同时存储多组同类型的数据,就需要使用二维数组。

二维数组:本质上就是元素为一维数组的一个数组。

二维数组的标记:[][]

1
int[][] arr; //arr是一个二维数组,可以看成元素是int[]一维数组类型的一维数组

二维数组声明的语法格式:

1
2
3
4
5
6
7
//推荐
元素的数据类型[][] 二维数组名;

//不推荐
元素的数据类型 二维数组名[][];
//不推荐
元素的数据类型[] 二维数组名[];

例如:

1
2
3
4
5
6
7
8
9
public class ArrayDefine {
public static void main(String[] args) {
//存储多组成绩
int[][] grades;

//存储多组姓名
String[][] names;
}
}

面试:

1
int[] x,y[];//x是一维数组,y是二维数组f

2. 二维数组的静态初始化

静态初始化:用静态数据(编译时已知)为数组初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//以下格式要求声明与静态初始化必须一起完成。

//静态初始化1
元素数据类型[][] 二维数组的名称 = {
{元素1,元素2,元素3...},
{第二行的值列表 ...},
...
{第n行的值列表 ...}
};

//静态初始化2
元素的数据类型[][] 二维数组名 = new 元素数据类型[][]{
{元素1,元素2,元素3...},
{ },
{ }
};

//静态初始化3
元素的数据类型[][] 二维数组名;
二维数组名 = new 元素的数据类型[][]{
{元素1,元素2,元素3...},
{第二行的值列表 ...},
{ }
};

如果是静态初始化,右侧的[ ] [ ]不可以写数字,行数和列数由{ }中的元素决定。

3. 二维数组的使用

  • 二维数组的长度/行数:二维数组名.length
  • 二维数组的某一行:二维数组名[行下标],此时相当于获取其中一组数据。它本质上是一个一维数组。行下标的范围:[0, 二维数组名.length-1]。此时把二维数组看成一维数组的话,元素是行对象。
  • 某一行的列数:二维数组名[行下标].length,因为二维数组的每一行是一个一维数组。
  • 某一个元素:二维数组名[行下标][列下标],即先确定行/组,再确定列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//用二维数组存储3个组的每个学员的成绩
public class TwoDimensionalArrayUse {
public static void main(String[] args) {

//声明并初始化二维数组array
int[][] score = {
{80,60,90,50,40},
{90,65,95,35,45},
{20,30,40,50}
};

//输出行数
System.out.println("一共有" + score.length+1 + "组");//输出行数

//输出每个组有多少个成员,即每行有多少列
for(int i = 0;i<score.length;i++){
System.out.println("第" + i + "组一共有" + score[i].length + "个学员");
}

//输出每个组的成员的成绩,即每行每列的元素。
for(int i = 0;i<score.length;i++){
System.out.print("第一组的成绩是:");
for (int j = 0;j<score[i].length-1;j++){
System.out.print(score[i][j] + ",");
}
System.out.print(score[i][score.length-1]);
System.out.println();
}

}
}

4. 二维数组的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for(int i=0; i<二维数组名.length; i++){ //二维数组对象.length
for(int j=0; j<二维数组名[i].length; j++){//二维数组行对象.length
System.out.print(二维数组名[i][j]);
}
System.out.println();
}

//强化for循环遍历二维数组
for (int[] arr : arrArr) {
for (int i : arr) {
System.out.print(i + " ");
}
System.out.println();
}

5. 二维数组的动态初始化

如果二维数组的每一个数据,甚至是每一行的列数,需要后期单独确定,那么就只能使用动态初始化方式了。动态初始化方式分为两种格式:

(1)规则二维表:每一行的列数是相同的

1
2
3
4
5
6
7
8
9
//(1)确定行数和列数
元素的数据类型[][] 二维数组名 = new 元素的数据类型[m][n];
m:表示这个二维数组有多少个一维数组。或者说一共二维表有几行
n:表示每一个一维数组的元素有多少个。或者说每一行共有一个单元格

//此时创建完数组,行数、列数确定,而且元素也都有默认值

//(2)再为元素赋新值
二维数组名[行下标][列下标] = 值;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class TwoDimensionalArrayDynamicInit {
public static void main(String[] args) {
//确定好m行n列
int m = Integer.parseInt(args[0]);
int n = Integer.parseInt(args[1]);

//(1)声明m行n列的二维数组,声明完成后,行数列数确定,元素也都有默认值。
int[][] array = new int[m][n];

//(2)为元素赋新值。
array[0][0] = 10;

System.out.println("第1行第1列的数值是:" + array[0][0] + " 第2行2列的数值是:" + array[1][1]);//第1行第1列的数值是:10 第2行2列的数值是:0

//遍历二维数组并赋值
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
array[i][j] = i + 1;
}
}

//遍历二维数组并输出
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
System.out.print(array[i][j] + " ");
}
System.out.println();
}
}
}

(2)不规则:每一行的列数不一样

1
2
3
4
5
6
7
8
9
10
11
12
//(1)先确定总行数
元素的数据类型[][] 二维数组名 = new 元素的数据类型[总行数][];

//此时只是确定了总行数,每一行里面现在是null

//(2)再确定每一行的列数,创建每一行的一维数组
二维数组名[行下标] = new 元素的数据类型[该行的总列数];

//此时已经new完的行的元素就有默认值了,没有new的行还是null

//(3)再为元素赋值
二维数组名[行下标][列下标] = 值;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 动态初始化-不规则二维表-每一行列数不同
*/
//(1)声明m行二维数组,声明完成后,行数确定,列数不确定。每一行里面现在是null
int[][] array1 = new int[3][];

//(2)为每一行规定列数。创建每一行的一维数组。
array1[0] = new int[1];//二维数组名[行下标] = new 元素数据类型[该行的总列数];第1行有1列
array1[1] = new int[2];//第2行有2列
array1[2] = new int[3];//第3行有3列

//(3)为每个元素赋值
for (int i = 0; i < array1.length; i++) {
for (int j = 0; j < array1[i].length; j++) {
array1[i][j] = i+1;
}
}

//输出二维数组所有元素。
for (int i = 0;i<array1.length;i++){
for(int j = 0;j<array1[i].length;j++){
System.out.print(array1[i][j] + " ");
}
System.out.println();
}

6. 空指针异常

1
2
3
4
5
6
7
8
public class Test26NullPointerException {
public static void main(String[] args) {
//定义数组
int[][] arr = new int[3][];

System.out.println(arr[0][0]);//NullPointerException
}
}

因为此时数组的每一行还未分配具体存储元素的空间,此时arr[0]是null,此时访问arr[0][0]会抛出NullPointerException 空指针异常。

4.7 练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//打印一个10行的杨辉三角
/*
1. 第一行有 1 个元素, 第 n 行有 n 个元素
2. 每一行的第一个元素和最后一个元素都是 1
3. 从第三行开始, 对于非第一个元素和最后一个元素的元素.
yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];
*/
public class YangHuiTriangle {
public static void main(String[] args) {
//声明二维数组并定义行数
int[][] array = new int[10][];
//定义二维数组的列数
for (int i = 0; i < 10; i++) {
array[i] = new int[i + 1];
}

for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
if (j == 0 || i == j) {
array[i][j] = 1;
} else {
array[i][j] = array[i - 1][j - 1] + array[i - 1][j];
}
}
}

//输出杨辉三角
for(int i = 0;i<array.length;i++){
for(int j = 0;j<array[i].length;j++){
System.out.printf("%-4d",array[i][j]);
}
System.out.println();
}
}
}

4.8 可变参数

//数组形参定义方法

public static void test(int a ,String[] books);

//以可变个数形参来定义方法

public static void test(int a ,String … books);

说明

1.可变参数:方法参数部分指定类型的参数个数是可变多个

2.声明方式:方法名(参数的类型名…参数名)

3.可变参数方法的使用与方法参数部分使用数组是一致的

4.方法的参数部分有可变形参,需要放在形参声明的最后

5.每个形参列表都只能有一个可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void test(String[] msg){
System.out.println(“含字符串数组参数的test方法 ");
}
public void test1(String book){
System.out.println(“****与可变形参方法构成重载的test1方法****");
}
public void test1(String ... books){
System.out.println("****形参长度可变的test1方法****");
}
public static void main(String[] args){
TestOverload to = new TestOverload();
//下面两次调用将执行第二个test方法
to.test1();
to.test1("aa" , "bb");
//下面将执行第一个test方法
to.test(new String[]{"aa"});
}

4.9 取子数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 取子数组
*/
//先把奇数放入新数组
int count = 0;
int[] newArr = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
if(arr[i] % 2 != 0){
newArr[count] = arr[i];
count++;
}
}

//缩减数组
int[] newNewArr = new int[count];//如果count为空,则表示空数组
for (int i = 0; i < newNewArr.length; i++) {
newNewArr[i] = newArr[i];
}

newArr = newNewArr;

for (int i = 0; i < newArr.length; i++) {
System.out.print(newArr[i] + " ");
}

5. 面向对象编程Oriented Programming

3条主线:

1)类和成员的研究

  • 真成员(可以被继承)

    • 属性 : 描述特征
    • 方法 : 描述行为
    • 内部类 :
  • 伪成员(不能被继承)

    • 构造器 : 初始化
    • 语句块 : 初始化

2)三大特征

  • 封装 Encapsulation
  • 继承 Inheritance
  • 多态 Polymorphism

3)其他关键字

this, pacakge, import, super, static, abstract, extends, implements, interface, enum, native…

5.1 面向对象编程

1、编程语言概述

所有的计算机程序一直都是围绕着两件事在进行的,程序设计就是用某种语言编写代码来完成这两件事。

  1. 如何表示和存储数据
    • 基本数据类型的常量和变量:表示和存储一个个独立的数据
    • 对象:表示和存储与某个具体事物相关的多个数据(例如:某个学生的姓名、年龄、联系方式等)
    • 数据结构:表示和存储一组对象,数据结构有数组、链表、栈、队列、散列表、二叉树、堆……
  2. 基于这些数据都有什么操作行为,其实就是实现什么功能
    • 数据的输入和输出
    • 基于一个或两个数据的操作:赋值运算、算术运算、比较运算、逻辑运算等
    • 基于一组数据的操作:统计分析、查找最大值、查找元素、排序、遍历等

2、程序设计方法(面向过程/对象)

  1. 面向过程的程序设计思想(Process-Oriented Programming),简称POP

    • 关注的焦点是过程:过程就是操作数据的步骤,如果某个过程的实现代码在很多地方重复出现,那么就可以把这个过程抽象为一个函数,这样就可以大大简化冗余代码,也便于维护。

    • 代码结构:以函数为组织单位。独立于函数之外的数据称为全局数据,在函数内部的称为局部数据。

  2. 面向对象的程序设计思想( Object Oriented Programming),简称OOP

    • 关注的焦点是类:面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。某个事物的一个具体个体称为实例或对象。
    • 代码结构:以类为组织单位。每种事物都具备自己的属性(即表示和存储数据,在类中用成员变量表示)和行为/功能(即操作数据,在类中用成员方法表示)。

3、类和对象

1.什么是类

是一类具有相同特性的事物的抽象描述,是一组相关属性行为的集合。

  • 属性:就是该事物的状态信息。
  • 行为:就是在你这个程序中,该状态信息要做什么操作,或者基于事物的状态能做什么。

2.什么是对象

对象是一类事物的一个具体个体。即对象是类的一个实例,必然具备该类事物的属性和行为。

例如:做一个养宠物的小游戏

类:人、猫、狗等

1
2
3
4
5
6
7
8
9
10
11
12
public class Dog{
String type; //种类
String nickname; //昵称
int energy; //能量

//吃东西
public void eat(){
System.out.println("吃东西...");
energy += 10;

}
}

3.类和对象的关系

类是对一类事物的描述,是抽象的

对象是一类事物的实例,是具体的

类是对象的模板,对象是类的实体

4、如何定义类

1、类的定义格式

关键字:class

1
2
3
[修饰符] class 类名{

}
1
2
3
public class student{

}

2、对象的创建

关键字:new

1
2
3
4
类名 变量名 = new 类名()// 通过new操作符在内存中创建对象实体

//给创建的对象声明一个引用变量, 并用其保存对象地址
//通过引用中的地址值就可以多次地定位对象并使用对象了

那么,引用变量中存储的是什么呢?答:对象地址

1
2
3
4
5
6
7
8
9
10
11
public class TestStudent{
public static void main(String[] args){
System.out.println(new Student()); // 但是这里打印的并不是地址

Student stu = new Student();
System.out.println(stu);// 但是这里打印的并不是地址, 是对象的toString()方法的结果

int[] arr = new int[5];
System.out.println(arr);// 但是这里打印的并不是地址
}
}

5.2 成员变量

1、如何声明成员变量

1
2
3
[修饰符] class 类名{
[修饰符] 数据类型 成员变量名;
}

示例:

1
2
3
4
5
public class Person{
String name;
char gender;
int age;
}

位置要求:必须在类中,方法外

类型要求:可以是Java的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等)

修饰符:成员变量的修饰符有很多,例如:public、protected、private、static、volatile、transient、final等

其中static可以将成员变量分为两大类,静态变量和非静态变量。其中静态变量又称为类变量,非静态变量又称为实例变量或者属性。

2、对象的实例变量

1、实例变量的特点

(1)实例变量的值是属于某个对象的

  • 必须通过对象才能访问实例变量
  • 每个对象的实例变量的值是独立的

(2)实例变量有默认值

分类 数据类型 默认值
基本类型 整数(byte,short,int,long) 0
浮点数(float,double) 0.0
字符(char) ‘\u0000’
布尔(boolean) false
数据类型 默认值
引用类型 数组,类,接口 null

2、实例变量的访问

1
对象.实例变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "我";
p1.age = 18;
p1.gender = "男";

System.out.println(p1.name);
System.out.println(p1.age);
System.out.println(p1.gender);
System.out.println("-----------------");
Person p2 = new Person();

System.out.println(p2.name);
System.out.println(p2.age);
System.out.println(p2.gender);
}
}

class Person{
String name;
String gender;
int age;
}

3、实例变量的内存分析

Java对象保存在内存中时,由以下三部分组成:

  • 对象头
    • Mark Word:记录了和当前对象有关的GC、锁等信息。
    • 指向类的指针:每一个对象需要记录它是由哪个类创建出来的,而Java对象的类数据保存在方法区,指向类的指针就是记录创建该对象的类数据在方法区的首地址。该指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
    • 数组长度(只有数组对象才有)
  • 实例数据
    • 即实例变量的值
  • 对齐填充
    • 因为JVM要求Java对象占的内存大小应该是8bit的倍数,如果不满足该大小,则需要补齐至8bit的倍数,没有特别的功能。

image-20211226153433712

4、实例变量与局部变量的区别

1、声明位置和方式
(1)实例变量:在类中方法外
(2)局部变量:在方法体{}中或方法的形参列表、代码块中

2、在内存中存储的位置不同
(1)实例变量:堆
(2)局部变量:栈

3、生命周期
(1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡,
而且每一个对象的实例变量是独立的。
(2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡,
而且每一次方法调用都是独立。

4、作用域
(1)实例变量:通过对象就可以使用,本类中“this.xx“,没有歧义还可以省略this.”,其他类中“对象.xx”
(2)局部变量:出了作用域就不能使用

5、修饰符
(1)实例变量:public,protected,private,final,volatile,transient等
(2)局部变量:final

6、默认值
(1)实例变量:有默认值
(2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。

5.3 封装encapsulation

1、封装概述

随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合”,而“高内聚,低耦合”的体现之一:

  • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
  • 低耦合:仅对外暴露少量的方法用于使用

隐藏对象内部的复杂性,只对外公开简单和可控的访问方式,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

2、如何实现封装

实现封装就是指控制类或成员的可见范围。这就需要依赖访问控制修饰符,也称为权限修饰符来控制。

修饰符 本类 本包 其他包子类 其他包非子类
private × × ×
缺省 × ×
protected ×
public

外部类:public和缺省

成员变量、成员方法、构造器、成员内部类:public,protected,缺省,private

3、成员变量/属性私有化问题

成员变量(field)私有化之后,提供标准的get/set方法,我们把这种成员变量也称为属性(property)

get/set操作的就是事物的属性,哪怕它没有对应的成员变量。

1、成员变量封装的目的

  • 隐藏类的实现细节
  • 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
  • 便于修改,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。

2、成员变量的实现步骤

  1. 使用private修饰成员变量
1
private 数据类型 变量名;

代码如下:

1
2
3
4
5
class Person {
private String name;
private int age;
private boolean marry;
}
  1. 提供 getXxx方法 / setXxx 方法,可以访问成员变量,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Person{
private String name;
private int age;
private boolean marry;

//设置姓名
public void setName(String sName){
name = n;
}

//获取姓名,此处使用了封装的原理,成员变量的控制修饰符private。
public String getName(){
return name;
}

//设置姓名
public void setAge(int sAge){
age = newAge;
}

//获取年龄
public int getAge(){
return age;
}

//设置结婚
public void setMarry(boolean sMarry){
marry = sMarry;
}

//获取结婚
public boolean isMarry(){
return marry;
}
}
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class PersonPackagingTest {
public static void main(String[] args) {
Person1 p = new Person1();

p.setName("哈哈");
p.setAge(15);
p.setMarry(true);

System.out.println(p.getAge());
System.out.println(p.getName());
System.out.println(p.isMarry());
System.out.println("---------------------");
}
}

class Person1{

private String name;
private int age;
private boolean marry;

//设置姓名
public void setName(String sName){
name = sName;
}

//获取姓名,此处使用了封装的原理,成员变量的控制修饰符private。
public String getName(){
return name;
}

//设置姓名
public void setAge(int sAge){
age = sAge;
}

//获取年龄
public int getAge(){
return age;
}

//设置结婚
public void setMarry(boolean sMarry){
marry = sMarry;
}

//获取结婚
public boolean isMarry(){
return marry;
}
}

4、实例方法使用当前对象的成员

在实例方法中还可以使用当前对象的其他成员。在Java中当前对象用this表示。

  • this:在实例方法中,表示调用该方法的对象。
  • 如果没有歧义,完全可以省略this。

1、使用this.

案例:矩形类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class ThisTest {
public static void main(String[] args) {
Rectangle rect = new Rectangle();

rect.setLength(10);
rect.setWidth(5);

System.out.println(rect.getInfo());
}
}

class Rectangle {
private int length;
private int width;

//获得长度
int getLength(){
return this.length;
}

//获得宽度
int getWidth(){
return this.width;
}

//设置长度
void setLength(int sLength){
this.length = sLength;
}

//设置宽度
void setWidth(int sWidth){
this.width = sWidth;
}

//计算面积
int area() {
return this.length * this.width;
}

//计算周长
int perimeter(){
return 2*(this.length + this.width);
}

//打印矩形
void print(char sign){
for(int i = 0;i<this.width;i++){
for(int j = 0;j<this.length;j++){
System.out.println(sign);
}
System.out.println();
}
}

//获得矩形信息
String getInfo(){
return "长:" + this.length + ",宽:" + this.width + ",面积:" + this.area() + ",周长:" + this.perimeter();
}
}

2、省略this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class ThisTest {
public static void main(String[] args) {
Rectangle rect = new Rectangle();

rect.setLength(10);
rect.setWidth(5);

System.out.println(rect.getInfo());
rect.print('*');
}
}

class Rectangle {
private int length;
private int width;

//获得长度
int getLength(){
return length;
}

//获得宽度
int getWidth(){
return width;
}

//设置长度
void setLength(int sLength){
length = sLength;
}

//设置宽度
void setWidth(int sWidth){
width = sWidth;
}

//计算面积
int area() {
return length * width;
}

//计算周长
int perimeter(){
return 2*(length + width);
}

//打印矩形
void print(char sign){
for(int i = 0;i<width;i++){
for(int j = 0;j<length;j++){
System.out.print(sign);
}
System.out.println();
}
}

//获得矩形信息
String getInfo(){
return "长:" + length + ",宽:" + width + ",面积:" + area() + ",周长:" + perimeter();
}
}

练习:

1679729205327

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class PackagingBoyGirl {
public static void main(String[] args) {
Girl mm = new Girl();
Boy gg = new Boy();

mm.setName("MM");
gg.setName("GG");

gg.marry(mm);
mm.marry(gg);
}
}


//设置实例Girl
class Girl{
private String name;

void setName(String i){
name = i;
}

String getName(){
return name;
}

void marry(Boy boy){
System.out.println(getName() + " Married to:" + boy.getName());
}
}

//设置实例Boy
class Boy{
private String name;
private int age;

void setName(String i){
name = i;
}

String getName(){
return name;
}

void setAge(int i){
age = i;
}

int getAge(){
return age;
}

void marry(Girl girl){
System.out.println(getName() + " Married to:" + girl.getName());
}

void shout(){

}
}

5.4 构造器(构造方法)

new完对象后,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。Java给我们提供了构造器(Constructor),直接为当前对象的某个或所有成员变量直接赋值。

1、构造器的作用

在创建对象时为对象进行初始化工作的特殊方法.

2、构造器的语法格式

构造器又称为构造方法。

首字母大写

1
2
3
4
5
6
7
8
【修饰符】 class 类名{
【修饰符】 类名(){
// 实例初始化代码
}
【修饰符】 类名(参数列表){
// 实例初始化代码
}
}

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Constructor {
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student("GG",18);

System.out.println(stu1.getInfo());
System.out.println(stu2.getInfo());
}
}

class Student{
String name;
int age;

//无参构造
public Student(){};

//有参构造
public Student(String name,int age){
this.name = name;
this.age = age;
}

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getInfo(){
return "姓名是:" + this.name + ",年龄是:" + this.age;
}
}

JavaBean

JavaBean是一种Java语言写成的可重用组件。

所谓javaBean,是指符合如下标准的Java类:

  • 类是公共的

  • 有一个无参的公共的构造器

  • 有属性,且有对应的get、set方法

用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class JavaBean{
private String name;
private int age;

//无参构造
public JavaBean(){};

//含参构造
public JavaBean(String name,int age){
setName(name);
}

public void setName(String name){
this.name = name;
}

public String getName(){
return name;
}

public void setAge(int age){
this.age = age;
}

public int getAge(){
return age;
}

5.5 包(package)

1、包的作用

(1)可以避免类重名:有了包之后,类的全名称就变为:包.类名

(2)可以控制某些类型或成员的可见范围

如果某个类型或者成员的权限修饰缺省的话,那么就仅限于本包使用。

(3)分类组织管理众多的类

2、声明包

关键字package

1
package 包名

注意:

(1)必须在源文件的代码首行

(2)一个源文件只能有一个声明包的package语句

包的命名规范和习惯:
(1)所有单词都小写,每一个单词之间使用.分割
(2)习惯用公司的域名倒置开头

例如:com.aierns.xxx;

3、跨包使用类

==注意:==只有public的类才能被跨包使用

(1)使用类型的全名称

例如:java.util.Scanner input = new java.util.Scanner(System.in);

(2)使用import 语句之后,代码中使用简名称

import语句告诉编译器到哪里去寻找类。

import语句的语法格式:

1
2
import 包.类名;
import 包.*;

注意:

使用java.lang包下的类,不需要import语句,就直接可以使用简名称

import语句必须在package下面,class的上面

当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。一个使用全名称,一个使用简名称

5.6 继承(inheritance)

1、Java中的继承

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。

多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类超类(superclass)或者基类

继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用或更一般,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。

2、继承的好处

  • 提高代码的复用性
  • 提高代码的扩展性
  • 表示类与类之间的is-a关系

3、继承的语法格式

通过 extends 关键字,可以声明一个子类继承另外一个父类。

1
2
3
4
5
6
7
【修饰符】 class 父类 {
...
}

【修饰符】 class 子类 extends 父类 {
...
}

父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* 定义动物类Animal,做为父类
*/
public class Animal {
// 定义name属性
String name;
// 定义age属性
int age;

// 定义动物的吃东西方法
public void eat() {
System.out.println(age + "岁的"
+ name + "在吃东西");
}
}

子类

1
2
3
4
5
6
7
8
9
10
11
/*
* 定义猫类Cat 继承 动物类Animal
*/
public class AnimalCat extends Animal{
int count;//记录每只猫抓老鼠的数量

public void catchMouse(){
count++;
System.out.println("已经抓了" + count + "只老鼠");
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
public class AnimalTest {
public static void main(String[] args) {
AnimalCat cat = new AnimalCat();
cat.name = "Tom";
cat.age = 2;
cat.eat();
cat.catchMouse();
cat.catchMouse();
cat.catchMouse();
}
}

4、继承的特点

  • 子类会继承父类所有成员(构造器除外)
  • Java只支持单继承,不支持多重继承(只能继承一个)
  • Java支持多层继承(继承体系)
  • 一个父类可以同时拥有多个子类

5、IDEA中如何查看继承关系

1、子类和父类是一种相对的概念。例如:B类对于A类来说是子类,但是对于C类来说是父类。

2、查看继承父系快捷键

选择A类名,按Ctrl+H就会显示A类的继承树。

例如:在类继承目录树中选中某个类,比如C类,按Ctrl+ Alt+U就会用图形化方式显示C类的继承祖宗。

6、权限修饰符限制问题

  • 外部类要跨包使用必须是public,否则仅限于本包使用
  • 成员的权限修饰符问题

(1)本包下使用:成员的权限修饰符可以是public、protected、缺省

(2)跨包使用时,如果类的权限修饰符缺省,成员权限修饰符>类的权限修饰符也没有意义

  • 父类成员变量私有化

子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。

7、继承注解

1
2
* @Override // 注解(annotation) : 是特殊的注释, 特殊在于可以被编译器和JVM识别.
* @Override此注解的作用就是提醒编译器, 这个方法要重写父类方法了, 请帮助我作条件检查. 如果有问题, 请编译出错

8、this与super的区别

1
2
* this : 表示方法的调用者对象的引用
* super : 表示本类的父类中继承来的成员的限定, 并不是父类对象
1
2
* this.属性 和 this.方法() 都有追溯性, 优先从本类中开始查找, 如果本类没有, 向上追溯
* super.属性 和 super.方法() 也有追溯性, 先从直接父类开始查找, 向上追溯.

9、子类构造器

  • 子类中所有的构造器默认都会调用父类中空参数的构造器

  • 子类构造器中的第1行, 默认就是super(); 作用就是调用父类构造器. 它必须在第一行, 达到了先调用父类构造器的效果.

  • 子类构造器中的第1行一定要么是super(…)要么是 this(…)

  • super(…) 直接调用父类构造器,this(…) 间接调用父类构造器

    结论 :

    •  子类构造器中的第一行必须要有对父类构造器的调用. 第一行的作用是保证先父后子.
      

5.7 方法重写(Override)

父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)

1、方法重写

1
2
3
4
5
6
7
8
9
10
11
public class Phone {
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}
1
2
3
4
5
6
7
8
9
//smartphone:智能手机
public class Smartphone extends Phone{
//重写父类的来电显示功能的方法
public void showNum(){
//来电显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class TestOverride {
public static void main(String[] args) {
// 创建子类对象
Smartphone sp = new Smartphone();

// 调用父类继承而来的方法
sp.call();

// 调用子类重写的方法
sp.showNum();
}
}

2、在子类中如何调用父类被重写的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.atguigu.inherited.method;

//smartphone:智能手机
public class Smartphone extends Phone{
//重写父类的来电显示功能的方法
public void showNum(){
//来电显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");

//保留父类来电显示号码的功能
super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出
}
}

3、IDEA重写方法快捷键:Ctrl+O

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//smartphone:智能手机
public class Smartphone extends Phone{
//重写父类的来电显示功能的方法
public void showNum(){
//来电显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");

//保留父类来电显示号码的功能
super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出
}

@Override
public void call() {
super.call();
System.out.println("视频通话");
}
}

@Override:写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。

4、重写方法的要求

1.必须保证父子类之间重写方法的方法签名必须一致(返回值类型 方法名, 参数列表)。

注意:如果返回值类型是基本数据类型和void,那么必须是相同.如果返回值类型是引用型时,子类重写方法可以返回父类方法的返回值的子类类型.

2.子类方法的权限必须【大于等于】父类方法的权限修饰符。

注意:public > protected > 缺省 > private

父类私有方法不能重写

跨包的父类缺省的方法也不能重写

3.父类不能被static,final,private修饰

4.子类重写方法抛出的受检异常异常类型要小于等于父类方法抛出的受检异常.

5、方法的重载和方法的重写

方法的重载:方法名相同,形参列表不同。不看返回值类型。

方法的重写:必须保证父子类之间重写方法的方法签名必须一致(返回值类型 方法名, 参数列表)

(1)同一个类中

1
2
3
4
5
6
7
8
9
10
11
public class TestOverload {
public int max(int a, int b){
return a > b ? a : b;
}
public double max(double a, double b){
return a > b ? a : b;
}
public int max(int a, int b,int c){
return max(max(a,b),c);
}
}

(2)父子类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TestOverloadOverride {
public static void main(String[] args) {
Son s = new Son();
s.method(1);//只有一个形式的method方法

Daughter d = new Daughter();
d.method(1);
d.method(1,2);//有两个形式的method方法
}
}

class Father{
public void method(int i){
System.out.println("Father.method");
}
}
class Son extends Father{
public void method(int i){//重写
System.out.println("Son.method");
}
}
class Daughter extends Father{
public void method(int i,int j){//重载
System.out.println("Daughter.method");
}
}

5.8 多态(polymorphism)

多态是继封装、继承之后,面向对象的第三大特性。

跑的动作,小猫、小狗和大象,跑起来是不一样的。可见,同一行为,对于不同的事物,可以体现出来的不同的形态。那么此时就会出现各种子类的类型。

子类对象有多种父类形态,把子类对象作为父类对象使用.

1、多态解决什么样的问题

案例:

(1)声明一个Dog类,包含public void eat()方法,输出“狗狗啃骨头”

(2)声明一个Cat类,包含public void eat()方法,输出“猫咪吃鱼仔”

(3)声明一个Person类,

  • 包含宠物属性
  • 包含领养宠物方法 public void adopt(宠物类型 pet)
  • 包含喂宠物吃东西的方法 public void feed(),实现为调用宠物对象.eat()方法
1
2
3
4
5
public class Dog {
public void eat(){
System.out.println("狗狗啃骨头");
}
}
1
2
3
4
5
public class Cat {
public void eat(){
System.out.println("猫咪吃鱼仔");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Person {
private Dog dog;

//adopt:领养
public void adopt(Dog dog){
this.dog = dog;
}

//feed:喂食
public void feed(){
if(dog != null){
dog.eat();
}
}
/*
问题:
1、从养狗切换到养猫怎么办?
修改代码把Dog修改为养猫?
2、或者有的人养狗,有的人养猫怎么办?
3、要是同时养多个狗,或猫怎么办?
4、要是还有更多其他宠物类型怎么办?
如果Java不支持多态,那么上面的问题将会非常麻烦,代码维护起来很难,扩展性很差。
*/
}

2、多态的形式和体现

1. 多态引用

Java规定父类类型的变量可以接收子类类型的对象。

1
父类类型 变量名 = 子类对象;

父类类型:指子类继承的父类类型,或者实现的父接口类型。

所以说继承是多态的前提

2. 多态引用的表现

表现:编译时类型与运行时类型不一致,编译时看“父类”,运行时看“子类”。

3. 多态引用的好处与弊端

弊端:编译时,只能调用父类声明的方法,不能调用子类扩展的方法;

好处:运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。

4. 多态演示

让Dog和Cat都继承Pet宠物类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Pet {
private String nickname;

public String getNickname() {
return nickname;
}

public void setNickname(String nickname) {
this.nickname = nickname;
}

public void eat(){
System.out.println(nickname + "吃东西");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Cat extends Pet {
//子类重写父类的方法
@Override
public void eat() {
System.out.println("猫咪" + getNickname() + "吃鱼仔");
}

//子类扩展的方法
public void catchMouse() {
System.out.println("抓老鼠");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Dog extends Pet {
//子类重写父类的方法
@Override
public void eat() {
System.out.println("狗狗" + getNickname() + "啃骨头");
}

//子类扩展的方法
public void watchHouse() {
System.out.println("看家");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestPet {
public static void main(String[] args) {
//多态引用
Pet pet = new Dog();
pet.setNickname("小白");

//多态的表现形式
/*
编译时看父类:只能调用父类声明的方法,不能调用子类扩展的方法;
运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;
*/
pet.eat();//运行时执行子类Dog重写的方法
// pet.watchHouse();//不能调用Dog子类扩展的方法

pet = new Cat();
pet.setNickname("雪球");
pet.eat();//运行时执行子类Cat重写的方法
}
}

3、应用多态解决问题

  1. 声明变量是父类类型,变量赋值子类对象
  • 方法的形参是父类类型,调用方法的实参是子类对象
  • 实例变量声明父类类型,实际存储的是子类对象
1
2
3
4
5
6
7
8
9
public class OnePersonOnePet {
private Pet pet;
public void adopt(Pet pet) {//形参是父类类型,实参是子类对象.
this.pet = pet;
}
public void feed(){
pet.eat();//pet实际引用的对象类型不同,执行的eat方法也不同
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestOnePersonOnePet {
public static void main(String[] args) {
OnePersonOnePet person = new OnePersonOnePet();

Dog dog = new Dog();
dog.setNickname("小白");
person.adopt(dog);//实参是dog子类对象,形参是父类Pet类型
person.feed();

Cat cat = new Cat();
cat.setNickname("雪球");
person.adopt(cat);//实参是cat子类对象,形参是父类Pet类型
person.feed();
}
}
  1. 数组元素是父类类型,元素对象是子类对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class OnePersonManyPets {
    private Pet[] pets;//数组元素类型是父类类型,元素存储的是子类对象

    public void adopt(Pet[] pets) {
    this.pets = pets;
    }

    public void feed() {
    for (int i = 0; i < pets.length; i++) {
    pets[i].eat();//pets[i]实际引用的对象类型不同,执行的eat方法也不同
    }
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestPets {
public static void main(String[] args) {
Pet[] pets = new Pet[2];
pets[0] = new Dog();//多态引用
pets[0].setNickname("小白");
pets[1] = new Cat();//多态引用
pets[1].setNickname("雪球");

OnePersonManyPets person = new OnePersonManyPets();
person.adopt(pets);
person.feed();
}
}
  1. 方法返回值类型声明为父类类型,实际返回的是子类对象
1
2
3
4
5
6
7
8
9
10
11
12
public class PetShop {
//返回值类型是父类类型,实际返回的是子类对象
public Pet sale(String type){
switch (type){
case "Dog":
return new Dog();
case "Cat":
return new Cat();
}
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestPetShop {
public static void main(String[] args) {
PetShop shop = new PetShop();

Pet dog = shop.sale("Dog");
dog.setNickname("小白");
dog.eat();

Pet cat = shop.sale("Cat");
cat.setNickname("雪球");
cat.eat();
}
}

4、向上转型与向下转型

对象的类型转换是和基本数据类型的转换是不同的。基本数据类型是把数据值copy了一份,相当于有两种数据类型的值。而对象的赋值不会产生两个对象。

因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。

但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点”小麻烦”。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过

向上转型:

​ 左父右子 ,向上转型(自动完成).

  • 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
  • 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
  • 此时,一定是安全的,而且也是自动完成的

向下转型:

​ 左子右父,向下转型: (子类类型)父类变量

  • 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
  • 但是,运行时,仍然是对象本身的类型
  • 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ClassCastTest {
public static void main(String[] args) {
//没有类型转换
Dog dog = new Dog();//dog的编译时类型和运行时类型都是Dog

//向上转型
Pet pet = new Dog();//pet的编译时类型是Pet,运行时类型是Dog
pet.setNickname("小白");
pet.eat();//可以调用父类Pet有声明的方法eat,但执行的是子类重写的eat方法体
// pet.watchHouse();//不能调用父类没有的方法watchHouse

Dog d = (Dog) pet;
System.out.println("d.nickname = " + d.getNickname());
d.eat();//可以调用eat方法
d.watchHouse();//可以调用子类扩展的方法watchHouse

Cat c = (Cat) pet;//编译通过,因为从语法检查来说,pet的编译时类型是Pet,Cat是Pet的子类,所以向下转型语法正确
//这句代码运行报错ClassCastException,因为pet变量的运行时类型是Dog,Dog和Cat之间是没有继承关系的
}
}

5、instanceof关键字

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。

1
变量/匿名对象 instanceof 数据类型 

那么,哪些instanceof判断会返回true呢?

  • 变量/匿名对象的编译时类型 与 instanceof后面数据类型是直系亲属关系才可以比较
  • 变量/匿名对象的运行时类型<= instanceof后面数据类型,才为true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestInstanceof {
public static void main(String[] args) {
Pet[] pets = new Pet[2];
pets[0] = new Dog();//多态引用
pets[0].setNickname("小白");
pets[1] = new Cat();//多态引用
pets[1].setNickname("雪球");

for (int i = 0; i < pets.length; i++) {
pets[i].eat();

if(pets[i] instanceof Dog){
Dog dog = (Dog) pets[i];
dog.watchHouse();
}else if(pets[i] instanceof Cat){
Cat cat = (Cat) pets[i];
cat.catchMouse();
}
}
}
}

6、虚方法

指在编译阶段和类加载阶段都不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。

当我们通过“对象xx.方法”的形式调用一个虚方法时,要如何确定它具体执行哪个方法呢?

(1)静态分派:先看这个对象xx的编译时类型,在这个对象的编译时类型中找到能匹配的方法

1
2
3
匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
A:找最匹配 实参的编译时类型 = 方法形参的类型
B:找兼容 实参的编译时类型 < 方法形参的类型

(2)动态绑定:再看这个对象xx的运行时类型,如果这个对象xx的运行时类重写了刚刚找到的那个匹配的方法,那么执行重写的,否则仍然执行刚才编译时类型中的那个匹配的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MyClass{
public void method(Father f) {
System.out.println("father");
}
public void method(Son s) {
System.out.println("son");
}
}
class MySub extends MyClass{
public void method(Father d) {
System.out.println("sub--father");
}
public void method(Daughter d) {
System.out.println("daughter");
}
}
class Father{

}
class Son extends Father{

}
class Daughter extends Father{

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class TestVirtualMethod {
public static void main(String[] args) {
MyClass my = new MySub();
Father f = new Father();
Son s = new Son();
Daughter d = new Daughter();
my.method(f);//sub--
/*
(1)静态分派:看my的编译时类型MyClass,在MyClass中找最匹配的
匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
A:找最匹配 实参的编译时类型 = 方法形参的类型
B:找兼容 实参的编译时类型 < 方法形参的类型
实参f的编译时类型是Father,形参(Father f) 、(Son s)
最匹配的是public void method(Father f)
(2)动态绑定:看my的运行时类型MySub,看在MySub中是否有对 public void method(Father f)进行重写
发现有重写,如果有重写,就执行重写的
public void method(Father d) {
System.out.println("sub--");
}
*/
my.method(s);//son
/*
(1)静态分派:看my的编译时类型MyClass,在MyClass中找最匹配的
匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
A:找最匹配 实参的编译时类型 = 方法形参的类型
B:找兼容 实参的编译时类型 < 方法形参的类型
实参s的编译时类型是Son,形参(Father f) 、(Son s)
最匹配的是public void method(Son s)
(2)动态绑定:看my的运行时类型MySub,看在MySub中是否有对 public void method(Son s)进行重写
发现没有重写,如果没有重写,就执行刚刚父类中找到的方法
*/
my.method(d);//sub--
/*
(1)静态分派:看my的编译时类型MyClass,在MyClass中找最匹配的
匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
A:找最匹配 实参的编译时类型 = 方法形参的类型
B:找兼容 实参的编译时类型 < 方法形参的类型
实参d的编译时类型是Daughter,形参(Father f) 、(Son s)
最匹配的是public void method(Father f)
(2)动态绑定:看my的运行时类型MySub,看在MySub中是否有对 public void method(Father f)进行重写
发现有重写,如果有重写,就执行重写的
public void method(Father d) {
System.out.println("sub--");
}
*/
}
}

7、成员变量没有多态一说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//成员变量没有多态一说
public class TestVariable {
public static void main(String[] args) {
Base b = new Sub();
System.out.println(b.a);//a = 1
System.out.println(((Sub)b).a);//a = 2

Sub s = new Sub();
System.out.println(s.a);//a = 2
System.out.println(((Base)s).a);//a = 1;
}
}
class Base{
int a = 1;
}
class Sub extends Base{
int a = 2;
}

8、什么具有多态性

只有非静态方法具有多态性,其他的都不具有.

5.9 权限访问修饰符

修饰符 作用对象
private 属性\方法
default 属性\方法\类\接口
protected 属性\方法
public 属性\方法\类\接口

即private和protected不能修饰类\接口

范围 private default protected public
同包同类
同包不同类
不同包的子类
不同包的非子类

protected主要用于继承,如果是不同包,需要是父类的子类才可以访问到protected修饰的属性或方法

5.10 实例初始化

  • 子类继承父类时,不会继承父类的构造器。只能通过super()或super(实参列表)的方式调用父类的构造器。
  • super();:子类构造器中一定会调用父类的构造器,默认调用父类的无参构造,super();可以省略。
  • super(实参列表);:如果父类没有无参构造或者有无参构造但是子类就是想要调用父类的有参构造,则必须使用super(实参列表);的语句。
  • super()和super(实参列表)都只能出现在子类构造器的首行

初始化相关代码

(1) 实例变量直接初始化

(2) 非静态代码块

(3) 构造器

5.11 关键字和API

1. this和super关键字

this: 当前方法的调用者对象

super: 引用父类声明的成员

1. this和super的使用格式
  • this
    • this.成员变量:表示当前对象的某个成员变量,而不是局部变量
    • this.成员方法:表示当前对象的某个成员方法,完全可以省略this.
    • this()或this(实参列表):调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错
  • super
    • super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的
    • super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的
    • super()或super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错
2. 避免子类和父类声明重名的成员变量

因为,子类会继承父类所有的成员变量,所以:

  • 如果重名的成员变量表示相同的意义,就无需重复声明
  • 如果重名的成员变量表示不同的意义,会引起歧义
3. 解决成员变量重名问题
  • 如果实例变量与局部变量重名,可以在实例变量前面加this.进行区别
  • 如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在子类中要访问父类声明的实例变量需要在父类实例变量前加super.,否则默认访问的是子类自己声明的实例变量(==就近原则==)
  • 如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父类中声明的实例变量,也可以用this.实例访问,也可以用super.实例变量访问
  • 变量前面没有super.和this.

    • 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的==局部变量==,
    • 如果不是局部变量,先从当前执行代码的==本类去找成员变量==
    • 如果从当前执行代码的本类中没有找到,会往上找==父类声明的成员变量==(权限修饰符允许在子类中访问的)
  • 变量前面有this.

    • 通过this找成员变量时,先从当前执行代码的==本类去找成员变量==
    • 如果从当前执行代码的本类中没有找到,会往上找==父类声明的成员变量(==权限修饰符允许在子类中访问的)
  • 变量前面super.

    • 通过super找成员变量,直接从当前执行代码的直接父类去找成员变量(权限修饰符允许在子类中访问的)
    • 如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问的)
4. 解决成员方法重写后调用问题
  • 如果子类没有重写父类的方法,只有权限修饰符运行,在子类中完全可以直接调用父类的方法;
  • 如果子类重写了父类的方法,在子类中需要通过super.才能调用父类被重写的方法,否则默认调用的子类重写的方法

2. native关键字

​ native只能修饰方法,表示这个方法的方法体代码不是用Java语言实现的,而是由C/C++语言编写的。

3. final关键字

1. final修饰类

​ 被final修饰的是终极类,不能被继承,没有子类

2. final修饰方法

​ 表示这个方法不能被子类重写

3. final修饰变量

​ final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。

5.12 Object根父类

java.lang.Object是类层次结构的根类,即所有类的父类。每个类都使用 Object 作为超类。

1. Object类的其中5个方法

  • toString()

方法签名 : public String toString();

直接使用System.out.println(对象),默认会自动调用

  • getClass()

方法签名 : public final Class<?> getClass();

  • equals()

方法签名 : public boolean equals(Object obj) : 用于判断当前对象this与指定对象obj是否“相等”

自反性: x.equals(x)返回true

传递性: x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true

一致性: 只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致

对称性: x.equals(y)与y.equals(x)结果应该一样

非空对象与null的equals一定是false

  • hashCode()

public int hashCode() : 返回每个对象的hash值

==如果重写equals,那么通常会一起重写hashCode()方法==

①如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;

②如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;

③如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false;

  • finalize()

protected void finalize():用于最终清理内存的方法

每一个对象的finalize()只会被调用一次,哪怕它多次被标记为垃圾对象。

面试题:对finalize()的理解?

  • 当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。

  • 这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码

  • 每一个对象的finalize方法只会被调用一次。

  • 子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存

5.13 对象关联

1
2
3
4
5
6
* 对象关联: 一个对象拥有另一个对象,为了让一个对象更方便的使用另一个对象
* 如何关联: 把要拥有或者关联的对象作为我的属性
* 一旦关联了对象:
* (1)全参构造
* (2)提供get/set方法
* (3)改造say方法

5.14 静态Static

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static可以修饰属性,方法,代码块,内部类

static修饰属性:
1.变量的分类(按位置分):成员变量(属性) vs 局部变量
成员变量的分类 :实例变量(不使用static修饰) vs 类变量(用static修饰)

2.同一个类的多个对象共同拥有一份类变量。每个对象各自拥有一份实例变量。
当该类的某个对象对类变量进行修改那么其它对象看到的是修改后的内容(类变量是共享的)。

3.类变量是随着类的加载而加载,类变量在方法区。
实例变量是随着对象的创建进行加载在堆中。
类加载只加载一次。类加载优先于创建对象。

4.调用类变量:对象名.类变量名。
类名.类变量名

static修饰方法:
1.static修饰方法叫作静态方法(类方法)。
2.静态方法是随着类的加载而加载的。
3.调用静态方法:静态方法(类方法)
4.非静态方法可以调用类变量,实例变量,非静态方法和类方法
静态方法中不能调用实例变量和非静态方法因为加载时机不同。
5.在静态方法中是否可以使用thissuper关键字?不可以

使用场景:
静态方法:
工具类(提供一些具体功能的常用方法的类)中的方法都是静态方法。
类变量:
常量:public static final double PI = 3.14159265358979323846;
如果多个对象拥要共享属性就需要用到类变量(比如:银行利率)。



1.子类继承父类中的类变量和静态方法了吗?继承到了
2.子类可以重写父类中的静态方法吗?不可以
3.为什么不可以重写父类的静态方法?
多态 :继承关系 方法的重写 (可以这么理解方法的重写就是为多态做铺垫)
多态:多态是动态绑定(在程序的运行的时候才知道调用的是谁的方法)
多态:父类的引用指向子类的对象 ----重点是子类的对象(静态方法不需对象)

静态方法:静态绑定-在编译的时候就决定了是哪个方法

5.15 抽象类

1
2
3
4
5
6
7
8
9
抽象类和抽象方法(abstract关键字)

abstract修饰类:抽象类
1.抽象类是不能被实例化的
2.抽象类如何使用:只能让子类继承抽象类(只要有抽象类必用多态)
3.子类一旦继承父类(抽象类)那么该子类必须实现父类中所有的抽象方法
注意:①子类也为抽象类那么不需要实现父类的抽象方法。
②如果父类(抽象类)重写了父类的父类(抽象类)那么子类(不是抽象类)可以不用实现该方法。

1
2
3
4
5
6
7
8
1.子类继承父类中的类变量和静态方法了吗?继承到了
2.子类可以重写父类中的静态方法吗?不可以
3.为什么不可以重写父类的静态方法?
多态 :继承关系 方法的重写 (可以这么理解方法的重写就是为多态做铺垫)
多态:多态是动态绑定(在程序的运行的时候才知道调用的是谁的方法)
多态:父类的引用指向子类的对象 ----重点是子类的对象(静态方法不需对象)

静态方法:静态绑定-在编译的时候就决定了是哪个方法

5.16 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
具体的类:对事物的描述
抽象的类:对同一类不同事物共性的描述
接口:不同的类不同的事物相同功能的描述

接口的格式:
权限修饰符(public和缺省的) interface 接口名{
}

说明:
1.接口和类是并列的关系。
2.接口中有
JDK1.8之前: 常量(public static final int AGE = 18)
抽象方法
JDK1.8之后: 常量,抽象方法,默认方法(default关键字修饰的方法),静态方法
3.接口中没有构造器不能创建实例。
4.怎么用接口 :
类和接口之间有的关系-实现关系(多实现):类 implements 接口1,接口2,.....
类和类的关系-继承关系而且是单继承
接口和接口的关系-继承关系而且是多继承
5.接口的多态性:接口的类型指向实现类的对象

5.17 内部类

5.18 枚举

5.19 注解

5.20 包装类

6. 枚举

7. 异常

8. 常用类

9. 集合

10. 迭代器

11. 泛型

12. IO流

Java常见问题

day01

JavaSE重点知识点列表:

  1. 变量
  2. 数据类型
  3. 循环
  4. 数组
  5. 多态
  6. 集合

day02

写出Java语言的8个特性

  1. 健壮性 强类型机制(所有数据必须要有数据类型) 异常处理 GC(垃圾回收) 标记 引用:安全的指针
  2. 安全 类加载器(ClassLoader)
  3. 简单 去掉了C/C++的复杂部分
  4. 多线程 高效并最大化利用CPU
  5. 分布式 网络中多主机协作
  6. 面向对象 关注的是有功能的对象
  7. 高效 编译型语言比解释型语言高效
  8. 跨平台 JVM有不同的版本

语句,类,方法的关系

  1. 类可以包含多个方法
  2. 方法可以包含多个语句
  3. 类是Java程序的基本单位
  4. 方法是功能单位,必须隶属于类,表达的是类和对象的功能
  5. 语句是最小执行单位,语句必须隶属于方法

什么是主类,什么是公共类,公共类注意点

主类是包含了主方法的类

公共类是Java文件中的唯一一个类,且类名与文件名相同

变量是什么

在内存中的一块固定类型,一个位置的空间,此空间中可以保存一个数据,且此空间中的数据可以随意变化.

数据类型的作用是什么

  1. 决定空间大小
  2. 决定空间中可以保存什么数据
  3. 决定数据能做什么

day03

数据类型

变量按照数据类型来分, 分为基本数据类型变量和引用数据类型变量, 请写出基本数据类型变量和引用数据类型变量的区别.

  • 基本数据类型: 空间中保存数据本身
    • 整数型 byte short int long
    • 浮点型 float double
    • 字符型 char
    • 布尔型 Boolean
  • 引用数据类型: 空间中保存其他数据的地址
    • 类 class
    • 接口 interface
    • 枚举 enum
    • 注解 @interface
    • 数组 [ ]

基本数据类型有8种, 写出8种基本数据类型

整数型 byte short int long

浮点型 float double

字符型 char

布尔型 Boolean

列出变量使用注意事项(6点), 并简单阐述你的理解

  1. 必须先声明后使用
  2. 必须初始化再使用
  3. 必须要有数据类型和变量名,数据类型的作用是3个(day02).变量名的作用是定位空间.
  4. 同一作用域内变量不能重复声明
  5. 只能用本作用域中的变量,由其声明语句所在的’{}’决定.
  6. 变量有其数据范围

按照兼容性从大到小对所有基本数据类型排序

byte->short->int->long->float->double

​ char->

day04

变量分类有两种分法, 第一种是按数据类型来分,另外一种是按照声明位置来分, 每一种又各分为哪些种类型. 各有什么特点?

  • 按数据类型

    • 整型 byte short int long

    • 浮点型 float double

    • 字符型 char

    • 布尔型 Boolean

  • 按声明位置

    • 局部变量: 声明在方法中的变量 范围小,寿命短

    • 成员变量: 声明在类中方法外的变量,范围长,寿命长

基本数据类型有8种, 写出8种基本数据类型, 并用的16进制形式写出所有整数数据类型的最小值和最大值

数据类型 占用内存空间 取值范围
byte 1字节 0x70-0x8F
char 2字节 0x0000-0xFFFF
short 2字节 0x8000-0x7FFF
int 4字节 0x8000_0000-0x7FFF_FFFF
long 8字节 0x8000_0000_0000_0000-0x7FFF_FFFF_FFFF_FFFF
float 4字节 -2^128~2^128
double 8字节 -2^1024~2^1024
Boolean 1字节 True / False

运算符%的作用是什么? 有什么实际的应用?

求余 判断是否可以整除.

  1. M % N 结果一定是小于N的. 可以让一个未知的数落入一个已知的范围N之内.

  2. M % N 结果如果为0, 表明M可以被N整除

  3. M % 2 如果结果为0, 说明M是偶数, 结果如果为非0, 就是奇数.

day05

列出变量的使用注意事项(至少6点)

1.必须先声明后使用

2.必须初始化后再使用

3.变量有其数据类型和变量名

4.变量有其作用范围

5.同一作用域内不能重复声明

6.只能用本作用域内的变量.由其声明语句所在的{}决定.

变量分类有两种分法, 第一种是按数据类型来分,另外一种是按照声明位置来分, 每一种又各分为哪些种类型. 各有什么特点?

  • 按数据类型

    • 整型 byte short int long

    • 浮点型 float double

    • 字符型 char

    • 布尔型 Boolean

  • 按声明位置

    • 局部变量: 声明在方法中的变量 范围小,寿命短

    • 成员变量: 声明在类中方法外的变量,范围长,寿命长

判断

1) if else if else if else 语句中, 如果同时有多个条件都为true, 那么将会有多个语句块被执行

if (条件1) {

​ 语句1;

} else if (条件2) {

​ 语句2;

} else if (条件3) {

​ 语句3

} else {

​ 语句4;

}

答:错误,分支结构只有第一个符合的被执行.

2) switch case case default 语句中, 如果同时有多个case都能进入, 那么将会有多个语句块被执行

答:多个case不可能出现相同的常量.

在switch结构中, switch()括号中的要求具体是什么? case后面的要求又是什么?

switch (n) { // n必须有变化, 数据类型必须是非long整数. 字符串, 枚举

​ case 1: // case 后面必须是常量, 因为它的底层是一个表格.

}

for循环的结构是什么? 执行流程是如何? 三种循环的选择依据是什么?

for(初始化语句A;循环条件B;迭代语句C){

​ 循环体D;

}

有确定的循环次数时使用for

无确定的循环次数时用while或者do while.

day06

简述break语句和continue语句的作用.

break 大事故,终止当前循环.用于switch-case语句和循环语句,用于中断case或最近的循环。

continue 小事故,跳到下一次循环.只能用于循环语句,跳过最近的当次循环, 直接进入下一次循环的迭代语句。

声明定义一个方法的语法格式是什么? 解释每部分的作用. 什么时候需要在方法中声明参数?

修饰符 返回值类型 方法名(参数列表){

​ 方法体;

​ return 返回值;

}

如何调用一个带有形参的方法? 形参和实参的作用分别是什么?

形参是构造方式时代入的参数,包含参数类型和参数名。

实参是调用方法时实际输入的参数值。

方法的返回值在哪里? 如何使用这个返回值?

方法的返回值在方法体的最后.

调用方法本身,可以得到方法的返回值

day08 数组 面向对象

数组是什么?什么类型的数据可以创建数组?

  1. 数组是一组相同数据类型的元素的集合.
  2. 任意数据类型都可以创建数组,基本数据类型和引用数据类型.

声明数组的方式?

1.动态初始化

1
2
3
4
5
6
//动态初始化1
int[] arr;
arr = new int[5];

//动态初始化2
int[] arr = new int[5];

2.静态初始化

1
2
3
4
5
6
//静态初始化1
int[] arr = {1,2,3,4,5};
//静态初始化2
int[] arr = new int[]{1,2,3,4,5};
int[] arr;
arr = new int[]{1,2,3,4,5}

什么是类?什么是对象?什么是实例?类中有哪些成员?各自的作用是什么?

类: 某种事物的抽象描述,是一组相关属性行为的集合

对象: 是一类事物的具体个体.即对象是类的一个实例, 必然具备该类事物的属性和行为.

实例: 对象

类中有哪些成员: 方法(描述事物的行为动作) 属性(描述事物的特征数据) 构造器

day09

类中有哪些成员?各有什么作用?成员意味着什么?什么是封装?如何封装?

属性: 描述事物的特征数值

方法: 描述事物的行为动作

构造器: 在创建对象时,为对象的初始化工作.有几个特殊点:

  1. 方法名和类名相同
  2. 不能有返回值声明
  3. 不能被一些关键字修饰
  4. 不能像普通方法一样随意调用

成员意味着在同一个类内可以相互随意访问

将成员用private修饰,后面用public修饰的setXXX()方法和getXxx()方法来修改和访问数据

什么是垃圾对象?垃圾对象会被立刻清理吗?如何清理垃圾?

没有被引用变量指向的对象.但是GC还在引用,所以可以辨识到垃圾对象

不一定会被立刻清理.

把垃圾对象占用的所有内存空间标记为可用状态.

描述创建对象过程

1.看方法区内是否有类模板

2.没有的话,用类加载器加载类模板

3.有的话,不加载类模板,保证类模板只有一份

4.直接使用,依据类模板中所有属性的定义信息(修饰符,数据类型,属性名),开辟一个独立的空间

5.jvm对空间数据清零,保证属性有缺省值0.

6.对属性显示赋值

7.执行构造方法

tips:每次创建对象有3次初始化,第一次是jvm的刷0,第二次是对属性显式赋值,第三次是执行构造方法.

封装的两个含义

  1. 成员私有化,保护成员
  2. 功能性封装:1.该我做的义不容辞 2.不该做的绝不多管闲事. —–边界清晰

类模板

​ 包含了所有属性的定义信息\修改符\数据类型\属性名.

1
2
3
4
public String say(){
//这里的this是指当前方法的调用者对象.
return "x: " + this.x + ",y: " + this.y;
}

JavaBean

  • 类是公共的
  • 有一个无参的公共的构造器
  • 有属性,且有对应的get\set方法

day12

1.什么是继承?为什么要继承?如何继承?

现有类创建子类,现有类又称为父类,基类,超类.

父类不能满足需要,需要子类在父类的基础上进行拓展.

通过extends关键字,声明一个子类继承另外一个父类

一句话: 子类是完全且更强大的父类

2.子类能继承父类的的私有成员吗?如何处理?

可以.提供get/set方法间接调用父类的私有成员.

3.为什么父类又叫基类或超类?

子类拥有父类的所有成员(构造器除外),并且强于基类.英文名叫superclass,所以叫超类.

4.什么是方法覆盖?方法覆盖有什么条件?

在子类中重写在父类中继承过来的方法.

条件:

①子类的访问权限控制符要大于等于父类

②子类方法签名要与父类一致(返回值类型 方法名 参数列表)

5.如果A类被B类继承,B类又被C类继承,在所有类中都有一个方法test(),创建C类对象,调用test()方法,执行的是哪个类的方法?在C类种有几个test()方法?

执行C类方法

测试类视角: C类中只有一个test()方法

如果在C类的内部: C类中2个test()方法 this.test() / super.test()

继承的概念来看: C类中有3个test()方法

day13

static关键字可以修饰什么

static可以修饰属性,方法,代码块,内部类

static修饰属性的特点

static修饰属性:

  • 类变量 : 所有的对象共同拥有一份
  • 实例变量(非静态) : 每个对象独自拥有一份

static修饰方法的特点

static修饰方法:

  • 静态方法 : 可以直接通过类名进行调用–不需要对象
    • 静态方法中为什么不能调用this和super? —>加载时机不同,静态方法通过类名调用
  • 非静态方法 : 通过对象调用(方法中的this不一样)

类变量\实例变量

类变量 : 所有的对象共同拥有一份

实例变量(非静态) : 每个对象独自拥有一份

day14

被static修饰的成员有什么特点?

static修饰属性,方法,代码块,内部类

共同点

  • 在类加载(只加载一次)时进行加载

不同点

  • static修饰属性
    • 类变量 : 所有的对象共同拥有一份
    • 实例变量(非静态) : 每个对象独自拥有一份
  • static修饰方法
    • 静态方法 : 可以直接通过类名直接调用–不需要对象
      • 静态方法中为什么不可以调用this和super? 加载时机不同,静态方法通过类名调用
    • 非静态方法 : 通过对象调用(方法中的this不一样)

被static修饰的成员在内存空间只有一份

什么成员应该声明成静态的?什么成员应该设置成非静态的?

类的成员: 属性,方法,构造器,代码块,内部类.

类变量设置成静态,实例变量设置成非静态

什么时候声明成类变量 : 同一个类的所有对象共享的时候

什么时候声明成静态方法 : 如果要做成工具类中的方法(可以类名直接调用-常用)

单例设计模式用来解决什么问题?如何实现单例设计模式?

设计模式: 用来解决问题的方案

整个系统中某个类只存在一个对象. 有懒加载 和 饿汉式.

给构造器的权限修饰符改为private

如何给main方法传参

1.在idea中是点edit—>program arguments—>参数1 参数2 参数3….

2.命令: java 字节码文件 参数1 参数2 ……

定义一个工具类EmploeeUtil

  • 提供方法中创建一个数组用来存放三个员工(Employee)的信息(id,name,salary)
  • 提供方法输入员工id可以输出该员工年薪annualSalary没有就输出查无此人
  • 提供方法输入员工id可以输出数组中员工信息没有就输出查无此人

day15

什么是多态?什么是虚方法调用?

多态

  • 从右向左: 子类对象的多种父类类型的形态
  • 从左向右: 父类类型的引用变量可以指向多种不同子类对象

虚方法调用

  • 多态引用调用重写的方法,编译时看重父类类型,运行时动态绑定具体子类的方法.

描述equals方法和hashcode方法的含义和作用

equals 比较2个对象的内容是否相同

hashcode 2个对象的内容相同hashcode相同

什么成员应该声明成静态的,什么成员应该声明为非静态?

共享的数据声明为类属性(静态),方法和调用者对象无关时,定义为静态方法.

静态环境中可以直接访问非静态成员吗?非静态环境中可以访问静态成员吗?各自的原因是什么?

①不能,静态环境中没有this,不能直接访问非静态成员.可以通过现new一个对象再通过对象来访问

1
new person().say();

②能

判断:

抽象类中必须包含抽象方法 False,可以不包含

抽象类中不能包含普通方法 False,抽象类可以包含具体方法

抽象类中可以包含抽象方法 True

抽象类不能创建对象,所以可以省略构造器 False,子类要继承父类,构造器虽然不继承,但是必须调用,所以不能

具体类最多允许包含1个抽象方法 False

抽象类主要用于被具体子类继承,具体子类可以不必理会抽象父类中的抽象方法 F

具体类中如果包含抽象方法,编译出错 T

一个类中如果包含抽象方法,这个类必须是抽象类 T

抽象类中必须包含属性,构造器和普通方法和抽象方法. F

抽象类中可以不包含抽象方法,具体子类必须实现全部的父类的抽象方法. T

代码块

静态代码块(类初始化器)

非静态代码块

变量 :

​ 内存中的一块被命名的且有特定数据类型约束的空间, 此空间中可以保存一个数据类型约定的数据, 并且此空间中的数据可以在其数据类型的范围内随意变化.

​ 变量声明 :

​ 数据类型 变量名;

​ 数据类型的作用 :

​ 1) 决定空间的大小

​ 2) 决定了空间中的保存什么数据

​ 3) 决定了空间中的数据可以做什么.

1 变量根据类型来分 :

​ 1) 基本数据类型(primitive) : 空间中保存数据本身

​ 数值型

​ 整数

​ byte, short, int ,long, char(没有负数)

​ 浮点数

​ float , double

​ 布尔型

​ boolean

​ 2) 引用数据类型(reference) : 空间中保存对象地址(首字节编号), 永远占用8字节.

​ 数组

​ 类, Student s;

​ 接口 , Flyer f; 100%多态引用

​ 枚举

​ 注解

  1. 变量根据 声明语句的位置来分 :

    1. 局部变量(local) : 在方法中声明的变量, 包括 参数和方法中的普通变量

      ​ 范围小, 寿命短, 存在于方法调用时的栈中, 没有自动初始化, 必须由程序员手工初始化.

    2. 成员变量(member) : 在类中方法外声明的变量.

      ​ 范围大, 寿命长, 都有自动初始化值0

      ​ static修饰的 : 静态变量, 静态属性, 类属性, 类变量

      ​ 寿命最长, 和类模板同周期, 存在于方法区中的类模板中

      ​ 没有static修饰的 : 非静态变量, 对象属性, 实例变量.

      ​ 寿命和对象同周期, 存在 于GC区中的对象中

day16

内部类

对象关联

普通对象关联

内部类对象关联

day17

描述什么是代理设计模式,在什么样的场景中使用

代理设计模式就是把代理对象当作被代理对象来使用.

使用场景:

​ ①类不能直接访问

​ ②需要在原有的基础上统一实现功能升级

完成代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface IHello{

}

class Test{
main(){
IHello ih = new IHello() {
@Override
public String transfer(int n){
return "" + n;
}
};

String s = ih.transfer(3);
sout(s); // "3"
}
}

异常按照处理方式分为几种,各包含哪些类?

受检异常 : 必须接收检查和处理的异常,如果不处理,编译报错,也称为编译时异常

​ Throwable

​ Exception及其子类.(RuntimeException除外)

非受检异常 : 不是必须接受检查和处理的异常,如果不处理,编译不出错,但是运行时还会出问题,也称为运行时异常(RuntimeException)

​ Error : 太过严重

​ RuntimeException : 太过常见和轻微

判断

  • 非受检异常就是必须不要处理的异常
  • 受检异常就是可以处理的异常
  • 非受检异常是不必须处理的异常
  • 受检异常可以对其忽略
  • 无论是什么异常,都必须对其进行处理
  • 只有受检异常会引起程序中断
  • 受检异常是必须对其进行处理的异常
  • 只有非受检异常会引起程序中断
  • 异常处理只适用于受检异常
  • 异常处理适用于所有异常,都是error

异常的处理有几种方式,各是什么?怎么处理

  • try - catch - finally
1
2
3
4
5
6
7
8
9
10
11
12
13
try{
可能出现异常的语句;
}catch(异常类型1 引用){
输出异常语句;
}catch(异常类型2 引用){
输出异常语句;
}catch(Exception e){
输出异常语句;
}finally{
无论前面try catch中发生什么都要执行,通常执行最重要的语句,释放资源;
}

被保护的语句;
  • throws : 抛出

方法声明中加入throws 异常类型列表,警告和提醒此方法的调用者,本方法的风险列表.

使用中有throws没有throw的抛出动作也是合法的,但是最好不要这样.

在方法中如果有throw而且后面有受检异常必须加throws,如果后面是非受检异常,则可以不加throws.

day18

day19

包装类的作用是什么? 包装类有哪些? 如何使用包装类把字符串转换成相应的基本数据类型?

包装类的作用: 把基本数据类型转换成对象

包装类有哪些? Byte Short Character Long Integer Float Double Boolean

使用包装类把字符串转换成相应的基本数据类型: Xxx.parseXxx(xxx);

什么是装箱, 什么是拆箱? 如何使用?

装箱 : 把基本数据类型转换成对象

​ 手动装箱 : Xxx i = new Xxx(xxx);

​ 自动装箱 : Xxx i = xxx;

拆箱 : 把对象转换成基本数据类型

​ 手动拆箱 : xxx i = Xxx.value(Xxx);

​ 自动拆箱 : xxx i = Xxx;

String和StringBuilder的区别是什么? StringBuilder和StringBuffer的区别又是什么?

String和StringBuilder的区别 :

String : 内容不可改变,如果改变需要创建新对象.

StringBuilder : 内容可以改变,如果改变不需要创建新对象.线程不安全但是效率高

StringBuffer : 内容可变,如果改变不需要创建新对象,线程安全但是效率低

问答:

String类中的方法 :

1.length() 作用是什么?

返回字符串长度

2.获取指定的下标处的字符的方法是哪个?

public char charAt(int index);

3.indexOf(String child, int start)的作用是什么, 其中参数是什么含义?

获取指定字符串的起始下标,start为开始索引下标(从左向右)

4.获取字符串的子串的方法是哪些? 分别描述参数的含义.

public String substring(int i,int j) : i开始下标,j为结束下标

5.startsWith(String str)和endsWith(String str)的作用是什么?

判断字符串是否以子串开始/结束.

6.比较字符串对象并忽略大小写的方法是?

public boolean equalsIgnoreCase(String s1);

public int compareToIgnoreCase(String s2);

7.trim()方法的作用是什么?

消除字符串首尾空白字符(码值小于等于32)

8.把字符串中的所有大写字母变小写, 小写字母变大写的方法是什么?

toUpperCase—>变大写,toLowerCase()—>变小写

9.replace(char ch1, char ch2)作用是什么? 和replaceAll有什么区别?

replace的作用是把全部ch1替换成ch2.

replaceAll支持正则表达式

10.把字符串以某个子串为切割器切割的方法是什么?

public String[] split(String str);

11.获取字符串对应的字符数组方法是什么?

public char[] toCharArray( ); //返回内部数组value的副本

StringBuilder类中的方法:

1.在字符串后面追加内容的方法是? 可以是什么类型的参数?

append(…) 可以是任意参数

2.在字符串的指定位置处插入新内容的方法是? 可以是什么类型的参数?

insert(int index,…) 可以是任意参数

如何把一个时间显示成 2017年05月19日 08时50分?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//用Calendar做
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2017);
cal.set(Calendar.MONTH, 4);
cal.set(Calendar.DAY_OF_MONTH, 19);
....
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分");
String str = sdf.farmat(cal.getTime());
sout(str);

//用LocalDateTime做
LocalDateTime ldt = LocalDateTime.now();
LocalDateTime ldt2 = ldt.withYear(2017).withMonth(5).withDayOfMonth(19).withHour(8).withMinitue(50);
DateTimeFormatter dtf = DateTimeFormatter.ofPatter("yyyy年MM月dd日 HH时mm分");
String str2 = dft.format(ldt2);