JAVA学习

JAVA

IDE

对于IntelliJ IDEA,首先需要创建项目,然后添加包,最后创建类,然后开始写函数

IDEA快捷键

  • ALT+D删除所在行
  • ALT+Enter自动添加包
  • ctrl+alt+L快速格式化代码
  • ctrl+alt+向下箭头快速复制粘贴当前行
  • shift+F10快速運行程序
  • alt+insert 快速生成构造器
  • ctrl+H查看类的继承关系
  • 将光标放在一个方法后,Ctrl+B可以定位到定义方法处
  • 在最后添加.var可以快速分配变量名new Scanner(System.in).var然后回车可以自动添加变量名

IDEA自定义模板

在file->settings->editor->Live template可以查看和增加模板

在java模板中,输入main之后回车就会自动补全,sout是输出的模板,fori是for循环的模板

自己也可以添加模板

记得添加应用场景,我们这里设置为JAVA

JAVA概述

JAVA"白皮书"的关键术语

  • 简单性
  • 面向对象,也就是重点在于数据即对象和对象的接口上
  • 分布式:JAVA有一个丰富的例程库,用于处理像HTTP和FTP之类的TCP/IP协议
  • 健壮性:JAVA采用的指针模型可以消除重写内存和损坏数据可能性
  • 安全性
  • 体系结构中立:编译过的代码可以很容易在任何机器上运行
  • 可移植性:JAVA的数据类型具有固定的大小,二进制数据以固定的格式进行存储和传输,字符串是用标准的Unicode格式存储的
  • 解释型:JAVA解释器可以在任何移植了解释器的机器上执行JAVA字节码
  • 高性能:字节码可以(在运行时刻)动态地翻译成对应运行这个应用的特定CPU的机器码
  • 多线程:多线程可以带来更好的交互响应和实时行为
  • 动态性:JAVA可以适应不断发展的环境

Java运行机制及运行过程

JRE和JDK

JAVA的基本程序设计结构

一个简单的JAVA应用程序

1
2
3
4
5
6
7
package com.Helloworld;

public class Helloworld {
public static void main(String[] args) {
System.out.println("Helloworld!");
}
}

其实class关键字表示类,class后跟类名(必须以字母开头,后面可以是字母和数字的任意组合,但不能是关键字),表明JAVA程序中的全部内容都包含在类中,public表示是公共的,用于控制程序的其他部分对这段代码的访问级别,main方法必须声明为public

源代码的文件名必须与公共类的名字相同,并用.java作为扩展名

与C中不同的是,Java中的所有函数都属于某个类的方法,而在C中被叫做成员函数

System.out.println(“Helloworld!”)这一句使用了System.out对象并调用了它的println方法,相当于函数调用

使用命令行编译和运行java文件

当我们编写好.java文件(源文件)之后,使用javac test.java命令编译为test.class文件(字节码文件),然后通过java test运行。有了java源文件,通过编译器将其编译成JVM可以识别的字节码文件,在该源文件目录下,通过javac编译工具对java文件进行编译。而运行就是将字节码文件装载到jvm执行

这里使用java test运行是因为java表示运行一个java的类,也就是类似自动补充为test.class

还需要注意一点:由于命令行默认为GBK编码,所以我们需要将java文件改为GBK编码格式

java开发注意事项

  • Java应用程序的执行入口是main()方法,其格式如下:public static main(String[] args){……}

  • 一个源文件最多只能有一个public类,其他类的个数不限,编译后每一个类都会生成一个class文件

  • 如果源文件包含一个public类,则其文件名必须按该类名命名

  • 一个源文件最多只能有一个public类,其他类的个数不限,也可以将main方法写入非public类中,然后指定运行非public类,这样入口方法就是非public类的main方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class test{
    public static void main(String[] args){
    System.out.println("helloworld!");
    }
    }

    class Dog{
    public static void main(String[] args){
    System.out.println("hello,Dog!");
    }
    }
    class Tiger{
    public static void main(String[] args){
    System.out.println("hello,Tiger!");
    }
    }

文档注释

对于不同的需求,文档注释也可以选择不同的符号

Java文档注释

数据类型

在Java中,一共有8种基本类型,其中4种整型、2种浮点类型、1种用于表示Unicode编码的字符单元的字符类型char和1种用于表示真值的boolean类型

整型

整型

加上前缀0b或者0B就可以写二进制数,如0b1001就是9

还可以为数字字面量加下划线,如1_000_000表示一百万,Java编译器会去除这些下划线

对于long类型的数据,需要在结尾加’L’或者’l’,如果将long类型的值赋值给int型变量,可能会有损失,需要使用强制转换

浮点类型

浮点类型

float类型的数值有一个后缀F或者f(例如3.14F),没有后缀F的浮点数值默认为Double类型,也可以在浮点数值后添加后缀D或d

浮点数在机器中存放形式:浮点数=符号位+指数位+尾数位,尾数可能会有损失,造成精度丢失

对于小数点前,如果都是0,可以省略,也就是说0.123可以省略为.123

科学计数法:5.12e2就是5.12×10^2

打印的结果如下,这里会默认为double类型数据,所以会在小数点后添加0

通常情况下我们使用double,因为其精度更高

由于是近似计算,所以对于运算结果是小数进行相等判断时,需要小心,应该是以两个数的差值的绝对值,在某个精度范围内判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class test{
public static void main(String[] args){
// float num1 = 1.1F;
// double num2 = .123;
//System.out.println(num2);
// System.out.println(5.12e2);
// System.out.println(5.12e-2);
double num1 = 2.7;
double num2 = 8.1 / 3;
System.out.println(num1);
System.out.println(num2);
if(num1 == num2){
System.out.println("相等");
}
if(Math.abs(num1 - num2) < 0.01){
System.out.println("差值接近,近似相等");
}
}
}

输出

Java API文档

Java8 API 中文版

java类的组织形式

char类型

char类型的值可以表示为十六进制值,其范围从\u0000到\uffff

转义符

Unicode和char类型

由于不同地区的编码机制不同,所以需要做出统一,这也促进了Unicode的出现,Unicode使用后两个字节来表示字符

字符编码表

boolean类型

boolean类型有两个值:false和true

c++和Java种boolean的区别

变量

Java中每个变量都需要一个类型

声明变量的提示

变量的初始化和C++类似,就不赘述了

常量

在Java中,利用关键字final指示常量(表示这个变量只能被赋值一次,被赋值之后就不能再改了)

1
final double PI=3.14;

在Java中,经常希望某个常量可以在一个类中的多个方法中被使用,通常将这些常量称为类常量

可以使用关键字static final设置一个类常量

类常量设置

此处使用public static final关键字来声明,那么在其他类的方法也可以使用

运算符

加减乘除和C++一样

但是对于浮点数的精度问题是我们需要解决的,因为不同位数机器计算得到的精度是不同的,所以对于使用strictfp关键字标记的方法必须使用严格的浮点计算来生成可再生的结果

数学函数与常量

在Math类中,包含了各种各样的数学寒湖是

导入Math包就不要在数学方法名和常量名前添加前缀"Math"

数值类型之间的转换

精度小的类型自动转换为精度大的数据类型,这个就是自动类型转换

byte、char、short之间可以进行运算,在计算时首先转换为int类型。不同类型数据进行运算时会转为精度最高的类型

强制类型转换

和C++一样,可能造成精度降低或溢出,但是如果想对浮点数进行舍入操作以便得到最接近的整数,可以使用Math.round方法

boolean的强制转换

char类型可以保存int的常量值,但不能保存int的变量值

基本数据类型和String类型的转换

基本数据类型转String类型

将基本数据类型的值+""即可

1
2
3
4
5
6
7
8
9
10
11
12
13
public class stringtobasic{
public static void main(String[] args){
int n1=100;
float f1=1.1F;
double d1=4.5;
boolean b1=true;
String s1=n1+"";
String s2=f1+"";
String s3=d1+"";
String s4=b1+"";
System.out.println(s1+" "+s2+" "+s3+" "+s4+" ");
}
}

String类型转基本数据类型

使用基本数据类型对应的包装类的相应方法,得到基本数据类型

1
2
3
4
5
6
7
8
9
public class stringtobasic{
public static void main(String[] args){
String s5 = "1213";
int num1=Integer.parseInt(s5);
double num2=Double.parseDouble(s5);
System.out.println(num1);
System.out.println(num2);
}
}

但是对于char类型,无法转换,只能取出字符串对应位置的字符,下面代码就是取出字符串中下标为0的字符

1
System.out.println(s5.charAt(0));

需要注意的事项

但是当字符串由字符组成时,对其进行转换就会报错

结合赋值和运算符

自增与自减运算符

关系和boolean运算符

关系运算符和C++相同,同时Java也支持三元运算符

位运算符

对于短路&&,第一个条件为假则不往后判断,而逻辑与&,即使第一个条件为假,第二个条件也会判断

除了按位与、或、非,左移右移,Java还有

括号与运算符级别

运算符有级别表

枚举类型

字符串

Java字符串就是Unicode字符序列,在标准Java类库中提供了一个预定义类,很自然地叫做String,每个用双引号括起来地字符串都是String类的一个实例

子串

String类中的substring方法可以从一个较大的字符串中提取处一个子串

1
2
String greeting="hello";
String s=greeting.substring(0,3);

表示截取greeting对象的前三个字符存储到s中

拼接

Java语言运行使用+号拼接两个字符串

1
2
3
String a="abcdefg";
String b="hijklmn";
string c=a+b;

上述代码将"abcdefghijklmn"赋值给c。

当将一个字符串与一个非字符串的值进行拼接时,后者被转换成字符串

例子

这种特性通常用在输出语句中,比如

1
System.out.println("The answer is "+answer);

如果需要把多个字符串放在一起,用一个定界符分割,可以使用静态join方法

字符串比较

使用equals方法进行字符串比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Scanner;
public class whiletest{
public static void main(String[] args){
Scanner myScanner = new Scanner(System.in);
String name = "";
String passwd = "";
int chance = 3;
for(int i=0; i<3;++i){
System.out.println("请输入您的用户名:");
name = myScanner.next();
System.out.println("请输入您的密码:");
passwd = myScanner.next();
if("丁真".equals(name) && "666".equals(passwd)){
System.out.println("登陆成功");
break;
}
else{
chance--;
System.out.println("你还有"+chance+"次登陆机会");
}
}
}
}

键盘输入语句

首先导入包中的类,然后创建一个对象,接着接收用户输入——不同的数据类型对应了不同的方法

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Scanner;//表示把java.util包中的Scanner类导入
public class inputtest{
public static void main(String[] args){
//new创建一个对象,创建Scanner对象
Scanner myScanner = new Scanner(System.in);
//接收用户输入,使用相关的方法
System.out.println("请输入你的名字:");
String name = myScanner.next();//接收用户输入
System.out.println("请输入你的年龄:");
int age=myScanner.nextInt();//接收用户输入,类型为int
}
}

程序控制结构

分支控制——单分支、双分支、多分支

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

public class controltest{
public static void main(String[] args){
System.out.println("请输入年龄:");
Scanner myScanner = new Scanner(System.in);
int age = myScanner.nextInt();
if(age > 18){
System.out.println("您的年龄大于18");
} else if(age == 18) {
System.out.println("您的年龄等于18");
} else {
System.out.println("您的年龄小于18");
}
}
}

在分支中还可以嵌套分支结构

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

public class controltest{
public static void main(String[] args){
Scanner myScanner = new Scanner(System.in);
System.out.println("请输入成绩:");
double score = myScanner.nextDouble();
if( score > 8.0 ){
System.out.println("请输入性别:");
char gender = myScanner.next().charAt(0);//由于接收的是字符串,需要转为字符
if( gender == '男'){
System.out.println("你是男的");
} else {
System.out.println("你是女的");
}
} else {
System.out.println("您出局了");
}
}
}

switch分支结构

每个case中的类型应该一致,也需要和switch中的类型(只能是byte,short,int,char,String,enum)一致

switch流程图

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
import java.util.Scanner;

public class switchtest{
public static void main(String[] args){
Scanner myScanner = new Scanner(System.in);
System.out.println("请输入a-g中的字符");
char input = myScanner.next().charAt(0);
switch(input){
case 'a':
System.out.println("星期一");
break;
case 'b':
System.out.println("星期二");
break;
case 'c':
System.out.println("星期三");
break;
case 'd':
System.out.println("星期四");
break;
case 'e':
System.out.println("星期五");
break;
case 'f':
System.out.println("星期六");
break;
case 'g':
System.out.println("星期天");
break;
default:
System.out.println("输入错误");
break;
}
}
}

循环分支结构

for循环

1
2
3
4
5
6
7
public class fortest{
public static void main(String[] args){
for(int i=0;i<10;++i){
System.out.println("helloworld");
}
}
}

while循环

1
2
3
4
5
6
7
8
9
public class whiletest{
public static void main(String[] args){
int n=0;
while(n<10){
System.out.println("hello");
n++;
}
}
}

do……while循环

也是和C一样的

打印空心金字塔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Scanner;
public class whiletest{
public static void main(String[] args){
for(int i=0;i<5;++i){
for(int k=0;k<4-i;++k){
System.out.print(' ');
}
for(int j=0;j<2*i+1;++j){
if(j==0||j==2*i||i==4){
System.out.print("*");
} else {
System.out.print(" ");
}
}
System.out.println();
}
}
}

数组、排序和查找

数组

数组创建后,如果没有赋值,有默认值

1
2
3
4
5
6
7
8
9
class arraytest{
public static void main(String[] args){
int a[] = new int[3];//声明数组并开辟空间,也可以先声明再开辟空间,这是动态初始化
int b[]={1,2,3};//这是静态初始化
for(int i=0;i<a.length;++i){
System.out.println(a[i]);
}
}
}

数组扩容

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
class arraytest{
public static void main(String[] args){
int arr[] = {1,2,3};
Scanner myScanner = new Scanner(System.in);
do{
System.out.println("您是否需要继续添加元素(Y/N)");
char a = myScanner.next().charAt(0);
if( a == 'Y' || a == 'y'){
System.out.println("请输入你想添加的元素");
int add = myScanner.nextInt();
int arrNew[]= new int[arr.length+1];
for(int i=0;i<arr.length;i++){
arrNew[i] = arr[i];
}
arrNew[arrNew.length-1] = add;
arr = arrNew;
System.out.println("添加元素后的数组为");
for(int i=0;i<arr.length;++i){
System.out.println(arr[i]);
}
}
else{
System.out.println("退出成功");
break;
}
}while(true);
}
}

二维数组的静态初始化,和C不同的是需要加{}来限制为一维数组,否则会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class arraytest{
public static void main(String[] args){
int arr[][] = {
{1,2,3,4,5},
{1,2,3,4,5},
{2,3,4,5,6},
{4,5,6,7,8}
};
for(int i=0;i<arr.length;i++){
for(int j=0;j<arr[i].length;++j){
System.out.print(arr[i][j]+" ");
}
System.out.println("");
}
}
}

二维数组的动态初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class arraytest{
public static void main(String[] args){
int arr[][]=new int[3][];//创建二维数组,但是只确定一维数组的个数,但是每个一维数组还没开辟空间
for(int i=0;i<arr.length;++i){
arr[i]=new int[i+1];//给每个一维数组开辟空间
for(int j=0;j<arr[i].length;++j){
arr[i][j]=i+1;
}
}
for(int i=0;i<arr.length;++i){
for(int j=0;j<arr[i].length;++j){
System.out.print(arr[i][j]+" ");
}
System.out.println();
}
}
}

杨辉三角

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class arraytest{
public static void main(String[] args){
int arr[][]=new int[10][];//创建二维数组,但是只确定一维数组的个数
for(int i=0;i<arr.length;++i){
arr[i]=new int[i+1];//给每个一维数组开辟空间
}
for(int i=0;i<arr.length;++i){
for(int j=0;j<arr[i].length;++j){
if(j==0||j==i){
arr[i][j]=1;
} else {
arr[i][j] = arr[i-1][j]+arr[i-1][j-1];
}
System.out.print(arr[i][j]+" ");
}
System.out.println();
}

}
}

排序

冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Sorttest{
public static void main(String[] args) {
int arr[] = {4,3,6,7,9,5};
for(int i=0; i < arr.length-1; i++){
for(int j = 0; j < arr.length-i-1; j++){
if(arr[j]>arr[j+1]){
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}

查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Insert{
public static void main(String[] args) {
int arr[] = {10,12,30,45,90};
int index=-1;
for(int i=0;i<arr.length;++i){
if(arr[i]==30){
index=i;
break;
}
}
if(index==-1){
System.out.println("未找到");
} else {
System.out.println("其所在下标为"+index);
}
}
}

面向对象编程

类与对象

引入类与对象的原因是同一个对象可能有多种属性和行为,类就是数据类型(我们自己定义的),而对象就是一个具体的实例

  1. 类是抽象的,概念的。代表一类事物
  2. 对象是具体的,实际的,代表一个具体事物,即实例
  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
26
27
28
class Object01{
public static void main(String[] args) {
//创建一个猫对象,然后赋值给cat1
Cat cat1 = new Cat();//直接创建对象,也可以先声明对象再创建
cat1.name ="小白";
cat1.age = 3;
cat1.color="白色";

//创建第二只猫,new是在堆中开辟空间,而new的返回值是开辟空间的地址,也就是对象,而cat2是对象名
Cat cat2 = new Cat();
cat2.name ="小花";
cat2.age = 10;
cat2.color="花色";

//访问对象属性
System.out.println("第一只猫的信息"+" "+cat1.name+" "+cat1.age+" "+cat1.color);
System.out.println("第二只猫的信息"+" "+cat2.name+" "+cat2.age+" "+cat2.color);
}
}

//使用面向对象的方式来解决养猫问题
//定义一个猫类Cat
class Cat{
//Cat类的属性
String name;
int age;
String color;
}

对象在内存中的存储

在执行创建类过程中,会加载类的属性信息和方法信息。对于属性信息,根据类型不一样分配不同的空间,字符串存储指向常量池的地址,基本数据类型则直接存储

属性和细节

属性也可以称为成员变量或者filed(字段),属性可以是对象也可以是基本数据类型

属性的定义语法同变量,示例:访问修饰符(public/private/protected/默认)-用于控制属性的访问范围,属性类型,属性名

类与对象的内存分配机制

new是在堆中开辟空间,而new的返回值是开辟空间的地址,也就是对象,而cat2是对象名,所以我们可以直接将cat2赋值给新的对象名(相当于地址拷贝),如

1
Cat cat3 = cat2; 

Java内存的结构分析

  • 栈:一般存放基本数据类型
  • 堆:存放对象(Cat cat,数组等)
  • 方法区:常量池(常量,比如字符串),类加载信息

Java创建对象的简单分析

  1. 先加载类信息(属性和方法信息,只会加载一次)
  2. 在堆中分配空间new(),进行默认初始化
  3. 把地址赋值给对象名,p就指向对象
  4. 进行指定初始化(属性赋值)

成员方法

相当于定义在类中的函数

基本定义和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Method01{
public static void main(String[] args) {
//方法的使用,先创建一个对象,然后进行调用
Person person1 = new Person();
person1.speak();
}
}

class Person{
String name;
int age;
//public是方法的属性,()表示无参,void表示无返回值,{}是方法体,可以写入需要执行的代码
public void speak(){
System.out.println("我是一个好人");
}
}

方法的调用机制

1
2
3
4
5
6
7
8
Person p1 = new Person();
int returnRes = p1.GetSum(10,20);

//在类中的方法
public int Getsum(int num1,int num2){
int res = num1 + num2;
return res;
}

new一个对象在堆中开辟空间

调用Getsum方法后开辟新栈,将参数通过栈传递,进行操作后返回到调用方法的位置

成员方法的好处

对于需要多次进行的操作,方法能够减少代码的冗杂度

方法使用细节

  1. 如果想让方法返回多个结果,可以使用数组存储返回值,也就是说返回值可以是基本数据类型,也可以是引用数据类型
  2. 实参和形参的类型必须相同或兼容、个数、顺序需要一致
  3. 方法体(方法内部)中不能再定义其他方法,也就是说方法不能嵌套定义
  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
class Method01{
public static void main(String[] args) {
Person2 person2 = new Person2();
int n=0;
person2.m();
}
}

class Person{
String name;
int age;
public void speak(){
System.out.println("speak");
}
public void cal01(int n){
speak();
System.out.println("cal01");
}
}

class Person2{
public void m(){
Person person1 = new Person();//需要在方法中创建Person类的对象
person1.cal01(0);
System.out.println("m类调用");
}
}

方法传参机制

  • 当传递的是基本数据类型时,传递的只是值,传递参数的地址的值不变,也就是值拷贝、值传递,此时形参的变化不会影响实参
  • 而引用类型传递的是地址的值,可以在方法中对实参的值进行修改,这就是引用传递

方法递归调用

即在方法体中调用自己,注意使用递归时要有递归出口,并且需要向推出递归的条件逼近

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
//递归打印
import java.util.Scanner;

public class Javatest{
public static void main(String[] args) {

}
}


class Method01{
public static void main(String[] args) {
test test1 = new test();
test1.Recursion01(10);
}
}

class test{
public void Recursion01(int n){
if(n>2){
Recursion01(n-1);
}
System.out.println(n);
}
}

使用递归解决迷宫问题

map

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
class Map{
public static void main(String[] args) {
//初始化迷宫,1表示设置了障碍物,0表示未设置
int map[][] = new int[8][7];
for(int i=0;i<7;++i){
map[0][i]=1;
map[7][i]=1;
}
for(int i=0;i<8;++i){
map[i][0]=1;
map[i][6]=1;
}
map[3][2]=1;
map[3][1]=1;
for(int i=0;i<8;++i){
for(int j=0;j<7;++j){
System.out.print(map[i][j]+" ");
}
System.out.println();
}
test test1 = new test();
boolean final1= test1.Findway(map,1,1);
System.out.println("\n=======找路的情况如下=======");
for(int i=0;i<8;++i){
for(int j=0;j<7;++j){
System.out.print(map[i][j]+" ");
}
System.out.println();
}
}
}
//使用递归回溯来解决迷宫问题
class test{//i、j表示老鼠所在的位置
//0表示可以走,1表示障碍物,2表示可以走,3表示走过,但是走不通是死路,当map[6][5]==2时,表示找到迷宫出口
//先确定老鼠找路的策略,因为有四个方向可以选择
public boolean Findway(int map[][],int i,int j){
if(map[6][5]==2){
return true;
} else {
if(map[i][j]==0){
map[i][j]=2;//标记可以走并且已经走过
if(Findway(map,i+1,j)){
return true;//尝试往下走
} else if(Findway(map,i,j+1)){//右
return true;
} else if(Findway(map,i-1,j)){//上
return true;
} else if(Findway(map,i,j-1)){//左
return true;
} else {
map[i][j]=3;//由于前面所有方向所返回的都是false,表示走不通,将该点标记为3,然后返回false
return false;//结束本次递归,也就是回溯到上一个位置
}
} else {//此时map[i][j]的值只能是1,2,3,1、3都表示不能走了,而2表示可以走,但是已经走过了,也可以直接结束本次递归,因为每次递归都是用于判断某个位置能不能走
return false;
}
}
}
}

回溯

使用上面的迷宫寻路问题来理解回溯,假设在该位置添加一个障碍物

在(1,1)位置往下走到(2,1),此时(2,1)左右下都为1,走不了,而上为2表示已经走过了,不需要再走,所以将(2,1)标为3,return false表示判断完毕,鉴定为不能走,然后回溯到(1,1)位置,因为在(1,1)位置只尝试了向下走,还未尝试其他方向

方法重载

java中允许同一个类中,多个同名方法的存在,但要求形参列表不一致(形参类型或者个数)返回类型不同不构成方法的重载,比如System.out.println(),out是PirntStream类型

重载的好处:

  • 减轻了起名的麻烦
  • 减轻了记名的麻烦

可变参数

Java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Overload1{
public static void main(String[] args) {
HspMethod m = new HspMethod();
System.out.println(m.sum(1,5,100));
System.out.println(m.sum(1,3));
}
}

class HspMethod{
public int sum(int... nums){
//int... 表示接收的是可变参数,类型是int,即可以接收多个int
//使用可变参数时,可以当作数组来使用,即nums可以当作数组
//遍历nums求和即可
int res = 0;
for(int i=0;i<nums.length;++i){
res+=nums[i];
}
return res;
}
}

使用细节

  • 可变参数的实参可以为数组,可变参数的本质就是数组
  • 可变参数可以和普通类型的参数一起放在形参列表,但必须宝恒可变参数在最后
  • 一个形参列表中只能出现一个可变参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Overload1{
public static void main(String[] args) {
HspMethod m = new HspMethod();
int arr[]={1,2,3};
int arr1[]={2,3,4};
m.sum(arr);
}
}

class HspMethod{
public void sum(int... nums){
//int... 表示接收的是可变参数,类型是int,即可以接收多个int
//使用可变参数时,可以当作数组来使用,即nums可以当作数组
//遍历nums求和即可
System.out.println("长度为:"+nums.length);
}
}

作用域

可以参考C语言的作用域:全局变量无需初始化,都可以使用,而局部变量只能在函数内部使用,需要初始化

  1. 在java中,主要的变量就是属性(成员变量)和局部变量
  2. 局部变量一般是指在成员方法中定义的变量
  3. Java中作用域的分类:全局变量:也就是属性,作用域为整个类体;局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中
  4. 全局变量可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,才能使用,因为没有默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Overload1{
public static void main(String[] args) {
Cat cat1 = new Cat();
cat1.cry();
}
}


class Cat{
//全局变量,也就是属性,作用域为整个类,属性在定义时可以直接赋值
int age = 10;
public void cry(){
//n和name为局部变量,其作用域为cry方法中
int n=10;
String name = "jack";
System.out.println(age);
}
}

作用域使用细节

  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
import java.util.Scanner;

public class Overload1{
public static void main(String[] args) {
Person person1 = new Person();
person1.cry();
Person2 person2 = new Person2();
person2.test(person1);
}
}


class Person{
//全局变量,也就是属性,作用域为整个类,属性在定义时可以直接赋值
int age = 10;
String name = "jack";
public void cry(){
//n和name为局部变量,其作用域为cry方法中
int n=10;
System.out.println(name);
}
}

class Person2{
public void test(Person p){
System.out.println(p.name);
}
}

构造器

构造方法又叫构造器,主要用于完成对新对象属性的初始化

  1. 构造器的修饰符可以默认,也可以是public、protected、private
  2. 构造器没有返回值
  3. 方法名和类名字必须相同
  4. 参数列表和成员方法一样的规则
  5. 在创建对象的时候,系统自动调用该类的构造器完成对对象的初始化

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Overload1{
public static void main(String[] args) {
constructor c1 = new constructor("jack",30);
}
}

class constructor{
String name;
int age;
public constructor(String pName,int pAge){
System.out.println("构造器被调用");
name = pName;
age = pAge;
}
}

构造器使用细节

  1. 一个类可以定义多个不同的构造器,即构造器重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Overload1{
public static void main(String[] args) {
constructor c1 = new constructor("jack",30);
constructor c2 = new constructor("kim");
}
}

class constructor{
String name;
int age;
public constructor(String pName,int pAge){
System.out.println("构造器被调用");
name = pName;
age = pAge;
}
public constructor(String pName){
name =pName;
}
}
  1. 如果没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器),即Person(){}。一旦定义了构造器,默认的构造器就会被覆盖

定义一个空类,使用javap对空类进行反编译,可以看到在类中有一个默认构造器(反编译出来的是构造器的声明)

this关键字

java虚拟机会给每个对象分配this,代表当前对象

简化理解:每个人都可以说"我的",但是每个人说出来的"我的"是不同的,所以每个对象的this都不一样,但是可以使用this代表当前对象

1
2
3
4
5
6
7
8
9
10
11
class constructor{
String name;
int age;
public constructor(String name,int age){
this.name = name;//this.name表示当前对象的name属性
this.age = age;//this.age表示当前对象的age属性
}
public constructor(String pName){
name =pName;
}
}
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 Overload1{
public static void main(String[] args) {
constructor c1 = new constructor("jack",30);
System.out.println(c1);
}
}

class constructor{
String name;
int age;
public constructor(String name,int age){
this.name = name;
this.age = age;
System.out.println(this);
}
public constructor(String pName){
name =pName;
}
}

通过上述结果打印的结果可知,this指向自己

下图是this在内存中的表示

this在内存中的理解

this使用细节

  1. this关键字可以用来访问本类的属性、方法、构造器
  2. this用于区分当前类的属性和局部变量
  3. 访问成员方法的语句:this.方法名(参数列表)
  4. 访问构造器语法:this(参数列表);注意只能在构造器中使用(即只能在构造器中访问另外一个构造器),并且必须放在构造器的第一行,注意不能在普通方法使用上述语句。this()可以在构造器中调用本类中的其他构造器
  5. this不能在类定义的外部使用,只能在类定义的方法中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Overload1{
public static void main(String[] args) {
constructor c1 = new constructor();
}
}

class constructor{
String name;
int age;
public constructor(String name,int age){
System.out.println("另一个构造器被调用");
}
public constructor(){
this("jack",100);
System.out.println("第一个构造器被调用");
}
}

包的三大作用

  1. 区分相同的类
  2. 当类很多时,可以很好地管理类(将功能相同或者近似的类放入同一个包中进行管理)
  3. 控制访问的范围-访问修饰符

包的基本语法

package com.hsperdu;

package关键字,表示打包

com.hsperdu包名

包的原理

包的本质实际上就是创建不同的文件夹/目录来保存类文件

由于两个Dog类属于两个不同的包,所以可以同时存在

包的案例

不同包下的Dog类

我们先在资源文件目录下创建两个包,名称为com.xiaoming和com.xiaoqiang,可以看到它自动生成了com文件夹,文件夹中存储两个我们需要的包

我们可以同时在两个包中创建Dog类,其放在了两个不同的包

我们可以导入不同的包进而使用其中的类,也可以使用包名指定使用包中的类,对于两个相同的类,只能导入其中一个包

包的命名和命名规范

命名规则

  1. 只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字

命名规范

  1. 一般是小写字母+小圆点
  2. 一般是com.公司名.项目名.应用模块名

常用的包

  1. java.lang //基本包,默认引入,不需要再引入
  2. java.util //util包,系统提供的工具包、工具类,使用Scanner
  3. java.net //网络包、网络开发
  4. java.awt //是做java的界面开发,GUI

包的使用细节

1
2
import java.util.Scanner;//表示只会引入java.util包下的Scanner
import java.util.*;//表示将java.util包下的所有类都引入

但是建议需要什么类就导入什么类即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.hspedu.pkg;

import java.util.Scanner;//表示只会引入java.util包下的Scanner
import java.util.*;//表示将java.util包下的所有类都引入

public class Import01 {
public static void main(String[] args) {
//使用系统提供的Arrays完成数组排序
int[] arr = {-1, 20, 2, 13, 3};
Arrays.sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
  1. package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package

  2. import指令位置放在package的下面,在类定义前面,可以有多句且无顺序要求

    表明Import01这个类属于com.hspedu.pkg这个包中

访问修饰符

  • 公开级别:用public修饰、对外公开
  • 受保护级别:用protected修饰,对子类和同一个包中的类公开
  • 默认级别:没有修饰符号,向同一个包的类公开
  • 私有级别:用private修饰,只有类本身可以访问,不对外公开

在同一类中

在同包不同类中

在不同包中

使用的注意事项

  1. 修饰符可以用来修饰类中的属性,成员方法以及类
  2. 只有默认的和public才能修饰类,并且遵循上述访问权限的特点
  3. 成员方法的访问规则和属性完全一样

封装

把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对数据进行操作

封装的理解和好处

  1. 隐藏方法的实现细节
  2. 可以对数据进行验证,保证安全合理

封装的实现

  1. 将属性进行私有化[不能直接修改属性]
  2. 提供一个公共的set方法,用于对属性判断并赋值
  3. 提供一个公共的get方法,用于获取属性的值
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
package com.hspedu.Encapsualtion01;

public class Encapsulation1 {
public static void main(String[] args) {
Person person = new Person();
person.setName("jackkkkk");
person.setAge(200);
person.setSalary(30000);
System.out.println(person.info());
//由于属性设置为private,不能直接访问,可以调用get方法
//System.out.println(person.getSalary());
}
}

class Person {
public String name;//name公开
private int age;//age私有化
private double salary;//
//自己写set和get方法太慢了,可以使用快捷键ALT+INS,选择setter和getter
//根据要求完善代码

public String getName() {
return name;
}

public void setName(String name) {
if (name.length() <= 6 && name.length() >= 2) {
this.name = name;
} else {
System.out.println("名字长度出错,默认为Admin");
this.name = "Admin";
}
}

public int getAge() {
return age;
}

public void setAge(int age) {
if (age >= 1 && age <= 120) {
this.age = age;
} else {
System.out.println("设置的年龄不对,需要在1-120之间,默认设置为18");
this.age = 18;
}
}

public double getSalary() {
return salary;
}

public void setSalary(double salary) {
this.salary = salary;
}

//写一个方法返回属性信息
public String info() {
return "name=" + this.name + " age=" + this.age + " salary=" + this.salary;
}
}

将构造器与set方法结合

当直接使用构造器,会发现不会对我们传入的参数进行检查,此时Set方法无效了。所以我们可以将Set方法写在构造器中,对传入的数据进验证

1
2
3
4
5
6
7
//构造器
Person person = new Person("jack",18,10000);
public Person(String name, int age, double salary) {
setName(name);
setAge(age);
setSalary(salary);
}

继承

使用继承来解决代码复用性当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可

比如在两个类中具有类似方法和三个完全相等的属性

Pupil类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//小学生->模拟小学生考试的情况
public class Pupil {
public int age;
public String name;
private double score;

public void setScore(double score) {
this.score = score;
}

public void testing() {
System.out.println("小学生" + name + "正在考小学数学");
}

public void showInfo() {
System.out.println("小学生名" + name + " 年龄" + age + " 分数" + score);
}
}

Graduate类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//模拟大学生考试的的情况
public class Graduate {
public int age;
public String name;
private double score;

public void setScore(double score) {
this.score = score;
}

public void testing() {//和Pupil类中的不一样
System.out.println("大学生" + name + "正在考大学数学");
}

public void showInfo() {
System.out.println("大学生名" + name + " 年龄" + age + " 分数" + score);
}
}

继承关系图

此时A类为父类(基类),B、C类为子类(派生类)

继承的基本语法

1
class 子类 extends 父类{}

两个子类

1
2
3
4
5
6
7
8
9
10
11
12
13
//小学生->模拟小学生考试的情况
public class Pupil extends Student{
public void testing() {
System.out.println("小学生" + name + "正在考小学数学");
}
}

//模拟大学生考试的的情况
public class Graduate extends Student{
public void testing() {//和Pupil类中的不一样
System.out.println("大学生" + name + "正在考大学数学");
}
}

父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Graduate和Pupil的父类
public class Student {
//共有属性
public int age;
public String name;
private double score;
//共有方法
public void setScore(double score) {
this.score = score;
}

public void showInfo() {
System.out.println("小学生名" + name + " 年龄" + age + " 分数" + score);
}
}

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ExtendTest {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name="小明";
pupil.age=10;
pupil.testing();
pupil.setScore(100);
pupil.showInfo();

Graduate graduate = new Graduate();
graduate.name="大明";
graduate.age=20;
graduate.testing();
graduate.setScore(120);
graduate.showInfo();
}
}

继承的好处

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了

继承的细节

  1. 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过公共的方法去访问(父类提供公共方法进行访问)

在父类中设置get方法来访问private属性、也可以在父类中定义公共方法调用private方法

  1. 子类必须调用父类的构造器,完成父类的初始化
1
Pupil pupil = new Pupil();//在父类和子类分别添加无参构造器,然后创建对象

  1. 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会先去调用父类的无参构造器。如果父类中没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作

当父类使用有参构造器,需要使用super来指定父类中的构造器(通过参数),且super()需要放在子类构造器的第一行,先对父类进行初始化再进行子类的初始化,所有的构造器如果没有写super()和this(),第一行默认都有super(),不过被省略了

  1. 如果希望指定去调用父类的某个构造器,则显式的调用一下:super(参数列表)。也就是当父类有多个构造器时,通过super中的参数列表来指定使用父类中的构造器

  2. super()和this()都只能放在构造器的第一行,因此不能同时存在

  3. java所有类都是Object的子类,Object是所有类的基类

  4. 父类构造器的调用不限于直接父类,将一直往上追溯直到Object类(顶级父类)。也就是说子类会调用父类的构造器,而父类也会先调用父类的父类的构造器,一直往上

  5. 子类最多只能继承一个父类(直接继承),即java中是单继承机制

  6. 不能滥用继承,子类和父类之间必须满足is a的逻辑关系,如猫 is a 动物

继承的本质

在内存中的创建

当父类和父类的父类具有相同的属性时,访问时采取就近原则,若此时我们需要访问的属性在父类中是private,则会报错,并且不会去爷爷类找

super关键字

super代表父类的引用,用于访问父类的属性、方法、构造器

  1. 访问父类的属性、方法,但不能访问父类的private属性和方法
  2. 访问父类的构造器(前面用过),super(参数列表),只能放在构造器的第一句