Java自学笔记(部分知识)

我居然找到以前的笔记,amazing!格式有空再改

day02 运算符

除法/的运算结果和运算对象的数据类型有关,两个数都是int,则商就是int,若商有小数,则截取小数部分;被除数和除数中只要有一个或两个都是浮点型数据,则商也是浮点型,不截取小数部分。

16/5 = =3  
16/5.0 = =3.20000  
 -13/4 = =-4  
 -13/-3 = =4  
3/5 = =0

取余%的运算符对象必须是整数,结果是整除后的余数,其余数的符号与被除数(%左边的数)相同

13%3 = =1            13%-3 = =1          -13%3 = =-1  
-13%23 = =-13         -13%-23 = =-13        3%5 = =3

规律:%中,若左边的数小于右边的数,则结果就为左边的数;若左边的数等于右边的数,则结果就为0;右边是1,结果为0
字符串数据和任何数据使用+都是相连接,最终都会变成字符串。

System.out.println("ab"+5+5);  //ab55
System.out.println("ab"+(5+5));   //ab10
int a=4, b=5;
System.out.println(a+b);  //9
System.out.println(a+","+b); // 4 , 5
System.out.println("a="+a+", b="+b); // a=4, b=5

转义字符:通过\来转变后面字母或者符号的含义。转义字符写在被转义字符的前面。

\n:换行 linux系统中,回车符由\n表示
\b:退格 相当于backspace键。
\r:按下回车键。 window系统中,回车符是由两个字符表示\n\r
\t:制表符。相当于Tab键

System.out.println("\"hello\""); //"hello"
System.out.println("\\hello\\"); //\hello\
char ch='n';//对的
char ch='\n'; // 对的,转换成回车符,是可以的
char ch='\'; //错的
char ch='\"';  //对的
char ch='\'';//对的
char ch=''';  //错的
char ch='你'; //对 java中char是两个字节,一个汉字也是两个字节
		
int x=3; //右边的值赋值给左边
x+=4; //左右两边的和赋值给左边
short s=4;
s=s+5;  //错的,两次运算,先做加法再做赋值, 丢失精度报错 
s+=5; //对的,一次运算只做一次赋值运,它内部有一个自动转换动作
s = short(s+2); //错的

解析:

   s  =   s   +   5;
 short  short   double     
   2B      2B     4B

先相加变成double 类型数据,再将其存储到short类型空间中,导致数据丢失精度所以报错(大的数据类型不能向小的数据类型自动转换)

逻辑运算符

true & true = true;
true & false = false;
false & true = false;
false & false = false;

& : 只要两边的boolean表达式结果,有一个为false,那么结果就是false
只有两边都为true,结果才为true。

true | true = true
true | false = true
false | true = true
false | false = false

|:两边只要有一个true,结果为true. 只有两边都为false,结果才为false

^:异或:相同为假(false),不同为真(true)

true ^ true = false
true ^ false = true
false ^ true = true
false ^ false = false

! 取反 真取反为为假,假取反为真

&时,左边无论真假,右边都进行运算

&&时,如果左边为真,右边参与运算;如果左边为假,那么右边不参与运算

|: 左边无论真假,右边都参与运算
||:左边为假,右边参与运算;左边为真,右边不参与运算

位移运算符

<< :左移 其实就是乘以2的移动的位数次幂
>> :右移 就是除以2的移动的位数次幂
>>> 无符号右移,被移位二进制最高位无论是0或者是1,空缺位都用0补。

例子:
3<<2 : 3左移两位,先把3换成二进制,整体左移两位,低位空位补0,被移除的高位丢弃

3 的二进制:  00000000 00000000 00000000 00000011 
左移两位后:00000000 00000000 00000000 0000001100 
所以   3<<2=12

6>>2: 6右移两位,先把6换成二进制,整体右移两位,若被移位的二进制最高位是0,右移后,高位空缺位补0;若被移位的二进制最高位是1,高位空缺位补1,被移除的低位丢弃。

6的二进制:  	00000000 00000000 00000000 00000110
右移两位后:	00000000 00000000 00000000 0000000110
所以    6>>2 = 1
6的反码: 11111111 11111111 11111111 11111001           
6的补码(-6的二进制): 11111111 11111111 11111111 11111010
右移两位后:            11111111 11111111 11111111 1111111010
所以  -6>>2 =-2           

结论:

往左移,越移越大;往右移,越移越小
正数:原码=反码=补码 负数:反码=原码取反 ;
补码=反码+1 ; -> 补码=原码的反码+1
原码=补码的反码+1 负数以补码的形式存储在计算机中

3<<2=12;   3<<1=6;   3<<3=24;
3*4=12     3*2=6      3*8=24
3*2^2=12    3*2^1=6     3*2^3=8

往左移,移几位就是乘以2的几次方,这里只验证了正数,负数还未验证

6>>2=1    6>>1=3    -6>>2 =-2    -6>>1=-3
6/2^2=1   6/2^1=3;  -6/2^2=-1   -6/2^1=-3

往右移,移几位就是除以2的几次方,这里只验证了正数,负数还未验证

验证6>>2 和-6>>2的结果时,出现了惊喜 6>>2=1 相当于6/4=1,-6>>2=-2 不相当于 -6/4=-1,估计那个规律适合正数,不适合所有负数。

3>>>1=1->3/2=1

一个数异或同一个数两次,结果还是那个数。

	int n = 3,m = 8,temp;
	System.out.println("n="+n+",m="+m);
	//用变量的方式交换两个数,编程常用
	temp = n;
	n = m;
	m = temp;
	System.out.println("n="+n+",m="+m);
		
	//不用变量的方式进行交换 
	//n的值为n与m的和,如果n和m的值非常大,容易超出int范围
	n = n + m;
	m = n - m; //将n原来的值赋值给m
	n = n - m; //将m原来的值赋值给n
	System.out.println("n="+n+",m="+m);
方法2	
n = n ^ m;
m = n ^ m;//(n ^ m) ^ m = n
n = n ^ m; // (n ^ m) ^ n =m
System.out.println("n="+n+",m="+m);

取某个二进制数的低四位:
例子:取低四位数

0000-0000 0000-0000 0100-1100 1110-0110
                      4    12  14    6
0000-0000 0000-0000 0100-1100 1110-0110     
& 0000-0000 0000-0000 0000-0000 0000-1111
  0000-0000 0000-0000 0000-0000 0000-0110

取倒数5-8位上的数:
此时用0000-0000 0000-0000 0000-0000 1111-0000进行与运算不太合适,因为低四位依旧保留了下来,所以原二进制数应该先右移四位,再进行与运算。如下:

原二进制数: 0000-0000 0000-0000 0100-1100 1110-0110
  右移四位: 0000 0000-0000 0000-0000 0100-1100 1110-0110
再进行与运算:&0000-0000 0000-0000 0000-0000 0000-1111
               0000-0000 0000-0000 0000-0000 0000-1110

//获取60的二进制数111100

System.out.println(Integer.toBinaryString(60)); 

例子:获取60对应的二进制数的低八位,并以十六进制形式显示结果。

0000-0000 0000-0000 0000-0000 0011-1100 =60

60&15=12; //获取低四位
0000-0000 0000-0000 0000-0000 0011-1100
0000-0000 0000-0000 0000-0000 0000-1111
0000-0000 0000-0000 0000-0000 0000-1100 =12   ->C (对应的十六进制)

temp =60>>4   //右移四位,因为这里是正数,所以用>>或>>>都是可以的
temp & 15 =3  //再取右移后的低四位
0000 0000-0000 0000-0000 0000-0000 0011-1100
&0000-0000 0000-0000 0000-0000 0000-1111
 0000-0000 0000-0000 0000-0000 0000-0011 =3

//取得的低位数以十六进制输出

int num = 60;

//获取60的最低4位,通过&15;

num & 15 =12;

//要获取下一组四位,将60右移4位

int temp = 60 >>4;

//对temp的值进行最低四位的获取。

temp & 15 =3;
十六进制数: 0-9 ‘A’   ‘B’  ‘C’  ‘D’  ‘E’  ‘F’
                 65   66   67
 			       10  11  12  13  14  15 
                 12 - 10 = 2 + ‘A’ = (char)67;

代码:

int num = 60;
		int n1 = num & 15;  
		System.out.println((char)(n1-10+'A'));//C
		num = num>> 4;
		int n2 = num & 15;
		System.out.println(n2); //3

故60的后八位数为3C.

要把原有数据移光,正数的话可用运算符>>或>>>,但负数必须要用>>>。因为负数用>>的话,高位一直补1,一直有有效位,那么这个数 永远也移不完,而用>>>,高位补的是0,与1进行与运算会依旧为0,故能移完该数。

在上述代码,int n1 = num & 15;int n2 = num & 15;
中,假如我们没有事先计算n1,n2,那么我们是如何得知n1大于9还是小于9呢,这就用到三元运算符(此处暂且不用if语句来实现试试),其格式如下:
(条件表达式)? 表达式1:表达式2;
如果条件为true,运算后的结果是表达式1;
如果条件为false,运算后的结果是表达式2;

例子:


        int x= 1, y;
		y = (x>1)?100:200;
		System.out.println("y="+y);//200

上述代码再次完善:

int num = 60;
int n1 = num & 15;
System.out.println(n1>9?(char)(n1-10+'A'):n1); //67
num = num>>>4;
int n2 = num & 15;
System.out.println(n2>9?(char)(n2-10+'A'):n2);//3

出现67,而不出现C的原因:未知

为找出原因,进行一下测试:

System.out.println(10>9?'A':3); //A
System.out.println(10>9?4:5);  //4
System.out.println(10>9?(char)65:5); //A
System.out.println(10>9?1+'A':5);//66
System.out.println(10>9?(char)(1+'A'):5);//B
int n=4;
System.out.println(10>9?(char)(n-1+'A'):5);//D

If语句

If else 结构,简写格式: 变量=(条件表达式)?表达式1:表达式2;

三元运算符:
好处:可以简化if else 代码
弊端:因为是一个运算符,所以运算完必须要有一个结果。
If…else 整体只要有一个满足,则整个if …else …语句解结束

仔细区分下语句:

int n = 3;
if(n>1)                             //结果:
	System.out.println("a");         //  a
if(n>2)
	System.out.println("b");         //  b
if(n>3)                 
	System.out.println("c");
else 
	System.out.println("d");         //  d
System.out.println("e");             //  e

需求1:根据用户定义的数值不同,打印对应的星期英文

方法一:
		int num = 1;
		if(num==1)
			System.out.println("Monday");
		if(num==2)
			System.out.println("TSD");
			
方法二:效率比方法一高
		if(num==1)
			System.out.println("Monday");
		else if(num==2)
			System.out.println("TSD");
		else
			System.out.println("nono");

需求:根据用户指定的月份,打印该月份所属的季节
3、4、5春季 6、7、8夏季 9、10、11 秋季 12 、1 、2 冬季

方法一:
		int x = 4;
		if(x==3 || x==4 || x==5)
			System.out.println(x+"春季");
		else if (x==6 || x==7 || x==8)
			System.out.println(x+"夏季");
		else if(x==9 || x==10 || x==11)
			System.out.println(x+"秋季");
		else if(x==12 || x==1|| x==2)
			System.out.println(x+"冬季");
		else
			System.out.println(x+"月份不存在");
			
方法二:
	    if(x>12 || x<1)
	    	System.out.println(x+"月份不存在");
	    else if(x>=3 && x<=5)
	    	System.out.println(x+"春季");
	    else if (x>=6 && x<=8)
			System.out.println(x+"夏季");
		else if(x>=9 && x<=11)
			System.out.println(x+"秋季");
		else 
			System.out.println(x+"冬季");

switch 语句

switch (表达式) (注意下方的常量表达式)
{
case 常量表达式1: 语句 1; break;
case 常量表达式2: 语句2; break;
. . . .
. . . .
case 常量表达式n : 语句 n; break;
default : 语句n+1; //break;
}

当常量表达式所表达的量与其中一个case语句中的常量相符时,就执行此case语句后面的语句,并依次下去执行后面所有case语句中的语句,除非遇到break;语句跳出switch语句为止。若与case语句中的常量都不相符时,直接执行default语句

没有break 语句使程序跳出swicth 语句 则程序将从选中的相应的语句开始,一直执行到右大括号处,若有break 语句,则每执行一条语句,都会跳出该循环,default后面的break可以有也可以没有,因为执行完default后就是跳出switch语句。

Switch中case语句之间以及default与case之间没有顺序要求

多个case可以共用一个表达式,例如:

int x =4;
switch(x)
{
	case 1:
  case 2: x++;
  case 3; x++
  default: 
}

Switch语句使用规则:

只能针对基本数据类型中的整型类型使用switch,这些类型包括int 、char(byte short 不常用)等。对于其他类型必须使用if语句。

Switch()的参数类型不能为实型,case标签必须是常量表达式,且必须唯一,即不允许两个case具有相同的值。

If和switch语句很像,如果判断的具体数值不多,而符合byte short int char这四种类型时,虽然两个语句都可以使用。建议使用switch语句,因为效率稍高。

其他情况:对于区间判断,对结果为boolean类型判断,使用if,if的使用范围更广。

If语句可以判断一个区间,执行效率比较低;
switch 语句执行效率比较快,不可以进行区间判断;
三目运算符 结构清晰不适于嵌套

day03 循环

while 循环的格式

 while (表达式)		
{
	语句A;
    循环体;
}

1、 只要表达式成立,语句就执行,否则跳出循环
2、与for 的相互比较

do …while 循环的格式

do 
{
   循环体语句;
}while (表达式);	//注意:此处有分号
	

1、 一般情况下,用 while 语句和 do …while 语句处理同一问题时,若两者的循环体部分是一样的,他们的结果也是一样的。 但是如果while 后面的表达式一开始就为假(0值),则两种循环的结果是不相同的。

2、do …while 语句中while 后面的分号 ;是必不可少的
3、while:先判断条件,只有条件满足才执行循环体
do…while:先执行循环体,再判断条件,条件满足,再继续执行循环体。总之,do …while:无论条件是否满足,循环体至少执行一次。

for循环格式

for (语句1; 条件语句;循环变量 )
            语句A;	

在这里插入图片描述
详细请看C语言自学笔记

for 和 while可以进行互换。如果需要定义循环增量,用for更为合适。

循环语句的其他特点:
//下列中间语句并不是条件语句,所以出错

int x=1;
for(System.out.println("a");System.out.println("b");System.out.println("c")){
			System.out.println("d");
			x++;
	}

//下面运行结果为adcdc,只要写的表达式合法,就不会出错

for(System.out.println("a"); x<3; System.out.println("c")){
			System.out.println("d");
			x++;
		}

或者如下:

	 int x=1;
	  for(System.out.println("a"); x<3; System.out.println("c"),x++){
			System.out.println("d");		
		}

for中表达式多了用“,”隔开

for(int x =0,y=9; x<3; x++ ){              
			System.out.println("x="+x);             
		}                                     

或者

for(int y=0; y<3; y++){
			
		}

或者:

int y=0;
		for(; y<3; ){
			y++;
		}

无限循环的最简单表现形式

for(; ;){}
while(true){}

习题练习

//获取1~10的和

int sum=0;
for(int i=1; i<=10; i++){
	sum=sum+i;
}
System.out.println("1-10之间的和为:"+sum);
	
//或
int sum1=0,x=1;
while(x<=10)
	{
		sum1+=x;
		x++;
	}
System.out.println("1-10之间的和为:"+sum1);

原理:通过变量记录住每次变化的结果,通过循环的形式,进行累加动作。

//在1-100中,求7的倍数及其倍数的个数
		int count=0;		
		System.out.println("7的倍数有:");


//i也可以从1开始,但是最小公倍数为它本身,建议从7开始
for(int i=7;i<=100;i++){
		if(i%7==0)
		{
			 System.out.println(i);
			 count++;
		 }
	}
System.out.println("7的倍数的个数为"+count);

计数器思想: 通过一个变量记录数据的状态变化,也通过循环完成

结果:
在这里插入图片描述
嵌套循环:

//外循环控制行,内循环控制列
		//矩形
		for(int i=0; i<3; i++){
			for(int j=0; j<4; j++){
				System.out.print("*");				
			}	
			System.out.println();
		}		
		System.out.println();
		
		//正三角形
		for(int i=0; i<5; i++){
			for(int j=0; j<=i; j++){
				System.out.print("*");
			}
			System.out.println();
		}
		System.out.println();
		//倒三角形方法1
		for(int i=0; i<5; i++){
			for(int j=0; j<=4-i; j++){
				System.out.print("*");
			}
			System.out.println();
		}

        //倒三角方法2
		int z1=5;
		for(int i=0; i<5; i++){
			for(int j=0; j<z1; j++){
				System.out.print("*");
			}
			System.out.println();
			z1--;
		}
		
		//倒三角方法3
		int z2=0;
		for(int i=0; i<5; i++){
			for(int j=z2; j<5; j++){
				System.out.print("*");
			}
			System.out.println();
			z2++;
		}
		//倒三角方法4
		for(int i=0; i<5; i++){
			for(int j=i; j<5; j++){
				System.out.print("*");
			}
			System.out.println();		
		}

运行结果:

在这里插入图片描述

三角形:尖朝上,可以改变条件,让条件随着外循环变化
       尖朝下,可以初始化值,让初始化随着外循环变化
//九九乘法表
		for(int i=1; i<10; i++){
			for(int j=1; j<=i; j++){
			System.out.print(j+"*"+i+"="+i*j+"\t");
			}
			System.out.println();
		}

在这里插入图片描述
break语句的格式:

break;

break语句只能用在循环语句和switch语句(开关语句)中
break语句在switch语句中的用法同上,在循环语句体中使用并被执行时,可以使程序终止循环
break出现在循环中,作用是跳出当前内循环语句,执行后面的代码。
在多重循环中 一个break 语句只能向外跳出一层。
break 语句对 if 语句不起作用

//标号只能用于循环上,给循环起名字的一种方式
//循环嵌套,break默认跳出内层循环
		for(int x=0; x<3; x++){
			for(int y=0; y<4; y++){
				System.out.println("x="+x);
				break ;
			}
		}
		System.out.println("----");          
		//利用标号,跳出外层循环
		w:for(int x=0; x<3; x++){
			q:for(int y=0; y<4; y++){
				System.out.println("x="+x);    
				break w;
			}
		}

结果:
在这里插入图片描述

continue语句的格式

continue;

continue语句只能作用在循环语句的循环体中,作用是结束本次循环,即跳过循环体中剩余的语句,转到判断循环条件的位置,直接判断循环条件,决定是否重新开始下一次循环。直接结束本次循环,不影响下一次循环

continue 语句通常与if 语句一起使用,即满足某一条件时便终止该次循环。

/*if条件满足,continue起作用,循环下的语句不再执行,
直接跳转到循环条件x++处,继续执行程序*/
		for(int x=1; x<=10; x++){
			if(x%2==1)
				continue;
			System.out.println("x="+x);
		}
		System.out.println();
	//continue ,跳转到w处的循环条件x++处,继续执行程序
	   w:for(int x=0; x<3; x++){
		   for(int y=0; y<4; y++){
			   System.out.println("x="+x);
			   continue w;
		   }
	   }

在这里插入图片描述
注意:

break 和continue语句作用的范围 break 和continue单独存在时,下面可以有任何语句,因为都执行不到。

语句练习

//等腰三角形
		for(int i=0; i<5; i++){
			for(int j=0; j<4-i; j++)
				System.out.print(" ");			
			for(int x=0; x<=i; x++)
			{
				System.out.print("*"+" ");
			}	
			System.out.println();
		}

结果:
在这里插入图片描述
在这里插入图片描述
函数的格式:

修饰符  返回值类型  函数名(参数类型 形式参数1,参数类型 形式参数2)
{
		执行语句;
       return 返回值;

返回值类型:函数运行后的结果的数据类型。
参数类型:是形式参数的数据类型。
形式参数:是一个变量,用于存储调用函数时传递给函数的实际参数。
实际参数:传递给形式参数的具体数值。
return 用于返回结果和结束函数
返回值:该值会返回给调用者。

对于函数没有具体返回值的情况,返回值类型用关键字void表示,那么该函数中的return语句如果在最后一行可以省略不写,(写了也不算错)。

函数中只能调用函数,不可以在函数内部定义函数
定义函数时,函数的结果对应返回给调用者,交由调用者处理。

public class Demo {
	public static int getResult(int num){ //注意static少了会报错
		return num*2;
	}
	public static void main(String []args){
		System.out.println(getResult(4)); //8
	}public class Demo {
	
	public static void main(String []args){ 
		
		System.out.println(getResult(4));//8
	}
	public static int getResult(int num){
		return num*2;
	}
}

Java和C/C++中函数定义均可写在main函数的前面或后面,但是C/C++中,若函数定义写在main后面时,在main前面必须要写函数声明,不然会报错。

//没有返回值的情况
public class Demo {
	public static void getResult(int num){
		System.out.println(num*2); 
		return;  //结束函数,可写可不写
	}
	public static void main(String []args){
		
		getResult(4);		
	}

函数重载

在同一个类中,允许存在一个以上的同名函数,只要它们的参数个数或者参数类型或不同参数类型排放的顺序不同即可,与返回值类型无关。

//原函数
	public static void show(int x, int y){
		
	}
	//重载,参数个数不同
	public static void show(int x, int y, int z){
		
	}
	//重载,形参类型不同
	public static void show(double x, int y){
		
	}
	public static void show(int x, double y){
		
	}
    public static void show(double x, double y){
		
	}
    //报错与原函数冲突,重载与形参变量名无关
    public static void show(int y, int x){
    	
    }
    //报错与原函数冲突,重载与返回值类型不同无关
    public static int show(int x, int y){
    	return x+y;
}

数组

同一种类型数据的集合,其实数组就是 一个容器。

格式1:
元素类型[] 数组名 = new 元素类型[元素个数或数组长度];
示例:int[] arr = new int[5];int arr[] = new int[5];
		
格式2:
元素类型[] 数组名 = new 元素类型[]{元素,元素……};
int[] arr = new int[]{3,5,1,7};..同上
int[] arr = {3,5,17};..同上
[]可以和数据类型放在一起,也可以和数组名放在一起

内存结构

栈内存:

用于存储局部变量,当数据使用完,所占空间会自动释放

堆内存:
数组和对象,通过new建立的实例都存放在堆内存中。

每一个实体都有内存地址值

实体中的变量都有默认初始化值

实体不再被使用,会在不确定的时间内被垃圾回收器回收 。

数组是单独的数据类型,如int [] x= new int[3];x是数组类型,所占空间为3*4B

凡是new出来的就是实体,在堆里面,堆里面存放的就是实体,实体包括数组和对象。
在这里插入图片描述
x指向(引用)了右边的内存空间
只有引用数据类型才可以用null.如上述数组可以写x=null
int型的数组定义后,默认值为0,
堆内存得实体是用于封装数据的,而堆内存的实体都有默认的值(与数据类型有关)

在这里插入图片描述
两个引用指向了同一个数组,x、y均指向右边的空间,因为x的地址赋值给了y
在这里插入图片描述
每new一次,就多分配一个空间,x、y指向右边不同的堆空间

day04

数组赋值:

//格式1
//int arr[] = new int[2];  声明数组
int[] arr = new int[2];
arr3[0]=1;
arr3[1]=2;
//格式2
//[]中不能有数字,与C/C++不同
int[] arr1 = new int[]{1,2,3,4,5};  
//格式3
int[] arr2 = {1,2,3,4,5};
//格式4
int[] arr3 = new int[5];
//利用循环给数组赋值
for(int i=0, j=1; i<arr3.length; i++,j++){  
			arr3[i]=j;
		}

定义数组常见问题:
1、 java.lang.ArrayIndexOutOfBoundsException

int[] arr = new int[3];
System.out.println(arr[3]);//下标越界

2、java.lang.NullPointerException

int[] arr = new int[3];
arr = null;
System.out.println(arr[1]); //空指针异常

获取数组中的元素

//定义数组并赋值
int[] arr = new int[]{1,2,3};
//方法一:数组元素单个输出
System.out.println("arr[0]="+arr[0]+" arr[1]="+arr[1]+" arr[2]="+arr[2]);
//方法二:利用循环输出数组元素
//数组中有一个属性可以直接获取到数组元素个数.length
//使用方法:数组名.length
for(int i=0; i<arr.length; i++){   
System.out.print("arr["+i+"]="+arr[i]+" ");
}

结果:
在这里插入图片描述

关于数组的习题练习:
1 定义一个功能,实现数组元素的打印

public class Demo {
	public static void printArray(int [] arr){
		for(int i=0; i<arr.length; i++){
			System.out.print("arr["+i+"]="+arr[i]+"\t");
		}
		System.out.println();
	}
	public static void main(String []args){
		int[] arr = new int[]{1,2,3,4};
		printArray(arr);
		printArray(arr);
	}	
}

在这里插入图片描述

2 让数组元素及数组长度均从控制台处输入

import java.util.Scanner;public class Demo {
	public static void main(String []args){
		
		System.out.println("请输入元素个数:");
		Scanner sc = new Scanner(System.in );
		int x = sc.nextInt();	
		//定义数组
		int[] arr = new int[x];
		
		System.out.println("请输入元素值:");
		for(int i=0; i<arr.length; i++){
			arr[i]=sc.nextInt();
		}
		
		//输出数组中的元素
		System.out.println("输出数组元素:");
		for(int i=0; i<arr.length; i++){
			System.out.print("arr["+i+"]="+arr[i]+"\t");
		}	
	}	

在这里插入图片描述

3 求数组中的最大值及最小值

import java.util.Scanner;
public class Demo {
	
	public static void main(String []args){
		int[] arr = new int[5];
		Scanner sc = new Scanner(System.in);
		
		System.out.println("请输入5个整型数据:");
		for(int i=0; i<arr.length; i++){
			arr[i] = sc.nextInt();
		}
		System.out.println(getMax(arr));
/*System.out.println(getMax_2(arr));
System.out.println(getMin(arr));
System.out.println(getMin_2(arr));*/
		
	}	
//获取最大值
//方法一:
	public static int getMax(int[] arr){
		int max = arr[0];//max 用来存储最大值
		for(int i=1; i<arr.length; i++){
			if(max<arr[i]){
				max=arr[i];
			}
		}
		return  max;
	}
}
//方法二
	public static int getMax_2(int[] arr){
		int max = 0;  //数组下标
		for(int i=1; i<arr.length; i++){
			if(arr[max]<arr[i]){
				max=i;
			}
		}
		return  arr[max];
	}
 

在这里插入图片描述

//获取数组的最小值
//方法一:
	public static int getMin(int[] arr){
		int min = arr[0];
		for(int i=1; i<arr.length; i++){
			if(min > arr[i])
				min = arr[i];		
		}
		return min;
	}
	
	//方法二:
	public static int getMin_2(int[] arr){
		int min = 0;
		for(int i=1; i<arr.length; i++){
			if(arr[min] > arr[i])
				min = i;
		}
		return arr[min];
	}

在这里插入图片描述

//获取最大值和最小值的第三种方法
public class Demo {
	
	public static void main(String []args){
		
		int[] arr =new int[]{3,1,4,2,7,5};
		
		getMax_3(arr);
		
		getMin_3(arr);
	}	
	//获取最大值
	public static void getMax_3(int[] arr){
		for(int i=1; i<arr.length; i++){
			if(arr[0]<arr[i]){
			  int temp = arr[0];
			 arr[0] = arr[i];
			 arr[i] = temp;		 
			}				
		}	
		System.out.println("最大值为"+arr[0]);		
	}
	//获取最大值
	public static void getMin_3(int[] arr){
		for(int i=0; i<arr.length; i++){
			if(arr[0]>arr[i])
			{
				int temp;
				temp = arr[0];
				arr[0] = arr[i];
				arr[i] = temp;
			}
		}
		System.out.println("最小值为"+arr[0]);
	}
}
 

在这里插入图片描述

boolean [] arr2 = new boolean [3];
		System.out.println(arr2[1]);  
		//结果: false因 默认为false 

4 选择排序
在这里插入图片描述

public class Demo {
	public static void selectSort(int[] arr){
		for(int i=0; i<arr.length-1; i++){  //控制比较的次数
			for(int j=i+1; j<arr.length; j++){ //外与内逐一比较	
				if(arr[i]>arr[j]){   //从小到大,改成<,就是从大到小
					int temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;
				}
			}
		}
		}
		public static void Print(int[] arr){
			for(int i=0; i<arr.length; i++){
				System.out.print(arr[i]+" ");
			}
			System.out.println();
		}
	public static void main(String []args){
		
		int[] arr =new int[]{3,1,4,2,7,5};
		System.out.println("排序前:");
		Print(arr);
		selectSort( arr);
		System.out.println("排序后:");
		Print(arr);		
	}	
}

在这里插入图片描述

选择排序:内循环结束一次,,最值出现角标位置上

5 冒泡排序
在这里插入图片描述

public class Demo {
	public static void bubbleSort(int[] arr){
		for(int i=0; i<arr.length-1; i++){  //外控制循环次数
			//-x:让每一次比较的元素减少, -1:避免角标越界
			for(int j=0; j<arr.length-i-1; j++){  
				if(arr[j]>arr[j+1]){ //从小到大,改成<,就是从大到小
				int temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;				
			}		
			}
		}
		}
		public static void Print(int[] arr){
			for(int i=0; i<arr.length; i++){
				System.out.print(arr[i]+" ");
			}
			System.out.println();
		}
	public static void main(String []args){
		
		int[] arr =new int[]{3,1,4,2,7,5};
		System.out.println("排序前:");
		Print(arr);
		bubbleSort(arr);
		System.out.println("排序后:");
		Print(arr);		
	}	
}

冒泡排序,两两比较,每次把最大的数放在后面,运行后的结果同选择排序。

6 普通查找

//定义功能,获取key第一次出现在数组中的位置,如果返回是-1,那么代表该key在数组中不存在
	public static int getIndex(int[] arr, int key){
		for(int i=0; i< arr.length; i++){
			if(arr[i]==key)
				return i;
		}
		return -1;
	}
	public static void main(String []args){
		
		int[] arr =new int[]{3,1,4,2,7,5,7};
		int index = getIndex(arr,7);
		System.out.println(index);  //结果:4
	}	
}

7 折半查找
提高查找查找效率,但数组里的数据必须是有序的。

public class Demo {
	//定义功能,获取key第一次出现在数组中的位置,如果返回是-1,那么代表该key在数组中不存在
		
	public static void main(String []args){
		
		int[] arr =new int[]{3,1,4,2,7,5,8};
		bubbleSort(arr);
		int index =halfSearch(arr, 7);
		System.out.println("index="+index);
		int index1 =halfSearch(arr, 10);
		System.out.println("index1="+index1);
	}
	//排序
	public static void bubbleSort(int[] arr){
		for(int i=0; i<arr.length-1; i++){
			for(int j=1+i; j<arr.length-1 ;j++){
				if(arr[i]>arr[j]){
					int temp = arr[i];
					arr[i] = arr[j];
					arr[j] =temp;
				}
			}
		}
	}
// 折半查找 方法一
	public static int halfSearch(int[] arr, int key){
		int min, mid, max;
		min = 0;
		max = arr.length-1;
		mid = (min+max)/2;
		while(arr[mid]!=key){
			if(arr[mid]>key){
				max = mid - 1;
			}
			else{
				min =  mid + 1;
			}
			if(min>max)
				return -1;
			mid = (min+max)/2;
		}
		return mid;
	}
//折半查找 方法二
public static int  halfSearch_2(int[] arr, int key){
		int min=0, max=arr.length-1, mid;
		while(min <=max){
			mid = (max+min)/2;	
			if(key>arr[mid])
				min = mid + 1;
			else if(key<arr[mid])
				max = mid -1;
			else
				return mid;
		}
		return -1; //return mid 就是插入一数据,它所在位的前一个数下标
	}
}

在这里插入图片描述

8 进制转换代码:

十进制 –> 二进制 (适用于正数)

public class Demo {
	public static void main(String []args){
		toBin(6);
	}
	public static void toBin(int num){
		StringBuffer sb = new StringBuffer();	
		while(num>0){
			//System.out.println(num%2);
			sb.append(num%2);  //数据存储在sb中 
			num = num/2;			
		}
		System.out.println(sb.reverse());  //sb里的数据倒着取为:110
	}	
}

十进制 –> 十六进制 (适用于正数)

public class Demo {
	public static void main(String []args){
//111100(看文档之前的内容)
		System.out.println(Integer.toBinaryString(60)); //111100
			toHex(60);
		}
	public  static void toHex(int num){
	StringBuffer sb = new StringBuffer();
		for(int i=0; i<8; i++){
			int temp = num & 15; //取后四位
			if(temp>9) 
			//	System.out.println((char)(temp-10+'A')); 
				sb.append((char)(temp-10+'A'));
			else
			//System.out.println(temp);
				sb.append(temp);
			num  = num >>>4;  //右移四位	
		}
		System.out.println(sb.reverse());   //0000003C
	}
}

查表法:
十进制->十六进制 (适用于正负数)
0 1 2 3 4 5 6 7 8 9 A B C D E F ==十六进制中的元素
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

public class Demo {
	public static void main(String []args){	
		toHex(60);
		}
	public  static void toHex(int num){
	 char[] chs = {'0','1','2','3',
			       '4','5','6','7',
			       '8','9','A','B',
			       'C','D','E','F'};
	 
	 //定义一个临时容器
	 char [] arr = new char[8];
	 
	 for(int i=0; i<8; i++){	 
		 int temp = num &15;
		// System.out.println(chs[temp]);
		 arr[i] = chs[temp];		 
		 num = num >>>4;	 
	 }
	 for(int i=arr.length-1 ; i>=0; i--){
		 System.out.print(arr[i]+" ");  //0 0 0 0 0 0 3 C
	 }
	}	
}

//上述代码,占据多余的空间,想去掉前面的0,可以优化代码
public class Demo {
	public static void main(String []args){	
		toHex(60);
		}
	public  static void toHex(int num){
	 char[] chs = {'0','1','2','3',
			       '4','5','6','7',
			       '8','9','A','B',
			       'C','D','E','F'};
	 
	 //定义一个临时容器
	 char [] arr = new char[8]; //字符数组默认值:'\u0000'
	/* int pos = 0;    
	 
	 while(num!=0){
		int temp = num &15;
		arr[pos++] = chs[temp];
		num = num >>>4;
	 }
	 for(int i=pos-1 ; i>=0; i--){
		 System.out.print(arr[i]+" "); //3C
	 }
	或者 */
	
    int pos = arr.length-1;    	 
	 while(num!=0){
		int temp = num &15;
		arr[pos--] = chs[temp];
		num = num >>>4;
	 }
	 System.out.println("pos="+pos);
	 
	 for(int i=pos+1 ; i<arr.length; i++){
		 System.out.print(arr[i]+" "); //3C
	 }	 
	}	
}

十进制 -> 二进制 (正负数均可以使用)

public class Demo {
	public static void main(String []args){	
		toBin(8);             //toBin(-6);
		}
	public static void toBin(int num){
		//定义二进制表
		char[] chs = {'0','1'};  //对应的下标存储对应的数据
		//定义一个临时存储容器 
		char [] arr = new char[32];
		//定义一个操作数组的指针
		int  pos = arr.length;
		while(num!=0){
			int temp = num & 1;
		 arr[--pos] = chs[temp];
		 num = num >>>1;
		}
		System.out.println("pos="+pos);    //pos=28   //pos=0
		for(int i=pos; i<arr.length; i++){
	System.out.print(arr[i]); //1000 //11111111111111111111111111111010
		}
	}
}

进制转化优化:(将查表法)
二进制和八进制里的元素都包含在十六进制中,所以可以用同一张表。

public class Demo {
	public static void main(String []args){	
		trans(10, 1, 1);   // 二进制:1010
		trans(10, 15, 4);   //十六进制:A
		trans(10, 7, 3);    //八进制:12
		}
	public static void trans(int num, int base, int offset){
		char[] chs = {'0','1','2','3',
			       '4','5','6','7',
			       '8','9','A','B',
			       'C','D','E','F'};
       char[] arr = new char[32];
       int pos = arr.length; 
       while(num!=0){
    	   int temp = num & base;
    	   arr[--pos] = chs[temp];
    	   num = num >>offset;
       }
       
       for(int i=pos;i<arr.length; i++)
       {
    	   System.out.print(arr[i]);
       }
       System.out.println();
	}
}

9 二维数组

数组中的数组
int[][] arr = new int[3][4];

定义了名为arr的二维数组,二维数组中有3个一维数组,每一个一维数组中有4个元素

注:这里的数组和C/C++中二维数组有所不同,C/C++中二维数组
如 int arr[3][4];
3表示的是行,4表示的列,意味着是3行4列的数组,一共有12个一维数组,每个一维数组中有1个元素,在初始化赋值时,前一个[]里的数字可以省略,后一个[]里的数不能省略。
Java里“=”左边的[]不能有数据,[]右边的第一个[]不能为空。

在这里插入图片描述
在这里插入图片描述

 二维数组第二个[]为空时,
 arr[0]=null   arr[1]=null arr[2]=null 地址

在这里插入图片描述

二维数组数据求和:
在这里插入图片描述
一维数组int[]x; 和二维数组int[][]y的判断与练习
在这里插入图片描述

一般不使用int[]x,y[];来定义,很多人都没见过。

day05面向对象

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

匿名对象的用法
1 当对对象的方法只调用一次时,可以用匿名对象来完成,这样写比较简化。
如果对一个对象进行多个成员调用,必须给这个对象起个名字。

class Car {
color = "red";
int num = 4;
void run(){
System.out.println(color+"..."+num);
}
}
正常定义:   Car c = new Car();
	                 	c.num = 5;	
匿名对象     new Car().num = 5;
   1new Car().num = 5; 
   2new Car().color = "blue";
   3new Car().run();    

1、 2、 3都是新new出来的匿名对象,先执行语句1,1执行完后释放堆空间,然后执行2语句,2语句执行完后释放堆空间,再执行3语句,依次类推。
其内存图,如下

在这里插入图片描述
2 可以将匿名对象作为实际参数进行传递

class Car {
	String color = "red";
	int num = 4;
	void run(){
		System.out.println(color+"..."+num);
	}
}
public class Demo {
	public static void main(String []args){	
		show(new Car());     //black...3
		Car q = new Car();   // black...3
		show(q);
		}
	//汽车修配厂,对汽车进行改装,将来的车够改成黑车,三个轮胎
	public static void show(Car c){
		c.color = "black";
		c.num = 3;
		c.run();
	}	
}
   

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

封装
是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
封装原则:
将不需要对外提供的内容都隐藏起来
把属性都隐藏,提供公共方法对其访问。

class Person{
	private int age;		
	public void setAge(int age) {
		if(age>=0 && age<120){
			this.age = age;
			speak();
		}
		else 
			System.out.println("age不合法");		
	}	
	public void speak(){
		System.out.println("age = "+age);
	}	
}
public class Demo {
	public static void main(String []args){	
		Person p = new Person();
		p.setAge(20);
		p.setAge(-20);
		}	
}
 

在这里插入图片描述 在这里插入图片描述

私有仅仅是封装的一种表现形式
之所以对外体工会访问方式,就因为在访问方式中加入逻辑判断等语句。对访问的数据进行操作。提高代码的健壮性。

构造函数
1 构造函数的特点
函数名与类名相同
不用定义返回值类型
不可以写return语句

作用: 给对象进行初始化
注意:
多个构造函数是以重载的形式存在的
系统默认构造函数(以Person类举例):

  Person(){		
}

a 对象一建立就会调用与之对应的构造函数
b 构造函数的作用:可以用于给对象进行初始化。
c 当一个类中没有定义构造函数时,那么系统会默认给该类加入一个空参数的构造函数,凡是有定义的构造函数,就使用定义的构造函数,系统不再给定。
d 构造函数和一般函数在写法上有不同,在运行上也有不同。
构造函数是在对象一建立就运行,给对象初始化;而一般方法是对象调用才执行,是给对象添加对象具备的功能。
一个对象建立,构造函数只运行一次,而一般方法可以被该对象调用多次。
C 什么时候定义构造函数?
当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中。

class Person{
	Person(){
		System.out.println("person");
	}
}
public class Demo {
	public static void main(String []args){	
		Person p = new Person();	  //person
		}	
}

2 构造函数的重载

class Person{
	String name;
	int age;	
	Person(){    //构造函数1
		System.out.println("name = "+name+"  "+"age = "+age);
	}	
	Person(String n){
		name = n;
		System.out.println("name = "+name+"  "+"age = "+age);
	}	
	Person(int a){	
		age = a;
		System.out.println("name = "+name+"  "+"age = "+age);
	}
	Person(String n , int a){
		name = n;
		age = a;
		System.out.println("name = "+name+"  "+"age = "+age);
	}
}
public class Demo {
	public static void main(String []args){	
		Person p = new Person();
		Person p1 = new Person("lisi");
		Person p2 = new Person(20);
		Person p3 = new Person("zhangsan",40);
		}	
}

在这里插入图片描述

若上述的“构造函数1”不存在,使用Person p = new Person();会报错。

3 构造代码块

作用:给对象进行初始化。
对象一建立就运行,而且优先于构造函数执行。它和构造函数的区别就是:构造代码块是给所有的对象进行统一初始化,而构造函数是给对应的对象初始化。
构造代码块中定义的是不同对象共性的初始化内容。

class Person{
	String name;
	int age;
	//构造代码块
	{
		System.out.println("Person code run");
	}
	Person(){
		System.out.println("name = "+name+"  "+"age = "+age);
	}	
	Person(String n , int a){
		name = n;
		 age = a;
		System.out.println("name = "+name+"  "+"age = "+age);
	}	
}
public class Demo {
	public static void main(String []args){	
		Person p = new Person();
		Person p2 = new Person("lisi",20);
		}	
}
 

在这里插入图片描述

this关键字

A 、this 代表当前对象

class Person{
	String name;
	int age;
	
	Person(String name , int age){
		this.name = name;
		this.age =age;		
	}
	public void speak(){
		System.out.println("name = "+name+"  "+"age = "+age);
		//不加this不会报错,但是加上this比较合理 输出的是当前对象的数据
		System.out.println("name = "+this.name+"  "+"age = "+this.age);
		this.show();//this不写也可以
		show();
	}
	public void show(){
		System.out.println(this.name);
	}
}
public class Demo {
	public static void main(String []args){			
		Person p= new Person("lisi",20);
		Person p1 = new Person("zhangsan",10);
		p.speak();
		p1.speak();
		}	
}


this先是代表p,后来又代表p1

在这里插入图片描述

B 、this 的应用:当定义类中功能时,该函数内部要用到调用该函数对象时,这时用this来表示这个对象。但凡本类功能内部使用了本类对象,都用this表示。

class Person{
	String name;
	int age;
	
	Person(String name , int age){
		this.name = name;
		this.age =age;		
	}
	public boolean compare(Person p1){
		/*if(this.age==p1.age)
			return true;
		else 
			return false;*/
		return this.age==p1.age;   //这里的this就相当于main中对象的p
	}
}
public class Demo {
	public static void main(String []args){			
		Person p= new Person("lisi",20);
		Person p1 = new Person("zhangsan",10);
		boolean result  = p.compare( p1);  //p是调用compare函数的对象
		System.out.println(result);   //结果为false
	}	
}

C 、this语句:用于构造函数之间进行互相调用(注意不是同时互相调用,而是单向互相调用)
This语句只能定义在构造函数的第一行。因为初始化要先执行。

class Person{
	String name;
	int age;
	Person(String name){
		this.name = name;
	}
	Person(String name, int age){
		//this.name= name;
		this(name);  //p(name);  new Person(name);
		this.age = age;
	}
	
}
public class Demo {
	public static void main(String []args){	
		Person p = new Person("lisi",20);
	}	
}

注意事项
java中的类
在一个JAVA源文件中可以定义多个类,并且还有import语句和package语句时,要特别注意这些规则:

一个源文件中只能有一个public类。
一个源文件可以有多个非public类。
源文件的名称应该和public类的类名保持一致。

例如:源文件中public类的类名是Employee,那么源文件应该命名为Employee.java。
如果一个类定义在某个包中,那么package语句应该在源文件的首行。
如果源文件包含import语句,那么应该放在package语句和类定义之间。如果没有package语句,那么import语句应该在源文件中最前面。
import语句和package语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。
类有若干种访问级别,并且类也分不同的类型:抽象类和final类等。
除了上面提到的几种类型,Java还有一些特殊的类,如内部类、匿名类。

this关键字总结:
1、通过this关键字可以明确地去访问一个类的成员变量,解决与局部变量名称相冲突问题
在这里插入图片描述
2、通过this关键字调用成员方法
在这里插入图片描述

3 、 通过this关键字调用构造方法

3.1 只能在构造方法中,使用this调用构造方法,不能在成员方法中使用,该语句必须位于第一行,且只能出现一次

class Person{
	public Person(){
		System.out.println("调用无参的构造方法");
	}
	public Person(String name){
		this(); //调用无参的构造方法,必须位于第一行,且只能出现一次
		System.out.println("调用有参的构造方法");
	}	
}
public class Demo {
	public static void main(String []args){			
	Person  p = new Person("lisi");  //实例化对象
	}	
}
 

在这里插入图片描述
3.2 不能在一个类中的两个构造方法中同时使用this互相调用,但允许单向调用。
A、同时互相调用报错
在这里插入图片描述
这种现象不允许出现,相当于死循环。

B、单向调用可行

class Person{
	public Person(){
	this("小芳");
		System.out.println("调用无参的构造方法");
	}
	public Person(String name){	
		System.out.println("调用有参的构造方法");
	}		
}
public class Demo {
	public static void main(String []args){			
	Person  p = new Person();  //实例化对象
	}	
}

在这里插入图片描述
C++在继承方面与java的不同之处

C++中一个派生类允许有多个基类,但java中子类只能有一个父类,但可以多个子类都继承父类

day06

static关键字
static 是一个修饰符,用于修饰成员(成员变量,成员函数)
当成员被静态修饰后,就多了一个调用方式,除了可以被对象调用外,还可以直接被类名调用。格式为:类名.成员变量名
静态使用注意事项:
1、静态方法只能访问静态成员(成员变量或成员方法)
非静态方法既可以访问静态,也可以访问非静态
2、静态方法中不可以定义this.super关键字。
因为静态优先于对象存在,所以静态方法中不可以出现this.

3、
4、静态有利有弊,利:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象中都存储一份。可以直接被类名调用。
弊端:声明周期过长,访问出现局限性(静态虽好,中能访问静态)
class Person{
static String name = “lisi”;
int age;
}
public class Demo {
public static void main(String []args){
Person p = new Person();
System.out.println(p.name); //lisi
System.out.println(Person.name); //lisi
}
}

了解主函数

args是argments的缩写
例子1:

例子2:

例子3:

啥时使用静态
class Person{
String name;
public void show(){
System.out.println(“haha”);
}
}
public class Demo {
public static void main(String []args){
Person p = new Person();
p.show(); //haha
}
}
//上述show()方法没有访问到成员变量,可以定义为static,修改后的代码为:
class Person{
String name;
public static void show(){ //没有访问到成员变量,可以定义为static
System.out.println(“haha”);
}
}
public class Demo {
public static void main(String []args){
//Person p = new Person();
//p.show();
Person.show(); //结果为haha
}
}

静态的使用

TestTool.java文件代码:
package day01;
public class TestTool {
//构造函数私有化,这样就不能创建对象
private TestTool(){}
//获取最大值
public static int getMax(int[] arr){
int max = 0;
for(int i=1; i<arr.length; i++){
if(arr[i]>arr[max]){
max = i;
}
}
return arr[max];
}

public static int getMin(int[] arr){
	int min = 0;
	for(int i=1; i<arr.length; i++){
		if(arr[i]<arr[min]){
			min = i;
		}		
	}
	return arr[min];
}

//选择排序
public static void selectSort(int[] arr){
	 //外循环控制选择次数
	for(int i=0; i<arr.length-1; i++){  
		//内循环控制比较次数 ,每次循环结束,左边少一个数比较
		for(int j =i+1; j<arr.length; j++){
			swap(arr,i,j);
		}
	}		
}

//冒泡排序

public static void bubbleSort(int[] arr){
//外循环控制冒泡次数
for(int i=0; i<arr.length-1; i++){
//内循环控制比较次数,每次 循环结束,右边就少一个数进行比较
for(int j=0; j<arr.length-i-1; j++){
swap(arr,j,j+1);

	  }
  }

}
//交换
public static void swap(int[]arr,int a, int b){
if(arr[a]>arr[b]){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
public static void print(int[] arr){
System.out.print("[");
for(int i=0 ;i<arr.length; i++){
System.out.print(arr[i]+",");
}
System.out.print("]");
System.out.println();
}
}

Demo.java文件代码:
package day01;
public class Demo {
public static void main(String []args){
int[] arr = {45,68,56,34,78,37};
//TestTool t = new TestTool();
int max = TestTool.getMax(arr);
System.out.println("max = "+max);
int min = TestTool.getMin(arr);
System.out.println("min = "+min);

	System.out.println("排序之前:");
	TestTool.print(arr);
	System.out.println("排序之后:");
	TestTool.selectSort(arr);
	TestTool.print(arr);
	TestTool.bubbleSort(arr);
	TestTool.print(arr);		
}	

}

视频77完毕,从此处开始到视频129,只记理论知识,截图,不再手写代码。
day11 多线程

创建线程-继承Thread类

多线程结果:随机性

public class DemoThread extends Thread {
String name ;
public DemoThread(String name){
this.name = name;
}
public void run(){
for(int i=0; i<10; i++){
System.out.println(name+"…run…"+i);
}
}
public static void main(String[] args){
DemoThread t1 = new DemoThread(“one”);
DemoThread t2 = new DemoThread(“two”);
//t1.start();
//t2.start();
t1.run();
t2.run();
for(int j=0; j <10; j++){
System.out.println(“main…run…”+j);
}
}
}

调用start()的结果 调用run()的结果

/*线程都有自己默认的名字(初始化的时候就有了)。

  • Thread-编号,该编号从0开始

  • 用getName()可以直接获取线程的默认名字

  • 也可以用子类构造函数修改默认名称或者使用setName()

  • ststic Thread currentThread() 返回当前正在指执行的线程对象的引用

  • 可以用this.getName()或者 类名.currentThread().getName()获取线程的名字
    */
    public class DemoThread extends Thread {
    DemoThread(String name){
    super(name);
    }
    public void run(){
    for(int i=0; i<10; i++){
    //System.out.println(this.getName()+"…run…"+i);
    System.out.println(DemoThread.currentThread().getName()+"…run…"+i);

    }	  
    

}
public static void main(String[] args){
DemoThread t1 = new DemoThread(“one”);
DemoThread t2 = new DemoThread(“two”);
t1.start();
t2.start();
for(int j=0; j <10; j++){
System.out.println(“main…run…”+j);
}
}
}
线程默认名字 修改线程名字

例:多窗口卖票

上述t1-t4均卖了1-99的票,故让4个人共用100张票,将票定义成静态变量,卖票正确。

但平时不太建议用静态于是取消静态,用一个对象创建线程即可,假如都改成t1会怎么样。

票也卖完了,但是程序报错,如何解决这种问题?因为 已经运行的程序
不需要再次开启。所以使用第二种创建线程的方式。
创建线程-实现Runnable接口

真正创建线程的只有Thread类或thread的子类
这里的tick没有static修饰,但却是t1-t4共用的数, 程序运行成功(未加sleep()),但也有可能出现安全隐患。

多线程同步问题
模拟出错隐患,在上述代码的基础上,进行部分改变得:(以下代码均加了sleep(),所以要解决同步问题)

改进后的代码,synchronized(对象){ //这里的对象不是唯一确定的
需要同步的代码块

1 使用同步代码块

例:银行存钱

如何让函数具有同步性?在上述基础上修改代码

2 使用同步函数

例:寻找同步锁

上述结果均执行的是show()函数,这是因为main可能瞬间将后面两句程序执行了,导致flag=false,所以一直执行的是show()函数,改进方法如下:

再次运行后,出现了错票,这是因为两个锁不是同一个锁

出现了错票:0,再次改进如下:使其使用同一个锁:this

在上述实验中,在tick和函数show()函数加修饰符static后再次运行程序,发现程序再次不安全,出现了0的票。

要想出现正确结果,需要进行下列改进(找到静态的共同锁:类名.class)

总结:同步函数的锁是this,静态同步函数的锁是Class对象
3 多线程-单例设计模式懒汉式的同步问题

解决了创建n多对象的问题,但是多线程访问时,每次都要判断锁,影响效率,所以进行下述代码修改:

A进来判断然后进入锁,B进来被挡在外面,因为A进入锁后s!=null,所以C直接没进来,就不用判断锁了,(双重判断)提高了效率。

记住this锁和静态代码块,以及单例模式中的锁。
4 死锁
package day01;
class Test implements Runnable{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
synchronized(MyLock.locka){
System.out.println(“if locka”);
synchronized(MyLock.lockb){
System.out.println(“if lockb”);
}
}
}
else {
synchronized(MyLock.lockb){
System.out.println(“else lockb”);
synchronized(MyLock.locka){
System.out.println(“else locka”);

			}
		}
		
	}
}

}

class MyLock{
static Object locka = new Object();
static Object lockb = new Object();
}
public class DeadLockTest {
public static void main(String[] args){
Thread t1 = new Thread(new Test(true));
Thread t2= new Thread(new Test(false));
t1.start();
t2.start();
}
}
互相锁住了
day12线程间的通信
理论知识
notify:只会唤醒等待该锁的其中一个线程。
notifyAll:唤醒等待该锁的所有线程。
既然notify会唤醒一个线程,并获取锁,notifyAll会唤醒所有线程并根据算法选取其中一个线程获取锁,那最终结果不都是只有一个线程获取锁吗?那JDK为什么还需要做出来这两个方法呢?这两种同步方法本质上会有什么区别?
这还要从对象内部锁的调度说起。
对象内部锁
其实,每个对象都拥有两个池,分别为锁池(EntrySet)和(WaitSet)等待池。
锁池:假如已经有线程A获取到了锁,这时候又有线程B需要获取这把锁(比如需要调用synchronized修饰的方法或者需要执行synchronized修饰的代码块),由于该锁已经被占用,所以线程B只能等待这把锁,这时候线程B将会进入这把锁的锁池。
等待池:假设线程A获取到锁之后,由于一些条件的不满足(例如生产者消费者模式中生产者获取到锁,然后判断队列为满),此时需要调用对象锁的wait方法,那么线程A将放弃这把锁,并进入这把锁的等待池。

如果有其他线程调用了锁的notify方法,则会根据一定的算法从等待池中选取一个线程,将此线程放入锁池。如果有其他线程调用了锁的notifyAll方法,则会将等待池中所有线程全部放入锁池,并争抢锁。
锁池与等待池的区别:等待池中的线程不能获取锁,而是需要被唤醒进入锁池,才有获取到锁的机会。
问题复现
那么使用notify和notifyAll到底会有什么区别呢?请看下面一组生产者消费者的例子。有两个生产者t1和t2,两个消费者t3和t4,以及一个长度为1的队列。

  1. 初始状态,这四个线程全部进入锁池,等待抢占锁。
  2. t3获取到锁,但是队列为空,故t3进入等待池。
  3. t4获取到锁,但是队列为空,故t4进入等待池。
  4. t1获取到锁,生产,队列满,调用notify,唤醒一个线程。由于此时t3和t4都在等待池中,所以会有一个线程从等待池进入锁池,假设此处t3进入锁池。
  5. 此时,锁池有t2和t3两个线程,假设t2获取到了锁,但是队列满,故t2进入等待池,放弃锁。
  6. 此时,t3获取到锁,消费,notify,由于此时等待池有两个线程t2和t4,假如唤醒的是t2,没问题开始生产,但是若唤醒的是t4,则因队列为空,继续wait。
  7. 此时若t1和t3已经执行结束,t1不在生产,t3不再消费,则t2和t4会一直留在锁池,行程死锁。
    如果此处使用notifyAll,则会把等待池中所有线程唤醒,不会形成所有线程都位于等待池,无法唤醒的情况,也就不会形成死锁,当然了,使用notifyAll方法会更加低效一些。
    如果此处是一个生产者一个消费者的情况,使用notify没有任何问题,且效率更高。
    sleep():是Thread类中的方法,可以设置具体的休眠时间,时间到了自动唤醒,线程不会释放对象锁。
    wait():是Object类中的方法,调用后线程会进入等待池,线程会释放对象锁
    notify():使用notify()方法之后,线程被唤醒,线程会进入对象锁定池准备。
    示例代码解决安全问题
    class Res{
    String name;
    String sex;
    }
    class Input implements Runnable{
    Res r ;
    Input(Res r ){
    this.r = r;
    }
    public void run(){
    int x =0;
    while(true){
    synchronized®{
    if(x==0){
    r.name = “mike”;
    r.sex = “male”;
    }
    else{
    r.name = “小红”;
    r.sex = “女”;
    }
    x =(x+1)%2;
    }
    }
    }
    }
    class Output implements Runnable{
    Res r;
    Output(Res r){
    this.r = r;
    }
    public void run(){
    while(true){
    synchronized®{
    System.out.println(r.name+"…"+r.sex );
    }
    }
    }
    }
    public class InputOutputDemo {
    public static void main(String[] args){
    Res r = new Res();
    Input in = new Input®;
    Output out = new Output®;
    Thread t1 = new Thread(in);
    Thread t2 = new Thread(out);
    t1.start();
    t2.start();
    }
    }

等待唤醒机制
为了满足输入一个数据就立马输出一个数据,需要对上述代码进行改动。

class Res{
String name;
String sex;
boolean flag = false;
}

class Input implements Runnable{
Res r ;
Input(Res r ){
this.r = r;
}
public void run(){
int x =0;
while(true){
synchronized®{
if(r.flag)
try{
r.wait();
}catch(Exception e){}

			if(x==0){
				r.name  = "mike";
				r.sex  = "male";
			}
			else{
				r.name = "小红";
				r.sex = "女";
			}
			x =(x+1)%2;
			r.flag = true;
			try{
				r.notify();
			}catch(Exception e){	}
			
		}							
	}		
}	

}
class Output implements Runnable{
Res r;
Output(Res r){
this.r = r;
}
public void run(){
while(true){
synchronized®{
if(!r.flag)
try{
r.wait();
}catch(Exception e){}
System.out.println(r.name+"…"+r.sex );
r.flag = false;
try{
r.notify();
}catch(Exception e){}

		}			
	}		
}	

}

public class InputOutputDemo {
public static void main(String[] args){
Res r = new Res();
Input in = new Input®;
Output out = new Output®;
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}

总结:

代码优化
再对上面代码进行优化
//flag = false的话,缓冲池为空,就赋值
//flag = true 的话,缓冲池为满,就输出
class Res{
private String name;
private String sex;
private boolean flag = false;

public synchronized void set(String name, String sex){  //赋值
	if(flag)  
		try{this.wait();}catch(Exception e){}
	this.name = name;
	this.sex  = sex;
	flag = true;	
	this.notify();		
}

public synchronized void out(){  //输出
	if(!flag)
		try{this.wait();}catch(Exception e){}
	System.out.println(name+"..."+sex);
	flag = false;
	this.notify();		
}

}

class Input implements Runnable{
private Res r ;
Input(Res r ){
this.r = r;
}
public void run(){
int x =0;
while(true){
if(x==0)
r.set(“mike”, “male”);
else
r.set(“小红”, “女”);
x =(x+1)%2;
}
}
}
class Output implements Runnable{
private Res r;
Output(Res r){
this.r = r;
}
public void run(){
while(true){
r.out();
}
}
}
public class InputOutputDemo {
public static void main(String[] args){
Res r = new Res();
new Thread(new Input®).start();
new Thread(new Output®).start();
}
}

生产者消费者
package day02;
class Resource{
private String name;
private int count =1;
private boolean flag = false;

 public synchronized void set(String name){
	 if(flag)
		 try{
			 wait();
		 }
	   catch(Exception e){ }
	 this.name = name+"..."+count++;
	 System.out.println(Thread.currentThread().getName()+"...生产者"+this.name);
	 flag = true;
	 this.notify();
 }
 public synchronized void  out(){
	 if(!flag)
		 try{
			 this.wait();
		 }
	 catch(Exception e){ }
		System.out.println(Thread.currentThread().getName()+".......消费者"+this.name);
		flag = false;
		this.notify();		
	}
}

class Producer implements Runnable{
	private Resource res;
	Producer(Resource res){
		this.res  = res;
	}
	public void run(){
		while(true){
			res.set("+商品+");
		}
	}
}

class Consumer implements Runnable{
 private 	Resource res;
	Consumer(Resource res){
		this.res  = res;
	}
	public void run(){
		while(true){
			res.out();
		}
	}
}

public class ProducerConsumer {

public static void main(String[] args) {
	Resource res = new Resource();
	Producer p = new Producer(res);
	Consumer c= new Consumer(res);
	
	Thread t1 = new Thread(p);
	Thread t2 = new Thread(c);
	t1.start();
	t2.start();		
}

}

假设多个生产者和消费者的线程同时运行,发现出现下列错误:同一个商品被消费了两次,原因是因为唤醒后,有一个线程没有判断标记

要解决上述问题需要对上述程序进行下列修改(利用循环while):

上述程序虽然避免了线程跳过判断标记,但是程序运行会锁死,锁死的原因是t1唤醒的是本方的t2,而不是t3/t4,导致全部线程等待,故再进行下面修改:
将this.notify()换成this.notifyAll()来唤醒所有程序

JDK1.5以后不再使用synchronized和 wait()、notity()、notityAll(),而是用lock和condition代替了。
生产者消费者JDK5.0升级版

将上述代码用1.5版本替换
package day02;
import java.util.concurrent.locks.*;
class Resource{
private String name;
private int count =1;
private boolean flag = false;
private Lock lock = new ReentrantLock(); //向上转型,右边的是lock的实现类
private Condition condition = lock.newCondition();

 public void set(String name) throws InterruptedException{
	lock.lock();  //获取锁
	try{
		 while(flag)
	         condition.await();  //等待
			 this.name = name+"..."+count++;
			 System.out.println(Thread.currentThread().getName()+"...生产者"+this.name);		 
			 flag = true;
			condition.signalAll();  //唤醒所有等待进程
	}
	finally{
		lock.unlock(); //释放锁
	}
	
 }
 
 public void  out() throws InterruptedException{
	 lock.lock();
	 try{
		 while(!flag)
			 condition.await();			 
			System.out.println(Thread.currentThread().getName()+".......消费者"+this.name);
			flag = false;
			condition.signalAll();	//唤醒所有线程		
	 }
	 finally{
		 lock.unlock();		
	 }
 }

}

class Producer implements Runnable{
	private Resource res;
	Producer(Resource res){
		this.res  = res;
	}
	public void run(){
		while(true){
			try{
				res.set("+商品+");
			}catch (Exception e){	}
		}
			
		}		
}

class Consumer implements Runnable{
 private 	Resource res;
	Consumer(Resource res){
		this.res  = res;
	}
	public void run(){
		while(true){
			try{
				res.out();
			}catch(Exception e){}				
		}
	}
}

public class ProducerConsumer {

public static void main(String[] args) {
	Resource res = new Resource();
	Producer p = new Producer(res);
	Consumer c= new Consumer(res);
	
	Thread t1 = new Thread(p);
	Thread t2 = new Thread(c);
	Thread t3 = new Thread(p);
	Thread t4 = new Thread(c);
	t1.start();
	t2.start();
	t3.start();
	t4.start();	
}

}

如果上述代码不使用condition.signalAll(),而是使用condition.signal()来代替condition.signalAll(),则需要对上述代码的下面部分进行修改。

总结:

释放锁的操作一定要执行

停止线程

package day02;

class StopThread implements Runnable{
private boolean flag = true;
public void run(){
while(flag){
System.out.println(Thread.currentThread().getName()+"…run");
}
}

public void changeFlag(){
	flag = false;
}

}
public class StopThreadDemo {
public static void main(String[] args){
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while(true){
if(num++==60){
st.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"…"+num);
}
}
}

上述代码正确无误,假设对上述代码进行下面改动,改成特殊情况:

运行结果:此时线程没有消耗资源,线程未执行完就停在了那里

对上述特殊情况寻找解决方案:使用interrupt()方法,部分代码修改如下:

运行结果:运行一次后,又回到等待中(中断状态,线程并没有顺利执行完):

想要线程顺利运行完,在上述修改的基础上,还要进行如下修改后,结果显示线程顺利结束。

守护线程

在上述代码的基础上再次进行修改

Join方法

新例子:

此处主线程执行权让给t1,并处于阻塞状态,等t1执行结束后,主线程又获取了执行权,此时只有t2和主线程互相竞争cpu资源。若将join()方法放在t2后(如下图),主线程是否获取执行权与t2线程是否结束无关。

优先级&yield方法

默认优先级是5,优先级一共有10级,即1-10

上述代码屏蔽掉join()方法后,在进行下图修改

设置好优先级依旧要抢资源,只是每个线程出现的频率不一样,于是就想到了yield方法。

重点:关于线程的例子:
使用了匿名对象

day13 String类

String s1 = “abc”;
String s2 = new String(“abc”);
String s3 = “abc”;
System.out.println(s1s2); //false
System.out.println(s1
s3);//true
//"abc"已经开辟空间了,所以对于s3不再重新开辟空间
String常见功能-获取和判断

1 获取

2 判断

例子:

3 转换
Java里字符串和

4 替换

例子1

注意:

例子2

5 切割

分割符不唯一,例子中的分割符为“,”

6 子串

注意:

7 转换、去空格、比较

8 字符数组和字符串的区别
Java里的字符数组和字符串之间的关系与C/C++里的字符串和字符数组之间的关系有很大的区别,这里只说java,C/C++见C语言自学文档。
public class String01 {
public static void main(String[] args){
String s = “abc”;
String s1 = new String(“abc”);
String s2 = “abc”;
char[] arr = {‘a’,‘b’,‘c’};
String s4 = new String(arr);

System.out.println(s==s1);  // false 内容相同,但所在空间不同
System.out.println(s.equals(s1)); // true 只比较内容

System.out.println(s==s2);// true "abc"空间已存在,s2指向已经存在的"abc",不再重新开辟空间
System.out.println(s.equals(s2));// true 只比较内容
	
System.out.println(s.equals(arr));// false 一个是字符串,一个是字符数组,

System.out.println(s==s4);// false  内容相同,但所在空间不同
System.out.println(s.equals(s4));// true 只比较内容

}

}
9 练习1
(练习视频未看,直接跳过)

StringBuffer

1 添加

2 删除

3 获取

4 修改(更新)

5 反转

包含头,不包含尾

StringBuilder
JDK1.5版本之后出现了StringBuilder
StringBuffer是线程同步
StringBuilder 是线程不同步 提高效率

基本数据类型对象包装类

基本数据类型对象包装类新特性
特性1:

Integer类型的数据为null时不能进行运算,不然会报错,如下图:

:特性2:

day14集合框架

共性方法

交集

迭代器

或者

List集合共性方法

List集合下标都是从0开始,从it.next()开始是因为它表示的是指针后方是否有元素,而不是元素后方是否还有元素。
ListIterator 列表迭代器

在上述代码的基础上进行修改:出现并发访问

使用迭代器里的remove()函数,进行删除操作:

因为Iterator只能进行三个操作,有局限性,所以使用ListIterator:

再在上述代码的基础上进行修改:只有list集合具备在迭代器遍历中的增、删、改、查,应为list是有序的,有下标

List集合具备对象的特点

Vector中的枚举

LinkedList

练习
练习1 对LinkedList方法的封装
package jihe;
import java.util.*;
class DuiLie{
private LinkedList link;

DuiLie(){
	 link = new LinkedList(); 
}			
public void myAdd(Object obc){
	link.addFirst(obc);
}
public Object  myGet(){
 return link.removeLast();
}
public boolean isNULL(){
	return link.isEmpty();
}

}
public class JiHe {

public static void main(String[] args){
	DuiLie que = new DuiLie();
	que.myAdd("java01");
	que.myAdd("java02");
	que.myAdd("java03");
	que.myAdd("java04");		
	while(!que.isNULL())
		System.out.println(que.myGet());			
}

}

练习2 去除ArrayList集合中的重复元素

运行结果:

若使用迭代器时,写了两遍it.next(),程序运行就有可能出错,如下:在上述代码基础上修改代码:

在迭代循环中next调用一次,就要hashnext判断一次。
练习3 将自定义对象作为元素存到ArrayList集合中,并除去重复元素。
package collection;

import java.util.*;

class Person{
private String name;
private int age;
Person(String name,int age){
this.name = name;
this.age =age;
}
public boolean equals(Object obj){ //自动被调用
if(!(obj instanceof Person)) //是Person类型就转Person类型,否则就返回false
return false;
Person p =(Person)obj;
System.out.println(this.name +"…"+p.name);
return this.name.equals(p.name) && this.age ==p.age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}

public class ArrayList1 {
public static void sop(Object obj){
System.out.println(obj);
}

public static ArrayList singleElement(ArrayList arrayList){
	ArrayList newList = new ArrayList();//创建新的列表存储不重复的数据
	Iterator  it = arrayList.iterator(); 
	while(it.hasNext()){
		Object obj = it.next();  
		if(!newList.contains(obj))  //判断新列表中是否包含obj元素
			newList.add(obj);
	}
	return newList;	
}
public static void main(String[] args){
	ArrayList al = new ArrayList();
	al.add(new Person("lili1",10))	;//al.add(Object obj);Object obj = new Person("lili1",10)
	al.add(new Person("lili1",10))	;
	al.add(new Person("lili2",11))	;
	al.add(new Person("lili3",12))	;
	al.add(new Person("lili3",12))	;
	al.add(new Person("lili4",13))	;
	al =  singleElement(al); //去重后的结果重新赋值给al
	Iterator it = al.iterator();
	while(it.hasNext()){
		Person p =(Person)it.next();
		sop("姓名:"+p.getName()+"  年龄:"+p.getAge());//不能连写两个next()
	}
}	

}
注释掉Person类中的equals()函数后再进行如下操作:

取消掉equals函数的注释之后,在运行程序:

Contains和remove用的都是equals.,arraylist和linkedlist依赖的都是equals.
HashSet
无顺序:

唯一性:

1 HashSet存储自定义对象
将练习三里的代码里的集合换成了hashSet,并重新定义了hashCode()函数。

按照条件设置哈希值:

尽量保证哈希值的唯一性:

2 HashSet判断和删除的依据

day05集合框架
TreeSet

TreeSet存储自定义对象

继承接口:

在Person类中重写方法:

先装三个对象,运行结果:

再在上述代码中添加一行,查看如何比较的:

接下来再次修改:

对比:

但是如果添加的Person的年龄相同,人名不同时,运行发现少一条记录:于是依旧要对代码进行修改:

测试:

总结:

发布了20 篇原创文章 · 获赞 2 · 访问量 3826

猜你喜欢

转载自blog.csdn.net/qq_43554951/article/details/105040927