【零基础 快速学Java】韩顺平 零基础30天学会Java

1、java跨平台的原因
java文件利用javac命令编译生成相应的class文件,利用java命令通过java虚拟机(JVM)解释运行。JVM屏蔽了底层OS的区别,实现了“一次编译,全平台执行”的功能。JVM包含在JDK当中,JDK版本代表着java版本。

2、JDK中有什么东西?


3、JDK安装细节
安装路径不能含有中文或者特殊字符比如空格
公共JRE最好安装,某些IDE可能会用到
4、配置环境变量path

首先,设置一个变量JAVA_HOME,对应的值设置为JDK安装主目录的绝对路径。
然后,在path中,加入一个值“%JAVA_HOME%\bin”。
如果以前配置过JDK的话,需要删除之前的缓存,见书签https://www.cnblogs.com/lastaz3/p/14853291.html。
5、使用sublime和javac编码问题
cmd使用的是GBK编码,所以,编辑器sublime也需要以GBK的方式保存java文件,这样cmd读取的时候才不会出错。
6、文件名和类名
一个文件一个类,文件名和类名需要完全相同,如果一个文件里的类名为hello,那么源文件就叫hello.java,编译后的文件叫做hello.class,这点和C语言不同。

7、执行的是类而不是文件
执行时,输入java hello而不是java hello.class,因为你本身运行的就是一个类,而不是class这个文件,所以不需要再加上class

8、java执行流程



9、java helloworld的注意事项和细节说明

一个.java文件只能有一个public类,但其他类的个数不限,.java文件名和public类名必须相同。编译后,每一个类都对应一个.class文件。
在一个文件中,写多个类,只有一个public类

经过编译后,每个类生成一个.class文件,文件名和类名相同

调用非public类的main方法,如下

10、快速学习新技术或者知识点的心得分享

11、几个比较特殊的转移字符
println引号内的斜杠\,也需要使用转义字符\\,如果要打印两个斜杠\\,则需要两个转义字符,共四个斜杠\\\\
英文双引号也一样,需要将左引号和右引号都换成转义字符\"。中文双引号不用。英文单引号需要使用转义字符,中文单引号不用。

注意看上图的代码,在第一部分,可以看到\t为什么叫制表符,打印效果确实如同表格一般对齐了。
第二部分,则是英文引号和中文引号的区别,英文引号需要使用转义字符,而中文不用。
12、回车与换行的区别
回车,转义字符是\r,作用是将光标移至本行最开头位置,后面的输出会从该位置继续输出。
换行,转义字符是\n,换到下一行最开头。
回车+换行 = 换行,也相当于敲一下回车键。
13、初学java易犯错误

路径问题注意相对路径的问题
14、java代码规范

对齐需要使用缩进,而不是空格
源文件需要使用utf8编码
大括号的两种风格:按照大括号的左边的位置,分为次行风格和行尾风格(推荐)
15、第二章作业

16、java中的+号的用法
数值+数值=数值
操作数有字符串,则做拼接运算。
运算顺序从左往右。
如果有多个加号,只要有一个字符串,那么从字符串往左的数值做加法,往右的做拼接。

17、java数据类型
基本数据类型8个,如下:
整型 byte[1],short[2],int[4],long[8]
浮点型 float[4],double[8]
字符型 char[2],存放单个字符,中英文均可,java采用utf16编码,任何字符都可以用2个字节编码
布尔型 boolean[1],true或者false
引用数据类型包括数组、类、接口
18、整型


第二点需要注意,如果直接将一个long类型的常量1L或变量long a赋值给int变量b,编译会报错,C则会警告

19、浮点类型



20、java类的组织形式

按照上图结构,要找某个类有哪些方法,需要知道这个类在哪个包中,再按照包->类->方法查找。
或者下载的JDK官方文档中有索引,可以直接查找
21、字符类型



虽然char这个类型本质上是对应字符的unicode编码,是一个数字,但如果不进行强制类型转换,只会输出unicode编码对应的字符,而不是那个数。

如果字符遇到了加号,相当于整数遇到了加号
如果字符串遇到了加号,则是拼接操作了。

22、常用的字符编码




23、布尔类型


24、自动类型转换规则







注意,同类型的byte进行计算,也会先转换成int,结果不能再赋给byte


25、强制类型转换规则
程序员需要主动进行强制类型转换,否则会报错。强制类型转换会造成溢出或者精度损失。

26、基本数据类型和字符串之间的转换






27、关于加号的一个题目

注意两个char相加,结果是int类型,输出码值
28、取模%的本质(重要)


貌似结果的正负与左边操作数符号相同?

29、i = i++

这个问题需要看JVM的运行过程,也就是字节码的底层实现逻辑。这里可以用优先级和栈来解释。
根据栈的先进后出的规则,对于优先级低的运算,其参数先压栈,后参与计算,后出栈;优先级高的运算,其参数后压栈,先参与计算,先出栈。
而++的优先级高于=,
这个表达式的具体执行过程如下:
首先,将i的值压栈,=的操作数入栈了,等待++完成后就出栈,
然后,自增操作将i的值+1,自增操作完成,
然后,=操作开始了,将栈顶的值赋给变量i,=操作的结果把自增的结果给覆盖掉了,
所以,i = i++ 的结果是i的值不变。
i = ++i 与上述不同的是先将变量自增,然后压栈,出栈。

上面的结果???怎样用栈来实现算术符号优先级???编译原理???
30、关系运算符 instanceof
检查是否有一个实例,是一个类的对象。会在OOP中讲解
31、关系运算符的细节说明

32、逻辑运算符(重要)


C语言中,
&&和||,这两个叫做逻辑与、逻辑或,这两个运算符常用来对两个操作符整体的逻辑进行运算,操作数一般是关系表达式,比如a<b这种。存在短路效应。
&和|,这两个叫做按位与、按位或,常用于位运算,操作数一般是两个真实的变量,比如a&b、a|b,结果一般是一个数
与C不同,java中不可以用0或者非0的数来表示true或者false。
所以,||和&&的操作数必须是boolean类型的常量或者变量,最后结果也必须是boolean。
但|和&的操作数可以是boolean,也可以是整形常量或变量。前一种情况和||、&&的运算一样,结果一样,后一种情况则是二进制按位运算,结果是个整数。
总结成这个表格

取反!a的操作数只能是boolean,不能用于按位取反,按位取反为~a
异或a^b的操作数既可以是boolean,也可以是整数
33、一道关于逻辑运算符的练习题

34、复合赋值运算符会进行类型转换

35、运算符的优先级

36、标识符的命名规则和规范(重要)


37、保留字

38、四种进制

39、原码、反码、补码(重点)

注意,补码是计算机“看”的数,原码是人“看”的数。我们看到的数(屏幕上打印的结果)都是原码,但我们分析计算过程时,需要将原码转换成补码再进行分析,这时得到的结果是补码,需要再转换成原码,才能给人看。
注意,java没有无符号数,全都是有符号数!!!这点与C不同!!!
40、位运算符
计算过程:
原码变补码
按位操作
补码变原码,原码就是结果
注意,按位与、按位或、按位取反、按位异或,都需要对补码的符号位进行操作。

41、第三章 运算符 作业


(第二题不考虑了)


42、switch用法

注意,switch()里面不能放boolean!!!
case后面跟着的多条操作不需要使用大括号

43、编程的两大思想
化繁为简:将需求分解,逐步实现,将复杂功能转换成基本功能+过滤器
先死后活:为了方便程序后续的修改,将常量用变量表示(封装、宏都是这种思想)
44、do……while
do……while 先执行一次操作,再进行条件判断,操作至少执行一次;
while和for都是先判断,后操作,不一定会执行操作。
但是,循环条件是一样的!
do{}while(表达式1),
while(表达式2),
for(;表达式3;),
同一需求对应的条件表达式是一样的!!!
45、break的用法

break + 标签的用法,与C不同
46、continue用法

有这个用法,但不推荐使用。
47、数组的初始化
动态方式:int[] a = new int[5];
int[] a; a = new int[5];(先声明再new)
静态方式:int[] a = new int[]{1,3,7,4,……};
静态省略方式:int[] a = {1,3,7,4,……};
动态方式根据[]内的数字为数组动态分配元素个数,
(一个很重要的区别:[ ]内的数字可以是变量,C中只能是常量)
静态方式根据{}内的元素个数确定元素个数,[]中不能写数字,
静态方式和静态省略方式创建数组不能先声明后赋值,只能声明的同时直接赋值。
数组的长度(元素个数)可以通过 数组名.length 来得到。
java中的数组下标从0开始,和C一样。
“int[] a = new int[5];” 也可以写成
"int a[] = new int[5];",前者比较清晰,后面学到二维数组、字符数组可能会更清楚一点。
48、数组的使用细节

49、数组的赋值方式

java的数组直接可以用等号赋值,是引用赋值,实际上是将arr1的地址赋给了arr2。arr1和arr2都相当于指向数字内存地址的两个符号链接。(arr2没有new,没有开辟新的空间)
注意值传递和引用传递的区别,联想到C的函数传参(传值or传地址)

注意!!!java中的数组是在堆中开辟空间存储的!!!栈中只存放指向对应内存的地址链接。

https://www.cnblogs.com/wangyage/p/7434703.html
传值的方式则需要重新new一个数组,用for循环完成数组拷贝
50、数组扩容和缩小
重新new一个数组,用for循环进行数组拷贝,将原数组指向新数组。但这样操作效率太低,可以考虑链表。
51、二维数组的初始化

法一:int arr[][] = new int[2][3];(或者 int[][] arr = new int[2][3];)
法二:int[][] arr; arr = new int[2][3];
法三:如下图

法四:静态初始化
int[][] arr = {{1,2}, {1}, {1,2,3}}; //一维数组的元素数可以不同


52、类与对象简介

由于单独变量和数组有以上缺点,为满足新的需求,创造了对象这个概念。对象的主要内容包括属性和行为。
类与对象的关系:类就像变量类型,是模板,是共性;对象就像是具体的变量,是实例。
属性,也叫成员变量 / 字段 / field
53、对象在内存中的存在形式



创建对象时,
首先,会加载类的信息到方法区,主要是属性和行为信息。
然后,为对象分配空间。在堆中,为基本类型的变量分配空间,存储变量的值;在方法区的常量池中,为字符串分配空间,存储字符串的值,相应地会在堆中存储常量池中字符串的地址。
最后,栈中存储着堆中对象空间的地址。
(有点一二级指针的意思)
54、创建对象的两种方式
先声明再创建
Cat cat;
cat = new Cat();
直接创建
Cat cat = new Cat();
55、对象的引用传递

56、null
null表示空指针,目前出现在
String已定义未赋值时,默认值为null
对象已声明未分配空间时,默认值为null
57、成员方法的介绍


58、二维数组作参数

59、成员方法的注意事项和使用细节


需要注意的点就是返回类型和参数类型都可以是类(具体方法?)或者数组,数组不需要像C考虑指针的问题了,比较方柏霓
同一个类中的方法不能嵌套定义。

同类方法调用:同一类中的一个方法可以直接调用该类的另一个方法。
跨类方法调用:调用发起方需要创建被调用类的对象,然后再通过 “对象.方法” 进行调用。
60、成员方法的传参机制
基本数据类型的传参和修改,不会影响调用者中原变量的值
引用数据类型(数组和对象)传递的是地址,可以通过形参影响实参。
放一个例子

(上面的调用其实有点别扭,使用成员方法时不需要将对象传参,下面比较正常)

比较上面两个成员方法及调用过程,得出使用成员方法的时候,发起调用的对象已经隐藏地成为了形参之一,无需声明,可以直接对成员变量进行修改。
61、递归的规则

62、方法重载
方法名必须相同,形参(类型或者数量)必须不同,返回值类型无所谓
方法名和参数列表共同构成了方法签名。通过方法名和参数列表可以唯一确定一个方法,所以同一个类中,没有哪个方法的方法名和参数列表是完全一样的。

println()就是重载的方法,后面的形参可以是不同类型的变量,这样就可以将多个不同操作的方法集合到一个方法名之下。将相似有微小区别的功能集成,方便使用。
63、可变参数

可变参数也可以用方法重载实现,但可变参数更简洁。

注意,可变参数可以和固定参数一起出现在参数列表中。但是必须固定参数在前,可变参数在后,且只能有一个可变参数。
可变参数对应的实参可以是一个个单个的变量,也可以是一个该类型的数组。

可变参数的本质是数组。
64、作用域



65、构造方法/构造器
构造方法又叫构造器(constructor),是类的一种特殊的方法,主要作用是完成对新对象的初始化,在创建对象时,系统会自动调用该类的构造器完成对象的初始化。

构造器可以重载。
构造器只是完成对象的初始化,而不是创建对象,对象空间已经分配好了。对象没有自定义的构造器也可以。

未定义构造器时,会执行默认构造器,Person() {}。
有构造器时,也可以显式地写出该默认构造器,相当于构造器重载,不会引起方法重复的冲突。
但是,有自定义构造器而没有显式定义默认构造器时,new的成员必须要带自定义构造器的参数,不能无参,否则报错。这时候类中就没有默认构造器了,这就是“覆盖”的含义。(如下图)



显式定义默认构造器的时候,可以写方法体。

66、对象创建流程分析
首先,new创建对象,
将类信息加载到方法区,
在堆中为对象开辟空间,存储基本变量类型的值,存默认值,
数组的创建流程不变,只是最后数组的内存地址会放入堆中对象分配给数组的那个内存空间中,
将字符串类型的数据放入方法区的常量池中(有的话),将对应地址放入堆中对象非陪给字符串的内存空间,字符串没初始化的该位置置为null,
最后,将堆中的对象内存地址放入栈中,
对象创建完成。
之后,通过构造器进行成员变量的初始化
67、this
this应用场景:
全局变量和局部变量同名且同时出现在局部变量的作用域内,用this.name来指代全局变量,用name表示局部变量。解决了一个作用域内变量不能重名的问题。
当然,不管是否变量重名,都可以使用this。
this的使用范围也不光限定在构造器。
对this的解释:
this相当于一个对象的隐藏属性,存储着对象的内存地址,指向对象自己。
哪个对象调用,this就指代那个对象。看到this,自动转换成对象名就行。

this注意事项:


this的一个典型用法:涉及两个对象,用this指代调用者。

68、一个关于作用域的错误

69、IDEA快捷键



模板快捷键:
main
fori
sout
70、包
包的本质:
通过创建不同的文件夹,保存不同的类 。
包的简单使用:
属于包的java文件需要在开头声明所在包: package 本包名。
一个类只能有一个package语句。
调用包中类的两种情况:
不属于包的java文件,开头通过import调用其他包里的类。
没有用import声明的,在new的时候类名前面必须带上包名
举个例子:

有两个包,com.lyl 和 com.shy,
有两个包中类com.lyl.TestPackageLyl 和 com.lyl.TestPackageShy,
两个类都有一个名为pout的方法。


在java文件中,各创建这两个类的一个对象,并调用pout() 方法。
一个是import的,一个是没有import的

可以看到,经过import的包,可以直接用类名,
没有import的包,必须在类名前加上报名,要写完整。
71、包的命名
命名规则:
与变量命名类似,但包可以用小圆点区分上下级目录,类似文件路径的/
命名规范:

72、常用的包

73、包的使用细节


顺序为:package、import、class
74、import用法
1、import语句用来完成导入其它类,同一个包下的类不需要导入,不在同一个包下需要手动导入。
2、import语法格式:
import 类名;
import 包名.*;
3、import语句需要编写到package语句之下,class语句之上。
4、java.lang.*:lang:language语言包,是java语言的核心类。不需要手动引入,系统自动引入。
5、什么时候需要import?
不是非java.lang包下,并且不在同一个包下的时候,需要使用import进行引入。
75、访问修饰符

注:子类指的是不同包中的子类,同包的子类算在同包中


只有默认、public可以修饰类,四种访问修饰符都可以修饰对象的成员和方法,规则相同。
76、封装
只能通过对象的方法对对象的属性进行操作,而不可以直接对属性进行修改,对属性的修改行为仅限于提供的public方法。

getter setter快速创建的快捷键:alt + insert
77、将构造器与setter结合:
上述的散步封装还有一个漏洞:如果调用者在new的时候直接调用构造器,那么就可以绕过setter方法的验证逻辑,进行不合法的操作;
将构造器中的复制操作也调用setter操作即可。
78、继承
将多个类中共同的属性和方法提取出来,作为父类。子类通过extends关键字继承父类的共性,只需要添加自己的特有属性和方法。提高代码的复用性。
子类也叫派生类,父类也叫基类、超类。

继承的基本语法:
class 子类 extends 父类 {}
79、继承的细节问题
(1)子类继承了所有的属性和方法,非私有属性和方法可以在子类直接访问,但私有属性和方法不能在子类直接访问,要通过父类提供的非私有的方法去访问。
(2)子类必须先调用父类的构造器,完成公共部分(父类)的初始化。然后调用子类的构造器,完成子类特有属性的初始化。子类中隐藏了super(); 这句指令,由编译器负责。
(3)创建子类时,不管调用子类的哪个构造器,默认都先会调用父类的无参构造器。如果父类没有提供无参构造器(只有有参构造器,没有显式定义无参构造器),则必须在子类的每一个构造器中都使用 super(父类构造器的参数) 指定父类的有参构造器,否则编译报错。
(4)如果希望显式地调用父类的某个构造器,则需要在子类构造器中指定。命令:super(父类相应构造器的参数)
(5)super在使用的时候,必须放在子类构造器的第一行
(6)super(参数列表) 和 this(参数列表) (this调用构造器时的用法)都只能放在构造器第一行,两者不能存在于一个构造器。
(7)java所有类都是Object类的子类,Object类是所有类的父类
(8)父类构造器的调用不限于直接父类,将一直往上追溯直到Object类的构造器。
(9)子类最多只能继承一个父类(直接继承)。如何让A类继承B类和C类?A继承B,B继承C
(10)不能滥用继承,子类和父类必须满足is-a的逻辑关系。
80、继承的本质
new子类的过程中,构造器的调用顺序是从最高父类Object类开始调用构造器,然后一级级父类调用各自的构造器,最后子类调用自己的构造器。
但是,访问属性和方法时,则是从子类开始,向更高级的父类搜索,直到Object类。类似于就近原则。

如果子类和父类有相同名称的属性,那么会就近选择子类的属性。如果属性
不可访问,那么会报错,不会考虑上一级的相同属性。
子类的方法和父类的方法可以完全相同(同方法名、同参数列表),和属性一样,也是就近原则。如果不可访问,则会报错,不会考虑上一级的相同的方法。
注意,更高级父类的同名属性不会被直接访问到(除非通过公开的方法间接访问),但是仍然存在,在内存中分配了空间。有没有和是否可以访问是两回事。
81、this、super调用构造器
两者调用构造器的语法均只能出现在构造器的第一行:其他方法不行,其他行不行。
并且,只能在第一行有一个this或者super,不能有第二个。
区别在于this是调用this所指代的类的对应构造器,super是调用父类的对应构造器。
82、继承的例题
题目:

答案:

先调用B()。
由于B()第一行有this,所以不会有super,this调用B(String name)。
B(String name) 第一行没有this,且B为子类,所以隐藏着super(),调用A()。
A为父类,不会有隐藏的super(),所以这就是最开始的语句了。
输出顺序:A() --> B(String name) --> B()
83、super关键字
super代表父类的引用,用于访问父类的属性、方法、构造器

针对1和2,子类在不考虑属性名、方法名相同的情况下,不用super也可以访问父类的非private属性和方法。
但如果父类和子类的属性、方法重名,那么只能通过super来访问父类。


84、方法重写/覆盖(override)
子类通过重写父类的方法,实现功能的替换。
要求:
子类和父类重写的方法的方法名、参数列表完全一样,否则就不是重写的关系
子类和父类的返回值类型可以相同(基本数据类型),或者子类的返回类型是父类返回类型的子类(对象)
子类方法不能缩小父类方法的访问权限。
85、重写和重载的比较

86、数组的length 和字符串的length()
比较方便地理解,数组一旦创建,它的长度是固定的,所以length可以看成是数组的一个属性。
而字符串背后是由char数组实现的,char数组已经具有了length的属性,字符串也有length,但经过封装不能直接访问,所以用公开的length()获得。实际上,length()也是return length
87、多态
(1)方法的多态
重写和重载就体现多态
(2)对象的多态

编译类型:来自对象名前的类名,来自定义
运行类型:变量实际指向的对象类型,来自new
这里类似于指针,指针的地址空间就存储着一个地址,指针本身的类型是不会变的,但是指针却可以指向不同类型的变量(类型不同可以使用强转)。
有点类似于不同类之间的强转。
对象通过不断修改运行类型,可以调用运行类型对象的方法。
编译类型和运行类型的提出,使得方法传参时实参和形参的对象类型不必严格相同,提高方法的复用性。


这两张图中,Dog和Cat都是Animal的子类。
在feed方法中,定义的形参是Animal,而调用时传入的实参却是Dog和Cat这两个子类。
将形参定义成编译类型,实参写成运行类型,利用对象的多态提高了方法的复用性。
对象多态的前提:两个对象(类)存在继承关系
(3)多态的实质
方法的访问顺序,先子类后父类

88、向上转型

向上转型的父类可以是直接父类,也可以是更高级的父类(比如Object类)
在编译阶段,能调用哪些成员,哪些方法,是由编译类型决定的。
向上转型时,编译时是父类,父类不能调用子类特有的方法和变量。
运行时就是子类了,调用父类的成员需要遵守访问权限,同时运行时还是遵循重写规则的那一套,同名变量或方法从子类到父类逐级搜索,就近采用。
总结,向上转型的调用规则:
(下面的私有指的是本类可调用,其他类不可调用,而不仅仅private)
父类中的
非私有方法和变量可以直接调用
私有方法不可以调用,私有变量通过非私有方法间接调用
子类中的
特有(父类中没有的,不是重写的)方法和变量不可以调用,与父类重名的方法和变量可以调用
子类的变量、方法与父类的变量、方法重名时
遵循重写、继承的规则进行访问(就近原则)
注意,通过这条规则,可以访问到子类中和父类重名的非私有变量和方法
89、向下转型

向下转型的目标是经过向上转型的父类引用(即指向子类对象的父类引用)
向上转型是子类的对象当父类用。
向下转型是对向上转型的补充,向下转型是将向上转型的父类引用转换成原本的子类引用,当子类用。
注意,只能将父类引用转换成原本的子类对象的引用,而不能是新的子类对象的引用。因为新的子类对象没创建。
问题是这么麻烦的过程,实际工作时会经常用吗?很离谱的规则
90、属性重写
属性没有重写之说,重复属性选择哪个取决于编译类型
91、instanceof 操作符
用法:aa instanceof BB
作用:判断对象aa的运行类型是否为BB类型或者BB类型的子类型,返回true或者false
92、 多态例题1

考察基本类型的强转和对象的向上向下转型的命令格式
注意Object obj = "Hello";
这里不是常见的new创建对象,因为是String类型,所以直接赋值了,但实际也是创建了对象,并使用了一个父类的引用指向子类的对象,所以是向上转型。
93、多态例题2

注意最后一个,b的运行类型是Sub,所以会先从子类中找方法。
注意,如果子类没有display(),父类有,那么会输出count=10,调用父类的重名属性。子类是继承而不是复制父类的成员和方法,继承的实质还是引用。子类调用父类方法时,相当于在父类属性的作用域当中,同名首选父类属性。
94、多态例题3
class A {
public String show(D obj){
return ("A and D");
}
public String show(A obj){
return ("A and A");
}
}
class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
class C extends B{}
class D extends B{}
public class Testduotai {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1"+a1.show(b));
System.out.println("2"+a1.show(c));
System.out.println("3"+a1.show(d));
System.out.println("4"+a2.show(b));
System.out.println("5"+a2.show(c));
System.out.println("6"+a2.show(d));
System.out.println("7"+b.show(b));
System.out.println("8"+b.show(c));
System.out.println("9"+b.show(d));
System.out.println("10"+d.show(a1));
System.out.println("11"+d.show(a2));
System.out.println("11"+d.show(b));
System.out.println("12"+d.show(c));
}
}
运行结果:
1A and A
2A and A
3A and D
4B and A
5B and A
6A and D
7B and B
8B and B
9A and D
10B and A
11B and A
11B and B
12B and B
结果解析:
https://www.cnblogs.com/lylhome/p/15795075.html
95、动态绑定机制
当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定
当调用对象属性时,没有动态绑定机制,哪里声明(在哪个变量的作用域内)哪里使用
调用属性的规则,常见的有两种情景,
直接调属性,要看对象的编译类型,
调用的方法中调用了属性,这就要看方法属于哪个对象了,要调相应对象的属性(涉及到作用域)
96、多态数组
创建一个父类类型的数组,这个数组可以存放父类类型和子类类型(向上转型)对象。先创建一个父类类型的数组,即一组指向父类类型的引用。然后对里面的每个元素创建一个对象,并指向该对象。这里面有向上转型的使用。
注意这两个new的作用。

调用父类方法时,for循环即可

调用子类方法时,for循环里面if+instanceof+向下转型即可

首先,通过if + instanceof + 向下转型,实现了访问子类方法的需求。通过 if + instanceof 判断对象的类型,并调用对应子类的方法。instanceof判断的是对象的运行类型(实际指向对象的类型)。
而向下转型恰好是将编译类型强转成运行类型,根据上面的结果可以就可以确定运行类型了。
其次,需要先判断元素是否是子类类型,然后再判断是否是父类类型,越小的子类越先判断。
判断的先后顺序不能错,因为instanceof只要是目标类或其子类,就会返回true。为了区分到底是目标类还是子类,需要先判断子类,如果不是,再判断目标类。
97、多态参数
方法的形参类型为父类,传入的实参类型是子类,是向上传参的一种情况。
98、== 与 equals方法
==是一个比较运算符
==既可以判断基本类型,也可以判断引用类型
用于基本类型时,判断两边的值是否相同
用于引用类型时,判断引用的是否是同一个对象(引用的对象地址是否相同)
注意,两个对象的类型不同时(不是同类也不是父子类),不能使用==比较,编译会报错
记一个错误例子 6 == 6.0 true
equals()是Object类的一个方法
只能对引用类型使用
默认判断的是地址是否相等。这和==作用相同。
但是子类往往重写该方法,用于判断属性的值是否相等。
String类中的equals(),被重写为比较字符串的内容是否相同,如下图

Integer类中的equals(),被重写为比较两数是否相同,如下例子

equals()重写例子


99、查看JDK源码
100、hashCode方法
简单了解,后面讲集合类会讲底层原理并重写

101、toString方法
Object类中的toString() 会返回字符串:
对象的全类名(报名+类名)+ @ +hashCode的十六进制
子类往往会重写toString方法,用于返回对象的属性信息。
重写例子如下,IDEA可以自动生成了

sout一个对象的时候,会默认调用toString方法,最终输出toString方法返回的内容
102、finalize方法
该方法目前不推荐使用了,简单了解即可,实际开发几乎不会使用
https://blog.csdn.net/shanghui815/article/details/6787855?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-4.no_search_link&spm=1001.2101.3001.4242.3&utm_relevant_index=7

子类中重写的finalize方法,IDEA有模板


103、IDEA断点调试
注意,在调试过程中,是运行状态,是以对象的运行类型来执行的。
快捷键:
点击行号 下断点
F7 步入
F8 步过
shift + F8 跳出
F9 继续执行至下一断点

调试过程中查看JDK源码 ,如果F7无法跳入源码,看下图方法

104、零钱通小项目
(1)实现功能:
基本功能:
面向过程
显示菜单并进行选择
显示明细
记录收益
记录消费
退出
改进功能:
退出确认
金额校验
面向对象
(2)先完成显示菜单及进行选择
做菜单一般使用do while ,因为菜单最少要显示一次
循环里面有显示菜单的内容,还有接受输入的scanner
根据接收的数字key,继续不同的功能,使用switch
(3)完成收支明细
一条收支记录,有以下三种常见的数据类型存储:
数组,但大小确定,不能动态增长
对象
字符串拼接,实现最简单
(4)确认退出机制
要求:用户输入4之后,再次确认用户是否确认退出 y/n,
y退出,n不退出,其他输入忽略,继续询问是否确认退出 y/n
一段代码完成一个功能,不要用一段长代码混在一起完成多个功能。
这样代码结构清晰,方便功能的增加和修改。
要充分了解需求,对需求进行分解,方便之后的维护
本例中,
不建议使用while{if……else if……else}这种结构,

建议使用while{}先判断输入是否是y/n,再根据输入用 if 修改退出的标志。

(5)判断用户输入的合法性
两种思路:
每个if判断一种不正确的输入情况,进行相应的错误处理。如果过了所有if,则执行正确输入的代码。
直接用if 判断是否是正确的情况,如果是,则运行正确输入的代码,如果不是,再判断是哪种错误输入,进行相应的错误处理。
老师推荐第一种,因为第一种方便之后加if应对更多的错误输入,而第二种的正确情况需要将正确代码用{}括起来,可读性差,结构复杂。
但是,我觉得,看具体情况吧。第一种如果错误情况罗列不全,那就可能有安全问题,第二种则更安全,就是白名单和黑名单的区别。
这毕竟不是真正的上线项目,有合法性检查的意识就行了,更多的问题交给测试。
(6)修改成面向对象
视频中的拆分,主要是将整个的方法这份成了几个方法,只用了一个对象,更像是函数的拆分。面向对象的三大特点没体现,不过面向CV编程是真得香~~
105、对OOP的一点理解
面向过程的编程,信息的传递是通过各个函数来传递的,信息是一份的,
面向对象的编程,还多了子类和父类的继承,通过封装、继承、多态,规定好了子类和父类的访问规则,信息是双份的,这样的使用更加灵活多变。
学习封装继承多态时,让我想起了C语言的跨源文件时修饰符、作用域的相关知识,感觉有点像,但后者比不上前者方便。前者只需要一个继承关系,就可以轻松调用父类的属性和方法,而C语言中信息的传递只能依靠变量挨个传递。
学完OOP,才知道C的繁琐。只能说jvm是个好东西啊,机器替人干了好多工作。
另外再复习一下多态和动态绑定机制

106、房屋出租系统
(1)需求分析
主菜单及选项选择
显示房屋信息
添加房屋信息
删除房屋信息
修改房屋信息
退出系统
与零钱通不同的是,删除及修改这两个操作。
修改要求的是查找。
选择怎样的数据结构来适应查找的要求?
删除要求的是查找,删除操作。(删除是一种特殊的修改)
选择怎样的数据结构来适应查找的要求?
其对应的删除操作怎么做?
删除后造成的碎片怎么办?
怎样将前后两部分拼接到一起?
这两个问题导致我们不能再选择和零钱通一样的字符串作为信息存储的数据结构了,那么还有两种选择:数组、对象。
(2)设计程序框架图(非常重要)
采用简单的分层模式

分为界面、业务层、数据层

(3)根据框架图,创建相应文件

为每一层建立一个子包,每个子包中存放每层需要的多个java文件。
这个项目比较简单,每个子包只有一个java文件。
但要有这个意识,做好以后扩展的准备,并且同包与不同包差别很大。
(4)工具类的使用
先写工具类,造轮子

static,静态方法,可以通过 类名.方法名 调用

这里主要包括了接收并处理输入用户输入的方法、循环确认Y/N的方法。
(5)显示菜单与退出功能
以此为例,分析完成一个功能的思路和过程

(6)显示房屋信息
这里注意每一层的作用
业务层向界面返回数据(这里不是直接打印数据)
界面调用业务层的方法,接收数据,并打印
各司其职

(7)添加房屋信息
界面接收用户输入的信息,创建House对象,id=1,发送给业务层
业务层接收House对象,修改其房屋id,加入数组
从这里开始设置了主键,即id。主键是唯一且自增的,用户无法指定主键。这样,主键就可以作为区分房屋信息的”指纹“了。后面用到的查找、删除、修改都以查找为基础,都需要使用主键查找。
要注意数组下标(房屋数)和已分配id的关系。
单纯的考虑添加功能,下标和id是有确定关系的,因为每增加一行房屋信息,下标和id都会+1。
但是,如果考虑删除功能,那么数组中间的元素可能会被删除,这时id不变,而下标(房屋数)-1,这样两者关系就乱了。
所以需要设置变量分别记录房屋数和已分配id,在增删的操作中对两者进行不同的操作。
(8)查找房屋信息
界面接收用户输入的id,发送给业务层
业务层接收id,根据查找的id,顺序遍历,将查找到的对象返回给界面(未找到则为null)
由于id是唯一的,这里就可以根据id查找房屋信息了。
设计的数组是id从小到大的(因为删除某个id后,后面元素依次前移)所以可以考虑二分查找
(9)删除房屋信息
界面接收用户输入的id,发送给业务层
业务层接收id,对数组进行查找,找到则删除,返回true,没找到则返回false。
删除操作,因为id是房屋信息的唯一标识,所以房屋信息被删除后,该id也被删除并弃用。
对于删除产生的空洞,将后面的房屋信息依次前移一位,始终保持数组前部是连续的房屋信息,后部全是null。
这样操作虽然删除后修补麻烦,但查找简单。这样的操作保证了数组下标(非null)始终等于房屋数-1,遍历时的i上界为房屋数-2,且顺序遍历遇不到空指针,二分查找id还是递增的。
(10)修改房屋信息
界面接收用户输入的id,发送给业务层
业务层接收id,对数组进行查找,找到则返回House对象,没找到则返回null
界面显示查找结果,如果查找到了对应id,则继续接收用户输入的修改信息,根据这些信息创建House对象,再发送给业务层
业务层接收对象,将指向旧House对象的引用指向新的,完成修改。
注意先返回查找结果,再进行修改。不是一开始就获取所有信息进行修改。在输入修改信息时有旧信息参照,也方便修改。这里查找并返回结果的逻辑不能省。
(11)总结
我在做项目之前在(1)中考虑了这些问题:
与零钱通不同的是,删除及修改这两个操作。
修改要求的是查找。
选择怎样的数据结构来适应查找的要求?
删除要求的是查找,删除操作。(删除是一种特殊的修改)
选择怎样的数据结构来适应查找的要求?
其对应的删除操作怎么做?
删除后造成的碎片怎么办?
怎样将前后两部分拼接到一起?
总结起来就是考虑查找效率、删除后的元素移动、扩容这几个的问题。
在这个项目中,选择了数组来存放房屋信息,
删除后,数组后半段集体前移,保证前面都是房屋信息,后面都是null,不会出现null和有效信息交错的情况。虽然移动的操作麻烦,但查找就简单了。
查找可以用顺序查找。由于引入了id,元素有序,并且删除后保证数组前段都是有效信息,可以二分查找
扩容,添加信息之前会判断数组是否有空位。在发现数组满了之后,创建2倍长度的数组,对原数组内容进行复制,在新数组中进行添加操作。
这里突然想到了,如果不断删除,应该也要缩容。
引入id和始终保持数组有序紧致,这两点是我在操作过程中没有想到的。
另外,最大的收获因该是尝试在项目中应用分层模式。
分析需求,确定有哪几个要完成的需求功能。
分层,确定每一层的层级功能是什么(需求功能和层级功能是两个方向上的,类似横向和纵向),针对层级功能,确定每一层需要哪几个类。
将一个需求功能分解到不同层。针对这一个需求功能,每一层需要干什么。并确定相应的变量和方法。
具体实现。再考虑每一层的方法的具体实现。
将需求功能一个一个完成,最终实现所有需求。
107、类变量/静态变量
定义语法:访问修饰符 static 数据类型 变量名;
(在class 类名{}中定义)
调用语法:类名.变量名
注意!
对静态变量的访问也需要符合访问修饰符的访问权限及范围
即使没有创建对象实例,也可以直接访问
用static修饰的变量,被叫做静态变量。静态变量在内存中是独一份的,共享的。
108、静态变量的位置
首先,需要知道类信息被存储在方法区中,并在堆中创建一个Class实例,每个类只有一个,相当于该类的一个模板,创建对象时依照每个类的class实例创建对象。
其次,静态变量的位置与JDK版本有关。JDK8之前,静态变量放在方法区中,JDK8之后,静态变量放在堆中的对应类的Class实例中。但不管哪个版本,静态变量都是唯一且被该类的对象共享的。
然后,静态变量是在类加载的时候就生成的,早于实例对象的创建。即使没有创建对象实例,也可以直接调用。
最后,静态变量的生命周期与类相同。随类的加载而开始,随类的消亡而销毁。
109、静态方法/类方法
定义语法:访问修饰符 static 数据返回类型 方法名() {}
(在class 类名{}中定义)
调用语法:类名.方法名
注意!
对静态方法的访问也需要符合访问修饰符的访问权限及范围
即使没有创建对象实例,也可以直接访问
110、类方法的使用
经典使用场景:工具类

实际上,如果方法中不涉及任何对象的成员,也就是说,该方法涉及的数据类型是基本数据类型。这就更像C语言中的函数了。
注意事项:


this.静态变量名(x)
super.静态变量名(x)
类名.静态变量名(√)当在构造器中出现静态变量时,为防止与参数同名,就可以用这种方法表示
静态方法如果想访问本类的非静态成员或方法,那么需要先创建对象,再调用即可
111、理解main方法的语法

注意,java中的命令行参数与C语言不同,args[ ]默认为空,而不会自动将exe的绝对路径存储在args[0]中。

112、代码块


代码块在加载类和创建对象(无论哪个构造器)时都会调用。
代码块的顺序是在构造器之前,但类中的多个代码块和变量定义语句之间是顺序执行的关系,谁在前谁先执行,谁在后谁后执行。
113、代码块的使用细节
什么时候调用?
1)static代码块只有第一次类加载的时候才会被执行。
普通代码块每创建一个对象,就会执行一次。

补充:加载子类时,父类也会被加载,并且父类先加载。
举例如下:

创建两个对象

结果如下:

static代码块在类加载的时候被执行,且只执行一次:
创建了两个对象,DD类加载了两次,但是static代码块只执行了一次。
普通代码块每创建一个对象,就会执行一次:
创建了两个对象实例,则执行两次普通代码块。

这一点和上面说的一个意思,使用静态成员会引起类加载,但只有类加载情况之一的创建对象才能引起普通代码块执行,使用静态成员则不会。
调用顺序是什么?
单个类中调用顺序如下:

如果有继承关系时,调用顺序如下:

原理如下:
先加载类,创建子类对象时,肯定会先加载父类和子类,先父类后子类,所以先调用父类的static代码块,后调用子类的static代码块
然后创建对象,由于构造器默认首行包含super(); 会先调用父类构造器,而父类的普通代码块的执行顺序又在父类构造器之前,子类的普通代码块的执行顺序在子类构造器之前,所以,顺序依次为父类普通代码块、父类构造器、子类普通代码块、子类构造器。
实际上在子类构造器中,第一行是默认的suoer();,后面则是默认隐藏的子类的普通代码块,然后才是子类构造器的语句。

完整的例子如下:




调用顺序总结如下:


访问权限及范围

114、代码块的一个例题

这个题的执行顺序每什么好说的,但是Test类中有一个成员变量是静态对象sam。sam创建对象时,需要通过new来调用构造器,那么构造器是静态方法吗?
首先,构造方法通过new操作符在静态方法中被调用。静态方法可以在未创建对象的时候调用,但是构造方法也可以在未创建对象的时候调用。从这个角度来看,构造方法算是个静态方法。其次,静态方法不能调用实例成员,而构造方法内部就可以调用实例变量和方法。这么看,构造方法又是个非静态方法。总之,这个问题的答案很难说,目前记住构造方法可以在静态方法中调用即可。
115、单例设计模式


适用于那些核心的、非常消耗资源、但又只需一个实例的类。
饿汉式单例模式


比较神奇的一点是,在类的内部存在该类的对象作为成员,有点套娃的感觉,想起了C语言中,链表节点(结构体套结构体指针)的形式。
懒汉式单例模式
单例模式的对象通常是重量级的对象,很耗资源,如果先创建而不用,是对资源的一种浪费。所以改进为使用时现场创建。

懒汉式的getInstance是被调用时,现场new对象的。
