CoreJava笔记

文章目录

chap01-Java入门

学习方法

  • 笔记 课上简单记一下笔记、详细记录问题;课下整理一份属于自己的笔记,推荐手写笔记
  • 问题 先独立思考0~5分钟;再问同学、问老师;最后总结问题,确保自己真正会了
  • 代码 多写!多写!多写! 提高手速、熟悉代码

Java

Java发展史

  • 1995年 Sun公司发布Java
  • 1996年 发布了JDK1.0
  • 1999年 发布JDK1.2 逐渐流行 J2EE(企业版) J2SE(标准版) J2ME(移动版)
  • 2004年 发布了JDK1.5
  • 2005年 改名为JDK5.0 JavaEE JavaSE JavaME
  • 2006年 正式开源,走向伟大
  • 2009年 Sun公司被Oracle收购
  • 2011年 发布了JDK7.0
  • 2014年 发布JDK8.0 增加了许多实用的新特性
  • 2018年 JavaEE正式由Eclipse基金会(开源组织)负责,JakartaEE(雅加达)

Java的优势(特点)

  • 简单性 比C简单
  • 跨平台
  • 纯面向对象
  • 开源、丰富的编程资源

Java的运行机制

  • 编译型 c c++ c# uc oc

    • 源文件----编译器—》机器码文件----》运行机器码文件
    • 优点: 效率高 编译一次,运行多次
    • 缺点:不能跨平台
  • 解释型 JS 脚本语言

    • 源文件----解释器—》逐行将源文件中的代码解释成机器码,逐行运行
    • 优点:跨平台
    • 缺点:效率低 每次执行都需要重新解释运行
  • Java的运行机制: 先编译,后解释

    • 源文件----编译器—》先编译成平台中立的字节码文件(.class文件)—解释器----》逐行解释.class文件,逐行运行
    • 特点: 既可以跨平台,效率又高

环境搭建

  • JDK的安装 尽量不要修改安装路径,双击打开,直接下一步

    • JDK的卸载:通过控制面板
  • 环境变量的配置

    • JAVA_HOME: JDK的安装路径 C:\Program Files\Java\jdk1.8.0_92
      • 作用:告诉电脑上其他需要使用JDK的软件,JDK的安装位置
    • Path: JDK的bin目录 C:\Program Files\Java\jdk1.8.0_92\bin 推荐使用:%JAVA_HOME%\bin
      • 作用:告诉电脑,JDK自带的命令程序在哪儿
    • CLASSPATH: .
      • 作用: 类路径 .(代表当前路径) JDK5.0后不需要配置
  • 名词解释

    • JDK :Java开发工具包 Java开发程序员必备
      • JDK = JRE + 编译器 + 类库 + 调试工具
    • JRE:Java运行环境 用户使用
      • JRE = 解释器 + JVM
    • JVM:Java虚拟机 所有的Java程序都是运行在JVM中;屏蔽底层操作系统的差异,为Java程序提供跨平台运行
  • 开发工具

    • Notepad++ 低级开发工具,前期使用,使用打基础阶段,提高代码熟练度

第一个Java代码

基础语法

  1. 创建一个Java源文件(.java文件)

  2. class 类 盛放代码的容器、Java程序的基本组成单元

    一个源文件中可以写多个类

  3. 类名 区分不同的类

  4. {} 划分类的范围

  5. public static void main(String[] args){
    		
    }
    主函数,程序运行的入口,只有写在主函数中的代码才可以运行到
    
  6. System.out.println();    
    换行打印输出语句:可以将()中的数据输出到控制台或黑窗口中
    System.out.print();  不换行打印输出语句
    
  7. 编译命令: javac 源文件名.java

    注意: 当编译成功后,源文件中的每个类都会对应生成一个.class文件

  8. 解释命令:java 类名

补充:

打开黑窗口的方式:

  1. win键+r键,再出现的窗口中输入cmd
  2. 通过notepad++

常用的DOS命令:

命令作用
盘符名:切换盘符
cd 文件夹名进入该文件夹
cd …退出当前文件夹
cd \回到盘符
dir查看当前目录下的所有文件
cls清屏

公开类

public class 公开类 公共的类,即可以随意访问的类

public 公开的、公共的

使用要求:

  1. 公开类类名必须和源文件名一致
  2. 每个源文件中最多只能有一个公开类,可以没有

Package

概念: 包 类似于文件夹

作用1: 帮助管理.class文件 (前期)

作用2: 划分项目结构,相同相似作用的源文件放在同一包下 (后期)

语法: package 包名1.包名2.包名3;

注意:
    当使用了package后,原有的编译命令也可以使用,原有的解释命令不能使用
    推荐(强制)使用
        带包编译命令:javac -d . 源文件名.java
        带包解释命令:java 包名.类名

使用要求:

  1. package语句,必须写在源文件中有效代码的第一个行
  2. 每个源文件中最多只能有一个package语句,可以没有
  3. 包名可以写多个,用.连接,代表多层包结构

编码规范

书写规范

  • 每行只写一行代码
  • 层级之间要有缩进 使用Tab缩进,不要使用空格

标识符命名规范

  • 语法:硬性规定,必须遵守
    • 标识符只能由数字、字母、_、$组成,数字不能开头
    • 严格区分大小写
    • 没有长度限制
    • 不能使用关键字、保留字(goto,const)、特殊单词(true、false、null)命名
  • 规范:不是硬性规定,可以不遵守,但是强烈建议遵守
    • 望名生义
    • 类名中的所有首字母大写
    • 变量名、函数名首单词首字母小写,其他单词首字母大写
    • 包名全小写
    • 常量名全大写,单词之间使用_连接

注释

概念:描述性的文字

作用:可以对下面的代码做一些解释说明;注释内容不会参与代码的编译过程,自然也不会运行;常常将一些不需要运行但也不想删除的代码注释掉;

  • 单行注释

    // 注释内容
    
  • 多行注释

    /* 
    	注释内容
    	注释内容
    */
    
    /* 
    class Test{
    	public static void main(String[] args){
    		//System.out.print("Hello");
    		System.out.print("World");
    		
    	}
    } 
    */
    
  • 文档注释

    作用:生成一套网页形式的代码说明文档

    特殊编译命令: javadoc -d 文件名 源文件名.java

    注意:只能写在公开类、主函数上面

    语法:

    /**
    这是一个公开类,public 代表公开公共的
    First是公开类类名,必须和源文件名保持一致
    */
    public class First{
    	/**
    	这是一个主函数,是程序运行的入口
    	*/
    	public static void main(String[] args){
    		System.out.println("我的第一个公开类");
    	}
    }
    

chap02-数据类型与运算符

变量

概念: 是内存中的一块儿存储空间,用来存储数据,是存储数据的基本单元

变量的组成: 数据类型 变量名 值(数据)

变量(虚拟的盒子) 实体盒子

数据类型 类型、材质

变量名 名字、标号

值(数据) 具体物品

语法:

1. 声明并赋值    最常用
    数据类型 变量名 = 值;  例如: int a = 10;
2. 先声明,再赋值
    声明:数据类型 变量名; 例如: int a;
    赋值:变量名 = 值; 例如: a = 10;
3. 同时声明多个相同类型的变量并赋值
    数据类型 变量名1 = 值1,变量名2 = 值2,变量名3 = 值3;
    例如:int a = 1,b,c = 3;
	等价于:
        int a = 1;
		int b;
		int c = 3;

注意: Java是强类型语言,变量所存储的值的类型必须与变量的数据类型一致!

数据类型

  • 基本数据类型
    • 4类8种
      • 整数类型:byte short int long
      • 浮点数类型: float double
      • 字符类型: char
      • 布尔类型: boolean
  • 引用数据类型(对象类型) 无数种!
    • String : 字符串类型

基本数据类型

  1. 整数类型

    数据类型字节长度取值范围取值范围(十进制)举例
    byte1B-27~27-1-128~127byte b = 10;
    short2B-215~215-1-32768~32767short s = 10;
    int4B-231~231-1-2147483648~2147483647int i = 10;
    long8B-263~263-1-9223372036854775808~9223372036854775807long l = 10L;

    注意:Java中默认的整数类型为int,最常用的也是int

    补充:

    ​ 计算机就是一系列的电路开关,每个开关都有两种状态,开或者关,开即1,关即0;

    ​ 每个1或者0,都存为一个位(bit,比特),最小的存储单元

    ​ 每8个位存储为一个字节(Byte),字节是最小的存储单位

    ​ 通常使用b代表位,B代表字节,即1B=8b;

  2. 浮点数类型-小数类型

    数据类型字节长度取值范围举例
    float(单精度浮点数)4B-3.4E38~3.4E38float f = 1.25F;
    double(双精度浮点数)8B-1.79E308~1.79E308double d = 2.5;

    注意: Java中默认的浮点数类型为double,最常用的也是double

  3. 字符类型-char

    数据类型字节长度取值范围字符集
    char2B0~65535Unicode(16进制)字符集

    注意:Unicode兼容ASCII字符集

    char类型的三种赋值方式:

    1. 使用’‘引起来的单个字符 char c1 = ‘A’; char c2 = ‘中’;
    2. 整数形式 char c3 = 65;
    3. 使用16进制的Unicode编码 char c4 = ‘\u0041’;

    补充:

    ​ 转义字符 \ 在Java中某些具有特殊含义的符号不能之间打印出来,需要使用转义字符

    单个单引号     \'
    单个双引号     \"
    单个\         \\
    水平跳格       \t
    换行          \n
    
  4. 布尔类型-boolean

    数据类型取值应用场景
    booleantrue(真) false(假)条件判断

    注意: boolean类型不参与数据运算

引用数据类型

  • String 字符串 由一串字符组成的串,必须有“”引起来
    • 字符串拼接: 任何数据类型通过+与字符串拼接到一起,整体都会变成字符串

数据类型转换

除了直接给一个变量赋一个确切的值之外,还可以通过一个变量给另一个变量赋值

int a = 10;

int b = a;

short s = a; // 错误,a 中的值可能会超出s的范围

  • 自动类型转换

    当小类型变量赋值为大类型变量时,可以直接赋值

    例如: int a = 10; long l = a;

    转换规则:

    ​ byte -> short -> int -> long -> float -> double

    ​ char -> int -> long -> float -> double

    class TestDataTypeChange{
    	public static void main(String[] args){
    		// byte b = 10;
    		// short s = b;
    		char c = 'A';
    		int i = c;
    		long l = i;
    		float f = l;
    		double d = f;
    		System.out.println(d);
    	}
    }
    
  • 强制类型转换 - 强转 (不常用)

    当大类型变量赋值为小类型变量时,不可以直接赋值,需要强转

    语法: 小数据类型 变量名 = (小数据类型)大数据类型变量名;

    例如: int i = 10000; byte b = (byte) i;

    强转规则:

    1. 大类型整数强转为小类型整数,若不超出范围,数据完整

      例如: long l = 100; int i = l; i = 100;

    2. 大类型整数强转为小类型整数,超出范围,直接舍去高位字节, 数据正负可能发生变化

      例如: int i = 10000; byte b = (byte) i; b = 16

    3. 小数类型转为整数类型,直接舍去小数部分,没有四舍五入

      例如: double d = 12.99; int i = (int)d; i = 12;

      class TestDataTypeChange1{
      	public static void main(String[] args){
      		double d1 = 0.1;
      		double d2 = -0.1;
      		 
      		int i1 = (int)d1;
      		int i2 = (int)d2;
      		System.out.println(i1+"  "+i2); 0  0
      	}
      }
      

自动类型提升

当进行算数运算时

若有double参与,最终结果提升为double类型;

若没有double,有float参与,最终结果提升为float;

若没有double、float,有long参与,最终结果提升为long;

除此之外,统统提升为int类型

class TestDataTypeChange2{
	public static void main(String[] args){
		byte b1 = 1;
		byte b2 = 2;
		int b3 = b1+b2;
		
		System.out.println(b3); 
		
		int i1 = 10;
		// int i2 = 3;
		double i2 = 3;
		System.out.println(i1/i2); 
	}
}

运算符

表达式

概念:通过运算符将变量或值连接起来的式子,并最终会得到一个结果,即表达式

1+2int a = 10; a+10;int a = 10;int b = 20; a+b;int a = 10;int b = 20; a>b;  布尔表达式,结果为truefalse

运算符

  1. 算数运算符

    + - *(乘,求积)/(除,求商) %(模,求余) 
    
    class TestOperator{
    	public static void main(String[] args){
    		int a = 10;
    		int b = 3;
    		System.out.println(a/b);//3
    		System.out.println(a%b);//1
    	}
    }
    
  2. 一元运算符

    运算符作用
    ++自增1
    自减1
    class TestOperator1{
    	public static void main(String[] args){
    		int a = 5;
    		//a++让a自增1,a++本身也是一个表达式,该表达式的值是a自增之前的值
    		int b = a++;
    		System.out.println(a);//6
    		System.out.println(b);//5
    		
    		//++a让a自增1,++a本身也是一个表达式,该表达式的值是a自增之后的值
    		int c = ++a;
    		System.out.println(a);//7
    		System.out.println(c);//7
    		
    		//-- 同理
    		int n = 5;
    		int m = n--;
    		System.out.println(n);//4
    		System.out.println(m);//5
    		
    		int m1 = --n;
    		System.out.println(n);//3
    		System.out.println(m1);//3
    		
    		int x = 5;
    		int y = (x++)+(++x)+(x--)+(--x);
    		//x      6      7     6     5
    		//y      5      7     7     5
    		System.out.println(x);//5
    		System.out.println(y);//24
    	}
    }
    
  3. 赋值运算符

    运算符作用
    =将=右边赋值给=左边
    +=先求和再赋值,不会发生自动类型提升
    -=先求差再赋值,不会发生自动类型提升
    *=先求积再赋值,不会发生自动类型提升
    /=先求商再赋值,不会发生自动类型提升
    %=先求余再赋值,不会发生自动类型提升
    class TestOperator2{
    	public static void main(String[] args){
    		int a = 5;
    		a += 1;//等价于 a = a+1;
    		System.out.println(a);
    		
    		byte b = 5;
    		//b = (byte)(b+1);
    		b += 1;
    		System.out.println(b);
    	}
    }
    
  4. 比较运算符 判断符号两边的值或变量的关系,为布尔表达式,结果为true或false

    运算符作用
    ==判断两边的值是否相等
    >大于
    <小于
    >=大于等于
    <=小于等于
    !=不等
    class TestOperator3{
    	public static void main(String[] args){
    		int a = 5;
    		int b = 10;
    		System.out.println(a==b);//false
    		System.out.println(a>b);//false
    		System.out.println(a<b);//true
    		System.out.println(a>=5);//true
    		System.out.println(b<=100);//true
    		System.out.println(a!=b);//true
    
    	}
    }
    
  5. 逻辑运算符 符号两边均为布尔表达式或布尔值,总结果依然为true或false

    运算符作用
    &&(与)若两边布尔表达式均为true,则总结果为true;有一个为false,总结果为false
    ||(或)若两边布尔表达式均为false,则总结果为false;有一个为true,总结果为true
    !(反)获取相反的结果,对true取反则为false;对false取反则为true
    class TestOperator4{
    	public static void main(String[] args){
    		int a = 5;
    		int b = 10;
    		System.out.println(a==b||a!=b);//false||true ->true
    		System.out.println(a>b&&a>3);//false&&true -> false
    		System.out.println(a<b&&b==10);//true
    		System.out.println(!(a>=5));// !true -> false
    	}
    }
    

    补充:

    ​ && 短路逻辑求与运算符 例如: a>b&&a>c,当a>b为false时,由于整体结果肯定为false,所以并不会判断a>c的结果

    ​ || 短路逻辑求或运算符 例如: a>b||a>c,当a>b为true时,由于整体结果肯定为true,所以并不会判断a>c的结果

    ​ & 非短路逻辑求与运算符 例如: a>b&a>c, 无论a>b的结果为什么,都会进行a>c的判断

    ​ | 非短路逻辑求与运算符 例如: a>b|a>c, 无论a>b的结果为什么,都会进行a>c的判断

    class TestOperator4{
    	public static void main(String[] args){
    		// int n = 10;
    		// int m = 20;
    		// System.out.println(n++>=100&&m++>=0);//false
    		// System.out.println(n);//11
    		// System.out.println(m);//20
    		
    		int n = 10;
    		int m = 20;
    		System.out.println(n++>=100&m++>=0);//false
    		System.out.println(n);//11
    		System.out.println(m);//21
    	}
    }
    
  6. 三元运算符

    运算符作用
    布尔表达式?值1:值2;对布尔表达式进行判断,若结果为true,则获得值1;若结果为false,则获得值2
    class TestOperator5{
    	public static void main(String[] args){
    		int weekDay = 6;
    		String result = (weekDay>=1&&weekDay<=5?"上课":"休息");
    		System.out.println(result);
    	}
    }
    class TestOperator6{
    	public static void main(String[] args){
    		int n = 9;
    		String result = (n%2==0?"偶数":"奇数");
    		System.out.println(result);
    	}
    }
    

扫描器

Scanner : 扫描器 类库中具有接受数据的功能类

创建语法:

java.util.Scanner sc = new java.util.Scanner(System.in);

常用方法:

sc.next(); 接受字符串

sc.nextInt(); 接收int

sc.nextDouble(); 接收double

sc.nextBoolean(); 接收boolean

import

概念: 导包,将类库中或其他地方的类,导入到当前源文件中

语法: import 包名.类名;

位置: package 语句下面,第一个类上面

注意:

1. *  代表当前包下的所有的类,不包括子包中的类
    例如:import java.util.*;  意味着将util包下的所有类都导入进来
2. *  不能连用
    例如: import java.*.*;  错误!!
3. java.lang是默认导入的!就等价于,在每个源文件中都有一个 import java.lang.*;
4. 当多个包中具有同名的类,需要指定具体路径(在类中写全 包名.类名)
import java.util.*;
import java.sql.*;
class TestImport{
	public static void main(String[] args){
		java.util.Date d = null;
		
	}
}

学习方法

整理笔记—> 抄写案例代码并且加注释 —> 写作业

chap03-流程控制

选择结构

概念: 先对某一条件做判断,根据判断结果,执行相应的流程

如果(我有钱){
    为所欲为
}
努力挣钱

基本if选择结构

语法:

if(布尔表达式){
    // 逻辑代码
}
//可有可无的后续代码

执行流程:

  1. 判断布尔表达式,若结果为true,则执行逻辑代码,并结束整个选择,执行后续代码;
  2. 若结果为false,则直接结束整个选择结构,执行后续代码
class TestIf{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		if(n%2==0){
			System.out.println(n+"为偶数");
		}
		System.out.println(n);
	}
}

if else选择结构

语法:

if(布尔表达式){
    //逻辑代码1
}else{
    //逻辑代码2
}
//可有可无的后续代码

执行流程:

  1. 先对布尔表达式做判断,若结果为true,则执行逻辑代码1,并结束整个选择结构,执行后续代码;
  2. 若结果为false,则执行逻辑代码2,结束整个选择结构,执行后续代码
class TestIfElse{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		if(n%2==0){
			System.out.println(n+"为偶数");
		}else{
			System.out.println(n+"为奇数");
		}
		System.out.println(n);
	}
}
class TestIfElse_a{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		//注意,当if或else后的代码块{}中只有一行代码,可以省略{}
		if(n%2==0)
			System.out.println(n+"为偶数");
		else 
			System.out.println(n+"为奇数");
		
		System.out.println(n);
	}
}

多重if选择结构

语法:

if(布尔表达式1){
    //逻辑代码1
}else if(布尔表达式2){
    //逻辑代码2
}else if(布尔表达式3){
    //逻辑代码3
}else if(布尔表达式4){
    //逻辑代码4
}else{
    //逻辑代码5
}

执行流程:

  1. 先对布尔表达式1进行判断,若结果为true,则执行逻辑代码1,并结束整个选择结构,执行后续代码;
  2. 若布尔表达式1结果为false,则进行布尔表达式2的判断,若结果为true,则执行逻辑代码2,并结束整个选择结构,执行后续代码;
  3. 。。。
  4. 若布尔表达式1、2、3、4结果统统为false,则执行else中的逻辑代码5;、

注意: 多个if互相排斥,从上到下,依次判断,当判断成功一个,其他的则不再判断

应用场景:区间判断

class TestIfElseIf{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入你的预算:");
		int money = sc.nextInt();
		if(money>30000){
			System.out.println("败家之眼、外星人、顶配MacBookPro");
		}else if(money>15000){
			System.out.println("Dell G7");
		}else if(money>8000){
			System.out.println("惠普、华为");
		}else if(money>4000){
			System.out.println("小米、神舟");
		}else{
			System.out.println("步步高点读机");
		}
	}
}
class TestIfElseIf_a{
	public static void main(String[] args){
		int n = 5;
		
		if(n++>10){
			System.out.println("if");
		}else if(n++>5){
			System.out.println("else if1");
		}else if(n++>4){
			System.out.println("else if2");
		}
		System.out.println(n);
	}
}

嵌套if选择结构

语法:

if(外层布尔表达式){
    if(内层布尔表达式1){
        //逻辑代码1
    }
}else{
    if(内层布尔表达式2){
        //逻辑代码2
    }else{
        //逻辑代码3
    }
}
注意: 只要结果完整,可以随意嵌套

执行流程:

  1. 先判断外层布尔表达式,若结果为true,则判断内层布尔表达式1,。。。。
  2. 若结果为false,则判断内层布尔表达式2,。。。。
class Test27{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入总条数:");
		int n = sc.nextInt();
		System.out.println("请输入每页显示条数:");
		int m = sc.nextInt();
		System.out.println("请输入查询页码:");
		int i = sc.nextInt();
		//计算总页数
		int count = (n%m==0?n/m:(n/m)+1);

		if(i>=1&&i<=count){
			if(i==count){
				System.out.println("显示第"+((i-1)*m+1)+"条到第"+n+"条");
			}else{
				System.out.println("显示第"+((i-1)*m+1)+"条到第"+i*m+"条");
			}		
		}else{
			System.out.println("youwu !");
		}		
	}
}

分支结构

语法:

switch(变量|表达式){
    case1:逻辑代码1;break;
    case2:逻辑代码1;break;
    case3:逻辑代码1;break;
    case4:逻辑代码1;break;
    case5:逻辑代码1;break;
    default:默认代码;
}

补充: break 关键字,可以结束当前分支或循环结构

变量的类型:byte short int char String(JDK7.0+)

执行流程:

  1. 若变量的值为值1,则执行逻辑代码1,然后再通过break关键字结束当前分支结构
  2. 若变量的值为值2,则执行逻辑代码2,然后再通过break关键字结束当前分支结构
  3. 若变量的值为值3,则执行逻辑代码3,然后再通过break关键字结束当前分支结构
  4. 若变量的值为值n,则执行逻辑代码n,然后再通过break关键字结束当前分支结构
  5. 若所有case的值都不匹配,则执行default中的默认代码

注意:

  1. 所有能够使用分支结果完成的代码,都可以使用多重if完成
  2. case中的值不能重复
  3. 若某一个case匹配成功,下面所有的case都不会再进行判断匹配;所以若没有及时的通过break结束分支结构,可能会执行到其他case中的逻辑代码
class TestSwitch{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入一个整数:");
		int n = sc.nextInt();
		switch(n){
			case 1:System.out.println("星期一");break;
			case 2:System.out.println("星期二");break;
			case 3:System.out.println("星期三");break;
			case 4:System.out.println("星期四");break;
			case 5:System.out.println("星期五");break;
			case 6:System.out.println("星期六");break;
			case 7:System.out.println("星期日");break;
			default:System.out.println("输入有误!");
		}
	}
}

class TestSwitch{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入一个整数:");
		int n = sc.nextInt();
		switch(n){
			case 1:System.out.println("星期一");break;
			case 2:System.out.println("星期二");break;
			case 3:System.out.println("星期三");break;
			case 4:System.out.println("星期四");break;
			case 5:System.out.println("星期五");break;
			case 6://System.out.println("星期天");break;
			case 7:System.out.println("星期天");break;
			default:System.out.println("输入有误!");
		}
	}
}
class TestSwitch{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入一个整数:");
		int n = sc.nextInt();
		switch(n){
			default:System.out.println("输入有误!");break;
			case 1:System.out.println("星期一");break;
			case 2:System.out.println("星期二");break;
			case 3:System.out.println("星期三");break;
			case 4:System.out.println("星期四");break;
			case 5:System.out.println("星期五");break;
			case 6://System.out.println("星期天");break;
			case 7:System.out.println("星期天");break;
			
		}
	}
}
class TestSwitch1{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入一个名字:");
		String name = sc.next();
		switch(name){
			case "孙悟空":System.out.println("齐天大圣");break;
			case "猪罡鬣":System.out.println("天蓬元帅");break;
			case "沙和尚":System.out.println("卷帘大将");break;
			case "乔碧罗":System.out.println("幻影坦克");break;
			default:System.out.println("路人甲");
		}
	}
}

局部变量

概念:定义在函数(方法)内部的变量

使用要求:

  1. 先赋值,再使用
  2. 作用范围:从定义的那一行开始,到定义所在的代码块的结束
  3. 作用范围重合内,变量名不能重复
class Test{
	public static void main(String[] args){
		int a = 10;
		if(a>0){
			//int a = 1;报错,命名重复
			int b = 20;
		}
		System.out.println(a+" "+b);//报错,找不到b变量
	}
}

循环结构

概念:满足某个条件,即可重复执行的一段代码

核心思路图:

在这里插入图片描述

while循环

语法:

while(布尔表达式){
    // 循环体(逻辑代码)
}
//可有可无的后续代码

执行流程:

  1. 判断布尔表达式(循环条件),若结果为true,则执行循环体
  2. 当循环体执行结束后,再次进行布尔表达式的判断,若结果为true,依然执行循环体
  3. 直至布尔表达式结果为false时,结束循环,执行后续代码

循环的组成部分: 循环变量、循环条件、循环体、循环迭代语句

while循环的特点: 先判断,再执行;不确定性循环,适用于循环次数不明确的场景

class TestWhile{
	public static void main(String[] args){
		//需求:打印十次HelloWorld
		//1. 循环变量:控制循环次数
		int i = 1;
		//由于没有真正的参与到循环的过程中,不属于循环变量
		int j = 1;
		//2. i<=10  循环条件:决定是否进行循环
		while(i<=10){
			//3. 循环体:每次循环需要执行的业务代码
			System.out.println("Hello World "+i);
			//4. 循环迭代(条件更新语句):控制循环变量的改变
			i++;
			j++;
		}
	}
}

死循环:

死循环原因:缺少循环迭代语句
class TestWhile1{
	public static void main(String[] args){
		//需求:打印十次HelloWorld
		//1. 循环变量:控制循环次数
		int i = 1;
		//2. i<=10  循环条件:决定是否进行循环
		while(i<=10){
			//3. 循环体:每次循环需要执行的业务代码
			System.out.println("Hello World "+i);
			//4. 循环迭代(条件更新语句):控制循环变量的改变
		}
	}
}
死循环原因:循环条件永远为true
class TestWhile2{
	public static void main(String[] args){
		//需求:打印十次HelloWorld
		//1. 循环变量:控制循环次数
		int i = 1;
		//2. i<=10  循环条件:决定是否进行循环
		while(true){
			//3. 循环体:每次循环需要执行的业务代码
			System.out.println("Hello World "+i);
			//4. 循环迭代(条件更新语句):控制循环变量的改变
			i++;
		}
	}
}
随堂小练习:
1. 通过循环,计算1~100的和
    提示:需要定义额外变量,接受1~100之间的每个数
class TestWhile3{
	public static void main(String[] args){
		//1+2+3+4+...+100
		// i为循环变量,还充当了1~100之间的每个数
		int i = 1;
		// 可以将1~100之间的每个数都加到sum上,最后sum的值就是最终结果
		int sum = 0;
		//通过循环可以获取到1~100之间的每个数,并用i表示
		while(i<=100){
			//将每次循环的i都加到sum中
			sum += i;
			//sum = sum+i;
			//循环迭代语句,使i的值发生改变
			i++;
		}
		System.out.println(sum);
		/* 
		i=1<=100
			sum = sum+i = 0+1; i++ -> i=2 
		i=2<=100
			sum = sum+i = 0+1+2; i++ -> i=3 
		i=3<=100
			sum = sum+i = 0+1+2+3; i++ -> i=4 
		....
		i=100<=100
			sum = sum+i = 0+1+2+3+..+99+100; i++ -> i=101 
		i=101>100	
			循环结束 
		*/
	}
}
2. 通过循环,计算1~100的偶数和
class TestWhile4{
	public static void main(String[] args){
		//1+2+3+4+...+100
		// i为循环变量,还充当了1~100之间的每个数
		int i = 1;
		// 可以将1~100之间的每个数都加到sum上,最后sum的值就是最终结果
		int sum = 0;
		//通过循环可以获取到1~100之间的每个数,并用i表示
		while(i<=100){
			//判断i是否为偶数,是偶数则加到sum中,不是则不加
			if(i%2==0){
                //将每次循环的i都加到sum中
				sum += i;				
			}
			//注意:i++必须放在if外面
			i++;
		}
		System.out.println(sum);
	}
}
3. 猜大小
    思路:
    	先获取到一个随机整数n,然后通过黑窗口输入整数m,若m==n,则打印,猜对了,并且游戏结束;
   			若m<n,则打印猜小了,请重新输入;
    		若m>n,则打印猜大了,请重新输入;
class TestGame{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		//java.util包下,Random对象为随机数对象,可以通过该对象方法获取随机数
		Random r = new Random();
		//nextInt(101) 获取0~100之间的随机数
		int n = r.nextInt(101);
		System.out.println("欢迎来到猜大小游戏!!");
		System.out.println("请输入一个0~100之间的整数:");
		
		while(true){
			int m = sc.nextInt();
			if(m==n){
				System.out.println("恭喜你,猜对了!!");
				break;
			}else if(m<n){
				System.out.println("猜小了,请重新输入:");
			}else if(m>n){
				System.out.println("猜大了,请重新输入:");
			}
		}
	}
}

do while循环

语法:

do{
    //循环体
}while(布尔表达式);

执行流程:

  1. 先执行一次循环体
  2. 再进行布尔表达式(循环条件)的判断,若结果为true,则再次执行循环体
  3. 直至结果为false,则结束循环

do while循环的特点:先执行,再判断;不确定性循环,适用于循环次数不明确的场景

案例: 
   同学写作业,老师批改作业,给出评价(truefalse);若为true,则作业完成;若为false,则需要再写一次作业
class TestDoWhile{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		boolean bo;
		do{
			System.out.println("500行代码~~~~");
			System.out.println("请老师批改:");
			bo = sc.nextBoolean();
		}while(!bo);
	}
}

for循环

语法:

for(循环变量;循环条件;循环迭代语句){
    //循环体
}

执行流程:

  1. 初始化循环变量(只执行一次)
  2. 判断循环条件,若结果为true,则执行循环体;
  3. 当循环体执行结束后,执行循环迭代语句
  4. 再次判断循环条件,若结果true,则执行循环体
  5. 直至结果为false,结束循环

for循环的特点:先判断,再执行;确定性循环,适用于循环次数明确的场景

//使用for循环,打印10次HelloWorld
class TestFor{
	public static void main(String[] args){
		for(int i=1;i<=10;i++){
			System.out.println("HelloWorld  "+i);
		}
	}
}
//使用for循环计算1~100的和
class TestFor1{
	public static void main(String[] args){
		int sum = 0;
		for(int i=1;i<=100;i++){
			sum += i;
		}
		System.out.println(sum);
	}
}

注意:

  1. 循环变量可以省略(或写在上面),;不能少
  2. 循环条件可以省略,可能造成死循环
  3. 循环迭代语句可以省略,可能造成死循环

总结

  1. while\for都是先判断,再执行;do while是先执行,再判断
  2. for循环为确定性循环,适用于循环次数明确的场景
  3. while和do while为不确定性循环,适用于循环次数不明确的场景
  4. 若无论如何都要执行一次,使用do while

流程控制

  1. break 跳出或结束当前循环、分支结构

  2. continue 跳过本次循环,继续下次循环

    class TestBreak{
    	public static void main(String[] args){
    		for(int i=1;i<=10;i++){
    			if(i==5){
    				break;
    			}
    			System.out.println(i);
    		}
    	}
    }
    class TestContinue{
    	public static void main(String[] args){
    		for(int i=1;i<=10;i++){
    			if(i==5){
    				continue;
    			}
    			System.out.println(i);
    		}
    	}
    }
    

循环嵌套

概念: 在一个循环内部再定义一个循环

注意:

  1. 只要语法结构完整,可以无限嵌套;但是一般推荐,只嵌套一层

  2. 内层循环体循环次数 = 外层循环次数*内层循环次数

  3. break及continue只能作用于直接包含它们的循环结构

  4. 循环的标号:在循环的前面给循环起一个名字,该名字即可代表该循环

    class TestForFor{
    	public static void main(String[] args){
    		outer:for(int i=1;i<=10;i++){
    			inner:for(int j=1;j<=10;j++){
    				if(j==5)break outer;
    				System.out.println("i="+i+"  j="+j);
    			}
    		}
    	}
    }
    
  5. 应用场景:图形打印、百钱买百鸡、打印乘法口诀表

//百钱买百鸡    100文钱需要买100只鸡   公鸡  3文/只  母鸡  2文/只  小鸡  1文/3只
//请问总共有哪些购买方案
//百钱买百鸡    100文钱需要买100只鸡   公鸡  3文/只  母鸡  2文/只  小鸡  1文/3只
//请问总共有哪些购买方案
/* 
思路:
	公鸡:i(0~33)    母鸡 j(0~50)   小鸡  100-i-j
	3*i+2*j+(100-i-j)/3==100
	
*/
class Test{
	public static void main(String[] args){
		for(int i=0;i<=33;i++){
			int max = (100-i*3)/2;
			for(int j=0;j<=max;j++){
				int k = 100-i-j;
				if(k%3==0&&3*i+2*j+k/3==100){
					System.out.println("公鸡个数:"+i+" 母鸡个数:"+j+" 小鸡个数:"+k);
				}
			}
		}
	}
}

chap04-函数

基本语法

概念:一段具有特定功能的代码,可以重复调用

class TestFunction{
	public static void main(String[] args){
		//20个*组成的分割线
		for(int i=1;i<=15;i++){
			System.out.print("*");
		}
		System.out.println();
		System.out.println("文能挂机喷队友");
		for(int i=1;i<=15;i++){
			System.out.print("*");
		}
		System.out.println();
		System.out.println("武能越塔送人头");
		for(int i=1;i<=15;i++){
			System.out.print("*");
		}
		System.out.println();
		System.out.println("进可孤身一挑五");
		for(int i=1;i<=15;i++){
			System.out.print("*");
		}
		System.out.println();
		System.out.println("退可坐等十五投");
		for(int i=1;i<=15;i++){
			System.out.print("*");
		}
		System.out.println();
	}
}
以上代码存在问题:
    1. 代码冗余     重复的代码写了太多遍
    2. 可维护性差   
    3. 可重用性差
解决办法:将打印分割线的功能写成一个函数,在需要分割线的地方调用函数即可

语法:

public static void 函数名(){
    // 功能代码
}

函数声明:  public static void 函数名()   -- 声明一个函数。一般的,通过声明就可以明白函数的作用
函数实现:  {  功能代码 }    -- 函数功能的具体实现代码
    
例如: 打印分割线函数
    public static void printLine(){
    	for(int i=1;i<=15;i++){
			System.out.print("*");
		}
		System.out.println();
	}

位置: 类的内部,其他函数的外部

//1
class Test{
    //2
    public static void main(String[] args){
    	//3
	}
    //4
}
//5
注意:只有位置2与位置4可以定义函数

调用函数: 通过 函数名(); 即可

注意:

  1. 函数定义完成后,必须进行调用,才会实现相应的功能
  2. () 不能省略!!!
  3. 当程序执行时,遇到函数调用语句,会先暂时离开当前函数,进入到被调用函数中执行代码;当被调用函数中的代码执行完毕后,程序会回到函数调用处,继续向下执行

参数

大多数情况下,函数的顺利执行需要从外界获取数据。在函数声明中,需要告诉外界此函数需要接收什么样的数据;在函数被调用时,函数调用者,就需要根据声明传入相应的数据;

形参:形式参数,定义在函数声明中(),作用: 告诉外界此函数需要什么样的数据

实参:实际参数,写在函数调用中(),作用:根据形参传入相应的实际数据

语法:

public static void 函数名(形参列表){
    //功能代码
}

0个形参:
public static void 函数名(){
    //功能代码
}
1个形参
public static void 函数名(参数类型 参数名){
    //功能代码
}
例如:
    public static void method(int n){}
多个参数:参数之间使用,隔开
public static void 函数名(参数类型1 参数名1,参数类型2 参数名2...,参数类型n 参数名n){
    //功能代码
}    

调用函数: 函数名(实参列表);

注意:当函数具有形参时,调用该函数时必须根据形参,传入相应的实参(类型、个数、顺序)

经验:形参相当于声明了一个作用范围为整个函数内部的局部变量;实参相当于为形参赋值

单个参数

class TestFunction{
	public static void main(String[] args){
		//20个*组成的分割线
		printLine1(10);
		System.out.println("文能挂机喷队友");
		printLine1(15);
		System.out.println("武能越塔送人头");
		printLine1(20);
		System.out.println("进可孤身一挑五");
		printLine1(25);
		System.out.println("退可坐等十五投");
		printLine1(30);
	}
	//函数:打印不同长度的分割线
	public static void printLine1(int num){
		for(int i=1;i<=num;i++){
			System.out.print("*");
		}
		System.out.println();
	}
	
}

多个参数

class TestFunction{
	public static void main(String[] args){
		//20个*组成的分割线
		printLine2(10,"*");
		System.out.println("文能挂机喷队友");
		printLine2(15,"~");
		System.out.println("武能越塔送人头");
		printLine2(20,"#");
		System.out.println("进可孤身一挑五");
		printLine2(25,"-");
		System.out.println("退可坐等十五投");
		printLine2(30,"=");
	}
	//函数:打印不同长度的分割线
	public static void printLine2(int num,String line){
		for(int i=1;i<=num;i++){
			System.out.print(line);
		}
		System.out.println();
	}
}

返回值

很多时候,函数执行结束后都需要返回一个结果,该结果即返回值!

语法:

public static 返回值类型 函数名(形参列表){
    // 功能代码
    //return 语句
}
注意:
    1. 返回值类型:8种基本数据类型、引用类型、 void(无返回值类型,表示函数没有返回值)
    2. 当返回值类型为8种基本数据类型或引用类型时,在函数实现中,必须书写return语句
    	return语句: return 返回值;  
			作用:表示结束函数,并将返回值返回到函数调用处
    3. 当返回值类型为void时,函数实现中不需要书写return语句!但是,也可以使用return关键字
        return;  表示结束函数!!
    4. return后面的返回值的类型必须和函数声明中定义的返回值类型一致!!

调用函数:

两种方式:
    1. 若需要使用返回值,可以使用变量接收返回值
    	返回值类型 变量名 = 函数名(实参列表)2. 若不需要使用返回值,或者只是简单的使用一次、打印,可以像之前直接调用
    	函数名(实参列表)class TestFunction2{
	public static void main(String[] args){
		//1. 用变量接收返回值
		// int c = add(1,2);
		// int d = c+2;
		// System.out.println(d);
		//2. 直接调用
		System.out.println(add(1,2));
		add1(2,3);
		//错误写法,由于add1函数没有返回值,所用不能写在输出语句中
		//System.out.println(add1(2,3));
	}
	//需求:定义一个求和函数
	public static int add(int a,int b){
		// int c = a+b;
		// return c;
		return a+b;
	}
	public static void add1(int a,int b){
		System.out.println(a+b);
	}
}

补充: return的用法

用法:

  1. 用在具有返回值类型(8种基本数据类型、引用类型)的函数中,必须通过return语句返回相应的值

return 返回值; 表示结束函数,并将返回值返回到函数调用处

  1. 用在没有返回值类型(void)的函数中,可以不写return语句;但也可以使用return

return ; 仅仅表示结束函数

class Test{
	public static void main(String[] args){
		Prime(11);
	}
	public static void Prime(int n){
		for(int i=2;i<n;i++){
			if(n%i==0){
				System.out.println(n+"不是质数");
				return;
			}
		}
		System.out.println(n+"是质数");
	}
}
思考:
   1. if后面为什么不写else
   2. return在本代码中的作用

return的使用要求

  1. return 语句后面,不能写其他代码
  2. 一个代码中,同一时间只能有一个有效的return语句
  3. return后面只能跟一个值
  4. 若在具有返回值类型的函数中,存在选择、循环结构,必须保证任何情况下都有return语句!
class TestReturn{
	public static void main(String[] args){
		method();
	}
	public static int method(){
		//编译器只能做基本的语法检查,只要写了选择或循环结构,
		//编译器就认为该结构有可能判断为false,从而不执行
		
		/* 
		if(true){
			return 0;
		} 
		*/
		if(true){
			return 0;
		}else{
			return 0;
		}
		//无法访问的代码
		//return 0;
	}
	public static int method1(){
		int a = 10;
		while(a>1){
			return 0;
		}
		return 0;
	}
}

总结

  1. 不能在一个函数内部,再定义其他函数
  2. 函数三要素:函数名、形参列表、返回值类型
  3. 一般的,一个函数只负责一个功能!
  4. 函数的优点
    1. 减少代码冗余
    2. 提高代码可读性
    3. 提高代码可维护性
    4. 提高代码可复用性

函数高级

函数嵌套调用

概念: 在一个函数中调用其他函数

class TestFunction3{
	public static void main(String[] args){
		System.out.println("main  start");
		ma();
		System.out.println("main  end");
	}
	public static void ma(){
		System.out.println("ma  start");
		mb();
		System.out.println("ma  end");
	}
	public static void mb(){
		System.out.println("mb  start");
		mc();
		System.out.println("mb  end");
	}
	public static void mc(){
		System.out.println("mc  start");
		System.out.println("mc  end");
	}
}

在这里插入图片描述

在这里插入图片描述

总结:

  1. 嵌套层次最深的函数,最先执行结束
  2. 调用函数的形参,可以作为被调用函数的实参

在这里插入图片描述

在这里插入图片描述

JVM的内存结构(了解)

在这里插入图片描述

栈: 是一种只能在顶部进行操作的线性表;即入栈(压栈)和出栈(弹栈)操作都只能对栈顶元素执行

栈的特点: 先进后出,后进先出

栈帧:栈的基本组成元素;每当执行一个函数时,都会自动生成一个栈帧,进入到栈空间中;该栈帧中保存着该函数的所有信息(局部变量表、操作数栈,动态链接、返回地址…);当函数代码执行结束,会执行出栈操作

在这里插入图片描述

递归(了解)

概念: 在函数内部调用自身函数(俗称:自己调自己)

作用: 可以高效的解决某些特定场景下的问题

注意:所有能够使用递归的场景,都可以通过循环代替

何时才能使用递归:

  • 当大问题可以拆分为小问题,并且大小问题解决方式相同

    例如:计算阶乘:  5!=5*4*3*2*1  4!=4*3*2*1  5!=5*4!
    
//递归  自己调自己
class TestDiGui{
	public static void main(String[] args){
		System.out.println("main  start");
		ma();
		System.out.println("main  end");
	}
	public static void ma(){
		System.out.println("ma  start");
		ma();
		System.out.println("ma  end");
	}
}
存在问题:无穷递归,导致栈溢出,程序运行终止
解决问题:设定正确的出口条件(回归条件)
//定义函数,接收一8以内的整数,计算其阶乘:
//for循环计算阶乘
class TestJieCheng{
	public static void main(String[] args){
		System.out.println(ma(3));
	}
	public static int ma(int n){
		int result = 1;
		for(int i=n;i>=1;i--){
			result *= i;
		}
		return result;
	}
}
//递归版计算阶乘
class TestJieCheng1{
	public static void main(String[] args){
		System.out.println(ma(3));
	}
	//ma(n) 计算出n的阶乘  n!= n*(n-1)!  ===》 n! = n*ma(n-1)
	/* 
		ma(n-1)  计算出n-1的阶乘
	*/
	public static int ma(int n){
		if(n==0||n==1) return 1;
		return n*ma(n-1);
	}
}

在这里插入图片描述

chap05-数组

概念: 是内存中一块连续的存储空间。可以存储多个相同类型的值,相当于声明了多个相同类型的变量

在这里插入图片描述

注意: 数组的长度是固定的、不可变的

创建数组

  • 第一种方式

    先声明: 数据类型[] 数组名;    例如: int[] arr;
    再分配空间: 数组名 = new 数据类型[长度];   例如:arr = new int[5];
    
  • 第二种方式 推荐使用

    声明的同时分配空间:数据类型[] 数组名 = new 数据类型[长度]; 
    例如: int[] arr = new int[5];
    
  • 第三种方式 推荐使用

    声明并初始化: 数据类型[] 数组名 = {1,值2,值3....};
    例如: int[] arr = {1,2,3,4,5};
    注意: 声明与初始化不能分开!!!
        int[] arr;
    	arr = {1,2,3,4,5};//错误语法!
    
  • 第四种方式

    声明并初始化: 数据类型[] 数组名 = new 数据类型[]{1,值2,值3....};
    例如: int[] arr = new int[]{1,2,3,4,5};
    注意:数组长度与初始化不能同时出现!!
        int[] arr = new int[5]{1,2,3,4,5};//错误语法
    

补充: 数组数据类型的写法

1. 数据类型[] 数组名;
2. 数据类型 []数组名;
3. 数据类型 数组名[];
推荐使用第一种!!

使用数组

  1. 数组长度: 数组的长度是固定的,通过 数组名.length 可以获取到

  2. 数组下标: 数组中的每个存储空间都有一个默认的标号,即下标;

    下标范围: 0~length-1

  3. 数组元素: 数组中的每个存储空间都为元素!

  4. 元素访问: 通过数组下标,可以访问到数组中的每个元素;

    ​ 取值: 数组名[下标]; 赋值: 数组名[下标] = 值;

在这里插入图片描述

class TestArray{
	public static void main(String[] args){
		//4种创建语法
		//1.
		// int[] arr1;
		// arr1 = new int[5];
		//2.
		// int[] arr2 = new int[5];
		//3.
		// int[] arr3 = {1,2,3,4,5};
		//4.
		// int[] arr4 = new int[]{1,2,3,4,5};
		
		int[] arr = new int[5];
		
		//赋值操作
		arr[0] = 1;
		arr[1] = 2;
		arr[2] = 3;
		arr[3] = 4;
		arr[4] = 5;
		// 运行报错:java.lang.ArrayIndexOutOfBoundsException 数组下标越界异常
		//arr[5] = 6;
		
		//取值操作
		System.out.println(arr[0]);
		System.out.println(arr[1]);
		System.out.println(arr[2]);
		System.out.println(arr[3]);
		System.out.println(arr[4]);
		System.out.println(arr.length);

	}
}
  1. 数组具有默认值,即数组的数据类型的默认值

    数据类型默认值
    整数类型0
    浮点数类型0.0
    字符类型0或‘\u0000’所对应的字符–空字符
    布尔类型false
    引用类型null
  2. 遍历数组: 通过下标逐个访问数组中的每个元素;需要借助for循环,获取每个数组下标,此时循环变量不仅仅控制循环次数,还要充当数组下标

    class TestArray{
    	public static void main(String[] args){
    		
    		int[] arr = {1,2,3,4,5};
    		
    		//遍历数组
    		for(int i=0;i<arr.length;i++){
    			System.out.println(arr[i]);
    		}
    	}
    }
    

深入数组底层

  1. 数组属于引用类型
  2. 数组引用(数组名)中存储的是数组首元素地址(首地址)
  3. 寻址公式: 首元素地址+下标*数组数据类型字节长度
  4. 数组下标为什么从0开始?答: 为了提高寻址效率!!
  5. 数组变量之间相互赋值,传递的是地址(首元素地址)
class TestArray1{
	public static void main(String[] args){
		//在当前栈帧中开辟一块空间叫做a,里面存储一个整数10
		int a = 10;
		//在当前栈帧中开辟一块空间叫做b
		int b = a;//将空间a中值,赋值(复制)给空间b
		System.out.println(b);//10
		
		b++;
		//当完成了b=a这样的赋值操作后,a与b之间就不存在联系了,a与b是相互独立的
		//所以b的变化,不会导致a变化
		System.out.println(a);//10 
		
		//在当前栈帧中开辟一块空间叫做arr,里面存储一个数组的首元素地址
		//数组对象存储在堆空间中,arr中的地址可以指向堆空间中的数组对象
		int[] arr = {1,2,3,4,5};
		//在当前栈帧中开辟一块空间叫做brr
		//将空间arr中的数组地址,赋值给空间brr,
		//此时arr与brr中的地址相同,即指向堆空间中同一数组对象
		int[] brr = arr;
		System.out.println(brr[0]);//1
		
		brr[0]++;
		//由于arr与brr实际指向的是同一数组对象,
		//所以无论是arr还是brr,只要修改了数组内容,另一个引用都可以看到变化
		System.out.println(arr[0]);//2
	}
}

在这里插入图片描述

class TestArray2{
	public static void main(String[] args){
		int a = 10;
		int[] arr = {1,2,3,4,5};
		ma(a,arr);
		System.out.println(a);//10 
		System.out.println(arr[0]);//2
	}
	public static void ma(int b,int[] brr){
		b++;
		brr[0]++;
	}
	
}

在这里插入图片描述

数组“扩容”

原理:

  1. 新建一个更大的数组
  2. 将旧数组中的数据一一复制到新数组中
  3. 将新数组的地址赋值给旧数组的引用

具体实现方式

  1. 使用for循环

    class TestArray3{
    	public static void main(String[] args){
    		int[] arr = {1,2,3,4,5};
    		//1.新建一个更大的数组
    		int[] brr = new int[arr.length*2];
    		//2.将旧数组中的值一一复制到新数组中
    		for(int i=0;i<arr.length;i++){
    			brr[i] = arr[i];
    		}
    		//3.将新数组的地址赋值给旧数组引用
    		arr = brr;
    		//遍历数组
    		for(int i=0;i<arr.length;i++){
    			System.out.println(arr[i]);
    		}
    	}
    }
    
  2. 使用System类中的数组扩容方法arraycopy(…);

    class TestArray4{
    	public static void main(String[] args){
    		int[] arr = {1,2,3,4,5};
    		//1.新建一个更大的数组
    		int[] brr = new int[arr.length*2];
    		//2.将旧数组中的值一一复制到新数组中
    		//System.arraycopy(旧数组名,旧数组复制起始下标,新数组名,新数组起始下标,复制长度);
    		System.arraycopy(arr,0,brr,0,arr.length);
    		//3.将新数组的地址赋值给旧数组引用
    		arr = brr;
    		//遍历数组
    		for(int i=0;i<arr.length;i++){
    			System.out.println(arr[i]);
    		}
    	}
    }
    
  3. 借助数组工具类Arrays实现扩容

    class TestArray5{
    	public static void main(String[] args){
    		int[] arr = {1,2,3,4,5};
    		//Arrays.copyOf(旧数组名,被返回的新数组的长度);
    		arr = java.util.Arrays.copyOf(arr,arr.length*2);
    		//遍历数组
    		for(int i=0;i<arr.length;i++){
    			System.out.println(arr[i]);
    		}
    	}
    }
    

思考:如何实现缩容??

答:只需要将新建的大数组改为小数组即可!

可变长参数(了解)

概念:定义形参时,参数的个数可以不确定;当调用函数时,由传递的实参个数决定

语法:

public static 返回值类型 函数名(数据类型... 参数名){
    //函数实现
}

注意:每个函数最多只能定义一个可变长参数,并且必须要定义在形参列表末尾

使用可变长参数:可以将可变长参数当做数组来使用

class Test{
	public static void main(String[] args){
		print(1,2,3,4);
		System.out.println("===========");
		print(11,12,13,14,15);
	}
	public static void print(int... a){
		for(int i=0;i<a.length;i++){
			System.out.println(a[i]);
		}
	}
}

二维数组

概念:一维数组的元素还是一维数组

在这里插入图片描述

创建语法

1.  先声明:  数据类型[][] 数组名;  例如: int[][] arr;
    后分配空间: 数组名 = new 数据类型[高维数组长度][低维数组长度];  例如:arr = new int[4][2];
2.  声明并分配空间:数据类型[][] 数组名 = new 数据类型[高维数组长度][低维数组长度];
	例如: int[][] arr = new int[4][2];
3.  声明并初始化: 数据类型[][] 数组名 = {{1,2,3},{1,2},{1},....{}};
	例如: int[][] arr = {{1,2,3},{4,5},{6,7,8,9,0}};
4.  创建不规则二维数组:
    	数据类型[][] 数组名 = new 数据类型[高维数组长度][];
		数组名[高维数组下标] = new 数据类型[低维数组长度];
		数组名[高维数组下标] = new 数据类型[低维数组长度];
		...
    例如:
        int[][] arr = new int[4][];
		arr[0] = new int[2];   arr[0] = {1,2};//错误语法
		arr[1] = new int[3];   arr[1] = new int[]{3,4,5};//正确语法
		arr[2] = new int[1];
		arr[3] = new int[5];

使用数组

通过 数组名[高维数组下标] 可以获取到相应的低维数组
通过 数组名[高维数组下标][低维数组下标] 可以获取到相应的低维数组中相应的下标所对应的元素

数组遍历:需要使用双重for循环

class TestArray1{
	public static void main(String[] args){
		int[][] arr = {{1,2},{3,4,5,6},{7,8,9}};
		
		//遍历二维数组
		//外层for循环获取到所有的低维数组,循环变量充当高维数组下标
		for(int i=0;i<arr.length;i++){
			//内层for循环获取到相应低维数组的所有元素,循环变量充当低维数组下标
			for(int j =0;j<arr[i].length;j++){
				System.out.print(arr[i][j]+" ");
			}
			System.out.println();
		}
	}
}
// 查找二维数组中的最大值与最小值,不使用排序
class TestArray2{
	public static void main(String[] args){
		int[][] arr = {{1,2},{3,4,5,6},{7,8,9}};
		
		int max = arr[0][0];
		int min = arr[0][0];
		
		for(int i=0;i<arr.length;i++){
			for(int j =0;j<arr[i].length;j++){
				if(arr[i][j]>max) max = arr[i][j];
				if(arr[i][j]<min) min = arr[i][j];
			}
		}
		System.out.println(min+"  "+max);
	}
}
// 计算二维数组的总和
class TestArray3{
	public static void main(String[] args){
		int[][] arr = {{1,2},{3,4,5,6},{7,8,9}};
		int sum = 0;
		
		for(int i=0;i<arr.length;i++){
			for(int j =0;j<arr[i].length;j++){
				sum += arr[i][j];
			}
		}
		System.out.println(sum);
	}
}

数组排序

冒泡排序

概念: 使数组中相邻的两个元素比较大小,根据需求决定是否交换位置

class TestArraySort{
	public static void main(String[] args){
		int[] arr = {9,6,3,7,2};
		
		/* 
			冒泡 完成升序排序  从小到大
			目标:2 3 6 7 9
			
				9  6  3  7  2
		下标   	0  1  2  3  4
	
	第一次冒泡  i=1    j=0;j<length-1;j++
		j~j+1
		0~1		6  9  3  7  2
		1~2		6  3  9  7  2
		2~3     6  3  7  9  2
		3~4     6  3  7  2  9
	结果:找出了最大值,并且放在了最右边(4下标)
	
	第二次冒泡  i=2    j=0;j<length-2;j++
		0~1		3  6  7  2  9
		1~2		3  6  7  2  9
		2~3		3  6  2  7  9
	结果:找出了第2大值,并且放在了3下标上
	
	第三次冒泡  i=3    j=0;j<arr.length-i;j++
		0~1		3  6  2  7  9
		1~2		3  2  6  7  9
	结果:找出了第3大值,并且放在了2下标上
	
	第四次冒泡
		0~1		2  3  6  7  9
	结果:找出了第4大值及最小值,并且放在了相应下标上
	
	冒泡次数:length-1次
	实现方式:双重for循环
		外层for循环: 控制冒泡次数 
			for(int i=1;i<arr.length;i++)
		内层for循环:控制相邻元素比较
			for(int j=0;j<arr.length-i;j++)

		*/
		for(int i=1;i<arr.length;i++){
			for(int j=0;j<arr.length-i;j++){
				if(arr[j]>arr[j+1]){
					int temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
		//遍历
		for(int i=0;i<arr.length;i++){
			System.out.println(arr[i]);
		}
	}
}

选择排序

概念: 每次选择,都固定一个元素,逐个与其他所有元素比较大小,根据需求决定是否交换位置

class TestArraySort2{
	public static void main(String[] args){
		int[] arr = {9,6,3,7,2};
		
		/* 
			选择 完成升序排序  从小到大
			目标:2 3 6 7 9
			
				9  6  3  7  2
		下标   	0  1  2  3  4
	
	第一个选择  i=0   j=1;j<arr.length;j++
		i~j
		0~1		6  9  3  7  2
		0~2		3  9  6  7  2
		0~3		3  9  6  7  2
		0~4		2  9  6  7  3
	结果:找出了最小值,放在了最左边(0下标)
	
	第二次选择	i=1   j=2;j<arr.length;j++
		1~2		2  6  9  7  3
		1~3     2  6  9  7  3
		1~4		2  3  9  7  6
	结果:找出了第2小值,放在了1下标
	
	第三次选择	i=2   j=i+1;j<arr.length;j++
		2~3     2  3  7  9  6
		2~4		2  3  6  9  7
	结果:找出了第3小值,放在了2下标
	
	第四次选择
		3~4		2  3  6  7  9
	结果:找出了第4小值及最大值,放在了相应下标
	
	选择次数:length-1
	实现方式:双重for循环
		外层for循环:控制每次选择所固定的元素下标
			for(int i=0;i<arr.length-1;i++)
		内层for循环:控制每次选择中的其他所有元素下标
			for(int j=i+1;j<arr.length;j++)
		*/
		
		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;
				}
			}
		}
		
		//遍历
		for(int i=0;i<arr.length;i++){
			System.out.println(arr[i]);
		}
	}
}

JDK排序

概念:借助数组工具类Arrays中的排序方法sort完成排序

注意:只能做升序排序!!

class TestArraySort3{
	public static void main(String[] args){
		int[] arr = {9,6,3,7,2};
		java.util.Arrays.sort(arr);
		//遍历
		for(int i=0;i<arr.length;i++){
			System.out.println(arr[i]);
		}
	}
}

chap06-面向对象

编程思想

概念:建立解决问题的思路,即编程思想

常见的编程思想

  1. 面向过程: 自顶向下,逐步求精,注重解决问题的过程,将解决问题分为一个一个的步骤,只要按照步骤执行就一定能解决问题
  2. 面向对象: 分析解决问题需要的哪些功能,而哪些对象具有这种功能;注重对象之间的配合调用

问题: 回家

面向过程: 拿着地图,分析回家需要经过哪些路口,每条路上走多远,遇到路口往哪儿拐,一步一步就走到家了

面向对象:只需要调用出租车(对象)的载客功能,由出租车送我们回家

注意:面向对象与面向过程并不是完全对立!面向对象是在面向过程的基础上实现的

面向对象

认识对象

什么是对象?

Java眼中的对象: 一切客观存在的事物都是对象!世界就是由对象组成的!—万物皆对象

​ 例如:水杯、桌子、电脑、空气、错误等等都是对象

程序中的对象: 内存中的一块存储空间,用来存储对现实对象的关注的部分,从而代表现实中的对象

在这里插入图片描述

  • 对象的组成:

    • 特征(属性): 有什么
    • 行为(方法): 干什么
    注意:有时候,一个对象的属性可能也是一个对象;对象的调用者可能也是一个对象
    例如:硬盘是电脑的属性,硬盘本身也是对象;程序员使用电脑开发,程序员也是对象
    
  • 对象间的关系

    • 一个对象继承了另一个对象 is a 例如:狗是一个动物
    • 小对象组成大对象 has a 例如:电脑由硬盘、cpu组成
    • 一个对象使用另一个对象 use a 例如:程序员使用电脑

重新认识类

类与对象的关系
  1. 类是对象的模板
  2. 类是人们对一系列相同相似事物(现实中的对象)的认识,抽取出共性定义在类中
  3. 对象就是根据类创建出来的实例,类似于根据模板生产物品

在这里插入图片描述

  1. 类由属性与方法组成

例如: Computer类 ------》电脑对象

类的组成
  1. 属性: 有什么 成员变量\实例变量

    位置: 类的内部,方法的外部

    语法:

    1. 声明属性: 数据类型 属性名;    例如:String name;
    2. 声明并初始化: 数据类型 属性名 = 值;  例如: int age = 18;
    
    错误语法: 先声明,再赋值
        例如: double score;
    		  score = 100;//错误语法!!
    
    class Student{
    	String name;
    	int age = 18;
    	double score;
    	//score = 100;错误
    }
    

    属性具有默认值:即属性的数据类型的默认值!

    属性的作用范围:至少为整个类的内部

    注意:当属性与局部变量命名冲突,局部变量优先!

    补充:局部变量与属性的区别

    局部变量属性
    位置定义在函数(方法)内部类的内部,方法的外部
    默认值没有默认值具有默认值
    作用范围从定义的那一行开始,到定义所在代码块的结束至少整个类的内部
    命名冲突作用范围重合内,不允许命名重复当属性与局部变量命名冲突,局部变量优先
  2. 方法: 干什么 成员方法 对象的具体的行为:吃喝拉撒睡、工作、学习、玩耍、等等

    位置: 类的内部,其他方法的外部

    语法:

    修饰符 返回值类型 方法名(形参列表){
    	//方法实现
    }
    注意:函数是特殊的方法!函数声明中需要写上static修饰符;而方法一般不写static
    
    class Student{
    	String name = "小郭";
    	int age = 18;
    	double score;
    	//score = 100;错误
    	
    	public void study(){
    		//局部变量优先!
    		//String name = "小红";
    		System.out.println(name+"沉迷学习无法自拔");
    	}
    	public void eat(){
    		System.out.println("吃饭饭");
    	}
    	public void sleep(){
    		System.out.println("睡觉觉");
    	}
    }
    
  3. 构造方法: Java规定的具有特殊功能的方法

    作用: 可以用来配合new关键字创建对象,并且可以在创建对象的过程中选择是否为对象属性赋值!

    分类:

    • 无参构造方法 没有形参列表,在创建对象时无法为对象属性赋值;可以在对象创建成功后,通过其他方式赋值

      语法: public 类名(){}

    • 有参构造方法 具有形参列表,在创建对象时可以将形参的值赋值给属性

      语法: public 类名(形参列表){ //赋值语句 }

    注意:

    1. 构造方法的方法名必须和类名相同!
    2. 构造方法没有返回值类型,注意连void都没有
    3. 构造方法无法手动调用,只有在创建对象时被调用一次
    4. 若代码中没有定义任何构造方法,默认提供一个公开无参构造方法

    经验:

    1. 一般的,若类中具有属性,需要提供有参、无参构造方法
    2. 可以定义多个形参个数不同的有参构造方法
    //学生类
    class Student{
    	//属性
    	String name = "小红";
    	int age = 18;
    	double score;
    	//score = 100;错误
    	//普通的行为方法
    	public void study(){
    		//局部变量优先!
    		//String name = "小郭";
    		System.out.println(name+"沉迷学习无法自拔");
    	}
    	public void eat(){
    		System.out.println("吃饭饭");
    	}
    	public void sleep(){
    		System.out.println("睡觉觉");
    	}
    	
    	//无参构造方法
    	public Student(){}
    	//具有两个参数的有参构造方法
    	public Student(String n,double s){
    		name = n;
    		score = s;
    	}
    	//具有三个参数的有参构造方法
    	public Student(String n,int a,double s){
    		name = n;
    		age = a;
    		score = s;
    	}
    }
    

    思考:普通方法与构造方法的区别?

    答: 普通方法是人为定义的一些具体功能、行为方法,例如吃喝拉撒睡、工作学习等等

    而构造方法它的作用是Java实先规定好的,是用来创建对象的;它们的关系就好像主函数与普通函数的关系

创建对象

创建对象的方式:

  1. 通过无参构造方法创建对象

    语法: 类名 引用 = new 类名(); 例如:Student s1 = new Student();

  2. 通过有参构造方法创建对象

    语法: 类名 引用 = new 类名(实参列表); 例如:Student s2 = new Student(“小郭”,20,80);

使用对象:

  • 使用对象的属性:
    • 通过 引用.属性名; 获取该对象的该属性
    • 通过 引用.属性名 = 值; 完成对该对象的该属性的再次赋值
  • 使用对象的方法:
    • 通过 引用.方法名(实参列表) 调用该对象的该方法
class TestObject{
	public static void main(String[] args){
		//通过无参构造方法创建对象
		Student s1 = new Student();
		System.out.println(s1.name+" "+s1.age+" "+s1.score);
		s1.study();
		s1.score = 100;
		System.out.println(s1.name+" "+s1.age+" "+s1.score);
		
		//通过有参构造方法创建对象
		Student s2 = new Student("小郭",20,80);
		System.out.println(s2.name+" "+s2.age+" "+s2.score);
		s2.study();

	}
}
//学生类
class Student{
	//属性
	String name = "小红";
	int age = 18;
	double score;
	//score = 100;错误
	//普通的行为方法
	public void study(){
		//局部变量优先!
		//String name = "小郭";
		System.out.println(name+"沉迷学习无法自拔");
	}
	public void eat(){
		System.out.println("吃饭饭");
	}
	public void sleep(){
		System.out.println("睡觉觉");
	}
	
	//无参构造方法
	public Student(){}
	//具有两个参数的有参构造方法
	public Student(String n,double s){
		name = n;
		score = s;
	}
	//具有三个参数的有参构造方法
	public Student(String n,int a,double s){
		name = n;
		age = a;
		score = s;
	}
}
方法重载(OverLoad)

概念:允许一个类中定义多个同名的方法,比如,构造方法

语法要求:

  1. 方法名必须相同
  2. 形参列表必须不同(个数、类型、顺序不同,仅仅形参名不同,不算重载)
  3. 修饰符、返回值类型、异常不作要求

使用重载方法:编译器在编译时,会根据实参列表选择调用相应的重载方法,采用就近向上匹配原则

方法重载的好处:对于方法调用者,屏蔽了多个功能相同相似,只是形参不同的方法的差异;方便调用者调用方法

class TestCounter{
	public static void main(String[] args){
		Counter1 c1 = new Counter1();
		
		System.out.println(c1.addInt(1,2));
		System.out.println(c1.addDouble(1.5,2.5));
		System.out.println(c1.addIntAndDouble(1,2.5));
		System.out.println(c1.addDoubleAndInt(1.5,2));
		
		Counter2 c2 = new Counter2();
		
		System.out.println(c2.add(1,2));
		System.out.println(c2.add(1.5,2.5));
		System.out.println(c2.add(1,2.5));
		System.out.println(c2.add(1.5,2));
	}
}
//创建计算器类,可以完成一些数学运算
//未使用方法重载
class Counter1{
	
	public int addInt(int a,int b){
		return a+b;
	}
	public double addDouble(double d1,double d2){
		return d1+d2;
	}
	public double addIntAndDouble(int a,double d){
		return a+d;
	}
	public double addDoubleAndInt(double d,int a){
		return a+d;
	}
}
//方法重载
class Counter2{
	
	public int add(int a,int b){
		return a+b;
	}
	public double add(double d1,double d2){
		return d1+d2;
	}
	public double add(int a,double d){
		return a+d;
	}
	public double add(double d,int a){
		return a+d;
	}
}

就近向上匹配原则:

class TestJavaKid{
	public static void main(String[] args){
        int a = 10;
		JavaKid jk = new JavaKid();
		int n = 1;
		jk.study(n);
		//方法重载特殊情况:对方法引用不明确
		jk.study(1,2);
	}
}
class JavaKid{
	public void study(byte n){
		System.out.println("byte");
	}
	public void study(short n){
		System.out.println("short");
	}
	// public void study(int n){
		// System.out.println("int");
	// }
	// public void study(long n){
		// System.out.println("long");
	// }
	public void study(float n){
		System.out.println("float");
	}
	public void study(double n){
		System.out.println("double");
	}
    // public void study(int n,int i){
		// System.out.println("int -- int");
	// }
	public void study(int i,double n){
		System.out.println("int -- double");
	}
	public void study(double n,int i){
		System.out.println("double -- int");
	}
}

重点:

  1. 程序中的对象与现实中的对象的的关系
  2. 类与对象的关系
  3. 类的组成,每个组成部分的作用及其语法、使用、注意事项
  4. 普通方法与构造方法的区别
  5. 无参构造方法与有参构造方法的区别,以及根据二者创建出来的对象的区别
  6. 创建对象的语法,对象的属性与方法的调用
  7. 明白int a = 10; 与 JavaKid jk = new JavaKid()的异同
  8. 基本数据类型变量与引用数据类型变量的异同
  9. 方法重载的语法及要求
this关键字
public Worker(String n,int a,double s){
    name = n;
    age = a;
    salary = s;
}
存在问题: 不满足标识符命名规范,无法望名知意!
 修改后:   
public Worker(String name,int age,double salary){
    name = name;
    age = age;
    salary = salary;
}
存在问题: 形参也属于局部变量,此时形参名与属性名重复,局部变量优先,即形参优先!那么此时 name = name中的两个name都是形参name,所以无法完成对属性赋值
  需要使用this将局部变量形参与属性区别开!!!

this的用法:

  1. 用在构造方法或普通方法中,代表当前实例对象

    • this.属性名; 代表当前对象的属性
    • this.方法名(实参); 代表调用当前对象的方法
    public Worker(String name,int age,double salary){
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    
    public void method(){
        int age = 20;
        System.out.println("局部变量:age"+age);
        System.out.println("属性:age"+this.age);
    }
    
  2. 必须要用在构造方法中,表示调用本类的其他构造方法

    • this(实参) 代表调用本类中具有相应形参的构造方法
    • 注意: 该语句,必须写在构造方法中的第一行
    public Student(String name,int age){
        this.name = name;
        this.age = age;
    }
    public Student(String name,int age,double score){
        this(name,age);
        this.score = score;
    }
    
class TestThis{
	public static void main(String[] args){
		Student s = new Student("zhangs",18,100);
		s.study();
		s.method();
	}
}
//模板
class Student{
	String name ;
	int age;
	double score;
	
	public Student(){}
	public Student(String name,int age){
		this.name = name;
		this.age = age;
	}
	public Student(String name,int age,double score){
		this(name,age);
		this.score = score;
	}
    
	public void study(){
		System.out.println(name+"沉迷学习无法自拔");
	}
	public void method(){
		int age = 20;
		System.out.println("局部变量:age"+age);
		System.out.println("属性:age"+this.age);
	}
}
对象的创建过程
  1. 分配空间(属性),给属性赋默认值
  2. 属性初始化,给属性赋初始值
  3. 执行构造方法,给属性赋构造参数值
class ClassA{
	public ClassA(){
		System.out.println("ClassA");
	}
}
class ClassB{
	public ClassB(){
		System.out.println("ClassB");
	}
}
class ClassC{
	ClassA ca = new ClassA();
	ClassB cb;
	public ClassC(){
		System.out.println("ClassC");
		cb = new ClassB();
	}
}
class TestClassC{
	public static void main(String[] args){
		ClassC cc = new ClassC();
	}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zRneiXlT-1628609627989)(CoreJava笔记.assets/image-20210304151600391.png)]

对象的内存结构
  • 类名 引用 = new 类名();

在这里插入图片描述

Student s1 = new Student();

Student s2 = s1;//只会在栈帧中开辟空间存放s1中的地址,此时s1与s2中保存的地址相同,指向堆中的同一对象

引用

现实生活中的引用:指使用现成的文章或者诗句

Java中的引用:即对象名,通过自身保存现成对象的地址,可以用来代表一个对象

数据类型的分类:

  1. 基本数据类型:4类8种
  2. 引用数据类型:String\数组\自定义类名… 无数种

变量的分类:

  1. 基本数据类型变量: 数据类型 变量名 = 值; 变量名中存值!
  2. 引用数据类型变量: 类名 引用 = new 类名(); 引用中存地址!

变量之间相互赋值:

  1. 基本数据类型变量之间传递的是值!

    int a = 10;
    int b = a;//将变量名a中存的值,传递给变量名b;此时a与b中存的都是10,但是a与b之间不存在联系,修改a或b的值,不会影响到另一个变量的值
    
  2. 引用数据类型变量之间传递的是地址!

    Student s1 = new Student();
    Student s2 = s1;//将引用s1中的地址传递给s2;此时s1与s2中存的地址相同,指向堆中的同一对象;无论通过s1或s2修改对象内容,另一个引用都可以观察到变化
    
class Test{
	public static void main(String[] args){
		int a = 10;
		int b = a;// b=10;
		b++;
		System.out.println(a);//10
		System.out.println(b);//11
		
		Student s1 = new Student();
		Student s2 = s1;
		s2.age = 18;
		System.out.println(s1.age);//18
		System.out.println(s2.age);//18
	}
}

在这里插入图片描述

chap07-三大特性

封装

问题:目前,可以随意的调用、修改一个对象的属性,这种行为是不合理的!!!

概念:是一个边界,是一个屏障,可以保护对象内部的数据不被外界随意访问

实现封装:借助访问(权限)修饰符–private 私有的

访问修饰符作用范围
public 公共的、公开的外界可以随意访问
private 私有的只能在本类中使用
  • 属性封装

    • 语法:private 数据类型 属性名;
    • 注意:一般的,当将属性私有化后,需要提供公开的get\set方法,供外界使用,从而去获取及修改属性
    注意:
        1. 每个私有属性都必须有自己独立的get(获取属性)\set(修改属性)方法
        2. get\set命名规范:get\set+属性名(首字母大写)
        3. 若属性的数据类型为boolean类型,get方法可以命名为:is+属性名(首字母大写);set方法不变
        4. 可以根据需求,选择是否提供get\set方法    
            
    class TestDog{
    	public static void main(String[] args){
    		Dog d = new Dog();
    		d.setName("旺财");
    		d.setAge(2);
    		
    		System.out.println(d.getName()+"  "+d.getAge());
    	}
    }
    class Dog{
    	private String name;
    	private int age;
    	private boolean sex;
    	
    	public Dog(){}
    	public Dog(String name,int age){
    		this.name = name;
    		this.age = age;
    	}
    	public Dog(String name,int age,boolean sex){
    		this(name,age);
    		this.sex = sex;
    	}
    	
    	public String getName(){
    		return name;
    	}
    	public void setName(String name){
    		this.name = name;
    	}
    	public int getAge(){
    		return age;
    	}
    	public void setAge(int age){
    		this.age = age;
    	}
    	public boolean isSex(){
    		return sex;
    	}
    	public void setSex(boolean sex){
    		this.sex = sex;
    	}
    }
    

    思考:为什么将属性封装后,还提供get\set方法?

    答: 封装只是起到保护的作用,而不是完全隔绝!

  • 方法封装 将一些只用于本类,并不希望被外界调用的方法私有化,禁止外界调用!

重点:

  1. this.与this(实参)的区别及使用注意事项
  2. 对象的创建过程,明白属性的三个赋值时机
  3. 明白引用中存储的内容,及对象的存储地方
  4. 基本数据类型变量与引用数据类型变量的区别:存储、传递
  5. private与public的区别,作用范围
  6. get\set方法的作用及语法及注意事项

继承

生活中的继承:一般的,指儿子继承爸爸的一些物品、财产

Java中的继承:指一个类(子类)可以继承(使用)另一个类(父类)的某些属性和方法

概念: 父类是子类共性的抽取!在父类中只能定义所有子类的共性部分;父类—子类是中“一般–特殊”的关系

语法:class 子类类名 extends 父类类名{}

继承的特点:当继承关系建立后,子类可以直接使用父类中可以被子类继承的方法与属性;子类中也可以定义自己独有的属性与方法

继承的好处:减少代码冗余,提高代码的可复用性

注意:

  1. 一般的,只有满足 is a的关系,才能建立继承关系
  2. 构造方法不能被继承
  3. Java是单继承—一个子类只能有一个直接父类!但是可以多级继承,可以被继承的属性或方法也是叠加的

补充: 访问修饰符

访问修饰符本类同包其他包子类不同包不子类继承性
public公开的可以被继承
protected受保护的可以被继承
default默认的同包子类可继承
private私有的不能被继承

从宽到严: public -> protected -> default -> private

注意:

  1. 以上四个访问修饰符都可以修饰 属性、方法、构造方法
  2. 只有个public与default可以修饰类
  3. 以上四个均不可以修饰局部变量
package d10;

public class ClassA{
	//公开的
	public int a = 10;
	//受保护的
	protected int b = 20;
	//默认的
	int c = 30;
	//私有的
	private int d = 40;
}
======================================
package d10;

//同包子类
public class ClassB extends ClassA{
	public ClassB(int a,int b,int c,int d){
		this.a = a;
		this.b = b;
		this.c = c;
		this.d = d;//无法继承
	}
}
=======================================
package p10;
import d10.ClassA;
//不同包子类
public class ClassC extends ClassA{
	public ClassC(int a,int b,int c,int d){
		this.a = a;
		this.b = b;
		this.c = c;//无法继承
		this.d = d;//无法继承
	}
}
=======================================
package p10;
import d10.ClassA;
//不同包不子类
public class ClassD{
	public static void main(String[] args){
		ClassA ca = new ClassA();
		
		System.out.println(ca.a);
		System.out.println(ca.b);//无法访问
		System.out.println(ca.c);//无法访问
		System.out.println(ca.d);//无法访问
	}
}
class TestAnimal{
	public static void main(String[] args){
		Dog d = new Dog("旺财",2,"红色");
		d.eat();
		d.sleep();
		d.lookDoor();
		System.out.println(d.name+" "+d.age+" "+d.color);
	}
}

//父类   只能定义所有子类的共性
class Animal{
	String name;
	int age;
	
	public void eat(){
		System.out.println("吃饭饭");
	}
	public void sleep(){
		System.out.println("睡觉觉");
	}
	
	public Animal(){}
	public Animal(String name,int age){
		this.name = name;
		this.age = age;
	}
}
//子类
class Dog extends Animal{
	String color;
	
	public void lookDoor(){
		System.out.println("看门门");
	}
	
	public Dog(){}
	public Dog(String name,int age,String color){
		this.name = name;
		this.age = age;
		this.color = color;
	}
}
//子类
class Bird extends Animal{
	String pinZhong;
	
	public void fly(){
		System.out.println("飞高高");
	}
	
	public Bird(){}
	public Bird(String name,int age,String pinZhong){
		this.name = name;
		this.age = age;
		this.pinZhong = pinZhong;
	}
}
//子类
class Fish extends Animal{
	double size;
	
	public void swim(){
		System.out.println("划水水");
	}
	
	public Fish(){}
	public Fish(String name,int age,double size){
		this.name = name;
		this.age = age;
		this.size = size;
	}
}

方法覆盖

概念:OverRide,又称为方法重写,即在子类中可以将从父类继承到的方法进行重新实现

语法要求:

  1. 方法三要素(返回值类型、方法名、形参列表)必须完全相同
  2. 访问修饰符相同或更宽

注意:

  1. 子类中覆盖后的方法优先执行
  2. 若父类中有一个方法使用private修饰,此时子类中有一个方法三要素完全与之相同、但访问修饰符为public的方法,此时不构成方法覆盖,因为子类继承不到该方法
class TestAnimal{
	public static void main(String[] args){
		Dog d = new Dog("旺财",2,"红色");
		d.eat();
		d.sleep();
		d.lookDoor();
		System.out.println(d.name+" "+d.age+" "+d.color);
	}
}

//父类   只能定义所有子类的共性
class Animal{
	String name;
	int age;
	
	public void eat(){
		System.out.println("吃饭饭");
	}
	public void sleep(){
		System.out.println("睡觉觉");
	}
	
	public Animal(){}
	public Animal(String name,int age){
		this.name = name;
		this.age = age;
	}
}
//子类
class Dog extends Animal{
	String color;
	
	public void eat(){
		System.out.println("狗吃骨头");
	}
	public void lookDoor(){
		System.out.println("看门门");
	}
	
	public Dog(){}
	public Dog(String name,int age,String color){
		this.name = name;
		this.age = age;
		this.color = color;
	}
}

对象的创建过程

  • 无父类的创建过程
    1. 分配空间,为属性赋默认值
    2. 属性初始化,为属性赋初始值
    3. 执行构造方法,完成对象的创建,为属性赋构造参数值
  • 有父类的创建过程
    1. 分配空间(父类+子类),为父子类属性赋默认值
    2. 初始化父类属性,为父类属性赋初始值
    3. 执行父类构造方法,完成父类对象的创建,为父类属性赋构造参数值
    4. 初始化子类属性,为子类属性赋初始值
    5. 执行子类构造方法,完成子类对象的创建,为子类属性赋构造参数值
  • 总结:
    • 创建子类对象时,必须先创建父类对象
    • 子类对象由父类对象加上子类独有内容共同组成

在这里插入图片描述

class TestAnimal{
	public static void main(String[] args){
		Animal a = new Animal("动物",1);
		System.out.println(a.getName());
		
		Dog d1 = new Dog("狗1",2,"黑色");
		// d1.setName("狗1");
		System.out.println(d1.getName());//狗1
		Dog d2 = new Dog();
		System.out.println(d2.getName());//null
	}
}

在这里插入图片描述

super关键字

当子类中定义了与父类中同名的属性时(属性遮蔽)或发生了方法覆盖时,需要加以区分,才能专项访问

super的两种用法:

  1. 用在构造方法、普通方法中,代表父类对象

    • super.属性名; 获取父类属性
    • super.方法名(); 调用父类方法

    注意:该用法中的属性与方法必须是子类可以继承到的

    //子类
    class Dog extends Animal{
    	String color;
    	
    	public void eat(){
    		System.out.println("狗吃骨头");
    	}
    	public void lookDoor(){
    		System.out.println("看门门");
    	}
    	
    	public Dog(){}
    	public Dog(String name,int age,String color){
    		super.name = name;
    		super.age = age;
    		this.color = color;
    	}
    }
    =========================================================
    class TestMyClass{
    	public static void main(String[] args){
    		MyClassB mb = new MyClassB();
    		mb.method();
    		mb.print();
    	}
    }
    class MyClassA{
    	int value = 10;
    	
    	public void print(){
    		System.out.println(value);
    	}
    }
    class MyClassB extends MyClassA{
    	int value = 20;
    	
    	public void print(){
            //调用父类中被覆盖的print方法
    		super.print();
    		System.out.println(value);
    	}
    	public void method(){
    		int value = 30;
    		System.out.println("局部变量:"+value);//30
    		System.out.println("子类属性:"+this.value);//20
    		System.out.println("父类属性:"+super.value);//10
    		
    	}
    }
    
  2. 必须用在子类构造方法中, 用来制定创建父类对象时,使用父类的哪个构造方法

    • super(); 调用父类无参构造方法
    • super(实参); 调用父类有参构造方法

    注意:

    1. super(…)必须写在子类构造方法中的第一行
    2. this(…)与super(…)不能同时出现
    3. 若子类构造方法第一行不是this(…)也不是super(…),则默认为super();
    class Dog extends Animal{
    	String color;
    	
    	public void eat(){
    		System.out.println("狗吃骨头");
    	}
    	public void lookDoor(){
    		System.out.println("看门门");
    	}
    	
    	public Dog(){}
    	public Dog(String name,int age,String color){
    		super(name,age);
    		this.color = color;
    	}
    }
    

面向对象代码规范写法:

//父类
class Animal{
    //父类私有属性
    private String name;
    private int age;
    //get、set方法
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
    //有参无参构造方法
    public Animal(){}
    public Animal(String name,int age){
        this.name = name;
        this.age = age;
    }
    //普通方法
    public void eat(){
        System.out.println("吃饭饭");
    }
    public void sleep(){
        System.out.println("睡觉觉");
    }
}
//子类继承父类
class Dog extends Animal{
    //子类独有私有化属性
    private String color;
    //子类get\set方法
    public String getColor(){
        return color;
    }
    public void setColor(String color){
        this.color = color;
    }
    //子类有参无参构造方法
    public Dog(){}
    public Dog(String name,int age,String color){
        //调用父类有参构造方法创建父类对象,完成父类name与age属性的赋值
        super(name,age);
        this.color = color;
    }
    //子类覆盖父类中的eat方法
    public void eat(){
        System.out.println("狗吃骨头");
    }
    //子类独有方法
    public void lookDoor(){
        System.out.println("看门门");
    }
}

多态

一个动物------> 狗

一个动物------> 猫

Dog d = new Dog(); 狗 是狗

Animal a = new Dog(); 狗 是动物

概念:父类的引用可以指向子类的对象

语法:父类类名 引用 = new 子类类名(实参);

特点:

  1. 父类的引用指向子类对象,并不会改变对象的实际类型
  2. 当使用多态时,只能调用引用类型中具有的方法
  3. 当使用多态时,会优先执行子类中覆盖后的方法

总结:方法调用看引用类型;方法的执行结果看对象的实际类型(子类类型)

注意:方法具有多态,属性没有多态!调用属性的引用是什么类型,就会获取什么类型中的属性。

class TestAnimal{
	public static void main(String[] args){
		Animal a = new Animal("a");
		a.eat();//吃饭饭
		a.sleep();//睡觉觉
		System.out.println(a.name);//a
		
		Dog d = new Dog("d");
		d.eat();//狗吃骨头
		d.sleep();//睡觉觉
		d.lookDoor();//看门门
		System.out.println(d.name);//null
		Animal d1 = d;
		System.out.println(d1.name);//d
		
		Animal dog = new Dog("dog");
		dog.eat();//狗吃骨头
		dog.sleep();//睡觉觉
		//错误,只能调用引用类型中具有的方法
		//dog.lookDoor();
		System.out.println(dog.name);//dog
	}
}

class Animal{
	String name;
	
	public Animal(){}
	public Animal(String name){
		this.name = name;
	}
	
	public void eat(){
		System.out.println("吃饭饭");
	}
	public void sleep(){
		System.out.println("睡觉觉");
	}
}
class Dog extends Animal{
    //注意:一般情况下,子类中不会定义与父类中同名的属性
	//String name;
	
	public Dog(){}
	public Dog(String name){
		super(name);
	}
	
	public void eat(){
		System.out.println("狗吃骨头");
	}
	public void lookDoor(){
		System.out.println("看门门");
	}
}

在这里插入图片描述

引用类型的类型转换

  1. 子类类型引用可以直接赋值给父类类型引用

    Dog d = new Dog();
    Animal a = d;
    
  2. 在多态场景下,父类类型引用赋值给子类类型引用,需要强转

    Animal a = new Dog();
    Dog d = (Dog)a;//强转的目的:编译器在编译代码时,无法准确得知引用a指向的对象的实际类型,而将Animal类型转为Dog类型,可能会出错,所以需要强转,告诉编译器,允许转换
    ========================================================================
    Animal a = new Cat();
    Dog d = (Dog)a;//由于强转,导致编译通过;但是解释器在工作的时候,会确认a指向的对象的实际类型,由于a指向的Cat类型对象,此时强转为Dog类型,所以会运行报错!!--java.lang.ClassCastException类型转换异常
    ========================================================================
    Animal a = new Animal();
    Dog d = (Dog)a;//编译通过,运行报错,原因同上!!
    ========================================================================
    Cat c = new Cat();
    Dog d3 = (Dog)c;//编译报错,不兼容类型!因为Cat不可能转换为Dog!
    
  3. 没有继承关系的两种类型,不允许相互转换

    Person p = new Person();
    Dog d = (Dog)p;//编译报错,不兼容类型!因为Person不可能转换为Dog!
    

instanceof关键字

语法: 引用 instanceof 类名 注意:这是一个boolean表达式,结果要么为true,要么为false

作用:判断引用指向的对象的实际类型与关键字后面的类名类型是否兼容,若兼容,则返回true,表示可以强转;若不兼容,则返回false,表示不能强转;

例如:
    if(a instanceof Dog){
		Dog d = (Dog)a;
    }else{
        Cat c = (Cat)a;
    }
====================================================================================
class TestInstanceof{
	public static void main(String[] args){
		//多态的应用场景1:定义父类类型的数组,可以存储子类类型的对象
		int[] arr = {1,2,3,4,5};

		Animal[] as = new Animal[4];
		
		as[0] = new Dog();
		as[1] = new Cat();
		as[2] = new Dog();
		as[3] = new Cat();
		
		//需求:调用数组as中所有Dog对象的lookDoor方法,Cat对象的catchMouse方法
		
		for(int i=0;i<as.length;i++){
			if(as[i] instanceof Dog){
				Dog d = (Dog)as[i];
				d.lookDoor();
			}else{
				Cat c = (Cat)as[i];
				c.catchMouse();
			}
		}
	}
}

多态的应用场景

  1. 应用在数组上,定义父类类型数组,可以存储子类类型对象;

    Animal[] as = {
        new Animal(),
        new Dog(),
        new Cat()
    }
    
  2. 应用在方法形参上,方法形参定义为父类类型,调用方法时,传入实参的类型可以为父类对象或其子类对象

  3. 应用在返回值类型上,方法返回值类型定义为父类类型,方法执行结束时,可以返回父类对象或其子类对象

    class Test{
    	public static void main(String[] args){
    		Dog d = new Dog();
    		Cat c = new Cat();
    		Wolf w = new Wolf();
    		Tiger t = new Tiger();
    		
    		feed(d);
    		feed(c);
    		Animal a = sell(2000000);
    		System.out.println(a);
    	}
    	//避免了方法重载,提高了代码的可重用性
    	public static void feed(Animal a){
    		a.eat();
    	}
    	public static Animal sell(double money){
    		if(money>1000000){
    			return new Tiger();
    		}else if(money>10000){
    			return new Wolf();
    		}else{
    			return new Dog();
    		}
    	} 
    	/*
    	public static void feed(Dog d){
    		d.eat();
    	}
    	public static void feed(Cat c){
    		c.eat();
    	} */
    }
    class Animal{
    	public void eat(){
    		System.out.println("吃饭饭");
    	}
    }
    class Dog extends Animal{
    	public void eat(){
    		System.out.println("狗吃骨头");
    	}
    }
    class Cat extends Animal{
    	public void eat(){
    		System.out.println("猫吃鱼");
    	}
    }
    class Wolf extends Animal{
    	public void eat(){
    		System.out.println("狼吃喜洋洋");
    	}
    }
    class Tiger extends Animal{
    	public void eat(){
    		System.out.println("老虎吃人");
    	}
    }
    

多态的好处

  1. 减少代码冗余,提高代码的可重用性
  2. 解耦合(依赖倒转原则)

重点:

  1. 多态的概念及语法,三个特点
  2. 理解:方法具有多态,属性没有多态
  3. instanceof的真正用法,及其判断的内容
  4. 多态的三个应用场景

chap08-三大修饰符

abstract

概念:抽象的,似是而非的,像却不是的

用法:

  1. 修饰类,抽象类,不完整的类

    语法: abstract class 类名

    特点:

    1. 不能实例化,即不能创建对象
    2. 可以被子类继承,作为子类对象的引用,强行使用多态
    3. 有构造方法,供子类使用
    class TestAbstract{
    	public static void main(String[] args){
    		//Animal a = new Animal("动物");
    		
    		Dog d = new Dog("旺财");
    		d.eat();
    		d.sleep();
    		d.lookDoor();
    		System.out.println(d.name);
    		
    		Animal dog = new Dog("来福");
    		dog.eat();
    		dog.sleep();
    		System.out.println(dog.name);
    	}
    }
    //由于现实中不存在"纯正"的动物对象,所以应该讲Animal类定义为抽象类,不能实例化
    abstract class Animal{
    	String name;
    	
    	public Animal(){}
    	public Animal(String name){
    		this.name = name;
    	}
    	
    	public void eat(){
    		System.out.println("吃饭饭");
    	}
    	public void sleep(){
    		System.out.println("睡觉觉");
    	}
    }
    class Dog extends Animal{
    	
    	public Dog(){}
    	public Dog(String name){
    		super(name);
    	}
    	
    	public void eat(){
    		System.out.println("狗吃骨头");
    	}
    	public void lookDoor(){
    		System.out.println("看门门");
    	}
    }
    
  2. 修饰方法,即抽象方法,不完整的方法—没有方法实现

    语法:

    访问修饰符 abstract 返回值类型 方法名(形参列表);abstract 访问修饰符 返回值类型 方法名(形参列表);
    注意:修饰符之间没有顺序要求
    

    特点:

    1. 抽象方法没有实现,即{}使用;代表,表示结束
    2. 抽象方法只有方法声明,作为标准,供子类去实现,统一所有子类中该方法的声明定义
    abstract class Animal{
    	String name;
    	
    	public Animal(){}
    	public Animal(String name){
    		this.name = name;
    	}
    	//由于现实中没有具体的动物对象,自然无法得知动物对象的吃方法应该如何实现,所以该方法应该定义为抽象的
        //只有声明,作为子类相应方法的标准,没有实现!!
    	public abstract void eat();
    	public void sleep(){
    		System.out.println("睡觉觉");
    	}
    }
    class Dog extends Animal{
    	
    	public Dog(){}
    	public Dog(String name){
    		super(name);
    	}
    	
    	public void eat(){
    		System.out.println("狗吃骨头");
    	}
    	public void lookDoor(){
    		System.out.println("看门门");
    	}
    }
    

补充:抽象类与抽象方法的关系

  1. 抽象类中可以有抽象方法,也可以没有抽象方法
  2. 抽象类中可以有普通方法
  3. 有抽象方法的类,一定是抽象类
  4. 若子类继承了一个抽象父类,必须实现父类中的所有抽象方法,否则自身也将成为抽象类

抽象的好处:

  1. 更加的贴近现实,符合客观规律
  2. 更加符合父类抽取子类共性的特点
  3. 将标准分离—方法声明定义在父类中,而方法实现由子类完成

抽象与多态的好处---------解耦合!!

class TestLamp{
	public static void main(String[] args){
		Lamp l = new Lamp();
		
		//给台灯l装灯泡
		l.setBulb(new RedBulb());
		//开灯
		l.on();
		
		//给台灯l装灯泡
		l.setBulb(new GreenBulb());
		//开灯
		l.on();
		
		//给台灯l装灯泡
		l.setBulb(new YellowBulb());
		//开灯
		l.on();
		
	}
}
//台灯类
class Lamp{
	//灯泡属性
	Bulb bulb;
	
	public void setBulb(Bulb bulb){
		this.bulb = bulb;
	}
	//开灯方法
	public void on(){
		bulb.shine();
	}
}

//灯泡类
abstract class Bulb{
	public abstract void shine();
} 
class RedBulb extends Bulb{
	public void shine(){
		System.out.println("发红光");
	}
}
class GreenBulb extends Bulb{
	public void shine(){
		System.out.println("发绿光");
	}
}
class YellowBulb extends Bulb{
	public void shine(){
		System.out.println("发黄光");
	}
}class TestLamp{
	public static void main(String[] args){
		Lamp l = new Lamp();
		
		//给台灯l装灯泡
		l.setBulb(new RedBulb());
		//开灯
		l.on();
		
		//给台灯l装灯泡
		l.setBulb(new GreenBulb());
		//开灯
		l.on();
		
		//给台灯l装灯泡
		l.setBulb(new YellowBulb());
		//开灯
		l.on();
		
	}
}
//台灯类
class Lamp{
	//灯泡属性
	Bulb bulb;
	
	public void setBulb(Bulb bulb){
		this.bulb = bulb;
	}
	//开灯方法
	public void on(){
		bulb.shine();
	}
}

//灯泡类
abstract class Bulb{
	public abstract void shine();
} 
class RedBulb extends Bulb{
	public void shine(){
		System.out.println("发红光");
	}
}
class GreenBulb extends Bulb{
	public void shine(){
		System.out.println("发绿光");
	}
}
class YellowBulb extends Bulb{
	public void shine(){
		System.out.println("发黄光");
	}
}

static

概念: 静态的

用法:

  1. 修饰属性,即静态属性,又称为静态变量、全局变量、类变量

    语法:

    访问修饰符 static 数据类型 属性名;
        或
    static 访问修饰符 数据类型 属性名;
    

    特点:

    1. 静态属性是全类共有----根据该类创建出的所有对象,共享该属性,不独属于某个对象
    2. 静态属性可以通过类名直接调用,不依赖对象引用----->类名.静态属性名;
    class TestStatic{
    	public static void main(String[] args){
    		System.out.println(MyClass.b);//0
    		
    		MyClass mc1 = new MyClass();
    		mc1.a = 10;
    		//静态属性可以通过类名直接访问,
    		//也可以通过引用访问,编译时,会自动将引用改为引用类型类名
    		mc1.b = 100;
    		
    		MyClass mc2 = new MyClass();
    		mc2.a = 20;
    		mc2.b = 200;
    		
    		System.out.println(mc1.a+"    "+mc1.b);//10  200
    		System.out.println(mc2.a+"    "+mc2.b);//20  200
    		
    		mc1.b = 300;
    		System.out.println(mc1.a+"    "+MyClass.b);//10  300
    		System.out.println(mc2.a+"    "+MyClass.b);//20  300
    	}
    }
    class MyClass{
    	int a;
    	static int b;
    }
    

在这里插入图片描述

补充:

  1. 静态属性是什么时候分配空间并赋予默认值的?

答:类加载的时候,分配空间并赋予默认值

  1. 什么是类加载?

答:当JVM第一次使用某个类时,会根据ClassPath(类路径)找到该类的字节码文件(存放着该类的全部信息),读取(加载)字节文件,将类的全部信息加载到JVM中的过程,就是类加载;一般的,类加载只会进行一次

  1. 类加载的时机?

答:

  1. 第一次创建对象时
  2. 第一次调用类中的静态成员时
  3. 创建子类对象会引发父类类加载
  4. 使用Class.forName(“类的权限定名”);
  5. 只声明引用,不会引发类加载
  1. 修饰方法,即静态方法,也称为函数

    语法:

    访问修饰符 static 返回值类型 方法名(形参列表){
        //方法实现
    }
    

    特点:

    1. 静态方法可以通过类名直接调用---->类名.静态方法名(实参);

    2. 静态方法中不能直接访问本类的非静态成员(静态属性、静态方法)

      思考:为什么函数都需要使用static修饰? 答:因为主函数是静态的

    3. 静态方法中不能使用this和super

    4. 静态方法只能被静态方法覆盖,但是没有多态—>调用该方法的引用是什么类型,就执行什么类型中的静态方法

    思考:

    问题1:为什么静态方法中不能直接访问本类的非静态成员?

    答:因为静态方法只需要使用类名就可以调用,所以执行静态方法时,对象可能还没有创建出来,而非静态成员(即普通方法、普通属性)都是必须依赖对象才能调用的,所以静态方法中不能直接访问本类的非静态成员

    问题2:为什么静态方法中不能使用this和super?

    答:因为静态方法只需要使用类名就可以调用,所以执行静态方法时,对象可能还没有创建出来,而this代表当前对象,super代表父类对象

    class TestClass{
    	public static void main(String[] args){
    		// ClassA.m2();
    		// ClassA ca = new ClassA();
    		// ca.m1();
    		// ca.m2();
    		
    		ClassA c = new ClassB();
    		c.m1();//执行子类中的m1方法----》 ClassB--m1
    		c.m2();//执行父类中的m2方法----》 20 10
    		
    		ClassB cb = (ClassB)c;
    		cb.m1();
    		cb.m2();
    	}
    }
    
    
    class ClassA{
    	int a = 10;
    	static int b = 20;
    	
    	public void m1(){
    		//m2();
    		System.out.println(a);
    		System.out.println(b);
    	}
    	public static void m2(){
    		//m1();
    		//System.out.println(this.a);
    		System.out.println(b);
    		
    		// ClassC cc = new ClassC();
    		// System.out.println(cc.c);
    		
    		ClassA ca = new ClassA();
    		System.out.println(ca.a);
    	}
    }
    class ClassB extends ClassA{
    	public void m1(){
    		System.out.println("ClassB--m1");
    	}
    	public static void m2(){
    		System.out.println("ClassB--m2");
    
    	}
    }
    class ClassC{
    	int c = 30;
    }
    
  2. 修饰初始代码块,即静态代码块

    • 初始代码块,又称为动态代码块
      • 位置: 类的内部,方法的外部
      • 语法: { //代码 }
      • 特点: 每次创建对象时,都会自动执行一次,无法手动调用
      • 作用: 一般用于初始化属性,由于属性可以直接初始化,所以初始代码块不常用!
    • 静态代码块
      • 位置:类的内部,方法的外部
      • 语法:static{ //代码 }
      • 特点:只会在类加载时自动执行一次,无法手动调用
      • 作用:一般用于后期读取配置文件
    class TestClassD{
    	public static void main(String[] args){
    		new ClassD();
    		new ClassD();
    	}
    }
    class ClassD{
    	int a = 10;
    	
    	{
    		a = 20;
    		System.out.println("初始代码块1:"+a);
    	}
    	{
    		a = 30;
    		System.out.println("初始代码块2:"+a);
    	}
    	static{
    		System.out.println("静态代码块");
    	}
    	
    	public ClassD(){
    		System.out.println("无参构造方法");
    	}
    }
    

在这里插入图片描述

final

概念: 最终的、最后的、不可变的

用法:

  1. 修饰变量(局部变量、属性),也称为常量

    语法: final 数据类型 变量名;

    特点:一经赋值,不可改变

    分类:

    • 基本数据类型常量 值不能改
    • 引用数据类型常量 地址不能改,但是地址指向的对象内容可变
    class TestFinal{
    	public static void main(String[] args){
    		final int n = 10;
    		//基本数据类型常量的值不可更改
    		//n++;
    		System.out.println(n);
    		
    		final MyClass mc = new MyClass();
    		mc.a++;
    		System.out.println(mc.a);
    		//引用数据类型常量的地址不能改
    		// mc = new MyClass();
    		
    	}
    }
    class MyClass{
    	int a = 100;
    }
    

    补充:被final修饰的属性没有默认值!!!

    注意:

    1. 若final修饰普通属性,必须在属性初始化或初始代码块中或构造方法中有且必须把握一次赋值机会,给该属性完成赋值;若选择使用构造方法赋值,必须保证所有构造方法中都有该属性的赋值语句,保证无论使用哪个构造方法创建对象,该属性都是有值的!!
    class TestClass{
    	public static void main(String[] args){
    		ClassA ca1 = new ClassA();
    		ClassA ca2 = new ClassA(20);
    		ClassA ca3 = new ClassA(30);
    		
    		System.out.println(ca1.a);
    		System.out.println(ca2.a);
    		System.out.println(ca3.a);
    	}
    }
    
    class ClassA{
    	//第一次赋值机会
    	final int a;//= 10;
    	//第二次赋值机会
    	// {
    		// a = 10;
    	// }
    	//第三次赋值机会
    	public ClassA(){
    		a = 10;
    	}
    	public ClassA(int a){
    		this.a = a;
    	}
    }
    
    1. 若final修饰静态属性,必须在属性初始化或静态代码块中有且必须把握一次赋值机会
    class ClassB{
    	//第一次赋值机会
    	final static int b;//=20;
    	static{
    		b = 20;
    	}
    }
    
  2. 修饰方法,即最终的方法,只能被继承,不能被覆盖

    class TestSuper{
    	public static void main(String[] args){
    		Sub sb = new Sub();
    		sb.m1();
    	}
    }
    class Super{
    	public final void m1(){
    		System.out.println("Super m1");
    	}
    }
    class Sub extends Super{
    	// public void m1(){
    		// System.out.println("Sub m1");
    	// }
    }
    
  3. 修饰类,即最终的类(断子绝孙类),不能被继承

    final class ClassE{}
    class ClassF extends ClassE{}
    

总结

  1. private不能与abstract连用;
  2. static不能与abstract连用;
  3. final不能与abstract连用;
  4. private\static\final是可以随意组合的;

重点:

  1. 抽象类与抽象方法的特点
  2. 抽象类与抽象方法的关系
  3. 静态属性与静态方法都可以通过类名直接调用—》不依赖对象
  4. 静态方法的使用要求—》为什么不能直接访问本类的非静态成员?为什么不能使用this和super
  5. 静态代码块的特点
  6. 什么是类加载?类加载的时机
  7. 基本数据类型常量与引用数据类型常量的区别
  8. 当final修饰普通属性和静态属性时,属性的赋值时机

chap09-接口

概念:

  1. 从语法角度上,接口是特殊的抽象类
  2. 是一种功能,实现接口往往意味着为实现类扩展了一种功能
  3. 是一个标准,是接口定义者和接口调用者都需要遵守的标准

定义语法: interface 接口名{}

特点:

  1. 接口中的属性,都是公开静态常量:public static final 数据类型 属性名;
  2. 接口中的方法,都是公开抽象方法:public abstract 返回值类型 方法名(形参列表);
  3. 接口不能独立创建对象,但可以作为引用指向实现类对象
  4. 接口中没有构造方法,不参与对象的创建过程
  5. 接口编译后,依然生成.class文件
//接口是特殊的抽象类
abstract class ClassA{
	public static final int M = 10;
	public static final int N = 20;
	
	public abstract void m1();
	public abstract void m2();
}
//接口
interface ClassA{
	public static final int M = 10;
	public static final int N = 20;
	
	public abstract void m1();
	public abstract void m2();
}
//注意:由于接口中的属性与方法的修饰符都是固定的,所以可以省略
//标准、规范的接口
interface IA{
	int M = 10;
	int N = 20;
	
	void m1();
	void m2();
}

实现接口:class 实现类类名 implements 接口名{}

注意:实现类若不想成为抽象类,必须实现接口中的所有抽象方法

class TestInterface{
	public static void main(String[] args){
		IAImpl ia1 = new IAImpl();
		ia1.m1();
		ia1.m2();
		
		IA ia2 = new IAImpl();
		ia2.m1();
		ia2.m2();
	}
}
interface IA{
	int M = 10;
	int N = 20;
	
	void m1();
	void m2();
}
//IA接口实现类
class IAImpl implements IA{
	public void m1(){
		System.out.println("IAImpl -- m1");
	}
	public void m2(){
		System.out.println("IAImpl -- m2");
	}
}

继承性:

  1. 类与类之间是单继承: 一个子类只能有一个直接父类
    • class 子类类名 extends 父类类名{}
  2. 接口与接口之间是多继承: 一个子接口可以有多个父接口
    • interface 子接口 extends 父接口1,父接口2,…{}

类与接口的关系:

  1. 实现关系:一个类实现一个接口
    • class 实现类 implements 接口{}
  2. 先继承,再实现
    • class 子类 extends 父类 implements 接口{}
  3. 先继承,再多实现
    • class 子类 extends 父类 implements 接口1,接口2,…{}

思考:父类与接口的区别?–亲爹与干爹的区别?

手机

​ 主要共性:打电话 父类–电话

​ 次要共性:玩游戏、听音乐、拍照、上网等 接口

答: 主要共性放在父类中,次要共性放在接口中;当父类无法满足子类的需求时,可以通过实现接口,扩展子类的功能

随堂小练习:
    定义电话类(Phone,具有打电话方法(call)
    定义游戏接口(Game),具有玩游戏方法(playGame)
    定义音乐接口(Music),具有听音乐方法(listenToMusic)
    定义手机类(MobilePhone),继承电话类并覆盖打电话方法,实现游戏、音乐接口并实现玩游戏和听音乐方法

abstract class Phone{
    public abstract void call();
}    
interface Game{
    void playGame();
}
interface Music{
	void listenToMusic();
}
class MobilePhone extends Phone implements Game,Music{
    public void call(){
        System.out.println("手机打电话");
    }
    public void playGame(){
        System.out.println("手机玩游戏");
    }
    public void listenToMusic(){
        System.out.println("手机听音乐");
    }
}

接口中的多态

  1. 和父类一样,接口可以作为引用,指向实现类对象—> 接口 引用 = new 实现类类名(实参);

  2. 当使用多态时,通过引用只能调用引用类型中具有的方法

  3. 实现类对象可以有多个不同类型的引用,表示看待该对象的角度不同

    class TestPhone{
    	public static void main(String[] args){
    		//创建手机对象
    		MobilePhone mp = new MobilePhone();
    		mp.call();
    		mp.playGame();
    		mp.listenToMusic();
    		
    		//将手机看作电话,只能调用打电话的方法
    		Phone p = mp;
    		p.call();
    		//将手机看作游戏机,只能调用玩游戏的方法
    		Game g = mp;
    		g.playGame();
    		//将手机看作音乐播放器,只能调用听音乐的方法
    		Music m = mp;
    		m.listenToMusic();
    	}
    }
    
  4. 接口多态的应用场景:和父类多态的应用场景一样;相比起父类,接口更加适合多态的特性,此时不需要再关注对象的类型(父类),只需要关注对象是否具有某个功能(接口)即可

    class TestStudent{
    	public static void main(String[] args){	
    		Student s = new Student();
    		s.relax(new MobilePhone());
    		s.relax(new Computer());
    	}
    }
    class Computer implements Game{
    	public void playGame(){
            System.out.println("电脑玩游戏");
        }
    }
    class Student{
    	public void relax(Game g){
    		g.playGame();
    	}
    }
    

引用类型的类型转换

回顾昨日:

  1. 子类引用可以直接赋值给父类引用
  2. 多态场景下,父类引用赋值给子类引用需要强转
  3. 没有继承关系的类型,不允许相互转换

放眼当下:

  • 任意类型都可以通过强转,赋值给接口类型的引用!编译通过,但是运行可能报错,可以在转换之前,通过instanceof关键字判断一下,是否兼容
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}
// class Person{}
interface Person{}
class Snoopy extends Dog implements Person{}
class TestAnimal{
	public static void main(String[] args){
		Animal a = new Dog();
		// Animal a = new Snoopy();
		Dog d = (Dog)a;
		System.out.println((a instanceof Person));
		//编译器在编译时,无法准确得知a引用指向的对象的实际类型,
		//但是该对象有可能实现了该接口,所以允许强转
		Person p1 = (Person)a;
		
		// Dog d1 = new Dog();
		// Person p2 = (Person)d1;
	}
}

接口回调

概念:先有接口的使用者,再有接口的实现者

class TestStudent{
	public static void main(String[] args){
		int[] arr = {3,6,1,9,2};
		//0~9
		Arrays.sort(arr);
		for(int i=0;i<arr.length;i++){
			System.out.print(arr[i]+"\t");
		}
		System.out.println();
		
		//"A"~"z"
		String[] ss = {"d","B","s","A"};
		Arrays.sort(ss);
		for(int i=0;i<ss.length;i++){
			System.out.print(ss[i]+"\t");
		}
		System.out.println();
		
		Student[] stus = {
			new Student("张三",17,90),
			new Student("李四",19,80),
			new Student("王五",18,100),
			new Student("赵六",20,70)
		};
		//sort方法排序原理:数组元素类型必须实现Comparable接口,
		//实现compareTo()方法,用以指定排序规则
		Arrays.sort(stus);
		for(int i=0;i<stus.length;i++){
			System.out.println(stus[i].name+" "+stus[i].age+" "+stus[i].score);
		}
	}
}
class Student implements Comparable<Student>{
	String name;
	int age;
	double score;
	
	public Student(){}
	public Student(String name,int age,double score){
		this.name = name;
		this.age = age;
		this.score = score;
	}
	//this 与 s进行比较
	//若返回一个负数,表示this在s前面
	//若返回一个正数,表示this在s后面
	//若返回零,表示this与s相等,顺序不变
	//需求:根据年龄做升序排序  从小到大
	public int compareTo(Student s){
		// if(this.age<s.age)return -1;
		// if(this.age>s.age)return 1;
		// return 0;
		
		// return this.age-s.age;
		//需求:根据成绩做降序排序  从大到小
		if(this.score>s.score) return -1;
		if(this.score<s.score) return 1;
		return 0;
	}
}

高版本语法补充

JDK8.0

  1. 默认方法

    default 返回值类型 方法名(形参列表){
    	// 方法实现
    }
    注意:该方法修饰符依然为public
    
  2. 静态方法

    static 返回值类型 方法名(形参列表){
    	// 方法实现
    }
    注意:该方法修饰符依然为public
    

JDK9.0

  1. 私有方法

    private 返回值类型 方法名(形参列表){
    	// 方法实现
    }
    

补充: 接口与抽象类的区别

抽象类接口
关键字abstract class\extendsinterface\implements
属性不做要求必须是公开静态常量
方法可以有、也可以没有抽象方法JDK8.0之前,必须是公开抽象方法
JDK8.0之后,可以定义具有方法实现的默认、私有、静态方法
构造方法有构造方法没有构造方法
继承性单继承多继承

接口分类

  • 常量接口 只定义属性,不定义方法
  • 标记接口 不定义属性,也不定义方法,只是作为一个标记;例如:Serializable 序列化接口
  • 函数(式)接口 只定义了一个抽象方法的接口
  • 普通接口 至少定义两个抽象方法

接口的好处

  1. 多继承 将共性分为主要共性与次要共性,主要共性可以放在父类中,次要共性放在接口
  2. 解耦合,降低程序各个模块之间的耦合性
  3. 方便程序设计、框架的搭建

重点:

  1. 接口的基本语法(定义、实现)
  2. 接口的多态,接口可以作为引用指向实现类对象,通过接口引用只能调用引用类型中的方法
  3. 先通过instanceof判断是否兼容,再决定是否强转
  4. Comparable接口及compareTo()
  5. 抽象类与接口的区别
  6. 接口分类

chap10-内部类

概念:在类的内部再定义一个类,即内部类

特点:

  1. 可以直接访问外部类的私有成员,不破坏封装
  2. 可以为外部类提供必要的功能组件
  3. 编译生成独立的.class文件

分类:

  1. 成员内部类 了解即可
  2. 静态内部类 了解即可
  3. 局部内部类 掌握
  4. 匿名内部类 熟练

成员内部类

概念:定义在类的内部,方法的外部,类似于成员变量

特点:

  1. 成员内部类对象的创建,需要依赖外部类对象

    外部类类名.内部类类名 引用 = 外部类对象名.new 内部类类名();

  2. 若外部类属性与内部类属性同名:可以通过this.属性名代码内部类属性;通过外部类类名.this.属性名代表外部类属性

  3. 成员内部类中不允许定义静态成员

class Test{
	public static void main(String[] args){
		//创建外部类对象
		Outer out = new Outer();
		//创建内部类对象
		Outer.Inner inn = out.new Inner();
		
		inn.method();
	}
}
//外部类
class Outer{
	private String str = "外部类私有属性";
	static String sta_str = "外部类静态属性";
	//成员内部类
	class Inner{
		private String str = "内部类私有属性";
		//成员内部类中不允许定义静态属性
		//static String innSta_str = "内部类静态属性";
		public void method(){
			String str = "内部类局部变量";
			System.out.println(str);
			System.out.println(this.str);
			System.out.println(Outer.this.str);
		}
		//成员内部类中不允许定义静态方法
		// public static void sta_method(){
			// String str = "内部类局部变量";
			// System.out.println(str);
			// System.out.println(this.str);
			// System.out.println(Outer.this.str);
		// }
	}
}

静态内部类

概念: 定义在类的内部,方法的外部,使用static修饰的类,类似于静态变量

特点:

  1. 静态内部类对象的创建需要依赖外部类类名

    外部类类名.内部类类名 引用 = new 外部类类名.内部类类名();

  2. 静态内部类的静态成员,可以通过 外部类类名.内部类类名.静态属性|静态方法

  3. 静态内部类只能访问外部类的静态成员

class Test{
	public static void main(String[] args){
		//创建内部类对象
		Outer.Inner inn = new Outer.Inner();
		inn.inn_method();
		System.out.println(Outer.Inner.innSta_str);
	}
}

//静态内部类
class Outer{
	private String str = "外部类私有属性";
	static String outSta_str = "外部类静态属性";
	
	static class Inner{
		private String innStr = "内部类私有属性";
		static String innSta_str = "内部类静态属性";
		
		public void inn_method(){
			//静态内部类中不允许访问外部类普通属性
			//System.out.println(str);
			System.out.println(innStr);
			System.out.println(innSta_str);
			System.out.println(outSta_str);
		}
	}
}

局部内部类

概念:定义在方法内部的类,类似于局部变量

特点:

  1. 局部内部类的作用范围:从定义的那一行开始,到定义所在代码块的结束

  2. 局部内部类中不允许定义静态成员

  3. 局部内部类可以访问外部类的局部变量,但是该局部变量必须是常量!

    JDK8.0之前,该局部变量前必须显示加上final修饰符

    JDK8.0之后,只要该局部变量是一个事实上的常量(即值没有改变过),可以不加final

class Test{
	public static void main(String[] args){
		//创建外部类对象
		Outer out = new Outer();
		out.out_method();
	}
}
//局部内部类
class Outer{
	private String str = "外部类私有属性";
	static String outSta_str = "外部类静态属性";
	
	public void out_method(){
		final int a = 10;
		int b = 20;
		//b++;
		class Inner{
			private String innStr = "局部内部类私有属性";
			public void inn_method(){
				System.out.println(str);
				System.out.println(outSta_str);
				System.out.println(innStr);
				System.out.println(a);
				System.out.println(b);
				
			}
		}
		//创建内部类对象
		Inner inn = new Inner();
		inn.inn_method();
		//b++;
	}
}
==================================================================
//局部内部类可以在一定程度上隐藏程序内部实现细节
interface JavaTeacher{
	void teach();
}
class School{
	public static JavaTeacher getTeacher(int i){
		class JavaTeacherA implements JavaTeacher{
			public void teach(){
				System.out.println("JavaTeacherA teach Java");
			}
		}
		class JavaTeacherB implements JavaTeacher{
			public void teach(){
				System.out.println("JavaTeacherB teach Java");
			}
		}
		
		if(i==0)return new JavaTeacherA();
		else return new JavaTeacherB();
	}
}
class TestSchool{
	public static void main(String[] args){
		JavaTeacher jt = School.getTeacher(0);
		jt.teach();
		//超出了局部内部类JavaTeacherB的作用范围
		//JavaTeacher jt1 = new JavaTeacherB();
		//jt1.teach();
	}
}

匿名内部类

概念: 特殊的局部内部类,将类的定义、方法的实现、对象的创建三合一

语法:接口名|类名 引用 = new 接口名|类名(){ //代码 };

特点:

  1. 具有局部内部类所有的特点
  2. 只能用来实现一个接口或继承一个类,一般的都用来实现接口
  3. 只能创建一个对象
  4. 不允许定义构造方法,只有一个默认的公开无参构造方法
  5. 定义匿名内部类,本质上是得到了一个接口实现类对象或子类对象
class Test{
	public static void main(String[] args){
		IA ia1 = new IAImpl();
		ia1.ma();
		
		class ClassA implements IA{
			public void ma(){
				System.out.println("局部内部类");
			}
		}
		IA ia2 = new ClassA();
		ia2.ma();
		
		//匿名内部类本质上是一个对象
		IA ia3 = new IA(){
			public void ma(){
				System.out.println("匿名内部类");
			}
		};
		ia3.ma();
	}
}
interface IA{
	void ma();
}
class IAImpl implements IA{
	public void ma(){
		System.out.println("接口外部实现类IAImpl");
	}
}
==============================================================
//匿名内部类不会打断开发人员的编码思路,也可以一定程度的隐藏程序内部的细节,但是可读性很差
interface JavaTeacher{
	void teach();
}
class School{
	public static JavaTeacher getTeacher(int i){
		if(i==0){
			JavaTeacher jt = new JavaTeacher(){
				public void teach(){
					System.out.println("JavaTeacherA teach Java");
				}
			};
			return jt; 
		}else{
			return new JavaTeacher(){
				public void teach(){
					System.out.println("JavaTeacherB teach Java");
				}
			};
		} 
	}
}
class TestSchool{
	public static void main(String[] args){
		JavaTeacher jt = School.getTeacher(10);
		jt.teach();
	}
} 

Lambda表达式

概念:匿名内部类的简化,只能实现函数式接口,三合一

语法:接口名 引用 = (形参列表)->{ //方法实现 };

​ -> 左边:Lambda表达式的参数部分,即函数式接口中抽象方法的形参列表

​ -> 右边:Lambda表达式的执行代码部分,即对函数式接口中抽象方法的实现

class TestLambda{
	public static void main(String[] args){
		IA ia1 = new IA(){
			public void ma(){
				System.out.println("匿名内部类");
			}
		};
		ia1.ma();
		
		//Lambda表达式本质上是接口的实现类对象
		IA ia2 = ()->{System.out.println("Lambda表达式");};
		ia2.ma();
	}
}

interface IA{
	void ma();
}

注意:

  1. Lambda只能用来实现函数式接口
  2. Lambda表达式本质上是一个接口实现类对象

Lambda表达式细节优化

  1. 参数部分的数据类型可以省略

    IB ib1 = (int n)->{System.out.println(n);};
    IB ib2 = (n)->{System.out.println(n);};
    ib2.mb(10);
    
    IC ic1 = (int n,int m)->{System.out.println(n+m);};
    IC ic2 = (n,m)->{System.out.println(n+m);};
    ic2.mc(2,3);
    
  2. 若只有一个参数,小括号可以省略

    IB ib1 = (int n)->{System.out.println(n);};
    IB ib2 = (n)->{System.out.println(n);};
    IB ib3 = n->{System.out.println(n);};
    ib3.mb(10);
    
  3. 若Lambda表达式的代码部分,只有一行代码,大括号可以省略

    IA ia2 = ()->{System.out.println("Lambda表达式");};
    IA ia3 = ()->System.out.println("Lambda表达式");
    ia3.ma();
    IB ib1 = (int n)->{System.out.println(n);};
    IB ib2 = (n)->{System.out.println(n);};
    IB ib3 = n->{System.out.println(n);};
    IB ib4 = n->System.out.println(n);
    ib4.mb(10);
    
  4. 若Lambda表达式的代码部分,只有一行return语句,return和大括号都可以省略

    ID id1 = (int n,int m)->{return n+m;};
    ID id2 = (n,m)-> n+m;
    =============================================================
    ID id1 = (int n,int m)->{return n+m;};
    ID id2 = (n,m)-> return n+m;//错误语法,大括号和return必须同时省略
    

chap11-常用类

Object类

概念: 又称为根类、超类、基类;Java中所有类的父类(直接或间接)

特点:

  1. Object类中定义了所有类都适用的一些方法,可以供其他类去继承;也可以根据需求,将这些方法进行覆盖
  2. Object类型的引用可以指向任意类型的对象
  3. 若一个类没有显示继承一个父类,则默认继承Object类

常用方法:

  1. getClass() 获取对象的实际类型,可以用于判断两个对象的类型是否相同

    class TestObject{
    	public static void main(String[] args){
    		Animal a1 = new Dog();
    		Animal a2 = new Cat();
    		Animal a3 = new Dog();
            
    		System.out.println(a1.getClass());
    		System.out.println(a2.getClass());
    		System.out.println(a1.getClass()==a2.getClass());//false
    		System.out.println(a1.getClass()==a3.getClass());//true
    		
    	}
    }
    
    class Animal{}
    class Dog extends Animal{}
    class Cat extends Animal{}
    
  2. boolean equals(Object o) 判断对象内容是否相等,方法内部默认比较的还是地址,所以需要进行覆盖,使之比较对象内容

    注意:从此以后,若两个对象的内容(属性)完全相同,即认为是同一对象;

    注意:一般情况下,重写equals()方法时,也要重写hashCode()方法,保证相同对象必须返回相同哈希码,不同对象尽量返回不同哈希码!

    class TestStudent{
    	public static void main(String[] args){
    		int a = 10;
    		int b = 10;
    		//== 表示判断是否相同,若两边是基本数据类型变量,则判断值是否相等
    		System.out.println(a==b);
    		
    		Student s1 = new Student("张三",18);
    		Student s2 = new Student("张三",18);
    		Student s3 = s1;
    		Student s4 = new Student("张三",19);
    		//== 表示判断是否相同,若两边是引用数据类型变量,则判断地址是否相等
    		System.out.println(s1==s2);//false
    		System.out.println(s1==s3);//true;
    		
    		System.out.println(s1.equals(s2));//true
    		System.out.println(s1.equals(s3));//true
    		System.out.println(s1.equals(s4));//false
    		
    		
    	}
    }
    class Student{
    	String name;
    	int age;
    	
    	public Student(){}
    	public Student(String name,int age){
    		this.name = name;
    		this.age = age;
    	}
    	
    	public boolean equals(Object o){
    		//自反性
    		if(this==o) return true;
    		//非空判断
    		if(o==null) return false;
    		//类型判断
    		if(this.getClass()!=o.getClass()) return false;
    		//类型强转
    		Student s = (Student)o;
    		//逐个比较属性
    		//判断字符串是否相同,必须使用equals()方法
    		if(this.name.equals(s.name)&&this.age==s.age)return true;
    		return false;
    	}
    }
    
  3. int hashCode() 可以返回对象的哈希码(默认将对象的地址转换为哈希码)

    要求:相同对象必须返回相同哈希码,不同对象尽量返回不同哈希码!

    class TestStudent{
    	public static void main(String[] args){	
    		Student s1 = new Student("张三",18);
    		Student s2 = new Student("张三",18);
    		Student s3 = new Student("李四",19);
    		
    		System.out.println(s1.hashCode());
    		System.out.println(s2.hashCode());
    		System.out.println(s3.hashCode());
    	}
    }
    class Student{
    	String name;
    	int age;
    	
    	public Student(){}
    	public Student(String name,int age){
    		this.name = name;
    		this.age = age;
    	}
        //根据对象的属性去获取对象的最终哈希码
    	public int hashCode(){
    		return name.hashCode()+age;
    	}
    }
    
  4. String toString() 返回对象的字符串形式

    class TestStudent{
    	public static void main(String[] args){
    		Student s1 = new Student("张三",18);
    		Student s2 = new Student("张三",18);
    		Student s3 = new Student("李四",19);
    		//输出语句会默认调用对象的toString()方法
    		System.out.println(s1.toString());
    		System.out.println(s2);
    		System.out.println(s3);
    	}
    }
    class Student{
    	String name;
    	int age;
    	
    	public Student(){}
    	public Student(String name,int age){
    		this.name = name;
    		this.age = age;
    	}
        //返回的字符串格式,可以根据需求自主决定
    	public String toString(){
    		//return name+age;
    		return "学生名字:"+name+", 学生年龄:"+age;
    	}
    }
    
  5. finalize() 用于垃圾对象的回收,释放内存空间

    • 垃圾对象: 没有任何引用指向的对象,即垃圾对象(零引用算法)
    • 垃圾回收:销毁垃圾对象,释放内存空间
    • 垃圾回收时机:
      • 自动垃圾回收:当内存被占用满,没有空间用来创建新对象时,垃圾回收器(GC)会自动调用所有垃圾对象的finalize()方法,销毁垃圾对象,释放内存空间
      • 手动垃圾回收: 通过 System.gc();可以手动通知GC回收垃圾,若GC空闲会立马回收垃圾对象,若是GC繁忙会推迟回收

总结:equals、hashCode、toString方法一般情况下都是要重写的

重点:

  1. Object在Java中的意义
  2. 前四个方法的作用,及重写语法

包装类

class Test{
	public static void main(String[] args){
		Object o = 12;
	}
}
问题:以上代码是否存在语法错误?Object引用是否能够指向基本数据类型的值
答:木有问题!

概念: 所有基本数据类型都有对应的引用类型,即包装类

基本数据类型包装类型
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

作用:

  1. Object可以统一管理到所有的数据类型
  2. 区分了0和null

经验:开发中,基本所有的属性数据类型都应该定义为包装类型

基本数据类型与包装类型的转换

  1. 基本数据类型------->包装类型

    • 通过构造方法: int i =10; Integer i1 = new Integer(i);
    • 通过静态方法valueOf() : int i = 10; Integer i1 = Integer.valueOf(i);
  2. 包装类型-------->基本数据类型

    • 通过xxxValue()方法: Integer i1 = new Integer(10); int i = i1.intValue();
      • 注意:xxx代表相应的基本数据类型
  3. JDK5.0以后,可以实现自动装箱、自动拆箱

    • 自动装箱:自动将基本数据类型转为包装类型
    • 自动拆箱:自动将包装类型转为基本数据类型
    class Test{
    	public static void main(String[] args){
            //自动装箱
    		Integer i1 = 10;
    		//自动拆箱
            int i2 = i1;
    		System.out.println(i1);
    		System.out.println(i2);
    	}
    }
    
    

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

  1. 基本数据类型------->String类型

    • +"" 例如: int i = 10; String s = i+""; 推荐使用!
    • 调用静态方法valueOf() 例如: int i = 10; String s = String.valueOf(i);
  2. String类型------->基本数据类型 重点!!!

    • 调用相应包装类的静态方法 包装类名.parseXxx();

      例如: String s = “12”; int i = Integer.parseInt(s);

      注意:字符串中存放的必须是相应的基本数据类型的值,否则会报运行时异常:java.lang.NumberFormatException 数据格式异常

      例如: String s = “12a”; int i = Integer.parseInt(s);//报异常

String类型与包装类型的转换

  1. 包装类型------->String类型

    • +“” Integer i1 = 10; String s = i1+""; 推荐使用!
    • 调用toString()方法 Integer i1 = 10; String s = i1.toString();
  2. String-------->包装类型

    • 通过构造方法 String s = “10”; Integer i1 = new Integer(s);
    • 调用静态方法valueOf String s = “10” ; Integer i1 = Integer.valueOf(s);

    注意:字符串中存放的必须是相应的包装类型的值,否则会报运行时异常:java.lang.NumberFormatException 数据格式异常

总结:

  1. 基本数据类型与包装类型转换:自动装箱、自动拆箱
  2. 基本属性类型转为String类型: +""
  3. String类型转为基本数据类型: 包装类名.parseXxx();

整数缓冲区

class Test{
	public static void main(String[] args){
		int i1 = 12;
		int i2 = 12;
		System.out.println(i1==i2);//true
		//整数缓冲区:-128~127  共256个整数,是最常用的整数
		//在整数缓冲区中已经预先创建好了这256个整数,若新建的Integer对象在这个区间内,
		//则会直接从整数缓冲区中获取,而不是创建新的对象
		Integer i3 = 12;
		Integer i4 = 12;
		System.out.println(i3==i4);//true
		
		Integer i5 = 200;
		Integer i6 = 200;
		System.out.println(i5==i6);//false
		
		//只有使用了new关键字,就一定会创建新的对象,不会再从缓冲区中获取
		Integer i7 = new Integer(12);
		Integer i8 = new Integer(12);
		System.out.println(i7==i8);//false
	}
}

String类

概念:字符串,由一系列字符组成的串

特点:常量,不可变长字符串

注意:字符串属于常量,内容不可变;所谓的字符串拼接,并不会在原有字符串的基础上拼接,而是创建了一个新的字符串对象,引用指向新字符串对象

String对象的创建方式:

1.  String str = "Hello";
2.  String str = new String("Hello");

若采用第一种方式创建String对象,会先在串池(字符串常量池)中寻找是否存在该字符串,若不存在,则在串池中开辟空间创建该字符串对象,引用指向串池中该对象;若存在,引用直接指向该对象;
    
若采用第二种方式创键String对象,会先在串池(字符串常量池)中寻找是否存在该字符串,若不存在,则在串池以及堆空间中年同时开辟空间创建该字符串对象,引用指向堆空间中的该字符串对象;若存在,则只在堆空间中开辟空间创建该字符串对象,引用指向堆空间中的该字符串对象;
    
===============================================================
class TestString{
	public static void main(String[] args){
		String s1 = "Hello";
		String s2 = "Hello";
		System.out.println(s1==s2);//true
		
		String s3 = new String("World");
		String s4 = new String("World");
		System.out.println(s3==s4);//false
		
		String s5 = "HelloWorld";
		String s6 = new String("HelloWorld");
		System.out.println(s5==s6);//false
		//intern()返回当前字符串对象在串池中的相应对象
		String s7 = s6.intern();
		System.out.println(s5==s7);//true
	}
}

String的不可变:

class TestString{
	public static void main(String[] args){
		Student s1 = new Student("小明");
		Student s2 = new Student("小郭");
		System.out.println(s1==s2);//false
		
		s2 = s1;
		System.out.println(s1==s2);//true
		System.out.println(s1.name);//小明
		System.out.println(s2.name);//小明
		
		s2.name = "小红";
		System.out.println(s1==s2);//true
		System.out.println(s1.name);//小红
		System.out.println(s2.name);//小红
		
		
		String str1 = "abc";
		String str2 = "abc";
		System.out.println(str1==str2);//true
		
		str2 = str1;
		System.out.println(str1==str2);//true
		System.out.println(str1);//abc
		System.out.println(str2);//abc
		
		str2 += "d";
		System.out.println(str1==str2);//false
		System.out.println(str1);//abc
		System.out.println(str2);//abcd
		
	}
}
class Student{
	String name;
	
	public Student(){}
	public Student(String name){
		this.name = name;
	}
}

在这里插入图片描述

可变长字符串:

  1. StringBuffer JDK1.0 线程安全 效率低
  2. StringBuilder JDK5.0 线程不安全 效率高
class TestStringBuilder{
	public static void main(String[] args){
		StringBuilder sb1 = new StringBuilder("abc");
		StringBuilder sb2 = new StringBuilder("abc");
		System.out.println(sb1==sb2);//false
		
		sb2 = sb1;
		System.out.println(sb1==sb2);//true
		
		//append()在当前字符串末尾拼接内容
		sb2.append("d");
		System.out.println(sb1==sb2);//true
		System.out.println(sb1);//abcd
		System.out.println(sb2);//abcd
		
	}
}

String类中的常用方法:

class TestString1{
	public static void main(String[] args){
		String str = "Hello";
		
		//1 int length()   返回当前字符串长度
		System.out.println(str.length());//5
		//2 char charAt(int index) 返回指定下标对应的字符
		System.out.println(str.charAt(1));//e
		//3 boolean contains(String s) 判断当前字符串是否包含s,若包含返回true,反之返回false;
		System.out.println(str.contains("el"));//true
		System.out.println(str.contains("he"));//false
		System.out.println(str.contains("eo"));//false
		//4 boolean startsWith(String s) 判断当前字符串是否以s开头
		System.out.println(str.startsWith("H"));//true
		System.out.println(str.startsWith("He"));//true
		System.out.println(str.startsWith("he"));//false
		System.out.println(str.startsWith("Hello"));//true
		//5 boolean endsWith(String suffix)  
		System.out.println(str.endsWith("o"));//true
		//6 boolean equals(Object anObject) 比较两个字符串是否相等
		System.out.println(str.equals("hello"));//false
		//7 boolean equalsIgnoreCase(String anotherString)  比较两个字符串是否相等,忽略大小写
		System.out.println(str.equalsIgnoreCase("hello"));//true
		//8 int indexOf(String str)  返回str在当前字符串中第一次出现的下标
		System.out.println(str.indexOf("l"));//2
		System.out.println(str.indexOf("lo"));//3
		//9 int lastIndexOf(String str)  返回str在当前字符串中最后一次出现的下标
		System.out.println(str.lastIndexOf("l"));//3
		//10 boolean isEmpty()  判断当前字符串是否为空串 ""  若为空串返回true,反之返回false
		System.out.println(str.isEmpty());//false
		String s1 = "";
		System.out.println(s1.length());//0
		System.out.println(s1.isEmpty());//true
		//11 String replace(String target, String replacement)  将当前字符传中的target替换为replacement;
		//必须使用旧引用接收方法的返回值,才能完成替换
		// str = str.replace("l","L");
		System.out.println(str.replace("l","L"));//false
		//12 String[] split(String regex)  通过regex将当前字符串分割成字符串数组
		// String[] ss = str.split("e");
		// for(String string:ss){
			// System.out.println(string);
		// }
		String[] ss = str.split("");
		for(String string:ss){
			System.out.println(string);
		}
		//13 String substring(int begin, int end) 返回起始下标begin及结束下标end之间的子串
		//包含起始下标,不包含结束下标
		System.out.println(str.substring(1,3));//el
		//14 char[] toCharArray()  将当前字符串转为char数组
		char[] cs = str.toCharArray();
		for(char c:cs){
			System.out.println(c);
		}
		//15 String toLowerCase()  将当前字符串转为全小写
		System.out.println(str.toLowerCase());//hello
		//16 String toUpperCase()  将当前字符串转为全大写
		System.out.println(str.toUpperCase());//	HELLO
		//17 String trim()  去除掉当前字符串开头和末尾的空格,中间的空格不会去除
		String s2 = " a b c ";
		System.out.println(s2.trim());//"a b c"
	}
}

chap12-集合

一.概念

集合:一种便利操作对象元素的容器,通常情况下可以代替数组.

都在java.util包下

学习方法:

  1. 学习集合接口及特点
  2. 学习接口中的方法
  3. 学习接口的实现类
  4. 遍历方式

二.根接口Collection

  • 特点:存放元素

  • 方法:

    add(Object o):将元素o添加至集合
    clear():清空集合
    remove(Object o):删除集合中元素o
    size():返回集合中的有效元素个数
    contains(Object o):判断集合中是否存在元素o
    isEmpty():判断集合是否为null
    toArray():将集合转为Object数组
    

三.List

  1. 特点:有序有下标,元素可重复

  2. 方法:

    add(int index,Object o):向集合中index位置处插入元素o
    remove(int index):把集合中下标index处的元素删除
    set(int index,Object o):把集合中index处的元素替换为o
    get(int index):获取下标index处的元素o
    
  3. 实现类

    • ArrayList[开发实战常用]

      • 特点

        有序有下标元素可重复

        底层数组实现,查询快,增删慢

        JDK1.2版本产物,线程不安全,效率高

      面试重点:new ArrayList()

      Arraylist如何创建底层数组,如何扩容?

      new ArrayList()创建集合时底层创建长度为0的数组,第一次添加元素时,数组长度扩容为10;后期数组满了之后再次扩容,长度为原始长度+原始长度/2;10–15–22–33–49

    • LinkedList

      • 特点

        有序有下标元素可重复

        底层链表实现,查询慢,增删快

    • Vector

      • 特点

        有序有下标元素可重复

        底层数组实现,查询快,增删慢

        JDK1.0版本产物,线程安全,效率低

      图例:

在这里插入图片描述

 >面试重点:ArrayList/LinkedList/Vector的区别是什么?
 >
 >
  1. 遍历方式

    • 下标遍历

      for(int i=0;i<集合名.size();i++) {
      	//通过集合名.get(i)可以获取集合元素
      }
      
    • foreach遍历

      for(元素类型 变量名:目标集合) {
          //通过变量名获取所有元素
      }
      
    • iterator遍历

      //获取迭代器
      Iterator i = list.iterator();
      while(i.hasNext()) {//hasNext():判断下一个元素是否为有效元素,是--true
      	//next():获取下一个元素,并指针向后移动一位
      }
      
    • 自遍历-forEach()

      //实现Consumer接口中的accept方法,完成对于元素的操作,参数值代表集合元素
      Consumer ca=t->System.out.println(t);
      //调用forEach(Consumer c)完成集合遍历(接口回调)
      list.forEach(ca);
      

四.泛型集合

  • 概念:类型安全的集合,约束存储的数据类型保持一致

  • 语法:

    List<数据类型> list=new ArrayList<数据类型>();
    
  • 作用:

    遍历时无需强转,直接使用泛型类型

  • 注意:

    • 前后泛型类型保持一致
    • 泛型不存在多态
    • 泛型只能使用八种基本类型的包装类
  • 泛型类

    class 类名<A,B>{}
    
  • 泛型接口

    interface 接口名<C>{}
    
  • 泛型方法

    public static<E extends 类名&接口名&接口名...> void m(E e){}
    

五.Collections

  • 倒序:Collections.reverse(list);

  • 随机:Collections.shuffle(list);

  • 排序:Collections.sort(list);

    使用sort方法,集合中的数据类型必实现Comparable接口
    重写其中方法
        public int compareTo(泛型类型){
        	负换正不换 后比前 参数为后面的元素
    	}
    
  • 排序:集合名.sort():

    使用外置比较器,需要写一个类实现Comparator接口
    重写其中方法
        public int compare(泛型类型 o1,泛型类型 o2){
        	负换正不换 o1:后 o1:前
    	}  
    使用Lambda表达式写法:
    list.sort((o1,o2)->{
    	//通过o1,o2指定排序规则
    });
    

六.Set

  • 特点:无序无下标,元素不可重复

  • 方法:无

  • 实现类

    • HashSet

      • 特点:

        ​ 无序无下标,元素不可重复

        ​ 底层由哈希表(数组+链表+红黑树)实现

      面试重点:如何保证HashSet的元素不重复?

      需要重写equals方法,可以进行内容的对比

      为了出发equlas的执行必须重写hashCode方法

      为了最少可能去调用equals方法,重写hashCode方法时,返回所有属性值的哈希码值之和

      public int hashCode() {
      return name.hashCode()+age.hashCode()+sex.hashCode()+score.hashCode();
      }

      面试重点:HashSet的底层实现是什么?

      new HashSet(),创建长度为16的空数组(哈希表),进行元素添加,添加元素时先计算哈希码值,对哈希码值进行%16;取余结果为元素在数组中的下标,如果获取到的哈希码值和集合中的元素的哈希码值相同需要进行equals对比,如果当前数组数据格不存在任何元素的话,可以直接添加,如果当前数组数据格存在元素的话,继续拉链存储,链表可以添加元素长度的8,如果超过8,转为红黑树存储(JDK8.0);如果达到数组加载因子的临界值,再次添加时,进行扩容,扩容长度是当前集合的一倍

    • LinkedHashSet

      • 特点:

        可以预知迭代顺序的set集合

        底层由哈希表+链表实现

    • TreeSet(Set–>SortedSet–>TreeSet)

      • 特点

        可以排序的set集合

  • 遍历方式

    • foreach遍历
    • 迭代器遍历
    • 自遍历–forEach
  • 结构图
    在这里插入图片描述

七.根接口Map

  • 特点

    键值对存储,键不能重复,值可以重复

  • 方法

    put(Object k,Object v):向集合中添加键值对
    remove(Object k):通过键删除键值对,返回删除值
    clear():清空集合
    get(Object k):通过键查询值
    size():返回有效元素的个数
    containsKey(Object k):判断集合中是否含有k键
    containsValue(Object v):判断集合中是否含有v值
    isEmpty():判断集合是否为null
    keySet():返回所有键组成的Set集合
    values():返回所有值组成的Collection集合
    entrySet():返回所有键值对对象(Map.Entry)组成的Set集合
    
  • 实现类

    • HashMap:

      • 特点:键值对存储,键不能重复,值可以重复,null可以作为键或者值

        底层哈希表实现

        JDK1.2版本产物,线程不安全,效率高

    • Hashtable:

      • 特点:键值对存储,键不能重复,值可以重复,null不可以作为键或者值

        JDK1.0版本产物,线程安全,效率低

    • LinkedHashMap:

      • 特点:可以记录插入顺序的map集合

        底层由哈希表+链表

    • Treemap:(Map–>SortedMap–>TreeMap)

      • 特点:按照键进行自然排序的map集合
    • Properties:(Map–>Hashtable–>Properties)

      • 特点:键和值都为String

        作用:通常用于读取配置文件

  • 遍历方式

    • 键遍历–keySet():返回所有键组成的Set集合

      Set<Integer> set=map.keySet();
      for(Integer i:set) {
      	System.out.println(i+"="+map.get(i));
      }
      
    • 值遍历–values():返回所有值组成的Collection集合

      Collection<String> coll=map.values();
      coll.forEach(v->System.out.println(v));
      
    • 键值对遍历–entrySet():返回所有键值对对象(Map.Entry)组成的Set集合

      Set<Map.Entry<Integer,String>> set2=map.entrySet();
      for(Map.Entry<Integer, String> e:set2) {
      	System.out.println(e.getKey()+"="+e.getValue());
      }
      map.forEach((t,u)->System.out.println(t+"="+u));
      
    • 自遍历

      map.forEach((t,u)->System.out.println(t+"="+u));
      
  • 图解

在这里插入图片描述

重点:

  1. 集合的作用
  2. List\Set\Map的特点
  3. ArrayList的创建方式,基本的存值取值及遍历方式(下标遍历\forEach遍历)
  4. HashSet的创建方式,基本的存值及遍历方式(forEach遍历)
  5. HashSet集合的底层原理(面试问题)
  6. HashMap的创建方式,基本的存值\取值及遍历方式(键遍历)
  7. HashMap与HashTable的区别

chap13-异常

一.概念

  • 异常:程序在运行时发生的不正常情况
  • 处理异常:预先写好一段代码,当异常发生时执行这段代码,避免程序异常给用户带来的不必要的麻烦或这损失.

二.异常的分类

  • Throwable(Error和Exception的父类)

    • 方法

      getMessage():返回此 throwable 的详细信息
      printStackTrace():返回此 throwable 的异常追踪信息
      
  • Error:

    • 仅仅靠程序无法解决的问题,一般是硬件问题,虚拟机问题或者程序的的逻辑问题,不处理
    • StackOverFlowError:栈溢出错误–无限递归
    • 溢出:空间不足(无限递归) 泄露:空间不足(空间不够大)
  • Exception:

    • 非RunTimeException:

      ​ 编译时期发生的异常情况,非运行时异常,编译时异常,受查异常,已检查异常,必须处理

      ​ 所以异常都是Exception除RuntimeException的子类型

      ​ java.lang.ClassNotFoundException:类未找到异常

    • RuntimeException:

      ​ 运行时期发生的异常情况,运行时异常,未检查异常,可以处理也可以不处理
      ​ 所有异常都是RuntimeException本身或其子类型

      ​ java.lang.NullPointerException:空指针异常

      ​ java.lang.ArrayIndexOutOfBoundsException:数组下标越界异常

      ​ java.lang.ClassCastException:类型转换异常

      ​ java.lang.ArithmeticException:算术异常

      ​ java.lang.NumberFormatException:数字格式异常

      ​ java.util.InputMismatchException:输入不匹配异常

      ​ java.lang.IndexOutOfBoundsException:下标越界异常

三.异常的产生

  • 自动产生异常:程序运行过程中遇到错误的代码,程序因为异常产生而终止
  • 手动产生异常:
    • 语法:throw 异常对象;(定义在方法内部)
    • 作用:手动产生异常相当于return,中止程序运行

四.异常的传递

  • 异常具有传递性,按照调用链(main–>m1–>m2–>m3–>m4)的反向(m4–>m3–>m2–>m1–>main)进行传递,导致程序因为异常中止,异常最终交由JVM,(JVM的处理方式,打印异常的追踪信息)

五.异常的处理

  • 消极处理

    • 语法:在访问声明的尾部throws 异常类型

      修饰符 返回值类型 方法名(参数表)throws 异常类型
      
    • 结果:可以保证程序编译通过

    • 作用:消极处理异常的方式是一种不负责任的异常处理,将异常向上声明,谁调用谁处理,程序还是会因为异常的产生而中止.

  • 积极处理

    • 语法:

      try{
          可能产生异常的代码
      }catch(捕获到的异常类型){
          对异常的处理 
      }
      
    • 注意:

      1.可以存在多个catch,异常对象产生后,catch从上到下依次进行匹配,如果匹配成功执行catch块中的内容
      2.由于多态的原因,父类异常类型可以匹配子类异常对象,catch中父类异常写在后面,子类异常写在前面
      3.可以捕获未发生的运行时异常,不能捕获未发生的非运行时异常
      
    • finally

      try{
          可能发生异常的代码
      }catch(捕获的异常类型){
          异常的处理
      }finally{
          无论异常是否发生都必须执行的代码
      }
      //finally:通常用来关闭资源
      

      面试重点:final/finalize/finally的区别?

    • 异常处理条线(异常处理结构)

      try{}catch(){}
      try{}catch(){}catch(){}...
      try{}catch(){}finally{}
      try{}catch(){}catch(){}...finally{}
      try{}finally{}
      try{try{}catch(){}}catch(){}
      try{}catch(){try{}catch(){}}
      try{}catch(){}finally{try{}catch(){}}
      
    • 作用:不仅保证程序编译通过,而且程序不会因为异常产生而中止

六.自定义异常

  • 自定义一个类,必须继承Exception或者其子类

    class AgeException extends RuntimeException{
    	public AgeException() {}
    	public AgeException(String message) {
    		super(message);
    	}
    }
    
  • 在产生异常的地方抛出异常

七.方法覆盖

  • 方法覆盖初级版

    方法名相同,参数列表相同,返回值类型相同,访问修饰符相同或更宽
    
  • 方法覆盖升级版

    方法名相同,参数列表相同,返回值类型相同,访问修饰符相同或更宽
    子类不能比父类抛出更大的异常(与运行时异常无关)
    
    //子类不能比父类抛出更大的异常(与运行时异常无关)
    class Super{
    	public void m1(){
    		System.out.println("m1---super");
    	}
    }
    class Sub extends Super{
    	public void m1() throws RuntimeException{
    		System.out.println("m1---sub");
    	}
    }
    
    
  • 方法覆盖终极版

    方法名相同,参数列表相同,访问修饰符相同或更宽
    子类不能比父类抛出更大的异常(与运行时异常无关)
    子类的返回值可以是父类方法返回值的子类型
    
    class Super{
    	public Object m2() {
    		System.out.println("m2---super");
    		return null;
    	}
    }
    class Sub extends Super{
    	public Double m2() {
    		System.out.println("m2---sub");
    		return null;
    	}
    }
    

重点:

  1. 异常的概念
  2. 异常的分类
  3. 异常的消极处理及积极处理\finally语句
  4. 自定义异常语法
  5. 方法覆盖的终极要求
  6. final与finalize与finally的区别(面试题)

chap14-IO

思考: 在之前,程序中的数据存在哪里?

答: 通过变量\对象\数组\集合,存在内存中,但是无法持久化保存

注意: 可以通过IO技术,讲数据存在磁盘上,完成持久化存储

I\O: input(输入)\output(输出)

流: 是一种对象,类似于水管\管道,通过流对象可以实现数据的传输(输入与输出)

在这里插入图片描述

流的分类

按照不同的分类方式:

  1. 按照数据的传输方向:

    • 输入流 将数据输入到JVM中
    • 输出流 将数据从JVM中输出到外界
  2. 按照数据的传输单位:

    • 字节流 以字节为传输单位,可以传输任意类型的数据(文本\图片\视频\音频等等)
    • 字符流 以字符为传输单位,只能传输文本文件(文本文件: 可以通过记事本打开的文件,例如: .txt .java .html等)
  3. 按照流的功能:

    • 节点流 具有实际传输能力的流
    • 过滤流 不具有实际传输能力,但是可以增强节点流的传输能力或为节点流增加新的功能

在这里插入图片描述

字节流

字节流的总父类

  • InputStream(抽象类) 字节输入流
  • OutputStream(抽象类) 字节输出流

节点流

  1. FileOutputStream 文件字节输出流

    • 创建语法1: FileOutputStream fos = new FileOutputStream(“文件路径”);

      • 注意: 默认覆盖掉文件中的原有内容

      • 绝对路径: 以电脑本身为参照物, 盘符名:/文件夹名/文件名,例如: c:/file/a.txt

        注意:从盘符开始找起,若文件不存在,会自动创建文件;但文件夹必须存在

      • 相对路径: 以项目本身为参照物, 文件夹名/文件名, 例如: file/a.txt

        注意: 在项目名(CoreJava)下寻找file文件夹,再寻找a.txt;若文件不存在,会自动创建文件;但文件夹必须存在

    • 创建语法2: FileOutputStream fos = new FileOutputStream(“文件路径”,true\false);

      true 代表在原有内容上追加新内容,不会覆盖掉原有内容

      false 覆盖掉原有内容

    • 常用方法

      1. void write(int b) 将字节b输出到指定的文件中
      class TestFileOutputStream {
      	public static void main(String[] args) throws Exception {
      		//创建文件字节输出流
      		FileOutputStream fos = new FileOutputStream("a.txt",true);
      		//输出数据
      		fos.write(67);
      		//关闭流
      		fos.close();
      	}
      }
      
      1. void write(byte[] b) 将字节数据b中的数据输出到指定文件中
      class TestFileOutputStream {
      	public static void main(String[] args) throws Exception {
      		//创建文件字节输出流
      		FileOutputStream fos = new FileOutputStream("a.txt");
      		//输出数据
      		String str = "ABCDEFGHIJKLMN";
      		byte[] bs = str.getBytes();
      		fos.write(bs);
      		//关闭流
      		fos.close();
      	}
      }
      
      1. void write(byte[] b, int off, int len) 从b数组的off下标开始(包含off下标),往后的len个元素输出到指定文件中
      class TestFileOutputStream {
      	public static void main(String[] args) throws Exception {
      		//创建文件字节输出流
      		FileOutputStream fos = new FileOutputStream("a.txt");
      		//输出数据
      		String str = "ABCDEFGHIJKLMN";
      		byte[] bs = str.getBytes();
      		fos.write(bs,1,5);//BCDEF
      		//关闭流
      		fos.close();
      	}
      }
      
  2. FileInputStream 文件字节输入流

    创建语法: FileInputStream fis = new FileInputStream(“文件路径”);

    ​ 注意: 文件必须存在,不会自动创建!!!

    常用方法:

    1. int read() 每次读取一个字节的数据,返回值为读取内容;若读取到文件末尾,返回-1
    class TestFileInputStream {
    	public static void main(String[] args) throws Exception {
    		//创建文件字节输入流
    		FileInputStream fis = new FileInputStream("a.txt");
    		//每次读取一个字节,返回值为读取内容
    		int i = fis.read();
    		System.out.println((char)i);
    		//关流
    		fis.close();
    	}
    }
    ===================================================================
    //循环读取
    class TestFileInputStream {
    	public static void main(String[] args) throws Exception {
    		//创建文件字节输入流
    		FileInputStream fis = new FileInputStream("a.txt");
    		//循环读取
    		while(true) {
    			int i = fis.read();
    			if(i==-1)break;
    			System.out.println((char)i);
    		}
    		//关流
    		fis.close();
    	}
    }    
    
    1. int read(byte[] b) 每次读取一个字节数组的数据, 返回值为读取元素个数;若读取到文件末尾,返回-1
    class TestFileInputStream {
    	public static void main(String[] args) throws Exception {
    		//创建文件字节输入流
    		FileInputStream fis = new FileInputStream("a.txt");
    		byte[] bs = new byte[10];
    		int len = fis.read(bs);
    		System.out.println(len);
    		for(byte b:bs) {
    			System.out.println((char)b);
    		}
    		//关流
    		fis.close();
    	}
    }
    ============================================================
    class TestFileInputStream {
    	public static void main(String[] args) throws Exception {
    		//创建文件字节输入流
    		FileInputStream fis = new FileInputStream("a.txt");
    		
    		byte[] bs = new byte[10];
            //循环读取 
    		while(true) {
    			int len = fis.read(bs);
    			System.out.println(len);
    			if(len==-1)break;
    			for(int i=0;i<len;i++) {
    				System.out.println((char)bs[i]);
    			}
    		}
    		//关流
    		fis.close();
    	}
    }
    
    1. int read(byte[] b, int off, int len) 每次读取一个字节数组,从数组的off下标开始存储,存储len个长度;若读取到文件末尾,返回-1
    class TestFileInputStream {
    	public static void main(String[] args) throws Exception {
    		//创建文件字节输入流
    		FileInputStream fis = new FileInputStream("a.txt");
    		
    		byte[] bs = new byte[10];
    		int len = fis.read(bs, 2, 4);
    		for(byte b:bs) {
    			System.out.println(b);
    		}
    		//关流
    		fis.close();
    	}
    }
    

IO标准异常处理:

try-catch-finally
class TestFileOutputStream1 {
	public static void main(String[] args){
		FileOutputStream fos =null;
		try {
			fos = new FileOutputStream("b.txt");
			fos.write(65);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			//非空判断
			if(fos!=null) {
				try {
					fos.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
		}
	}
}
class TestFileInputStream1 {
	public static void main(String[] args){
		FileInputStream fis = null;
		try {
			fis = new FileInputStream("b.txt");
			int i = fis.read();
			System.out.println(i);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if (fis!=null) {
				try {
					fis.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}
======================================================================
JDK7.0   try-with-resource   实现自动关流    可以写多个对象,对象之间使用;隔开
try(实现了AutoCloseable接口的对象1;
    实现了AutoCloseable接口的对象2
   ){
    //可能发生异常的代码
}catch(异常 e){
    //处理异常代码
}    
class TestFileOutputStream2 {
	public static void main(String[] args){
		try (FileOutputStream fos = new FileOutputStream("b.txt")) {
			fos.write(67);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
class TestFileInputStream2 {
	public static void main(String[] args){
		try (FileInputStream fis = new FileInputStream("b.txt")) {
			int i = fis.read();
			System.out.println(i);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

文件拷贝----复制粘贴

class TestFileCopy {
	public static void main(String[] args) {
		fileCopy2("d:/a.wmv", "f:/a.wmv");
	}
	public static void fileCopy1(String startAddr,String endAddr) {
		long t1 = System.nanoTime();
		try (FileInputStream fis = new FileInputStream(startAddr);
				FileOutputStream fos = new FileOutputStream(endAddr)) {
			while(true) {
				int i = fis.read();
				if(i==-1)break;
				fos.write(i);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long t2 = System.nanoTime();
		System.out.println((t2-t1)/1E9);
	}
	public static void fileCopy2(String startAddr,String endAddr) {
		long t1 = System.nanoTime();
		try (FileInputStream fis = new FileInputStream(startAddr);
				FileOutputStream fos = new FileOutputStream(endAddr)) {
			//充当数组缓冲区,减少访问磁盘次数,提高效率
			byte[] bs = new byte[1024];
			while(true) {
                //返回值为有效读取长度
				int len = fis.read(bs);
                //若len为-1 说明读取到文件末尾,结束循环读取
				if(len==-1)break;
                //每次输出一个数组,从0下标开始,到len个长度结束
				fos.write(bs,0,len);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long t2 = System.nanoTime();
		System.out.println((t2-t1)/1E9);
	}
}

过滤流

  • BufferedInputStream BufferedOutputStream 缓冲过滤流

    创建语法:

    ​ BufferedInputStream bis = new BufferedInputStream(fis);

      BufferedOutputStream bos = new BufferedOutputStream(fos);
    

    特点: 内置数组缓冲区,减少访问磁盘次数,提高效率

    注意:由于数据会先进入缓冲区,所以需要flush()将数据从缓冲区中刷新出来;或者直接close()也行,会自动刷新缓冲区

    注意:若使用过滤流,只需要关闭最外层流即可!

    class TestBufferedOutputStream {
    	public static void main(String[] args) throws Exception {
    		FileOutputStream fos = new FileOutputStream("c.txt");
    		BufferedOutputStream bos = new BufferedOutputStream(fos);
    		bos.write(66);
    		//刷新缓冲区
    //		bos.flush();
    		bos.close();
    		
    		FileInputStream fis = new FileInputStream("c.txt");
    		BufferedInputStream bis = new BufferedInputStream(fis);
    		int i = bis.read();
    		System.out.println(i);
    		bis.close();
    	}
    }
    

    文件拷贝----过滤流版

    class TestFileCopy {
    	public static void main(String[] args) {
    		//fileCopy2("d:/a.wmv", "f:/a.wmv");  1.11秒
    		fileCopy3("d:/a.wmv", "f:/a.wmv");//0.29秒
    	}
    	public static void fileCopy1(String startAddr,String endAddr) {
    		long t1 = System.nanoTime();
    		try (FileInputStream fis = new FileInputStream(startAddr);
    				FileOutputStream fos = new FileOutputStream(endAddr)) {
    			while(true) {
    				int i = fis.read();
    				if(i==-1)break;
    				fos.write(i);
    			}
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		long t2 = System.nanoTime();
    		System.out.println((t2-t1)/1E9);
    	}
    	public static void fileCopy2(String startAddr,String endAddr) {
    		long t1 = System.nanoTime();
    		try (FileInputStream fis = new FileInputStream(startAddr);
    				FileOutputStream fos = new FileOutputStream(endAddr)) {
    			//充当数组缓冲区,减少访问磁盘次数,提高效率
    			byte[] bs = new byte[1024];
    			while(true) {
    				int len = fis.read(bs);
    				if(len==-1)break;
    				fos.write(bs,0,len);
    			}
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		long t2 = System.nanoTime();
    		System.out.println((t2-t1)/1E9);
    	}
    	public static void fileCopy3(String startAddr,String endAddr) {
    		long t1 = System.nanoTime();
    		try (FileInputStream fis = new FileInputStream(startAddr);
    				BufferedInputStream bis = new BufferedInputStream(fis);
    				FileOutputStream fos = new FileOutputStream(endAddr);
    				BufferedOutputStream bos = new BufferedOutputStream(fos)) {
    			//充当数组缓冲区,减少访问磁盘次数,提高效率
    			byte[] bs = new byte[1024];
    			while(true) {
    				int len = bis.read(bs);
    				if(len==-1)break;
    				bos.write(bs,0,len);
    			}
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		long t2 = System.nanoTime();
    		System.out.println((t2-t1)/1E9);
    	}
    }
    
  • PrintStream 标准输出流

    • 面试问题: 请你说一下你对输出语句(System.out.println())的理解-----面试问题
    • 回答: System是一个类,out是该类的静态属性,类型为PrintStream,println()是该流的一个方法
  • ObjectInputStream ObjectOutputSteam 对象过滤流

    • 作用1: 增加了读写8种基本数据类型的能力

      class TestObjectOutputStream {
      	public static void main(String[] args) throws Exception {
      		FileOutputStream fos = new FileOutputStream("d.txt");
      		ObjectOutputStream oos = new ObjectOutputStream(fos);
      		oos.writeDouble(1.25);
      		oos.close();
      		
      		FileInputStream fis = new FileInputStream("d.txt");
      		ObjectInputStream ois = new ObjectInputStream(fis);
      		double d = ois.readDouble();
      		System.out.println(d);//1.25
      		ois.close();
      	}
      }
      
    • 作用2: 增加了读写对象的能力

      • 注意1: 当读取到文件末尾时,会报异常:java.io.EOFException;可以作为结束循环的条件
      class TestObjectOutputStream {
      	public static void main(String[] args) throws Exception {
      		FileOutputStream fos = new FileOutputStream("f.txt");
      		ObjectOutputStream oos = new ObjectOutputStream(fos);
      		oos.writeObject("窗前明月光");
      		oos.writeObject("疑是地上霜");
      		oos.close();
      		
      		FileInputStream fis = new FileInputStream("f.txt");
      		ObjectInputStream ois = new ObjectInputStream(fis);
      		while(true) {
      			try {
      				String str = (String)ois.readObject();
      				System.out.println(str);
      			} catch (Exception e) {
      				break;
      			}
      		}
      		
      		ois.close();
      	}
      }
      
      • 注意2:若读取的对象类型为自定义类型,该类型必须实现序列化接口–Serializable,表示该类型的对象可以放在流上传输;注意,该类型中的属性的类型若是自定义类型,也需要实现Serializable;

        可以通过transient修饰符,完成反序列化,即该属性的值不会在流上传输

      class TestObjectOutputStream {
      	public static void main(String[] args) throws Exception {
      		FileOutputStream fos = new FileOutputStream("g.txt");
      		ObjectOutputStream oos = new ObjectOutputStream(fos);
      		Student s1 = new Student("小郭",18,100);
      		oos.writeObject(s1);
      		oos.close();
      		
      		FileInputStream fis = new FileInputStream("g.txt");
      		ObjectInputStream ois = new ObjectInputStream(fis);
      		Student stu =(Student)ois.readObject();
      		System.out.println(stu);
      		ois.close();
      	}
      }
      class Student implements Serializable{
      	private String name;
      	private int age;
      	private transient double scoer;
      	public String getName() {
      		return name;
      	}
      	public void setName(String name) {
      		this.name = name;
      	}
      	public int getAge() {
      		return age;
      	}
      	public void setAge(int age) {
      		this.age = age;
      	}
      	public double getScoer() {
      		return scoer;
      	}
      	public void setScoer(double scoer) {
      		this.scoer = scoer;
      	}
      	public Student() {
      		super();
      		// TODO Auto-generated constructor stub
      	}
      	public Student(String name, int age, double scoer) {
      		super();
      		this.name = name;
      		this.age = age;
      		this.scoer = scoer;
      	}
      	@Override
      	public int hashCode() {
      		final int prime = 31;
      		int result = 1;
      		result = prime * result + age;
      		result = prime * result + ((name == null) ? 0 : name.hashCode());
      		long temp;
      		temp = Double.doubleToLongBits(scoer);
      		result = prime * result + (int) (temp ^ (temp >>> 32));
      		return result;
      	}
      	@Override
      	public boolean equals(Object obj) {
      		if (this == obj)
      			return true;
      		if (obj == null)
      			return false;
      		if (getClass() != obj.getClass())
      			return false;
      		Student other = (Student) obj;
      		if (age != other.age)
      			return false;
      		if (name == null) {
      			if (other.name != null)
      				return false;
      		} else if (!name.equals(other.name))
      			return false;
      		if (Double.doubleToLongBits(scoer) != Double.doubleToLongBits(other.scoer))
      			return false;
      		return true;
      	}
      	@Override
      	public String toString() {
      		return "Student [name=" + name + ", age=" + age + ", scoer=" + scoer + "]";
      	}
      }
      

字符流

概念: 专门用来处理char及String类型的数据, 设置编解码集

字符编码集

计算机底层只能识别二进制码,而所有二进制都可以转换为十进制的整数,每一个字符都对应了相应的一个整数

编码: 字符-------------->整数

​ 例如: 加密过程: 将原文加密为密文

解码: 整数-------------->字符

​ 例如: 解密过程: 将密文解为原文

字符编码集: 即每个字符与整数的对应关系

常见的字符编码集:

  • GBK 简体中文
  • Big5 繁体中文
  • ISO-8859-1 西欧
  • ASCII 美国 美国交换标准,美国是永远不会出现乱码的国家
  • Unicode 万国码\统一码
  • UTF-8 行业标准 每个字符根据大小可能占用1个字节\2个字节\3个字节
  • UTF-16 Java底层默认使用的编码集,每个字符,无论大小固定占用两个字节

字符流

字符流总父类(抽象类):

  • Writer 字符输出流
  • Reader 字符输入流
节点流
  • FileWriter 文件字符输出流

    ​ 创建语法: FileWriter fw = new FileWriter(“文件路径”);

    ​ 常用方法: void write(String str) 输出一个字符串

    class TestFileWriter {
    	public static void main(String[] args) throws Exception {
    		FileWriter fw = new FileWriter("a.txt");
    		fw.write("好好学习,天天向上");
    		fw.close();
    	}
    }
    
  • FileReader 文件字符输入流

    创建语法: FileReader fr = new FileReader(“文件路径”);

    常用方法: int read() 每次读取一个字符,返回值为读取内容;读取到文件末尾,返回-1;

    class TestFileReader {
    	public static void main(String[] args) throws Exception {
    		FileReader fr = new FileReader("a.txt");
    		int i = fr.read();
    		System.out.println((char)i);
    		
    	}
    }
    ==========================================================
    class TestFileReader {
    	public static void main(String[] args) throws Exception {
    		FileReader fr = new FileReader("a.txt");
    		while(true) {
    			int i = fr.read();
    			if(i==-1)break;
    			System.out.print((char)i);
    		}
    		fr.close();
    	}
    }
    
过滤流
  • PrintWriter|BufferedReader 缓冲过滤流

    • 注意: 使用PrintWriter代替BufferedWriter,是因为PrintWriter中有更多好用\强大的方法
    • 创建语法: 基于字符节点流
    class TestPrintWriter {
    	public static void main(String[] args) throws Exception {
    		FileWriter fw = new FileWriter("b.txt");
    		PrintWriter pw = new PrintWriter(fw);
    		//输出并换行
    		pw.println("好好学习,天天向上");
    		pw.println("锻炼身体,报效祖国");
    		pw.close();
    		
    		FileReader fr = new FileReader("b.txt");
    		BufferedReader br = new BufferedReader(fr);
    		while(true) {
    			//读取一行数据;若读取到文件末尾,返回null
    			String str = br.readLine();
    			if(str==null)break;
    			System.out.println(str);
    		}
    		br.close();
    	}
    }
    
  • InputStreamReader|OutputStreamWriter 桥转换流

    • 作用:可以将字节流转换为字符流,并指定编码集
    • 创建语法:
      • InputStreamReader isr = new InputStreamReader(字节输入流对象,“编码集”);
      • OutputStreamWriter osw = new OutputStreamWriter(字节输出流对象,“编码集”);
    class TestOutputStreamWriter {
    	public static void main(String[] args) throws Exception {
    		//1. 创建字节节点流
    		FileOutputStream fos = new FileOutputStream("c.txt");
    		//2.创建桥转换流
    		OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
    		//3.创建缓冲过滤流
    		PrintWriter pw = new PrintWriter(osw);
    		//4.输出数据
    		pw.println("好好学习");
    		pw.println("天天向上");
    		pw.println("锻炼身体");
    		pw.println("报效祖国");
    		//5.关流
    		pw.close();
    		
    		FileInputStream fis = new FileInputStream("c.txt");
    		InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
    		BufferedReader br = new BufferedReader(isr);
    		while(true) {
    			String line = br.readLine();
    			if(line==null)break;
    			System.out.println(line);
    		}
    		br.close();
    	}
    }
    

File类

IO流: 处理文件中的数据

File类: 处理文件或文件夹的

File对象: 代表一个文件或文件夹

创建语法: File f = new File(“文件路径”); e:/2101/a/b a.txt

常用方法:

  • boolean createNewFile() 创建文件,若文件不存在,则创建成功并返回true;若文件已存在,则创建失败并返回false
  • boolean mkdir() 创建文件夹,若文件夹不存在,则创建成功并返回true;若文件夹已存在,则创建失败并返回false;注意:父文件夹必须存在!!!
  • boolean mkdirs() 创建文件夹,并同时创建不存在的父文件夹
  • boolean delete() 删除文件或空文件夹,删除成功返回true,反之返回false
  • boolean exists() 判断文件或文件夹是否存在,存在返回true,不存在返回false
  • String getAbsolutePath() 返回当前File对象绝对路径(String形式)
  • String getName() 返回当前File对象的名字
  • String getParent() 返回当前File对象的父目录地址
  • String getPath() 返回当前File对象相对路径(String形式)
  • boolean isDirectory() 判断当前File对象是否为文件夹
  • boolean isFile() 判断当前File对象是否为文件
  • long length() 获取当前File对象的大小
  • File[] listFiles() 返回当前File对象(文件夹对象)中所有File对象
class TestFile {
	public static void main(String[] args) {
//		File f1 = new File("a.txt");
//		System.out.println(f1.isDirectory());
//		System.out.println(f1.isFile());
//		System.out.println(f1.getAbsolutePath());
//		System.out.println(f1.getPath());
//		System.out.println(f1.getParent());
//		File f2 = new File("file/j.txt");
//		System.out.println(f2.getParent());
//		System.out.println(f1.getName());
//		System.out.println(f1.length());
		//对文件调用listFiles() 会返回null
//		File[] files = f1.listFiles();
//		for (File file : files) {
//			System.out.println(file);
//		}
//		System.out.println(f1.exists());
		
		
		File f2 = new File("E:\\2101\\CoreJava");
		System.out.println(f2.isDirectory());
		System.out.println(f2.getName());
		System.out.println(f2.getAbsolutePath());
		System.out.println(f2.getPath());
		System.out.println(f2.mkdir());
		System.out.println(f2.getParent());
		File[] files = f2.listFiles();
		for (File file : files) {
			System.out.println(file.getAbsolutePath());
		}
	}
}
随堂小练习: 查询  E:\2101\CoreJava  目录下所有的java文件,并复制到桌面上的copyJava的文件夹中
    
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class TestFileCopy {
	public static void main(String[] args) {
		searchJava(new File("E:\\2101\\CoreJava"));
	}
	//找出所有的Java文件
	public static void searchJava(File f) {
		if(f.exists()&&f.isDirectory()) {
			File[] files = f.listFiles();
			for (File file : files) {
				if(file.isDirectory()) {
					searchJava(file);
				}else {
					if(file.getName().endsWith(".java")) {
						copyJava(file);
					}
				}
			}
		}
	}
	//文件拷贝--复制Java文件
	public static void copyJava(File f) {
		File tofile = new File("C:\\Users\\Administrator\\Desktop\\copyJava");
		tofile.mkdir();
		try (//1.创建字节输入流
		 FileInputStream fis = new FileInputStream(f.getAbsolutePath());
				//2.桥转换流
				 InputStreamReader isr = new InputStreamReader(fis,"GBK");
				//3.字符缓冲过滤流
				 BufferedReader br = new BufferedReader(isr);
				FileOutputStream fos = new FileOutputStream(tofile.getAbsolutePath()+"\\"+f.getName());
				OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");
				PrintWriter pw = new PrintWriter(osw)) {
			while(true) {
				String line = br.readLine();
				if(line==null) break;
				pw.println(line);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}    


chap15-多线程

进程

概念: OS(操作系统)中并发执行(同时执行)的多个任务程序

注意: 只有正在执行的程序才是进程!

并发原理: 宏观并行,微观串行

OS会将一个时间段的CPU处理能力,分成多个时间片,单个时间片内CPU只能执行一个程序,只要时间片划分的足够细小,交替频率足够快,就能形成宏观并行的假象,实质上依然是微观串行!!

线程

概念: 是进程中并发执行的多个任务逻辑,轻量级进程

例如: QQ是一个进程,而QQ中打开的多个聊天窗口是多个线程

注意: 线程是进程的基本组成单元

并发原理: 宏观并行,微观串行

Java中没有多进程,只有多线程!!!

因为所有的Java代码都是运行在JVM中,而JVM本身就是一个进程!在之间所学中,JVM中只有一个线程(main主线程),当main函数执行结束了,就意味着主线程结束了,即JVM进程也就结束了;但从今天开始,我们除了主线程之外,可能还会创建其他的线程,若主线程结束,其他线程尚未执行结束,则程序(JVM进程)也不会结束!

线程的组成:

  1. CPU时间片 OS负责分配
  2. 数据
    • 栈 每个线程都有自己独立的栈空间
    • 堆 堆空间是所有线程共享的
  3. 代码

创建线程

  1. 继承线程类(Thread), 覆盖run()方法

    class TestThread {
    	public static void main(String[] args) {
    		System.out.println("main   start ");
    		//创建线程对象
    		Thread t1 = new MyThread();
    		//开启线程
    		//t1.run(); 错误,该代码只是调用了一个对象的方法,不属于开启线程
    		t1.start();
    		System.out.println("main   end ");
    	}
    }
    //1. 继承线程类
    class MyThread extends Thread{
    	//run方法中定义当前线程对象所要执行的任务逻辑代码
    	public void run() {
    		for(int i=1;i<=100;i++) {
    			System.out.println("t1: "+i);
    		}
    	}
    }
    
  2. 实现Runnable接口,实现run方法

    class TestThread {
    	public static void main(String[] args) {
    		System.out.println("main   start ");
    		//创建线程对象
    		//1. 创建任务对象
    		Task task = new Task();
    		//2. 创建线程对象
    		Thread t2 = new Thread(task);
    		//3. 开启线程
    		t2.start();
    		System.out.println("main   end ");
    	}
    }
    //2. 实现Runnable接口
    class Task implements Runnable{
    
    	public void run() {
    		for(int i=1;i<=100;i++) {
    			System.out.println("t2: "+i);
    		}
    	}
    	
    }
    
  3. 匿名内部类创建线程对象–特殊写法而已,不属于新的创建方式!

    class TestThread1 {
    	public static void main(String[] args) {
    		System.out.println("main   start ");
    		Thread t1 = new Thread() {
    			public void run() {
    				for(int i=1;i<=1000;i++) {
    					System.out.println("t1: "+i);
    				}
    			}
    		};
    		
    		Thread t2 = new Thread(new Runnable() {
    			public void run() {
    				for(int i = 1;i<=1000;i++) {
    					System.out.println("t2: "+i);
    				}
    			}
    		});
    		
    		t1.start();
    		t2.start();
    		
    		System.out.println("main   end ");
    	}
    }
    

线程状态

  • 基本状态

在这里插入图片描述

  • 等待状态

    • 睡眠 static void sleep(long millis) 使当前线程睡眠millis毫秒,此时当前线程会进入有限期等待状态,当睡眠时间结束时,当前线程会回到就绪状态,重新等待时间片的分配

      class TestSleep {
      	public static void main(String[] args) {
      		System.out.println("main    start");
      		Thread t = new Thread() {
      			public void run() {
      				for(int i=1;i<=100;i++) {
      					try {
      						Thread.sleep(100);
      					} catch (InterruptedException e) {
      						// TODO Auto-generated catch block
      						e.printStackTrace();
      					}
      					System.out.println("t: "+i);
      				}
      			}
      		};
      		
      		t.start();
      		System.out.println("main      end");
      	}
      }
      
    • 加入 t.join() ; 使t线程加入到当前线程中,优先执行t线程,当前线程进入无限期等待状态,等待t线程执行结束后,当前线程才会回到就绪状态等待时间片的分配

      class TestJoin {
      	public static void main(String[] args) throws Exception {
      		System.out.println("main   start ");
      		Thread t1 = new Thread() {
      			public void run() {
      				for(int i=1;i<=1000;i++) {
      					System.out.println("t1: "+i);
      				}
      			}
      		};
      		
      		Thread t2 = new Thread(new Runnable() {
      			public void run() {
      				for(int i = 1;i<=1000;i++) {
      					System.out.println("t2: "+i);
      				}
      			}
      		});
      		
      		t1.start();
      		t2.start();
      		//将t1线程加入到当前主线程中,此时,主线程进入无限期等待状态,等待t1执行结束后,主线程才会继续向下执行
      		//此时,t1与t2依然需要争抢时间片,交替打印
      		t1.join();
      		//将t2线程加入到当前主线程中,此时,主线程进入无限期等待状态,等待t2执行结束后,主线程才会继续向下执行
      		//此时,t1与t2依然需要争抢时间片,交替打印
      		t2.join();
      		
      		System.out.println("main   end ");
      	}
      }
      

    在这里插入图片描述

线程池

程序存在问题: 线程的频繁创建与销毁, 浪费系统资源,影响程序性能

可以通过线程池解决该问题!

概念: 一个存放线程对象的容器,当需要线程对象时,从池中获取,不需要时,不会立马销毁线程对象,而是归还给线程池,以待下次复用

作用: 减少创建与销毁线程对象的次数,提高程序性能,节约系统资源

Executor : 线程池总接口

ExecutorService: 该接口的实现类对象,即线程池对象

  • submit(Runnable task) 发布任务
  • void shutdown() 关闭线程池

Executors: 线程池工具类,定义了一些创建或操作线程池的方法

  • static ExecutorService newFixedThreadPool(int nThreads) 创建一个具有固定数量线程对象的线程池对象
  • static ExecutorService newCachedThreadPool() 创建一个线程对象数量不固定的线程池对象
class TestExecutor {
	public static void main(String[] args) {
		//1. 创建具有固定数量的线程池对象
		//ExecutorService es = Executors.newFixedThreadPool(2);
		ExecutorService es = Executors.newCachedThreadPool();
		
		//创建任务对象
		Runnable r1 = new Runnable() {
			public void run() {
				for(int i=1;i<=100;i++) {
					System.out.println("r1: "+i);
				}
			}
		};
		Runnable r2 = new Runnable() {
			public void run() {
				for(int i=1;i<=100;i++) {
					System.out.println("r2: "+i);
				}
			}
		};
		Runnable r3 = new Runnable() {
			public void run() {
				for(int i=1;i<=100;i++) {
					System.out.println("r3: "+i);
				}
			}
		};
		
		//发布任务
		es.submit(r1);
		es.submit(r2);
		//池中只有两个线程对象,所以后面发布的任务对象,必须等待前面两个任务对象
		//结束一个,才会开始执行
		es.submit(r3);
		//关闭线程池
		es.shutdown();
	}
}

Callable

Callable: JDK5.0出现,类似于Runnable接口,其接口实现类对象,类似于Runnable接口实现类对象,同样都属于线程的任务对象

  • V call() throws Exception ; 类似于run()方法,都是线程对象需要执行的具体逻辑代码;该方法的返回值即当前线程对象执行结束后的最终结果

call()与run()的区别: call方法具有返回值,并且可以上抛异常

Future : 接口, Future对象中存储着当前线程执行结束后的最终结果

  • V get() 返回Future对象中存储的线程执行结果.若通过该方法获取线程最终执行结果时,线程尚未执行结束,没有最终结果,那么当前线程(即调用get方法的线程)会进入无限期等待状态,等待线程执行结束,返回其结果
class TestCallable {
	public static void main(String[] args) throws Exception {
		//创建线程池对象
		ExecutorService es = Executors.newCachedThreadPool();
		//创建任务对象
		Callable<Integer> c1 = new Callable<Integer>() {
			public Integer call() throws Exception {
				int sum = 0;
				for(int i=1;i<=50;i++) {
					sum+=i;
				}
				return sum;
			}
		};
		Callable<Integer> c2 = new Callable<Integer>() {
			public Integer call() throws Exception {
				int sum = 0;
				for(int i=51;i<=100;i++) {
					sum+=i;
				}
				return sum;
			}
		};
		
		
		//发布任务,并且线程的最终执行结果会存储在f1\f2中
		Future<Integer> f1 = es.submit(c1);
		Future<Integer> f2 = es.submit(c2);
		
		//从f1与f2中获取线程的最终执行结果,若此时线程尚未执行结束,当前线程(主线程)会进入无限期等待状态
		//等待线程执行结束后,再获取到执行结果
		Integer i1 = f1.get();
		Integer i2 = f2.get();
		
		System.out.println(i1);
		System.out.println(i2);
		System.out.println(i1+i2);
		//关闭线程池
		es.shutdown();
	}
}

线程安全问题

class TestList {
	public static void main(String[] args) throws Exception {
		MyList list = new MyList();
		
		Thread t1 = new Thread() {
			public void run() {
				list.add("C");
			}
		};
		
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				list.add("D");
			}
		});
		
		t1.start();
		t2.start();
		
		t1.join();
		t2.join();
		
		list.print();
	}
}

//自定义List集合类
class MyList{
	private String[] ss = {"A","B","",""};
	//统计集合内有效元素个数
	private int index = 2;
	
	//添加元素的方法
	public void add(String str) {
		ss[index] = str;
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		index++;
	}
	//遍历集合
	public void print() {
		for(int i=0;i<index;i++) {
			System.out.print(ss[i]+"\t");
		}
		System.out.println("有效元素个数为: "+index);
	}
}

线程安全问题: 当多个线程访问同一对象(临界资源)时,可能破坏其原子性操作,导致数据不一致的问题

临界资源: 被多个线程同时访问的数据(对象)

原子性操作: 多步操作被当做一个整体,不能分割,要么同时执行成功,要么同时执行失败

线程同步

  1. 同步代码块 为原子性操作加锁,使其能够同步执行

    语法: 对o对象加锁的同步代码块,锁住的原子性操作
    synchronized (锁对象 o) {
    	//同步代码		
    }
    注意: Java中的每个对象,都有一个唯一的互斥锁标记,可以用来分配给线程,只有拿到锁标记的线程才能够进入对该对象加锁的同步代码块;若多个线程都具有对该对象加锁的同步代码块,那么多个线程之间除了争抢时间片外,还需要争抢锁标记,只有拿到锁标记的线程才能执行代码,并且在执行结束之前,其他线程会进入阻塞状态,无法执行;只有当拿到锁标记的线程执行结束,就会释放锁标记,其他线程才可以拿到锁标记,然后回到就绪状态,等待分配时间片执行代码
    
    Object o = new Object();
    
    t1线程
        synchronized (o) {
    		//同步代码		
    	}
    t2线程
        synchronized (o) {
    		//同步代码		
    	}    
    注意: 此时t1与t2需要争抢时间片与锁标记,只有同时具有二者,才可以执行代码   
        
    人           线程          t1与t2
    厕所隔间      临界资源       list
    脱裤子\拉屎   原子性操作      add()
    隔间的锁     锁对象\锁标记     o
    
  2. 同步方法

    对当前对象加锁,只有拿到当前对象的锁标记才能执行该方法中的代码
    修饰符 synchronized 返回值类型 方法名(形参列表){
        
    }
    等价于:
    修饰符 返回值类型 方法名(形参列表){
        synchronized (this) {
    		//同步代码		
    	} 
    }
    class TestList {
    	public static void main(String[] args) throws Exception {
    		MyList list = new MyList();
    		//锁对象,具有锁标记
    		Object o = new Object();
    		
    		Thread t1 = new Thread() {
    			public void run() {
    				list.add("C");	
    			}
    		};
    		
    		Thread t2 = new Thread(new Runnable() {
    			public void run() {
    				list.add("D");
    			}
    		});
    		
    		t1.start();
    		t2.start();
    		
    		t1.join();
    		t2.join();
    		
    		list.print();
    	}
    }
    
    //自定义List集合类
    class MyList{
    	private String[] ss = {"A","B","",""};
    	//统计集合内有效元素个数
    	private int index = 2;
    	
    	//添加元素的方法
    	public synchronized void add(String str) {
    		ss[index] = str;
    		try {
    			Thread.sleep(100);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		index++;
    	}
    	//遍历集合
    	public void print() {
    		for(int i=0;i<index;i++) {
    			System.out.print(ss[i]+"\t");
    		}
    		System.out.println("有效元素个数为: "+index);
    	}
    }
    

在这里插入图片描述

死锁

t1
	synchronized(o1){
    	
    	synchronized(o2){
    		
		}
	}
t2
	synchronized(o2){
    	
    	synchronized(o1){
    		
		}
	}

线程通信:

  • o.wait() 使当前线程释放自身所具有的所有锁标记并进入无限期等待状态,并且当前线程也会进入o的等待队列当中,等待被唤醒,然后进入就绪状态等待分配时间片\获取锁标记
  • o.notify() 随机唤醒自身等待队列中的一个线程
  • o.notifyAll() 唤醒自身等待队列中的全部线程
  • 注意: 上述三个方法都必须用在对o加锁的同步代码中

面试题: 请问wait()与sleep()方法的区别?

答:

  1. wait()方法属于Object类,sleep()属于Thread的静态方法
  2. sleep()只会释放时间片,不会释放锁标记,并且不需要被唤醒
  3. wait()会同时释放时间片与锁标记,并且需要被唤醒
class TestDeathLock {
	public static void main(String[] args) {
		Object o1 = new Object();
		Object o2 = new Object();
		
		Thread t1 = new Thread() {
			public void run() {
				synchronized (o1) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					synchronized (o2) {
						System.out.println("A");
						System.out.println("B");
						System.out.println("C");
						System.out.println("D");
						o2.notify();
					}
				}
			}
		};
		Thread t2 = new Thread() {
			public void run() {
				synchronized (o2) {
					try {
						o2.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					synchronized (o1) {
						System.out.println(1);
						System.out.println(2);
						System.out.println(3);
						System.out.println(4);
					}
				}
			}
		};
		
		t1.start();
		t2.start();
	}
}

线程安全的集合类

悲观锁: 总是悲观的认为肯定会出现线程安全问题,所以会直接使用synchronized或Lock加锁,保证线程安全

乐观锁: 总是乐观的认为不会出现线程安全问题,所以不会使用synchronized加锁或Lock加锁,而是等到真正出现了数据不一致问题时,通过算法(CAS)保证线程安全

  • ConcurrentHashMap 保证线程安全,又可以尽可能提高并发效率

    • JDK1.8之前 分段锁(16段) 控制锁的粒度,减少线程之间的冲突,从而提高线程并发效率
    • JDK1.8之后 CAS算法+少量synchronized

    CAS: compare and swap 比较并交换; 存在三个值: V 备份值 E 预期值 B 新值

    只有,当 V == E的时候,才会将 B赋值给内存值

    int i = 1; t1使i自增1 t2使i自增1

    t1 V = 1-----> B = 2 ----> E = 2! = V ----> 再次循环----> V = 2----->B = 3 -----> E = 2 =V-----> i = B = 3;

    t2 V = 1-----> B = 2-----> E = 1 = V -----> i = B=2;

    t3 …i = B = 1;

    存在ABA问题: 版本号机制 A-B-A ------> 1A-2B-3A

  • CopyOnWriteArrayList

    • 写操作加锁并且复制一个副本,在副本上完成写操作,最后用副本代替旧集合
    • 读操作不加锁,可以并发读
    • 特点: 牺牲写操作的效率,大大提高的读操作的效率
    • 应用场景: 应用于读操作远远多于写操作的场景
  • CopyOnWriteArraySet 原理同上!

队列

Queue : 队列,是一种集合接口

特点: FIFO(先进先出)

常用方法:

  • boolean add(E e) 向队列中添加元素,若集合满了,添加失败则报异常
  • boolean offer(E e) 向队列中添加元素,若集合满了,添加失败则返回false
  • E element() 获取队列中的头元素,若队列为空,则获取失败则报异常
  • E peek() 获取队列中的头元素,若队列为空,则获取失败则返回null
  • E remove() 删除队列中的头元素,若队列为空,则删除失败则报异常
  • E poll() 删除队列中的头元素,若队列为空,则删除失败则返回null

实现类:

  • LinkedList
  • ConcurrentLinkedQueue 线程安全的队列,采用CAS算法
class TestQueue {
	public static void main(String[] args) {
		Queue<String> list = new LinkedList<String>();
		
		list.add("A");
		list.offer("B");
		
		System.out.println(list.element());
		list.remove();
		System.out.println(list.peek());
	}
}

子接口

  • BlockingQueue 阻塞队列 当某个线程从该队列中获取元素时,若该队列为空,获取失败,则该线程会进入阻塞状态
    • ArrayBlockingQueue 有界队列 当某个线程向该队列中添加元素时,若该队列满了,添加失败,则该线程会进入阻塞状态
    • LinkedBlockingQueue 无界队列

在这里插入图片描述

chap16-反射

反射

概念: 是一个底层技术,常用与框架底层的设计或开发工具的设计

类对象

类对象: 是类加载的产物,存储了一个类的全部信息(属性\方法\父类\接口等),存储在方法区中

注意: 一般的类加载只会进行一次,所以类对象也只存在一个!!

类的对象:根据类所创建出来的一个实例对象

动物园: 老虎Tiger

​ 记录老虎全部信息的牌子--------类对象 Tiger.class Class类

​ 笼子里面的活生生的老虎--------类的对象 new Tiger(); Tiger类

获取类对象的方式:

  1. 类名.class 也可以获取基本数据类型的类对象: 基本数据类型.class 例如: int.class

    Class class1 = ArrayList.class;
    Class c2 = int.class;
    
  2. 引用.getClass();

    ArrayList<String> list = new ArrayList<String>();
    Class c1 = list.getClass();
    
  3. Class.forName(“类的全限定名”);

    Class c2 = Class.forName("java.util.ArrayList");
    Class c3 = Class.forName("day23.Student");
    

常用方法:

  • getName() 获取类的全限定名–包名.类名

  • getPackage 获取Package对象—包对象

  • getSuperclass() 获取父类类对象

  • Class[] getInterfaces() 获取全部接口类对象

    class TestReflect {
    	public static void main(String[] args) throws Exception {
    		ArrayList<String> list = new ArrayList<String>();
    		Class c1 = list.getClass();
    		System.out.println(c1);
    		
    		System.out.println(c1.getName());
    		System.out.println(c1.getPackage());
    		System.out.println(c1.getSuperclass());
    		Class[] cs = c1.getInterfaces();
    		for (Class class1 : cs) {
    			System.out.println(class1);
    		}
    	} 
    }
    

基于类对象获取Field对象(即属性对象)

  • Field getField(String name) 获取本类或父类中的某个公开属性,参数代表属性名
  • Field getDeclaredField(String name) 获取本类的某个属性,参数代表属性名
  • Field[] getFields() 获取本类及父类中的全部公开属性
  • Field[] getDeclaredFields() 获取本类的全部属性

基于类对象获取Method对象(即方法对象)

  • Method getMethod(String name, 类对象…) 获取本类或父类中的某个公开方法
    • 参数1: 代表方法名
    • 参数2: 代表该方法的形参列表属性所对应的属性的类对象列表
    • 例如:
      • public void println(int a, String s, double d);
      • getMethod(“println”,int.class,String.class,double.class)
  • Method getDeclaredMethod(String name, 类对象…) 获取本类的某个方法
    • 参数含义同上
  • Method[] getMethods() 获取本类及父类中的全部公开方法
  • Method[] getDeclaredMethods() 获取本类的全部方法

基于类对象获取Constructor对象(即构造方法对象)

  • Constructor getConstructor(类<?>… parameterTypes) 获取本类的某个公开构造方法
    • 参数: 代表构造方法的形参列表的数据类型对应的类对象列表
    • 例如:
      • public Student(String name,int age){}
      • getConstructor(String.class,int.class);
  • Constructor getDeclaredConstructor(类<?>… parameterTypes) 获取本类的某个构造方法
    • 参数同上
  • Constructor<?>[] getConstructors() 获取本类的全部公开构造方法
  • Constructor<?>[] getDeclaredtConstructors() 获取本类的全部构造方法

使用反射手段,获取或操作对象的信息

class TestReflect {
	public static void main(String[] args) throws Exception {
		Class c1 = Student.class;
		
		//newInstance() 可以根据类对象可以创建实例对象,默认采用无参构造方法
		Student s1 = (Student)c1.newInstance(); //考试重点
		System.out.println(s1);
		//获取Student类的有参构造方法
		Constructor con1 = c1.getConstructor(String.class,int.class);
		Student s2 = (Student)con1.newInstance("小郭",18);
		System.out.println(s2);
		
		//获取私有属性对象
		Field fname = c1.getDeclaredField("name");
		//setAccessible() 设置临时访问权限, true表示可以访问, false表示不可以
		fname.setAccessible(true);
		//set()  参数1表示想要赋值的对象,参数2表示给属性赋的值
		fname.set(s1,"小红");
		System.out.println(s1);
		
		//获取公开的study方法
		Method m1 = c1.getMethod("study");
		//invoke() 可以执行方法, 参数1表示执行哪个对象中的方法,参数2表示执行方法时传入的实参
		m1.invoke(s1); //考试重点
		m1.invoke(s2);
		
		//获取私有的study方法
		Method m2 = c1.getDeclaredMethod("study", String.class);
		m2.setAccessible(true);
		m2.invoke(s1, "Java");
		m2.invoke(s2, "Java");
	} 
}
class Student{
	private String name;
	private int age;
	public Student() {
		super();
		// TODO Auto-generated constructor stub
	}
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	
	public void study() {
		System.out.println(name+"沉迷学习");
	}
	private void study(String keCheng) {
		System.out.println(name+"沉迷学习"+keCheng);
	}
}

反射的优缺点:

优点:

  • 打破封装
  • 代码更加灵活
  • 可以用于框架底层及工具的开发

缺点:

  • 可读性差
  • 操作繁琐
  • 打破封装

设计模式

概念: 是一种被反复使用,被众多程序员认可的代码套路

23种 GOF(Gang of Four 四人组)

单例模式

概念: 创建一个类,该类只能创建一个实例对象

  1. 饿汉式 可能浪费空间

    //饿汉式
    class ClassA{
    	//预先创建好一个ClassA对象,以供外界获取
    	private static ClassA ca = new ClassA();
    	//外界获取该类对象的方法
    	public static ClassA newInstance() {
    		return ca;
    	}
    	//将构造方法私有化  禁止外界通过构造方法随意创建对象
    	private ClassA() {}
    }
    
  2. 懒汉式 不会浪费空间,但是并发效率低

    //懒汉式
    class ClassB{
    	private static ClassB cb = null;
    	//为了避免出现线程不安全问题,需要给该方法加锁
    	public static synchronized ClassB newInstance() {
    		if(cb==null) cb = new ClassB();
    		return cb;
    	}
    	private ClassB() {}
    }
    
  3. 双重校验锁 不浪费空间,在保证线程安全的前提下,尽量提高了并发效率

    //双重校验锁
    class ClassC {
    	private static ClassC cc = null;
    	//1.  cc=null  cc = new ClassC();   存在锁
    	//2.  cc!=null return cc;           不存在锁
    	public static ClassC newInstance() {
    		if(cc==null) {
                
    			synchronized(ClassC.class) {
    				if(cc==null)cc = new ClassC();
    			}
                
    		}
    		return cc;
    	}
    	private ClassC() {}
    }
    
  4. 静态内部类实现 懒加载

    //静态内部类实现
    class ClassD{
    	private static class Holder{
    		static ClassD cd = new ClassD();
    	}
    	public static ClassD newInstance() {
    		return Holder.cd;
    	}
    	private ClassD() {}
    }
    

工厂模式

概念: 将对象的创建,交给工厂方法(工厂对象)

开闭原则: 修改关闭,扩展开放

class TestFactory {
	public static void main(String[] args) {
		Animal a = createAnimal();
		a.eat();
	}
	public static Animal createAnimal() {
		Animal a = null;
		try (FileReader fr = new FileReader("animal.properties")) {
			//Properties集合类是专门用来读取配置文件的
			Properties pro = new Properties();
			//load()可以将流中的数据加载到集合中
			pro.load(fr);
			//getProperty(String key) 根据键获取值
			String className = pro.getProperty("className");
			
			Class c = Class.forName(className);
			a = (Animal)c.newInstance();
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		return a;
	}
	
}
class Animal{
	public void eat() {
		System.out.println("吃饭饭");
	}
}
class Dog extends Animal{
	public void eat() {
		System.out.println("狗吃骨头");
	}
}
class Cat extends Animal{
	public void eat() {
		System.out.println("猫吃鱼");
	}
}
class Wolf extends Animal{
	public void eat() {
		System.out.println("狼吃喜洋洋");
	}
}
class Tiger extends Animal{
	public void eat() {
		System.out.println("老虎吃人");
	}
}

ss TestQueue {
public static void main(String[] args) {
Queue list = new LinkedList();

	list.add("A");
	list.offer("B");
	
	System.out.println(list.element());
	list.remove();
	System.out.println(list.peek());
}

}


子接口    

* BlockingQueue   阻塞队列   当某个线程从该队列中获取元素时,若该队列为空,获取失败,则该线程会进入阻塞状态
  * ArrayBlockingQueue       有界队列     当某个线程向该队列中添加元素时,若该队列满了,添加失败,则该线程会进入阻塞状态
  * LinkedBlockingQueue     无界队列

[外链图片转存中...(img-NUgGryi2-1628609627999)]

# chap16-反射

## 反射

概念: 是一个底层技术,常用与框架底层的设计或开发工具的设计

### 类对象

类对象: 是类加载的产物,存储了一个类的全部信息(属性\方法\父类\接口等),存储在方法区中

注意: 一般的类加载只会进行一次,所以类对象也只存在一个!!

类的对象:根据类所创建出来的一个实例对象

>动物园: 老虎Tiger
>
>​	记录老虎全部信息的牌子--------类对象          Tiger.class               Class类
>
>​	笼子里面的活生生的老虎--------类的对象      new  Tiger();            Tiger类

获取类对象的方式:

1. 类名.class       也可以获取基本数据类型的类对象: 基本数据类型.class     例如:  int.class

   ~~~java
   Class class1 = ArrayList.class;
   Class c2 = int.class;
  1. 引用.getClass();

    ArrayList<String> list = new ArrayList<String>();
    Class c1 = list.getClass();
    
  2. Class.forName(“类的全限定名”);

    Class c2 = Class.forName("java.util.ArrayList");
    Class c3 = Class.forName("day23.Student");
    

常用方法:

  • getName() 获取类的全限定名–包名.类名

  • getPackage 获取Package对象—包对象

  • getSuperclass() 获取父类类对象

  • Class[] getInterfaces() 获取全部接口类对象

    class TestReflect {
    	public static void main(String[] args) throws Exception {
    		ArrayList<String> list = new ArrayList<String>();
    		Class c1 = list.getClass();
    		System.out.println(c1);
    		
    		System.out.println(c1.getName());
    		System.out.println(c1.getPackage());
    		System.out.println(c1.getSuperclass());
    		Class[] cs = c1.getInterfaces();
    		for (Class class1 : cs) {
    			System.out.println(class1);
    		}
    	} 
    }
    

基于类对象获取Field对象(即属性对象)

  • Field getField(String name) 获取本类或父类中的某个公开属性,参数代表属性名
  • Field getDeclaredField(String name) 获取本类的某个属性,参数代表属性名
  • Field[] getFields() 获取本类及父类中的全部公开属性
  • Field[] getDeclaredFields() 获取本类的全部属性

基于类对象获取Method对象(即方法对象)

  • Method getMethod(String name, 类对象…) 获取本类或父类中的某个公开方法
    • 参数1: 代表方法名
    • 参数2: 代表该方法的形参列表属性所对应的属性的类对象列表
    • 例如:
      • public void println(int a, String s, double d);
      • getMethod(“println”,int.class,String.class,double.class)
  • Method getDeclaredMethod(String name, 类对象…) 获取本类的某个方法
    • 参数含义同上
  • Method[] getMethods() 获取本类及父类中的全部公开方法
  • Method[] getDeclaredMethods() 获取本类的全部方法

基于类对象获取Constructor对象(即构造方法对象)

  • Constructor getConstructor(类<?>… parameterTypes) 获取本类的某个公开构造方法
    • 参数: 代表构造方法的形参列表的数据类型对应的类对象列表
    • 例如:
      • public Student(String name,int age){}
      • getConstructor(String.class,int.class);
  • Constructor getDeclaredConstructor(类<?>… parameterTypes) 获取本类的某个构造方法
    • 参数同上
  • Constructor<?>[] getConstructors() 获取本类的全部公开构造方法
  • Constructor<?>[] getDeclaredtConstructors() 获取本类的全部构造方法

使用反射手段,获取或操作对象的信息

class TestReflect {
	public static void main(String[] args) throws Exception {
		Class c1 = Student.class;
		
		//newInstance() 可以根据类对象可以创建实例对象,默认采用无参构造方法
		Student s1 = (Student)c1.newInstance(); //考试重点
		System.out.println(s1);
		//获取Student类的有参构造方法
		Constructor con1 = c1.getConstructor(String.class,int.class);
		Student s2 = (Student)con1.newInstance("小郭",18);
		System.out.println(s2);
		
		//获取私有属性对象
		Field fname = c1.getDeclaredField("name");
		//setAccessible() 设置临时访问权限, true表示可以访问, false表示不可以
		fname.setAccessible(true);
		//set()  参数1表示想要赋值的对象,参数2表示给属性赋的值
		fname.set(s1,"小红");
		System.out.println(s1);
		
		//获取公开的study方法
		Method m1 = c1.getMethod("study");
		//invoke() 可以执行方法, 参数1表示执行哪个对象中的方法,参数2表示执行方法时传入的实参
		m1.invoke(s1); //考试重点
		m1.invoke(s2);
		
		//获取私有的study方法
		Method m2 = c1.getDeclaredMethod("study", String.class);
		m2.setAccessible(true);
		m2.invoke(s1, "Java");
		m2.invoke(s2, "Java");
	} 
}
class Student{
	private String name;
	private int age;
	public Student() {
		super();
		// TODO Auto-generated constructor stub
	}
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	
	public void study() {
		System.out.println(name+"沉迷学习");
	}
	private void study(String keCheng) {
		System.out.println(name+"沉迷学习"+keCheng);
	}
}

反射的优缺点:

优点:

  • 打破封装
  • 代码更加灵活
  • 可以用于框架底层及工具的开发

缺点:

  • 可读性差
  • 操作繁琐
  • 打破封装

设计模式

概念: 是一种被反复使用,被众多程序员认可的代码套路

23种 GOF(Gang of Four 四人组)

单例模式

概念: 创建一个类,该类只能创建一个实例对象

  1. 饿汉式 可能浪费空间

    //饿汉式
    class ClassA{
    	//预先创建好一个ClassA对象,以供外界获取
    	private static ClassA ca = new ClassA();
    	//外界获取该类对象的方法
    	public static ClassA newInstance() {
    		return ca;
    	}
    	//将构造方法私有化  禁止外界通过构造方法随意创建对象
    	private ClassA() {}
    }
    
  2. 懒汉式 不会浪费空间,但是并发效率低

    //懒汉式
    class ClassB{
    	private static ClassB cb = null;
    	//为了避免出现线程不安全问题,需要给该方法加锁
    	public static synchronized ClassB newInstance() {
    		if(cb==null) cb = new ClassB();
    		return cb;
    	}
    	private ClassB() {}
    }
    
  3. 双重校验锁 不浪费空间,在保证线程安全的前提下,尽量提高了并发效率

    //双重校验锁
    class ClassC {
    	private static ClassC cc = null;
    	//1.  cc=null  cc = new ClassC();   存在锁
    	//2.  cc!=null return cc;           不存在锁
    	public static ClassC newInstance() {
    		if(cc==null) {
                
    			synchronized(ClassC.class) {
    				if(cc==null)cc = new ClassC();
    			}
                
    		}
    		return cc;
    	}
    	private ClassC() {}
    }
    
  4. 静态内部类实现 懒加载

    //静态内部类实现
    class ClassD{
    	private static class Holder{
    		static ClassD cd = new ClassD();
    	}
    	public static ClassD newInstance() {
    		return Holder.cd;
    	}
    	private ClassD() {}
    }
    

工厂模式

概念: 将对象的创建,交给工厂方法(工厂对象)

开闭原则: 修改关闭,扩展开放

class TestFactory {
	public static void main(String[] args) {
		Animal a = createAnimal();
		a.eat();
	}
	public static Animal createAnimal() {
		Animal a = null;
		try (FileReader fr = new FileReader("animal.properties")) {
			//Properties集合类是专门用来读取配置文件的
			Properties pro = new Properties();
			//load()可以将流中的数据加载到集合中
			pro.load(fr);
			//getProperty(String key) 根据键获取值
			String className = pro.getProperty("className");
			
			Class c = Class.forName(className);
			a = (Animal)c.newInstance();
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		return a;
	}
	
}
class Animal{
	public void eat() {
		System.out.println("吃饭饭");
	}
}
class Dog extends Animal{
	public void eat() {
		System.out.println("狗吃骨头");
	}
}
class Cat extends Animal{
	public void eat() {
		System.out.println("猫吃鱼");
	}
}
class Wolf extends Animal{
	public void eat() {
		System.out.println("狼吃喜洋洋");
	}
}
class Tiger extends Animal{
	public void eat() {
		System.out.println("老虎吃人");
	}
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值