java学习记录第一部分(基本语法-->接口)

Java学习

java开发的一些事项

1、每个源文件的基本组成部分是类class,java应用程序执行的入口是main方法,public static void main(String[] args){……}

2、一个源文件中最多只能有一个pblic类,其他类的数量不限,且文件名必须和此public类名称一样。

3、每一个类编译后,都会生成相应的.class文件。

4、main方法也可以写在非public类中,这样在运行该非public类的时候,入口方法就是这个非public类的main方法。

JDK、JRE、JVM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 因为加号是从左到右运算的

java的数据类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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。所以在我们判断两个小数是否相等时,应该是两个数差值的绝对是小于某一个阈值就可以判断相等

Java类的组织形式

一个JDK下面有很多个包(package),一个包下面有很多接口、类、异常、枚举,一个类下面有很多字段、构造方法、成员方法。

Char类型

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个字节表示一个汉字)

Boolean类型

注意一点,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)

Java标识符的命名规范

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的区别

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就代表哪个对象。有了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)]

包(package)

包的本质就是创建文不同的文件夹来保存类文件,不同包下面的类可以同名,而同一个包下面的类不能重名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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代表父类的引用,用于访问父类的属性、方法、构造器

super可以访问父类的属性/方法,但不能访问父类的private属性/方法,使用 super.属性名/super.方法名 进行访问

访问父类的构造器上面已经说过,唯一要注意的是这句话只能放在子类构造器的第一句

一些细节

1、调用父类构造器的好处:分工明确,父类属性由父类构造器初始化,子类属性由子类构造器初始化

2、当父类方法(属性)和子类方法(属性)重名的时候,想访问父类方法必须通过super

直接调用方法,this.方法名,super.方法名三者的区别:直接调用和this一样,都是从本类开始一级一级向上找;super是直接从父类开始向上找,省去了找本类的这个过程。

访问属性也是一样的

3、super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。A->B->C

super和this的比较

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

方法的重写(覆盖) override

简单的说:方法覆盖(重写)就是子类有一个方法,和父类(有可能是更高级的父类)的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法。

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、子类方法不能缩小父类方法的访问权限。

方法重写(override)和重载(overload)的比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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类型的子类型

java的动态绑定机制(非常重要)

当调用对象方法的时候,该方法回合该对象的内存地址(运行类型)绑定。

当调用对象属性时,没有动态绑定机制,哪里声明哪里使用

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("这是个老师");    }}

Object类详解

equals方法

equals和==的比较

== 是一个比较运算符

1、 == 既可以判断基础类型,又可以判断引用类型

2、 == 判断基础类型的时候,判断值是否相等

3、 == 判断引用类型的时候,判断地址是不是相等(判断是不是同一个对象)

equals是Object类的一个方法,只能判断引用类型

Object类中的equals判断的也是地址是否相等,但是其子类往往重写这个方法来判断值是否相等

hashCode方法

1、hashCode的作用是提高具有哈希结构的容器的效率

2、多个引用,如果指向的是同一个对象,则哈希值一定一样

3、两个引用如果指向的对象不同,则哈希值一定不同

4、哈希值主要根据地址号来的,但是哈希值不能完全等价于地址

toString方法

Object默认返回:全类名(包名+类名)+@+哈希值(hashCode)的十六进制

子类往往重写toString方法,用于返回对象的属性信息,当我们直接输出一个对象时,toString方法会被默认调用。

finalize方法

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方法

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可以修饰类、属性、方法、局部变量

在某些情况下,程序员有相关需求有可能使用到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修饰过的,不能再被继承。

抽象类(abstract)

当父类的某些方法,需要声明,但是又不确定如何实现的时候,可以将其声明为抽象方法,那么这个类就是抽象类。所谓的抽象方法,就是没有实现的方法(没有大括号{} ),也就是说没有方法体。当一个类中存在抽象方法的时候,这个类必须也是抽象类

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

使用细节

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)多个接口

继承(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。

你可能感兴趣的:(java,java)