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虚拟机
《渡月桥思君》
年少总将被不可得之物困其一生
不要指望谁陪你一辈子,没光的时候连影子都不陪你
代码开头写入
/**
*@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
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是一个整数,默认输出是unicode码对应的字符,我们可以简单的通过强制类型转换将字符回缩为数字
public class char01{
public static void main(String[] args){
char a = '李';
System.out.println("李的ASCII码为"+(int)a); //李的ASCII码为26446
}
}
语法:将基本类型的值+ “” 即可
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 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
只是更加专业
大驼峰
】:XxxYyyZzz,比如:TankShotGame小驼峰
】:xxxYyyZzz,比如:tankShotGame使用扫描器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,结果为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
位运算符
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(int i = 1; i < 10; i++){
System.out.println("Yeah");
}
System.out.println(i);
//运行:报错
当i在for循环中初始化,那么i的作用域就只在for循环里
不管while是否满足,首先先执行一次do语句,再判断while条件是否满足
int i = 1;
do{
System.out.println(i);
i++;
}while(i <= 10); //while后面有分号
直接退出,while for switch循环,执行下面语句
只退出当前判断,依旧执行循环
退出方法用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];
}
简单版:创建新链表,赋值后再将新链表地址赋给旧链表
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);
}
}
使用数据结构链表实现动态数组
冒泡排序
就像一个气泡从水底上升到空气中,每次比较相邻的两个大小,最终会将最大值放到最后一位,第二轮将第二大值放在倒数第二位,依次类推
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]+" ");
}
}
}
顺序查找
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("查无此游戏");
}
}
}
二分查找
静态创建
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]只是一个有三个空间的一维数组
}
}
动态创建
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;
}
}
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){ //错误:一个形参列表只能出现一个可变参数
}
}
新对象的初始化
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;
}
默认的构造器就覆盖了
,就不能再使用默认的无参构造器了,除非显示的定义一下,即: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来指定对象的属性
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还是按照就近原则,是方法的形式参数
}
注意只能在构造器中使用(即只能在构造器中访问另一个构造器,并且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是JDK提供的一个命令行工具,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;
}
}
}
已经自定义为 ctrl + d
ctrl + alt + ↓
alt + /
ctrl + /
alt + enter
ctrl + alt + l
alt + insert
或者鼠标右键选择generate,再选择constractor
按住ctrl不松手可以选择全部变量
将光标放在一个方法上,输入 ctrl + B,可以自动定位到方法位置
末尾加上 .var 便可自动生成变量名
file -> settings -> editor -> Live templates
查看有哪些模板快捷键/可以自己增加模板
包的作用
包的本质:实际上就是创建不同的文件夹保存类文件
包的命名规则:只能包含数字,字母,下划线,小圆点,但不能用数字开头,不能是关键字或保留字
demo.class.execl1 //class是关键字
demo.12a.Test //12a数字开头
com.公司名.项目名.业务模块名
举例:com.sina,crm.user //用户模块
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包下的所有类都引入
public
修饰,对外公开protected
修饰,对子类和同一个包中的类公开没有修饰符号
,向同一个包的类公开private
修饰,只有类本身可以访问,不对外公开访问级别 | 访问控制修饰符 | 同类 | 同包 | 子类 | 不同包 | |
---|---|---|---|---|---|---|
1 | 公开 | public | ✔ | ✔ | ✔ | ✔ |
2 | 受保护 | protected | ✔ | ✔ | ✔ | X |
3 | 默认 | 没有修饰符号 | ✔ | ✔ | X | X |
4 | 私有 | private | ✔ | X | X | X |
细节:修饰符可以修饰类中的属性,方法,类
只有默认和public可以修饰类
封装、继承和多态
封装就是把抽象出来的数据[属性]
和对数据的操作[方法]
封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法]
,才能对数据进行操作
好处
封装的实现步骤
将属性进行私有化private(让外部不能直接修改属性)
提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx(形参){ //Xxx表示某个属性
//加入数据验证的业务逻辑
属性 = 参数名;
}
提供一个公共的(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 父类{
}
子类继承了父类的所有属性和方法,但是私有属性和方法不能在子类直接访问,要通过公共的方法去访问
默认属性和方法若和主类在同一个包内可以访问,不在则也不可以访问(访问修饰符知识)
子类必须调用父类构造器,完成对父类的初始化
当创建子类对象时,不管使用子类的哪个构造器,默认情况下都会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器,实现对父类的初始化工作,否则编译报错
如果希望指定去调用父类的某个构造器,则显式的调用一下:super(参数列表)
super在使用时,需要放在构造器第一行(super只能在构造器中使用)
super()和this()都需要放在构造器第一行,所以这两个不能共存一个构造器中
java所有类都是object的子类,object是所有类的基类
父类构造器的调用不限于直接父类,将一直往上追溯直到object类(顶级父类)
子类最多只能继承一个父类(指直接继承),即Java中是单继承机制
思考:如何让A同时继承B类和C类属性和方法呢(让A继承B类,B类再继承C类即可)
Person is a Music? Person extends Music is 不合理
Cat is an Animal? Cat extends Animal is 合理
No. | 区别点 | this | super |
---|---|---|---|
1 | 访问权限 | 访问本类中的属性,没有则到父类中继续查找 | 直接访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法,没有则到父类中继续查找 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器首行 | 调用父类构造器,必须放在子类构造器首行 |
4 | 特殊 | 表示当前对象 | 子类中访问父类对象 |
子类的某个方法方法名称和父类的一模一样,参数和父类的一模一样,返回类型也和父类方法一样或者返回类型是父类返回类型的子类,就称子类的这个方法覆盖了父类的那个方法(子类返回类型也和父类方法一样,或者返回类型是父类返回类型的子类,比如父类返回类型是Object,子类方法返回类型是String)
子类方法不能缩小父类方法的访问权限(比如父类是默认,子类可以是public,protected,默认但不能是private这种更小权限)
方法或对象具有多种形态,是面向对象的第三大特征,,多态是建立在封装和继承基础之上的
*举例:*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("狗在叫。。。");
}
}
多态的前提是:两个对象存在继承关系
多态的向上转型:父类的引用指向了子类的对象(语法:父类类型 引用名 = new 子类类型();)可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员(因为在编译阶段能调用哪些是由编译类型决定的)
多态的向下转型:语法:子类类型 引用名 = (子类类型)父类引用;只能强转父类的引用,不能强转父类的对象;要求父类的引用必须指向的是当前目标类型的对象;可以调用子类类型中所有的成员
Animal animal = new Dog();
Dog dog = (Dog) animal;
属性没有重写之说,属性的值看编译类型
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();
}
}
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;
}
}
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方法会被默认调用
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
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时,动态下断点
也叫静态变量,会被类的所有对象实例共享
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;
}
也叫静态方法,会被类的所有对象实例共享
静态方法就可以访问静态属性或变量
class A {
//类变量的访问需要遵循访问权限
public static int fee;
public static void setPay(int fee) {
//this.fee = fee; //this关键字在静态方法中不可使用
A.fee = fee; //静态初始化
}
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)]
其实说白了就是类中方法体之外只能写属性初始化这种语句,无法直接写基本语句,虽然构造器也可以写其他语句,这个代码块其实就是一种补充,很简洁的在类中也可以直接写基本语句
class A{
int name; //写属性是对的
System.out.println(); //错误,类的方法体,构造器或主方法外不可直接写基本语句
}
代码块又称为初始化块,属于类的成员,类似于方法,但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用
[修饰符]{
代码
};
语法注意点:
使用细节
静态代码块的主要作用是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次,如果是普通代码块,每创建一个对象,都会执行
类什么时候被加载:
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
普通代码块:在创建对象实例时(new对象时),会被隐式调用。被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行
创建一个对象时,在一个类的调用顺序是:(重点)
构造器的最前面其实隐含了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的构造器被执行
静态代码块只能直接调用静态成员,普通代码块可以直接调用任意成员
总结:创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
父类的构造方法
子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
子类的构造方法
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的构造器被执行
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
饿汉意思是很着急,一旦加载,这个对象也被创建好了,即使你没有使用到这个对象
步骤
构造器私有化
类的内部创建对象(该对象是static)
向外暴露一个静态的公共方法
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;
}
}
final可以修饰、属性、方法和局部变量
细节:
final修饰的属性又叫常量(按照标识符命名规范,一般用字母全大写来命名)
final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一:
如果final修饰的属性是静态的,则初始化的位置只能是
final类不能继承,但是可以实例化对象
如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法
final不能修饰构造方法(即构造器)
final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
public static final int age = 16;
包装类(Integer,Double,Flout,Boolean等等都是final),String也是final类
访问修饰符 abstract 类名 {
访问修饰符 abstract 返回类型 方法名(参数列表); (没有方法体)
}
一般来说,抽象类会被继承,由其子类来实现抽象方法
细节:
抽象类不能被实例化
抽象类不一定要包含abstract方法
一旦类包含了abstract方法,则这个类必须声明为abstract
abstract只能修饰类和方法,不能修饰属性和其他的
抽象类可以有任意成员【抽象类本质还是类】
抽象方法不能有主体,即不能实现
abstract void aaa() { } //错误,出现了{}这个方法体了
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法(重写),除非它自己也声明为abstract类
抽象方法不能使用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关键字修饰
细节:
接口和实现接口的类同样和继承一样体现了多态,我们可以对照着看下面的例子
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); //这样写编译器便可以区分开来
局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
可以直接访问外部类的所有成员,包含私有的
不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
作用域:仅仅在定义它的方法或代码块中。
局部内部类 访问 外部类的成员【访问方式:直接访问】
外部类 访问 局部外部类的成员
访问方式:创建对象,再访问
外部其他类不能访问局部内部类(因为局部内部类地位是以一个局部变量)
如果外部类和局部内部类重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.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
成员内部类是定义在外部类的成员位置,并且没有static修饰
可以直接访问外部类的所有成员,包含私有的
可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
作用域:和外部其他成员一样,为整个类体
成员内部类 访问 外部类成员【直接访问】
外部类 访问 成员内部类【创建对象,再访问】
外部其他类 访问 成员内部类【两种方法:见代码实例注释】
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修饰
可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
静态内部类 访问 外部类(比如:静态属性) 【访问方式:直接访问所有静态成员】
外部类 访问 静态内部类【创建对象,再访问】
外部其他类 访问 静态内部类【三种方法:见代码实例注释】
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();
}
}