一、复习
1.1 一维数组之二分查找
//假设数组是arr,要求数组的元素必须是有序的,例如:从小到大
//要查找的目标值是target
int left = 0;
int right = arr.length-1;
int index = -1;
while(left <= right){
int mid = left + (right - left)/2;
if(target == arr[mid]){
index = mid;
break;
}else if(target > arr[mid]){
left = mid + 1;
}else{
right = mid - 1;
}
}
if(index != -1){
//找到了
}else{
//没找到
}
//如果没找到,target应该插入到arr[left]的位置
1.2 二维数组
静态初始化:
元素的类型[][] 数组名 = {{第一组的元素们}, {第二组的元素们}, {第三组的元素们}};
动态初始化:
元素的类型[][] 数组名 = new 元素的类型[总的行数][每一行的元素的个数];
//例如:int[][] arr = new int[3][5]; 一共有3行,每一行有5个元素
元素的类型[][] 数组名 = new 元素的类型[总的行数][];
数组名[行下标] = new 元素的类型[这一行的长度];
例如:int[][] arr = new int[3][]; 一共有3行
arr[0] = new int[5];
arr[1] = new int[6];
arr[2] = new int[4];
遍历:
for(int i=0; i<数组名.length; i++){
for(int j=0; j<数组名[i].length; j++){
数组名[i][j]代表一个元素
}
}
1.3 数组工具类
1、java.util.Arrays
Arrays.toString(数组)
:拼接元素为 [元素 1,元素 2,元素 3]Arrays.sort(数组)
:排序Arrays.copyOf(原数组,新数组的长度)
:从原数组复制一些元素构成新数组Arrays.copyOfRange(原数组,起始下标from,终止下标end)
:从原数组复制一些元素构成新数组Arrays.binarySearch(数组, 目标值)
:二分查找,如果目标值存在,返回目标值的下标,如果目标值不存在,返回-插入点-1
2、System
System.arraycopy(原数组,原数组要被复制的元素的最左边下标, 目标数组, 目标数组中存放新元素最左边的下标, 一共复制几个元素)
System.arraycopy(arr, 0, nums, 3, 5)
: 从 arr[0]开始取 5 个元素,复制到 nums 数组中,nums 从[3]开始存储 5 个新元素System.arraycopy(arr, 0, arr, 3, 5)
:右移 3 个位置,移动了 5 个元素System.arraycopy(arr, 3 arr, 0, 5)
:左移 3 个位置,移动了 5 个元素
3、Hutool 工具组件中 ArrayUtil
ArrayUtil.indexOf(数组,目标值)
:顺序查找,如果目标值存在,返回目标值的下标,如果目标值不存在,返回-1ArrayUtil.max(数组)
:返回值最大值ArrayUtil.min(数组)
:返回值最小值ArrayUtil.reverse(数组)
:数组反转ArrayUtil.toString(数组)
:拼接元素为 [元素 1,元素 2,元素 3]1.4 方法
方法的声明格式:
public class 类名{
【①修饰符】 ②返回值类型 ③方法名(【④形参列表】){
⑤方法体语句;
}
}
【① 修饰符】:目前都是 public static
② 返回值类型:
- void:代表方法没有返回值
- 非 void:代表方法有返回值,方法体中必须有 return 结果值; 来返回结果
③ 方法名:一个标识符,遵循小驼峰命名法,见名知意
(【④ 形参列表】)
- () :无参或空参
- (数据类型 参数名, 数据类型 参数名)
⑤ 方法体语句:完成方法功能的语句
方法的调用格式:(前提 public static)
- 同类调用:直接通过方法名调用
- 跨类调用:类名.方法名 进行调用
二、方法
2.1 方法的调用过程分析(理解)
方法的调用会在栈内存开辟空间,用于存储方法的局部变量的数据等,这个动作称为“入栈”。
方法一次调用结束会释放它占用的栈内存空间,这个动作称为“出栈”。如果有返回值,会将结果返回到调用处。
public class MethodTest1 {
//方法:可以实现求任意两个整数的和
public static int add(int a,int b){
int he = a + b;
return he;
}
public static void main(String[] args) {
int x = 1;
int y = 2;
int result = add(x, y);
int a = 3;
int b = 4;
int sum = add(a,b);
}
}
结论:每一次方法调用都会有入栈,调用结束都会出栈。如果同一个方法调用多次,就会入栈多次,出栈多次。栈结构遵循先进后出的原则。
2.2 方法的参数传递机制(理解)
1、情况一:参数是基本数据类型
public class MethodParamTest1 {
public static void main(String[] args) {
int a = 1;
change(a);
System.out.println("a = " + a);//1
}
public static void change(int a){
a=100;
a++;
}
}
结论:
当参数是基本数据类型时,实参给形参的是数据值的副本,接下来在方法中对形参做任何修改与实参
无关
。
public class MethodParamTest1_2 {
public static void main(String[] args) {
int a = 1;
a = change(a);//接收返回值
System.out.println("a = " + a);//101
}
public static int change(int a){
a=100;
a++;
return a;
}
}
2、情况二:参数是引用数据类型
Java的数据类型分为2大类:
(1)基本数据类型:8种 byte,short,int,long,float,double,char,boolean
(2)引用数据类型:
数组: int[]等
类: String,Scanner等
....
public class MethodParamTest2 {
public static void main(String[] args) {
int[] arr = {1};
change(arr);
System.out.println("arr[0] = " + arr[0]);
}
public static void change(int[] arr){
arr[0] = 100;
}
}
结论:当参数是引用数据类型时,实参给形参的是地址值的副本,那么此时通过形参修改元素等内容,会影响实参对应元素。
public class MethodParamTest3 {
public static void main(String[] args) {
int[] arr = {1};
change(arr);
System.out.println("arr[0] = " + arr[0]);//100
}
public static void change(int[] arr){
arr[0] = 100;
arr = new int[]{200};
}
}
结论:当参数是引用数据类型时,实参一开始给形参的是地址值副本,但是如果形参
又
指向新的数组对象时,就与原来的实参无关的。
3、练习题
public class Exercise1{
public static void main(String[] args){
int i=1;
i=i++;
change(i);
System.out.println(i);//1
}
public static int change(int i){
i++;
return i;
}
}
import java.util.Arrays;
public class Exercise2{
public static void main(String[] args){
int[] arr = {1,2,3,4,5};
change(arr);
System.out.println(Arrays.toString(arr));
}
public static void change(int[] arr){
arr = Arrays.copyOf(arr, arr.length*2);
for(int i=0; i<arr.length; i++){
arr[i] *= 2;
}
}
}
2.3 方法的重载(要求掌握)
方法的重载(Overload):同一个类中出现两个或多个方法,它们的名称完全相同
,但是它们的形参列表不同
,这里的形参列表不同是指类型、个数、顺序不同,与形参名无关,这样的两个或多个方法称为重载。方法重载的概念与修饰符、返回值类型无关。
调用重载的方法如何选择?
- 先找最匹配的。实参的类型、个数、顺序与形参完全一致
- 如果没有最匹配的,找可以兼容的。 形参的类型 > 实参的类型,当然此时个数与顺序得对得上
public class OverloadTest1 {
//定义1个方法,可以求任意2个整数的和
public static int add(int a, int b){
return a + b;
}
//定义1个方法,可以求任意2个小数的和
public static double add(double a, double b){
return a + b;
}
//定义1个方法,可以求任意3个整数的和
public static int add(int a, int b,int c){
return a + b + c;
}
/* public static int add(int x, int y){//不是重载,因为形参的个数与类型无法区分
return x + y;
}*/
/*public static double add(int x, int y) {//不是重载,因为形参的个数与类型无法区分
return (double)x + y;
}*/
public static void main(String[] args) {
System.out.println(add(1,4));//add(int a, int b)
System.out.println(add(1.0,4.0));//add(double a,double b)
System.out.println(add(1,4.0));//add(double a,double b)
System.out.println(add(1,2,3));//add(int a, int b,int c)
// System.out.println(add(1.0,2.0,3.0));//错误,没有可以完全匹配的,也没有可以兼容
System.out.println(add('a','b'));//add(int a, int b)
// System.out.println(add('a','b','c','d'));//错误,个数对不上
}
}
2.4 可变参数(能看懂即可)
可变参数:表示某个形参在调用时,可以传入 0~n 个对应的实参,即实参的个数可变。
优势:
- 可变参数部分可以传入 0~n 个对应的实参,或传入对应类型的数组,更灵活
缺点:
- 一个方法最多只能有 1 个可变参数
- 且可变参数必须是形参列表的最后 1 个
如何选择?可变参数还是数组
如果形参只有 1 个数组类型且是最后 1 个形参,那么选择可变参数更灵活。否则依然选择数组。
public class VarParamTest1 {
//需求:设计一个方法,可以求任意个整数的和
public static int add(int... nums){
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
/* public static int add(int[] nums){//错误,因为编译器认为int[]和int...是一样的
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}*/
public static void main(String[] args) {
System.out.println(add());//0个整数
System.out.println(add(4));//1个整数
System.out.println(add(1,2,3,4,5));//5个整数
System.out.println(add(6,1));//2个整数
int[] arr = {10,20,3,40};
System.out.println(add(arr));//也支持传入数组
}
/* public static void m1(int... nums, double... args){//错误,因为一个方法只能有1个形参
}*/
/* public static void m2(double... args,int num ){//错误,可变参数必须是最后一个形参
}*/
}
2.5 命令行参数(了解)
public class CommandParamTest {
/*
(1)main方法的(String[] args)是不是形参? 是
(2)如何给main方法的形参传值?
它的参数传递需要单独传:A:命令行
*/
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]+" ");
}
}
}
2.6 递归(难,0 基础的先放一放)
递归的概念:一个方法自己调用自己,就叫递归。
递归必须有出口,否则出现无限递归,一定会发生 StackOverflowError 错误。
案例 1:n!
public class RecursionTest2 {
public static void main(String[] args) {
System.out.println(f(5));
}
/*
求n!
n! = n * (n-1)!
假设 f(n)代表 n!的话
f(n) = n * f(n-1)
当n = 1 ,f(1) = 1
*/
public static int f(int n){
if(n==1){
return 1;
}
return n * f(n-1);//递归调用
}
}
案例 2:斐波那契数列
案例:计算斐波那契数列(Fibonacci)的第 n 个值,斐波那契数列满足如下规律,
1,1,2,3,5,8,13,21,....
即从第三个数开始,一个数等于前两个数之和。假设f(n)
代表斐波那契数列的第 n 个值,那么f(n)
满足:
f(n)
= f(n-2)
+ f(n-1)
;
public class RecursionTest3 {
public static void main(String[] args) {
System.out.println(fibonacci(5));
}
/*
案例:计算斐波那契数列(Fibonacci)的第n个值,斐波那契数列满足如下规律,
1,1,2,3,5,8,13,21,....
即从第三个数开始,一个数等于前两个数之和。假设f(n)代表斐波那契数列的第n个值,那么f(n)满足:
f(n) = f(n-2) + f(n-1);
*/
public static int fibonacci(int n){
if(n<=2){
return 1;
}
return fibonacci(n-1) + fibonacci(n-2);
}
}
练习题 1
public class RecursionExercise1 {
public static void main(String[] args) {
/*System.out.println(f(10));
System.out.println(f(9));
System.out.println(f(8));*/
for(int i=10; i>=1; i--){
System.out.println(f(i));
}
}
/*
猴子吃桃子问题,猴子第一天摘下若干个桃子,当即吃了所有桃子的一半,还不过瘾,又多吃了一个。
第二天又将仅剩下的桃子吃掉了一半,又多吃了一个。
以后每天都吃了前一天剩下的一半多一个。
到第十天,只剩下一个桃子。试求第一天共摘了多少桃子?
假设 f(n)代表第n天桃子的数量
f(10) = 1
f(9) ÷ 2 -1 = f(10)
f(9) = (f(10) + 1 ) * 2
...
f(n) = (f(n+1) + 1 ) * 2
*/
public static int f(int n){
if(n>10 || n<1){
return 0;
}
if(n==10){
return 1;
}
return (f(n+1) + 1 ) * 2;
}
}
练习题 2
public class RecursionExercise2 {
public static void main(String[] args) {
for(int i=1; i<=10; i++){
System.out.println(i +"级台阶共有几种走法:" +f(i));
}
}
/*
有n级台阶,一次只能上1步或2步,共有多少种走法?
假设 f(n)代表 n级台阶的总走法
f(1) 1
f(2) 11 2
f(3) 111 21 12
f(4) 1111 211 121 112 22
f(5) 11111 2111 1211 1121 221 1112 212 122
f(n) = f(n-1) + f(n-2)
*/
public static int f(int n){
if(n==1){
return 1;
}
if(n==2){
return 2;
}
return f(n-1) + f(n-2);
}
}
用循环改写递归的代码
public class LoopTest {
public static void main(String[] args) {
System.out.println(f(5));//120
System.out.println(peach(1));//1534
System.out.println(step(10));//89
}
//n! 使用循环实现
public static int f(int n){
int result = 1;
for(int i=1; i<=n; i++){
result *= i;
}
return result;
}
/*
猴子吃桃子问题,猴子第一天摘下若干个桃子,当即吃了所有桃子的一半,还不过瘾,又多吃了一个。
第二天又将仅剩下的桃子吃掉了一半,又多吃了一个。
以后每天都吃了前一天剩下的一半多一个。到第十天,只剩下一个桃子。试求第一天共摘了多少桃子?
*/
public static int peach(int day){
int count = 1;//第10天
for(int i=9; i>=day; i--){
count = (count+1)*2;
}
return count;
}
//有n级台阶,一次只能上1步或2步,共有多少种走法?
public static int step(int n){
if(n==1){
return 1;
}
if(n==2){
return 2;
}
int lastTwo = 1;//最后一步跨2级
int lastOne = 2;//最后一步跨1级
//对于初始值,计算第3级台阶的走法,
//最后一步跨2级 就1种走法 1(2)
//最后一步跨1级 就2种走法 11(1) 2(1)
int current = 0;
for(int i=3; i<=n; i++) {
current = lastTwo + lastOne;
lastTwo = lastOne;
lastOne = current;
}
return current;
}
}
三、面向对象
举例:把大象装进冰箱的问题?
面向过程的编程思想:
- 第一步:把冰箱门打开
- 第二步:把大象装进去
- 第三步:把冰箱门关上
面向对象的编程思想:
- 先把事物抽象成类:冰箱、大象、人
- 每一个事物中封装对应的属性和方法
class 冰箱{
String 品牌;
int 长;
int 宽;
int 高;
void open(){
}
void close(){
}
void save(){
}
}
class 大象{
double 体重;
void walk(){
}
}
class 人{
String name;
void pull(东西){
if(东西是大象){
大象.walk();
}else if(东西是冰箱){
冰箱.open();
}
}
void push(东西){
if(东西是大象){
大象.walk();
}else if(东西是冰箱){
冰箱.close();
}
}
}
class 主类{
public static void main(String[] args){
人 r = new 人("张三");
大象 e = new 大象(5);
冰箱 b = new 冰箱("格力", 5,10,10);
r.pull(b);
r.push(e);
r.push(b);
}
}
3.1 类与对象(两者关系、语法格式)
- 类:一类具有相同特征的事物的
抽象
描述。- 例如:学生(属性:姓名、性别、民族、身份证号码,行为:学习、考试)
- 例如:老师(属性:姓名、性别、民族、身份证号码,行为:教学,监考、出卷子)
- 对象:是这一类事物中的一个
具体
的个体、实例、实体。- 例如:姓名:张三,性别:男,民族:汉,身份证号码:110250199912031235,
- 比喻:类是设计图,模板,对象是根据设计图造出来产品
声明类的语法格式:
【修饰符】 class 类名{
}
关键字:class,就代表定义了一个新的类
先有类还是先有对象?
如果从代码的编写角度,一定是先写类,再 new 对象。
如果从设计代码的角度,先观察和分析各种对象,找到它们的共性,才能抽取出对应类。
创建对象的语法格式:
new 类名() //匿名对象
类名 对象名 = new 类名(); //给对象取名字
Scanner input = new Scanner(System.in);//Scanner是类名,input是对象名
System.out.print("请输入一个整数:");
int num = input.nextInt();
示例代码:
public class Student {
String name;
char gender;
String nation;
String cardId;
public void study(){
System.out.println(name + "good good study, day day up");
}
public void exam(){
System.out.println(name + "蒙的全对,不会的不考");
}
}
public class TestStudent {
public static void main(String[] args) {
Student s = new Student();
s.name = "李刚";
s.gender = '男';
s.nation ="汉族";
s.cardId = "110250199912031235";
s.study();
s.exam();
Student s2 = new Student();
s2.name = "李涛";
s2.gender = '男';
s2.nation ="汉族";
s2.cardId = "110250199912031234";
s2.study();
s2.exam();
new Student().study();
}
}
3.2 类的成员之一:成员变量(未完待续)
类的成员变量是用于描述事物的数据特征的,即属性特征。
平时生活中,说的属性,是事物的数据特征属性,例如:文件的标题、修改日期、大小等
编程中,说的属性,具有 get 方法的成员变量才叫属性
声明格式:
【修饰符】 class 类名{
【修饰符】 数据类型 成员变量名;
【修饰符】 数据类型 成员变量名;
【修饰符】 数据类型 成员变量名;
}
分类:
- 静态的成员变量,简称静态变量
- 非静态的成员变量,简称实例变量
静态变量 | 实例变量 | |
---|---|---|
前面有没有 static | 有 | 无 |
是否依赖对象 | 不依赖 | 依赖 |
是否所有对象共享 | 共享 | 每一个对象都是独立的 |
静态变量与实例变量的相同点:都有默认值
数据类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0F |
double | 0.0 |
char | \u0000 |
boolean | false |
引用数据类型(String、数组等) | null |
思考题:什么情况下,成员变量应该声明为静态的?什么情况下,成员变量不应该声明为静态的?
如果这个成员变量的值是需要所有对象共享的,就必须声明为静态。
如果这个成员变量的值不应该被共享,那么就必须声明为非静态。
例如:银行卡类:有利率,余额,
- 利率是静态的,所有人都一样
- 余额是非静态的,每一个人都不同
package com.atguigu.oop;
public class Chinese {//中国人
public static String country;//静态变量,所以中国人的国家名是相同的
public String name;//实例变量,每一个中国人的名字是独立的
public int age;//实例变量,每一个中国人的年龄是独立的
}
package com.atguigu.oop;
public class TestChinese {
public static void main(String[] args) {
Chinese.country = "中国";
System.out.println("country = " + Chinese.country);
// System.out.println(Chinese.name);//错误
// System.out.println(Chinese.age);//错误
//name和age是非静态的实例,它们依赖于Chinese的对象
Chinese c1 = new Chinese();
c1.name = "李刚";
c1.age = 26;
System.out.println("c1.name = " + c1.name);
System.out.println("c1.age = " + c1.age);
System.out.println("c1.country = " + c1.country);
Chinese c2 = new Chinese();
c2.name = "李涛";
c2.age = 28;
System.out.println("c2.name = " + c2.name);
System.out.println("c2.age = " + c2.age);
System.out.println("c2.country = " + c2.country);
System.out.println("===================");
c1.country = "中华人民共和国";
c1.name = "大刚";
System.out.println("c1.name = " + c1.name);
System.out.println("c1.age = " + c1.age);
System.out.println("c1.country = " + c1.country);
System.out.println("c2.name = " + c2.name);
System.out.println("c2.age = " + c2.age);
System.out.println("c2.country = " + c2.country);
}
}
3.3 包(会建包,会导包)
1、包的作用
- 就相当于文件,分类管理。
- 包相当于类的前缀,可以避免类的重名,即包名.类名才是类的全名称
- 例如:String 类的全名称, java.lang.String
- Scanner 类的全名称, java.util.Scanner;
2、包名的命名规范
- 见名知意
- 所有单词都小写,单词之间使用.分割
- 采用公司域名倒置的形式,例如:com.atguigu.xxx
- 除了核心类库,咱们自己的包名,不要以 java.开头
3、如何创建包?
4、跨包使用类
import 包.类名;
import 包.*; //这个包的所有类都可以使用了,只能省略类名
相关代码:
- one 包
package com.atguigu.one;
public class Teacher {
}
package com.atguigu.one;
public class TeacherDemo {
public static void main(String[] args) {
Teacher t = new Teacher();
}
}
package com.atguigu.one;
public class Employee {
}
- three 包
package com.atguigu.three;
public class Teacher {
}
- two 包
package com.atguigu.two;
/*import com.atguigu.one.Employee;
import com.atguigu.one.Teacher;*/
import com.atguigu.one.*;
public class TestTeacher {
public static void main(String[] args) {
//创建one包的Teacher类型
Teacher t = new Teacher();
//创建Employee类
Employee e = new Employee();
//创建three包的Teacher类型
//当两个类同名,但是包不同,只能1个使用import,另一个使用全名称
com.atguigu.three.Teacher t2 = new com.atguigu.three.Teacher();
}
}