今日学习总结:
课前联系
输入日期,计算日期在本年的天数
public class Test01 {
public static void main(String[] args) {
// 输入
Scanner sc = new Scanner(System.in);
int year = 0,month = 0,date = 0;
while (true) {
year = inputNum(sc, 1, 3000, "年份");
month = inputNum(sc, 1, 12, "月份");
date = inputNum(sc, 1, 31, "日期");
boolean bb = validateDate(year, month, date);
if (bb) break;
System.out.println("请输入合法的年月日!");
}
System.out.println(year+"-"+month+"-"+date);
// 计算
// 输出
}
public static boolean validateDate(int year, int month, int date) {
boolean res = false;
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
res = date <= 31 && date>=1;
break;
case 4:
case 6:
case 9:
case 11:
res = date <= 30 && date>=1;
break;
case 2:
boolean bb = run(year);
if (bb) res = date <= 29 && date>=1;
else res = date <= 28 && date>=1;
break;
default:
res = false;
break;
}
return res;
}
public static boolean run(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
public static int inputNum(Scanner sc, int min, int max, String str) {
int res = 0;
while (true) {
System.out.println(str + ":");
String ss = sc.nextLine();
try {
res = Integer.parseInt(ss);
if (res >= min && res <= max)
break;
System.out.println("请输入合理的" + str + "数据");
} catch (Exception e) {
System.out.println("输入" + str + "数据不合法!");
}
}
return res;
}
}
方法的概念
多次反复编写明显是不合适的,而结构化编程中的模块化在Java中的体现就是自定义方法。
需求:输出1-100之间所有素数
public class Test1 {
// 特殊的方法,用于表示程序的执行起始点,也就意味着不能写错
public static void main(String[] args) {
for(int i=2;i<101;i++){
if(abc(i)){ //调用处传入的数据是实参
System.out.println(i);
}
}
System.out.println(abc(10,0));//语法报错,因为abc方法在定义时,只说明一个
参数,但是调用处是两个参数,不对应。
}
//定义方法,命名规则为要求见名知意,一般建议首字母小写,大写字母分词。否则必须添加注
释说明
//判断kk是否为素数
//boolean表示这个方法的返回值必须为boolean,也就是只能true/false,其它类型则出错
//方法名称:标识符的命名规则
//()中的内容为方法参数,就是调用方法时必须传递的数据
//int表示传入的参数必须为int类型,否则报错
//至于参数的名称可以理解为占位符,称之为形参,调用时会被传递的数据所替代
public static boolean abc(int kk){//自定义方法,用于封装一个具体的处理过程,
当需要使用这个处理过程时只需要通过名称就可以直接调用。实际参数的传递是通过实参和形参一一对
应实现的(位置对应)。
boolean res=true;
for(int i=2;i<=kk/2;i++){
if(kk%i==0){
res=false;
break;
}
}
return res;//return表示立即终止当前程序的运行,并返回调用处
}
}
方法可以理解为一个命名的代码块,通过名称就可以重复使用这段代码,而不需要反复书写,可以达到代码重用的目的
方法可以有参数,也可以没有参数;方法可以有返回值,也可以没有返回值[必须声明返回值为void]
方法定义的具体位置没有关系,可以先写调用【会有报错】,然后定义方法也可以;先写定义后调用也可以。
调用方式常见的有三种
单独调用。这种方式无法使用方法的返回值。格式:方法名称(参数值);
public static void show(int k,long res) {
if (k<1) {
k=1;
}
for (int i = 0; i < k; i++) {
System.out.println(res);
}
}
调用是show(1,5),这个show方法没有返回值,所以不能接收返回值,否则语法报错【intk=show(1,5);】;如果有返回值,可以不接受返回值
public static int pp(){...}
int k=pp(); //语法正确
pp(); //语法正确
打印调用。这种方式可以将方法的返回值直接打印。格式:System.out.println(方法名称(参数值)); 注意使用sysout方法调用需要有返回值
赋值调用。这种方式可以将方法的返回值赋值给一个变量,注意变量的数据类型必须和方法的返回值类型对应。格式:数据类型 变量名称= 方法名称(参数值)
重名问题
变量的名称是否可以与方法名称重名?可以。
public class Test03 {
public static void main(String[] args) {
pp(); //方法名称允许相同
pp(11);
}
public static void pp() {
System.out.println("void...pp()");
}
public static void pp(int k) { //两个同名的方法参数不同才可以重名定义
System.out.println("void...ppp()");
}
//方法参数不同有3种情况:类型不同、数量不同、顺序不同[不是方法参数名称]
}
public class Test03 {
public static void main(String[] args) {
pp();
pp(11);
}
public static void pp() {
System.out.println("void...pp()");
}
public static void pp(int k) {
System.out.println("void...pp()");
}
public static void pp(int k1,String s1) {
System.out.println("void...ppp()");
}
public static void pp(int s1,int k1) {
System.out.println("void...ppp0()");
}
}
两个不同的方法中,能否各自有一个重名的变量?可以,而且各个方法中的临时变量之间没有任何关系。
main方法中有2个临时变量begin和end,add方法中也有两个临时变量begin和end,这个方法中的临时变量没有任何关系,各自生存在不同的方法中
参数传递
- 形式参数:在定义方法的时候,写在小括号之内的变量,就叫形式参数。实际上在方法定义中起到占位符的作用,会在方法调用时被传递过来的实际值所替代
- 实际参数:在调用方法的时候,真正传入方法里的数据,叫做实际参数。
圆括号中的实参列表为调用方法时实际传入的实际参数,称为实参列表。声明方法时圆括号中的参数称为形式参数,形式参数和实际参数在数据类型和个数上一定要匹配
注意:调用方法时形式参数和实际参数的个数和顺序必须一致,数据类型也必须相同。
两条规则
对于基本类型来说,形式参数的操作【不会】影响实际参数。值是单向传递
对于引用类型来说,形式参数的操作【会】影响实际参数。
对任何语言来说,方法或者函数解决了需要重复使用的代码的次数
递归调用
递归调用指在方法执行过程中允许出现直接或者间接的该方法本身的调用
计算阶乘5!
阶乘: 0!=1,n!=(n-1)!×n
public class Test04 {
public static void main(String[] args) {
int res=jie(5);
System.out.println("5!="+res);
}
public static int jie(int k) { //自己直接调用自己---递归调用
if(k==0)
return 1;
return k*jie(k-1);
}
}
int res=1;
for(int i=1;i<=n;i++) res*=i;
斐波那契数列Fibonacci sequence,又称黄金分割数列,以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34…
以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
斐波那契在《算盘书》中提出了一个有趣的兔子问题:一般而言,兔子在出生两个月后,就有繁殖
能力,一对兔子每个月能生出一对小兔子来。如果所有兔都不死,那么一年以后可以繁殖多少对兔
子?
public class Test05 {
public static void main(String[] args) {
int months = 0;
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("月份:");
String ss = sc.nextLine();
try {
months = Integer.parseInt(ss);
if (months > 0) break;
System.out.println("请重新输入月份数!");
} catch (Exception e) {
System.out.println("请输入合法的月份数!");
}
}
int num = tongji(months);
System.out.println(months + "月后的兔子数为:" + num);
}
public static int tongji(int months) {
if (months > 0) {
if (months == 1 || months == 2) return 1;
return tongji(months - 1) + tongji(months - 2);
}
return 0;
}
}
递归调用比较符合正常人的思维方式,但是相当的浪费内存,所以如果能使用其他方式解决就不要使用递归。
循环和递归对比:
- 递归:易于理解、速度慢、存储空间大
- 循环:不易于理解、速度快、存储空间小
有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13… 求出这个数列的前20项之和
public class Test06 {
public static void main(String[] args) {
double res = 0;
for (int i = 1; i <= 20; i++) {
res += 1. * diguiFenzi(i) / diguiFenmu(i);
}
System.out.println(res);
}
public static int diguiFenzi(int n) {
// 如果使用递归调用必须保证有退出递归的点,必须保证不断逼近退出的点
if (n == 1) return 2;
else if (n == 2) return 3;
else return diguiFenzi(n - 1) + diguiFenzi(n - 2);
}
public static int diguiFenmu(int n) {
if (n == 1 || n == 2) return n;
else return diguiFenmu(n - 1) + diguiFenmu(n - 2);
}
}
汉诺塔问题:
梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘
移动次数是f(n).显然f(1)=1,f(2)=3,f(3)=7,且f(k+1)=2*f(k)+1
猴子吃桃问题:
public class 汉诺塔问题 {
public static void main(String[] args) {
System.out.println(move(3));
}
public static long move(int n) {
if (n == 1)
return 1;
return 2 * move(n - 1) + 1;
}
}
递归调用的特征
Java语言支持方法的递归调用
- 使用递归调用时必须可以逐渐接近结束点,不能发散
- 递归调用比较符合正常人的思维方式,但是相当的浪费内存,所以如果能使用其他方式解决就不要使用
数组
数组是表示多个相同类型变量的集合(在一个数组中所存放的所有元素的类型必须一致),可以使用共同的名字引用它
属于复杂数据类型
- 由类型相同的元素组成的有顺序的数据集合
- Java数组是固定的不能扩展[长度一旦声明,不能修改]
- 可以存储基本数据类型或对象
- 数组可以定义为任意数据类型,并且可分为一维数组或多维数组
一维数组
一维数组实质上是相同类型变量的列表。要创建一个数组必须首先定义数组变量所需的类型。通用的一维数组的声明格式是:type var-name[ ];
int[] arr1 = new int[10]; // int[]或arr2[]都是用于声明是数组类型,int用于声明每个
元素都是int类型
int arr2[] = new int[20]; //要求使用数组之前必须先声明所需空间大小,即存储的元素个
数,一旦声明则不能修改
int[] brr;
brr[0]=123; //语法报错
- 数组中的元素必须类型相同,不能存储不同类型的元素,除非使用Object[]
int[] brr=new int[10];
brr[0] = 123.; //类型必须一致 brr[0]表示数组的第0个元素,[]中是索引下标值--
序号
Object[] brr=new Object[10];
brr[0] = 123.;
brr[1]="sdafsd";
- 可以通过数组的下标操作数组中所存储的元素,注意下标从0开始,例如arr[0]+=12;修改元素的值System.out.println(arr[0]);获取元素
使用数组之前,必须先定义后使用,定义的方式: int[] arr或者int arr[]
- 声明数组变量后,必须进行初始化操作,也就是定义数组的长度 arr=new
int[5],这里表示开启可以存放5个int类型数据的数组,这些元素时连续存放的 - 简单类型的数组初始化长度后,每个元素都有默认值
int[] arr=new int[10];
//所有的数值型数据默认0,boolean类型默认false,char类型默认'\0'
System.out.println(arr[1]); //输出为0
- 创建数组后,每个元素都有一个默认值,如果针对的是引用类型,则默认值为null;如果是简单类型中byte short int long float double,则默认值为0,如果char类型默认值为\0,如果boolean类型,则默认false
Integer[] arr=new Integer[10];
System.out.println(arr[1]); //输出为null
在Java中允许直接赋值初始
- int[] arr={1,2,3,4,5,6,7}; 注意在于int[]中不能写具体的对应长度
- 也可以写成new int[]{1,2,3,4,5,6,7},但是int[]中不能有长度值
int[] arr= {1,2,3,4,5,6};
System.out.println(arr[3]);
int[] brr=new int[] {1,2,3,4,5,67};//注意不能修改int[]为int[6],否则语法报错
System.out.p
声明一个int型的一维数组:int number[];
[ ]在字节左边,对后面所有变量都有影响char[] s, t;
int arr[],brr[]; //等价于int[] arr,brr;
arr=new int[5];
brr=new int[3];
System.out.println();
数组虽然声明了变量类型。但不存在实际的数值,它的值为null。为了使数组number 成为实际的、物理上存在的整型数组,你必须用运算符new 来为其分配地址并且把它赋给number
int[] arr;//声明整型数组变量arr
System.out.println(arr); //语法错误,必须先声明后使用,先赋初值后使用
运算符new 是专门用来分配内存的运算符,格式为:array-var = new type[size]; 开启出来的空间是连续,可以通过下标进行快速定位
例如上面的代码: int[] kk=new int[10];
length属性
数组对象中有个属性length表示的是数组的长度
int[] arr=new int[] {1,2,3,4,5,6};
System.out.println(arr.length); //获取数组中的元素个数
使用运算符new来分配数组,必须指定数组元素的类型和数组元素的个数。用运算符new分配数组后,数组中的元素将会被自动初始化为一个值,这个值具体是什么和类型相关。如果是数值类型【整数、浮点数、char类型】其中自动初始值为0,如果boolean类型则自动初始值为false
比如s = newchar[26];程序,生成一个包含26个字符值的数组,并将每个数组元素的初始值设为‘\u0000’
- 创建简单类型的数组时,如果是数值型(整数或者浮点数),默认元素为0;如果是字符型,默认元素为\u0000[注意这里不是字符0,字符0的值为48];如果是boolean型,则默认元素为false
- //如果创建的时复杂类型的数组,例如String[]b=new String[10],则默认元素为null
数组元素的访问
针对数组中的元素可以通过下标进行访问,访问的语法为: 数据变量的名称[下标]
数组中的所有元素将被初始化为零。接下来可以给数组中每个元素赋值
kk[0]=10;
kk[1]+=13;
……
这样给数组赋值显得不够现实
声明的同时初始化
在数组定义时指定元素的初始值,即称为数组的初始化
int a[]={1,2,3,4,5};
声明一个数组并初始数组内容
int[] score = {90, 85, 55, 94, 77}; //也可以写作new int[]{90, 85, 55, 94, 77},但是不允许写成newint[5]{90, 85, 55, 94, 77},语法报错
int[] arr=new int[] {2,5,3,1,9,0};
for(int i=0;i<arr.length;i++)
System.out.println("数组arr的第"+i+"个元素值为:"+arr[i]);
int[] arr = new int[] { 2, 5, 3, 1, 9, 0 };
for (int i = 0; i < arr.length; i++)
System.out.printf("%s[%d] = %d\n", "arr", i, arr[i]); //printf用于格式化
输出,
格式化输出
int[] score = {90, 85, 55, 94, 77}; //也可以写作new int[]{90, 85, 55, 94, 77}
for(int i = 0; i < score.length; i++)
System.out.printf("score[%d] = %d\n", i, score[i]);
//前面的格式符号%x的个数应该和后面参数一致,如果出现了参数不足时则会报错
int[] arr = new int[] { 2, 5, 3, 1, 9, 0 };
for (int i = 0; i <= arr.length; i++) //注意=的问题
System.out.printf("%s[%d] = %d\n", "arr", i, arr[i]); //没有语法错误,但
是执行代码会有报错
错误分析:在控制台上可以看到报错信息ArrayIndexOutOfBoundsException表示数组越界,就是访问一个不存在的元素
当取出数组元素的时,数组下标索引不能超出数组范围[0到length-1],如果超出数组范围会发生ArrayIndexOutOfBoundsException。例如声明一个数组的元素个数为5,而去取了它的第6个,那么程序就会提示下标越界的错误
可以使用动态的方式来宣告数组长度,而不用在程序中事先决定数组大小
public class Test08 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int len=sc.nextInt();
int[] arr=initArr(len);//可以根据输入的参数创建不同长度的数组
System.out.println(arr.length);
}
public static int[] initArr(int length) {
// if(length<1)
// return new int[5];
return new int[length];//初始化数组长度时参数不同小于0,但是允许等于0
}
}
循环打印出小写字母a~z
char c='a';
for(int i=0;i<26;i++)
System.out.println((char)(c+i));
char[] arr=new char[26];
for(int i=0;i<26;i++)
arr[i]=(char)(c+i);
//输出数组中的数据
for(int i=0;i<arr.length;i++)
System.out.println(arr[i]);
//foreach结构,是用于遍历集合写法的语法糖,是for循环的简化写法
for(char temp:arr)
System.out.println(temp);
将同一个对象指定给两个参考名称
运行后则brr变量和arr变量都指向同一个数组,就是前面创建的数组,其中存放了26个字符的数组
char[] arr=new char[26];
for(int i=0;i<arr.length;i++)
arr[i]=(char)('a'+i);
char[] brr=arr;//运行结果是变量brr和变量arr指代同一个位置
brr[brr.length-1]='\u9e00'; //修改brr中的内容实际上也修改了arr的内容
for(char temp:arr)
System.out.println(temp);
运行后则brr变量和arr变量都指向同一个数组,就是前面创建的数组,其中存放了26个字符的数组
char[] arr=new char[26];
for(int i=0;i<arr.length;i++)
arr[i]=(char)('a'+i);
char[] brr=arr;//运行结果是变量brr和变量arr指代同一个位置
brr[brr.length-1]='\u9e00'; //修改brr中的内容实际上也修改了arr的内容
for(char temp:arr)
System.out.println(temp);
基础: 简化版概念—理解引用数据类型
brr=arr意思是将arr所代表的复杂类型数据的地址传递给brr变量,结果是brr和arr变量使用的是同一个复杂类型数据,不管是brr修改或者arr修改,实际上修改是同一个数据,会发现一个修改,另外一个也同时发生变化
数组复制备份:注意这里不是地址值的复制,因为地址值的复制brr=arr,实际上两个变量引用的是同一个复杂类型数据
需要两个不同复杂类型数据,操作中不相互影响
使用循环作数组复制—克隆
扩展:实现一个可变长的数组【Java中要求数组的长度是确定,一旦定义则不允许修改】
/**
* 用于实现数组长度的自动增长
* @param arr 原始数组
* @param pos 需要添加数据的位置
* @param num 添加的数据
* @return 添加数据后的数组,有可能长度已经发生变化
*/
public static int[] changeLength(int[] arr,int pos,int num){
if(pos<arr.length){
arr[pos]=num;
return arr;
}else{
int newLength=arr.length*2;
if(pos>=newLength)
newLength=pos+1;
int[] res=new int[newLength];
for(int i=0;i<arr.length;i++)
res[i]=arr[i];
res[pos]=num;
return res; }
}
两种写法的比较:
方法1:优势在于避免频繁的值拷贝,但是有可能会有开辟出的没有用的空间
方法2:不要不断的进行变长,但是每次变长都会导致值拷贝的过程,所以比较浪费时间,优势在于用多少空间则开辟多少空间
提示:一般情况下最佳实践为合理的评估长度,最少减少增长次数
数组的操作
length为数组对象的属性成员,可以直接使用【数组名称.length】获取数组长度。但是由于序号是从0开始,所以最大下标值为length-1,否则ArrayIndexOutOfBoundsException表示数组下标越界
数组的长度一旦定义后,则不能改变。如果需要改变则需要重新创建数组
使用System类别所提供的arraycopy()方法 注意
int[] arr1 = {1, 2, 3, 4, 5};int[] arr2 = new int[5];
System.arraycopy(arr1, 0, arr2, 0, arr1.length);参数含义为从arr1的第0个位置开始拷贝,拷贝到arr2的第0个位置开始,总共拷贝arr1.length个元素
Java.lang.System类的静态方法arraycopy(Object源数组,int起始下标,Object目标数组,int目标起始下标,int长度) 如果源数组数目超过目标数组边界会抛出IndexOutOfBoundsException异常
int[] arr={1,2,3,4,5};
int[] brr=new int[arr.length];
System.arraycopy(arr, 0, brr, 0, 3);
for(int temp:brr)
System.out.println(temp);
练习题
获取某个数组中的最小值
public class A2 {
public static void main(String[] args) {
//生成一个随机数的数组,长10
int[] arr=new int[10];
Random r=new Random();
for(int i=0;i<arr.length;i++)
arr[i]=r.nextInt();
System.out.println(arr);//[I@4e25154f 数组的直接输出格式
for(int temp:arr) //自定义输出数组中的每个元素
System.out.print(temp+"\t");
System.out.println(); //实现下次输出从头开始输出
int min=Integer.MAX_VALUE;
for(int i=0;i<arr.length;i++)
if(arr[i]<min)
min=arr[i];
System.out.println(min);
} }
B哥去参加青年歌手大奖赛,有10个评委打分,(去掉一个最高一个最低)求平均分
public class A3 {
public static void main(String[] args) {
// 评委打分 0-10
Scanner sc = new Scanner(System.in);
double[] arr = new double[10];
for (int i = 0; i < arr.length; i++)
inputNum(arr, i, sc);
for (double temp : arr)
System.out.println(temp);
// 获取最值
double min = min(arr);
double max = max(arr);
//按照业务规则定义算法实现
double res=0;
for(double temp:arr)
res+=temp;
res-=min;
res-=max;
res/=8;
System.out.println("平均分:"+res);
}
// 用于获取arr数组中的最大值
public static double max(double[] arr) {
double res = Double.MIN_VALUE;
if (arr != null) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] > res)
res = arr[i];
}
}
return res;
}
// 用于获取arr数组中的最小值
public static double min(double[] arr) {
double res = Double.MAX_VALUE;// 默认值为double类型的最大取值
if (arr != null) // 用于避免NullPointerException
for (int i = 0; i < arr.length; i++)
if (arr[i] < res res = arr[i];
return res;
}
//用于实现用户键盘录入评分,arr存储数据的数组,index是第几个评分,sc键盘
public static void inputNum(double[] arr, int index, Scanner sc) {
while (true) {
System.out.println("请打分:");
String ss = sc.nextLine();
try {
double d = Double.parseDouble(ss);
if (d >= 0 && d <= 10) {
arr[index] = d;
break;
}
System.out.println("输入的分数不合法!");
} catch (Exception e) {
System.out.println("输入数据格式不正确");
}
}
} }
将指定数组中的数组元素进行反转 ,例如:{1,2,3,4,5} 反转{5,4,3,2,1}
int[] arr=new int[]{1,2,3,4,5};
for(int i=0;i<arr.length/2;i++){
int temp=arr[i];
int pos2=arr.length-i-1;
arr[i]=arr[pos2];
arr[pos2]=temp; }