Java基础

Day01:

1.java的组成部分:

  • JDK:JAVA程序开发工具包;(默认安装在"C:\Program Files\Java");

  • JRE:JAVA程序运行环境;

  • JVM:JAVA虚拟机

在这里插入图片描述

2.配置环境变量:

JAVE_HOME:JDK的根目录

PATH:系统中可执行文件的路径:%JAVA_HOME%\bin

设置环境变量,我们可以只要设置PAYH就行,把双百分号的JAVA_HOME补充为路径就行,那为啥要配置JAVA_HOME?

这样方便jdk版本的切换,后期的维护,也是一种统一的标准.

ps:

用户环境变量:针对特定用户。

系统环境变量:所有用户。

如果只有一个用户,两个环境变量都可以配置一个,区别不大.

本人从系统环境变量进行配置:

step1:

先把刚刚下载的JDK的安装路径(“C:\Program Files\Java”)复制下来:

step2.:

右键单击我的电脑,选择属性:
在这里插入图片描述

step3:进入环境变量配置
在这里插入图片描述
点击新建,变量名填写JAVA_HOME,变量值为JDK的安装目录.
在这里插入图片描述
双击系统变量中的path,点击添加,内容为JDK安装路径下的bin路径:C:\Program Files\Java\jdk1.8.0_202\bin,将该项上移最上面,以免被别的覆盖,然后确定.(PATH:是系统中可执行文件的目录。是在bin目录下)
在这里插入图片描述
验证安装结果:

win+r输入cmd后,输入java -version:
在这里插入图片描述
在这里插入图片描述

环境变量配置完成!

注意:命令行窗口的环境变量是打开时的内容,如果修改了环境变量需要重新开启命令行窗口。

3.开发工具:

IDEA,eclipse.(新手推荐eclipse);

4.文件的编译过程:

Java[源]文件—(javac指令:javac 文件名.后缀名)–>.class结尾的字节码文件—(java指令:java 类名)—JVM-------->系统指令,运行程序(Windows,Linux)
在这里插入图片描述

5.JAVA注释:

  • 单行注释://
  • 多行注释:/**/
  • 文档注释:/** (一般用于补充说明类和方法的注释)

6.写第一个JAVA程序,控制台打印一个数:

public calss Easy{
    //main方法  程序的主方法 java程序的入口
    public static void main(String []args){
        //实例化Scanner的一个对象(scan);
        //System.in是输入流  
        Scanner scan=new Scanner(System.in);
        //声明一个int类型的变量(num)保存写入的整数
        int num=scan.nextLine();
        //输出整数num;
        System.out.println(num)
    }
}

Helloworld程序:(整个过程)

public static void main(String [] args){}主方法 java程序入口 java程序就从这个方法开始执行.

javac 文件名 ------>编译执行 将java源文件编译成.clsaa文件

java 主类名 执行命令 JVM会加载这个类,并执行main()方法

输入输出

输出:

print();

System.out.println(“”)//可以不传参数,也可以传任何类型的参数

​ System.out.print()

printf();

System.out.printf("你好"); //直接输出内容  你好

System.out.println();//换行,因为printf()没有换行,是在同一行显示内容

//占位符:%s
System.out.printf("你好啊%s","张三");  //你好啊张三

System.out.printf("%s你好啊%s","张三","李四");//张三你好啊李四


Scanner sacn =new Scanner(System.in)

  1. 必须关闭Scanner

  2. 遗留回车符非问题:next()

    1. 不用;

    2. 吃掉回车符(nextLine());

7.变量

变量的定义:

JAVA是一种强数据类型语言

  • 变量声明: 类型+变量名:
  • 声明并赋值:
  • 变量名称在同一作用域(大括号)不可重复.

在程序运行阶段,可以发生变化(重新赋值)的量 ------------------------常量相反.

  • 注意:Java是大小写敏感的语言

  • 作用:在程序中用来记录数据的量(名字);

  • 标识符(名字)命名规范: [变量名,常量,方法,类,包,参数名,文件,Html自定属性]

    ​ 1.标识符只能包含字母, 数字 , _, $;(写中文不报错,但是不允许);

      1. 标识符首字母不能是数字;
      2. 标识符不能是关键字;
    

命名规范:

1.类名首字母大写;

2.变量名首字母一般小写,使用驼峰命名法(单词的首字母大写,其余小写): maxNum,MyClass;

  1. 包名要全部小写,写一个包尽量使用一个单词
  2. 所有标识符尽量做到见名知意.

8.八个基本数据类型:

存储数据的基本单位:

一个字节等于8bit,就相当于8个二进制位:2^8;

整数类型:

​ byte, short ,int, long
​ //byte 1个字节 -2^7 ~ 2^8-1 -128~127
​ //short 2个字节 -2^15 ~ 2^15-1
​ //int 4个字节 -2^31 ~ 2^31-1
​ //long 8个字节 -2^63 ~ 2^63-1
​ //使用 不同的数据类型,记录不同的数据范围,节省空间

整数数据类型默认是int,整数数据类型的变量可以直接赋值相对范围的数值

byte b=127;
int a=10;
short s=10;
long l=10;

注意:JAVA是一种强数据类型语言(数据类型之间的界限是十分明确的)

在基本数据类型中,取值范围大的类型可以存储(赋值)取值类型小的类型

short范围>byte类型范围,就可以将byte类型的变量赋给short类型(向上转换:隐式转换:在基本数据类型中,取值范围小的类型可以直接转换为取值范围大的类型):

a=b;
s=b;
l=b;
s=a;
l=a;
l=s;
//都是向上转换,隐式转换.

强制类型转换:在基本数据类型中,取值范围小的类型可以直接转换为取值范围大的类型.

b=(byte)l;
a=(int)l;
s=(short)l;
//long l向下进行强制类型转换

在基本数据类型中,强制类型转换就是一刀切(只保留有效位[二进制]):

b=128;//short
a=(byte)b;//byte
System.out.println(b);//128
System.out.println(a);//-128

整数类型运算结果一定是整数类型,默认是int,如果运算结果>int范围,则为大的范围,小的范围(byte)相加,是Int型

		byte a=10;
		byte b=20;
//		a=a+b;//报错.因为a是一个byte类型,他们的结果是int型的,所有会报错:不报错的话:a=(byte)(a+b);强转:int-->byte
		
		int c=10;
		long d=20;
		c=c+d;//报错:这样就是long类型的结果.

整数类型的默认值为0

//进制
		//java程序里面默认十进制
		int num=12;
		//二进制
		num=0b101;
		//八进制
		num=076;
		//十六进制0-9 A B C D E F
		num=0xAF;
		
		//加运算
		num=12+23;
		//减运算
		num=12-23;
		//乘运算:
		num=12*23;
		//除运算
		num=12/23;
		//取余运算
		num=12%5;//2
//		num=-12%5;  //-2看主题,取余的数是正数还是确定结果的正数还是负数,0就是0

浮点型(小数):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • float 4个字节 单精度浮点型

  • float比int 范围大

  • float m=40;    这里的40是一个整数,float的字节是4,int类型的字节也是四个,范围大的数据类型可以接受范围小的数据类型,因为float范围是比int的范围大,所有范围小的可以直接赋值给float这个变量m.并不会报错!
    
    
    float g=40.0f;  浮点型的默认值是double,所以声明float的方式就两种强转和末尾加1
        			g变量后的40.0不加f,默认的是double,所有会报错!
    
  • double 8个字节 双精度浮点型 默认类型 程序中的小数默认都是double

  • //程序中默认的是double
    //float类型赋值两种方式:
    	//数值后+f标识是float类型
    	float f=12.8f;
    	//强转
    	float f1=(float)12.8;
    
  • double比long范围大

  • 浮点型不能精确的表示一个数值,浮点型不能用于表示金额,价格,税率等和金钱有关的内容 推荐使用BigDecimal

  • 浮点型的默认值是0.0. float就是0.0f.

布尔类型(判断类型):

  • boolean false/true 默认是false
  • boolean类型不能和任何其他类型转换
  • 1个字节/4个字节

字符型:

char 用来表示一个字符.

java使用unicode编码,用两个字节表示一个字符,我们一般使用UTF-8字符集.

unicode编码可以使用十六机制表示

可以直接使用数组表示字符

char类型范围: [\u0000,\uFFFF] 0-65535

//char只能表示一个字符
		char c='a';
		//用十六进制编码表示
		c='\u0000';//char类型的默认值
		c='\uFFFF';//char类型最大值 65535
		c='\u0041';//0041是十六进制的65   65对应的编码是A
		System.out.println(c);//A
		
		//0-65535
		char cc=12;
		cc='\u000c';
		
		//报错
//		cc=65536;
		
		//整数型可以赋值为char
		int n='A';
		byte n1='A';
		short n2='A';
		long n3='A';
		
		//char转为整数型
		char o=50;
		byte o1=(byte)o;
		short o2=(short)o;
		int o3=(int)o;
		long o4=(long)o;

9.转义符:

		//转义符\
		c='\'';//单引号
		c='\"';//双引号
		c='\n';//换行符
		c='\r';//回车符
		c='\t';//制表符
		c='\\';//第一个\是转义,第二个反斜线是符号\;

Day02:

1.运算符号:

加减乘除取余:

整数和整数运算一定是一个整数!

int a=12;
		int b=13;
		
		//两数之和:
		int sum=a+b;
		System.out.println("两数之和是:"+sum);
		
		
		//两数之差
		int result=a-b;
		System.out.println("两数之差是:"+result);
		
		//;两数之积
		result=a*b;
		System.out.println("两数之积是:"+result);
		
		//两数之商
		result=a/b;
		System.out.println("两数之商:"+result);
		
		//两数取余
		result=a%b;   //a%b的结果的正度是由a的正负确定的
		System.out.println("两数的余数是:"+result);
		//可以对负数进行取余
		
		
		//= 赋值运算  优先级最低
		
		result=20%3;
		System.out.println("-20取余3:"+result);
		
		result=-20%3;
		System.out.println("20取余3:"+result);

赋值运算:

//赋值运算:
		//= += -= /+= %=
		a=12;
		//在a基础上+2
		a+=2;
		a=a+2;
		
		byte d=12;
		d+=2;//这是一步运算
		//报错
//		d=d+2;//这是两步,先算a+2,然后把结果赋值给d;

一元运算:

//()优先级最高
		//一元运算
		//++ --
		a=12;
		//对a的值加1
		a++;  ++a;
		//对a的值减1
		a--;  --a;
		
		a=a++;
		a=++a;
		
		//a++
		//对a的值加1
		//对表达式的取值
		//后++,先(a的值)赋值(赋给表达式)后加1
		a=12;
		b=a++;
		//a=13; b=12;

比较运算符:

结果是boolean的值

< = > >= <= !=

int a=12;
		int b=23;
		
		boolean r =a>b; //false
		r=a<b;//true;
		r=a<=b;//false
		r=a>= b;//false
		r=a==b;//基本数据类型中,==比较的值是否相等  3=3.0
		等于是比较的值是否相等!!!,别看类型.
		double c=12.0;
		r=a==c;
		System.out.println(r);//true

逻辑运算符:

结果也是boolean值

  • &&: (并且) A&&B 只有当A和B都为true,表达式的结果才为True,有一个false,整个表达式的结果即为false

  • int price=3500;
    		
    		boolean isBuy=price >=2500 && price<=4000;
    		
    		if(isBuy) {
    			System.out.println("购买");
    		}else {
    			System.out.println("需要申请资金");
    		}
    
  • ||: (或者) 当A和B只有有一个为true,则整个表达式结果为true,只有A和B都为false,整个表达式的结果才为false

  • if(isBuy || price<=0) {
    			System.out.println("成交!");
    		}else {
    			System.out.println("不要了");
    		}
    
  • !:(翻转结果) 原来为true,翻转之后为false:

短路现象:

A&&B 如果A为false,整个表达式的结果就可以判断为false,B不需要做运算了,B就短路了.

int a=2;
		boolean r=a++>0&&a++<0;
		System.out.println(r);//false
		System.out.println(a);//4
		
		boolean m=a++<0&&a++<0;//短路
		System.out.println(m);//false
		System.out.println(a);//3

A||B 如果A为true,整个表达式的结果就可以判断为true,B不需要做运算了,B就短路了.

a=12;
		r=a++==12||++a==12; //短路
		System.out.println(r);//true
		System.out.println(a);//13

位运算符:

凡是位运算符,都是把值先转换成二进制,再进行后续的处理
(因为位运算是针对二进制数进行的一种运算,对于十进制这些数值的位运算来说,会先将其转为二进制,再对其进行位运算,之后将运算结果再转为十进制。)****

二进制运算是最快的运算方式: 8除以2最快的方法方式 8>>1

  • 移位运算符:>>(向左移位) >>> <<(向右移位)

  • 按位运算符:& (与) | (或) ^(异或) ~(取反)

  • **(>>)😗*右移 (除以2)<<:二进制数据整体右移(各二进位全部右移若干位,对无符号数,高位补符号位

  •  int c=20;	//00000000 00000000 00000000 00010100
    
            		int d=c>>1; //00000000 00000000 00000000 00001010 /=10
    
            		int e=c>>2;	//00000000 00000000 00000000 00000101 /=5
    
            		int f=c>>5; //00000000 00000000 00000000 00000000 /=0(移完了)
    
            		int g=c>>32;//00000000 00000000 00000000 00010100 /=20(32的倍数又移回来了)
    
            		System.out.println("c=20右移一位:"+d); 
    
            		System.out.println("c=20右移两位:"+e); 
    
            		System.out.println("c=20右移五位:"+f); 
    
            		System.out.println("c=20右移32位:"+g); 
    
  • **(<<)😗*左移 <<:二进制数据值整体左移(各二进位全部左移若干位,高位丢弃,低位补0)

  • //左移 <<:二进制数据值整体左移(各二进位全部左移若干位,高位丢弃,低位补0)
    		int i=30; 		//00000000 00000000 000000000 00011110 
    		int i1=i<<1;	//00000000 00000000 000000000 00111100 //=60
    		System.out.println("30的向左移动一位:"+i1);  
    
  • //>>和>>>的区别:

  • a=-1;   //111111111111111111111111|1
    		b=a>>1;//-1    //补符号位  符号位是0还是1,我们就补相应的几位0或1
    		System.out.println(b);
    		b=a>>>1;//2147483647  //补0
    		//111111111111111111111111111111111111
    		//011111111111111111111111111111111111|1
    		System.out.println(b);
    		System.out.println(Integer.MAX_VALUE);//2147483647
    
  • 负数的二进制如何表示:

  • 1.死记:-1的二进制全部为1:  111111111111111111111111111111111111111111111...-2就为:111111111111111111111111111111110;
    -3;111111111111111111111111111111101;
    
    2.1先写出正数的二进制:
    1:000000000000000000000000000000000000001;
    
    2.2取反码加1
      1111111111111111111111111111111111111110 +1=...111;  (这就得到了-1的二进制)
    
    -2:
    先写出2的二进制:
    		000000010;
    取反加1:
    		111111101 +1=111111110;
    
    -3:先写出3的二进制:
    		000000011;
    取反加1:
    		111111100 +1=111111101
    
  • (&):按位与&:二进制数据按位与操作(都为1才为1)

  • int a=6; //二进制:00000000 00000000 00000000 00000110
    		int b=11;//二进制:00000000 00000000 00000000 00001011
    	 //				     00000010//2
    		System.out.println(a&b);//2
    
  • (|):按位或|:二进制数据按位或操作(有1则为1)

  • int a=6;	//00000000 00000000 00000000 00000110
    int b=11	//00000000 00000000 00000000 00001011
    			//00000000 00000000 00000000 00000000 00001111  =15
    System.out.println(a|b);//15
    
  • ():按位异或:二进制数据按位异或操作(相同为假0、不同为真1)

  • int a=6;	//00000000 00000000 00000000 00000110
    int b=11	//00000000 00000000 00000000 00001011
        		//00000000 00000000 00000000 00001101  =13
    System.out.println(a^b);//13
    
  • (~)取反:

  • int a=2;
    00000000 00000000 00000000 00000010
    11111111 11111111 11111111 11111101 =-3
    System.out.println(~a); 
    
  • //&& 与 &的区别:

  • boolean r=true&&false;
    r=true&false;//0 false
    //两者都可以进行逻辑运算,&&有短路现象,&没有短路现象

**三元运算符 **

** 取值**

//A?B:C
		//A是一个boolean类型的结果   如果A为true,此表达式就取B的值,A为false,表达式就取C的值
		a=true?12:23;
		System.out.println(a);//12
		
		a=12>0?12:13;
		System.out.println(a);//12
		System.out.println(12 > 0? 12:12.0);//12.0

运算符优先顺序:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.分支判断:

1.if-else:

注意:

  • 尽量避免重复条件判断(顺序先写范围大的部分)
  • 就算只有一行代码也要写大括号
//定义一个年龄变量.超过60岁的,打印领取100元
		//超过80岁的 领取500元
		//超过90岁的,领取1000元
		//超过100岁的,领取3500元
		//其他年龄不领
		Scanner scan=new Scanner(System.in);
		int age=scan.nextInt();
		if(age>=100) {
			System.out.println("领取3500元");
		}else if(age>=90) {
			System.out.println("领取1000元");
		}else if(age>=80) {
			System.out.println("领取500元");
		}else if(age>=60){
			System.out.println("领取100元");
		}else {
			System.out.println("不领");
		}

2.switch-case:

匹配某个变量的值,如果某一个case下被匹配到,那么就从这一行开始运行直到代码结束或者运行到break.

int sex=0;  
		switch(sex) {
		default://所有的case项都不匹配才执行default;
			System.out.println("外星人");
			break;
		case 0:
			System.out.println("男性");
			break;
		case 1:
			System.out.println("女性");
			break;
		
		}
  • 代码顺序可以错乱,执行顺序不变
  • sex=3,执行顺序还是先执行case,发现没有匹配到,就执行default.
  • switch可以匹配什么值:byte short int char String(字符串),枚举(enum)

3.for循环:

for(声明计数器;继续循环的条件;计数器的递增){循环代码块}

能够明确循环次数的时候使用:

//打印1000以内的偶数
		for(int i=0;i<1000;i++) {
			if(i%2==0) {
				System.out.println("1000以内的偶数:"+i);
			}
		}

4.while()循环:

不能够明确循环次数的时候使用:

//while()
		//打印1-100
		int i=1;
		//while的语法
		while(i<100) {
			System.out.println(i);
			i++;
		}
		
		//打印8765的值
		int num=8765;
		int a;//在循环体外声明,减少内存和时间
		while(num>0) {
			a=num%10;
			num=num/10;
			System.out.println(a);
		}
	猫抓老鼠:			
				/tom  最多吃200斤的老鼠
				//随机出现一只老鼠2-5斤
				//tom一共吃了多少只老鼠
		int count=0;
		int mounce;
		int weigh=0;
		while(true) {
			mounce=(int)(Math.random()*4+2);//[0-1]  --->[2,5]//定义老鼠
			System.out.println("老鼠的重量为:"+mounce);
			weigh+=mounce;
			if(weigh>=200) {
				break;
			}
			count++;
		}
		System.out.println("猫抓了"+count+"只老鼠");

5.do–while循环:

格式: do {

	}while(true);
		//1.do while先执行一遍,然后再判断,如果条件成立,就继续循环
		//do..while循环至少循环一遍
		int count=300;
		do {
			System.out.println("完成了"+count+"个订单");
		}while(count<200);

6.流程控制语句:

break:

结束循环体

num=15;
		boolean zhishu=true;  //num是质数
		for( i=2;i<num;i++) {
			if(num%i==0) {
				zhishu=false;
				break;
			}
		}
		if(zhishu) {
			System.out.println(num+"是质数");
		}

continue:

跳出当前循环,进入下次循环

int sum=0;
		for(i=1;i<num;i++){
			if(num%i!=0) {
				//不能整除,不用相加
				continue;
			}
			sum=sum+i;
			System.out.println(i);
		}

7.死循环:

死循环:没有明确的结束条件.

	int num;
	//死循环后的代码是无效的代码(不可能运行的代码)
		num=0;//报错
	//不是死循环,是无线循环,因为有明确的循环条件,虽然这个循环条件永远达不到.但是程序不认为是死循环.
	for(num=1;true;num++) {
		if(num<0) {
			break;
		}
	}
	//不会报错
	num++;
	//死循环:
	//		for(;;) {};//报错
		num++;

8.多重循环:

怎么跳出多重循环:

//方法一:
	static void test1() {
		//给代码块起一个标识
		demo:for(int i=0;i<20;i++) {
			for(int j=0;j<30;j++) {
				//如果i*j==30,就结束循环
				if(i*j==30) {
					 break demo;
				}
			}
		}
	}
	//方法二:
	static void test11() {
		//共锁
		boolean bool=true;
		//给代码块起一个标识
		for(int i=0;i<20&&bool;i++) {
			for(int j=0;j<30&&bool;j++) {
				//如果i*j==30,就结束循环
				if(i*j==30) {
					 bool=false;
				}
			}
		}
	}

Day03:

1.数组:

定义:

相同类型数据按有序顺序排列的集合.

数组的特点:

数组是有序的;

一旦创建一个数组,那么底层就开辟一块连续的地址,并且数组的长度是不可变的;

数组是引用数据类型,数组的元素可以是引用数据类型,也可以是基本数据类型.

数组的分类:

按维度分:一维数组,二维数组

按类型分:基本数据类型数组,引用数据类型数组

数组的初始值:

整型:0

浮点型:0.0

布尔型:false

字符型:Ascll码表对应的数值0

引用数据类型:“null”

数组的声明:

int [] array;//推荐
int arr[];

赋值:

直接赋值:
//直接赋值
		int[] arr1= {1,2,4,5,6,7};//这种方式只有声明并赋值时才可以使用
		int[] arr2=new int[] {1,2,3,4,5};
间接赋值:
//间接赋值
int[] arr3=new int[8];  //[8]  这个数组中可以存放8个数据
		arr2=new int[7];
		arr2=new int[] {1,2,3};
//		arr2= {1,2,3};这种方式只有声明并赋值时才可以使用,因为没有声明.
		arr1=new int[2];

使用数组中的数据:

		arr=new int[] {2,3,44,77,23};
		//使用下标获取数组中的数据:下标从0开始
//		arr[0];  从arr数组中取出下标为0 数据
		System.out.println(arr[0]);//2
		//获取77
		System.out.println(arr[3]);//77
		
		//数组中的元素也可以重新赋值
		arr[0]=220;
		System.out.println(arr[0]);//220
		arr[2]=4;
		System.out.println(arr[0]);

数组的限制:

1.数组只能存放声明数据类型的值(对象)
2.数组定义(声明)后,数组长度不可改变.

		arr=new int[6];//arr中只能存放6个整数类型的数据
		arr[0]=12;
		arr[5]=22;
//		arr[6]=33;  //报错:角标越界!    数组不能超过下标界限 长度-1
		System.out.println(Arrays.toString(arr));

常用数组问题:

两个变量交换值:
int a=12;
		int b=23;
		//通用方式  第三变量
		int temp;
		temp=a;//把a的值赋给第三变量    t=12 a=12 b=23
		a=b;//a的值有temp记录了,现在将b的值交给a    t=12  a=23  b=23
		b=temp;//
//整数之间交换值
		int a=12;
		int b=23;
		//加法:
		a=a+b;  //a  35  b  23  可以算出a原来的值
				 
		b=a-b;  //a  35  a-b 12 b=12   可以算出b原来的值   将这个值赋给a
		
		a=a-b;  //a	 12  a-b=12 b=12
		System.out.println(a);
		System.out.println(b);
	//位异或
	/**
	 * >>补符号位   >>>补0   <<补0
	 * &:两个都为1则为1
	 * |:有1则为1
	 * ^:相同为0,不同为1
	 * ~:取反
	 */
	a=12; //1100		
	b=15; //1111
	
	//a^b //0011
	a=a^b;// a^b=3  a=3 b=15
	//a^b 
	//0011
	//1111
	//1100=12  赋值给b
	b=a^b; //a=3 b=12 
	//a^b
	a=a^b;
	System.out.println(a);
	System.out.println(b);

排序:

1.冒泡排序
//冒泡排序
	public static void main(String[] args) {
		int arr[]=new int[] {12,56,23,7,89,4,58};
		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 temp =arr[j];
					arr[j]=arr[j+1];
					arr[j+1]=temp;
				}
			}
			System.out.println("确定第"+(i+1)+"个最大值后"+Arrays.toString(arr));
		}
	}

2.选择排序:
//默认是索引第一位(i)为最小值,然后去遍历i以后的数组,找出最小的数再和第一个数进行比较......
		int []arr=new int[] {12,56,23,7,89,4,58,88};
		int min;
		for(int i=0;i<arr.length;i++) {
			min=i;//将未排序的集合的每一个元素当做最小值
			//i是确定数组中最小值存放的位置
			//找到最小值的位置
			//j代表未排序数组中每个元素的下标
			//j=i,就不用每次从0开始找,因为前面最小值一个个的再增加
			for(int j=i;j<arr.length;j++) {
				if(arr[min]>arr[j]) {
					min=j;
				}
			}
			//最小和arr[i]进行交换
			if(min!=i) {
				int temp=arr[i];
				arr[i]=arr[min];
				arr[min]=temp;
			}
		}
		System.out.println(Arrays.toString(arr));

2.方法:

定义:

返回值类型 方法名 (参数){方法体}

 void method(int num) {
		if(num%2==0) {
			System.out.println(num+"是偶数");
		}else {
			System.out.println(num+"是奇数");
		}
	}

//return 一个方法只能返回一个对象或一个值
//执行return,这个方法就结束了,return后面的代码不会执行
	static void test(int []arr) {
		if(arr==null||arr.length==0){
			return;//中断方法的作用.,虽然方法是void,但是后面不能跟值,也不能跟null.
		}else {
			int length=arr.length;
			int temp=arr[0];
			arr[0]=arr[length-1];
			arr[length-1]=temp;
		}
	}
可变参数:

//一个方法只能有一个可变参数,可变参数必须写在参数列表的最后一个
//可变参数,数量传入数据的数据可变,类型不可变

public static double sum(double...nums) {//不确定有几个double型的数,但是有方法名相同的,明确的参数个数,会优先使用明确的方法
		double sum=0;
    nums就是可变参数,当中数组来使用
		for(int i=0;i<nums.length;i++) {
			sum+=nums[i];
		}
		return sum;
	}

Day04:

面向对象:

类和对象

类的声明

在程序中,是先有类,再有对象的.

使用class关键字声明一个类:

//在一个java类中只能有一个外部类(public)
class Person{
	//人类
	//年龄  名字 性别 肤色 属性  全局变量--在类中可以直接访问的量
	int age;//声明了一个age属性
	String name;//声明一个name属性
	char sex;
	String color;
	//吃饭 睡觉 说话 		//方法(行为)
	public void eat() {
		System.out.println(name+"在吃饭");
	}
	
	public int sleep() {
		return 7;
	}
}

类中可以定义属性,方法,这些被称为全局变量,在类中任何地方都可以访问的变量.

对象的声明:

通过new关键字+构造方法即是对象的声明

//声明Person变量,并实例化对象(在内存中开辟一块内存)
		
		//new Person()就是一个新对象,使用p变量来记录
		Person p=new Person();//new 构造方法	构造方法名和类名一样
		System.out.println(p.age);//0
类的调用

类中的属性和方法(非静态)都是通过对象.属性/方法进行调用和赋值的:

//new Person()就是一个新对象,使用p变量来记录
		Person p=new Person();//new 构造方法	构造方法名和类名一样
		System.out.println(p.age);//0
		//给对象赋值
		p.age=18;
		System.out.println(p.age);//18
		p.name="张三";
		p.color="黄色";
		p.sex='1';
		System.out.println("姓名:"+p.name);
		System.out.println("肤色:"+p.color);
		System.out.println("性别:"+p.sex);
		
		//调用对象方法
		p.eat();//张三在吃饭
		

练习:

定义一个手机类 属性:品牌,价格; 功能:通话:

1.类的声明:

class Phone{
	//定义属性:price
	int price;
	
	//定义属性:brand
	String brand;
	
	//定义功能(方法):打电话
	public void call() {
		System.out.println("打电话!");
	}
}

2.类的属性和方法调用

		//实例化Phone的对象phone
		Phone phone=new Phone();
		//给对象的属性赋值
		phone.brand="华为";
		phone.price=7999;
		//调用对象的方法
		phone.call();
		//输出对象的属性
		System.out.println("品牌为:"+phone.brand);
		System.out.println("价格为:"+phone.price);
static关键字的使用:
  • 1.作用范围:可以用来修饰,属性,方法,内部类,代码块.

  • 2.使用static修饰属性:静态变量(类变量):(静态属性方法可以类名.属性方法调用,不需要对象.属性方法调用)

  • 2.1属性:按是否被static修饰可分为:静态变量 vs 非静态变量(实例变量)

  • 实例变量:我们创建的多个对象,每个对象都独立的拥有一套类中的非静态属性,当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改
    
  • 静态变量:我们创建多个对象,多个对象共享一个静态变量,当通过某一个对象修改该静态变量的 值后,会导致其他对象调用此静态变量时,是修改后的。
    
  • 2.2:static修饰属性的其他说明

1.静态变量随着类的加载而加载。可以通过”类.静态变量“的方式进行调用
2.静态变量的加载要早于对象的创建。
3.由于类只会加载一次,则静态变量在内存中也只会存在一份(存在方法区的静态域中)
4.      类变量     实例变量
 	类    yes         no
 	对象   yes         yes
 	
  • 3.使用static修饰方法:静态方法:

  • 1.随着类的加载而加载,可以通过“类.静态方法”调用
    2.         类变量     实例变量
     		类    yes         no
     		对象   yes         yes
    3. 静态方法中,只能调用静态方法和属性
    非静态方法中,既可以调用非静态方法和属性,也可以调用静态方法和属性
    
  • static注意点:

  • 5.1在静态方法内,不可以使用this,super关键字
    5.2关于静态属性和静态方法的使用,从生命周期去使用
    
  • 开发中,如何确定方法是否要声明为static?

    工具类中的方法,习惯性声明为static,比如Math,Arrays,Colooection.
    *      >类中的常量,也常声明为static
    

在书写代码时候,一个java文件中只能有一个public类,并且必须和文件吗同名

也可以没有这个类.main()只能在public公共类中,每个类都可以有静态方法/静态属性,通过类名+静态方法直接调用,是随着类的加载而加载,main方法只能在公共类(public)中,类中的非静态方法则是通过对象.方法/属性进行调用赋值.

例子:

class Human{
	static String name;
			//方法属于对象还是属性,怎么调用?
			//对象的:没有使用static修饰的,必须使用对象调用
			//类的:使用static定义的属性和方法的类就是类的,也叫做静态的
			//静态方法和静态属性可以直接使用类名调用
	//每个类都可以有静态方法/静态属性,通过类名+静态方法直接调用,是随着类的加载而加载,main方法只能在公共类(public)中,类中的非静态方法则是通过对象.方法/属性进行调用赋值
	public static void changeWorld() {
		
		System.out.println("人类正在改变世界!!");
	}
	
	public void eat() {
		System.out.println("吃饭");
	}
	
}

练习:

package com.easy.demo06;

public class Test01 {

	public static void main(String[] args) {
        //实例化卡车对象
		Trunck t=new Trunck();
        //声明并赋值给变量weight为50
		int weigh=50;
        //
		boolean result=t.load(weigh);
		System.out.println(result);

	}

}


class Trunck{
	//声明一个卡车类,声明一个运载重量属性,声明一个运载的方法,传入货物重量,记录货物重量
	//定义一个最大载重,运载时,如果超重再打载重,打印拉不了
	
	double weigh;//运载重量属性
	
	
	public boolean load(double weight) {
		weigh+=weight;
		if(weigh>maxWeigh) {
			return false;
		}else {
			return true;
		}
	}
	
    //声明一个静态变量,随着类的加载而加载,声明在对象之前的.
	static double maxWeigh=100;
	
}

三大特性:

封装:
定义:

将类中的属性或方法通过权限修饰符来控制访问权限:

访问权限修饰符(访问权限递减):
  • public:公共的(任何类都可以访问);
  • protected:受保护的,家族的(子类和本包下的可以访问,子类可以是本包的,也可以是别的包中继承这个类的)
  • default默认的(本包下的可以访问)
  • private:私有的(自身类中才可以访问)
  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
calss Easy01{
public static String public_name="李四";
	static String default_name="李四";
	private static String private_name="李四";


	public static void test() {
		System.out.println(public_name);
		System.out.println(default_name);
		System.out.println(private_name);
	}
}

在该类中声明了三个静态属性,他们的权限修饰符都不一样,test()中可以直接的拿到这些属性值.马上我就拿不到私有属性了,因为私有属性只能在Easy01中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

导包:

本包下默认的类不需要导包,在一个类中,导包可能会发生类名相同的冲突,当发生冲突时,我们可以使用全类名(包名+类名)来精确的指定导入的哪个包

package com.easy.demo01;
//本包下的类不需要导包
//默认可以访问本包下的类
//import com.easy.demo07.*;
public class Exer {

	public static void test() {
		//在一个类中,类使用的类名方式冲突
		//当发生冲突时,可以使用类的全名(包名+类名)
		System.out.println(com.easy.demo07.Easy01.public_name);
		//受保护的访问不了
//		System.out.println(Easy01.default_name);
//		System.out.println(Easy01.private_name);

	}
}
Getter/Setter():

Java的封装性就是使用权限修饰符来控制的其他类的访问权限,其中private权限修饰符是权限最小的,只能用于当前类使用调用,当我们外部需要使用这些方法属性的时候,这个类就提供Getter/Setter方法来使用:

class{
    //声明一个私有属性
    private String name;
    
    //私有方法:
    //setter()
    public void setName(String name){
        this.name=name;
    }
    
    //getter()
    public String getNmae(){
        return this name;
    }
    
}

关键字:this

this是指当前类的属性或方法,是类中声明的全局变量,而传入的name是参数,是调用该方法的对象传入的值,意思就是一个对象调用私有方法set(),给类中的属性赋值.

1.使用:可以用来修饰属性,方法,构造器

this修饰属性和方法
* this理解为,当前对象
*
*      2.1:在类的方法中我们可以使用“this.属性”或者“this.方法”的方法调用当前类的属性和方法,但是
*      通常情况下,我们都选择省略“this”,特殊情况下,如果方法是形参和类的属性同名时,我们必须
*      显示的使用“this.变量"的方式,表面此变量是属性,而非形参
*
*       2.2:在类的方法中我们可以使用“this.属性”或者“this.方法”的方法调用当前正在创建的的属性和方法,但是
*        通常情况下,我们都选择省略“this”,特殊情况下,如果构造器的形参和类的属性同名时,我们必须
*        显示的使用“this.变量"的方式,表面此变量是属性,而非形参
继承:

子类继承父类,子类就能拥有父类中声明的属性和方法.

并且在Java中,只支持单继承,一个类只能继承一个直接类,就是值class A extends B而不能继承C 或者D类,但是java允许多重继承,意味着一个类可以被A继承,也可以被B继承.

作用:减少编写的代码,规范了子类的行为.

注意:当父类存在属性是private修饰的,子类也得不到这个属性.只能通过get/set方法

class A{
    int age;
    
    //子类访问不到
    private String name;
    
}

class B extend A{
    //该类有age属性
}
代码块执行顺序
public class Test2 {

	public static void main(String[] args) {
		B b=new B();//只声明子类运行
	}
}

声明:
class A{
    
    //成员代码块(直接在类中声明的代码块)
    //运行:实例化对象时,先运行成员代码块,再运行构造方法
    {
        
    }
    
    //静态代码块
    //运行:在类加载的时候运行,优先运行静态代码块
    static {
        
    }
}

class B extends A{
	
	{
		System.out.println("子类代码块");
	}
	
	static {
		System.out.println("子类静态代码块");
	}
	
	B(){
		System.out.println("子类构造方法");
	}
}

输出结果是:

  • 父类静态代码块
  • 子类静态代码块
  • 父类代码块
  • 父类构造方法
  • 子类代码块
  • 子类构造方法

代码块优先执行父类的静态代码块,再执行子类的静态代码块,然后执行代码块,父类优于子类,再执行构造方法,父类优于子类方法.

构造方法:

  • 构造方法名和类名一样

  • 构造方法不能定义返回值类型

作用:一般是用来初始化成员属性和成员方法的,即new对象产生后,就调用了对象的属性和方法,一个对象的建立,构造方法就运行一次.

class A{
    int age;
    A(){
        
    }
    
    A(int age){
        this.age=age;
    }
}

calss B extends A{
    B(){
       子类的构造方法中,首行必须调用父类的构造方法
		所有子类构造方法中首行默认调用父类的无参构造方法
		如果父类的无参方法消失(或者没有定义无参构造方法,需要明文确定			调用哪个无参构造方法)
		子类调用父类构造方法,使用super关键字
        
        //我子类构造方法中第一行不写调父类的构造器
        System.out.println();//报错,父类的构造方法必须在子类的第一行   
    }
}

构造方法就是用来造对象的,每个类都至少有一个构造方法,默认是空参的,如果自己定义了带参的构造器,那么默认的构造器将会被覆盖,如果该类被继承,子类必须使用带参的构造方法.

Object:

  • Object所有的引用类型默认继承Object
  • Object是所有引用类型的超级父类
  • Object中的属性和方法:
public class Easy04 {
	public static void main(String[] args) {
		//基本数据类型
		byte b=12;
		int a=b;
		
		//类型转换
		//向上转型:父类的变量可以指向子类的对象
		
		Son s=new Son();
		Object o=s;
		Parent p=s;
		
		//s对象能够调用到自身声明类型能够访问到的方法
		s.test();
		s.youkuang();
		
		p.youkuang();
		
//		o.test();
//		o.youkuang();
		
		
		
		//强制转换可能会报异常,只有确定该对象属于某一类型,才能正确转换(成功转换),否则会报错(类型转换异常)
		Parent pp=new Parent();
		//oo是Object,但是此时是指向pp的(Parent)
		Object oo=pp;
		
		Parent pp1=(Parent)oo;
		
//		Son ss=(Son)oo;//报错:java.lang.ClassCastException
		
		//确定某一对象是否属于该类型
		System.out.println(oo instanceof Parent );//true
		System.out.println(oo instanceof Son );//false
		System.out.println(oo instanceof Object );//true
		System.out.println(oo instanceof String );//false
	}

}
多态:

执行一个方法会出现多重形态

静态多态(编译时多态)

​ **方法的重载:**一个类中方法名一样,即为重载(跟返回值没有关系)

参数列表确定,调用的方法:

  • ​ 参数的类型;

  • ​ 参数的个数;

  • ​ 参数的顺序;

    返回值类型:

     1. 子类重写的方法名和形参列表与父类被重写的方法名和形参列表相同;
    *       2.子类重写的方法的权限修饰符不小于父类的被重写的方法的权限修饰符;
    *          >子类中不能重写父类中private的方法;
    *       3.返回值类型:
    *          >父类被重写方法的返回值类型为void,那么子类重写的方法也只能是void返回值类型
    *          >父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或者A类的子类
    *          >父类被重写的方法的返回值类型是基本数据类型,子类重写的方法的返回值类型必须是相同的基本数据类型
    *       4.子类重写的方法抛出的异常类型不大于我们被重写的方法抛出的异常类型
    

动态多态(运行时多态):

  • ​ 方法的重写:子类继承父类,对继承过来的方法进行重新定义.

  • ​ 可以通过Override注解验证是否是重写的方法.

  • 注意:子类重写的方法访问权限不能小于父类的方法访问权限;

    • 重写的方法返回值只能更精确(就是父类的子类对象).

    • 父类静态方法不能被重写,但是子类可以调用.

父类的变量可以指向任意子类的对象,子类重写父类的方法,引发非多态.

面向对象特征之三就是多态性:

多态性可以理解为一个事物的多种形态,就是父类的引用指向子类的对象(子类的对象赋给父类的引用).

多态的使用:有了多态以后,只能调用父类中声明的方法,但是在运行期,我们实际执行的是子类重写父类的方法

总结:编译看左边,运行看右边。

对象的多态性,只适合方法,不适用于属性.

class Person{
    void eat(){
        System.out.println("人吃饭");
    }
}


//子类继承父类(多态条件之一:继承)
class Man extends Person{
    //子类继承父类(多态条件之二:方法的重写)
    void eat(){
        System.out.println("男人要多吃饭,好干活!");
    }
}

public class PersonTest{
    Public static void main(String[]args){
        //实例化对象
        Person p=new Person();
        p1.eat();//吃饭
        
        Man m=new Man();
        m.eat();//男人要多吃饭,好干活!   
        以上都没发生多态
            
            
       Person p1=new Man();//子类的对象赋给父类的引用
        p1.eat()//输出的是子类还是父类的方法呢???
        //编译看左边,运行看右边.
        //所以运行时,看=右边,那就是Man()中的方法(虚拟方法调用)
        //输出:男人要多吃饭,好干活!
    }
}
final:
  • 修饰类:不可以被继承.比如 String
  • 修饰方法:方法不可以被重写,但是可以继承父类的方法.
  • 修饰属性:这个属性不可被重写赋值
class A{
   static final int []a={1,2,3};
    
    public static void mani(String args[]){
        a=new int[20];//错误,final修饰的值不能被重新赋值
        
        a[1]=10;//可以,虽然不能重写赋值,但是可以修改这个数组里面的内容
    }
}



class B{
    String name;
    public static void mani(String args[]){
        final B b=new B();
        b=new B();//错误,不能对final修饰非值重新赋值,但是可以修改这个对象内部的(属性)
        b.name="小平";
    }
    
}
  • ​ 成员不可变量(对象的)

    public final

    可以不直接赋值,但是得在每个构造方法中赋值

    class A{
        String name;
        
        public final int age;
        A(){
            age=10;//给成员不可变量赋值
        }
        
        A(String name){
            this.name=name;
            age=20;//给成员不可变量赋值
        }
    }
    
  • ​ 常量(类的)

public static final

必须给与初始值

​ 也可以在静态代码块中初始化

class A{
    //声明一个常量并直接赋值
    public static final int MAX_NUM=8000;
    //1.常量名书写名字母都要大写,用下划线分割.
    //2.必须使用final static修饰,静态的常量,在类加载时就直接运行了,程序运行我们就干预不了
    //所以我们往往都在声明时直接进行赋值,或者在静态代码块进行赋值.
    
    //声明一个常量
    public static final int MIN_NUM;
    //在静态代码块进行赋值
    static{
        MIN_NUM=0;
    }
    
}

静态方法则是看左边:

class A{
    public static void test(){
        Sysotem.out.println("静态父类方法");
    }
}

class B extends A{
    @Override//会报错,静态方法不能重写!
      public static void test(){
        Sysotem.out.println("静态子类方法");
    }
}

class Test{
    A a=new B();//多态
    a.test();//一般情况是.编译看左边,运行看右边(应该是调用的子类的方法的)
    //但是test()是静态的,这时候多态的情况下,就是调用的父类的方法(看左边).
}

抽象类:

  • 使用abstract修饰的类

  • 抽象类不能被实例化对象

  • 可以定义抽象方法,也可以定义实体方法

  • 抽象方法就是:没有方法体的方法

  • abstract class A{
        public void test();//抽象方法
        
        //实体方法
        public void test(){
            System.out.println("这是一个实体方法");
        }
    }
    
  • 作用主要用于继承

抽象类的继承:

抽象类可以继承实体类

实体类继承抽象类,必须要实现抽象类中的抽象方法

抽象类可以继承抽象类

注意:

抽象类不能使用final来修饰

抽象方法也不能使用final和private修饰修饰

抽象类一般使用Abstract修饰

总结:

abstract修饰类:
          >此类不可以实例化,就是不能new
          >抽象类中一定有构造器,便于子类实例化时调用
         >开发中。都会提供抽象类的子类,让子类对象实例化,完成相关操作


abstract修饰方法:
          >只有方法的声明,没有方法体          
          >包含抽象方法的类,一定是抽象类,反之抽象类中可以没有抽象方法
         >若子类重写了父类中的所有的抽象方法,此子类才可以实例化,子类需要重写父类及其父类的父类的抽象方法,这样这个子类才可以new,将子类实例化否则就只可以abstract。
          >若子类没有重写父类中的所有的抽象方法,此子类也是个抽象类,需要使用abstract修饰

Day05:

接口:(Interface)

作用:用于规范子类行为.

特点:方法都是抽象方法.

方法:

  • 默认使用public abstract
  • 接口中的方法不能使用其他修饰符
标准写法:
public abstract void test();

简写:
void test();

接口中可以定义静态方法,注意抽象和静态并不能同时修饰一个方法

public static abstract void test(){
    
}

属性:

接口中的属性都是常量:public static final修饰

属性也是默认使用public static final来修饰的,所以也可以声明

标准写法:
public static final int MAX=10;

简写:
int MIN=0;

实例化:

接口是没有直接实例化对象的(不能new())

构造方法:接口中没有构造方法,但是抽象类中有构造方法.(类中一定有构造方法)

使用:

实现类实现接口,使用implements 实现接口,实现类必须实现接口中的所有抽象方法,一个类口可以实现多个接口,接口和接口之间可以继承,接口之间用逗号隔开,实现类的接口之间不能出现返回值类型不一样的方法,不能出现冲突.

Interface A extends B{//接口可以继承接口
    void test();
    int eat(String food);
}

Interface B{
    void run();
    String eat(String food);//出现问题,同样是吃的方法,,返回值却不一样,就出现冲突了.
}

class C implements A,B{
    @Override
    void test(){
        System.out.println("实现类实现接口的抽象方法")
    }
    
     @Override
    void run(){
        System.out.println("实现类实现接口的抽象方法")
    }
}

抽象类可以实现接口,也可以不实现接口中的抽象方法.(因为抽象类中的方法可以是抽象的,也可以是实体的方法.)

注意:final不可以来修饰接口:

因为final修饰的类或方法,是不能被继承或者被重写的,而接口就是用来被继承的,定义实现类的规范.

字符串:

String字符串:使用一对""表示.

String s="123";//定义一个字符串并赋值     ---字面量赋值  s在常量池中
String s1=new String("123");//通过new的方式
  • String 声明为final的,是不可被继承的.
  • String实现了serializable接口,表示字符串时支持序列化的。实现了Comparable接口,表示字符串可以比较大小.
  • String内部定义了final char[] value用于存储字符串数据

String代表不可变序列,简称:不可变性

体现:

1.当对现有的字符串进行连接操作时,需要重新指定内存区域赋值,不能使用原有的value赋值;

2.当对字符串赋值时,需要重新指定内存区域赋值,不能使用原有的value赋值;

3.当调用String的replace()方法修改字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value赋值;

通过字面量的方式赋值(区别于new)给一个字符串赋值,字面量方式赋值的字符串是在常量池中.

字符串常量池中不会重复存储相同的内容的字符串.

  • 面试题:
  • String s=new String(“abc”);用这种方式创建对象,内存中创建了几个对象?
  • 答案:一个或者两个,一个是在堆空间new出来的,另一个是在char[]对应的常量池的数据"abc".首先检查常量池中是否有对应的副本,当常量池中没有"abc",那么复制对象放到常量池中去,然后返回堆空间的对象.如果有常量池中有"abc",则直接返回常量池中的对象

比较==和equals():

System.out.println(12==12.0);//true
//因为==在基本数据类型中是比较值是否相等

String str1=new String ("123");
String str2=new String ("123");
System.out.println(str1==str2);//false
//在引用数据类型中,==是比较两个对象是否指向同一地址(同一个对象),new出来的都会重写开辟一块空间的.

对于自定义类中,我们需要去重写equals方法.(equals方法定义在Object中,equals方法在Object中是==的含义,比较的是地址值(是否为一个对象))

String str1=new String ("123");
String str2=new String ("123");
//我们使用了equals()方法,为啥还是false ,因为我们自定义类是自动继承了Object超级父类的equals()方法,而超级父类中的此方法,就是比较s1==s2,这是比较两个对象是否指向同一个地址值的,这不满足我们的业务需求,所以我们需要重写equals().
System.out.println(str1==str2);//false

对于程序自带的类(String)

是重写过了equals()方法,所有可以直接使用此方法比较字符串的值是否想等.

书写equals()方法:

public boolean equals(Object obj) {
	//Student对象的学号一样,就应该为一个学生
	
	//1.先确保传入的对象是否为Student对象
	if(obj instanceof Student) {
		//2.数据类型转换 Object-->Student
		Student temp=(Student) obj;//强制类型转换
		return this.code==temp.code;
	}
	return false;
}

字符串的==比较案例:

		String str1="1234";
		String str2=new String("1234");
		String str3=new String("1234");
		String str4="12"+"34";
		String s51="12";//变量
		String str5=s51+"34";
		final String s61="12";
		String str6=s61+"34";
		System.out.println(str1==str2);//false
		System.out.println(str2==str3);//false
		System.out.println(str1==str3);//false
		//JDK	常量编译优化
		System.out.println(str1==str4);//true  在编译过程,将常量的表达式运算完成(自动已经编译了"1234",在运行时已经可以确定了}----编译优化
		System.out.println(str1==str5);//false
		System.out.println(str1==str6);//true

字符串的方法:

  • 获取长度:length()
String s="123";
s.length()//3
    

  • 获取下标对应的字符charAt()
String str="12345678";
char c=str.charAt(4);//5
  • 遍历字符串字符:

    for(int i=0;i<str.length();i++) {
    c=str.charAt(i);
    System.out.print©;
    if(i==str.length()-1) {
    System.out.println();
    }
    }

截取字符串字符substring():

  • sbuString(StartIndex)从开始位置截取到最后
  • sbuString(StartIndex,endIndex) 从开始位置截取到Index-1的位置.
String str="xxxxx20231018xxxxxxx";
String substr=str.substring(5, 13);  //此范围是左闭右开的[5 13)----[5....12]

字符串截取,不会改变原来的字符串

  • 查找子串出现的位置(下标):indexOf() 找不到则返回-1
int index=str.indexOf("2023");//查看2023有没有出现过,开始出现的位置----5
  • 分割字符串:
str  = "123425624926425";
String[]arr=str.split("2");//[1, 34, 56, 49, 64, 5]

String str1= "5599.64.46.32.5";
String[]arr1=str1.split(".");//正则表达式,.在正则表达式中表示任意字符 
System.out.println(arr1);//[]
arr1=str1.split("\\.");//转义成.

  • 去除字符串前后空白位:
str="    123     123    ";
System.out.println(str.trim());
  • 拼接字符串:

+号的优先级是一样的(计算和拼接,运算顺序一样)

System.out.println(12+23+"");//32""
		
System.out.println(""+12+23);//1223

字符串用+拼接会产生很多中间量,且会一直存在,永久占用常量池中的内存空间
尽量避免使用+号拼接字符串,尤其是在未知明确次数的循环中
解决方案:使用一个可变数组来存储要拼接的字符串,拼接完成后转换为字符串
这样可以有效避免中间量的生成.

  • 关于StringBulider和StringBuffer的使用:

String, StringBuliderStringBuffer三者的异同:

  • String:JDK1.0出现的,不可变的字符序列; 底层使用char[]型数组存储
  • StringBuffer:JDK1.0出现的 可变的字符序列线程安全的,效率低; 底层使用char[]型数组存储
  • StringBulider:可变的字符序列:JDK1.5出现的,线程不安全,效率高; 底层使用char[]型数组存储

共同之处三者都是char[]数组存储的.区别主要是线程安全问题和字符序列是否可变(线程安全与效率成反比)

  • 源码分析:
String str=new String();//char[] value=new char[0];
String str11=new String("abc");//value=new cahr[]{'a','b','c'}

StringBuffer sb1=new StringBuffer(); //char[] value=new cahr[16];底层创建了长度为16的char型数组.
sb1.append('a');//value[0]='a';
sb1.append('b');//value[1]='b';


StringBuffer sb2=new StringBuffer("abc");//cahr[] value=new char['abc'+length()+16]

问题一:

System.out.println(sb2.length);//3 不要因为底层有个默认长度16的数组就说16,里面只有"abc"这字符串的长度

问题二:

扩容问题:如果添加数据的数据在底层数组盛不下来,那就需要扩容底层的数组.默认情况下,扩容为原来的2倍(左移)+2,同时要将原来的元素复制到新的数组中.

指导意义:开发中建议使用StringBuffer(int capacity)

体会StringBuffer字符序列可变性:

StringBuffer sb1=new StringBuffer("abc");
sb1.setCharAt(0,'m');
System.out.println(sb1);//mbc  如果是String的话,会返回一个新的String,原来这个不会改变,这个就只有一个数组,在一个数组上进行修改。

StringBuffer,StringBuilder常用方法

  • StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接(在字符串后面追加)

  • StringBuffer str=new StringBuffer("abc");
    str.append(1);
    str.append("1")
    System.out.println(str1);//abc11  
    
  • StringBuffer delete(int start,int end):删除指定位置的内容(左闭右开)

  • StringBuffer str=new StringBuffer("abc11");
    StringBuffer result=str.delete(2,4);//就是删除索引为2,3的值
    System.out.println(result);//ab1
    
  • StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str

  • StringBuffer str=new StringBuffer("abc11");
    StringBuffer result=str.replace(2,4,"hello");
    System.out.println(result);//abhello1;
    
  • StringBuffer insert(int offset, xxx):在指定位置插入xxx

  • StringBuffer reverse() :把当前字符序列逆转

  • public int indexOf(String str)

  • public String substring(int start,int end)返回一个从start开始到end索引结束的左闭右开的子集

  • public int length()

  • public char charAt(int n )

  • public void setCharAt(int n ,char ch)

总结:

  • 增:append(xxx)
  • 删:delete(int start,int end) deleteCharAt(int n)
  • 改:setCharAt(int n)
  • 查:charAt(int n)
  • 插:insert(int offset,xxx)
  • 长度:length()
  • 遍历:for()+charAt()

封装类:

基本数据类型:

byte short int long float double boolean char

为啥需要封装类型:

Java为了达到万物皆对象的理念,每一个基本数据类型,都提供了一个对应的封装类型(引用类型)

基本数据类型的封装类:

Byte Short Integer Long Float Double Boolean

Character

字符串转为Int数值的方法:parseInt()

int num=parseInt(“12”);

装箱和拆箱:

  • 装箱和拆箱

num=12;

**装箱:**将基本数据类型转为对应的封装类型

Integer b=num;

拆箱:将基本数据类型的封装类型的对象,转换成对应的基本数据类型的值.

int a=new Integer(12);

  • 缓存:

基本数据类型的整数,会默认缓存-128~127每一个数值(255个int数值方便我们使用)

Integer i1=127;
Integer i2=127;
Integer i3=new Integer(12);
Integer i4=212;
Integer i5=212;

System.out.println("---------------------------------------");
System.out.println(i1==i2);//true		因为127没有超过缓存,所以是指向的一个对象
System.out.println("---------------------------------------");
System.out.println(i1==i3);//false 一个是直接赋值,另一个是new出来的
System.out.println(i4==i5);//false	超出了缓存范围,就新建对象,那么==就不相等(==是比较的同一个对象).

我们可以通过-XX:AutoBoxCacheMax指令修改Integer的默认缓存,只针对Integerd ,其他基本类型不能修改.

在此输入命令.入口在RunConfiguration–Arguments

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

假设我们设置默认值为300
Integer i4=212;
Integer i5=212;
System.out.println(i4==i5);//true.没有超过缓存,所有还是true

Short s1=200;
Short s2=200;
System.out.println(s1==s2);	//false  因为我们修改的缓存只对int有限,其他默认都是-128!~127这个范围

注意:

  • 布尔型和浮点型没有缓存
  • 基本数据类型的封装类型之间没有隐式转换的
  • 基本数据类型和封装类型比较,是将封装类型转换成基本数据类型,在进行比较基本类型比较

常用类:

数学类:Math

系统类:System

时间类:Date Calendar

Math

	double d=Math.random();//[0-1)
	System.out.println(d);
	Random r=new Random(12);
	Random r1=new Random(12);//生成一个伪随机数,种子数一样,那么随机数也是一样的,但是该对象下一次再调用该种子的方法
	System.out.println(r.nextDouble());//0.7298928061101974
	System.out.println(r1.nextDouble());//0.7298928061101974
	System.out.println(r.nextDouble());//0.2750691655200749
	System.out.println(r1.nextDouble());//0.2750691655200749
	
	Random r2=new Random();//以当前时间为参照生成的种子数,在运行阶段,肯定是先运行上面的代码,这个种子就是由时间决定的,所以时间不一样,那么种子就不一样了,所有后面的
	//随机数nextDouble()不一样.
	Random r3=new Random();
	System.out.println(r2.nextDouble());//0.6441392457983449
	System.out.println(r3.nextDouble());//0.40042837707879153
	
	//Math  向上取整
	double d1=Math.ceil(12.00000001);//返回是是double类型!!!!!!!
	System.out.println(d1);//13.0
	
	//Math	向下取整
	double d2=Math.floor(12.999999);
	System.out.println(d2);//12.0
	
	long l1=Math.round(12.2);//返回的long
	System.out.println(12);
	int l2=Math.round(125);
	System.out.println(l2);
	
	int a=Math.abs(-12);
	System.out.println(a);

System

long ctm=System.currentTimeMillis();
		System.out.println(ctm);
		
		//呼叫垃圾回收
		System.gc();
		
		//退出
//		System.exit(0);
//		for(int i=0;i<100;i++) {
//			if(i==12) {
//				System.exit(0);
//				System.out.println(i);
//			}
//			System.out.println(i);
//		}
		
		//获取系统信息
		Properties pp=System.getProperties();

Date

//当前时间:运行这行代码的时间
		Date date=new Date();
		System.out.println(date);//Fri Oct 13 16:36:46 CST 2023   这种格式不好看
		
		
		SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
		String sdfstr=sdf.format(date);
		System.out.println(sdfstr);//2023-10-13 16:36:46 048
		
		date.getTime();//long类型的返回值
		//Date对象的时间是使用long类型的数值来表示	每一毫秒加1   1970-1-1 0:0:0 000------从该时间开始计时的
		
		date=new Date(0);//初始时间
		sdfstr=sdf.format(date);
		System.out.println(sdfstr);//1970-01-01 08:00:00 000  我们在东八区

Calendar

//Calendar  没有公共的构造方法
		Calendar c=Calendar.getInstance();
//		c.set(Calendar.YEAR,3023);
		
		int year=c.get(Calendar.YEAR);
		System.out.println(year);//2023
		
		int month=c.get(Calendar.MONTH);
		System.out.println(month);//9    0-11
		
		int day=c.get(Calendar.DAY_OF_MONTH);
		System.out.println(day);
		c.getTime();//返回当前日历对象对应的Date对象

Day06:

集合框架的概述:

  • 集合:
  • 就是对多个数据进行存储操作的结构,简称Java容器
  • 说明:此时的存储,只要是指内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库)

数组在存储多个数据方面的特点:

  1. 数组定义后,长度不可变;
  2. 数组中只能存放指定类型的对象
  3. 数组存储数据的特点:有序,可重复,对于无序,不可重复的需求,不能满足

集合框架:

 |Iterator接口:提供迭代器,用于集合的遍历
 		|-----Collection接口:单列集合,用来存储一个一个的对象
*          		|----List接口:存储有序的,可重复的数据. [1,1,2,3,4,4,5]     ---->"动态数组"
*              		|---ArrayList,LinkedLoist,Vector(实体类)
*
*          		|----Set接口:存储无序的,不可重复的数据  [2,4,6,5,7,1,8]   ---->高中讲的"集合"
*              		|---HashSet,LinkedHashMap,TreeSet
*
*|-----Map接口:双列集合,用来存储一对(key - value)一对的数据
*          	   		|---HashMap,LinkedHashMap,TreeMap,HashTable,Properties

在这里插入图片描述

Collection

向Collection接口的实现类的对象中添加数据OBJ时,要求obj所在类要重写equals().

Collection为例:
//声明集合Collecion
Collection coll=new ArrayList();
//添加对象
coll.add(123);
coll.add(456);
coll.add("tom");
coll.add(false);
coll.add(new Person("Jerry",20));

方法:

  • 1.**contains(Object obj)😗*判断当前集合是否包含obj

我们在判断时会调用obj对象所在类的equals()

boolean contains = coll.contains(123);
System.out.println(contains);//true
System.out.println(coll.contains("tom"));//true  就说明不是==,而是调用了equals(),判断对象的值是否为tom
System.out.println(coll.contains(new Person("Jerry",20)));//false Person是自定义类,没有重写toString方法,所有比较的是地址值(是否为同一个对象),上面的String是重写了toString(),所以比较的对象的是值
//解决将false-->true:去Person类中重写toString()方法
  • 2.containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在于当前集合中。
Collection coll1 = Arrays.asList(123,456);
 System.out.println(coll.containsAll(coll1));//true
  • 3.remove(Object obj):从当前集合中删除obj元素
Collection coll=new ArrayList();
coll.add(123);
coll.remove(123);
System.out.println(coll);//空集合
  • 4.removeAll():从当前集合移除另一个集合的所有元素
Collection coll=new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
Collection coll1=Arrays.asList(123,4567);
coll.removeAll(coll1);
System.out.println(coll);
  • 5.hashCode():返回当前对象的hash值
System.out.println(coll.hashCode());
  • 6.集合—>数组:toArray()
//定义集合
Collection coll=new ArrayList();
//将集合转化为数组
Object[] array = coll.toArray();
// 遍历数组
for (int i=0;i<array.length;i++){
   System.out.println(array[i]);
}
  • 7.数组---->集合:调用Arrays的静态方法asList()
//定义一个集合List
List<String> list = Arrays.asList("AA", "BB", "CC");
System.out.println(list);

List<int[]> list1 = Arrays.asList(new int[]{123, 456});
System.out.println(list1.size());//1  将123,456识别成一个元素了

List<Integer> list2 = Arrays.asList(123, 456);
System.out.println(list2.size());//2  使用包装类解决
  • 8.iterator:返回Iterator接口的实例,用于遍历集合元素

内部方法:

hasNext() :判断是否还有下一个元素

next():①:指针下移;②:将下移后的集合位置上的元素返回

在这里插入图片描述

错误使用:

  • 错误方式一:

没有使用hasNext()判断下一个元素是否存在

Iterator iterator = coll.iterator();
       while (iterator.next()!=null){
           System.out.println(iterator.next());
       }
  • 错误方式二:

死循环,一直输出123,因为每次循环都是一个新的迭代器。
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。

while (coll.iterator().hasNext()){
            System.out.println(iterator.next());
        }

遍历集合的方式:

  • 使用迭代器遍历集合:
 iterator=coll.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        
  • 增强for循环

for(集合中元素的类型 局部变量 : 集合对象)

//内部仍然调用的是迭代器

for(Object obj : coll){
    System.out.println(obj);
}

List:

List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。

源码分析;
ArrayList:
  • jdk 7的情况下:

ArrayList list =new ArrayList(); 就是创建了一个长度为10的Object[]数组elementData.


list.add(123); elementData[0]=new Integer(123);

list.add(11); 如果此次的添加导致底层elementData数组容量不足,则扩容,默认情况下,扩容为原来的容量的1.5倍,同时需要将原来的数组复制到新的数组中.

结论:建议开发中使用带参的构造器:ArrayList list=new ArrayList(int Capacity)

  • jdk 8中的ArrayList的变化:

ArrayList list=new ArrayList(); 底层Object[] elementData初始化为{},并没有创建长度为10的数组,后续的添加和扩容与jdk7相同

小结:jdk7中的ArrayList的对象的创建类似于单例模式的饿汉式,而jdk8中的ArrayList的对象的创建类似单例的懒汉式,延迟了数组的创建,节省空间!!!

LinkedList:

LinkedList list =new LinkedList(); 内部声明看Node类型的first和last属性,默认值为null.

list.add(123); 将123封装到Node中,创建Node对象

其中Node定义 为:体现了LinkedList的双向链表的说法

private static class Node<E>{
               E item;
               Node<E> next;
               Node<E> prev;
 
              Node(Node<E> prev,E element,Node<E> next){
                   this.item=element;
                   this.next=next;
                 this.prev=prev
              }
          }
Vector:

jdk7,jdk8通过Vector()构造器创建对象时,底层都创建了长度为10的数组
在扩容方面,默认是原来的数组长度的2倍


面试题:

  • ArrayList.LinkedList,Vector三者的异同?

同:三个类都是实现了List接口,存储数据的特点相同:存储有序的,可重复的数据;

不同:

  • ArrayList和LinkedList的异同 二者都线程不安全,相对线程安全的Vector,执行效率高。 此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于双向链表的数据结构。对于 随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增 和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。

  • ArrayList和Vector的区别 Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于 强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用 ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大 小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

  • ArrayList.LinkedList分别在哪种情况下使用:

    ArrayList:查找(索引下标)

    LinkedList:插入和删除


常用方法:
  • add(Object obj);向集合中添加数据

  • add(int index,Object item):在Indedx位置插入item元素

ArrayList list=new ArrayList();
list.add(123);
list.add("AA");
list.add(new Person("tom",13));
[123,AA,Person{name='tom',age=13}]


list.add(1,"BB");
System.out.println(list);
[123,BB,AA,Person{name='tom',age=13}]
  • Object get(int index);获取指定Index位置的元素
ArrayList list=new ArrayList();
list.add(123);
list.get(0);  //123
  • int indexOf(Object obj):返回obj在集合中首次出现的位置,没找到返回-1
ArrayList list=new ArrayList();
list.add(123);
list.add("AA");
list.indexOf(123); //0在索引为0的位置出现123这个对象
  • int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
ArrayList list=new ArrayList();
list.add(123);
list.add("AA");
list.add(new Person("tom",13));
list.lastIndexOf("AA");   //1
  • Object remove(int index):移除指定Index位置的元素,并返回此元素(对象)
ArrayList list=new ArrayList();
list.add(123);
list.add("AA");
list.remove(0);
  • Object set(int index,Object obj):设置指定index位置的元素为obj
ArrayList list=new ArrayList();
list.add(123);
list.set(0,456);
System.out.println(list)  //456
  • List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex,左闭右开
ArrayList list=new ArrayList();
list.add(123);
list.add(456);
list.subList(0,1);//[123]  
  • 集合的长度:size()

**集合扩容的关键部分:**System.arraycopy(Object src ,int srcPos ,Object des,int desPos,int length)

int[]arr1= {1,2,3,3,45,5,6,7,89};
int[]arr2=new int[30];
System.out.println(Arrays.toString(arr1));
System.out.println(Arrays.toString(arr2));

System.arraycopy(arr1, 0, arr2, 0, arr1.length);  //从原数组arr1的索引0开始复制.从目标数组索引0的位置开始粘贴,粘贴原数组的长度的个数
System.out.println(Arrays.toString(arr1));
System.out.println(Arrays.toString(arr2));
	 * Object  src	原数组
	 * int srcPos 从原数组的哪个位置开始复制
	 * Object des  目标数组
	 * int destPos 从目标数组的哪个位置开始粘贴
	 * int length  粘贴的长度

Set:

存储无序的,不可重复的数据

特点:
  • 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的Hash值决定的(进来的数据和出去的数据顺序是一样的)
  • 不可重复性:保证添加的元素按照equals()判断时,不能返回true.及相同的元素只能添加一个

Set接口没有额外定义新的方法,使用的都是Collection中声明过的方法

两个要求:

  • 向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
  • 重写的hashCode()和equals()尽可能保持一致性,相等的对象必须具有相等的散列码
添加元素的过程:
  • 我们这里以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组的存储位置(索引位置),判断数组此位置上是否已经有元素;
*          如果此位置上没有其他元素,则元素a添加成功,---->情况一
*          如果此位置上有其他元素b(则以链表形式存在的多个元素),则比较元素a和b的哈希值:
*              如果哈希值不相同,则元素a 添加成功。---情况二
*              如果哈希值相同,进而调用元素a所在的类的equals()方法:
*                  equals()返回true,则元素a添加失败
*                  equals()返回false,则元素a添加成功 --->情况三
  • 对于添加成功的情况2和3而言,元素a与已经存在指定索引位置上数据以链表的方式存储。
  • jdk 7:元素a放到数组中,指向原来的元素
  • jdk 8:原来的元素在数组中,指向元素a

总结:七上八下

下图就为jdk8的表现形式.链表上新增的数就需要

在这里插入图片描述

比较Comparable compareto

TreeSet:
  • 向TreeSet中添加的数据,要求是相同的类
  • 两种排列方式:自然排序(实现Comparable接口),定制排序(Comparator)

Map:

在这里插入图片描述

  • map是一个独立接口常用类,与Collecation家族无关.
  • HashCode:将一个对象转换为一个数组序列.
  • Hash冲突:计算出来的HashCode的值相同.

实例化一个HashMap对象

Map map=new HashMap();

map是以键值对的方式来存储的

常用方法

添加数据:put()

map.put(2001, "张三");
map.put(2002,"李四");
map.put(2003, "王五")

获取数据:get()

Object o =map.get(2001);
System.out.println(o);

设置数据:put()

map.put(2001, "张小平");
obj1=map.get(2001);
System.out.println(obj1); //张小平

删除数据:remove()

Object obj=map.remove(2002);
System.out.println(obj);//返回删除的value
System.out.println(map);

HashMap的存储过程:

HashMap的数据结构是数组+双向链表+红黑树的集合

数组的初始容量为16:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

数组扩容的阈值:0.75

static final float DEFAULT_LOAD_FACTOR = 0.75f;

链表扩容阈值:8

static final int TREEIFY_THRESHOLD = 8;

退树化的阈值:6

static final int UNTREEIFY_THRESHOLD = 6;

哈希值的产生过程:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

我们以HashMap为例:

Map map=new HashMap()
map.put(a);

首先我们向map集合中添加元素,map.put(key value),通过map.get(key)然后通过哈希算法算出哈希值,根据哈希值,将a值传入到数组的指定位置,如果该哈希值算出的位置为空,就直接将a放在数组的该位置,如果该哈希值算出的位置存在值了,那么我们就通过链表的形式,在该位置存放.随着数据不断的增加,数组的默认数组为16,当数组中存在的元素的个数达到数组总长度的0.75(3/4)时,此时就扩充数组为原来数组长度的2倍,原来数组长度为16,此时长度就为32.(这是扩容的情况一),当单链表长度达到8时,并且数组的长度是小于64的时候,此时数组的长度也会扩容,为原来数组长度的2倍(这是扩容的情况二).

当单链表的长度达到8,数组长度达到64的时候,此时链表就会发生树化,用红黑树的形式来存储数据,树化是哪个链表长度达到8,哪个链表就数化,其他链表还是以链表形式存放数据.如果树化后的节点数小于6,就退化为链表.

在这里插入图片描述

map中是在指定位置替换值,不是再去new一个map以键值对存储进去.

/**
* HashMap的初始值容量是16
* 数组扩容阈值是0.75
* 何时扩容:
* 1.HashMap中存储的数据个数超过数组长度*0.75,就要扩容
* 链表的扩容阈值:8
* 2.如果单个链表的长度超过8,但是数组的长度没有超过64,就扩容
* 何时树化(针对链表的):
* 单链长度超大8,数组长度超过64才进行树化
*如果树化后的节点数小于6,就退树化,变成链表
*/

HashMap 基于 Hash 算法实现,通过 put(key,value) 存储,get(key) 来获取 value
当传入 key 时,HashMap 会根据 key,调用 hash(Object key) 方法,计算出 hash 值,根据 hash 值将 value 保存在 Node 对象里,Node 对象保存在数组里
当计算出的 hash 值相同时,称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value
当 hash 冲突的个数(单个单链表的个数):小于等于 8 使用链表;大于 8 且 tab length 大于等于 64 时,使用红黑树解决链表查询慢的问题

哈希map是基于三种数据结构的:数组,链表和红黑树.通过map.put(key,value)向集合中传入数据,通过哈希算法算出传入的key值,所得到的哈希码就是在数组中的位置数组的初始容量为16,当数组元素个数达到数组长度的0.75时,数组会进行扩容,扩容为原来数组长度的2倍,如果算出的哈希码是一样的,就产生了哈希碰撞,HashMap是通过链表和红黑树的数据结构进行处理.当单个单链长度达到8,且数组长度不足64的时候,数组会进行扩容,扩容为原来的2倍,当单链表个数达到8,数组长度达到64的时候,单链表会进行树化,当树的节点个数不足6的时候,就会退树化,成为链表.

Hashtable:古老类,不可存Null值,线程安全

泛型:

  • 在定义类,方法时,声明一个泛型,比如代表任意的一个类型
  • 在类中声明的泛型,在整个类中可以直接使用
  • 作用:保证数据类型的安全避免出现ClassCaseException

泛型定义方法:

public <R> R getInstance(R r) {
		return r;//传入的参数是啥类型,返回的就是啥类型
	}

Day07:

异常:

异常:就是程序中出现一些意料之外的情况(处理不了的情况).

程序中出现异常,之后的代码就不会运行,程序中断!

我们就需要抛出异常,保证代码的健壮性.

常见的异常:

空指针:NullPointerException

数组下标越界:ArrayIndexOutOfBoundsException

字符串下标越界:StringIndexOutOfBoundsException

数学运算异常:ArithmeticException

数字转换异常:NumberFormatException

如何抛出异常?

throw:在方法中,特定的情况下,具体抛出一个异常对象

throws,在定义方法中,声明该方法可能会抛出异常的类型

static void test(int num,Object obj) Throws ArithmeticException,NullPointerException{
    //该方法可能会出现异常:
    //ArithmeticException
   System.out.println(12/num);
    //NullPointerException
    boj.toString();
    
    throws new NullPointerException("空指针异常");
    throws new ArithmeticException("数学运算方法") 
    
}

当我们在方法中用throw抛出异常,该方法声明时也需要用throws声明该方法可能会出现的异常

如何处理异常:try…catch…finally

try:代码块内是可能会出现异常的代码

catch:代码块内是捕捉try代码块中可能出现的异常

finally:不管try中是否有异常,finally一定会执行

分开处理:

static void test(){
    try{
        //可能会出现异常的代码
      System.out.println(12/num);
      Object obj=new Object();
      boj.toString();
        
        //捕获异常
    }catch(ArithmeticException e){
        //对用户友好的提示
        System.out.println("数学运算异常:");
        //打印出异常信息
        e.printStackTrace();
    }catch(NullPointerException e){
        System.out.println("空指针异常:");
        e.printStackTrace();
    }catch(ArrayIndexOutOfBoundsException){
        //该异常没有捕捉到
        System.out.println("数组下标越界异常:");
        e.printStackTrace();
    }
    finally{
        System.out.println("我一定会执行");
    }
}

整合处理:

  • 方法一:
static void test(int num){
    try{
       System.out.println(12/num);
      Object obj=new Object();
      boj.toString(); 
    }catch(Exception e){
        System.out.println("程序出现异常:");
        e.printStackTrace();
    }finally{
        System.out.println("我一定会执行");
    }
}
  • 方法二:
public static void test(int num){
    try{
        System.out.println(12/num);
      Object obj=new Object();
      obj.toString();
    }catch(NumllPointerException | ArithmeticException e){
        System.out.println("程序出现异常:");
        e.printStackTrace();
    }finally{
        System.out.println("我一定会执行");
    }
}

try…catch…finally运行的顺序

//捕获到异常时的运行顺序

public static void test(int num){
    try{
        System.out.ptintln("---1--");
       System.out.println(12/num); //此时出现异常,中断try后面的代码,所有2不会被输出,直接跳动catch代码块中去找对应异常
        System.out.ptintln("---2--");
    }catch(ArithmeticException e){
        //直接来到这里,输出3
        System.out.ptintln("---3--");
    }finally{
        //finally中的代码最后输出4
        System.out.ptintln("---4--");
    }
}

public static void main(String args[]){
    test(0);//12/0就出现了数学运算异常
}
输出:134.

//没有捕获到异常时的运行顺序

public static void test(int num){
    try{
        System.out.ptintln("---1--");
       System.out.println(12/num); //此时没有出现异常,计算12/num并输出,然后继续运行try下面的代码,输出2
        System.out.ptintln("---2--");
    }catch(ArithmeticException e){
        //没有捕捉到异常,不会输出3
        System.out.ptintln("---3--");
    }finally{
        //finally中的代码最后输出4
        System.out.ptintln("---4--");
    }
}

public static void main(String args[]){
    test(2);
}
输出1 6 2 4

异常家族树:

在这里插入图片描述

运行时异常:

  • 是RuntimeException的子类
  • 不需要明文处理该异常(就是不处理它程序也不会报红)
  • 可以通过代码来规避该异常
  • 不需要使用throws声明方法可能抛出的运行时异常
public static void method(Student stu) {
		if(stu==null) {
			return;
		}
		try {
			stu.age=stu.age+1;
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}

检查型异常(编译时异常):

  • 是Exception的子类,但是没有继承RuntimeException
  • 必须明文处理该异常,否则代码会报错
  • 检查型异常多数是和程序之外的资源交互的,不能通过代码规避
  • 如果方法中,抛出检查型异常,必须明文声明throws异常类型

自定义异常:

异常的类名体现异常的作用(异常名字也是何时抛出的时机);

  • 定义异常:

1.首先自定义类继承Exception或者RuntimeException类(运行时异常继承后者,检查型异常继承前者);

2.定义两个构造器:

  • 空参构造器
  • 一个带参数string类型的有参构造
public class NumberOutOfBoundsException extends RuntimeException{//运行时异常
	public NumberOutOfBoundsException() {
		
	}
	
	public NumberOutOfBoundsException(String message) {
		super(message);
	}
	
}

IO流:

文件File

构造器:

  • File(String filePath)
  • File(String parentPath.String childPath)
  • File(File parentPath,String childPath)

文件路径:

  • 相对路径:相对于某个路径下,指明的路径
  • 绝对路径: 包含盘符在内的文件或目录的路径

路径分割符:

  • windows:\
  • unix:/

File类的使用:

  • File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
  • File类声明在java.io包下
  • File类涉及到关于文件或目录的创建,删除,重命名。修改时间,文件大小等方法,并未涉及到写入或者读取文件内容的操作,如果需要写入或读取文件的内容,必须使用IO流完成.
  • 后续File类的对象常会作为参数传递到流的构造器中,指明读取或者写入的"终点"

实例化文件对象:

File file=new File("文件路径");
//可以通过file.exists()方法判断输入的文件是否存在

创建文件:

file.creatNewFile();//返回boolean值,返回是否创建文件成功,创建成功的话就是创建Flie实例化对象时输入的路径里面的文件.

**创建文件夹:**如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目

file.mkdirs();//创建文件目录。如果上层文件目录不存在,一并创建
file.mkdir()//不可隔级创建文件夹

**删除文件:**public boolean delete():删除文件或者文件夹 (要想删除成功,删除文件目录下不能有子目录或文件)

file.delete();

删除文件夹:

file.delete();

流的介绍

流的分类:

  • 操作的数据单位分:字节流,字符流

  • 操作的流向分:输入流,输出流.(以java程序为参照物,流动进java程序的则为输入流,反之为输出流)
    在这里插入图片描述

  • 流的角色不同分:节点流,处理流

  • 可以两两组合:

  • InputStream:输入字节流

  • OutputStream:输出字节流

  • Reader:输入字符流

  • Writer:输出字符流

流的角色:

字节流,字符流,处理流(在流的基础上进行的)

流的体系结构:

在这里插入图片描述

文件读取:

FileReader(输入字符流):

语法:

FileReader fr=new FileReader(new File());

会引起FileNotFoundException异常,用try-catch包起来

记得在finally代码块中关流,fr.close,我们还得先判断fr对象是否为null,不为空再用try-catch关流.

String构造方法的补充:

public String(char value[], int offset, int count)

char[] value:传入一个char型数组,(我们一般会声明char[]数组长度)

int offset:从字符串哪个地方开始传入,一般是0;

int count:获取个数.

fr.read()每次一个字符一个字符的读取

效率太慢了,所有我们定义一个char cbuf[10] 数组,传给read(cubf),就是每一次读10个字符,十个十个的读,取决于char数组的长度

char [] a= {'1','2','3','4','5','6','7'};
String b=new String(a,4,2);
System.out.println(b); //56

输入流演示:

public static void main(String [] args){
    File src=new File("d:\\hello\\world.txt");
    FileReader fr=null;//作为全局变量,为了关流
    try{
        //创建字符输入流对象
        fr=new FileReader(src);
        int length=0;
        //读取数据的容器
        char[] cubf=new char[6];
     //['1','1','1','1','1','1','1','1','1','1','1''1','1',] 13个字符
			//那么fr.read(arr)每次读取6个字符,将每次读取的字符个数传给length,6!=-1
			//所以就将这6个数据添加到字符串中,进行到第三次的时候,length=1了,第四次就=-1
			//就不往字符串中添加数据了
        while((length=fr.read(cubf))!=-1){
           String str=new String(arr,0,length); 
        }
    }catch(Exception e){
        e.printStackTrance();
    }finally{
       if(fr!=null) {
				try {
					fr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			} 
    }
}

递归,文件删除的方法,阶乘

写出文件:

FileWriter(输出字符流):

在这里插入图片描述

fw=new FileWriter(file, true); //FileWriter(file, true);将文件追加到文件后面

默认是覆盖原文件的内容

FileWriter fw=new FileWriter(new File());

会引起FileNotFoundException异常,用try-catch包起来

记得在finally代码块中关流,fr.close,我们还得先判断fr对象是否为null,不为空再用try-catch关流.

fw.wirter() :

在这里插入图片描述

输入输出字符流结合:

public static void main(String[] args) {
		File src=new File("D:\\java\\java.txt");
		
		File dest=new File("D:\\test\\java1.txt");
//		将src的内容复制到desc
		char[] cubf=new char[64];//使用cubf存放每次读取的内容 
		int length=0;//每次读取数据的个数
		FileReader fr=null;
		FileWriter fw=null;
		
		try {
			//将src中的内容读入程序  输入流
			fr=new FileReader(src);
			
			//将读到的内容写进dest文件,输出流
			fw=new FileWriter(dest);
			
			//每次读取的数据长度赋值给length 读取完毕,返回-1
			while((length=fr.read(cubf))!=-1) {
				fw.write(cubf,0,length);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			if(fr!=null) {
				try {
					fr.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if(fw!=null) {
				try {
					fw.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
	}

字节流

  • 字符流只能读取文本内容 txt html java properites css js xml yml
  • 字节流可以读取任何数据 word jpeg 图片 音频 视频

文件输入字节流:FileInputStream

文件输出字节流:FileOutputStream

典型案例:复制图片:

		File src=new File("D:\\java\\img.jpeg"); 
		
		File desc=new File("D:\\test\\img.jpeg");
		//流的创建
		FileInputStream fis=null;
		FileOutputStream fos=null;
		
		//流的操作
		try{
            //节点流与目标文件连接
			fis=new FileInputStream(src);
            //输出流将图片复制到该文件
			fos=new FileOutputStream(desc);
            
			//byte型数组,便于每次读取的大小,也是每次读取的字节放到该数组
            //这里就是立刻读取,就马上输出了,后面的缓冲流就不一样
			byte []byuf=new byte[64];
            //每次读取的字节数
			int length=0;
            //读取字节,存放到byte数组中
			while((length=fis.read(byuf))!=-1) {
                //将读取的字节输出到指定的文件下
				fos.write(byuf, 0, length);
			}

缓冲流:

又叫做处理流,里面包着节点流,

  • 缓冲输入流:BufferedReader

作为缓冲流和输入流FileReader就是将read()方法升级了,提供了readLine()方法,

由原来的一个一个的读取数据变为现在一行一行的读取数据效率有所提高.并没有减少IO次数

  • **缓存输出流:**BufferedWriter

作为缓冲流,该流和原来的FileWriter输出流一样,使用write()方法,但是作为缓冲,

该方法内部提供了一个缓冲区,可以理解为数组.默认缓冲容量为8192,当使用该write()方法时,不会直接将数据输出,而是先将文件缓存到缓存区,当缓存区存不了要存入的数据的时候.

该数据的大小是小于总缓存容量8192,缓存区会先写出数组中已有的内容,然后将该数据放在数组中保存;

该数据的大小是大于总缓存容量8192,缓存区会先把数组中的数据写出去,然后将该数据写出去(节点流(FileWriter)的writer方法输出去);

执行完后要手动执行flush();

这也就是缓冲流的主要作用,不像原来,是读取一个然后输出一个,这样就增加了IO次数,有了缓存大大减少了IO次数.

对象流:

对象流也是处理流,里面会包裹一个流

  • 对象输出流:ObjectOutputStream

案例:将一个Student对象输出到指定路径下:

//实例化Student对象
Student stu=new Student();
String filePath="D:\\java\\test\\obj.data";//指定dest路径
File file =new File(filePath);

//创建流
//文件输出流(参数传的是文件File)
FileOutputStream  fos=null
//对象输出流(参数就是包裹的文件输出流)
ObjectOutputStream oos=null;

try{
    //实例化流
    fos=new FileOutputStream(filePath);
    oos=new ObjectOutputStream(fos);
    oos.writeObject(stu);//输出对象,需要传入目标对象
}catch(Exception e){
    e.printStack
}finally{
    关流..
}

//Class类
public class implements Serializable {//对象所属的类一定要实现可序列化接口
    javabean....
}

反序列化:将有效序列转换成对象的过程

  • 对象输入流:ObjectIutputStream

序列化:将对象转换成有效序列(二进制)的过程,将有效序列从程序输出

File file=new File("D:\\java\\test\\obj.data");
FileInputStream fis=null;
ObjectInputStream ois=null;

try{
    fis=new FileInooutStream(file);
    ois=new ObjectInputStream(fis);
    //将对象读取进来,存放在obj
    Object obj=ois.readObject();
    //可以判断一下obj是否和stu为同一个对象
    if(obj instanceof Student){
        Student s=(Student) obj;
        boolean isEqual=s==stu;
        System.out.println(isEqual);//答案是false
    }
    }
}catch(){
    关流.....
}

转换流:

将字节流转换为字符流.

  • InputStreamReader
  • OutputStreamWriter

序列化:

  • 工具流:ObjectOutputStream/ObjectInputStream
  • 想要序列化必须实现Serializable接口
  • 序列化出去的对象和反序列的对象不是同一个对象,反序列化会创建新的对象

使用常量serializeVersionUID控制类型的序列化版本,如果该值发生变化,之前序列化去的数据(对象),就不能反序列化回来.

序列化对象,这个对象中的属性以及保存(数组中)的数据也必须是可序列化的.

关键字:transient.

屏蔽属性序列化/反序列化

在这里插入图片描述

常见的问题:

什么是序列化?

  • 将对象转换成有效序列(二进制)

怎样实现序列化?

  • 该对象必须是可序列化的(实现Serializable接口)
  • 文件对象(File):指向对象序列化的文件目录
  • 文件字节输出流:用流连接文件对象
  • 处理流(对象流)ObjectOutputStream:将文件输出流包裹起来,传输对象(oos.writeObject(对象));

序列化要注意什么?对序列化的理解?说一说java序列化

transient作用

  • 避免类中的属性被序列化,该属性为默认值

  • class T{
        String name;
        transient String gender;//该属性,序列化后的对象,打印这个对象,该属性为默认值null.
    }
    

serialVersionUID版本号的作用.

是用于在序列化和反序列化过程中进行核验的一个版本号。如果接收方为对象加载的类与相应发送方类的serialVersionId不同,则反序列化将导致InvalidClassException。且该字段必须是staticfinal的且类型为long.

private static final long serialVersionUID=12345L;

克隆:

什么是克隆:

  • 将一个对象复制一份,形成一个新的个体

怎么实现克隆:

  • 实现Cloneable接口
  • 重写clone方法

注意:

  • clone方法定义在Object类中
  • 克隆会创建新的对象
//克隆
class Student implements Cloneable{
    String name;
    String id;
    
    //重写克隆方法
	public Object clone() throws CloneNotSupportedException{
		return super.clone();
	}
    
    javaBean
}

public static void main(String args[]) throws CloneNotSupportedException{
    //实例化一个Student对象
   Student s=new Student("小胖",1);
    
    //克隆对象
    object o=s.clone();//直接抛出异常,该克隆是一个Object对象
    
    //比较两个对象是否为一个对象
    System.out.println(s==o);//false
    
}

深拷贝/浅拷贝

浅拷贝:只克隆自身对象

//定义学生类
class Student implements Cloneable{
    String name;
    Teacher Tname;
    
    Student(String name,Teacher t){
        this.name=name;
        this.Tname=t;
    }
    
    //重写克隆方法
    public Object clone() Throws CloneNotSupportedException{
        return super().clone;//抛出异常
    }
    ....
}
//定义教师类....

//测试main
public static void main(String args[]) throws CloneNotSupportedException{
    //实例化对象
    Teacher t=new Teacher("王老师");
    Student s=new Student("小明",t);
    //克隆一个对象s
    Object clone_s=s.clone()//抛出异常
    //获取克隆对象的属性
    Teacher filed=clone_s.Tname;  
    //修改克隆对象的属性
    filed.setName("李老师");     
    System.out.println(s);
    System.out.println(clone_s);
}

此时我们只克隆了学生类,老师类并没有克隆
这就是浅拷贝,那么老师这个属性不管是在学生克隆前,还是学生克隆后,该属性都没有克隆,都是指向一个对象的
所有此时我将王老师修改成了李老师,我Student克隆前后的老师都是李老师这一对象!!!


深克隆:也会克隆自身对象中的属性(引用类型/对象).

//定义学生类
class Student implements Cloneable{
    String name;
    Teacher Tname;
    
    Student(String name,Teacher t){
        this.name=name;
        this.Tname=t;
    }
    
    //重写克隆方法
    public Object clone() Throws CloneNotSupportedException{
        //克隆自身也要克隆自身的引用对象
        Student s=(Student)super.clone();//克隆Student
        Teacher t=(Teacher)Tname.clone();//克隆教师类
        
        //将克隆的教师类克隆后作为属性赋给Tname
       s.Tname=t;
        
        return s;
    }
    ....
}
//定义教师类
public Teacher() implements Cloneable() throws CloneNotSupportedExceptiom{
    String name;
    Teacher(Teacher name){
        this.name=nane
    }
    
    //也要重写克隆方法(在Object中)
    public Object clone(){
        return super().clone();//调用Object的克隆方法
    }
    
    .....javabean
}

//测试main
public static void main(String args[]) throws CloneNotSupportedException{
    //实例化对象
    Teacher t=new Teacher("王老师");
    Student s=new Student("小明",t);
    //克隆一个对象s
    Object clone_s=s.clone()//抛出异常
    //获取克隆对象的属性
    Teacher filed=clone_s.Tname;  
    //修改克隆对象的属性
    filed.setName("李老师");
    
   System.out.println(s);
   System.out.println(clone_s); 
}

在这里插入图片描述

  • 如果是浅拷贝,那么Teacher的name都是王五.
  • 深拷贝的步骤:
  • 克隆类及克隆类中的引用对象都需要实现Cloneable接口,重写clone(),引用对象的重写方法只是super().clone即可,自身类则需要在重写的clone()方法中克隆自身类,及引用对象类,然后将克隆的引用对象赋给自身类中对应的属性.
  • 在测试main方法中,实例化所需对象,然后克隆自身类,通过克隆后的自身类,获取克隆自身类中的属性,然后set方法修改引用类属性,最后打印克隆前和克隆后的自身类,会发现克隆前后的引用类属性是不一样的!!!

原型设计模式(克隆的使用)

Day08

线程

线程是操作系统能够进行运算调用的最小单位,包含在进程之中,一个进程有多个线程,每条线程并行执行不同的任务

实现线程的方式:

  • 继承Thread类,重写run方法

  • class Test extends Thread{
        //重写run方法
        @Override
    	public void run() {
    		//该方法就是存放线程逻辑的代码,线程是干嘛的就是写在此处
    	}
    }
    
    //直接创建线程类的对象
    public class T{
        public static void main(String[] args) throws InterruptedException{
            //实例化继承Thread类的对象
            Test t=new Test();
            t.start();//启动线程
            //哪个线程运行到这就休眠1秒
            Thread.sleep(1000);
        }
    }
    
  • **实现Runnable接口,重写run()方法再实例化一个线程运行该任务

  • class Test implements Runnalbe{//实现Runnable接口
        //重写run方法
        @Override
    	public void run() {
    		//该方法就是存放线程逻辑的代码,线程是干嘛的就是写在此处
    	}
    }
    
    public class T{
        //实例化一个实现Runnalbe接口的类
        Test run=new Test();
        //实例化Thread对象,将run传入
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);
        //开启线程
        t1.start();
        t2.start();
    }
    

比较前面两种方式:

由于Java是单继承,多实现的,实现Runnable更灵活,还可以继承别的类,并且Runnable对象中的数据对所有对象(t1,t2)都是共享的.

  • 怎么理解数据是共享的:继承Thread的方式,是每次实例化这个类,每一次都是一套自己的属性,对其修改或者使用也是其中的一个,内存是独立的.
  • 而实现接口的方式,是实例化一个实现接口类,然后我们是通过实例化多个Thread类的对象,在每一个Thread的对象中传入这一个实现Runnable接口的类的对象,就相当于多个Thread类一起使用这个run对象.

线程的声明周期:

在这里插入图片描述

新建状态:

Thread t1=new Thread(); t1处于新建状态

就绪状态

t1.start() 进入就绪状态

yield():礼让CPU资源,让CPU重新分配资源,运行状态------>就绪状态 静态方法

运行状态

CPU分配时间给该线程,线程就是运行状态.

等待状态

等待CPU分配资源的状态

sleep():线程等到执行的毫秒数,进入就绪状态

join():在B线程中调用A.join()方法,B等待A运行完毕后再运行线程B

class A extends Thread{
    @Override
	public void run() {
		System.out.println("A綫程開始執行,A綫程休眠5秒");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("A綫程運行完畢");
	}
}

class B extends Thread{
    Thread t=null;
    
    //构造方法,初始化t
    public ThreadB(Thread ta) {
		super();
		this.ta=ta;
	}
    
    	@Override
	public void run() {
		System.out.println("B綫程正在運行");
		try {
            //构造器传入a对象,ta=a,调用join()那么会在B线程优先执行A线程
			ta.join();//会有异常,重写方法,只能处理异常不能抛出
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("B綫程运行完毕");
		
	}
}

class Test{
    public static void main(String args[]){
        实例化线程对象
        A a=new A();
        //将a传入b中
        B b=new B(a);
        
        //启动线程
        a.start();
        b.start();
    }
}
阻塞状态

在这里插入图片描述

死亡状态

线程运行完毕或终止线程或者抛出异常:

stop() 将要遗弃的方法,不建议使用

interrupt(); 只有在等待状态才会终止,在运行时的线程不会结束,终止方式是抛异常

线程的方法:

  • sleep(),让线程睡眠,单位为毫秒
  • join():A线程在B线程中使用A.join(),A线程优先运行,再运行B线程
  • yields():将线程的CUP让给其他线程
  • run():执行线程代码的逻辑,线程的功能
  • start():启动线程
  • currentThread():静态方法,获取当前线程
  • setPrioritity(int):设置线程优先级

线程优先级:1~10,默认为5

优先级越高,获取CUP几率越大.

  • setDaemon(boolean):守护线程

比如:GC(垃圾回收)

只有所有的业务线程都结束,守护线程才结束.

public static void main(String[] args) {
		Runnable r=new Runnable() {
			//对类的定义  (匿名内部类)
			
			public void run() {
				for(int i=0;i<100;i++) {
					System.out.println(Thread.currentThread().getName()+"  "+i);
				}
			}
		};
		Thread t1=new Thread(r);
		Thread t2=new Thread(r);
		//设置守护线程
		t1.setDaemon(true);
		
		//设置优先级
		t1.setPriority(3);//1~10,默认优先级为5
		t2.setPriority(6);
		//优先级越高,获取CPU的几率越高.
		t1.setName("t1");
		t2.setName("t2");
		
		t1.start();
		t2.start();
	}

使用Lambda表达式

	public static void testInterrupt() {
		Thread t=new Thread(() ->{
			System.out.println("hello");
		});
	}

线程安全:

多个线程去处理同一对象,数据不会发生错乱和丢失,能够确定线程的安全(StringBuffer)---------线程安全

StringBuilder strb=new StringBuilder();
		Runnable run=new Runnable() {
			@Override
			public void run() {
				for(int i=0;i<100;i++) {
					strb.append('a');
				}
			}
		};
		
		Thread t1=new Thread(run);
		Thread t2=new Thread(run);
		
		t1.start();
		t2.start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println(strb.length());

多个线程处理同一个对象,发生数据错乱或丢失的情况,就叫做线程不安全,是该对象是线程不安全的(StringBuilder)

StringBuffer strb=new StringBuffer();
		Runnable run=new Runnable() {
			@Override
			public void run() {
				for(int i=0;i<100;i++) {
					strb.append('a');
				}
			}
		};
		
		Thread t1=new Thread(run);
		Thread t2=new Thread(run);
		
		t1.start();
		t2.start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println(strb.length());

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

这就是线程不安全所导致的,会出现数据的混乱,缺失;

原因:

  1. 线程是抢占执行的。
  2. 有的操作不是原子的。当 cpu 执行一个线程过程时,调度器可能调走CPU,去执行另一个线程,此线程的操作可能还没有结束;(通过锁来解决)

线程之间是独立的,比如,线程A和线程B一起进入了str.append()追加方法,现在将要在位置20这个地方添加元素,A添加了,然后就出去了,此时B线程不知道A的操作,所有也向20这个位置添加了一个元素,将A之前加的数据给覆盖了,这就造成了数据的缺失.

这里出现角标越界是因为底层数组扩容的时候出现错误.

解决线程不安全的方法:

  • 关键字:synchronized 也具有锁对象

同一时间,确保只有一条线程进入方法运行,达到同步效果

可以修饰方法:修饰非静态方法,锁对象就是this,修饰静态方法,锁对象就是类对象:类名.class

//synchronized修饰方法   
public static  synchronized void add(Object obj) {
	list.add(obj);
}

可以修饰代码块;

public class Easy2 {
	public static void main(String[] args) {
		Object lock=new Object();
		 //将lock对象传入进去
		ThreadA a=new ThreadA(lock);
		ThreadB b=new ThreadB(lock);
		
		a.setName("a");
		b.setName("A");
		
		a.start();
		b.start();
	}
}


class ThreadA extends Thread{
	Object lock;
	
	public ThreadA(Object obj) {
		this.lock=obj;
	}
	
	@Override
	public void run() {
		print();
	}
	
	public void print() {
		//达到同步的关键在于锁对象
		synchronized (lock) {//this只能做到当前对象的线程安全,如果this代表不同的对象,不能达到同步的效果.
			//同一个锁对象管理多个代码块或方法(多个方法都用了同一个锁对象),同一时间,这些方法只能有一个线程运行.
			//一个锁对象,同一时间只能放行一条线程,跟是否是一个方法/代码块没有关系
			for(int i=0;i<100;i++) {
				System.out.println(this.getName()+"   "+i);
			}
		}
		
	}
}



class ThreadB extends Thread{
	Object lock;
	
	public ThreadB(Object obj) {
		this.lock=obj;
	}
	
	@Override
	public void run() {
		print();
	}
	
	public void print() {
		//达到同步的关键在于锁对象
		synchronized (lock) {//this只能做到当前对象的线程安全,如果this代表不同的对象,不能达到同步的效果.
			//同一个锁对象管理多个代码块或方法(多个方法都用了同一个锁对象),同一时间,这些方法只能有一个线程运行.
			//一个锁对象,同一时间只能放行一条线程
			for(int i=0;i<100;i++) {
				System.out.println(this.getName()+"   "+i);
			}
		}
		
	}
}

注意事项:

  1. synchronized时,一定要注意,对于同一个业务的多个线程加锁对象,一定要是同一个对象(加同一把锁)。
  2. synchronized修饰代码块时,代码块在静态方法块中时,不能使用this对象

**锁:**解决线程不安全的方式二

  • 悲观锁:在业务代码中,总以为有别的线程会来修改当前线程正在处理的数据,添加锁对象进行同步管理.有锁对象就是悲观锁
  • 乐观锁:在代码业务中,在业务代码中,乐观到以为,当前线程处理数据时,没有其他线程来干扰,不加锁对象,只是通过验证来实现数据安全.

两种实现乐观锁的方法:

  • 比较版本号 没有进行锁的操作,但是线程安全(多线程下没有出现数据错乱).
  • CAS 比较并替换.

锁对象的方法:只有锁对象才可以去调用:

  • wait()
  • notify()/notifyall()
static List list=new ArrayList();
public static void add(Object obj){
    synchronized(list){//list集合就是锁对象
       System.out.println(Thread.currentThread().getName()+"进入代码块执行唤醒notify");
       list.notify();
        try{
            Thread.sleep(1000);//哪个线程执行到这就sleep.在睡眠期间没有其他线程进来执行,还是占据着锁,没有被释放
        }catch(InterruptedException e){
            e.printStackTrance();
        }
        
        try{
            System.out.println(Thread.currentThread().getName()+"进入代码块执行wait关进小黑屋");
            //线程被wait了,那么将会失去锁,别的线程拿到锁进来.
            list.wait();
            //以该对象为锁的同步代码块当中,才可以调用该对象的wait()和notify(),锁对象让运行到该行代码的线程等待.被唤醒的线程继续在外面等待,等到cup资源后,执行wait后面的代码.
        }catch(InterruptedException e){
            e.printStackTrace();
        }
       //被唤醒的线程.获取cup后直接执行这里 System.out.println(Thread.currentThread().getName()+"进入代码块执行唤醒notify");
        list.notify();
        
    }
}

public static void main(String[] args) {
    Thread t1=new ThreadZ();
	Thread t2=new ThreadZ();
    
    t1.setName("t1");
	t2.setName("t2");
    
    t1.start();
	t2.start();
}

class ThreadZ extends Thread{
	@Override
	public void run() {
			Easy5.add(0);
	}
}
sleep和Thread有啥区别:

sleep是Thread的静态方法,wait是定义在Object中锁对象调用的方法
sleep是等待时候后自醒
wait必须使用其他线程使用notify唤醒
sleep保持锁状态, wait会放弃锁

重量级分类:

  • 偏向锁:

  • public class Easy6 {
    	public void teat() {
    		synchronized (this) {
    			System.out.println("------进入第一个同步代码块");
    			synchronized (this) {//偏向锁:两个同步代码块相同的锁对象,A线程进入第一个同步代码块时,已经获取了锁对象的权限,在第二个同步代码块中就不需要再获取锁权限.
    				//这时候,该锁对象对于该线程就是一个偏向锁.
    				System.out.println("----------进入第二个同步代码块");
    				try {
    					Thread.sleep(1000);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    				
    				System.out.println("----------结束第二个同步代码块");
    			}
    		}
    	}
    }
    
    
  • 轻量级锁/自旋锁:当一个线程要进入A锁对象的同步代码块.发现已经有线程获取锁对象权限了该线程就会自旋尝试获取锁对象的权限,这是A锁对象对于该线程就是轻量级锁,也叫自旋锁(上限自旋15次)

  • 重量级锁:如果一个线程,自旋获取锁对象的权限,一直没获取到,该线程就会进入等待状态,这时该锁对象对于该线程就是重量级锁.

重入性:

  • 可重入锁:java中都是可重入锁
  • 非可重入锁

公平性:

  • 公平锁:先来后到
  • 非公平锁:随机的,synchronized

关键字:volatile

public class Easy7 {
	 static boolean b=true;
	//保证变量对每个线程的可见性
	//所有的线程访问该变量都要从主内存空间读取该变量的数据


	
	public static void main(String[] args) throws InterruptedException {
		ThreadE t1=new ThreadE();
		ThreadE t2=new ThreadE();
		t1.start();
		t2.start();
		
		Thread.sleep(1000);
		b=false;
		System.out.println("已经将b改为false");
	}
}
class ThreadE extends Thread{
	@Override
	public void run() {
		while(Easy7.b) {
			
		}
		System.out.println(this.getName()+"结束");
	}
}

在这里插入图片描述

如上代码,首先定义了一个静态布尔类型的变量b,赋值为true,然后再main方法中,创建2个线程,启动2个线程,并给b赋值为false,执行输出语句,当我们启动线程start()时,就会运行ThreadE中的run方法,这是线程逻辑代码的地方,run方法中就是个循环,如果b为true就会一直死循环,这是我们给b的初始值,但是我们发现输出结果,并没有线程结束的语句,说明现场一直在while中死循环,执行不到结束输出语句,我们在Main方法中已经修改了b的值,为啥在线程中b还是true,因为线程不知道b值已经改变了,所有我们需要使用volatile.让线程知道我们已经修改了b值.这样run方法才不会进入死循环,输出结束语句

在这里插入图片描述

  • volatile对应的变量可能在你的程序本身不知道的情况下发生改变
  • 比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量
  • 你自己的程序,是无法判定何时这个变量会发生变化
  • 还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
  • 对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,**而不会利用cache当中的原有数值,**以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。

枚举

使用:

  1. 枚举类的理解:类的对象只有有限个,确定的.
  2. 当需要定义一组常量时,强烈建议使用枚举类
  3. 如果枚举类只有一个对象,则可以作为单例模式的实现方法

定义:

方式一:JDK5.0之前,自定义枚举类

方式二:JDK5.0之后,可以使用关键字enum定义枚举类

常用方法:

vaules():返回枚举类型的对象数组,该方法可以很方便的遍历所有的枚举值

valueOf(String str):可以吧一个字符串转为对应的枚举类对象,要求字符串必须是枚举类对象的"名字",如不是,会有运行时异常"IllegalArgumentException。

toString():返回当前枚举类对象常量的名称

注意:

  • 枚举类没有对外的构造方法
  • 实例的声明,必须要放到首行

反射


反射中的核心类是Class

程序运行阶段,可以动态获取类中的描述(属性,方法,构造方法)

获取这些描述后,就可以修改(获取)属性值.这类的描述就是Class类的对象,该对象就是类对象(运行时类)

每个类都有唯一一个对象来描述这个类,这个对象就属于Class类的对象


获取类对象(获取对类描述的对象)的方式

  • 通过类名.class (前提是已经知道该类)
Class c=Student.class;       (这就是获取Student对象的类对象)
  • 通过对象.getClass() (前提是已经知道类的对象)
Student stu=new Student();

Class c=stu.getClass();
  • 通过Class.forname()
Class c=Class.forName("全类名");
全类名:包名+类名

类对象获取属性

返回值Field类

  • getFiled(String name):参数为要获取的类属性,返回值为Field
  • getFields():返回值为Field[]数组
  • Field对象.set(Object obj, Object value)

可以获取当前类中能访问到的public属性(包括父类/接口中的)

可以获取当前类中能访问到的所有public属性,放进Fields数组中

Class Studnet extends Teacher implements A{
    public static final int a=10;
    public static int  b;
    private int c;
    int d
}
Class Teacher{
    public String s;
    String ss;
}

Interface A{
    public static final int AA=10;
}
Student s=new Student();
Class c=s.getclass();
//获取类对象c的属性
Field f_a=c.getField("a");//参数为要获取的属性名
Field[] f_arr=c.getFiled();
//会返回Student类中可以访问的所有的public属性:a,b,s,AA

//通过属性描述f_a修改某一个类的对应属性:
f_a.set(s,20);
  • getDeclaredField(String field):参数为要获取的类属性,返回值为Field
  • getDeclaredFileds:返回值为Field数组

可以获取当前类中声明的属性(父类,接口啥的别来,但是自身类除了public以外的修饰符也可以获取).

可以获取当前类中声明的所有属性.

Field f_a=c,getDeclaredField("a");
Field f_arr=getDeclareFields();//获取Student类中声明的属性
//会获取到a,b,c,d:

反射可以破坏封装性:

setAccessible(boolean);

当boolean为true,且此时通过反射获取到类对象的属性,我们可以通过属性,改变具体某一个对象的属性值,这就打破了面向对象的封装性

Class c=Student.calss;
Field f=c.getDeclaredField("name");
//此时f就是类对象的属性
//设置可进入
f.setAccessible(true);
//修改xx类的属性值
set(Object obj,Object val)
f.set(stu,"李四");
将stu对象中的name属性修改为李四

类对象获取方法

返回值为Method类

  • getMehod(String name, Class<?>… parameterTypes):参数一为要获取的类的方法名,参数二为可变参数(会考虑三要素:数据类型,顺序,数量)
  • getMethods():返回一个Method[]数组.
  • invoke((Object obj, Object… args)):参数一为运行哪个对象,参数二为形参列表,根据方法的参数属性赋相应的值

获取类中能够访问到的public的方法;

获取类中能够访问到的所有public方法;

Class Student{
    
    public void test() {
		System.out.println("Student中的test方法1");
	};
	
	public void test(String str,int num) {
		System.out.println("Student中的test方法2");
	};
}
public class test{
    Student t=new Studnet();
    Class c=t.class;
    
    //获取方法
    //不带参的方法
    Method m=c.getMethod("test");//获取Student为test的无参方法
    //带参的方法
    Method m1=c.getMethod("test",String.class,int.class);//h后面两个为方法为test中的参数的class
    
    //运行方法
    m.invoke("t");//运行不带参的方法,直接写入一个对象,表示运行的是哪个对象中的方法为test.
    
    m.invoke("t","123",12);运行带参的方法,写入具体方法对象,再写上具体的类型的值
}
  • getDeclaredMethod():获取定义在该类中的方法
  • getDeclaredMethods():获取定义在该类中所有的方法的数组

类对象获取构造方法

返回Constructor类

获取构造器,方法中不会传入类对象,因为构造器名和类名想同

  • getConstructor(Class<?>… parameterTypes):一个参数,可变参数
  • getConstructors():无参
  • newInstance(Object … initargs):Constructor.netInstance(),将构造器类型的具体赋值,创建对象成功

通过参数列表获取指定的public构造方法

获取所有的public 构造方法

Class Student{
    public int id;
    public String name
        
    public Student(){
	}
    
    public Student(String name){
        this.name=name;
    }
    
    public Student(String name,int id){
        this.String=name;
        this.id=id;
    }
}

public class Test{
    Student s=new Student();
    Class c=s.class;
    
    //获取构造器
    //获取一个参数的
    Constructor con=c.getConstructor(String.class);//参数直接写构造器中参数的类型.class
    
    //获取两个参数的构造器
    Constructor conn=c.getConstructor(String.class,int.calss);
    
    //此时就有了构造器,可以创建对象了
    Object obj=conn.newInstance("小平",21);//这是创建对象的第四种方式
}
  • getDeclaredConstructor():通过参数列表获取构造方法
  • getDeclaredConstructors():获取所有的构造方法

通过无参构造构造方法创建实例(这就是在创建一个类是,往往会提供一个无参构造的方法)

Class c=Student.class;
//不需要去使用getConstructor()来创建构造器,然后用构造器调用newInstance()实例化对象

//直接类对象c使用方法创建被描述的类中的空参构造
obj=c.newInstance();

创建对象的四种方式:

  1. new的方式
  2. 反序列化
  3. 克隆
  4. 反射

判断修饰符(属性,方法,构造器):

在这里插入图片描述

获取父类和父级接口

在这里插入图片描述

解释每个类只有一个对象来描述xx类

Class c=Student.class;//Student对象的类对象c
Student stu=new Student();
Class cc=stu.getClass();//Student对象的类对象cc
//看通过两种方式来描述Student类的这两个类是否==
System.out.println(c==cc);//true:为一个对象

网络编程:

  • 网络编程有两个主要的问题:
  1. 如何准确地定位网络上一台或多台主机,定义主机上的特定的应用
  2. 找到主机后如何可靠高效的进行数据传输.
  • 网络编程中的两个要素:
  1. 对应问题1:IP和端口号
  2. 对应问题2:提供网络通信协议:TCP/IP参考模型(应用层,传输层,网络层,物理+数据链路层)
  • 通信要素之一:IP和端口号
  • 1.ip:唯一的标识:Internet上的计算机(通信实体)
  • 2.在java中使用InetAddress类代替IP
  • 3.IP分类:IPV4,IPV6;万维网,局域网
  • 4.域名:www.baidu.com www.mi.com www.sina.com
  • 5.本地回路地址:127.0.0.1,对应着localhost
    6.如何实例化InetAddress:两个方法:getByName(String host) getLoalhost()
  • 两个常用方法;getHostName(),getHostAddress()
    7.端口号:正在计算机上运行的进程
  • 要求:不同进程有不同的端口号
  • 范围:被规定为一个16位的整数0~65535
  • 端口分类:
  • 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口
  • 80,FTP占用端口21,Telnet占用端口23)
  • 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占
  • 用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
  • 动态/私有端口:49152~65535。
  • 8.端口号与IP地址的组合得到一个网络套接字:Socket

函数式接口:

只有一个未实现的方法为函数式接口

常见的函数式接口:Runnable

public interface EasyInterface {
	String method();
    
    jdk8新特性:
接口中可以写带有方法体的方法,但是必须写default权限修饰符
    default void test() {
		
	}
	
}

Lambda表达式:

只能去处理函数式接口

public interface EasyInterface2 {
	int sum(int a,int b);
}

public interface EasyInterface2 {
	String test(){
        
    }
}


public class Easy2{
    public static void main(String[] args) {
        //不用表达式,则这样书写
        EasyInterface ei=new EasyInterface() {
			@Override
			public String method() {
				System.out.println("--------------");
				return null;
			}
		};
        
        //不带参数的接口方法
        ei= ()->{
			System.out.println("你好");
			return "";
		};
        
        //带参数的接口方法
		EasyInterface2 ei2=(a,b)->a+b;
		
		EasyInterface2 ei22=(a,b)->{return a+b;};
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值