1、每个源文件的基本组成部分是类class,java应用程序执行的入口是main方法,public static void main(String[] args){……}
2、一个源文件中最多只能有一个pblic类,其他类的数量不限,且文件名必须和此public类名称一样。
3、每一个类编译后,都会生成相应的.class文件。
4、main方法也可以写在非public类中,这样在运行该非public类的时候,入口方法就是这个非public类的main方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JvowBkFz-1633952250515)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210822102155191.png)]
三者的关系如下图所示,JDK(Java SE Development Kit),Java标准开发包,它提供了编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等。JRE( Java Runtime Environment) ,Java运行环境,用于解释执行Java的字节码文件。JVM(Java Virtual Mechinal),Java虚拟机,是JRE的一部分。它是整个java实现跨平台的最核心的部分,负责解释执行字节码文件,是可运行java字节码文件的虚拟计算机。所有平台的上的JVM向编译器提供相同的接口,而编译器只需要面向虚拟机,生成虚拟机能识别的代码,然后由虚拟机来解释执行。
变量包括三个关键因素:类型 变量名 变量值
不同类型所占用的内存空间不同,而变量名就相当于一个地址,指向一段内存空间,变量值就是这段内存空间所存放的数据。
加号两边都是数值,做算数加法运算,其中有一边是字符串的,做字符串拼接
这里要注意System.out.println(“hello”+100+3); 的结果是hello1003 而不是 hello103 因为加号是从左到右运算的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LExXFTuC-1633952250521)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210822172702964.png)]这里要注意的是char类型占2个字节,和C++不一样,C++是一个字节,还有就是,不管在什么OS中,这些数据类型所占的内存大小是不会改变的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmN7Tv3l-1633952250528)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210822174548673.png)]
关于浮点数的存储:浮点数的存储主要有符号位、指数位、尾数位,要注意尾数位可能丢失,造成精度丢失问题。在定义float的时候,后面一定要加上‘f’或者“F”,例如,float a = 1.1F;如果不加的话会报错—>不兼容的类型: 从double转换到float可能会有损失,因为java的浮点数类型默认是double,另外,在定义的时候可以省略0,例如,double a = 0.123和double a = .123是等价的。
还是精度问题,System.out.println(2.7 == 8.1/3); 的结果是false而不是true。所以在我们判断两个小数是否相等时,应该是两个数差值的绝对是小于某一个阈值就可以判断相等。
一个JDK下面有很多个包(package),一个包下面有很多接口、类、异常、枚举,一个类下面有很多字段、构造方法、成员方法。
System.out.println('a' + 1);
注意这句话的输出结果是98而不是a1,先将‘a’转成整数,然后再+1。
字符在计算机中存储的时候,是存的其ASCII码的二进制表示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kxywb2KO-1633952250530)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210824101050803.png)]
ASCII不够用,Unicode浪费空间,utf-8是对Unicode的改进(变长编码,1-6个字节表示一个字符,1个字节表示一个英文字母,3个字节表示一个汉字)
注意一点,java中的Boolean类型和C++中的bool的区别是,不能用0和1代表true和false。
char能转成int,byte可以转成short,short可以转成int (byte,short)和char类型不会相互自动转换。例如:
byte a = 1;
char c = a;
这样会报错,因为byte和char不会进行自动类型转换。但是byte、short和char类型是可以进行运算的,在运算之前会先转换成int类型,注意就算只有byte和short运算,也会转成int,byte和byte运算也会转成int,而float存在时,如果没有double出现,就不会自动转double
转成int之后的转换路径如下图所示:int -> long -> float ->double
要注意的是当有多种数据类型参与运算的时候,会先把所有的数转成其中精度最高的,然后再进行计算。
例如,float a = 10 + 0.1;会报错,因为0.1的类型是默认的浮点型double,所以a的类型也应该是double或者将0.1改为0.1F。
精度大数据给精度小的会报错,反之则会进行自动类型转换,boolean类型的数字不参与自动转换。
强制类型转换的时候要注意精度丢失和数据溢出等问题
强制转换只针对最近的操作数有效,例如(int)10.5 * 3的结果是30而不是31,(int)(10.5 * 3)的结果才是31
char类型只能保存整数常量,如果用整数变量给char赋值的时候需要强制类型转换
1、基本数据类型转换成字符串类型
int a = 10; String s = a + “”;//加个空字串即可
2、字符串转基本数据类型 --> parse方法
String s = "123";
int n = Integer.parseInt(s);
System.out.println(n);
double d = Double.parseDouble(s);
System.out.println(d);
输出的结果是123 123.0 如果s的值是字母的话,则会抛出异常,s的值只能是数字,正负都可以。同样的,Long、Byte、Boolean、Short都可以调用其parseXXX方法。但是boolean的时候,源字符串只能是true或者false
String s = "abc";
System.out.println(s.charAt(0));
用上面的代码能够提取出字符串中的指定字符,更改chartAt()里面的数字就能提取出指定字符。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CPMVC11R-1633952250532)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210825100351527.png)]
最后的结果是1,不是2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OfyCisGA-1633952250536)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210825100551132.png)]
这道题的结果是2
逻辑与(&)和短路与(&&)的区别:&&在第一个条件为假的时候不再进行后面的判断,&仍要进行,所以&的效率相对较低。
同样的,逻辑或(|)和短路或(||)也是如此,||在第一个条件为真的时候不再进行后面的判断,|仍要进行
异或(^)是相同为假,不同为真
复合赋值运算符会进行强制类型转换
byte i = 10;
i += 1;
上面的代码等价于 i = (byte)(i + 1)
1、包名:多单词组成时,所有字母都小写aaa.bbb.ccc
2、类和接口名:多个单词组成时,所有单词的首字母都大写AaaBbbCcc
3、变量名、方法名:多单词组成的时候,第一个单词首字母小写,其余单词首字母大写aaaBbbCcc
4、常量名:所有字母都大写,多单词时每个单词用下划线连接AAA_BBB_CCC
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtCXODWq-1633952250539)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210826172519386.png)]
java中有三个位运算符,<< 、>> 、 >>> 注意,无<<<符号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kxps0iX3-1633952250544)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210826173233416.png)]
左移相当于*2,右移相当于/2
while是先判断后执行,可以一次都不执行,do while是先执行后判断,所以最少要执行一次。
数组的定义: int a[] = new int[5] 定义一个名称为a的大小为5的int类型数组,新定义的数组默认初始化为0。
int a[] = new int[5];
int b[] = {1,2,3,4,5};
int c[] = new int[]{1,2,3,4,5};
数组的三种定义方式如上所示,当然也可以先声明后new
int d[];
d = new int[5];
这样在new之前,只是声明了一个变量,但是在内存空间中并未开辟新的空间,new之后才会开辟5个整型类型的空间来使用。
int、short、btye初始值为0,double/float类型为0.0,char类型为ASCII码为0的字符,boolean类型为false,String类型是null
数组可以用数组名.length获取数组的大小。
数组是引用类型数据,数组的数据是对象(Object)
基本数据类型的赋值方式:值拷贝,如下所示:
int a = 10;
int b = a;
b = 20;
System.out.println(a);
System.out.println(b);
结果是10 20,这说明b的变化并不会影响到a。
而数组默认是引用传递,赋的值是地址,赋值方式为引用传达。
int arr1[] = {1,2,3};int arr2[] = arr1;arr2[0] = 5;for(int i = 0;i<arr1.length;i++){ System.out.print(arr1[i] + " ");}for(int i = 0;i<arr2.length;i++){ System.out.print(arr2[i] + " ");}
结果是5 2 3 5 2 3 这说明arr1的值受到arr2的影响。
值传递发生在内存的“栈”中,一个变量直接复制的另一个变量的值。而对于数组来讲,其在栈中的数组名指向的是一个地址,而这个地址是在堆中的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xjWjyvmH-1633952250545)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210829092958569.png)]
如果数组想要实现值传递,应该用for循环进行赋值
int arr1[] = {1,2,3};int arr2[] = new int[arr1.length];for(int i = 0;i<arr1.length;i++){ arr2[i] = arr1[i];}
此时arr1和arr2是两个独立的空间。
内部排序指的是将数据全部加载到内存中进行排序,包括交换排序、选择排序、插入排序等
外部排序是指数据量过大,无法全部加载到内存中,需要借用外部存储进行排序,包括合并排序和直接合并排序
int a[][] = new int[5][5];for(int i = 0;i < a.length;i++){ for(int j = 0;j < a[i].length;j++){ System.out.print(a[i][j] + " "); } System.out.println();}
二位数组的定义和遍历的方法如上所示,注意两个for循环怎么写的。可以理解为二维数组的每个元素都是一个一维数组。其实在JVM内存中也是这样存放的,一个一维数组里面存的还是一个地址,这个地址指向具体的数值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rxZ4NgXX-1633952250546)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210829164734857.png)]
所以和一维数组一样,也是引用传递。所以二维数组并不是一段连续的空间,而是多个离散的小空间,而每个离散的小空间都是一个一维数组,一维数组里面各个数值的地址是连续的。高维数组也是一样的道理。
java中二维数组的列可以不确定,但是行必须要确定,int a[][] = new int[5] [ ];这样的声明方式是可行的,但是这样做只是相当于给一维数组开了个空间,并没有给二维数组开空间,即一维数组里面的值全是null,并不是地址。正确的使用方法如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RH9yCMYB-1633952250548)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210829170601812.png)]
注意for循环里面也要new一下
类是一个抽象,里面有属性(成员变量)和行为(成员方法)。类是可以嵌套定义的,即一个类中可以包含另一个类。
一个类可以创建出多个具有不同特点的对象,比如人这个类,可以实例化出张三、李四等多个具体的对象。
类是对象的模板,对象是类的实例。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RVGXW8cz-1633952250550)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210904220129401.png)]
如果是引用数据类型,堆里面存的是地址,如果是基本数据类型,堆里面存的是数值。
同样的,对于对象来讲,引用的时候也是引用传递
Person p1 = new Person();p1.name = "ming";p1.age = 20;Person p2 = new Person();p2 = p1;System.out.println(p2.name + " " + p2.age);
结果是ming 20
java内存的结构分析
栈:一般存放基本数据类型
堆:存放对象、数组等
方法区:常量池(常量、字符串)、类加载信息
类信息只会加载一次,就是当第一次new这个类的时候会加载,以后再new的时候不会重新加载了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zop83io4-1633952250551)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210905123506979.png)]
注意是在栈里面开辟新的独立空间
一个方法只能有一个返回值,如果需要返回多个数据的话,可以通过返回数组来返回多个值。
public int[] AA(int a,int b){}
另外,方法之中不能定义方法,即方法不能嵌套定义。同一个类中的一个方法可以直接调用其他方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qqpb6Gv5-1633952250552)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210905202747944.png)]
但是一个类中的方法想去调用另一个类的方法,需要new一个实例出来才能实现。而且要在第一个类的方法中new一个第二个类二点实例。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jcYCkVGw-1633952250553)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210905213600652.png)]
基本数据类型也是值传递,就是把参数在成员方法中又拷贝了一份,成员方法中参数的运算并不影响主方法中原来的数据。
引用数据类型在堆中存储,其作为参数传递的时候是引用传递,其实也就是将这一段堆内存的首地址拷贝过去了,也就是在成员方法中指向了这个引用数据类型,所以成员方法对其进行操作,也会影响到主方法。
可以通过形参修改实参。
注意每次使用一个成员方法的时候,都会在栈内新开辟一段内存空间。
同一个类中允许存在多个名称相同、传入参数不同(形参列表不一致)的方法,称为重载。
方法名必须相同,形参列表必须不同(参数个数、顺序、参数数据类型有一个不同),返回值类型无要求,注意参数名称无所谓。
java允许将同一个类中多个同名、同功能但参数个数不同的方法封装成一个方法,可以通过可变参数实现。
基本语法为:访问修饰符 返回类型 方法名(数据类型… 参数名称){}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RW59ll4N-1633952250555)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210907220928591.png)]
可变参数是为了方便方法重载使用的,使用可变参数的时候,参数里面的nums可以看作一个数组。
可变参数可以和普通参数放在同一个形参列表内,但是要注意把可变参数放在最后
全局变量不赋值就可以使用,因为有默认值。但是局部变量必须要赋值之后才能使用。
全局变量可以被本类使用,也可以被其他类使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mz4c5ASl-1633952250555)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210914094035172.png)]
全局变量可以加public等修饰符,局部变量不能加
构造方法(构造器)是类的一种特殊方法。构造器的作用:完成对新对象的初始化。
1、方法名和类名相同
2、没有返回值,也不能写void
3、在创建对象时,系统会自动的调用该类的构造方法完成新对象的初始化
public class Test { public static void main(String[] args){ Person a = new Person("mzw", 21); System.out.println(a.age); }}class Person{ String name; int age; public Person(String pName, int pAge){ name = pName; age = pAge; }}
构造器是一种特殊的方法,也能够进行重载
public Person(String pName, int pAge){ name = pName; age = pAge;}public Person(String pName){ name = pName;}
若不定义构造器,则会自动生成一个默认构造器,这个默认构造器是没有参数的。在定义了自己的构造器之后,默认构造器无法使用,若想重新使用无参构造器,则需要再次定义。
使用构造器创建对象的时候,还是先创建一个对象并默认赋值,复制完成之后再调用显示构造方法给其重新赋值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hM7nYpJf-1633952250556)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210916093652044.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DohK2HaK-1633952250557)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210916094008425.png)]
这里要注意的是基本数据类型直接赋值,而引用数据类型(如字符串)会存放在常量池里面。
使用javap命令可以将一个.class文件反编译成java代码
每个对象(在堆里面)都有个默认的this,其中存放的是这个对象本身的地址
哪个对象调用,this就代表哪个对象。有了this关键字之后,构造器的形参就可以和类里面的参数名称一样了,如下所示:
public class Test { public static void main(String[] args){ Person a = new Person("mzw", 21); Person b = new Person("mtong"); System.out.println(a.age); System.out.println(b.name + " " + b.age); }}class Person{ String name; int age; public Person(String name, int age){ this.name = name; this.age = age; } public Person(String pName){ name = pName; }}
this关键字可以访问本类的属性、方法、构造器
public class Test { public static void main(String[] args){ Person a = new Person(); }}class Person{ String name; int age; public Person(){ //通过无参构造器访问有参构造器 this("qqq",11); System.out.println("Person 的无参构造器"); } public Person(String name , int age){ System.out.println("Person 的有参构造器"); }}
如果要使用this去访问另一个构造器,那么this语句必须是第一条语句
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hm0loyBL-1633952250557)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210916140536178.png)]
包的本质就是创建文不同的文件夹来保存类文件,不同包下面的类可以同名,而同一个包下面的类不能重名
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZnNO57Kg-1633952250558)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210917213545101.png)]
java提供四种访问修饰符,用于控制方法和属性的访问权限。
public:对外公开
protected:对子类和同一个包中的类公开
默认:没有修饰符好,向同一个包的类公开,若子类在同一个包内,那么默认的也可以访问
private:只有类本身可以访问,不公开
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d2iemuGa-1633952250558)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210918092853906.png)]
修饰符可以用来修饰类中的属性、成员方法和类本身,注意只有默认和public才能修饰类本身
封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。
封装不但可以隐藏实现细节,还能够对数据进行验证
1、将属性私有化(private),不是将类初始化
2、提供一个公共(public)的set方法,用于对属性的判断和赋值
3、提供一个公共的get方法,用于获取属性的值
class Person{ public String name; private int age; public void setAge(int age) { if (age >= 1 && age <= 120) this.age = age; else System.out.println("年龄有误"); } public int getAge() { return age; } public void setName(String name){ if(name.length() >= 2 && name.length() <= 5) this.name = name; else System.out.println("名字长度不符合规定"); } public String getName(){ return name; }}
如果有构造器的话,可以直接将set方法写在构造器中。
public Person(String name , int age){ this.setName(name); this.setAge(age);}
为什么要继承? --提升代码的复用性
当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可。
public class pupil extends student{ public void say(){ System.out.println("pupil " + getName() + " say hello!"); }}
1、子类继承了父类所有的属性和方法,但是private属性不能直接在子类中访问,需要通过公共的方法(get、set)来间接访问
2、子类必须调用父类的构造器,完成父类的初始化
3、当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器(默认了一句super();被隐藏了,没有显示出来),如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过
父类的有参构造器如下(无参构造器被覆盖):
public student(String name, int age) { setName(name); setAge(age);}
子类的写法如下:此时要指定使用父类的有参构造器
public class college extends student{ public void say(){//子类专属方法 System.out.println("college" + getName() + "say hello!"); } public college(){//子类无参构造器 super("mzw",22); } public college(String name , int age){//子类有参构造器 super("mzw",22); }}
4、如果希望指定去调用父类的某个构造器,则显式的调用一下: super(参数列表) 同上 先有父亲才有儿子,所以要先调父类构造器
5、super使用时,要放到构造器的第一行,且super只能在构造器中使用,不能在其他成员方法中使用
6、super()和this()都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
7、Java中所有的类都是Object的子类,在IDEA中点击类,按住CTRL + H可以查看类的继承关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VEbpzuIT-1633952250559)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210919163843420.png)]
8、父类的构造器的调用不仅限于直接父类,要一直向上追溯到Object类(顶级父类)
9、java中子类最多只能继承一个父类,单继承机制
10、继承不能滥用,父类和子类直接必须满足“is - a”的关系,比如"Cat is an Animal".所以cat可以继承animal
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-52AliSOF-1633952250560)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210919171217852.png)]
程序执行的过程中,首先是在方法区加载我们的类,注意从Object开始加载,到Son结束
加载完成后,在堆里面分配空间
还是先从Grandpa类里面找,一直到Son类结束
子类访问属性的时候,首先要在子类本身当中寻找是否有此属性,若有且允许访问,那么就返回此属性。否则向其直接父类寻找,若有且允许访问,那么就返回此属性,若没有,继续向上找直到Object,若还是没有,会报错。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JGOnOunR-1633952250561)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210920091627006.png)]
public class Test { public static void main(String[] args) { Son son = new Son(); System.out.println(son.name); System.out.println(son.age); System.out.println(son.hobby); }}class GrandPa{ String name = "大头爷爷"; String hobby = "旅游";}class Father extends GrandPa{ String name = "大头爸爸"; int age = 40;}class Son extends Father{ String name = "大头儿子";}
这段代码的执行结果是:大头儿子、40、旅游
super代表父类的引用,用于访问父类的属性、方法、构造器
super可以访问父类的属性/方法,但不能访问父类的private属性/方法,使用 super.属性名/super.方法名 进行访问
访问父类的构造器上面已经说过,唯一要注意的是这句话只能放在子类构造器的第一句。
1、调用父类构造器的好处:分工明确,父类属性由父类构造器初始化,子类属性由子类构造器初始化
2、当父类方法(属性)和子类方法(属性)重名的时候,想访问父类方法必须通过super
直接调用方法,this.方法名,super.方法名三者的区别:直接调用和this一样,都是从本类开始一级一级向上找;super是直接从父类开始向上找,省去了找本类的这个过程。
访问属性也是一样的
3、super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。A->B->C
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YWeRugUW-1633952250561)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210920093822750.png)]
简单的说:方法覆盖(重写)就是子类有一个方法,和父类(有可能是更高级的父类)的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法。
public class Animal { public void jiao(){ System.out.println("动物在叫···"); }}public class Dog extends Animal{ public void jiao(){ System.out.println("狗在叫···"); }}
1、子类方法的形参列表、方法名称要和父类完全一样,不然不能叫重写
2、子类方法的返回类型和父类方法的放回类型一样,或者是父类方法返回类型的子类,例如:父类返回类型为Object,子类返回类型是String。
3、子类方法不能缩小父类方法的访问权限。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hjrfkyFi-1633952250562)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20210920212548553.png)]
学生类继承了人类,张三是一个学生实体,同时也是个人类实体,即张三这个人对应了多种状态。
1、方法的多态:重写和重载就体现了多态
2、对象的多态(核心)
注意事项:
(1)一个对象的编译类型和运行类型可以不一致,如Animal animal = new Dog(); 用父类引用子类对象,animal的编译类型是Animal而运行类型是Dog。
(2)编译类型在定义对象的时候就就确定了,不能改变。
(3)运行类型是可以变化的 animal = new Cat();
(4)编译类型看 = 的左边,运行类型看右边
多态的前提:两个对象(类)存在继承关系
多态的向上转型:
1、本质:父类引用指向了子类对象
2、语法:Animal animal = new Dog(); Object也是可以的
3、特点:编译类型看左边,运行类型看右边;可以调用父类中的所有成员(须遵守访问权限),不能调用子类中的特有成员。(因为能调用哪些成员是在编译过程中由编译类型决定的,最终运行效果看运行类型,找方法的时候还是从子类开始一级一级往上找)
多态的向下转型:
1、语法:子类类型 引用名 = (子类类型) 父类引用 说白了就是个强制转换
2、只能强转父类的引用,不能强转父类的对象(对象本身是不会变化的)
3、要求父类的引用必须指向的是当前目标类型的对象(父类对象本来指向的就是当前目标类型才能转)
Animal animal = new Cat();Cat cat = (Cat) animal;
4、当向下转型后,可以调用子类类型中所有的成员
属性没有重写之说,属性的值直接看编译类型,和方法从运行类型开始查找不一样。
使用instanceof比较操作符,能够半段对象的运行类型是否为XX类型或者XX类型的子类型
当调用对象方法的时候,该方法回合该对象的内存地址(运行类型)绑定。
当调用对象属性时,没有动态绑定机制,哪里声明哪里使用。
public static void main(String[] args) { Person p = new Student(); //Person的ii是100,Student的ii是19,Student继承Person //如果子类和父类都有此方法,调的是Student里面的(运行类型),如果子类没有,那还是会从子类开始一级一级向上找 System.out.println(p.getIi());//19 //如果子类父类都有ii这个属性,那么要看p的编译类型 System.out.println(p.ii);//100}
public static void main(String[] args) { Person persons[] = new Person[5]; persons[0] = new Person("Person1",2); persons[1] = new Student("Stu1",10,100); persons[2] = new Student("Stu2",11,99); persons[3] = new Teacher("Tea1",30,5000); persons[4] = new Teacher("Tea2",40,6000); for(int i = 0 ; i < 5 ; i ++){ //persons[i]的编译类型是Person,运行类型不同 persons[i].say();//动态绑定机制 }}
使用父类数组存储子类对象,那么如何使用子类特有方法呢?(提示:instanceof + 强制类型转换)
for(int i = 0 ; i < 5 ; i ++){ if(persons[i] instanceof Student ){//判断persons[i]的运行类型是不是Student类或其子类 ((Student)persons[i]).study(); } if(persons[i] instanceof Teacher ){//判断persons[i]的运行类型是不是Teacher类或其子类 ((Teacher)persons[i]).teach(); } persons[i].say();//动态绑定机制}
多态参数:方法的传参类型定义为父类类型,实参可以是子类类型
public void getinfo(Person p){ if(p instanceof Student){ System.out.println("这是个学生"); } else if(p instanceof Teacher){ System.out.println("这是个老师"); }}
equals和==的比较
== 是一个比较运算符
1、 == 既可以判断基础类型,又可以判断引用类型
2、 == 判断基础类型的时候,判断值是否相等
3、 == 判断引用类型的时候,判断地址是不是相等(判断是不是同一个对象)
equals是Object类的一个方法,只能判断引用类型
Object类中的equals判断的也是地址是否相等,但是其子类往往重写这个方法来判断值是否相等
1、hashCode的作用是提高具有哈希结构的容器的效率
2、多个引用,如果指向的是同一个对象,则哈希值一定一样
3、两个引用如果指向的对象不同,则哈希值一定不同
4、哈希值主要根据地址号来的,但是哈希值不能完全等价于地址
Object默认返回:全类名(包名+类名)+@+哈希值(hashCode)的十六进制
子类往往重写toString方法,用于返回对象的属性信息,当我们直接输出一个对象时,toString方法会被默认调用。
1、当对象被回收时,系统会自动调用对象的finalize方法,子类可以重写该方法做一些释放资源的操作。
2、什么时候被回收:当某个对象没有任何引用时,jvm就会认为这个对象是个垃圾对象,会使用垃圾回收机制来销毁该对象,在销毁对象前,会先调用finalize方法。
3、要注意垃圾回收的时候,并不是产生垃圾了就立刻回收,而是有一套自己的算法。可以使用System.gc()来主动调用,当然,调用也不一定会成功,能否成功取决于多种因素。
在实际开发中,几乎不会重写这个方法。
提示:在断点调试过程中,是以对象的运行类型来执行的
1、断点调试是指在程序的某一行设置-个断点,调试时,程序运行到这一行就会停住,然后你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。进行分析从而找到这个Bug
2、断点调试是程序员必须掌握的技能。
3、断点调试也能帮助我们查看java底层源代码的执行过程,提高程序员的Java水平。
F8 单步调试,不进入函数内部
F7 单步调试,进入函数内部
Shift+F7 选择要进入的函数
Shift+F8 跳出函数
Alt+F9 运行到断点
Alt+F8 执行表达式查看结果
F9 继续执行,进入下一个断点或执行完程序,可以在debug的过程中动态下断点
Ctrl+F8 设置/取消当前行断点
Ctrl+Shift+F8 查看断点
类变量是一个类的所有对象共享的变量,在类加载的时候就生成,关于静态变量的位置,说法很多,但是
(1)静态变量在这个类对应的class实例的尾部,而class实例在堆里面(JDK8及之后)
(2)静态变量在方法区中一个叫静态域的区域中(JDK8之前)
定义方法:public(访问权限) static int(数据类型) a(变量名)
访问方法:(1)类名.类变量名(静态变量在类加载的时候就生成,不依赖于对象实例) 推荐
(2)对象实例.类变量名
类变量的生命周期随着类加载开始,随着类消亡结束
同样的,在普通方法的访问权限修饰符之后加上static,这个方法就变成了静态方法,静态方法只能访问静态变量(方法),但是非静态方法同样可以访问静态变量。同样,静态方法也能通过类名和对象名两种方式来访问。类方法中,不能使用this和super关键字。
静态方法的使用场景:各种工具类居多,如Math.abs()。这个方法就是直接通过类名调用求绝对值的方法。
main方法的形式:public static void main(String[] args)
1、main方法是java虚拟机在调用 ,所以一定是public , 因为java虚拟机和main方法不在同一个类。
2、java虚拟机在调用main方法的时候不需要创建对象,所以一定是static
3、该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数。
4、各个参数是在程序执行的时候传进去的,传进去的各个参数构成一个字符串数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cm1ewQKe-1633952250563)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20211010090630206.png)]
由于main方法是静态方法,所以main方法可以直接调用其所在类的静态成员,但不能访问非静态成员。若想访问非静态成员,则必须实例化一个对象,通过对象去访问。
代码块是类中的成员(类的一个组成部分),将逻辑语句封装到{}中,但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
基本语法: [static]{ 代码 };
相关说明:
1、static是可选的,不是必选的。有static的修饰的成为静态代码块,否则称普通代码块(非静态代码块)
2、打括号中可以是任意的逻辑语句
3、大括号后的分号可以省略
代码块相当于对构造器的一种补充机制,可以做初始化操作,且代码块的执行顺序优先于构造器,因此在多个构造器有冗余代码的时候,可以抽象到代码块中。
如果是静态代码块,作用就是对类进行初始化,随着类的加载而执行,且只执行一次。如果是普通代码块,在创建(new)对象的时候执行,每实例化一个对象就执行一次,和类加载没什么关系,但是只使用类的静态成员的时候,普通代码块不会执行。
1、创建对象实例的时候(new)
2、使用子类对象实例,父类也会被加载,而且父类先被加载
3、使用类的静态成员(静态属性、静态方法)的时候
1、调用静态代码块和静态属性初始化(注意,二者优先级相同,若有多个静态代码块和多个静态变量初始化,则按照他们定义的顺序调用)
2、调用普通代码块和普通属性初始化(同上,若有多个,按照顺序)
3、调用构造方法,构造器最前面隐含了super()和调用普通代码块,因此是先走super()调用父类的相关东西,然后回来调用自己的普通代码块,再然后是自己的构造器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vlly17qg-1633952250564)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20211010155800262.png)]
静态代码块只能调用静态成员,普通代码块能够调用静态和非静态成员
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lgu9y4io-1633952250568)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20211010162612610.png)]
什么是单例设计模式?
单例 = 单个的实例,采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
单例模式有两种:饿汉式和懒汉式
实现步骤如下:
1、构造器私有化(private) --》 防止直接new
2、类的内部创建对象
3、向外暴露一个静态的公共方法(getInstance)
public class ehan { public static void main(String[] args) { //直接通过方法可以获取对象 SingleClass instance = SingleClass.getInstance(); System.out.println(instance); }}class SingleClass{ private String name; private SingleClass(String name){//构造器私有化,防止直接new一个对象 this.name = name; } //在类的内部直接创建对象,为了能够在静态方法中返回这个singleclass对象,需要将其修饰为static private static SingleClass singleclass = new SingleClass("饿汉01"); public static SingleClass getInstance(){ return singleclass; }}
为什么叫饿汉式?因为有可能这个对象并没有被使用,但是已经被创建好了,有点着急,所以起名叫饿汉式。也正是因为如此,所以饿汉式有可能造成资源的浪费,因为单例往往都是重量级的对象。
在javaSE标准类中,java.lang.Runtime就是经典的单例模式
public class lanhan { public static void main(String[] args) { GirlFriend instance = GirlFriend.getInstance(); System.out.println(instance); }}class GirlFriend{ private String name; private GirlFriend(String name){//构造器私有化,防止直接new一个对象 this.name = name; } //在类的内部直接创建对象,为了能够在静态方法中返回这个gf对象,需要将其修饰为static private static GirlFriend gf;//因为在这里只是定义而并没有使用,所以其构造器并不会被调用 public static GirlFriend getInstance(){ if(gf == null){//如果不存在的话,就创建一个 gf = new GirlFriend("懒汉01");//这里构造器才真正被调用 } return gf; }}
只有当用户使用getInstance()的时候才会创建一个对象,当用户再次调用getInstance()方法时,会返回上次实例化的对象。懒汉式相对于饿汉式,不会造成资源浪费,但是存在线程不安全的问题。
final可以修饰类、属性、方法、局部变量
在某些情况下,程序员有相关需求有可能使用到final:
1、当某个类不希望被其他类继承的时候,可以用final修饰
2、当不希望父类的某个方法被子类**覆盖/重写(Override)**的时候,可以使用final关键字修饰
3、当不希望类的某个属性的值被修改,可以用final修饰
4、当不希望某个类的局部变量被修改,可以使用final修饰
final在大多数修饰的情况下,可以被理解为常量,只能赋值一次,不能再修改。但是如果它修饰的是引用类型的变量,那么它所指向的对象则可能改变。虽然不能让其指向其它对象,但是它指向的这个对象本身的内容还是可以修改的。
1、final修饰的属性在定义时,必须赋初值,且以后不能再修改。赋值的位置可以有如下几种:
(1)在定义的时候直接赋值 public final int RATE = 0.08
(2)在构造器中
(3)在构造方法中
class AA {
public final int A = 10;
public final int A1;
public final int A2;
public AA() {
A1 = 100;
}
{
A2 = 1000;
}
}
2、如果final修饰的属性是静态的(static),那么它只能直接赋值或者在静态代码块中赋值,不能在构造器中赋值
3、如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
4、final不能修饰构造器
5、final和static往往搭配使用,效率更高,因为不会导致类的加载,底层编译器做了优化处理
public class Final_ {
public static void main(String[] args) {
System.out.println(AA.A);
}
}
class AA {
public final static int A = 10;
static{
System.out.println("静态代码块被执行。。。");
}
}
这段代码的运行结果是10,没有那句静态代码执行的提示语。
6、包装类(String、Double等)是final修饰过的,不能再被继承。
当父类的某些方法,需要声明,但是又不确定如何实现的时候,可以将其声明为抽象方法,那么这个类就是抽象类。所谓的抽象方法,就是没有实现的方法(没有大括号{} ),也就是说没有方法体。当一个类中存在抽象方法的时候,这个类必须也是抽象类。
一般来说,抽象类会被继承,由其子类来实现这个抽象方法。
1、抽象类不能被实例化。
2、抽象类不一定要包含abstract方法。
3、一旦一个类包含了抽象方法,那么这个类一定要是抽象类。
4、abstract只能修饰类和方法,不能修饰其他的。
5、抽象类可以有类的任意成员(抽象类的本质还是个类),如:构造器、静态属性等。
6、抽象方法不能有方法体(大括号)
7、如果一个非抽象类继承了抽象类,那么它必须实现抽象类的所有抽象方法。若用抽象类继承其它抽象类,则无该要求。
8、抽象方法不能使用private、final、static来修饰,因为抽象方法就是用来重写的,而这三个关键字都是和重写无关的。
可以将多个类中的相同方法写成一个抽象类,类中不同的方法在这个抽象类中用抽象方法,然后让每个类去继承抽象类,实现各自的方法即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ttmAQ18i-1633952250571)(C:\Users\86135\AppData\Roaming\Typora\typora-user-images\image-20211011100638047.png)]
接口就是给出一些没有实现的方法,封装到一起,某个类要使用的时候,根据具体情况实现相关方法。语法如下:
public interface USB {
//属性
//方法
}
class phone implements USB{
//独有属性
//独有方法
//必须实现的接口的抽象方法
}
JDK7及之前,接口中的方法必须全是抽象方法,不能有方法体,但是JDK8及之后,可以有默认实现方法(default关键字修饰)和静态实现方法(static关键字修饰)
public interface USB {
//属性
public int i = 10;
//方法
public void test();//抽象方法,可不用abstract关键字修饰
default public void hi(){//default
System.out.println("666");
}
static public void hello(){//static
System.out.println("777");
}
}
注意在接口中,抽象方法可以省略abstract关键字。
1、接口不能被实例化
2、接口中的方法都是public,接口中的抽象方法,可以不用abstract关键字修饰。
3、一个普通类实现接口,就必须将该接口的所有方法都实现。但是如果接口中有默认或静态实现方法,则普通类可以不重写此方法。
4、如果使用抽象类实现接口,则可以不用实现接口中的所有方法。
5、一个类同时可以实现多个接口,多个接口之间用逗号隔开。多个接口中有相同名称、相同返回类型的方法,在类中也只需要实现一次。
6、接口中的属性只能是final的,且是public static final类型的
7、接口中属性的访问形式:接口名.属性名
8、接口不能继承其它的类,但是可以继承(extends,不是implements)多个接口
实现接口是对java单继承的一种补充机制,一个类可以同时实现继承和实现。
当子类继承了父类,就自动的拥有父类的功能,如果子类需要扩展功能,可以通过实现接口的方式扩展。
继承的价值:解决代码的复用性和可维护性
接口的价值:设计好各种规范(方法),让其他类取实现这些方法。也就是说更加灵活。
接口能够在一定程度上实现代码解耦(接口规范性+动态绑定)
若某个方法的形参是接口类型,那么其可以接收实现了该接口的对象实例。同样的,一个接口类型的数组,也可以接收实现这个接口的类的实例化对象。但是在调用实现类的特有方法的时候,需要向下转型。
举个栗子:
public class IF {
public static void main(String[] args) {
A x1 = new b();
B x2 = new b();
}
}
interface A {}
interface B extends A {}
class b implements B{
}
接口B继承了接口A,b类实现了接口B,那么b的实例化对象也可以被A引用。实际上也就相当于b这个类也实现了接口A。