JAVA学习

基本概念

  • 一个源文件只能有一个public类,其他类的个数不限。编译后,每一个类都对应生成一个 .class 文件
  • 源文件名与public类同名
  • javadoc注释
  • 文档注释用终端生成html文件命令
javadoc -d 文件夹地址 javadoc标签 XXX.java
  • Java程序中 "+" 号的使用
    • 当左右两边都是数值型时,做加法运算
    • 当左右两边有一方为字符串,则做拼接运算
System.out.println(100+98);		//198
System.out.println("100"+98);		//10098
System.out.println(100+"Hello");		//100Hello
System.out.println("Hello"+100+98);		//Hello10098

数据类型

引用类型

  • 接口
  • 数组

浮点数

  • Java默认浮点数是double类型,float类型要在数值后加 f/F
  • Java浮点类型保留小数点后位数
    • float - 保留小数点后7位 - C语言中保留6位
    • double - 保留小数点后16位 - C语言中保留15位
		double a = 0.92381930109121314334452222222;	
		System.out.println(a);	//0.9238193010912131
		float b = 0.213123123123f;	
		System.out.println(b);	//0.21312313
        //一些小细节
        double a = 2.7;
		double b = 8.1 / 3;	//实际结果为2.7
		System.out.println(a);	//2.7
		System.out.println(b);	//2.6999999999999997
		if(a == b){
			System.out.println("相等");	
		}else{
			System.out.println("不相等");		//输出不相等
		}
		//由此我们可以得到一个重要结论
		//当对两个运算结果为小数的进行判断比较是,应当小心
		//应该用两个小数的差值的绝对值小于某个很小的范围时,认为两个运算结果相等
		//应按照如下写法
		if(Math.abs(a - b) < 0.0001){
			System.out.println("相等");		//输出相等
		}
  • 字符和编码时一一对应的
  • Java中不可以用0非0来替代false和true,这点与C语言不同。
    • Boolean只能是true或者false 不能是0或者1

基本数据类型转换

自动类型转换
  • 低精度向高精度自动转换
  • byte==>short==>int==>long==>float==>double
  • char==>int==>long==>float==>double
//细节
int n1 = 10;
float d1 = n1 + 1.1;		//错误,右边其实结果是double类型
float d1 = n1 + 1.1F;		//正确写法
System.out.println(d1);

int n2 = 1.0; //错误	右边是double 高精度不能自动转换为低精度

//byte和short、char之间不发生自动类型转换
//byte和int自动类型转换的一些小细节
byte b1 = 10;			//可以,具体数复制给byte时,先判断数值范围,再判断类型	10在byte范围内:-128-127
int a = 1;
b1 = a;	//变量a在内存中占四个字节,是不能把他赋给byte,因为byte只有一个字节的空间
char c1 = 'a';
b1 = c1;	//错误,byte和char之间不能自动类型转换

//byte、short、char之间可以计算,在计算时首先转换为int类型

byte b2 = 1;
byte b3 = 1;
short s1 = 1;
short s2 = s1 + b2; // 错误,b2 + s1 在计算时转换为了int型
int b = s1 + b2; //正确
byte b4 = b2 + b3; //错误,b2 + b3 ==> int
//boolean 不参与类型的自动转换
//自动提升原则:表达式结果的类型自动提升为操作数中最大的类型
byte b5 = 1;
short s4 = 100;
int c = 1;
double num = 1.1;
float f1 = 1.1f;
double num1 = b5 + s4 + c + num; //必须用double类型的变量接收右边的运算结果,因为右边运算结果的类型以及提升到double类型
double num2 = b5 + s4 + c + f1; //这样可以,右边运算结果为float,满足自动转换原则
强制类型转换
  • 高精度类型向低精度类型转换,由编写代码的人使用强制转换符号**()**进行类型转换,同时转换过程中会造成精度损失
  1. C语言中直接通过赋值号就能进行类型转换,但是也会造成精度损失,编译可以通过,但是编译器会提示警告。C语言也可以用强制类型转换符 ( 进行有精度损失的强制类型转换)
  2. Java中高精度类型向低精度类型转换必须使用强制转换符号(),会造成精度损失,否则编译无法通过,直接提示错误。
  3. 强制类型转换符只对最近的操作数有效,这点与C语言相同,C语言中是因为**()**运算符优先级高。
int a = (int)10+ 2.1*2;	//错误,右边其实还是double型
//正确写法
int a = (int)(10+ 2.1*2);
  1. char类型可以保存 int 类型的常量值,但是不能保存 int 类型的变量值(需要强制转换)。这点与C语言不同
  2. C语言中** 只要被char保存的值在其范围内,就可以保存,无论变量值还是常量值**;这点原因还是因为C语言中直接通过赋值号就能进行类型转换
  3. byte进行运算时,当作int类型处理,运算结果用int接收
  4. Short进行运算时,当作int类型处理,运算结果用int接收
  5. char进行运算时,当作int类型处理,运算结果用int接收

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-881UZUUn-1652873405290)(https://cdn.jsdelivr.net/gh/MCXXHYK/PicGo/202205121717674.png#crop=0&crop=0&crop=1&crop=1&id=HDf9U&originHeight=896&originWidth=1356&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

基本数据类型和字符串的转换

1.** 基本数据类型转字符串**:加 “”

int n1 = 100;
float f1 = 4.5F;
double d1 = 1.2;
boolean b1 = true;
String s1 = n1 + "";
String s2 = f1 + "";
String s3= d1 + "";
String s4 = b1 + "";
System.out.println(s1 + " " + s2 + " " + s3 + " " + s4);	//100 4.5 1.2 true    字符串
  1. **字符串转基本数据类型 **

    • 语法:通过基本数据类型的包装类调用parseXXX方法即可
int a =Integer.parseInt("123");
double b = Double.parseDouble("123");
float f = Float.parseFloat("123");
short s = Short.parseShort("12");
long l = Long.parseLong("2131");
boolean b = Boolean.parseBoolean("true");
byte by = Byte.parseByte("11");
//字符串==>取出字符
String s1 = "123";
char c1 = s1.charAt(0); //1
char c2 = s1.charAt(1); //2
char c3 = s1.charAt(2); //3
关系运算符 instanceof:检查是否是类的对象,返回值为true和false
boolean judge = "hh" instanceof String;	//true
短路与 && 、逻辑与 & 区别
  • 短路与有一个条件为假,后面条件直接不用判断,且后面代码不再执行,结果返回假;
  • 逻辑与必须要把所有条件都判断完,才会返回结果 效率低
短路或 ||、逻辑或 | 区别
  • 短路或有一个条件为真,后面条件直接不用判断,且后面代码不再执行,结果返回真;
  • 逻辑或必须要把所有条件都判断完,才会返回结果 效率低

tips:**复合赋值运算符再进行计算时会进行类型转换 **

byte b1 = 123;
b1 += 1;	//等价于 b1 = (byte)(b1 + 1); 
//分析:按理说 b1 += 1;右边是 b1 +1 ==>int 赋值给左边byte会发生错误,
																			//但是这里其实复合赋值运算符进行运算是会自动进行类型转换的
//b1++也是如此
//b1++等价于 b1 = (byte)(b1+1);	也会发生类型转换

//细节
int i = 1;
i = i++;		//1) temp = i ; 2) i = i +1; 3) i = temp;  执行顺序是这样的
System.out.println(i); //1
int j = 0;
j = i++;		//1) temp = i ; 2) i = i +1; 3) j = temp;  执行顺序是这样的	j=1

int a = 1;
a = ++a;
System.out.println(a); //2	执行顺序 1) a = a +1; 2) temp = a ; 3) a = temp;

键盘输入

import java.util.Scanner;	//导包

public class UserScanner {

	public static void main(String[] args){
		//1.创建对象
		Scanner sc = new Scanner(System.in);
    //2.调用方法
		String s1 = sc.next();		//字符串
		int n1 = sc.nextInt();		//整型值
		double d1 = sc.nextDouble();//浮点型输入
    char c1 = sc.next().charAt(0);	// 字符型输入
		System.out.println("==================");
		System.out.println(s1);
		System.out.println(n1);
		System.out.println(d1);
    System.out.println(c1);
		// System.out.println(s1);
	}
}

金字塔

/*

    *      	1层		4空格		1star 
   ***	   	2层		3空格		3star 
  *****	   	3层		2空格		5star 
 *******   	4层		1空格		7star 
*********  	5层		0空格		9star 
*/
/*

    *      	1层		4空格		1star 
   * *	   	2层		3空格		2star 
  *   *	   	3层		2空格		2star 
 *     *   	4层		1空格		2star 
*********  	5层		0空格		9star 
*/

public class Pyramid {

	void disPlayPyramid () {
		int totol = 5;
		//i控制层数
		for (int i = 1 ; i <= totol ; i++) {
			//每次打印star之前先打印空格
			for (int k = 1; k <= totol-i; k++){
				System.out.print(" ");
			}
			//j控制每一层打印的star个数
			for (int j = 1;j <= 2*i-1;j++){
				if(j == 1 || j == 2*i-1 || i == totol){
					System.out.print("*");						
				}else{
					System.out.print(" ");
				}
			}

			System.out.println("");
		}
	}
	public static void main(String[] args) {
		Pyramid py = new Pyramid();
		py.disPlayPyramid();
	}
}

原码、反码、补码

规则:

  • 最高位为符号位,0 ==> 正数 ,1 ==> 负数
  • 正数 ==> 原码 = 反码 =补码
  • 0的原码 = 反码 =补码 = 0
  • 负数的原码 符号位不变,其余按位取反 -----> 反码 加一----> 补码 减一------> 反码 取反 ------>得原码的绝对值
  • 补码转原码时,若其符号为 1 时,说明其是负数,所转的原码是该该负数的绝对值
  • 计算机运算(存储)时都是以补码形式运算的
  • 看运算结果时,要看的是其原码(运算结果是补码时,要转成原码才能打印出来)

位运算

  • **按位与&、按位或|、按位异或 ^、按位取反 ~ **
    • 按位与&:两个数全为1,结果为1 ;否则取0 ;
    • 按位或|:两个数全为0,结果为0 ;否则为1;
    • 按位异或 ^:两个数不同时为1(1 0);相同时为0;(0 0 、1 1);
    • 按位取反 ~:0 ==> 1 ; 1 ==> 0 ;

//2   0010    3		0011      2 & 3 = 0010     ==> 2
//-2  原码:10000000 00000000 00000000 00000010  
//		补码:11111111 11111111 11111111 11111110  
//		~-2	 00000000 00000000 00000000 00000001	这是补码,运算结果要转成原码
//   运算结果的原码:00000000 00000000 00000000 00000001
//		~-2 ==> 1
  • **算数右移 >> 、算数左移 << 、逻辑右移 >>> **
    • 算数右移 >>: 低位溢出,符号位不变,并用符号位补溢出的高位 a >> b ===>本质:a / 2/ 2/……/2 除以b个2
    • 算数左移 <<:符号位不变,低位补0 a << b ====>本质:a _ 2 _ 2 _ ……_2 乘以b个2
    • 逻辑右移 >>>:低位溢出,高位补0 ;逻辑右移 >>>也叫无符号右移

随机数生成

Double d1 = Math.random();	//  返回值时介于(0.00,1.00)的Double值
//随机生成1-100之间的随机整数
int n1 = (int)(Math.random()*100)+1	//(int)(Math.random()*100) 0-99

循环控制

break

  • break出现在多层循环中,可以通过指定循环标签,来终止哪一层循环,但是实际开发中尽量避免使用标签;break label1;
    • 如果break在多层循环中,没有指定标签,则break终止的是离他最近的一层循环
  • 字符串比较 String1.equal(String2);
    • 把String1写成被计较的值,可以避免控制异常
	//实现登录验证,有三次机会,如果用户名为”胡永康“,密码为”123456“,则提示登录成功;
	//否则提示登录失败,并显示还有几次机会
	void loginValidate(){
		int n = 3;
		Scanner sc = new Scanner(System.in);
		for( int i = 1; i <=n; i++){
			System.out.print("请输入用户名:");
			String uname = sc.next();
			System.out.println("");
			System.out.print("请输入密码:");
			String password = sc.next();
			if("胡永康".equals(uname) && "123456".equals(password)){	
				System.out.println("登录成功....");
				break;
			}else{	
				System.out.println("登录失败,还剩"+(n-i)+"次机会!");
			}
		}
	}

continue

  • continue出现在多层循环中,可以通过指定循环标签,来直接进行哪一层的下一次循环,但是实际开发中尽量避免使用标签;continue label1;
  • 如果continue在多层循环中,没有指定标签,则continue直接进行的是离他最近的一层循环的下一次循环

return

  • return 可以返回方法的返回值,当在方法中直接使用 **return;**时,则该方法直接结束,后面不再执行;
  • return 用在主方法main上时,表示程序直接终止

数组

数组长度(大小):arr.length

数组两种写法

//1.动态初始化
int[] a = new int[5];	//数组动态初始化
int a[] = new int[5];	//两种写法均可
//也可以先声明再初始化
int[] a;				//  先只声明,而不分配空间
a = new int[5];	//此时分配空间
//2.静态初始化
int a[] = {2,5,6,2,5,7,9};

数组使用注意事项

  • 数组创建后没有赋值,有默认值
int \ byte \ short \ long ==> 0				float ==> 0.0			double ==> 0.0
char ==> /u0000		string ==> null boolean ==> false
  • 数组下标必须在指定范围内使用
  • 数组属于引用类型,数组型数据是对象

数组扩容(动态扩容)

基本思路
  • 创建新数组,
  • 使新数组的长度等于原来的数组加上要扩容的长度,
  • 把原来的数组内容拷贝过来,
  • 把新内容填入新的数组,
  • 并让原来的数组名指向新的数组。
//扩容arr数组
int[] arr = {1,2,3};
//1.创建新的数组
int[] arrNew = new int[arr.length + 1]; //给新的数组创建原来的数组长度加一的空间
//2.遍历将原来的数组内容依次拷贝过来
for( int i = 0; i < arr.length; i++){
  arrNew[i] = arr[i];
}
//3.把新内容填入新的数组
arrNew[arr.length] = 4;
//4.让原来的数组名指向新的数组
arr = arrNew; //arrNew[]自动被销毁(垃圾回收机制)
优化版本
import java.util.Scanner;
public class ArrayAdd {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		//原来的数组
		int[] arr = {1,2,3};
		do {
			//1.创建新数组
			int arrNew[] = new int[arr.length+1];
			//2.拷贝原来数组内容到新数组
			for( int i = 0; i < arr.length; i++) {
				arrNew[i] = arr[i];
			}
			//3.询问用户是否添加新内容到数组
			System.out.println("是否添加新内容到数组?(Y/N)");
			char input = sc.next().charAt(0);
			if( input == 'N') {
				break;	
			}else if ( input == 'Y') {
				System.out.println("请输入你想添加的数值:");
				//3.获取要添加的数值
				Integer addNum = sc.nextInt();
				arrNew[arr.length] = addNum;
				//4.将原来的的数组名指向新的数组
				arr = arrNew;
				System.out.println("添加成功!===========");
				//5.打印数组中当前数据
				for ( int i = 0; i < arr.length; i++ ) {
				System.out.print("arr["+i+"]==>"+arr[i]+"\t");
				}
			}else{
				System.out.println("添加失败!");
				break; 
			}
		}while(true);
	}
}

数组缩减

import java.util.Scanner;
public class ArrayReduce {
	public static void main(String[] args) {
		//1.定义一个数组
		int[] arr = {1,2,3};

		//2.创建输入环境
		Scanner sc = new Scanner(System.in);
		//3.进入数组缩减
		do {
			//4.定义一个新的数组
			int[] arrNew = new int[arr.length - 1];
			System.out.println("是否要缩减数组?(y/n)");
			char input = sc.next().charAt(0);
			if( input == 'n'){
				break;
			}else if ( input == 'y' && arr.length > 1) {
				//5.拷贝前length-1个元素到新数组
				for ( int i = 0; i < arrNew.length; i++) {
					arrNew[i] = arr[i];
				}
				//6.原来数组指向新的数组
				arr = arrNew;
				//7.打印缩减后的数组
				System.out.println("缩减后的数组==================");
				for ( int j = 0; j < arr.length ; j++) {
					System.out.print(arr[j]+"\t");
				}
			}else if ( arr.length <= 1 ){
				System.out.println("不能再缩减了!");
				break;
			}
		}while(true);
	}
}

排序

1.冒泡排序

算法思想:
//将其:57,12,63,7,4,9,12,52使用冒泡排序按照从小到大的顺序排序
 public class BubbleSort {
 	public static void main(String[] args){
 		//1.定义一个数组存放数据,静态初始化数组
 		int[] arr = {57,12,63,7,4,9,12,52};
 		//2.冒泡排序	arr.length -1 趟排序 每趟排序确定一个最大值 i ==> 趟数 ==> 已排好序的数个数
 		for (int i = 0 ; i < arr.length - 1 ; i++ ) {
 			int flag = 1;
 			if( arr.length == 0) {
 				System.out.println("排序失败,数组长度为0!");
 				break;
 			}
 			//3.一趟冒泡排序 只给前面未被排序的元素排序 后面 i 个元素已经排好序 <==> int j = 0; j < arr.length - 1 - i
 			for( int j = 0; j < arr.length - 1 - i; j++) {
 				//比较相邻元素大小,若不是按照递增顺序,则发生交换
 				if( arr[j] > arr[j + 1]){
 					int temp = arr[j];
 					arr[j] = arr[j + 1];
 					arr[j + 1] = temp;
 					flag = -1;
 				}
 			}
 			//如果一趟排序之后flag没变化,说明数组已经有序,无需在进行下次排序,直接跳出循环
	 		if(flag == 1){
	 			break;
	 		}else{
	 			System.out.println("\n第"+(i+1)+"趟排序后的数组===========");
	 			for ( int k = 0; k < arr.length; k++ ) {
	 			System.out.print(arr[k]+"\t");
	 			}
 			}
 		}
 		//4.打印排序后的数组
 		System.out.println("\n全部排序完成后的数组===========");
 		for ( int k = 0; k < arr.length; k++ ) {
 			System.out.print(arr[k]+"\t");
 		}
 	}
}
总结:
  • 一共发生arr.length - 1 趟排序,
  • 每趟排序确定一个最大值,
  • 通过相邻元素比较进行交换,
  • 每趟排序发生 arr.length - 1 - i 次比较

二维数组

基本介绍

  • 每个元素是一个一维数组的数组
  • 如果要放二维数组的第 i 个元素中第 j 个值,应该这样访问: arr[i-1][j-1]; 因为数组下标都是从0开始的
public static void main(String[] args){
  int[][] arr = {{1, 2, 3, 4, 5}, {0, 0, 1, 2, 3}, {1, 2, 5, 3, 7}, {4, 5, 6, 2, 1}, {0, 0, 0, 0, 0}};
  for( int i = 0; i< arr.length; i++){
    for( int j = 0; j < arr[i].length; j++){
      System.out.print(arr[i][j]+" ");
    }
    System.out.println("");
  }
}

二维数组的初始化

动态初始化01
public static void main(String[] args){
  // int[][] arr = new int[i][j];		i 代表这个数组初始化为含有 i 个一维数组的数组,j代表每个一维数组含有j个元素
  int[][] arr = new int[5][4]; //5 代表这个数组初始化为含有5个一维数组的数组,4代表每个一维数组含有4个元素
}
动态初始化02(列数不确定)
/*
	用二维数组打印以下:
	1
	2 2
	3 3 3
*/
public static void main(String[] args){
  // int[][] arr = new int[i][];		i 代表这个数组初始化为含有 i 个一维数组的数组,但是每个一维数组还没有开辟空间
  int[][] arr = new int[3][]; //3 代表这个数组初始化为含有5个一维数组的数组,但是每个一维数组还没有开辟空间
 	for( int i = 0; i < arr.length; i++) {
    //给二维数组中的每一个一维数组开辟内存空间;如果不给每个一维数组分配空间,那么每个一维数组都为 null,即arr[i] == null
    arr[i] = new int[i + 1];
    //给一维数组遍历赋值
    for( int j = 0; j < arr[i].length; j++) {
      arr[i][j] = i+1;
    }
  } 
  System.out.println("====arr元素=====");
  for( int i = 0; i < arr.length; i++) {
    for( int j = 0; j < arr[i].length; j++) {
      System.out.print(arr[i][j]+" ");
    }
    System.out.println("");
  }
}

杨辉三角形

/*
	使用一个二维数组打印杨辉三角形
	1
	1 1
	1 2  1
	1 3  3  1
	1 4  6  4  1
	1 5 10 10  5 1
	每行首个元素和最后一个元素都是1
	从第三行开始,除了第一个元素和最后一个元素,每个元素都是上一行同一列元素加上上一行前一列的和
	即 arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1]
*/
public class YangHui {
	public static void main(String[] args) {
		//动态创建一个二维数组
		int[][] arr = new int[10][];
		//遍历二维数组
		for( int i = 0; i < arr.length; i++) {
			//给每一个一维数组开辟内存空间
			arr[i] = new int[i + 1];
			//遍历一维数组
			for( int j = 0; j < arr[i].length; j++) {
				if( j == 0 || j == arr[i].length - 1) {
					arr[i][j] = 1;
				}else{
					arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1];
				}
			}
		}
		// 打印二维数组
		for( int i = 0; i < arr.length; i++) {
			for( int j = 0; j < arr[i].length; j++) {
				System.out.print(arr[i][j]+"\t");
			}
			System.out.println("");
		}
	}
}

类与对象(基础)

基本介绍

  • 对象:属性 + 行为;是一个具体的实例。
  • 类:一种数据类型
  • 类的五大成员:属性、方法、构造器、代码块、内部类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u1Hkpi4B-1652873405294)(https://raw.githubusercontent.com/MCXXHYK/PicGo/main/image-20220503200153147.png#crop=0&crop=0&crop=1&crop=1&id=EVclS&originHeight=1086&originWidth=2484&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pHktv11U-1652873405297)(https://raw.githubusercontent.com/MCXXHYK/PicGo/main/image-20220503200311717.png#crop=0&crop=0&crop=1&crop=1&id=QwwBU&originHeight=1058&originWidth=2458&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

  • Java内存分配机制:
    • 栈内存:一般存放基本数据类型(局部变量)
    • 堆内存:存放对象、数组
    • 方法区:常量池(常量,例如字符串)、类加载信息

方法

细节和注意事项
  1. 一个方法只能有一个返回值,如何返回多个返回值?可用数组解决
//一个方法只能有一个返回值,如何返回多个返回值?可用数组解决
class AA {
	public int[] getSumAndSub(int num1, int num2) {
    	int[] resArr = new int[2];
    	resArr[0] = num1 + num2;
   		resArr[1] = num1 - num2;
    	//用数组返回多个返回值
    	return resArr;
  }
}
  1. 方法调用细节
    1. 同一个类中调用类中的方法,直接调用即可;
    2. 跨类调用方法时,需要创建该方法类的对象,用对象调用该方法。例如:A类中调用B类方法,需要先创建B类的对象,再用B类的对象调用B类的方法。
    3. 跨类的方法调用与方法的访问修饰符有关
  2. 成员方法传参细节
    1. 基本数据类型传参,是值拷贝,不可以通过形参修改实参
    2. 引用类型传参,传递的是地址,可以通过形参修改实参

递归

递归必须向退出递归的条件逼近,否则就会无限递归,造成栈溢出。

迷宫问题

/*
	迷宫问题求解:
		让小球自己找到路径
		用二维数组解决
		【规定】:
		设置 0 ==> 不是墙,可以走
		设置 1 ==> 墙,不能走
		设置 2 ==> 探路,插旗,表示可以走(是通路)
		设置 3 ==> 表示能走,但是是死路
		起点初始化为(1,1)
		二维数组定义为6行7列
		探测路径为:下 ==> 右 ==> 上 ==> 左
		【分析】:当数组终点为 2 时,说明已经找到了一条通路
*/
public class MiGong {
    //初始化一个二维数组
    static int[][] initTwoDemensionArray( int i, int j) {
        //1.开辟二维数组的内存空间
        int[][] arr = new int[i][j];
        //2.初始化数组
		/*
				1 1 1 1 1 1 1
				1 0 0 0 0 0 1
				1 0 0 0 0 0 1
				1 1 1 0 0 0 1
				1 0	0 0	0 0	1
				1 1 1 1 1 1 1
		*/
        for( int k = 0; k < i; k++) {
            for( int t = 0; t < j; t++) {
                if(k > 0 && t >0 && k < i-1 && t < j-1) {
                    arr[k][t] = 0;
                }else{
                    arr[k][t] = 1;
                }
            }
            arr[3][1] = 1;
            arr[3][2] = 1;
        }
        return arr;
    }
    //打印二维数组
    static void printTwoDemensionArray(int[][] arr) {
        for( int i = 0; i < arr.length; i++) {
            for( int j = 0; j <arr[i].length; j++) {
                System.out.print(arr[i][j]+" ");
            }
            System.out.println();
        }
    }
    //寻找路径 策略:下 ==> 右 ==> 上 ==> 左
    boolean findWay(int[][] arr, int i, int j) {
        //判断是否到达终点	避坑:这里到达终点条件不要写成 i == 4 && j == 5 ,因为这样写的话arr[4][5]就不会插旗 
        if( arr[4][5] == 2) {
            return true;
        }else {
            //1.判断当前位置状态( 0, 1, 2, 3 )
            // 0代表此位置可以走,但是没有插旗,将其划定为路径
            if (arr[i][j] == 0) {
                //插旗 假定可以周围可以走通
                arr[i][j] = 2;
                //探路 下 ==> 右 ==> 上 ==> 左
                if (findWay(arr, i + 1, j)) {					//向下走
                    return true;
                } else if (findWay(arr, i, j + 1)) {	//向右走
                    return true;
                } else if (findWay(arr, i - 1, j)) {	//向上走
                    return true;
                } else if (findWay(arr, i, j - 1)) {	//向左走
                    return true;
                } else {
                    arr[i][j] = 3;
                    return false;
                }
            } else {    //当前位置为 1 / 2 / 3
                return false;
            }
        }
    }
    public static void main(String[] args) {

        //1.初始化迷宫
        int[][] arr = initTwoDemensionArray(6,7);
        //2.打印迷宫
        printTwoDemensionArray(arr);
        //3.从(1,1)出发找寻路径,终点为(4,5)
        MiGong m1 = new MiGong();
        m1.findWay(arr, 1, 1);
        //4.打印插好旗的迷宫 (迷宫路径)
        System.out.println("\n====迷宫路径=====\n");
        m1.printTwoDemensionArray(arr);
    }

汉诺塔问题

/*
	汉诺塔问题
*/
public class HanoiTower {
	public static void main(String[] args) {
		T t = new T();
		// System.out.println("=========一个盘子============");
		// t.move(1, 'A', 'B', 'C');
		// System.out.println("=========两个盘子============");
		// t.move(2, 'A', 'B', 'C');
		// System.out.println("=========三个盘子============");
		// t.move(3, 'A', 'B', 'C');
		System.out.println("=========5个盘子============");
		t.move(5, 'A', 'B', 'C');	
	}
}
class T {
	int count = 1;	//记录移动次数
	//汉诺塔移动	num ==> 盘子数 source ==> 从哪里移动	via ==> 经过		distinct ==> 到哪里
	public void move(int num, char source, char via, char distinct) {
		//如果塔上只有一个盘子,直接把盘子 source -> distinct
		if(num == 1) {
			System.out.println("从"+source+"->"+distinct+"\t"+(count++));
		}else{	//塔上有多个盘子	看出两个盘子
			move(num - 1 , source, distinct, via);	//把source上面的 num - 1 个盘子先通过 distinct 移动到 via
			System.out.println("从"+source+"->"+distinct+"\t"+(count++));	// 最后一个盘子直接 source -> distinct
			move(num - 1, via, source, distinct);	//把via上面的 num - 1 个盘子先通过 source 移动到 distinct
		}
	}
}
/*
	八皇后问题
*/

方法重载

细节和注意事项
  • 方法名必须相同
  • 形参列表必须不同,可以区分。参数顺序,类型,个数等等。(参数名无要求)
  • 返回类型无要求。(两个**仅是返回类型不同**的方法会造成重复定义,并不会构成重载)
public int sum(int n1, int n2) {
  	return n1 + n2;
}
public void sum(int n1, int n2) {  
  	int res = n1 + n2;
}
//上面两个方法重复定义,并不没有构成方法重载

可变参数

  • Java中允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。
  • 语法:访问修饰符 返回类型 方法名(数据类型**…** 形参名){
    }
class HspMethod {
  //形参列表: 数据类型 + 三个点 + 参数名  nums可以当作数组来使用
  public int sum(int... nums) {
    int sum = 0;
    System.out.println("接收了"+nums.length+"个参数");
    for( int i = 0; i < nums.length; i++) {
      return sum += nums[i];
    }
  }
}
可变参数细节
  • 可变参数的实参可以为0个或任意多个
  • 可变参数的实参可以为数组
  • 可变参数的本质就是数组
  • 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
  • —个形参列表中只能出现一个可变参数

作用域

  • 全局变量(属性)可以不赋值,直接使用,因为有默认值,
  • 局部变量必须赋值后,才能使用,因为没有默认值
作用域细节
  • 全局变量(属性)和局部变量可以重名,访问时遵循就近原则
  • 同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名
  • 全局变量(属性)生命周期较长,伴随着对象的创建而创建,伴随着对象的死亡而死亡
  • 局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而死亡,即在一次方法调用过程中。
  • 作用域范围不同
  • 全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)
  • 局部变量:只能在本类中对应的方法中使用
  • 修饰符不同
    • 全局变量/属性可以加修饰符
    • 局部变量不可以加修饰符

构造器(构造方法)

用来创建对象时,就对对象进行初始化。

语法
/*
        [修饰符]	方法名	 (形参列表){

        方法体;

        }
*/
注意事项
  1. 构造器的修饰符可以默认,也可以是public protected private
  • 构造器没有返回值
  • 方法名 和类名字必须一样,严格相同,首字母都是大写
  • 参数列表 和 成员方法一样的规则
  • 构造器的调用,由系统完成
/*
	构造器
*/
public class Constructor01 {
	public static void main(String[] args) {
		Person p = new Person(18, "hyk");
		System.out.println("名字:"+p.name+"\t"+"年龄:"+p.age);
	}
}
class Person {
	int age;
	String name;

	public Person(int pAge, String pName){
		age = pAge;
		name = pName;
	}
}
构造器使用细节
  1. 一个类可以定义多个不同的构造器,即构造器重载
    比如:我们可以再给Person类定义一个构造器,用来创建对象的时候,只指定人名,不需要指定年龄
  2. 构造器名和类名要相同
  3. 构造器没有返回值
  4. 构造器是完成对象的初始化,井不是创建对象
  5. 在创建对象时,系统自动的调用该类的构造方法
  6. 如果没有定义构造器,系统会自动给类生成一个默认无参构造器(也
    叫默认构造器),比如 Dog(){}。 使用javap指令反编译看看
  7. 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无
    参构造器,除非_显式的定义一下_,即:Person(){}
//看一个案例
class Person{//类Person
  int age=90;
  String name;
  Person(String n,int a){//构造器
    name=n;//给属性赋值
    age=a; 
  }
}
public class Constructor {
	Person p=new Person("小倩",20);
}
/*
流程分析(面试题)
1.方法区中加载Person类信息(Person.class),只会加载一次
2.在堆中分配对象内存空间(地址)
3.完成对象初始化
	3.1 默认初始化 age=0 name=null 
	3.2 显式初始化 age=90,name=null,
	3.3 构造器的初始化 age =20, name=小倩
4.把对象在堆中的地址,返回给 p(p是对象名,也可以理解成是对象的引用)
*/

this 关键字:代表当前对象

public Dog(String name, int age){//构造器
    //this.name 就是当前对象的属性name
    this.name = name:
    //this.age 就是当前对象的属性age
		this.age = age;
}

//哪个对象调用这个构造器,这个this就指的是哪个对象

使用细节

  1. this关键字可以用来访问本类的属性、方法、构造器
  2. this用于区分当前类的属性和局部变量
  3. 访向成员方法的语法:this.方法名(参数列表)
  4. 访问构造器语法:this(参数列表);注意只能在构造器中使用 (即只能在构造器中访问另外一个构造器)
public T() {	// 无参构造
  this("yk", 18);		//在构造器中访问另外一个构造器 且此语句需放置构造器的第一条
}
public T(String name, int age) {	// 无参构造
  System.out.println("有参构造");
}
  1. this不能在类定义的外部使用,只能在类定义的方法中使用
/*
	比较两个人是否是同一个人:年龄和姓名都相同就认为是同一个人
*/
public class ThisExercise {

	public static void main(String[] args) {
		Person p1 = new Person(12,"张三");
		Person p2 = new Person(12,"张三");
		boolean res = p1.compareTo(p2);
		System.out.println(res);		//true
	}
}
class Person {

	//属性
	int age;
	String name;

	//有参构造
	public Person(int age, String name) {
		this.name = name;
		this.age = age;
	}

	//比较方法
	public boolean compareTo(Person p) {
		return this.name.equals(p.name) && this.age == p.age;
	}
}

作用

  1. 区分相同名字的类
  2. 当类很多时,可以很好的管理类「看Java API 文档]
  3. 控制访问范围

语法

**包基本语法**
package com.hspedu;	需放在每个.java文件第一行
说明:
1. package 关键字,表示打包。
2. com.hspedu:表示包名
  • java.lang.* //lang包是基本包,默认引入,不需要再引入
  • java.util.* //util 包,系统提供的工具包,工具类,使用 Scanner
  • java.net. * //网络包,网络开发
  • java.awt.* //是做java的界面开发,GUI

访问修饰符

基本介绍

java提供四种访问控制修饰符号,用于**控制方法和属性(成员变量)**的访问权限(范围):

  • 公开级别:用public 修饰,对外公开
  • 受保护级别:用protected修饰,对子类和同一个包中的类公开
  • 默认级别:没有修饰符号,向同一个包的类公开
  • 私有级别:用private修饰,只有类本身可以访问,不对外公开
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zp5fmpp9-1652873405298)(https://raw.githubusercontent.com/MCXXHYK/PicGo/main/image-20220505091456922.png#crop=0&crop=0&crop=1&crop=1&id=Ddnae&originHeight=270&originWidth=762&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

注意事项

  • 修饰符可以用来修饰类中的属性,成员方法以及类
  • 只有默认的和public才能修饰类,并且遵循上述访问权限的特点。
  • 因为没有学习继承,因此关于在子类中的访问权限,我们讲完子类后,再回头讲解
  • 成员方法的访问规则和属性完全一样

面向对象三大特征

封装

封装的实现步骤(三部曲)
  1. 属性进行私有化private 【不能直接修改属性】
  2. 提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){	//Xxx 表示某个属性
		//加入数据验证的业务逻辑
		属性=参数名;
}
  1. 提供一个公共的(public)get方法,用于获取属性的值
public 数据类型 getXxx(){ 		//权限判断,Xxx 某个属性
		return xx;
}
/*
看一个案例
那么在java中如何实现这种类似的控制呢?
请大家看一个小程序(EncapsulationO1java),
不能随便查看人的年龄,工资等隐私,
井对设置的年龄进行合理的验证,
年龄合理就设置:否则给默认,年龄,必须在1-120,
年龄,工资不能直接查看,name的长度在2-6字符之间
*/

PS:有参构造器可以和set方法一起使用,用来控制权限

继承

基本介绍

继承可以解决代码复用,让我们的编程更加靠近人类思维,当多个类存在相同的属性(变量)和方法时,可以以这些类中抽象出父类,在父类中定义这些相同的属佳和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。

语法
class 子类 extends 父类{
  
}
//1)子类就会自动拥有父类定义的属性和方法
//2)父类又叫超类,基类.
//3)子类又叫派生类.
细节
  1. 子类继承了所有的属性和方法非私有属性可以直接访问但是私有属性和方法不能在子类直接访向,要通过父类所提供的公共的方法间接去访问
  2. 子类必须调用父类的构造器,完成父类的初始化(子类构造器中隐藏了super()
  3. 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
  4. 如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过.
#【解读】:
#1.父类中有无参构造器时,子类中不管是有参构造器还是无参构造器都会隐藏一个super() ,默认调用父类的无参构造器。
#2.父类中没有无参构造器时,
#	 即父类中有参构造器将默认无参构造器覆盖掉时,
#	 且没有在父类中另外重新声明无参构造器,
#	 则在子类的构造器中必须用super去指定使用父类的哪个构造器
#	 完成对父类的初始化,否则编译无法通过。
#【另注】:初始化子类时,父类也会被初始化,且父类先被初始化
  1. 如果希望指定去调用父类的某个构造器,则显式的调用一下:super(参数列表),默认调用的是无参构造器
  2. super在使用时,需要放在构造器第一行
  3. super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
  4. java所有类都是Object类的子类,Object 是所有类的基类。
  5. 父类构造器的调用不限于直接父类!将一直往上追湖直到Object类(顶级父类),从上往下调用构造器
  6. 子类最多只能继承一个父类(指直接继承),即java中是单继承机制
  7. 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
继承本质
  1. 子类对象访问属性或调用方法时注意事项:
    • 首先看子类是否有该属性
    • 如果子类中有该属性,并且可以直接访问(没有private),则返回信息
    • 如果子类中没有该属性,就看其直接父类(上一层父类)有没有该属性,如果有,就停止查找,并且该属性可以直接访问(没有private),则返回信息(如果这里属性如果被私有化了,别的类的不能访问,则子类可以通过父类提供的公共get/set方法访问该属性)
    • 如果直接父类中还没有该属性,则再向上找,直到有该属性并且可以访问的父类(就近原则,此"近"是指距离子类的距离)
package com.test.extend_;

public class extend_test {
    public static void main(String[] args) {
        Son son = new Son();
        System.out.println(son.name);   //小头儿子
        System.out.println(son.age);    //30
        System.out.println(son.hobby);  //旅游
    }
}

class GrandPa {
    String name = "超大头爷爷";
    String hobby = "旅游";
}

class Father extends GrandPa {
    String name = "大头爸爸";
    int age = 30;
}

class Son extends Father {
    String name = "小头儿子";
}
/*
	【运行原理】:
			Son son = new Son();
			1.先在方法区中加载类的信息,先加载最顶级父类的信息,再从上至下一直加载到子类
			2.然后再在堆中,创建对象,对象中依次创建父类的属性,再创建子类的属性,字符串常量等引用类型
			放在方法区的常量池,堆中会有一个地址指向它。所有的属性(从父类到子类)全部创建完毕,
			一个子类对象在堆中就创建完成了,这时会在栈中创建一个引用指向堆中的对象。
			
*/
//如果这里属性如果被私有化了,别的类的不能访问,则子类可以通过父类提供的公共get/set方法访问该属性。
/*
	class Father extends GrandPa {
    String name = "大头爸爸";
    private int age = 30;
    
    public int getAge()  {
        return age;
    }
}
public class extend_test {
    public static void main(String[] args) {
        Son son = new Son();
        System.out.println(son.getAge());
    }
}
/

super关键字
  • 访问父类的属性,但不能访问父类的private属性
    • 【案例】super.属性名
  • 访问父类的方法,不能访问父类的private方法
    • super.方法名(参数列表);
  • 访问父类的构造器(这点前面用过):
    • super(参数列表);只能放在_构造器_的第一句,只能出现一句!(其他不是构造器的方法中不能使用)
使用细节
  • 调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子
    类初始化
  • 子类有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须
    通过super指定;如果没有重名,使用super、this、直接访问是一样的效果!
  • super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用
    super去访问爷爷类的成员;如果多个基类(上级)中都有同名的成员,使用super访
    遵循就近原则,但是也要遵循访问权限的原则。(private 修饰后不能直接访问)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GpD70MnO-1652873405299)(https://raw.githubusercontent.com/MCXXHYK/PicGo/main/image-20220505213741744.png#crop=0&crop=0&crop=1&crop=1&id=hHnyi&originHeight=814&originWidth=1814&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

方法重写(override)

基本介绍:

简单的说:方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称、返回类型(或者返回类型也有继承关系)、参数一样,那么我们就说子类的这个方法覆盖了父类的方法

条件:
  • 子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一样
  • 子类方法的返回类型和父类方法返回类型一样
  • 或者子类方法的返回类型父类返回类型的子类
    • 比如父类 返回类型是 Object,子类方法返回类型是String ,String是Object的子类
  • 子类方法不能缩小父类方法的访问权限 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0AmpuRNp-1652873405300)(https://raw.githubusercontent.com/MCXXHYK/PicGo/main/image-20220505215758230.png#crop=0&crop=0&crop=1&crop=1&id=D6i5F&originHeight=528&originWidth=1944&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

多态

基本介绍

方法和对象具有多种形态,多态建立在继承和封装之上。

  1. 具体体现
  • 方法的多态:重写和重载
  • 对象的动态:
    • 一个对象的编译类型和运行类型可以不一致
    • 编译类型在定义对象时,就确定了,不能改变
    • 运行类型是可以变化的
    • 编译类型看定义时 " = " 号 的左边,运行类型看 " = " 号的 右边
//案例
Animal animal = new Dog(); 【animal 编译类型是Animal,运行类型Dog】  父类引用可以指向子类对象(向上转型)
animal = new Cat(): 【animal 的运行类型变成了 Cat, 编译类型仍然是 Animal
  1. 细节
  • 多态的前提是:两个对象(类)存在继承关系
  • 多态的向上转型
    • 本质:父类的引用指向了子类的对象
    • 语法:父类类型引用名= new 子类类型()
    • 特点:编译类型看左边,运行类型看右边
    • 规则:
      • 这个父类引用可以调用父类中的所有成员(需遵守访问权限),
      • 不能调用子类中特有成员
      • 最终运行效果看子类(运行类型)的具体实现!即调用方法时(运行时),仍然是从子类(运行类型)开始向上查找方法,然后调用
//案例
Animal animal = new Dog(); 【animal 编译类型是Animal,运行类型Dog】  父类引用可以指向子类对象(向上转型)
animal = new Cat(): 【animal 的运行类型变成了 Cat, 编译类型仍然是 Animal
  • 多态的向下转型
    • 语法:子类类型 引用名=(子类类型)父类引用
Animal animal = new Dog();
Dog dog = (Dog)animal;	  		//  子类类型 引用名=(子类类型)父类引用
  - **只能强转父类的引用,不能强转父类的对象**
  - 要求**父类的引用必须指向的是当前目标类型的对象**
  - **向下转型后,可以调用子类类型中所有的成员**,**解决了向上转型中父类引用不能调用子类特有成员的问题**
  • 属性没有重写之说,属性的值看编译类型
 public class PolyDetail {
    public static void main(String[] args) {
        Base base = new Sub();
        System.out.println(base.count); //看编译类型 10	属性没有重写之说,属性的值是由编译类型决定的,不能按照方法重写思路
        base.ok();      							//haha
    }
}
class Base {
    int count = 10;

    public void ok() {
        System.out.println("ok");
    }
}
class Sub extends Base {
    int count = 20;

    @Override
    public void ok() {
        System.out.println("haha");
    }
}
  • instanceof 比较操作符,用于判断对象运行的类型是否为 XX类型 或 XX类型的子类型
 public class PolyDetail {
    public static void main(String[] args) {
        Base base = new Sub();
        Sub sub = (Sub) base;
        System.out.println(sub instanceof Sub);	//true	对象 sub 的运行类型是否为 Sub 类型
        System.out.println(sub instanceof Base);	//true	对象 sub 的运行类型是否为 Base 类型的子类型
    }
}
class Base {

}
class Sub extends Base {
}
java的动态绑定机制
  1. 调用对象方法的时候,该方法会和该对象的(
    存地址/运行类型)绑定
  2. 调用对象属性时,没有动态绑定机制,哪里声
    明,那里使用
public class Dynamic_Bind {
    public static void main(String[] args) {
        // a 编译类型 A 运行类型 B
        A a = new B();
        /**
         * 把B中sum()注释后 40 -> 30
         * 因为此时a.sum()调用的是父类的sum(),
         * 父类sum()中有个getI(),
         * 这个getI()调用的是子类的getI(),因为调用方法时,
         * 该方法会和对象的运行类型绑定,即动态绑定机制,所以此处是 20 + 10 -> 30
         */
        System.out.println(a.sum());    //40 -> 30
        /**
         * 把B中sum1()注释后 30 -> 20
         * 因为此时a.sum1()调用的是父类的sum1(),
         * 父类sum1()中直接return i + 10;
         * 这个 i 调用的是父类的 i ,因为调用对象属性时,
         * 该属性没有动态绑定机制,哪里声明 哪里使用, 所以此处是 10 + 10 -> 20
         */
        System.out.println(a.sum1());   //30 -> 20
    }
}
class A {   //父类
    public int i = 10;

    public int sum() {
        return getI() + 10;
    }

    public int sum1() {
        return i + 10;
    }

    public int getI() {
        return i;
    }
}
class B extends A {//子类
    public int i = 20;

//    public int sum() {
//        return i + 20;
//    }

    public int getI() {
        return i;
    }

//    public int sum1() {
//        return i + 10;
//    }
}
多态的应用
  1. 多态数组:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
package com.poly.polyArray;
/*
*   应用实例:现有一个继承结构如下:
*   要求创建1个Person对象、2个Student 对象和2个
    Teacher对象,统一放在数组中,并调用每个对象say方法.
    com.poly.polyArray.Person
    name: String
    age:  int
    say():  String
    属性private
    应用实例升级:如何调用子类特有的方法,比如
    Teacher 有一个teach(), Student 有一个study()怎么调用?
**/
public class Poly_Array {
    public static void main(String[] args) {
        Person[] person = new Person[5];
       person[0] = new Person("yk", 18);
       person[1] = new Student("小明", 8, 59.5);
       person[2] = new Student("小刚", 9, 99.5);
       person[3] = new Teacher("smith", 30, 10000);
       person[4] = new Teacher("武忠祥",60,200000);
        for (int i = 0; i < person.length ; i++) {
            //如果数组中元素不是Student类或者其子类
            if( person[i] instanceof Student) {
                Student stu = (Student) person[i];
                stu.study();
                //等价于
                //((Student) person[i]).study();
            }else if (person[i] instanceof Teacher) {
                ((Teacher) person[i]).teach();
            }else if (person[i] instanceof Person) {

            }else{
                System.out.println("你的类型有误,请检查!");
            }
//            System.out.println(person[i].say());
        }
    }
}
  1. 多态参数:方法定义的形参类型为父类类型,实参允许为子类类型。

Object类详解

  • equals()方法
    • equals() 和 == 的对比:
      • ==:既可以判断基本类型,又可以判断引用类型
      • ==:如果判断基本类型,判断的是值是否相等。示例:int i=10;double d=10.0;
      • ==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象
      • equals():是Object类中的方法,只能判断引用类型
      • equals():默认判断的是地址是否相等, 子类中往往重写该方法,用于判断内容是否相等。
package com.poly.equals;

public class Equals_Detail {
    public static void main(String[] args) {
        Equals_Test t1= new Equals_Test(18,"jack",20000);
        Equals_Test t2= new Equals_Test(18,"jack",20000);
        System.out.println(t1.equals(t2));;
    }
}
package com.poly.equals;

public class Equals_Test {
    private int age;
    private String name;
    private int id;

    public Equals_Test(int age, String name, int id) {
        this.age = age;
        this.name = name;
        this.id = id;
    }

    public boolean equals(Object o) {
        if( o != null && o == this ) {
            return true;
        }else if(o instanceof Equals_Test) {
            Equals_Test e = (Equals_Test) o;
            return e.age == age && e.name.equals(name) && e.id == id;
        }
        return false;
    }
}

      /*名称					概念							  用于基本数据类型							用于引用类型
						
       ==						比较运算符						 可以,判断值是否相等				可以,判断两个对象是否相等

      equals					Object类的方法			    不可以								可以,默认是判断两个对象是否相等								
									  Java类都可以使用equals                         但是子类往往重写该方法,比较对象的
																															 属性是否相等,比如(String,lnteger)
*/
  • hashCode()方法:返回该对象的哈希码值
    • 提高具有哈希结构的容器的效率!
    • 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
    • 两个引用,如果指向的是不同对象,则哈希值是不一样的
    • 哈希值主要根据地址来的,但是不能完全将哈希值等价于地址。
      • 案例演示IHashCode_javal: obj。hashCode0[测试:A obj1= new A0; A obj2 = new A0: A
        obj3 = obj1]
    • 后面在集合,中hashCode 如果需要的话,也会重写
  • toString()方法
    • 基本介绍
      • 默认返回:全类名+@+哈希值的十六进制, 子类往往重写toString方法,用于返回对象的属性信息
        重写toString方法**,打印对象或拼接对象时,都会自动调用该对象的toString形式**.
      • 当直接输出一个对象时,toString 方法会被默认的调用,比如
        System.out.printin(monster);
        就会默认调用 monster.toString()
/*
  案例演示:Monster [name, job, sal]案例:ToString_java
  当直接输出一个对象时,toString 方法会被默认的调用
*/

@Override
    public String toString() {
        return "Monster{" +
                "name='" + name + '\'' +
                ", job='" + job + '\'' +
                ", sal=" + sal +
                '}';
    }
  • finalize()方法
    • 当对象被回收时系统自动调用该对象的finalize方法子类可以重写该方法,
      做一些释放资源的操作
    • 什么时候被回收:当某个对象没有任何引用时,则 jvm就认为这个对象是一个垃
      圾对象,就会使用垃圾回收机制来销毁该对象在销毁该对象前,会先调用finalize方法。
    • 垃圾回收机制的调用,是由系统来决定,也可以通过System.gc() 主动触发垃圾
      回收机制
  • 断点调试
    • F7(跳入)F8(跳过) shift+ F8(跳出) F9( resume,执行到下一个断点
    • F7**:跳入方法内**
    • F8:逐行执行代码.
    • shift+F8:跳出方法

房屋出租系统

项目需求:

实现基于文本界面的《房屋出租软件》。能够实现对房屋信息的添加、修改和删除(用数组实现),并能够打印房屋明细表。

类变量(静态变量)

定义:

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的
对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改
的也是同一个变量。

说明:

  • 类变量由关键字static修饰,其最大的特点就是会被类中所有对象共享
  • 类变量可以通过类名直接访问
  • 类变量在类加载时就生成了

类变量内存布局

定义类变量
【访问修饰符】 static 【数据类型】 变量名(推荐)
  或者
 static 【访问修饰符】【数据类型】 变量名
访问类变量
类名.变量名	(推荐)
  或者
对象名.变量名

使用细节

  • 什么时候需要用类变量
    当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态
    变量)
  • 类变量与实例变量(普通属性)区别
    类变量是该类的所有对象共享的,而实例变量每个对象独享的。
  • 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
    类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问,但java设计者推荐
    我们使用 类名.类变量名方式访问。【前提是满足访问修饰符的访问权限和范围
  • 实例变量不能通过 类名.类变量名 方式访问。
  • 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,
    就可以使用类变量了。
  • 类变量的生命周期是随类的加载开始,随着类消亡而销毁

类方法(静态方法)

形式

【访问修饰符】 static 【数据类型】 方法名 {}(推荐)
  或者
 static 【访问修饰符】【数据类型】 方法名	{}

类方法调用

类名.类方法名	(推荐)
  或者
对象名.类方法名
  【前提】:满足访问修饰符的访问权限

细节

  • 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区类方法中无this的参数
  • 普通方法中隐含着this的参数
  • 类方法可以通过类名调用,也可以通过对象名调用
  • 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调
  • 类方法中**不允许使用和对象有关的关键字,比如this和super,**普通方法(成员方法)可以。
  • 类方法中只能访问静态变量或静态方法
  • **普通成员方法,既可以访问 非静态成员,也可以访问静态成员 **。

main方法

深入理解main方法

public static void main(String[] args) {
  
}
  • main方法时虛拟机调用
  • java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
  • java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
  • 该方法接收String类型的数组参数该数组中保存执行java命令时传递给所
    运行的类的参数
  • java 执行的程序 参数1参数2 参数3
javac Hello.java
java Hello tom jack smith			#这里tom jack smith 就是传入String数组的参数,执行命令时传入

特别提示

  • main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
  • 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能
    通过这个对象去访问类中的非静态成员

代码块

基本介绍

  • 代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句
    封装在方法体中,通过{ }包围起来。
  • 但和方法不同,没有方法名,没有返回,没有参数,只有方法体而且不用通过对象或
    类显式调用,而是加载类时,或创建对象时隐式调用

基本语法

[修饰符]{
	代码
}

说明注意:

  • 修饰符要么不写,要么用static
  • 代码块分为两类,使用static 修饰的叫静态代码块没有static修饰的,叫普通代码块/非静态代码块
  • 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
  • 结尾括号的分号可以写上,也可以省略。
  • 调用构造器时会先调用代码块中的内容
    • 使用场景:多个构造器中有相同的重复语句,可以将其抽取到代码块中。
    • 普通代码块可以理解为是对构造器的补充机制,可以做初始化操作

注意事项

  • static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且
    只会执行一次。如果是普通代码块,每创建一个对象,就执行。
  • 类什么时候被加载?
    • 创建对象实例时(new)
    • 创建子类对象实例,父类也会被加载;而且父类先被加载,子类后被加载
    • 使用类的静态成员时(静态属性,静态方法)
  • 普通的代码块,在创建对象实例时,才会被隐式的调用。被创建一次,就会调用一次。
  • 如果只是使用类的静态成员时,普通代码块并不会执行。
  • 创建一个对象时,在一个类 调用顺序是:(重点,难点
    1. 调用静态代码块和静态属性初始化(注意:静态代码块和静态
      属性初始化调用的优先级一样,如果有多个静态代码块和多个静
      态变量初始化,则按他们定义的顺序调用)[举例说明]
    2. 调用普通代码块和普通属性的初始化(注意:普通代码块和普
      通属性初始化调用的优先级一样,如果有多个普通代码块和多
      个普通属性初始化,则按定义顺序调用)
    3. 调用构造方法
  • 构造器的最前面其实隐含了 supe()和 调用普通代码块;
  • 静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
package com.ykstudy.code_block;

public class CodeBlock {
    public static void main(String[] args) {
        new BB();
        /*执行顺序:
                (1)AA的代码块
                (2)AA的构造器被调用
                (3)BB的代码块
                (4)BB的构造器被调用
        */
    }
}

class AA {

    {
        System.out.println("AA的代码块");
    }

    public AA() {
        //(1)super()
        //(2)本类的普通代码块
        System.out.println("AA的构造器被调用");
    }
}

class BB extends AA {

    {
        System.out.println("BB的代码块");
    }

    public BB() {
        //(1)super()
        //(2)本类的普通代码块
        System.out.println("BB的构造器被调用");
    }
}
  • 我们看一下创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
    1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    2. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    3. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    4. 父类的构造方法
    5. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    6. 子类的构造方法
  • 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
class A02 { //父类
    private static int n1 = getVal01();
    static {
        System.out.println("A02的一个静态代码块..");//(2)
    }
    {
        System.out.println("A02的第一个普通代码块..");//(5)
    }
    public int n3 = getVal02();//普通属性的初始化
    public static int getVal01() {
        System.out.println("getVal01");//(1)
        return 10;
    }
 
    public int getVal02() {
        System.out.println("getVal02");//(6)
        return 10;
    }
 
    public A02() {//构造器
        //隐藏
        //super()
        //普通代码和普通属性的初始化......
        System.out.println("A02的构造器");//(7)
    }
}
 
class B02 extends A02 { //
 
    private static int n3 = getVal03();
 
    static {
        System.out.println("B02的一个静态代码块..");//(4)
    }
    public int n5 = getVal04();
    {
        System.out.println("B02的第一个普通代码块..");//(9)
    }
 
    public static int getVal03() {
        System.out.println("getVal03");//(3)
        return 10;
    }
 
    public int getVal04() {
        System.out.println("getVal04");//(8)
        return 10;
    }
    //一定要慢慢的去品..
    public B02() {//构造器
        //隐藏了
        //super()
        //普通代码块和普通属性的初始化...
        System.out.println("B02的构造器");//(10)
    }
}
 
public Class Code_Block_Detail {
  public static void main(String[] args) {
    new B02();//分析代码执行顺序
    /*
    	(1)先进行类信息的加载,先加载父类信息(static修饰的属性和代码块),再加载子类信息(static修饰的属性和代码块)
    		 (static修饰的方法只有在被调用时才会加载)
    	(2)类加载完毕之后,再进行对象创建
    */
  }
}

设计模式

单例设计模式

  • 说明:从运行到结束的过程中,某个类只有一个实例存在
1. 饿汉式
  • 步骤
    1. 构造器私有化 => 防止直接 new
    2. 类的内部创建对象
    3. **向外暴露一个静态的公共方法。**例如:public void getInstance(){ }
2. 懒汉式
  • 步骤
    1. 构造器私有化 ==> 防止直接new
    2. 定义一个私有静态变量(不初始化)保存类的对象
    3. 提供一个公共静态方法返回类的对象
    4. 判断类中保存对象的静态变量有没有被初始化
package com.ykstudy.single_;


public class Single_ton01 {
    public static void main(String[] args) {
        //Girlfriend{name='小红'} 饿汉式
        System.out.println(Girlfriend01.getInstance());
        //Girlfriend02{name='美羊羊'} 懒汉式
        System.out.println(Girlfriend02.getInstance());

    }
}

//饿汉式
class Girlfriend01 {

    private String name;

    //2.在类的内部定义私有的静态变量保存新创建的对象
    private static Girlfriend01 gf = new Girlfriend01("小红");

    //1.构造器私有化
    private Girlfriend01(String name) {
        this.name = name;
    }

    //3.对外部提供一个公有的静态方法
    public static Girlfriend01 getInstance() {
        return gf;
    }

    @Override
    public String toString() {
        return "Girlfriend{" +
                "name='" + name + '\'' +
                '}';
    }
}
//懒汉式
class Girlfriend02 {

    private String name;
    //2.定义一个私有静态变量(不初始化)保存类的对象
    private static Girlfriend02 gf;

    //1.构造器私有化
    public Girlfriend02(String name) {
        this.name = name;
    }
    //3.提供一个公共静态方法返回类的对象
    public static Girlfriend02 getInstance() {
        //4.判断类中保存对象的静态变量有没有被初始化
        if(gf == null) {
            gf = new Girlfriend02("美羊羊");
        }
        return gf;
    }

    @Override
    public String toString() {
        return "Girlfriend02{" +
                "name='" + name + '\'' +
                '}';
    }
}
两种方式存在的问题(饿汉式 || 懒汉式)
  • 饿汉式的问题:在类加载时候就创建,可能存在资源浪费问题
  • 懒汉式的问题:线程安全问题,后面我们学了线程后,在进行完善。

final关键字

final 中文意思:最后的,最终的.

final 可以修饰类、属性、方法和局部变量.

在某些情况下,程序员可能有以下需求,就会使用到final:

  1. **当不希望类被继承时,**可以用final修饰.
  2. 当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。
  3. 当不希望类的的某个属性的值被修改,可以用final修饰.
  4. 当不希望某个局部变量被修改,可以使用final修饰

注意事项

  1. final修饰的属性又叫常量,一般用 XX_XX_XX来命名
  2. final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
    • 定义时:如 public final double TAX RATE=0.08;
    • 构造器
    • 代码块
  3. 如果final修饰的属性静态的,则初始化的位置只能是
    • 定义时
    • 在静态代码块
    • 但是不能在构造器中赋值。
  4. final类不能继承,但是可以实例化对象
  5. 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。
  6. 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法
  7. final不能修饰构造方法(即构造器)
  8. final 和 static 往往搭配使用,效率更高,修饰基本数据类型时不会导致类加载.底层编译器做了优化处理。
class Demo{
public static final int i=16; //

static{
  System.out.printiln(”韩顺平教育~");
}
  1. 包装类(Integer,Double, Float,Boolean等都是final类),String也是final类

抽象类(Abstract)

父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,然后用abstract 来修饰该类就是抽象类。

//我们看看如何把Animal做成抽象类,并让子类Cat类实现。 
abstract class Animal{
    String name;
    int age;
		abstract public void cry(); //抽象方法没有方法体,写成抽象方法后,类也要加abstract修饰
}

细节和注意事项

  1. 抽象类不能被实例化
  2. 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
  3. 一旦类包含了abstract方法,则这个类必须声明为abstract
  4. abstract 只能修饰类和方法,不能修饰属性和其它的
  5. 抽象类可以拥有任意成员【抽象类的本质还是类】
  6. 抽象方法没有方法体,即不能在本类实现。
  7. 抽象类一旦被继承,其子类必须实现其所有抽象方法。除非其子类也被abstract修饰。
  8. 抽象方法不能使用private、 final 和 static来修饰,因为这些关键字都是和重写相违背

模版设计模式——抽象类最佳实践

public class Template_Mode {

    public static void main(String[] args) {
        B b = new B(); 
        b.calculateTime();
    }
}
/*
	设计一个抽象类(Template),能完成如下功能:
    编写方法calculateTime(),可以计算某段代码的耗时时间
    编写抽象方法job()
*/
abstract class A {
    //定义一个抽象方法,子类重写
    public abstract void job();
		//把抽象方法塞到这个普通方法里面形成模板
    public void calculateTime() {
        //开始时间
        long start = System.currentTimeMillis();
        //实现任务
        job();  //动态绑定机制
        //结束时间
        long end = System.currentTimeMillis();
        System.out.println("任务时间==" + (end - start));	//137
    }
}

class B extends A {
 	
    @Override
    public void job() {
        long num = 0;
        for (long i = 0; i < 100000000; i++) {
            num *= i;
        }
    }
}

接口

  • 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。

语法:

interface 接口名{
  
  //属性
  //方法(默认抽象方法;default修饰的方法;static修饰的方法)
  

  int num;
  //接口中方法默认为抽象方法
  public void cry();
  
  //jdk8以后接口中方法加关键词 static default修饰时方法可以有方法体
  static void smile() {
    System.out.println("haha");
  }
  
  default void play() {
    System.out.println("playing now");
  }
}

class 类名 implements 接口名{ 
	  /*
    自己属性;
    自己方法;
    必须实现的接口的抽象方法
  */
}

小结:

  1. Jdk7.0前接口里的所有方法都没有方法体
    2.Jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现

注意事项

  1. 接口不能被实例化

  2. 接口中所有的方法是 public方法接口中抽象方法,可以不用abstract 修饰

     void aa()
    
     实际上是 public abstract void aa();
    
  3. 一个普通类实现接口,就必须将该接口的所有方法都实现。

  4. 抽象类实现接口,可以不用实现接口的方法。

  5. 一个类同时可以实现多个接口

  6. 接口中的属性,只能是final的,而且是 public static final 修饰符。比如:
    int a=1;实际上是 public static final int a=1;(必须初始化)
    接口中属性的访问形式:接口名.属性名

  7. 一个接口不能继承其它的类,但是可以继承多个别的接口

interface A extends B, C {

}

interface B {
  
}

interface C {
  
}
  1. 接口的修饰符只能是 public 和默认,这点和类的修饰符是一样的。

实现接口和继承类

  1. 实现接口是对Java单继承的一种补充
  2. 当子类继承了父类,就自动的拥有了父类的功能
  3. 如果子类需要扩展功能,可以通过实现接口的方式扩展
  4. 继承的价值:解决代码的复用性和可维护性
  5. 接口的价值:设计,设计好各种规范(方法),让其他类去实现这些方法
  6. 接口比继承更加灵活
    继承是is a的关系,而接口只需要满足like a的关系
  7. 接口在一定程度上实现代码的解耦【即:接口规范性 + 动态绑定】
    比喻:猴子继承爬树,可以实现鱼一样的游泳,可以实现小鸟一样飞翔

接口多态特性

  1. 接口多态参数:接口类型的变量可以指向实现了此接口的对象实例
public class A {
    public static void main(String[] args) {
        //接口类型的变量可以指向实现了此接口的对象实例 
        IF if =  new B();	
    }
}

Interface IF {
    
}
class B implements IF {

} 
  1. 接口多态数组
  2. 接口多态传递
public class InterfacePolyPass {

public static void main(String[] args) {

    //接口类型的变量可以指向,实现了该接口的类的对象实例
 	IG ig = new Teacher();
    //如果IG 继承了 IH 接口,而Teacher 类实现了IG接口 
    //那么,实际上就相当于 Teacher 类也实现了IH接口
	IH ih =  new Teacher();
	}
}
interface IH { }

interface IG extends IH{ }

class Teacher implements IG {
}

内部类

基本介绍

一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类(inner class), 嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员.

  • 【思考】:类的五大成员是哪些?[属性,方法、构造器、代码块、内部类],
  • 内部类最大的特点就是可以直接访问私有属性,井且可以体现类与类之间的包含关系,
    • 注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类.

基本语法

class Outer{ //外部类

class Inner{ //内部类

}


class Other{ //外部其他类

}

内部类的分类

1. 局部内部类

【说明】:局部内部类是定义在外部类的局部位置,比如方法中,代码块中,并且有类名

  1. 可以直接访问外部类的所有成员,包含私有的
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final 修饰,因为局部变量也可以使用final
  3. 作用域:仅仅在定义它的方法或代码块中。
  4. 局部内部类—访问 ==> 外部类的成员 [访问方式:直接访问]
  5. 外部类—访问===>局部内部类的成员
    访问方式:创建对象,再访问(注意:必须在作用域内)
  6. 外部其他类—不能访问====>局部内部类(因为局部内部类地位是一个局部变量)
  7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

【演示】

//Outero2.this 本质就是外部类的对象,即哪个对象调用了m1, Outero2.this就是哪个对象
System.out printin("外部类的n2-+外部类名.this.n2);
2. 匿名内部类(重要‼️)

【说明】:匿名内部类是定义在外部类的局部位置,比如方法中,井且没有类名
(1) 本质是类;
** (2)内部类**
** (3)没有名字(系统底层分配名字)**
** (4)同时还是一个对象!!!**

  • 匿名内部类的基本语法
new 类或接口(参数列表){

    类体

};

【细节】:

  1. 可以直接访问外部类的所有成员,包含私有的
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final 修饰,因为局部变量也可以使用final
  3. 作用域:仅仅在定义它的方法或代码块中。
  4. 局部内部类—访问 ==> 外部类的成员 [访问方式:直接访问]
  5. 外部类—访问===>局部内部类的成员
    访问方式:创建对象,再访问(注意:必须在作用域内)
  6. 外部其他类—不能访问====>局部内部类(因为局部内部类地位是一个局部变量)
  7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

【特点】:

  • 没有类名的局部内部类(一切特征都与局部内部类相同)
  • 必须继承一个父类或者实现一个接口
  • 定义类、实现类、创建对象的语法合并,只能创建一个该类的对象
  • 优点:减少代码量
  • 缺点可读性较差
// 使用匿名内部类优化(相当于创建了一个局部内部类)
Usb usb = new Usb(){ // Usb为一个接口
  @Override
  public void service(){
    sout("连接电脑成功,fan开始工作")
  }
};
usb.service();
匿名内部类最佳实践
  • 匿名内部类用作实参直接传递
public class AnoymousInnerClass {

    public static void main(String[] args) {
        Cellphone cellphone = new Cellphone();
        /*
        * Bell bell = new Bell() {
        *   @Override
        *   public void ring() {
        *       System.out.println("懒猪起床了!");
        *   }
        * }
        *
        * 这里匿名内类系统底层给其分配了一个名字:AnoymousInnerClass$1
        * 这里其实是
        *         class AnoymousInnerClass$1 implements Bell {
        *               @Override
        *               public void ring() {
        *                   System.out.println("懒猪起床了!");
        *                   System.out.println(this.getClass());
        *               }
        *         }
        * */
        cellphone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("懒猪起床了!");
                System.out.println(this.getClass());    //class AnoymousInnerClass$1
            }
        });
        cellphone.alarmClock(new Bell() {

            @Override
            public void ring() {
                System.out.println("小伙伴下课了!");
            }
        });
    }
}

/*
     有一个铃声接口Bell,里面有个ring方法。
     有一个手机类Cellphone,具有闹钟功能 alarmClock, 参数是Bell类型(右图)
     测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,
     打印:懒猪起床了再传入另一个匿名内部类(对象),打印:
     小伙伴上课了
*/
interface Bell {

    void ring();
}

class Cellphone {

    public void alarmClock(Bell bell) {
        bell.ring();
    }
}
3. 成员内部类

说明:成员内部类是定义在外部类的成员位置,并且没有static修饰。

  1. 可以直接访问外部类的所有成员,包含私有的
class Outer01{ //外部类 private int n1 = 10;

public String name = "张三”;

    class Innter01{

        public void say(){

            System.out.println("Outer01 的n1 =" + n1 
                               + "outer01 的 name "+  name );

        }
    }
}
  1. 可以添加任意访问修饰符(public. protected、默认、Private),因为它的地位就是一个成员
  2. 作用域:和外部类的其他成员一样,为整个类体

比如前面案例,**在外部类的成员方法中创建成员内部类对象,再调用方法. **

  1. 成**员内部类—访问---->外部类(比如:属性)[访问方式:直接访问] **
  2. 外部类—访问------>内部类(说明) 访问方式:创建对象,再访问
  3. 外部其他类—访问---->成员内部类
//外部其他类访问成员内部类两种方式
//1.第一种方法	outter.new Inner() 相当于把new Inner() 当作outter的成员
Outter.Inner inner = outter.new Inner();
//2.在外部类中编写一个方法可以返回 Inner 对象

class Outer{ //外部类 private int n1 = 10;

    public String name = "张三”;

    class Innter{

        public void say(){

            System.out.println("Outer01 的n1 =" + n1 
                               + "outer01 的 name "+  name );

        }
    }
    //第二种方式:在外部类中编写一个方法可以返回 Inner 对象
    public Inner getInner() {
        return new Inner();
    }
    
}

public class Test {
    public static void main(String[] args) {
        Outter outter = new Outter();
        //第一种方式
        Outter.Inner inner = outter.new Inner();
    }
}

  1. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
4. 静态内部类

【说明】:静态内部类是定义在外部类的成员位置,井且有static修饰

  1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
  2. 可以添加任意访问修饰符(public. protected、默认、private),因为它的地位就是一个成员。
  3. 作用域**:同其他的成员,为整个类体**
class Outer02{//外部类 private int n1 = 10;

    private static String name = “张三”;
    static class Inner02{

        public void say(){

        System.out.println(name);

        //不能直接访问外部类的非静态成员 
        //System.out.println(n1);
        }
    }

    public void show(){
    //外部类使用内部类 new Inner020.say();
    }
}
  1. 静态内部类—访问---->外部类(比如:静态属性)[访问方式:直接访问所有静态成员]
  2. 外部类—访问----->静态内部类访问方式:创建对象,再访问
  3. 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问

枚举和注解

枚举

【说明】:

  1. 枚举对应英文(enumeration, 简写 enum)
  2. 枚举是一组常量的集合。
  3. 可以这里理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象
自定义实现枚举
  1. 构造器私有化
  2. 本类内部创建一组对象
  3. 对外暴露对象(通过为对象添加public final static修饰符)
  4. 可以提供 get()方法,但是不要提供 set()方法
package com.yk.enum_;

/**
 * @author YK
 * @version 1.0
 */
public class Enumeration01 {
    public static void main(String[] args) {
        System.out.println(Season.SPRING);
    }
}

class Season {

    private String name;
    private String weather;
    //2.本类创建一组公开常量对象 3.对外暴露对象(通过对象添加 public static final 修饰符)
    public static final Season SPRING = new Season("春天","温暖");
    public static final Season SUMMER = new Season("夏天","炎热");
    public static final Season AUTUMN = new Season("秋天","凉爽");
    public static final Season WINTER = new Season("冬天","寒冷");

    //1.构造器私有化
    private Season(String name, String weather) {
        this.weather = weather;
        this.name = name;
    }
    //4.可以提供get()方法 但是不要提高set()方法
    public String getName() {
        return name;
    }

    public String getWeather() {
        return weather;
    }

    @Override
    public String toString() {
        return "Season{" +
                "name='" + name + '\'' +
                ", weather='" + weather + '\'' +
                '}';
    }
}
关键字 enum 实现枚举
  1. 使用关键字 enum 替代 class
  2. public static final Season SPRING = new Season(“春天”,“温暖”)直接使用 SPRING(“春天”,“温暖”)

解读:常量名(实参列表)

  1. 如果有多个常量(对象),使用** , **号间隔即可
  2. 使用enum实现枚举, 要求将定义常量对象写在最前面
package com.yk.enum_;

/**
 * @author YK
 * @version 1.0
 */
public class Enumeration01 {
    public static void main(String[] args) {
        System.out.println(Season.SPRING);
    }
}
//1. class ==> enum
enum Season {
    //2.public static final Season SPRING = new Season("春天","温暖")
    //直接使用 SPRING("春天","温暖") 	枚举常量对象写在最前面
    SPRING("春天" ,"温暖") ,
    SUMMER("夏天","炎热"),
    AUTUMN("秋天","凉爽"),
    WINTER("冬天","寒冷");

    private String name;
    private String weather;


    //构造器私有化
    private Season(String name, String weather) {
        this.weather = weather;
        this.name = name;
    }
    //可以提供get()方法 但是不要提高set()方法
    public String getName() {
        return name;
    }

    public String getWeather() {
        return weather;
    }

    @Override
    public String toString() {
        return "Season{" +
                "name='" + name + '\'' +
                ", weather='" + weather + '\'' +
                '}';
    }
}

enum 实现枚举类注意事项
  1. 当我们使用enum 关键字实现一个枚举类时,默认会继承Enum类,并且此时该枚举类是一个final修饰的类[如何证明:使用javap 工具反编译]
  2. 传统的 public static final Season SPRING = new Season(“春天”,"温暖 ");简化成 SPRING("春天””温暖”),这里必须知道,它调用的是哪个构造器,通过实参可以看出来.
  3. 如果使用无参构造器创建枚举对象,则实参列表和 () 都可以省略
  4. 当有多个枚举对象时,使用,间隔,最后有一个分号结尾
  5. 枚举对象必须放在枚举类的行首
  6. 枚举类中构造器是私有的
  7. enum 实现的枚举类虽然不能继承其他类了(因为存在隐式继承,继承Enum类),但是可以实现接口
enum 常用方法
/*
    toString:Enum类己经重写过了,返回的是当前对象名,子类可以重写该方法,
    用于返回对象的属性信息 
    name:返回当前对象名(常量名),子类中不能重写 
    ordinal:返回当前对象的位置号,默认从0开始
    values:返回当前枚举类中所有的常量,返回的是 enum[] 类型
    valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常!
    compareTo:比较两个枚举常量,比较的就是编号!返回的是编号差值
*/

package com.yk.enum_;

/**
 * @author YK
 * @version 1.0
 * enum 常用方法
 */
public class Enumeration01 {
    public static void main(String[] args) {
        //toString: Season{name='春天', weather='温暖'}
        System.out.println(Season.SPRING);
        //values: 返回当前枚举类中所有的常量,返回的是 enum[] 类型
        Season[] seasons = Season.values();
        //增强for循环 从数组seasons中依次取出元素
        for (Season season : seasons) {
            System.out.println(season);
        }
        //name: 返回当前对象名(常量名),子类中不能重写
        System.out.println(Season.SPRING.name());   //SPRING
        //ordinal:返回当前对象的位置号,默认从0开始
        System.out.println(Season.SPRING.ordinal()); // 0
        //valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常!
        Season season1 = Season.valueOf("AUTUMN");
        System.out.println(season1);
        //compareTo:比较两个枚举常量,比较的就是编号!
        System.out.println(Season.SPRING.compareTo(Season.SUMMER)); //-1
    }
}

enum Season {


    SPRING("春天" ,"温暖") ,
    SUMMER("夏天","炎热"),
    AUTUMN("秋天","凉爽"),
    WINTER("冬天","寒冷");


    private String name;
    private String weather;


    //构造器私有化
    private Season(String name, String weather) {
        this.weather = weather;
        this.name = name;
    }
    //可以提供get()方法 但是不要提高set()方法
    public String getName() {
        return name;
    }

    public String getWeather() {
        return weather;
    }

    @Override
    public String toString() {
        return "Season{" +
                "name='" + name + '\'' +
                ", weather='" + weather + '\'' +
                '}';
    }
}

注解(Annotation)

【说明】:

  1. 注解(Annotation)也被称为元数据(Metadata),用于修饰解释包、 类、方法、属性、构造器、局部变量等数据信息。
  2. 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
  3. 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替java EE旧版中所遗留的繁冗代码和XML配置等。
1. @Override 使用说明
  • @Override 表示指定重写父类的方法(从编译层面验证),如果父类没有fly方法,则会报错
  • 如果不写@Override 注解,而父类仍有 public void fly(){},仍然构成重写
  • @Override 只能修饰方法,不能修饰其它类,包,属性等等
  • 查看@Override注解源码为 @Target(ElementType.METHOD),说明只能修饰方法
  • @Target 是修饰注解的注解,称为元注解
2. @Deprecated 使用说明
  • 表示某个元素(类或者方法)已过时
  • 过时并不代表不能用,只是不建议用

【说明】:

  1. 用于表示某个程序元素(类,方法等)己过时
  2. 可以修饰方法,类,字段,包,参数等等
  3. @Target(value={CONSTRUCTOR, FIELD, LOCAL VARIABLE, METHOD. PACKAGE, PARAMETER, TYPE})
  4. @Deprecated 的作用可以做到新旧版本的兼容和过渡
3. @SuppresWarnings :抑制警告
元注解
  1. **@Retention ** //指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME
  2. @Target // 指定注解可以在哪些地方使用
  3. @Documented //指定该注解是否会在javadoc体现
  4. **@Inherited **//子类会继承父类注解

你可能感兴趣的:(javase编程,intellij-idea,java)