自学java面向实习从零开始路线

自学java面向实习从零开始路线

  • Java
    • 目录
      • Java基础
        • Java开发底层
        • 感慨语录
          • bgm
          • 1
          • 2
        • 文档注释
        • 快速多行注释
        • 停止运行程序
        • 数据类型
          • 浮点型
            • float&&double
          • 字符型
            • char
          • 基本数据类型和String类型转换
            • 基本数据类型->String类型
            • String类型基本数据类型->基本数据类型
        • 随机数生成
        • 运算符
          • 算术运算符
            • ++(自增)
            • %(取余)
            • 易错点
          • 逻辑运算符
            • &与&&(与)
            • | 与 ||(或)
            • !(取反)
            • ^(逻辑异或)
            • 易错点
          • 符合赋值运算符(自动类型转换)
          • 三元运算符
        • 标识符的命名规则和规范
        • 键盘输入与字符串比较
        • 进制转换与位运算
          • 进制
          • 原码、反码、补码
          • 位运算
        • 控制循环
          • switch
          • for
          • do...while
          • 退出循环
            • break
            • continue
            • return
          • 思考题
        • 数组
          • 一维数组
            • 数组创建
            • 数组赋值
            • 数组扩容
            • 数组排序
            • 数组查找
          • 二维数组
            • 数组创建
            • 练习题—杨辉三角
        • 面向对象编程(初级)
          • 方法
            • 方法调用
            • 方法传参机制
            • 方法重载
            • 可变参数
            • 构造器
            • this
          • 变量作用域
            • 基本使用
            • 细节
        • javap反编译
        • 递归
          • 斐波那契数列
          • 老鼠出迷宫
          • 汉诺塔
          • 八皇后
        • IDEA快捷键
          • 删除光标所在行
          • 复制光标所在行到下一行
          • 补全代码
          • 添加注释
          • 导入该行需要的类
          • 快速格式化美化代码
          • 生成构造器
          • 定位方法
          • 自动分配变量名
          • 模板
        • 面向对象编程(中级)
            • 包基础知识
            • 包的使用
          • 访问修饰符
          • 面向对象编程三大特征
          • 封装
            • 封装基础
            • 封装与构造器
          • 继承
            • 继承基础
            • 细节
            • 变量访问规则
            • super关键字
            • 方法覆盖(重写)
          • 多态
            • 多态的具体体现
            • 细节
            • 动态绑定机制
            • instanceof
            • 多态的应用
          • 查看JDK原码
          • Object类
            • equals方法
            • hashCode方法
            • toString方法
            • finalize方法
          • 断点调试
        • 面向对象编程(高级)
          • 类变量
          • 类方法
          • main方法
          • 代码块
          • 单例设计模式
            • 饿汉式
            • 懒汉式
            • 饿汉式VS懒汉式
          • final
          • 抽象类
            • 基础
            • 应用:模板设计模式
          • 接口
            • 概念
            • 实现接口vs继承类
            • 接口多态特性
            • 思考题
          • 内部类
            • 局部内部类
            • 匿名内部类
            • 成员内部类
            • 静态内部类

Java

目录

Java基础

Java开发底层

JDK(Java Development Kit)Java开发工具包

JDK = JRE + Java的开发工具(java, javac, javadoc, javap等)

JRE(Java Runtime Environment)Java运行环境

JRE = JVM + Java核心库类

JVM(Java Virtual Machine)Java虚拟机

感慨语录

bgm

《渡月桥思君》

1

年少总将被不可得之物困其一生

2

不要指望谁陪你一辈子,没光的时候连影子都不陪你

文档注释

  • 基本格式

代码开头写入

/**
    *@author   LX
    *@version  1.0.0
*/ 
javadoc -d 产生注释文档到哪个文件夹的路径 -Java标签 -Java标签 java文件名
  • 例子
javadoc -d C:\Users\god\Desktop\java学习代码 -author -version hello.java

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xeB20HGG-1668777222956)(C:\Users\god\AppData\Roaming\Typora\typora-user-images\image-20220927111012658.png)]

快速多行注释

有时候我们想讲多行代码转化为注释,在每一行的行首输入’//'太过麻烦,我们可以讲这几行框选出来,输入ctrl+/,便可快速实现多行注释(多行代码分行注释)

// System.out.println(5.12e2);
// System.out.println(5.12e-2);
// System.out.println(5.12E-2);
// double num1=1.112233445566778899;
// System.out.println(num1);

输入ctrl+Shift+/,便可快速实现多行注释(多行代码整体注释)

/*
        //类名.类变量名访问
        System.out.println(A.name);
        A a = new A();
        //对象名.类变量名访问
        System.out.println(a.name);
*/

停止运行程序

输入ctrl+c

数据类型

浮点型
float&&double

java 的浮点型常量默认为double类型,声明float常量,须后加‘f’或‘F’

double	num1 = 1.1314;
float	num2 = 1.1314f;

float 4个字节

double 8个字节

所以

float	num1 = 1.1314;					//错误的,因为不加‘f’或‘F’说明是默认的double类型的,多字节转化为少字节可能会造成精度丢失,编译器会报错
double	num2 = 1.1314f;					//正确的,少字节转化为多字节
字符型
char

本质上char是一个整数,默认输出是unicode码对应的字符,我们可以简单的通过强制类型转换将字符回缩为数字

public class char01{
    public static void main(String[] args){
        char a = '李';
        System.out.println("李的ASCII码为"+(int)a);     //李的ASCII码为26446
    } 
}
基本数据类型和String类型转换
基本数据类型->String类型

语法:将基本类型的值+ “” 即可

int n1 = 100;
float f1 = 1.01f;
double d1 = 1.01;
boolean b1 = true;
String s1 = n1 + "";
String s2 = f1 + "";
String s3 = d1 + "";
String s4 = b1 + "";
String类型基本数据类型->基本数据类型

语法:使用基本数据类型对应的包装类的相应方法,得到基本数据类型

String s1 = "123";
int num1 = Integer.parseInt(s1);
double num2 = Double.parseDouble(s1);
float num3 = Float.parseFloat(s1);
boolean num4 = Boolean.parseBoolean(s1);

字符串 ->char

从字符串的第一个字符得到char

String s1 = "123";
System.out.println(s1.charAt(0));

随机数生成

Math.random() 生成随机数[0,1)

for(int i = 0; i < 20; i++){
        System.out.println(Math.random());         //生成随机数[0,1)
}

for(int i = 0; i < 20; i++){
        System.out.println((int)(Math.random()*100)+1);   //生成随机数[1,100]
}

运算符

算术运算符
++(自增)

i++ 先赋值后自增

++i 先自增后赋值

int j = 8;
int	k = ++j;
System.out.println(k);
System.out.println(j);
//运行:k = 9; j = 9
等价于j = j + 1; k = j
int j = 8;
int	k = j++;
System.out.println(k);
System.out.println(j);
//运行:k = 8; j = 9
等价于k = j; j = j + 1
%(取余)

a % b=a - (int)a / b * b

int a = 10 % 2;		  //10 % 2 = 10 - 10 / 2 * 2 = 0
int b = -10.5 % 3;	  //-10.5 % 3 = -10.5 - (int)(-10.5)/3*3 = -10.5 - (-10) /3* 3 = -1.5
易错点
int j = 1;
j = j++;
System.out.println(j);
//运行:j = 1
规则使用临时变量temp:(1)temp = j;(2)j = j + 1;(3)j = temp;
int j = 1;
j = ++j;
System.out.println(j);
//运行:j = 2
规则使用临时变量temp:(1)j = j + 1;(2)temp = j;(3)j = temp;
逻辑运算符
&与&&(与)

a & b 逻辑与 a,b全真结果才为真 若a为假,仍需判断b,结果虽然b是真是假都为假

a && b 短路与 a,b全真结果才为真 若a为假,跳过判断b,结果直接为假

短路操作效率高,实际开发基本都用短路操作

int a = 4;
int b = 9;
if(a < 1 & ++b < 50){
System.out.println("ok");
}
System.out.println("b = " + b);
//运行:b = 10
int a = 4;
int b = 9;
if(a < 1 && ++b < 50){
System.out.println("ok");
}
System.out.println("b = " + b);
//运行:b = 9
| 与 ||(或)

a | b 逻辑或 a,b有真结果就为真 若a为真,仍需判断b,结果虽然b是真是假都为真

a || b 短路或 a,b有真结果就为真 若a为真,跳过判断b,结果直接为真

短路操作效率高,实际开发基本都用短路操作

例子参见&与&&的java实例区别

!(取反)

操作取反,T->F,F->T

System.out.println(100>99);
System.out.println(!(100>99));
^(逻辑异或)

a^b a 与 b不同真假时,结果才为true,否则为false

System.out.println((2>1)^(4>3));
//运行:false
易错点
public class test03{
    public static void main(String[] args){
        boolean x = false;
        boolean y = true;
        System.out.println(x = true);
        System.out.println(y = false);
    }
}
//运行:true
//	   false
赋值是真还是假,结果也就是真还是假

小试牛刀

public class test03{
    public static void main(String[] args){
        boolean x = true;
        boolean y = false;
        short z = 46;
        if((z++ == 46)&&(y = true))
        z++;
        if((x = false)||(++z == 49))
        z++;
        System.out.println("z = "+ z);
    }
}
//运行:z = 50
符合赋值运算符(自动类型转换)

复合赋值运算符会自动进行类型转换

byte b = 2;
b += 3;
b++;
//运行:不报错
b += 3 实际上等价于 b = (byte)(b + 3;
否则若b = b + 3;右边是byte类型与int类型相加实际上会自动类型转换为int类型,此时再赋给byte类型的b就会不合适,因为多字节转化为少字节可能会造成精度丢失,编译器会报错。b++的自动类型转换一致
三元运算符

条件表达式 ? 表达式1 :表达式2

public class ternaryopertar{
    public static void main(String[] args){
        int a = 10;
        int b = 100;
        int result = a > b ? a++ : b--;
        System.out.println(result);
    }
}
//运行:100
b--先将b赋给result,后进行b-1

标识符的命名规则和规范

只是更加专业

  1. 包名:多单词组成时,所有单词都小写:xxxyyyzzz,比如:com.hsp.crm
  2. 类名、接口名:多单词组成时,所有单词首字母大写【大驼峰】:XxxYyyZzz,比如:TankShotGame
  3. 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始首字母大写【小驼峰】:xxxYyyZzz,比如:tankShotGame
  4. 常量名:所有字母都大写,多单词时每个字母用下划线连接:XXX_YYY_ZZZ,比如:定义一个所得税率 TAX_RATE

键盘输入与字符串比较

使用扫描器Scanner

.equals()方法比较字符串

import java.util.Scanner;       //表示把java.util包下的Scanner类导入
public class Input{
    public static void main(String[] args){
        // 演示接受用户的输入
        // 步骤
        // Scanner类 表示 简单文本扫描器,在java.util包下
        // 1.引入/导入Scanner类所在的包
        // 2.创建Scanner对象
        Scanner myScanner = new Scanner(System.in);
        //3.接受用户的输入,使用相关的方法
        System.out.println("输入代号");
        char code = myScanner.next().charAt(0);     //接受用户输入字符
        System.out.println("输入姓名");
        String name = myScanner.next();      		//接受用户输入字符串
        System.out.println("输入年龄");
        int age = myScanner.nextInt();       		//接受用户输入int
        System.out.println("输入期待月薪");
        double sal = myScanner.nextDouble(); 		//接受用户输入double
        System.out.println("求职者信息如下");
        System.out.println("代号 = "+ code +"姓名 = "+ name +" 年龄 = "+ age +" 期待月薪 = "+ sal);
        if(name.equals("老板")){                     //字符串比较
         System.out.println("老板好!");   
        }
            
    }
}

进制转换与位运算

进制

二进制 以0b或0B开头表示

八进制 以数字0开头表示

十进制

十六进制 以0x或0X开头表示

int n1 = 0b1010;    //二进制
int n2 = 01010;		//八进制
int n3 = 1010;		//十进制
int n4 = 0x1010;	//十六进制
原码、反码、补码
  1. 有符号的二进制,最高位为符号位:0表示正数,1表示负数(记忆方法:0绕着中心旋转90°还是0是正数,1绕着中心旋转90°是变成—是负数)
  2. 正数的原码,反码,补码都一样(三码合一)
  3. 负数的反码=它的原码符号位不变,其他位取反
  4. 负数的补码=它的反码+1
  5. java没有无符号数,换言之,java中的数都是有符号的
  6. 在计算机运算的时候,都是以补码的方式来运算
  7. 当我们看运算结果的时候,要看他的原码
位运算

补码进行运算,最后返回原码

按位与&: 两位全为1,结果为1,否则为0

按位或|: 两位有一个为1,结果为1,否则为0

按位异或^: 两位一个为0,一个为1,结果为1,否则为0

按位取反~: 0->1,1->0

System.out.println(2&3);
System.out.println(~-2);
System.out.println(~2);
//运行: 2
//		1
//     -3

位运算符

  1. 算术右移>>:低位溢出,符号位不变,并用符号位补溢出的高位
  2. 算数左移<<:符号位不变,低位补0
  3. 逻辑右移(无符号右移)>>>:低位溢出,高位补0
  4. 特别说明:没有<<<符号

控制循环

switch
public class SwitchDetail{
    public static void main(String[] args){
        char c = 'a';
       //细节1:表达式数据类型,应和case后的数据类型一致,或者是可以自动
       //转成可以相互比较的类型,比如下面代码中输入的是字符,而常量是int
       //细节2:case子句的值只能是常量或常量表达式,而不能是变量
        switch(c){
            case 'a' :
                System.out.println("ok1");
                break;
            case 11 :
                System.out.println("ok2");
                break;
            default :
                System.out.println("No");
        }
    }
}
//运行:ok1
public class SwitchDetail{
    public static void main(String[] args){
        char c = 'a';
        switch(c){
            case 'a' :
                System.out.println("ok1");
            case 11 :
                System.out.println("ok2");
                break;
            default :
                System.out.println("No");
        }
    }
}
//运行:ok1
//	   ok2
第一个case满足后没有break,所以忽略第二个case条件,直接运行第二个case的语句,此时看到break而跳出switch
for
for(int i = 1; i < 10; i++){
    System.out.println("Yeah");
}
System.out.println(i);
//运行:报错
当i在for循环中初始化,那么i的作用域就只在for循环里
do…while

不管while是否满足,首先先执行一次do语句,再判断while条件是否满足

int i = 1;
do{
    System.out.println(i);
    i++;
}while(i <= 10);          //while后面有分号
退出循环
break

直接退出,while for switch循环,执行下面语句

continue

只退出当前判断,依旧执行循环

return

退出方法用return;

思考题

打印输出一个空心金字塔

思路

1.先简单打印这样的

*
**
***
****
*****
这种可以看作半金字塔

2.进阶:打印如下金字塔

    *
   *** 
  *****   
 *******     
*********
这种金字塔只需要再多打印每行前面的空格数即可

3.再进阶:打印如下金字塔

     *
    * *
   *   *
  *     *
 *       *
这种金字塔只需要再多加一个if(j == 0||j == 2*i-1)即可只打印每行的第一个和最后一个
空出的部分用else打印空格

4.完成:打印如下金字塔

将if判断多加一个或条件即可

//     *
//    * *
//   *   *
//  *     *
// *********
import java.util.Scanner;
public class Star{
    public static void main(String[] args){
        Scanner myScanner = new Scanner(System.in);
        System.out.println("请输入层数来构建属于你的空心金字塔吧!");
        int num = myScanner.nextInt();
        int i;  //层数变量
        int j;  //每层个数
        for(i = 0; i <= num; i++){
            for(j = 0; j <= num-i; j++){               
                System.out.print(" ");
            }

            for(j = 0; j <= 2*i-1; j++){
                if(j == 0||j == 2*i-1||i == num){        //i == num确保了金字塔最后一层
                System.out.print("*");
                }else{
                    System.out.print(" ");
                }
            }
            System.out.println("");
        }

    }
}

数组

一维数组
数组创建
  • 第一种动态分配方式
double Scores[] = new double[5];
  • 第二种动态分配方式
double Scores[];                      //先声明数组,再分配空间
Scores = new double[5];
  • 第三种静态初始化方式
double Scores[] = {2, 3.1, 4, 5, 1};
数组赋值

数组创建后,如果没有赋值,有默认值:int 0 ; short 0 ;byte 0 ;float 0.0; double 0.0;char \u0000; boolean false , String null …

  • 数组在默认情况下是引用传递,赋的值是地址,
int arr1[] = {1,2,3};
int arr2[] = arr1;
arr2[0] = 10;
//此时arr1[0] 也变成了10,可以看出是引用传递

如果要数组拷贝,而不是传递地址,需要先建立数组地址

int arr1[] = {1,2,3};
int arr2[] = new int[arr1.length];            //建立数组空间,此时初始值都是0
for(int i = 0; i < arr1.length; i++){
    arr2[i] = arr1[i];
}
数组扩容
  1. 简单版:创建新链表,赋值后再将新链表地址赋给旧链表

    import java.util.Scanner;
    public class Array01{
        public static void main(String[] args){
        Scanner myScanner = new Scanner(System.in);
        int arr[] = {1, 2, 3};
        System.out.println("初始数组的值为:");
        for(int i = 0; i < arr.length; i++){
            System.out.print(arr[i]+" ");
        }
        do{
        System.out.println("\n请输入要插入数组的值:");
        int addNum = myScanner.nextInt();
        int arrNew[] = new int[arr.length + 1];
        for(int i = 0; i < arr.length; i++){
            arrNew[i] = arr[i];
        }  
        arrNew[arrNew.length - 1] = addNum;
        arr = arrNew;
        System.out.println("现在数组的值为:");
        for(int i = 0; i < arr.length; i++){
            System.out.print(arr[i]+" ");
        }
        System.out.println("还想插入新数据吗,回复y/n");
        char key = myScanner.next().charAt(0);
        if(key == 'n'){
            break;
        }
        }while(true);
    } 
    } 
    
  2. 使用数据结构链表实现动态数组

数组排序

冒泡排序

就像一个气泡从水底上升到空气中,每次比较相邻的两个大小,最终会将最大值放到最后一位,第二轮将第二大值放在倒数第二位,依次类推

public class Test174{
    public static void main(String[] args){
      int arr[] = {1,5,2,4,3};
      int temp = 0;                                     //辅助交换变量
      for(int i = 0; i < arr.length-1; i++){
        for(int j = 0; j < arr.length -1-i; j++){
            if(arr [j] > arr[j+1]){
                temp = arr[j+1];
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
      }
       for(int i = 0; i < arr.length; i++){
        System.out.print(arr[i]+" ");
    }
    }
}
数组查找
  1. 顺序查找

    import java.util.Scanner;
    public class Test175{
        public static void main(String[] args){
        Scanner myScanner = new Scanner(System.in);
        String names[] = {"王者荣耀","穿越火线","天天酷跑","吃鸡","生死狙击"};
        System.out.println("请输入你要查找的游戏名,如果找到稍后将返回数据库中编号");
        String findName = myScanner.next();
        int index = -1;               //一个编程技巧:通过标识符判断是否查找成功
        for(int i = 0; i < names.length; i++){
            if(findName.equals(names[i])){           //字符串匹配比较
               System.out.println("恭喜您找到了!\n数据库编号为"+i);
               index = i;
               break; 
            }
        }
        if(index == -1){             //通过标识符
            System.out.println("查无此游戏");
        }
    } 
    }
    
  2. 二分查找

二维数组
数组创建
  1. 静态创建

    public class Test176{
        public static void main(String[] args){
            int arr[][] = { {1,0,0,0}, {2,0,0,0}, {3,0,0}, {4,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("");
            }
            System.out.println(arr[2][3]);        //数组越界:arr[2]只是一个有三个空间的一维数组
        }
    }
    
  2. 动态创建

    public class Test179{
        public static void main(String[] args){
            int arr[][] = new int[3][];
            for(int i = 0; i < arr.length; i++){
                arr[i] = new int[i+1];         //给每一个一维数组开空间,可以自己任意划定空间
            }     
        }
    }
    
练习题—杨辉三角
/*杨辉三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
*/
import java.util.Scanner;
public class Test182{
    public static void main(String[] args){
        Scanner myScanner = new Scanner(System.in);
        System.out.println("请输入要生成的杨辉三角行数");
        int num = myScanner.nextInt();
        int yangHui[][] = new int[num][];
        System.out.println("为您生成的杨辉三角图形为");
        for(int i = 0; i < yangHui.length; i++){
            yangHui[i] = new int[i+1];
            for(int j = 0; j < yangHui[i].length; j++){
                if(j == 0 || j == yangHui[i].length-1){
                    yangHui[i][j] = 1;
                }else{
                    yangHui[i][j] = yangHui[i-1][j]+yangHui[i-1][j-1];    //规律
                }
            }
        }
        for(int i = 0; i < yangHui.length; i++){
            for(int j = 0; j < yangHui[i].length; j++){
                System.out.print(yangHui[i][j]+" ");
            }
            System.out.println("");
    }
}
}

面向对象编程(初级)

创建类,每个java程序只能有一个public类

public class Test203{
    public static void main(String[] args){
        person p1 = new person();              //创建对象
        p1.num = 1;
        p1.OutPut();
        int i = p1.sum(1,99);
        System.out.println(p1.sum(1,99));
        System.out.println(i);
    }
}

class person{                             //类
    int num;                                 
    public void OutPut(){                 //创建方法
        System.out.println("学java");
    }
    public int sum(int x, int y){         //创建有输入参数的方法
        int sunNum = x + y;
        return sunNum;
    }

}
方法
方法调用

当程序执行到调用方法时,程序会在main主栈额外开一个方法栈进行执行

同一个类中的方法调用:直接调用即可

public class Test209{
    public static void main(String[] args){
        A a = new A();
        a.sayOk();
    }
}

class A{
    
    public void print1(int n){
        System.out.println("调用print1 "+n);
    }

    public void sayOk(){
        print1(10);     						//同一个类中的方法可以直接调用
        System.out.println("继续执行sayOk()");
    }
}
方法传参机制
  • 基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参
public class Test209{
    public static void main(String[] args){
        int a = 10;
        int b = 20;
        System.out.println("调用置换方法前a = " + a + " b = " + b);
        AA op = new AA();
        op.swap(a,b);	//当程序执行到调用方法时,程序会在main主栈额外开一个方法栈,然后对传入的参数进行变换,并不会影响到传入的变量本身的值
        System.out.println("调用置换方法后a = " + a + " b = " + b);
    }
}

class AA{

    public void swap(int a, int b){
        int temp = a;
        a = b;
        b = temp;
    }
}
  • 引用数据类型,传递的是地址,形参的值改变会影响实参,但是形参的地址改变却不影响实参
public class Test213{
    public static void main(String[] args){
        person p = new person();
        p.age = 20;
        B b = new B();
        b.change1(p);
        System.out.println(p.age); //change1改变的是值,依旧是同一个地址,所以p.age = 9999
        b.change2(p);
        System.out.println(p.age); //change2改变的是地址,所以不会影响到main栈p指向的空间所以依旧是p.age = 9999
    }
}

class person{
    String name;
    int age;
}

class B{

    public void change1(person p){
        p.age = 9999;
    }
    public void change2(person p){
        p = null;
    }
}
方法重载
  1. java中允许同一个类中,多个同名方法存在,但是要求形参列表不一致,不必关心方法的返回值是否一样
  2. 好处:
    • 减轻了起名的麻烦
    • 减轻了记名的麻烦
可变参数

java允许将同一个类中,多个同名同功能但参数个数不同的方法,封装成一个方法

public class Test234{
    public static void main(String[] args){
    AA a = new AA();
    a.sum();
    a.sum(1,2,3,4,5);
    }
}

class AA{

    //int...表示接受的是可变参数,类型是int,个数为0到任意多个
    //使用可变参数时,可以当成数组来使用 即参数名nums可当做数组
    public int sum(int... nums){
        int res = 0;
        for(int i = 0; i < nums.length; i++){
            res += nums[i];
        }
        System.out.println("接受参数数量为 " + nums.length);
        System.out.println("计算结果为 " + res);
        return res;    
    }

}

//运行: 接受参数数量为 0
//      计算结果为 0
//      接受参数数量为5
//      计算结果为 15

细节:

  • 可变参数的实参可以是数组
  • 可变参数的本质就是数组
  • 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
  • 一个形参列表只能出现一个可变参数
public class Test234{
    public static void main(String[] args){
    AA a = new AA();
    int[] arr1 = {1, 2, 3};
    int[] arr2 = {10, 20}; 
    a.sum(arr1);             //正确:会将数组中各个值相加
    a.sum(arr1, arr2);       //错误:实参本质应该是(int[]),这里输入(int[],int[])
    }
}

class AA{

    public int sum(int... nums){
        int res = 0;
        for(int i = 0; i < nums.length; i++){
            res += nums[i];
        }
        System.out.println("接受参数数量为 " + nums.length);
        System.out.println("计算结果为 " + res);
        return res;
    
    }

    public void f1(String str, double... nums){   //可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后

    }
    public void f1(double... str, double... nums){ //错误:一个形参列表只能出现一个可变参数
    }
    
}
构造器
  1. 构造方法又叫构造器,是类的一种特殊的方法,它的主要作用是完成对新对象的初始化
  2. 用法:
    • 构造器没有返回值,也不能写void
    • 构造器的名称必须和类名一样
public class Test239{
    public static void main(String[] args){
    AA a = new AA("李华", 21);
    System.out.println(a.name);
    System.out.println(a.age);
    }
}

class AA{
    String name;
    int age;

    public AA(String aName, int aAge){
        System.out.println("构造器被调用~~完成对象的属性初始化");
        name = aName;
        age = aAge;
    }


  1. 细节:
    • 一个类可以定义多个不同的构造器,即构造器重载
    • 构造器是完成对象的初始化,而不是创建对象
    • 在创建对象时,系统自动的调用该类的构造方法
    • 如果没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造方法)
    • 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器了,除非显示的定义一下,即:Dog(){}
public class Test239{
    public static void main(String[] args){
    AA a = new AA("李华", 21);
	//a.AA();                   //错误:在创建对象时,系统自动的调用该类的构造方法,而不能自己调用
    //AA a = new AA();  		//错误:一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器了         
    }
}

class Dog{
        // 如果没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造方法)
    	//可以通过javap指令 反编译查看到这个默认的构造方法
        // 默认构造器:
        // Dog(){
    
        // }
}

class AA{
    String name;
    int age;

    public AA(String aName, int aAge){
        name = aName;
        age = aAge;
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1wSylAzs-1668777222957)(C:\Users\god\AppData\Roaming\Typora\typora-user-images\image-20221023104103975.png)]

this

为了简化构造器的形式参数,使它和对象的属性一样,在方法中需要引入this来指定对象的属性

class AA{
    String name;
    int age;

    public AA(String name, int age){
        System.out.println("构造器被调用~~完成对象的属性初始化");
        this.name = name;    //this.name指的是对象的属性,name还是按照就近原则,是方法的形式参数
        this.age = age;		 //this.age指的是对象的属性,age还是按照就近原则,是方法的形式参数
    }
  • 细节
    1. this关键字可以用来访问本类的属性,方法,构造器
    2. this用来区分当前类的属性和局部变量
    3. 访问成员方法的语法:this.方法名(参数列表)
    4. 访问构造器语法:this(参数列表);注意只能在构造器中使用(即只能在构造器中访问另一个构造器,并且this(参数列表)必须放在第一条语句)
    5. this不能在类定义的外部使用,只能在类定义的方法中使用
public class Test250{
    public static void main(String[] args){
    AA a = new AA();
    a.f1();
}
    
class AA{
    String name;
    int age;
	public AA(){			//在无参构造器中访问到另一个构造器
        this("jack", 3);    //使用this在构造器中访问另一个构造器,且必须放在第一条 
        System.out.println("AA()构造器被调用~~完成对象的属性初始化");
    }
    
    public AA(String name, int age){
        System.out.println("AA(String name, int age)构造器被调用~~完成对象的属性初始化");
        this.name = name;
       	this.age = age;
    }
    public void f1(){
         String name = "lucy";
         System.out.println(name);  //作用域先判断就近原则,此时局部变量也有name,所以此处是lucy
         System.out.println(this.name); //this直接定位到类的属性name,所以此处是jack
    }
}
  • 构造器复用
public class Test260{
    public static void main(String[] args){
        Employee e = new Employee("工程师", 1000000, "li");
}
}

class Employee{
    String job;
    double sal;
    String name;
    char gender;
    int age;

    public Employee(String job, double sal, String name){
        this.job = job;
        this.sal = sal;
        this.name = name;
    }

    public Employee(String job, double sal, String name, char gender, int age){
        this(job, sal, name);  //构造器复用
        this.gender = gender;
        this.age = age;
    }
}
变量作用域
基本使用
  • 全局变量:也就是属性,作用域为整个类体,类中的成员方法可以使用属性
  • 局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中。局部变量一般指在成员方法中定义的变量,它的作用域就在方法中
  • 全局变量(属性)可以不赋值,直接使用,因为有默认值。局部变量必须赋值后才能使用,因为没有默认值
细节
  • 属性和局部变量可以重名,访问时遵循就近原则

  • 在同一个作用域中,比如在同一个成员方法中,两个局部变量不能重名

  • 属性生命周期比较长,伴随着对象的创立而创立,伴随着对象的消亡而消亡。局部变量生命周期比较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而消亡,即在一次方法调用过程中

public class Test237{
    public static void main(String[] args){
    AA a = new AA();
    a.say();        //调用方法时创建方法中name变量,执行完后自动销毁
    }
}

class AA{
    String name = "jack";
	
    public void say(){
        String name = "li";   //重名的属性和局部变量访问时遵循就近原则
        System.out.println("say()name = " + name);
    }

}
  • 全局变量(属性)可以被本类使用,也可以被其他类使用(通过对象调用)
public class Test238{
    public static void main(String[] args){
    AA a = new AA();
    BB b = new BB();
    a.say();
    b.say01();
    b.say02(a);
    }
}

class AA{
    String name = "jack";

    public void say(){
        String name = "li";
        System.out.println("say()name = " + name);
    }

}

class BB{

    public void say01(){
        AA a = new AA();
        System.out.println(a.name);  //跨类方法调用1
    }
    public void say02(AA p){
        System.out.println(p.name);  //跨类方法调用2
    }

}
  • 全局变量(属性)可以加修饰符,不加就是默认,局部变量不可以加修饰符

javap反编译

javap是JDK提供的一个命令行工具,javap能对给定的.class文件提供的字节代码进行反编译

  • 用法:javap 文件名.class
class Dog{
        // 如果没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造方法)
    	//可以通过javap指令 反编译查看到这个默认的构造方法
        // 默认构造器:
        // Dog(){
    
        // }
}


//输入 javap Dog.class
运行结果:
Compiled from "Test238.java" //Dog类所在的文件的主类名
class Dog {
Dog();
}

递归

斐波那契数列
import java.util.Scanner;
public class Test220{
    public static void main(String[] args){
        Scanner myScanner = new Scanner(System.in);
        AA a = new AA();
        System.out.println("斐波那契数列是形如:1,1,2,3,5,8,13的数字,请输入你想要生成的具有几位的斐波那契数列");
        int fibonacciNum = myScanner.nextInt();
        System.out.println("生成的斐波那契数列为");
        for(int i = 1; i <= fibonacciNum; i++)
        System.out.println(a.Fibonacci(i));
    }
}

class AA{

    public int Fibonacci(int n){
        return n >= 3 ? Fibonacci(n-1)+Fibonacci(n-2) : 1;
    }
}
老鼠出迷宫
import java.util.Scanner;
public class Test223{
    public static void main(String[] args){
        System.out.println("即将开始老鼠出迷宫游戏");
        Scanner myScanner = new Scanner(System.in);
        mouse a = new mouse();
        System.out.println("请输入您要生成的围墙行数");
        int xNum = myScanner.nextInt();
        System.out.println("请输入您要生成的围墙列数");
        int yNum = myScanner.nextInt();
        int[][] map = new int[xNum][yNum];
        for(int i = 0; i < map.length; i++){
            for(int j = 0; j < map[i].length; j++){
                if(i == 0 || i == map.length-1 || j == 0 || j == map[i].length-1)
                map[i][j] = 1;
            }
        }
        do{
        System.out.println("1表示围墙,0代表未堵住的路,2表示老鼠最终选择走的路,3表示老鼠走过但是走不通");
        System.out.println("====生成的初始围墙为=====");
        for(int i = 0; i < map.length; i++){
            for(int j = 0; j < map[i].length; j++){
                System.out.print(map[i][j]+" ");
            }
            System.out.println("");
        }
        System.out.println("还想插入新的围墙吗,回复y/n");
        char key = myScanner.next().charAt(0);
        if(key == 'n'){
            break;
        }
        System.out.println("请输入您要生成的围墙所在行数");
        xNum = myScanner.nextInt();
        System.out.println("请输入您要生成的围墙所在列数");
        yNum = myScanner.nextInt();
        map[xNum-1][yNum-1] = 1;
        }while(true);
        
        //定义老鼠出发点位
        System.out.println("请输入您老想要鼠出发所在行数");
        xNum = myScanner.nextInt();
        System.out.println("请输入您老想要鼠出发所在列数");
        yNum = myScanner.nextInt();
        
        boolean gameResult = a.findWay(map,xNum-1,yNum-1);
        System.out.println("====突围后的迷宫图为=====");
        for(int i = 0; i < map.length; i++){
            for(int j = 0; j < map[i].length; j++){
                System.out.print(map[i][j]+" ");
            }
            System.out.println("");
        }
        if(gameResult){
            System.out.println("老鼠出逃成功!^^");
        }else{
            System.out.println("老鼠出逃失败");
        }
    }
}

class mouse{

    public boolean findWay(int[][] map, int x ,int y){
        int xNum = map.length;
        int yNum = map[xNum-1].length;
        //找路策略:0表示可以走,1表示围墙走不了,2表示可以走,并且走过的路,3表示走过但是走不通
        if(map[xNum-2][yNum-2] == 2){
            return true;
        }else{
            if(map[x][y] == 0){
                map[x][y] = 2;
            }else{
                return false;
            }            
            //按照先判断下面是否可以走->右->上->左
            if(findWay(map,x+1,y)){
                return true;
            }else if(findWay(map,x,y+1)){
                return true;
            }else if(findWay(map,x-1,y)){
                return true;
            }else if(findWay(map,x,y-1)){
                return true;
            }else{
                map[x][y] = 3;
                return false;
            }

        }
    }

}
汉诺塔
import java.util.Scanner;
public class Test226{
    public static void main(String[] args){
        Scanner myScanner = new Scanner(System.in);
        System.out.println("===请输入您要生成的a盘的基础盘数====");
        int num = myScanner.nextInt();
        tower t = new tower();
        System.out.println("===盘间的移动次序为====");
        int moveNum = t.move(num, 'a', 'b', 'c');
        System.out.println("总共移动次数为:" + moveNum);
    }
}

class tower{
    int moveNum = 0;

    public int move(int num, char a, char b, char c){  //将a所有盘借助b移动到c        
        if(num == 1){
            System.out.println(a + "->" + c);
            moveNum++;
        }else{
            move(num - 1 , a, c, b);          //将a最后一个盘之上的所有盘借助c移动到b
            System.out.println(a + "->" + c); //将a最后一个盘直接移动到c
            moveNum++;
            move(num - 1, b, a, c);           //将b盘之上的所有盘借助a移动到c
        }
        return moveNum;
    }

}
八皇后

难度太大,思考了三个小时,敲了116行代码最后宣布放弃,待继续补充基础知识后必将回来重新啃下这快骨头

//错误代码,仅代表我思考过了

public class Test227{
    public static void main(String[] args){
        int[][] map = new int[8][8];
        AA a = new AA();
        a.distribution(map,0,0);
        for(int i = 0; i < map.length; i++){
            for(int j = 0; j < map[i].length; j++){
                System.out.print(map[i][j]+" ");
            }
            System.out.println("");
        }

    }
}

class AA{
    int queenNum = 0;

    public void move(int[][] map, int x, int y){
        
        int yNum = y;
        for(int i = 1; yNum > 0; yNum--){
            if(x+i < 0 || y-i > 7 || x+i > 7 || y-i < 0){
            break;
            }
            map[x+i][y-i] = 1;
            i++;
            
        }
        int xNum = 7 - x;
        for(int i = 1; xNum > 0; xNum--){
            if(x+i < 0 || y+i > 7 || x+i > 7 || y+i < 0){
            break;
            }
            map[x+i][y+i] = 1;
            i++;
            
        }
        
        for(int i = 0; i < 8; i++){
            if(i != y){
            map[x][i] = 1;
            }
        }
        for(int i = 0; i < 8; i++){
            if(i != x){
            map[i][y] = 1;
            }
        } 

    }

    public void reduction(int[][] map, int x, int y){
        
       int yNum = y;
        for(int i = 1; yNum > 0; yNum--){
            if(x+i < 0 || y-i > 7 || x+i > 7 || y-i < 0){
            break;
            }
            map[x+i][y-i] = 0;
            i++;
        }
        int xNum = 7 - x;
        for(int i = 1; xNum > 0; xNum--){
            if(x+i < 0 || y+i > 7 || x+i > 7 || y+i < 0){
            break;
            }
            map[x+i][y+i] = 0;
            i++;
        }
        
        for(int i = 0; i < 8; i++){
            if(i != y){
            map[x][i] = 0;
            }
        }
        for(int i = 0; i < 8; i++){
            if(i != x){
            map[i][y] = 0;
            }
        } 

    }

    public boolean distribution(int[][] map, int x, int y){
        if(y < 0 || y > 7 || x < 0 || x > 7){
            return false;
        }        
        if(queenNum == 8){
            return true;
        }else {
            if(map[x][y] == 0){
                map[x][y] = 2;
                queenNum++;
                move(map, x, y);
            }else{
                return false;
            }

            if(distribution(map,x,y+1)){
                return true;
            }else if(distribution(map,x,y-1)){
                return true;
            }else if(distribution(map,x+1,y+2)){
                return true;
            }else if(distribution(map,x+1,y-2)){
                return true;
            }else{  
                map[x][y] = 0;  
                reduction(map, x, y);   
                return false;
            }

    }

}

IDEA快捷键

删除光标所在行

已经自定义为 ctrl + d

复制光标所在行到下一行

ctrl + alt + ↓

补全代码

alt + /

添加注释

ctrl + /

导入该行需要的类

alt + enter

快速格式化美化代码

ctrl + alt + l

生成构造器

alt + insert

或者鼠标右键选择generate,再选择constractor

按住ctrl不松手可以选择全部变量

定位方法

将光标放在一个方法上,输入 ctrl + B,可以自动定位到方法位置

自动分配变量名

末尾加上 .var 便可自动生成变量名

模板

file -> settings -> editor -> Live templates

查看有哪些模板快捷键/可以自己增加模板

面向对象编程(中级)

包基础知识
  • 包的作用

    1. 区分相同名字的类
    2. 当类很多的时候,可以很好的管理类
    3. 控制访问范围
  • 包的本质:实际上就是创建不同的文件夹保存类文件

  • 包的命名规则:只能包含数字,字母,下划线,小圆点,但不能用数字开头,不能是关键字或保留字

​ demo.class.execl1 //class是关键字

​ demo.12a.Test //12a数字开头

  • 包的命名规范:一般是小写字母加小圆点

​ com.公司名.项目名.业务模块名

​ 举例:com.sina,crm.user //用户模块

  • Java常用包

​ java.lang //lang包是基本包,默认引入,不需要再引入

​ java.util //util包是系统提供的工具包,工具类,比如Scanner

​ java.net //网络包,网络开发

​ java.awt //是做Java的页面开发,GUI

包的使用
//注意:
//建议:我们需要使用哪个类,就哪个类即可,不建议使用*导入
import java.util.Scanner;       //表示只引入java.util包下的Scanner类
import java.util.*;			   //表示将java.util包下的所有类都引入
访问修饰符
  1. 公开级别:用public修饰,对外公开
  2. 受保护级别:用protected修饰,对子类和同一个包中的类公开
  3. 默认级别:没有修饰符号,向同一个包的类公开
  4. 私有级别:用private修饰,只有类本身可以访问,不对外公开
访问级别 访问控制修饰符 同类 同包 子类 不同包
1 公开 public
2 受保护 protected X
3 默认 没有修饰符号 X X
4 私有 private X X X
  • 细节:修饰符可以修饰类中的属性,方法,类

    只有默认和public可以修饰类

面向对象编程三大特征

封装、继承和多态

封装
封装基础
  • 定义

​ 封装就是把抽象出来的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作

  • 好处

    1. 隐藏实现细节:用户无需关注方法的实现细节,只需要传入参数,对方法进行调用
    2. 可以对传入的数据进行验证,保证安全合理
  • 封装的实现步骤

    1. 将属性进行私有化private(让外部不能直接修改属性)

    2. 提供一个公共的(public)set方法,用于对属性判断并赋值

      public void setXxx(形参){    		//Xxx表示某个属性
      	//加入数据验证的业务逻辑
      	属性 = 参数名;
      }
      
    3. 提供一个公共的(public)get方法,用于获取属性的值

      public 数据类型 getXxx(){    		//Xxx表示某个属性
      	//加入权限判断的业务逻辑
      	return Xxx}
      
  • Tips:

​ IDEA中可以通过鼠标右键进入generate,再选择Getter and Setter,按住ctrl即可选择多个变量设置封装方法

封装与构造器

​ 如果还是和之前一样创建构造器,让this.age = age;那么对age传入数据的判断与封装便毫无意义,所以我们需要在构造器中调用set(Xxx)方法便可以了

public class Person {
    String name;
    private int age;
    private double salary;

    public Person() {
    }

    public Person(String name, int age, double salary) {   
        setName(name);				//构造器中调用set(Xxx)方法以完善封装
        setAge(age);
        setSalary(salary);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        if (name.length() < 2 || name.length() > 6) {
            System.out.println("姓名应该在2~6个字符之间");
            this.name = "佚名";
        } else {
            this.name = name;
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age >= 1 && age <= 120) {
            this.age = age;
        } else {
            System.out.println("输入年龄有误,应该在1~120之间");
            this.age = 18;
        }
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public void info() {
        System.out.println(name + " " + age + " " + salary);
    }
}
继承
继承基础

​ 为了代码复用性,当多个类存在相同的属性和方法时,可以从这些类中抽取出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends声明继承父类即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N373h9Wd-1668777222958)(C:\Users\god\AppData\Roaming\Typora\typora-user-images\image-20221028100939418.png)]

  • 语法

​ class 子类 extends 父类{

​ }

细节
  1. 子类继承了父类的所有属性和方法,但是私有属性和方法不能在子类直接访问,要通过公共的方法去访问

    默认属性和方法若和主类在同一个包内可以访问,不在则也不可以访问(访问修饰符知识)

  2. 子类必须调用父类构造器,完成对父类的初始化

  3. 当创建子类对象时,不管使用子类的哪个构造器,默认情况下都会去调用父类的无参构造器如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器,实现对父类的初始化工作,否则编译报错

  4. 如果希望指定去调用父类的某个构造器,则显式的调用一下:super(参数列表)

  5. super在使用时,需要放在构造器第一行(super只能在构造器中使用)

  6. super()和this()都需要放在构造器第一行,所以这两个不能共存一个构造器中

  7. java所有类都是object的子类,object是所有类的基类

  8. 父类构造器的调用不限于直接父类,将一直往上追溯直到object类(顶级父类)

  9. 子类最多只能继承一个父类(指直接继承),即Java中是单继承机制

​ 思考:如何让A同时继承B类和C类属性和方法呢(让A继承B类,B类再继承C类即可)

  1. 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系

​ Person is a Music? Person extends Music is 不合理

​ Cat is an Animal? Cat extends Animal is 合理

变量访问规则
  • 按照查找关系来确定访问的是哪个类中的哪个变量
    1. 首先看子类是否有该属性
    2. 如果子类有这个属性,并且可以访问,则返回这个属性的信息(当子类有这个属性,却没有访问权限时候,它不会再接着去父类查找了,而会直接报错
    3. 如果子类没有这个属性,就看父类有没有这个属性(如果父类有这个属性,并且可以访问,则返回这个属性的信息…)
    4. 如果父类没有就按照(3)的规则,继续找上级父亲,直到Object…
super关键字
  • super代表父类的引用,用于访问父类的属性,方法,构造器
    1. 访问父类的属性(或方法),但不能访问父类的private属性(或方法) super.属性名(或方法名)
    2. 访问父类的构造器(详见继承细节
    3. 当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super(直接跳过查看本类)
    4. super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以通过super去访问爷爷类的成员(如果多个基类中都有同名的成员,使用super访问要依据变量访问规则)
No. 区别点 this super
1 访问权限 访问本类中的属性,没有则到父类中继续查找 直接访问父类中的属性
2 调用方法 访问本类中的方法,没有则到父类中继续查找 直接访问父类中的方法
3 调用构造器 调用本类构造器,必须放在构造器首行 调用父类构造器,必须放在子类构造器首行
4 特殊 表示当前对象 子类中访问父类对象
方法覆盖(重写)
  • 子类的某个方法方法名称和父类的一模一样,参数和父类的一模一样,返回类型也和父类方法一样或者返回类型是父类返回类型的子类,就称子类的这个方法覆盖了父类的那个方法(子类返回类型也和父类方法一样,或者返回类型是父类返回类型的子类,比如父类返回类型是Object,子类方法返回类型是String)

  • 子类方法不能缩小父类方法的访问权限(比如父类是默认,子类可以是public,protected,默认但不能是private这种更小权限)

多态

方法或对象具有多种形态,是面向对象的第三大特征,,多态是建立在封装和继承基础之上的

多态的具体体现
  1. 方法的多态:重写和重载就体现多态
  2. 对象的多态(核心):(1)一个对象的编译类型和运行类型可以不一致*(运行类型可以是编译类型的子类)*;(2)编译类型在定义对象时就确定了,不能改变;(3)运行类型可以是变化的;(4)编译类型看等号的左边,运行类型看等号的右边;(4)对于方法的形式参数而言,实际参数可以传入形式参数子类对象(此时形参就是编译类型,实参就是运行类型)

*举例:*Animal animal = new Dog();【Dog类是Animal类的子类,animal的编译类型为Animal,运行类型为Dog】

​ animal = new Cat();【animal的运行类型变成了Cat,编译类型仍然是Animal】

package Test308;

public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.cry();			//animal类不同的运行类型体现了对象的多态
        animal = new Dog();
        animal.cry();
        animal = new Cat();
        animal.cry();
    }
}
public class Animal {
    public void cry(){
        System.out.println("动物在叫。。。");
    }
}
public class Cat extends Animal{
    public void cry(){
        System.out.println("猫在叫。。。");
    }
}
public class Dog extends Animal{
    public void cry(){
        System.out.println("狗在叫。。。");
    }
}
package Test308;

public class Test {
    public static void main(String[] args) {
		Animal animal = new Animal();
        animal.cry(animal);
        Dog dog = new Dog();
        animal.cry(dog);		//对于方法的形式参数而言,实际参数可以传入形式参数子类对象(此时形参就是编译类型,实参就是运行类型)
        Cat cat = new Cat();
        animal.cry(cat);		//对于方法的形式参数而言,实际参数可以传入形式参数子类对象(此时形参就是编译类型,实参就是运行类型)
    }
}
public class Animal {
    public void cry(Animal animal){
        System.out.println("动物在叫。。。");
    }
}
public class Cat extends Animal{
    public void cry(){
        System.out.println("猫在叫。。。");
    }
}
public class Dog extends Animal{
    public void cry(){
        System.out.println("狗在叫。。。");
    }
}
细节
  • 多态的前提是:两个对象存在继承关系

    1. 多态的向上转型:父类的引用指向了子类的对象(语法:父类类型 引用名 = new 子类类型();)可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员(因为在编译阶段能调用哪些是由编译类型决定的)

    2. 多态的向下转型:语法:子类类型 引用名 = (子类类型)父类引用;只能强转父类的引用,不能强转父类的对象;要求父类的引用必须指向的是当前目标类型的对象;可以调用子类类型中所有的成员

      Animal animal = new Dog();
      Dog dog = (Dog) animal;
      
    3. 属性没有重写之说,属性的值看编译类型

动态绑定机制
  1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
  2. 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用(看编译类型)
instanceof
  • 如果instanceof前面的对象的运行类型是instanceof后面的类的子类或者和instanceof后面的类相同,则返回true
Animal animal = new Animal();
System.out.println(animal instanceof Dog);  //false
多态的应用
  • 多态数组
public class Test {
    public static void main(String[] args) {
        Person[] person = new Person[5];   //设置父类数组,运行类型可以为子类
        person[0] = new Person("李", 20);
        person[1] = new Student("李", 21, 100);
        person[2] = new Student("王", 21, 99);
        person[3] = new Teacher("ling", 32, 1000000);
        person[4] = new Teacher("shao",33,1000000);
        for (int i = 0; i < person.length; i++) {
            System.out.println(person[i].say());
            if(person[i] instanceof Student){     //设置判断才能调用子类特有方法
                ((Student) person[i]).study();     //调用子类特有方法(父类没有)需向下转型
            } else if (person[i] instanceof Teacher) {
                ((Teacher) person[i]).teach();
            }
        }
    }
}
public class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    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 String say() {
        return name + "\t" + age;
    }
}
public class Student extends Person {
    private double score;

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    public String say() {
        return super.say() + "\t" + score;
    }
    
    public void study() {
        System.out.println(getName() + " is studing");
    }
}
public class Teacher extends Person {
    private double salary;

    public Teacher(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public String say() {
        return super.say() + "\t" + salary;
    }
    public void teach() {
        System.out.println(getName() + " is teaching");
    }

}
  • 多态参数
public void testWork(Employee e){  			//多态参数本质就是向上转型,达到可以输入本类以及子类
        if(e instanceof Worker){
            ((Worker) e).work();        	//向下转型访问子类的独有方法
        } else if (e instanceof Manage) {
            ((Manage) e).manage();
        }
    }
查看JDK原码
  • 鼠标放在方法上面,鼠标右键选择go to,再选择declaration and usages即可
Object类
equals方法
  • == 与 equals的比较
    1. ==如果判断基本类型,判断的是值是否相等;如果判断引用类型,判断的是地址是否相等,即判断是不是同一个对象(不看编译类型)
    2. equals是Object类中的方法,只能判断引用类型,默认判断的是地址是否相等,子类中常常重写该方法,用于判断内容是否相等(语法:引用类型的对象.equals(引用类型的对象))
int a = 10;
double b = 10.0;
System.out.println(a == b);  //true
String string1 = new String("abcde");
String string2 = new String("abcde");
System.out.println(string1 == string2);//false,因为对于引用类型是判断地址
System.out.println(string1.equals(string2));//true;String子类改写了equals方法使得可以比较字符串是否相同内容(JDK中可以查看原码)
Integer integer1 = new Integer(1000);
Integer integer2 = new Integer(1000);
System.out.println(integer1 == integer2);//false;因为对于引用类型是判断地址
System.out.println(integer1.equals(integer2));//true;Integer子类也重写了(JDK中可以查看原码)

自己重写equals方法,使得可以判断两个类所有的属性是否相等

public class EqualsExercise {
    public static void main(String[] args) {
        Person person1 = new Person("li", 20, '男');
        Person person2 = new Person("li", 20, '男');
        System.out.println(person1.equals(person2));
    }
}

class Person {
    private String name;
    private int age;
    private char gender;

    public boolean equals(Object obj) {
        if (obj == this) {        //先判断两个类是不是一个对象
            return true;
        } else if (obj instanceof Person) {  //再判断类型
            Person p = (Person) obj;		//向下转型
            return p.name.equals(this.name) && p.age == this.age && p.gender == this.gender;
//return ((Person) obj).name.equals(this.name) && ((Person) obj).age == this.age &&((Person) obj).gender == this.gender;
        }

        return false;
    }

    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
}
hashCode方法
  • 返回该对象的哈希码值
    1. 提高具有哈希结构的容器的效率
    2. 哈希值主要根据地址号来的,不能完全将哈希值等价于地址
    3. 两个引用如果指向同一个对象,则哈希值肯定是一样的
    4. 集合中,如果需要的话,也会重写hashCode
public class Hashcode_ {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();
        A a3 = a1;
        System.out.println(a1.hashCode());
        System.out.println(a2.hashCode());
        System.out.println(a3.hashCode());
    }
}
class A{}
//运行:
1324119927
990368553
1324119927
toString方法
  • 默认返回:全类名+@+哈希值的十六进制(全类名:包名加类名),子类往往重写toString方法,用于返回对象的属性信息

  • 当直接输出一个对象时,toString方法会被默认调用

public class ToString_ {
    public static void main(String[] args) {
        Monster monster = new Monster();
        System.out.println(monster.toString());
        System.out.println(monster);//直接输出一个对象时默认调用monster.toString()
    }
}
class Monster {
}
//运行:
com.test322.Monster@4eec7777
com.test322.Monster@4eec7777
finalize方法
  • 当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作
  • 什么时候被回收:当某个对象没有任何引用时,则JVM就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize方法
  • 垃圾回收机制的调用,是由系统来决定的(即由自己的GC算法),也可以通过System,gc()主动触发垃圾回收机制
public class Finalize_ {
    public static void main(String[] args) {
        AA a = new AA();
        a = null; //让对象失去引用
//主动触发垃圾回收机制,此时垃圾回收以及后续语句多线程同时执行,所以程序结束语句会先执行,调用后至
        System.gc(); 
        System.out.println("程序结束");
    }
}

class AA{
    @Override
    protected void finalize() throws Throwable { //重写finalize()方法
        System.out.println("销毁了对象");
        System.out.println("释放了资源");
    }
}
//运行:
程序结束
销毁了对象
释放了资源
断点调试

F8逐行执行代码

F7跳入方法体内部

shift+F8跳出方法体内部

F9直接执行到下一个断点

断点可以在debug时,动态下断点

面向对象编程(高级)

类变量

也叫静态变量,会被类的所有对象实例共享

  • 语法:访问修饰符 static 数据类型 变量名
public class Test {
    public static void main(String[] args) {
        //类名.类变量名访问
        System.out.println(A.name);
        A a = new A();
        //对象名.类变量名访问
        System.out.println(a.name);
    }
}

class A {
    //类变量的访问需要遵循访问权限
    public static String name;

}
类方法

也叫静态方法,会被类的所有对象实例共享

静态方法就可以访问静态属性或变量

  • 使用细节:
    1. 类方法中不允许使用和对象有关的关键字,比如this和super
    2. 静态方法中只能访问静态变量或静态方法(非静态成员只能通过new实例化才能在静态方法中被调用)
    3. 普通成员方法既可以非静态成员,也可以访问静态成员(遵循访问权限
class A {
    //类变量的访问需要遵循访问权限
    public static int fee;

    public static void setPay(int fee) {
        //this.fee = fee;                     //this关键字在静态方法中不可使用
        A.fee = fee;                          //静态初始化
    }
main方法
  • public static void main(String[] args)
    1. main方法时虚拟机调用
    2. Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
    3. Java虚拟机执行类的main()方法时不必创建对象,所以该方法必须是static
    4. 该方法接受String类型对的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数
    5. java 执行的程序 参数1 参数2 参数3
public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }
    }
}
//命令行编译:
javac Test.java
java Test tom jack smith
//结果
tom 
jack 
smith

IDEA中通过Edit Configurations中的Program arguments(程序参数)中填入参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uowsZuQZ-1668777222959)(C:\Users\god\AppData\Roaming\Typora\typora-user-images\image-20221111173313717.png)]

  • 细节
    1. 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
    2. 但不能直接访问该类中的非静态成员,必须创建该类的一个实例对象,才能通过这个对象来访问该类中的非静态成员
代码块

其实说白了就是类中方法体之外只能写属性初始化这种语句,无法直接写基本语句,虽然构造器也可以写其他语句,这个代码块其实就是一种补充,很简洁的在类中也可以直接写基本语句

class A{
int name;      //写属性是对的
System.out.println();  //错误,类的方法体,构造器或主方法外不可直接写基本语句
}

代码块又称为初始化块,属于类的成员,类似于方法,但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用

  • 基本语法:

​ [修饰符]{

​ 代码

​ };

  • 语法注意点:

    1. 修饰符可选,要写的话也只能写static
    2. 有static修饰的叫做静态代码块,没有static修饰的,叫普通代码块/非静态代码块
    3. ;号可以写上,也可以省略
    4. 一般用于当多个构造器都有重复语句时,抽取到初始化块中,提高代码的复用性
  • 使用细节

    1. 静态代码块的主要作用是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次,如果是普通代码块,每创建一个对象,都会执行

    2. 类什么时候被加载:

      1. 创建对象实例时(new)
      2. 创建子类对象实例时候,父类会被加载,而且父类先被加载,子类后被加载
      3. 使用类的静态成员时(静态属性,静态方法)[父类先被加载,子类后被加载,最后再使用类的静态成员]
      public class Test {
          public static void main(String[] args) {
              System.out.println(B.age);
              System.out.println(B.age);    //静态代码块只会被执行一次
          }
      }
      
      class A {
         static {
              System.out.println("A的静态代码1被执行");
          }
      }
      
      class B extends A {
          public static int age = 20;
      
          static {
              System.out.println("B的静态代码1被执行");
          }
      }
      //运行:
      A的静态代码1被执行
      B的静态代码1被执行
      20
      20
      
    3. 普通代码块:在创建对象实例时(new对象时),会被隐式调用。被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行

    4. 创建一个对象时,在一个类的调用顺序是:(重点

      1. 调用静态代码块和静态属性初始化(优先级一样,按照定义顺序调用)
      2. 调用普通代码块和普通属性的初始化(优先级一样,按照定义顺序调用)
      3. 调用构造方法
    5. 构造器的最前面其实隐含了super()和调用普通代码块,静态相关的代码块在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的

      public class Test {
          public static void main(String[] args) {
              new B();
      
          }
      }
      
      class A {
      
          {
              System.out.println("父类A的普通代码块1被执行");
          }
      
          A() {
              System.out.println("父类A的构造器被执行"); //隐含了super()和调用普通代码块
          }
      }
      
      class B extends A {
          {
              System.out.println("子类B的普通代码块1被执行");
          }
      
      
          B() {
              System.out.println("子类B的构造器被执行"); //隐含了super()和调用普通代码块
          }
      }
      //运行:
      父类A的普通代码块1被执行
      父类A的构造器被执行
      子类B的普通代码块1被执行
      子类B的构造器被执行
      
    6. 静态代码块只能直接调用静态成员,普通代码块可以直接调用任意成员

  • 总结:创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:

    1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)

    2. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)

    3. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)

    4. 父类的构造方法

    5. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)

    6. 子类的构造方法

      public class Test {
          public static void main(String[] args) {
              new B();
      
          }
      }
      
      class A {
          public static int age = getval();
          static {
              System.out.println("父类A的静态代码1被执行");
          }
      
          {
              System.out.println("父类A的普通代码块1被执行");
          }
      
          A() {
              System.out.println("父类A的构造器被执行");
          }
          public static int getval(){
              System.out.println("getA");
              return 10;
          }
      }
      
      class B extends A {
          public static int age = getval();
          {
              System.out.println("子类B的普通代码块1被执行");
          }
      
          static {
              System.out.println("子类B的静态代码1被执行");
          }
      
          B() {
              System.out.println("子类B的构造器被执行");
          }
          public static int getval(){
              System.out.println("getB");
              return 10;
          }
      }
      //运行:
      getA
      父类A的静态代码1被执行
      getB
      子类B的静态代码1被执行
      父类A的普通代码块1被执行
      父类A的构造器被执行
      子类B的普通代码块1被执行
      子类B的构造器被执行
      
单例设计模式

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

饿汉式

饿汉意思是很着急,一旦加载,这个对象也被创建好了,即使你没有使用到这个对象

  • 步骤

    1. 构造器私有化

    2. 类的内部创建对象(该对象是static)

    3. 向外暴露一个静态的公共方法

      public class Test {
          public static void main(String[] args) {
              GirlFriend a = GirlFriend.getInstance();
              GirlFriend b = GirlFriend.getInstance();
              System.out.println(a.equals(b)); //输出为true,证明了是单例设计模式
      
          }
      }
      
      class GirlFriend {
          private static GirlFriend gd = new GirlFriend();  //静态属性只会在类加载时初始化一次,因此保证了类只有一个对象
      
          private GirlFriend() {       //保证无法自己new一个对象
      
          }
          public static GirlFriend getInstance(){  //构造器被限制所以无法通过对象访问这个公共方法,所以设置为static
              return gd;
          }
      
      }
      
懒汉式

步骤与饿汉式类似,不过类加载时候不会创建对象,只有当用户使用getInstance()方法时,才会创建并返回对象

public class Test {
    public static void main(String[] args) {
        GirlFriend a = GirlFriend.getInstance();
        GirlFriend b = GirlFriend.getInstance();
        System.out.println(a.equals(b)); //输出为true,证明了是单例设计模式

    }
}

class GirlFriend {
    private static GirlFriend gd;

    private GirlFriend() {       //保证无法自己new一个对象

    }

    public static GirlFriend getInstance() {  //构造器被限制所以无法通过对象访问这个公共方法,所以设置为static
        if(gd == null){
            gd = new GirlFriend();
        }
        return gd;
    }

}

饿汉式VS懒汉式
  1. 二者最主要区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式式在使用时才创建
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题(因为如果多个线程同时访问到 if(gd == null) 语句时,会同时创建多个对象,最后返回保留最后一个返回的对象)
  3. 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用(其他方式完成了类的加载),那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
final

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

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

    1. final修饰的属性又叫常量(按照标识符命名规范,一般用字母全大写来命名)

    2. final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一:

      1. 定义时:如 public final double TAX_RATE = 0.08;
      2. 在构造器中(非静态属性
      3. 在代码块中
    3. 如果final修饰的属性是静态的,则初始化的位置只能是

      1. 定义时
      2. 在静态代码块中(不能在构造器中赋值)
    4. final类不能继承,但是可以实例化对象

    5. 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承

    6. 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法

    7. final不能修饰构造方法(即构造器)

    8. final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理

      public static final int age = 16;

    9. 包装类(Integer,Double,Flout,Boolean等等都是final),String也是final类

抽象类
基础
  • 当父类的一些方法不能确定时,考虑将该方法设计为抽象方法,所谓抽象方法就是没有实现的方法,所谓没有实现就是没有方法体
  • 当一个类中存在抽象方法时,需要将该类声明为abstract类,用abstract来修饰该类就是抽象类

​ 访问修饰符 abstract 类名 {

​ 访问修饰符 abstract 返回类型 方法名(参数列表); (没有方法体)

​ }

  • 一般来说,抽象类会被继承,由其子类来实现抽象方法

  • 细节:

    1. 抽象类不能被实例化

    2. 抽象类不一定要包含abstract方法

    3. 一旦类包含了abstract方法,则这个类必须声明为abstract

    4. abstract只能修饰类和方法,不能修饰属性和其他的

    5. 抽象类可以有任意成员【抽象类本质还是类

    6. 抽象方法不能有主体,即不能实现

      abstract void aaa() { } //错误,出现了{}这个方法体了

    7. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法(重写),除非它自己也声明为abstract类

    8. 抽象方法不能使用private 、final和 static来修饰,因为这些关键字都是和重写相违背的

应用:模板设计模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FSmipOpO-1668777222959)(C:\Users\god\AppData\Roaming\Typora\typora-user-images\image-20221115184621474.png)]

接口
概念
interface 接口名{
	属性
	方法
}
class 类名 implements 接口{
    自己的属性
    自己的方法
    必须实现的接口的抽象方法
}
  • 在接口中,抽象方法,可以省略abstract关键字

  • 在JDK7以前,接口里的所有方法都没有方法体,即都是抽象方法

  • 在JDK8后,可以有静态方法,也可以有默认方法,普通方法需要使用default关键字修饰

  • 细节

    1. 接口不能被实例化
    2. 接口中的所有方法必须是public方法(没有修饰符默认是public),接口中的抽象方法,可以不用abstract修饰 ( void hi() 等价于 public abstract void hi() )
    3. 一个普通类实现接口,就必须将该接口的所有方法都实现
    4. 抽象类实现接口,可以不用实现接口的方法
    5. 一个类同时可以实现多个接口 (class 类名 implements 接口1,接口2())
    6. 接口中的属性,只能是final的,而且是public static final的修饰符。(比如:int a = 1; 实际上等价于public static final int a = 1;)
    7. 接口中属性的访问形式:接口名.属性名(因为默认是static的)
    8. 一个接口不能继承其他类,但是可以继承多个别的接口(interface A extends B,C {} )(普通类是单继承机制的
    9. 接口的修饰符只能是 public 和 默认,这点和类的修饰符是一样的
实现接口vs继承类
  • 可以简单来说,接口是Java单继承机制的一种补充,一个接口可以同时继承多个接口,一个类又可以实现多个接口
  • 接口比继承更加灵活,继承是满足is - a的关系,而接口只需满足like - a的关系
  • 接口的价值主要在于:设计好各种规范(方法),让其他类去实现这些方法
接口多态特性

接口和实现接口的类同样和继承一样体现了多态,我们可以对照着看下面的例子

public class Test {
    public static void main(String[] args) {
        Usb[] usb = new Usb[3];
        usb[0] = new Camera();
        usb[1] = new Phone();
        usb[2] = new Camera();

        for (int i = 0; i < usb.length; i++) {
            useWork(usb[i]);
            if (usb[i] instanceof Phone) {   //多态的向下转型
                ((Phone) usb[i]).call(); 
            }
        }
    }

    public static void useWork(Usb usb){  //多态的向上转型
        usb.work();         //动态绑定
    }
}

interface Usb {
    void work();
}

class Camera implements Usb {
    @Override
    public void work() {
        System.out.println("camera is working");
    }
}

class Phone implements Usb {
    @Override
    public void work() {
        System.out.println("phone is working");
    }

    public void call(){                     //特有方法必须向下转型调用
        System.out.println("l am calling");
    }
}

  • 接口动态传递

​ 如果一个接口继承了另一个接口,而一个类实现了子接口,那么实际上相当于这个类也实现了父接口(继承知识),此时接口类型的变量也可以指向这个类的对象实例。这被称为“接口动态传递

public class Test {
    public static void main(String[] args) {
     //接口类型的变量可以指向实现了该接口的类的对象实例
        Usb1 usb1 = new Phone();
        /*
        如果一个接口继承了另一个接口,而一个类实现了子接口
        那么实际上相当于这个类也实现了父接口(继承知识)
        此时接口类型的变量也可以指向这个类的对象实例
        这被称为“接口动态传递”
        */
		Usb2 usb2 = new Phone();
    }
}

interface Usb1 extends Usb2{}
interface Usb2{}
class Phone implements Usb1{ }
思考题

看看下面代码是否有错误

public class Test {
    public static void main(String[] args) {
        B b = new B();
        b.getA();
    }
}

interface A1 {
    int a = 1;   //等价于 public static final int a = 1;
}

class A2 {
    int a = 2;
}

class B extends A2 implements A1 {         //类的定义更加完善了
    public void getA() {
        System.out.println(a);  //直接写a会报错,因为编译器无法判断调用哪个a
    }
}
//修改:
System.out.println(A1.a + " " + super.a);  //这样写编译器便可以区分开来
内部类
局部内部类
  • 局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。

    1. 可以直接访问外部类的所有成员,包含私有的

    2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final

    3. 作用域:仅仅在定义它的方法或代码块中

    4. 局部内部类 访问 外部类的成员【访问方式:直接访问】

    5. 外部类 访问 局部外部类的成员

      ​ 访问方式:创建对象,再访问

    6. 外部其他类不能访问局部内部类(因为局部内部类地位是以一个局部变量)

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

      class A {
          int age = 10;
      
          public void work() {
      
              class B {                 //局部内部类
                  public void getAge() {
                      int age = 20;
                      System.out.println("age = " + age); //重名变量依旧按照就近原则
                      System.out.println(A.this.age); //访问重名属性得 类名.this.属性名(类名.this指代的是A类,直接this指带的是内部类)
                  }
              }
              //局部内部类作用域:只在定义它的方法或代码块中
              B b = new B();  //外部类访问局部类,在方法中创建对象,访问即可
              b.getAge();     //局部内部类访问外部类成员,直接访问即可
          }
      }
      

      证明:如果外部类和局部内部类重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

      public class Test {
          public static void main(String[] args) {
              A a = new A();
              a.work();
              System.out.println(a);
          }
      }
      
      class A {
          public void work() {
              class B {                 //局部内部类
                  public void getAge() {
                      System.out.println("A.this "+ A.this); //证明A.this才代表A类的属性
                      System.out.println("this "+ this);      //直接this指代的是内部类B
                  }
              }
              B b = new B();
              b.getAge();
          }
      }
      //运行:
      A.this 	homework.test414.A@682a0b20
      this 	homework.test414.A$1B@3d075dc0
      		homework.test414.A@682a0b20
      
匿名内部类

匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名

public class Test {
    public static void main(String[] args) {
        new A().inClass();
    }
}

class A {
    public void inClass(){
    /* 底层原理:
       1.接口本来是不可以直接创建对象的,
       但是这里的new B()其实系统会为它创建一个匿名内部类
       这里的b的编译类型是B,运行类型其实是匿名内部类
       2.编译器内部会创建一个匿名内部类去实现B接口:
       class XXXX implements B{
       public void cry() {
                System.out.println("B is crying");
            }
       }
       3.这里的类名是系统给的,所以被称为匿名内部类,其实XXXX是外部类名+$+第几个匿名内部类
       比如这里就是A$1
       4.jdk底层在创建A$1这个匿名内部类,马上就创建了A$1实例,并把地址返回给了b对象
    */
        B b = new B(){
            @Override
            public void cry() {
                System.out.println("B is crying");
            }
        };               					//创建结束必须写上;
 	/*
        编译器内部同样会创建一个匿名内部类,不过是去继承C类
        其他和基于接口的匿名内部类完全一样
        class XXXX extends C{
        void work() {
                super.work();
            }
        }
    */
        C c = new C(10){            //基于类的匿名内部类(与接口类似)
            @Override				//上面的传入参数会直接传给C类的构造器
            void work() {
                super.work();
            }
        };

        System.out.println(b.getClass());  //验证一下匿名内部类名称(getClass()方法会打印运行型)
        System.out.println(c.getClass());   
        b.cry();
    }

}

interface B {
    void cry();
}
class C{
    int age ;

    public C(int age) {
        this.age = age;
    }

    void work(){

    }
}

//运行:
class homework.test416.A$1
class homework.test416.A$2
B is crying
  • 细节:
    1. 匿名内部类不可以有构造方法
    2. 其他访问细节和局部内部类一样
成员内部类
  • 成员内部类是定义在外部类的成员位置,并且没有static修饰

    1. 可以直接访问外部类的所有成员,包含私有的

    2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员

    3. 作用域:和外部其他成员一样,为整个类体

    4. 成员内部类 访问 外部类成员【直接访问】

    5. 外部类 访问 成员内部类【创建对象,再访问】

    6. 外部其他类 访问 成员内部类【两种方法:见代码实例注释】

      public class Test {
          public static void main(String[] args) {
              A a = new A();
             /*外部其他类使用成员内部类的方式:
             第一种方式:
             把new B()看成是外部类对象a的成员
             这就是一个语法,不要特别纠结
             */
              A.B b1 = a.new B();                //b1和b2本质一样
              A.B b2 = new A().new B();
              /*第二种方法:
              在外部类中,编写一个方法,可以返回B类对象
              */
              A.B b3 = a.getB();					 //b3和b4本质一样
              A.B b4 = new A().getB();
          }
      }
      
      class A {
      
          class B {
              public void work() {
                  System.out.println("working");
              }
          }
      
          public B getB(){
              return new B();
          }
      }
      
静态内部类
  • 静态内部类是定义在外部类的成员位置,并且有static修饰

    1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员

    2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员

    3. 静态内部类 访问 外部类(比如:静态属性) 【访问方式:直接访问所有静态成员】

    4. 外部类 访问 静态内部类【创建对象,再访问】

    5. 外部其他类 访问 静态内部类【三种方法:见代码实例注释】

      public class Test {
          public static void main(String[] args) {
              //方法1:静态调用
              A.B b1 = new A.B();
              //方法2:通过方法调用
              A.B b2 = new A().getB();
              //方法3:通过静态方法调用
              A.B b3 = A.getB_();
          }
      }
      
      class A{
          static class B{
      
          }
      
          public B getB(){           //普通成员既可以直接调用静态方法,也可以调用其他
              return new B();
          }
          public static B getB_(){  //静态成员可以直接调用静态方法
              return new B();
          }
      }
      

你可能感兴趣的:(java,jvm,开发语言,学习)