目录
一.准备知识
0.下载JDK到windows
1.环境变量path与classpath区别
2.Java的一些规定
3.Java语言的四大特点
4.Java主要组成部分
5.Java的核心机制
二.关键字
三.标识符(文件名称)
四.注释
五.常量 - 不能改变的数值
六.进制
七.变量
八.数据类型
九.数据类型转换
十.运算符
1.算术运算符(注意细节)
2.赋值运算符
3.比较运算符
4.逻辑运算符(*)
5.条件运算符(三元/三目运算符)
6.位运算符(*)
十一.程序流程控制
1.if语句(三种格式)
2.switch语句
3.成员变量和局部变量的比较(***)
4.循环结构
十二.函数\方法
函数的重载--overload(与函数的重写相区分)
十三.数组
十四.Java 内存结构
十五.二分查找
十六.二维数组
十七.进制转换
十八.面向对象
十九.类与对象
二十.构造函数
二十一.This关键字:指当前类的当前对象
二十二.面向对象三大特性
1.封装
2.继承
Super的用法:
final:不可改变的
抽象类和抽象方法:
3.多态
二十三.代码块
二十四.Static(静态)关键字
二十五.main函数--程序的唯一入口
二十六.单例
二十七.接口
下载jdk下载jdk之后 - 去windows高级变量 - 详见下图 具体操作详见google
PS:因为 javac指令仅仅能在JDK安装目录下的bin目录下使用,不配置程序只能放到bin目录;
1.临时设置:set path=%path%;JDK的全路径
2.永久设置:新建一个JAVA_HOME+jdk文件路径地址,编辑path为%JAVA_HOME%\bin;
1. PATH环境变量。作用是指定命令搜索路径,在命令行下面执行命令如javac编译java程序时,它会到PATH变量所指定的路径中查找看是否能找到相应的命令程序。我们需要把jdk安装目录下的bin目录增加到现有的PATH变量中,bin目录中包含经常要用到的可执行文件如javac/java/javadoc等待,设置好PATH变量后,就可以在任何目录下执行javac/java等工具了。
2. CLASSPATH环境变量。作用是指定类搜索路径,要使用已经编写好的类,前提当然是能够找到它们了,JVM就是通过CLASSPATH来寻找类的。我们需要把jdk安装目录下的lib子目录中的dt.jar和tools.jar设置到CLASSPATH中,当然,当前目录“.”也必须加入到该变量中。
3. JAVA_HOME环境变量。它指向jdk的安装目录,Eclipse/NetBeans/Tomcat等软件就是通过搜索JAVA_HOME变量来找到并使用安装好的jdk。
1、若在源文件中定义了声明为public的类,需要将类所在的源文件的文件名取名为类名
2、在同一个源文件中有且只能有一个定义为public的类
3、编译(javac)时文件名大小写是不敏感的,执行(java)的时候加载的类名是大小写敏感的
------------------------解决方案-------------------------
这是 Windows 系统的关系,Windows 文件系统的文件名不区分大小写。
Calculate.java 编译之后会产生两个类文件:Calculate.class 和 calculate.class。
1.跨平台性:一次编译多平台使用;
2.面向对象
3.健壮性
4.解释性的语言
JDK:(Java development kit) 包括Jre and 和开发工具(编译工具--javac.exe and 打包工具--jar.exe)
JRE:(Java Runtime Environment) 包括虚拟机(Java Virtual Machine) and 各种核心类库
1.JVM(是以字码为机器指令的CPU;虚拟机不能跨平台)
Java字节码是Java源文件编译产生的中间文件;
java虚拟机是可运行java字节码的假想计算机 ,java的跨平台性也是相对与其他编程语言而言的。
PS:C语言程序和JAVA程序的执行区别:
先介绍一下,c语言的编译过程是将C语言源程序,也就是C写的程序文件经过C编译程序编译后,生成windows可执行文件exe文件,然后在windows中执行。再介绍java的编译过程,先是java源程序(扩展名为java的文件),由java编译程序编译成java字节码文件(class文件),在java虚拟机中执行。机器码是由CPU识别执行的。Java编译后的文件是字节码文件, 电脑只能运行机器码。Java是在运行的时候把编译好的字节码变成机器码。C/C++在编译的时候直接编译成机器码。
2.垃圾回收机制
当一个对象成为垃圾之后仍然会占用内存,Java虚拟机会自动将垃圾对象回收以释放内存;可以手动通过System.gc();来通知JVM启动垃圾回收,当一个对象被回收释放的时候,对象的finalize()方法会被自动调用;
以下实例予以证明:
class Person{
//finalize函数会在对象被回收时候自动调用
public void finalize(){
System.out.println("对象即将被回收。");
}
}
public class Test{
public static void main(String[] args){
Person p1 =new Person();
Person p2 =new Person();
//将对象指向null就会通知JVM该对象成为垃圾对象
p1=null;
p2=null;
//手动调用垃圾回收
System.gc();
sleep 30;//延长程序运行
}
}
运行结果:
对象即将被回收。
对象即将被回收。
定义:被Java语言赋予特殊含义的单词
特点:关键字中所有字母均小写
定义:对各种变量、方法和类要素命名时使用的字符序列
组成:26个英文字母大小写,数字0-9,符号 _ 和 $
java中的命名规范:
定义:注释是用来解释和说明程序的文字;
作用:提高代码的可阅读性;
Note:
1.先写注释,再写代码,代码是思想的体现
2.注释不会被编译器编译
分类:
1.整数常量:(100)
2.小数常量:(52.2)
3.布尔型常量:只有true 和 false两个数值 (true)
4.字符型常量:‘ 数字 字母 字符 ’ :('汉')
5.字符串常量:“ 一个或多个字符 ”
6.null常量:只有一个数值 null (***???)
数值型常量和逻辑常量不需要用引号包起来。逻辑常量只有true和false两个,数值型根据有无小数点再分为整型和实型。而用单引号包起来的是字符型,用双引号包起来的是字符串。例如1024是整型,而"1024"是字符串,'@'是字符,而"@"是字符串。
分清楚:系数,权值,基数 and 其转换规则。
系数:每一位上的数值
基数:x进制的x
权值:数据从右到左的索引值,以0开始到n-1
(任意进制转化为十进制:系数*基数^权值相加)
(十进制转化为任意进制:数值除以基数直到商为0,最后将余数 倒序 输出)
PS:C语言规定,把内存的最高位作为符号位,且用0表示正数,用1表示负数。
原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。
反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。
补码:反码加1称为补码。也就是说,要得到一个数的补码,先得到反码,然后反码加上1,所得数称为补码。
比如-6在计算机中的二进制表示方式:
# -6 的正值 6 的二进制:
00000000 00000000 00000000 00000110
# 取反得反码:
11111111 11111111 11111111 11111001
# +1 得补码,即 -6 在计算机中的二进制表示:
11111111 11111111 11111111 11111010
定义:内存中的一个存储区域,该区域有自己的名称(变量名)和类型(数据类型),该区域的数据可以在同一类型范围内不断变化。
格式:变量数据类型 变量名 = 初始化值;
Eg: byte b = 5;
作用:不断存放同一类型的常量,并可以重复使用。
Eg: System.out.println( b ); (***)
作用范围: { } 之间;
Java语言是强类型语言,对于每一种数据都定义了明确的具体数据类型,在内存中分配了不同大小的内存空间。
PS:
注意: 1字节(byte)=8位(bits)
java中可以从任意基本类型转型到另外的基本类型
除了boolean 类型不可以转换为其他的数据类型。
自动类型转换(也叫隐式类型转换)
强制类型转换(也叫显式类型转换)
默认转换
int x = 4;
byte y = 5;
x = x+y;
System.out.println(x);
强制转换(当大的数据类型放到小的数据类型会有精度损失的时候需要强制转换)
int z = 4;
byte q = 5;
q =(byte) (z+q); //进行强制转换
System.out.println(q);
思考:
byte b1=3,b2=4,b;
b=b1+b2; //(错误)需要强制转换
b=3+4;
哪句是编译失败的呢?为什么呢?
答:第二句错误,大数据到小数据转换过程中会有精度损失;
+ - * / :加(正)减(负)乘除
% :取模(取余数)
++ :自增 :初试值+1 ( a++:先运行 后计算 / ++a: 先计算 后运行)
--:自减:初始值-1(a--:先运行 后计算/ --a:先计算后运行)
+:字符串相加 (除了可以将字符串相加,还可以将非字符串转化为字符串)
比如System.out.println("5+5="+5+5); //打印结果是?
答:5+5=55
Notes:
/ : 系统默认是int类型所以是取整数的 如果需要精确结果 需要*1.0
如: int i= 4562;
// i = i/ 1000*1000; 输出为4000 因为默认int类型取整数4 *1000
i= (int)(i*1.0/1000*1000);
System.out.println(i); //这样输出结果就是4562;
%: 输出结果的正负号是由被除数的正负决定的 (即对负数取模,可以将符号忽略不记。如 5%-2 = 1;但被模数为负数就另当别论了)
比如: System.out.println(-4%3); // -1 (和被除数正负号一样)
System.out.println(3%-2); // 1 (除数正负不影响取模正负)
= :赋值
int a,b,c; //int a=b=c=5 错误
a=b=c=5;
+= :加上后面的数值(隐形转换 不需要强制转换)--除非赋值数值超出数据类型的范围
-= :减去后面的数值
*= :乘以后面的数值
/= :除以后面的数值
%= :取模后面的数值
==:相等于
!=:不等于
<:小于
>:大于
<=:小于等于
>=:大于等于
instanceof :检查是否是class的对象(比如:''Hello" instanceof String //true)
Notes:
1.比较运算符的结果都是boolean型,也就是要么是true,要么是false。
2.比较运算符“==”不能误写成“=” (赋值)。
& (与):一假则假
| (或):一真则真,全假则假
^ (异或):相同为假 ,不同为真
! (非):与原有的真假相反(***???)
&& (短路与):如果左边为真,右边参与运算,
如果左边为假,那么右边不参与运算
|| (短路或):区别同理,双或时,左边为真,右边不参与运算;
左边为假,右边参与运算。
格式:(条件表达式)?表达式1:表达式2;
如果条件true,执行表达式1;
如果条件false,执行表达式2;
比如:int i = 5;
i = (i<10)?i+1:i-1;
System.out.println(i); //()true,执行冒号前面的表达式1 =6
又比如 :int i =5;
i = (i<10)?【(i>6)?(i<6)?i+2:i+1:{i-1}】:i+1;
1.第一个括号()true,看冒号前面的【】;
2.第二个括号()false,看冒号后面的{}; =5-1=4
Note: 位运算是直接对二进制进行运算。
计算口诀:
左移:数据 * 2^左移数
右移:数据 / 2^右移数
顺序结构,按顺序执行程序;
选择结构如下:
第一种格式:if(条件表达式){执行语句;}
条件表达式为真 则执行语句,为假则不执行
Note:
1.if();不能有分号
2.条件表达式无论多复杂,最终的结果是布尔类型的
3.{ }可以不写,if语句只执行第一行命令,之后的不属于if语句,所以建议规范都写上。
第二种格式:if(条件表达式){执行语句1;} else {执行语句2;}
条件表达式成立,则执行语句1 ;不成立,则执行语句2.
Note:
1.{执行语句1}的大括号可以不加 但是建议规范格式 // 和条件运算符相区别:
三元运算符运算完要有值出现。好处是:可以写在其他表达式中。
2.条件运算符 与 if -else 不一定可以相互转换(***)
因为条件运算符后面不能加执行语句,而if else是加的执行语句
3.else不能同时存在多个
第三种格式:if(条件表达式1){执行语句1;} else if(条件表达式2){执行语句2;}else if(条件表达式3){执行语句3;} ... ... else {执行语句N;} : 先判断条件表达式1 成立,则执行语句1,不成立则继续判断条件表达式2……所有条件表达式均不成立则执行else语句
Note:
1.else if可以多次使用
2.可以没有else
If语句嵌套 if
格式:if(条件表达式1){ if (条件表达式2){执行语句;} }
需求:判断输入的数字是否大于15小于20
Note:
println 打印结束换行
print 打印结束不换行 可以用“ ” 添加空格
格式:switch(表达式){
case 取值1:
System.out,println();---语句体
break;
……
default:
System.out.println();
}
执行顺序:
首先计算出表达式中的值,然后开始和case中的取值一一比较,有对应的值就会进行之后的语句体,然后break停止循环;如果所有case取值都没有表达式对应的值,那么就会执行最后的default语句并停止循环。
Note:注意switch语句格式的书写!!!
1.default的位置和执行语句的位置无关,因为先执行case 后执行 default;
2.break跳出当前循环(不再进行循环) --- break 用在循环和switch结构中
continue 结束本次循环(进行下一次循环) ---continue只能用在循环结构中
return: 结束循环并返回后面的值;
成员变量 | 局部变量 | |
在内存中的位置 |
在堆内存中跟在对应的对象后面 |
在栈内存中 |
生命周期 |
和类一致 |
和函数一致 |
作用范围 | 在类中可重复使用 | 和函数一致 |
初始值 | 有默认的初始值 | 没有初始值,需要赋予初始值 |
循环的四大要素:初始化,判断体,方法体,迭代
1.while
格式: while(条件表达式){执行语句;} :首先判断条件表达式,true则执行语句,继续判断条件表达式直到表达式false,结束循环。
Note:不能有分号;
2.do while
格式 do {执行语句;} while(条件表达式); :首先执行语句再判断表达式,表达式true,则继续执行语句,直到条件表达式false,结束循环。
Note:
1.while 与 do while的区别:
a.while先判断后执行 do while 先执行后判断;
b.while 后面没有; do while 后有;
c.while执行语句不一定执行, do while至少会执行语句1次;(与a联系)
2. while 不加 { }的时候,只会执行第一句输出,并继续判断。
3.for
格式:for(初始化表达式;条件表达式;循环后的操作表达式){执行语句;} :首先定义初始值后,判断是否满足条件表达式,true 则执行循环体,再进行循环后的操作表达式, 接着继续进行判断条件表达式,直至 false停止循环。
Note:
1.初试表达式只执行一次,循环体可能不会被执行;
2.循环后的操作表达式只要能保证数据的增或减即可。
PS:while 和 for的区别:
1.while 初始值定义是全局的, for初始值定义是局部的,只能在for循环中使用;
2.如果知道循环次数可以用for循环,当不知道循环次数而只知道判断条件的时候用while循环更适合;
格式:
修饰符 返回值类型 函数名(参数类型 形式参数1 , 参数类型2,……){
执行语句; return返回值 }
Notes:
1.当函数没有返回值的时候,不能使用变量接收
2.没有返回值的函数里面 -- void,函数默认自动会在末尾可以加上return;也可
以自己手动加; (return null;是错误的)
3.封装类型中(后面会学到),不给定义的变量初始值就默认为0;目前必须
对定义的变量进行初始值赋值;
定义:在同一个类中,允许存在一个以上的同名函数,只要他们的参数个数或者参数类型不同即可;JVM是根据参数个数和参数类型不同来判断并执行对应的函数;
特点
处理同一类型的问题或者具有同一类型的功能就可以运用函数的重载 -- 面向对象的思想。
Supplement: (Scanner的简单使用)
包:java.util.Scanner
该类是java提供的扫描控制台录入数据的。
Notes:
一次Scanner s = new Scanner(System.in); 只能输入一种类型的数据
(可多次使用),但若是需要输入其他类型的数据需要重新定义一个新的Scanner
变量 比如 Scanner ss = new Scanner (System.in);
概念:同一种类型的数据的集合,就是一个容器;
优点:可以自动给数组的元素从0开始编号,方便这些元素的操作;
格式(两种):
1. 元素类型[ ] 数组名 = new 元素类型 [元素个数或者数组长度] ;
int [ ] arr = new int [ 6 ];
2. int [ ] a = new [ ] { 2,3,4,5} ;
int a [ ] = new [ ] {2,3,4,5} ; PS:可读性不如上面的写法;
数组常见的问题:(运行才会出现的错误)
1.角标越界:java.lang.ArrayIndexOutOfBoundsException:6
2.空指针异常:java.lang.NullPointerException
Eg: arr = null;
System.out.println( arr [ 0 ] ) ;
数组一旦确定长度就不能修改。
遍历数组:调用一个方法遍历 主方法里定义数组;
PS:
形式参数:函数里面的一个变量,用来装实际参数的变量引用;
实际参数:真正的具有实际意义参数,基本数据类型--栈内存;实际对象--堆内存;
java程序在运行的时候,需要在内存中分配空间,目的是为了提高运算效率,与对空间进行不同区域的划分,因为每一片区域均有特定的处理数据方式和内存管理方式;
栈内存:
堆内存:
方法区:
本地方法区:该内存块是jvm虚拟机调用系统使用的,我们所使用的应用层无法使用;
寄存器:cpu运算的时候所使用的内存块;
java中的参数传递:
基本数据类型:新建一个方法,新建方法中的变量可以取相同的名称,和主方法里的同名变量没有直接联系;
数组:栈内存中只存放数组的变量名,而实际参数是存放在堆内存中的,不管是新建方法还是主方法里面对数组的元素进行替换,都会直接替换掉原始数组的元素;
堆:存的是数组,对象,通过new创建的实例,均是存放在堆中。
方法区 :
本地方法区:寄存器
一、什么是二分查找?
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
算法要求:1、必须采用顺序存储结构;2、必须按关键字大小有序排列。
时间复杂度:O(h)=O(log2n)
代码实现:
/**
* @param args
* @description:二分查找
*/
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 2, 3, 4, 5, 21, 21, 21, 21, 21, 23, 24, 56, 67, 34};
Scanner scan = new Scanner(System.in);
System.out.println("请输入目标查找值: ");
int aim_num = scan.nextInt();
System.out.println(search(arr, aim_num));
}
/**
* @description:二分查找方式一
* @param arr 数组
* @param aim_num 目标值
* @return 返回角标结果
*/
public static String search(int[] arr, int aim_num) {
int minIndex = 0;
int maxIndex = arr.length - 1; //13
int midIndex = (minIndex + maxIndex) / 2; //6
// 二分查找
while (aim_num != arr[midIndex]) {
if (aim_num > arr[midIndex]) {
minIndex = midIndex + 1;
midIndex = (minIndex + maxIndex) / 2;
} else if (aim_num < arr[midIndex]) {
maxIndex = midIndex - 1;
midIndex = (minIndex + maxIndex) / 2;
}
if (maxIndex < minIndex) {
midIndex = -1;
break;
}
}
// 判断是否有重复值
int temp_index = midIndex;
String result = "目标值角标为:" + temp_index;
while (temp_index >= 0) {
if (aim_num == arr[--temp_index]) {
System.out.println("左侧有重复值");
result = result + ", " + temp_index;
} else
break;
}
while (midIndex <= arr.length) {
if (aim_num == arr[++midIndex]) {
System.out.println("右侧有重复值");
result = result + " ," + midIndex;
} else
break;
}
return result;
}
数据基本查找:(遍历数组后一个个进行比对查找)
优点:过程安全性好
缺点:效率较差
二分查找(每次都是从数组的中间开始查找)
前提:数组是有序的
优点:效率高
适用场景:查询数组中某个数的角标或者某元素个数
格式1:数据类型 [ ] [ ] 数组名称 = new 数据类型 [一维数组的个数] [每个一维数组里面元素的个数];
int [ ] [ ] ab = new int [ 2 ] [ 3 ];
注意 :输出的时候 :第一个[ ]里面存的是在堆里的一维数组的地址值(地址值--提示在内存中的哪个位置,是16进制的 比如:0x123), 第二个[ ]是数组里面的角标;
格式2:数据类型 [ ] [ ] 数组名称 = new 数据类型 [一维数组的个数] [ ] ;
int [ ] [ ] a = new int [ 2 ] [ ] ; 有2个一维数组,但是均没有创建;
注意:a [ 0 ] = new int [ 2 ]; -----第一个数组里面定义一个数组 元素个数为2
a [ 1 ] = new int [ 3 ]; -----第二个数组里面定义一个数组 元素个数为3
格式3:数据类型 [ ] [ ] 数组名称 = new 数据类型 [ ] [ ] {{ } , { } , { } , .....};
int [ ] [ ] a = new int [ ] [ ] { {1,2,3}, {4,1} };
Notes:
1.System.out.pritnln(arr.length); 可以输出二维数组的一维数组的个数;
2.System.out.pritnln(arr[ 2 ].length); 可以输出二维数组中角标为2的一维数组的数组长度;
3.: [ [ 表示是二维数组 ,若一个就表示一维数组;I表示 int 类型数据 ;@后面表示具体内存的位置.
图示:
格式1:
格式2:
遍历二维数组:两个for循环嵌套即可
1.首先 for循环 获取一维数组
2.再用一个for循环嵌套 获取每一个一维数组的元素
for(int i = 0; i < arr.length; i++){
for(int j = 0 ; j < arr [ i ].length ; j++){
System.out.println(arr[ i ] [ j ]);
}
}
PS:直接输出数组是数组的地址值。[[I@1b6d3586
运算规则:10进制转换成16进制,与运算15,再右移4位,循环运算
/**
* 进制转换 - 查表法
*
* @description:10进制转换成任意进制 调用函数toHex(60,15,4)
* @params:num:需要运算的十进制数值,base:与运算基数,bit:左移位数
*/
public static void toHex(int num, int base, int bit) {
char[] hex = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] c = new char[32];
int index = 0;
while (num > 0) {
int tem = num & base; // 16进制与运算15,8进制与运算7,2进制与运算1
c[index] = hex[tem];
index++;
num = num >>> bit; // 16进制右移4位,8进制右移3位,2进制右移1位
// >>表示是带符号的右移;>>>表示无符号的右移
}
// 将数组倒序打印
for (int i = index - 1; i >= 0; i--) {
System.out.println(c[i]);
}
}
相对于面向过程而言的。
面向过程:适用于 简单的事务,解决问题使用的是线性思维。
面向对象:更适用于 复杂的事务,解决问题使用的是宏观思维。
两者之间的关系:
面向对象编程本质:以类的方式组织代码,以对象的方式组织数据。
面向对象的三大特征
封装:封装是面向对象核心,将对象的属性和行为封装起来,不需要让外界知道具体的实现细节。所谓类的封装,指在定义一个类时,将类中的属性私有化,即使用private修饰属性使其成为私有属性,私有属性只能在其所在类中被访问,为了让外界能够访问私有属性,需要提供一些public修饰的公有方法。
继承:继承性主要是描述类与类之间的关系,通过继承,可以在无须重新编写原有类的情况下对原有类的功能进行扩展。继承不仅增加了代码的复用性,提高了开发效率,并且为程序的修改补充提供了便利。
多态:多态性是指在程序中允许出现重名现象,它指在一个类中定义的属性和方法被其他类继承之后可以具有不同的数据类型或着表现出不同的行为,这使得同一个属性和方法在不同的类中具有不同的语义。例如,吃这一动作,中国人使用筷子进行吃这个动作,美国人使用叉子进行吃这个动作,不同的对象所表现的行为是不一样的。
首先:类是具体对象的抽象,对象则是类的具体表现。
类与对象的关系:
类:
对象:
创建对象的过程
图示:
类:描述定义对象的属性(成员变量)和行为(成员函数),属性在类里的体现是成员变量,而行为则是成员函数;
成员变量和局部变量的区别:
成员变量:
局部变量:
匿名对象:没有名称的对象,对象的一种简化方式
Q:在内存中是在哪里??????? 直接在堆内存中?
答:创建一个匿名对象 就在堆内存中创建一个匿名对象(即每次创建的匿名对象的地址值不相同); 所以称匿名
对象 只能调用一次;
运用场景:
1.只能调用一次 该对象的方法;
两次调用 地址值不同 即创建的对象不是同一个;
2.可作为实际参数进行传递;
调用函数的时候可以直接作为实际参数;比如show(new Student());
匿名对象创建在内存中的过程:
1.首先在方法区加载一个类的字节码文件;
2.在堆内存中创建一个对象,之后进行加载成员属性并赋予默认值, 随后随机生成一个地址值;
3.通过把刚才生成的地址值给栈内存中的局部变量 然后去方法区 调用方法;
4.调用完毕方法之后 就会被标记 之后在不确定的时间被垃圾回收机制回收;
形式参数和实际参数区别:
形式参数:形式参数是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的实际参数。
实际参数:在调用一个函数时,函数名后面括弧中的参数(可以是一个表达式)称为"实际参数"。
区别:本质不同:形参的本质是一个名字,不占用内存空间。
实参的本质是一个变量,已经占用内存空间----基本数据类型--栈内存;实际对象--堆内存。
形式参数:方法里面的一个变量,用来装实际参数的变量引用;
实际参数:真正的具有实际意义参数,基本数据类型--栈内存;实际对象--堆内存;
作用:初始化对象(数据),在实例化对象的同时调用构造函数中的属性和方法;
格式:权限修饰符 类名 () { }
特点:
空参构造函数:初始化的数据都是默认值(系统默认添加的);
class Cat{
public cat(){}//空参构造函数,初始化的数值都是缺省值
}
Note:
系统默认添加的前提:当类里面没有其他的有参构造函数时,系统会默认添加空参构造函数;如果类里面有其他的有参构造
函数 则需要手动添加并调用空参构造函数,否则编译程序会报错。
有参构造函数:初始化的数据都是定义的值;
class Cat{//有参构造函数
public Cat(String c,boolean s,String s){
color=c;
sex=s;
type=s;
}
}
调用的时候可以直接进行赋值 比如:Cat cc = new Cat("白色" , "false" , " 加菲猫");
构造函数之间的关系属于重载关系;
1.同一个类里
2.函数名相同
3.参数类型和参数个数(列表)不同
4.与返回值类型无关
|
普通函数 | 构造函数 |
格式 | 可能没有返回值 | 不需要指定返回值 |
用途 | 将某一功能封装到一个函数里面 | 实现对象的初始化 |
调用 | 被对象调用的 | 在初始化对象的时候进行调用 |
注意:
应用场景:
1.为了区分局部变量和隐含的成员变量;this调用的是成员变量(被哪个对象调用,this就代表那个对象);
2.可以用在构造函数中;
3.而且构造函数也可以调用构造函数(有参调用有参,无参调用有参等),但是this()调用构造函数必须放在第一行;
比如图中的this(40);调用构造函数语句必须写在第一行;
注意:this. 作用范围是当前类;
作用:
1.在有参构造函数中属性的赋值时,为了区分局部变量中隐含的成员变量/全局变量(同名的);
2. 当方法中(函数名后面的括号中)没有声明变量时,该变量默认会带this.变量名,代表全局变量;
如图两张图表达相同:
3.接上一句,如果像图中的getAge方法中那样声明变量的话,那么如果不加this.就表示声明的局部变量了;
4. this指代的就是调用这个方法的对象;方法被哪个对象调用,this就代表那个对象。
s这个对象调用了setAge,setName方法,那么下图的this.age/this.Name中的this指代的均是s对象;
定义:指将对象的属性和实现过程(成员属性和方法)隐藏起来,提供一个公共的方法供外界去调用。
好处:
原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏起来,提供公共方法对其访问。
关键字(注意):private
1.权限修饰符(private , default , protected, public)
private | default | protected | public | |
same class | √ | √ | √ | √ |
same package | √ | √ | √ | |
include child classes | √ | √ | ||
all classes | √ |
只有public可以跨包使用;
注意protected在子类中可以使用的例子
2.用于修饰成员变量以及成员函数 (成员属性和成员方法)
3.被私有化的成员只有在本类之中有效;
常用场景:
1.将成员变量私有化,对外提供对应的set,get方法(是用public修饰的 可以在main函数里面调用)对其进行访问。这样就提高数据访问的安全性。
2.将方法私有化,比如创建一个工具类(里面每一个方法都是用static修饰的,就不用创建对象再去调用方法了,可以直接用类名调用,提高效率)中的某一个方法中还需要调用另一个方法,那么被内部调用的另一个方法就可以用private修饰,这样外部就不能调用只能在工具类中被调用,这样就提高了安全性。
如图:冒泡排序过程中需要 替换 这个方法,可以调用tihuan这个方法,但是考虑到安全性,tihuan这个方法可以私有化。
多个类存在相同属性和行为的时候,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只需继承那一个类即可;
子类可以获取父类所有非私有的属性和方法。
特点:
1.可以多层(重)继承(子类继承父类,父类可以继续继承父类)
2.一个子类只能继承一个父类(单继承),一个父类可以有多个子类
注意:
1.被继承的类: 父类/超类/基类
2.继承的类: 子类
3.子类可以继承父类非私有的属性和函数
作用(好处):
1.使得类与类之间产生联系
2.提高代码的复用性(直接调用,不需要再重复编写代码)
3.多态(一种事物在运行时的多种不同形态)的前提 (***)
4.提高代码的维护性;(改一处就全改了)
note:
1.不要仅仅为了一个功能去继承一个类,因为继承类是集成所有的功能
2.继承的弊端:类的耦合性很高---父类一出现错误,全部子类均会出现错误;在开发中需要尽量减少类与类之间的耦合性;
继承里的成员变量:
1.子类与父类的成员变量名相同的时候:(就近原则)
首先是去找子类的方法内部找,看是否有变量;
若没有,再去找子类成员变量范围找;
还没有,最后去父类成员范围内找;
注意不会去父类的方法里找(因为父类的方法里面定义的变量是在那个局部里面才能使用,除了那个方法就没有定义了);
而且不去考虑父类的父类,如果以上的所有步骤都没有找到,就会报错;
2.子类与父类成员变量名不同的时候,直接在子类方法内输出相对应的变量即可;如图:
3.父类定义的变量在子类中重新赋值之后,即子类若含有与父类一样的属性名,则输出的值以就近原则输出,即输出的是子类的赋值;
除非重新创建一个父类的对象 然后进行输出。或者用一个更简便的方法,就是用super.num直接调用,就不需要创建对象了。
4.注意下图中 输出三个n值的方式:1.直接输出 2.this调用当前类的n值 3.super调用父类的n值:
Note:
this: 代表当前类对象的引用
super:代表父类的对象的引用
继承里的方法:
1.当方法名不同时,使用子类对象进行调用
2.当方法名相同时,需要重写
函数的重载:
1.在同一个类里
2.方法名相同
3.参数类型和参数个数不同(参数列表不同)
4.与返回值类型无关 (*)
函数的重写:(Override)
1.方法名相同
2.参数类型或参数个数相同(参数列表相同)
3.在不同类中
4.返回值类型相同
5.重写方法(子类方法)的权限不低于被重写的方法(父类里的)的权限(public>protected>default>private)
注意:
静态方法只能重写静态方法:
如图cry方法; 否则就像show方法一样都不用static修饰;
继承里的成员方法:
子类对象调用成员方法的顺序:
首先去子类中找,找不到再去父类中找,否则会报错;
注意:
1.初始化子类的时候会先初始化父类;
2.当父类中没有空参数的构造函数时,子类的构造函数必须通过this或者super语句指定要访问的构造函数。
继承里的构造函数(3点):
1.在main函数里创建子类对象时,默认会先调用父类构造函数来初始化父类属性和方法。
而且在子类构造函数第一行默认调用父类的空参构造函数;如图: super(); 就是默认添加的。
输出结果:
父类的空参构造函数
子类的空参构造函数
2.如果父类含其他构造函数的而没有空参构造函数,则需要将有参构造函数手动添加到子类构造函数中。
如图:super(10);
3.注意:
super(……)或者this(……)必须出现在第一条语句上,否则就会有父类数据的多次初始化;
(为什么会有多次初始化?
答:因为创建对象的时候会默认调用父类构造函数来初始化父类的属性和方法,如果不写在第一行,还会有第二次初始化。
而父类数据多次初始化有什么危害?
答:多次在堆内存中创建对象,占内存空间;
this.属性 ---当前类对象的属性 super.属性---父类对象的属性
this()---当前类的空参构造函数 super()----父类的空参构造函数
this(参数)---当前类的有参构造函数 super(参数)----父类的有参构造函数
this.成员方法() ---当前类的成员方法 super.成员方法---父类的成员方法
1.修饰成员变量,该变量就变为常量,常量名字母全部大写
2.修饰成员方法,该方法可以被继承;但是不能被重写
3.不能修饰构造函数,构造代码块;
4.修饰类,该类不能被继承;
5.内部类只能访问被final修饰的局部变量;
继承里的代码块:
父类:
1.构造代码块:
2.静态构造代码块:
3.构造函数
子类:
1.构造代码块
2.静态构造代码块
3.构造函数
父类静态构造代码块 子类静态构造代码块 然后在栈内存中存入一个局部变量 ,然后再堆内存中new 一个子类对象,但是在子类构造函数第一行默认添加super();--调用父类构造函数,所以会先创建父类对象,所以接下来输出的是父类构造代码块,父类构造函数;最后子类创建对象,输出子类构造代码块和子类构造函数。
抽象:将多个事物中共有的特点提取出来。
抽象类
是子类的的一个模板,子类必须实现抽象类里的抽象方法,抽象类也可以进行拓展,即抽象类也能含有非抽象方法和属性;
抽象方法(没有方法体的方法--方法名不同,其他都相同)的格式:
public abstract void +方法名 (); ---------不用加方法体,在()后面直接加 ;即可。
特点:
1.抽象方法只能放在抽象类里;抽象方法需要子类继承父类并重写;
2.继承抽象类的两种方式:
可以实现抽象类里面所有的抽象方法(子类继承父类并重写父类中的抽象方法)----比如图中Cat类
或者将该子类变成抽象类;----比如图中Dog类;
3.抽象类里不一定只有抽象方法,还可能有非抽象方法,该非抽象方法也可以在主函数直接调用。
4☆.抽象类不能在main函数里创建对象/实例化,但是有构造函数;
(因为抽象类里面有抽象方法,而抽象方法没有具体的方法体,所以对象调用的时候没有意义。因此,不能在main函数里创建对象;
而构造函数的作用 是加载抽象类里非抽象的属性和方法来供子类使用,所以抽象类里有构造函数。)
方式:通过子类的创建对象/实例化来初始化抽象类。
这是为了初始化抽象类里非抽象的方法和属性(构造方法的作用);
5.抽象类里可以有成员变量,也可以有常量。
6.怎么使用抽象类?
答:子类继承抽象类,实现抽象类的所有抽象方法(重写的方式),然后通过在main函数中创建子类对象调用重写之后的方法。
7.abstract 与哪些关键词冲突?
private:因为private修饰的抽象方法只能在本类中使用,不能被继承,而抽象方法必须通过继承来重写。
static:因为被static修饰的方法可以直接被类名调用,但是抽象方法中没有方法体,调用了也没有实际意义;
final:因为被final修饰的方法不能被重写,但是抽象类中的抽象方法必须通过子类重写来实现;
default:表示的是默认的,不需要手动添加
多态:一种事物的多种存在形态
作用:提高了程序的扩展性
父类引用指向子类对象--多态的向上转型;
继承是多态的前提;即 使用父类或接口接受子类的具体对象;
有方法重写--接口;
格式:
父类 变量名 / 接口 接口名 = new 子类();
注意:
1.多态中的对象不能访问子类中特有的方法 / 属性;
2.父类引用指向子类对象 :Animal(父类) a(对象--子类的) = new Cat()(子类);
***在内存中的过程:
在栈内存中开辟一个main函数区域,
首先在方法区加载类ErHa(子类)字节码文件,
然后加载Dog(父类)的字节码文件,
然后在堆内存中new一个父类的对象,产生一个地址值指向方法区的方法,同时赋值默认值;
然后继续在堆内存中new一个子类的对象,产生一个地址值指向方法区的方法,同时赋予默认值;
然后继续在堆内存中new一个父类的对象,产生一个地址值指向方法区的方法,同时赋值默认值;
然后继续在堆内存中new一个子类的对象,产生一个地址值指向方法区的方法,同时赋予默认值;
3.子类的对象放到了父类里,但是不能运用子类的特有的方法,需要强转才能实现--多态的向下转型。
格式:子类 变量名 = (子类) 需要转型的子类变量名
instanceof 判断该对象是否属于这个类 ,返回的值是Boolean类型的;
代码块:用一对{}括起来的代码;
分类(根据 位置和声明 的不同分类):
1.局部代码块:
位置:在方法内
特点:
作用:限定变量生命周期(生命周期限定在一对{}之间),代码块结束其中定义的变量立即释放,提高内存空间利用率;
2.构造函数(无参,有参):
位置:类里
特点:创建对象时调用,创建几个对象就会调用几次;
作用:用来初始化对象;
3.构造代码块:
位置:写在类里的,但是在函数之外的一对{ };
特点:创建对象时调用,创建几个对象就会调用几次;
作用:可以将构造方法中相同的代码存放到一起,每次调用构造方法都可以执行 并且在构造方法之前执行。
执行顺序:
先执行静态构造代码块(随着类的加载),之后(随着对象的创建)执行构造代码块,随后(随着函数的调用 / 进入函数层面之后)执行构造函数,局部代码块的执行与放的位置有关 (如果放在main函数里面那就和位置有关,如果是放在某一方法里面的话,当函数调用的时候才会执行) 。
△4.同步代码块(线程)
适用场景:当所有的对象均含有相同的属性值的时候,可以将属性用static修饰;
当所有的对象均有相同的方法的时候,可以将方法用static修饰;
修饰构造代码块,使其变成静态构造代码块,优于构造代码块执行;
调用属性的方式:
特点:
注意:
静态变量 | 成员变量 | |
调用方式 | 可以类名直接调用(也可以通过对象调用) |
只能通过创建对象来调用 |
内存中的位置 | 方法区的static区域 |
堆内存对应的对象里 |
使用的对象 | 所有对象 | 创建它的对象(谁创建谁使用) |
初始化方式 | 随着类的加载而加载 |
随着对象的创建而创建 |
生命周期 | 与类一致 | 与栈内存里的对象一致 |
虚拟机JVM直接通过类名方式调用main函数--需要static修饰,不需要进行创建对象后再使用对象调用(因为创建对象需要在方法里面创建 ,而此时还没有函数,所以只能用static修饰,使得可以用类名直接调用main函数)
Note:
String[ ] args 是传统的传入数据的方式 ,如今可以用Scanner 从控制台传入数据; 而不摈弃传统方式则是为了兼容传统的项目;
设计模式:一共23种;
其中最常用的是 单例设计模式:保证内存中只有一个对象;
1.构造函数私有化(不能让其他类来创建对象);
注意:将构造函数私有化,是为了保证在main函数里面不能创建对象,而只能在类的内部创建对象,然后在外界获取该对象;
获取的对象只有一个就算获取多次也是同一个对象;
2.在类的内部创建一个对象(需要有private static修饰);
3.提供一个公共的方法获取该对象(只能用类名进行调用,不能再创建对象调用了,因此需要在调用的函数名之前加上static以实现在外界用类名调用,这个时候函数变成静态的了,然而静态方法只能访问静态成员,所以之前在类的内部创建的对象也需要static修饰;之后为了不让外界能够直接访问类内部创建的对象,还需要用private修饰--所以类内部创建的对象名之前应该有private static修饰);
饿汉式:在类的内部直接创建对象(类一加载对象就创建了) -----线程安全
懒汉式: 在类的内部不会直接创建对象,只有调用获取对象的方法时才会创建(可以节省内存); -----线程不安全
△ 拓展:工厂设计模式,装饰模式;
抽象类回顾
abstract class A{
public abstract void show();
public abstract void show1();
public abstract void show2();
public abstract void show3();
}
接口格式
interface A{
public (abstract) void show ();
public (abstract) void show1 (); //默认抽象方法
public static void show2(); //静态方法
public default void show3(); //????
}
使用方法:使用接口(类)实现接口 -- implements
格式:class B implements A {
}
主要功能:扩展功能
Notes:
关系note:
1.类与接口:类可以多实现接口;
类与接口没有继承关系;类可以同时继承类和实现接口;
2.接口与接口:接口可以多继承接口,类只能单继承类;
接口不能实现接口(因为实现接口需要重写,这样接口中就会出现具体的方法,与接口中只能有抽象方法和静态方法相冲突;)
抽象类和接口区别:
1.构造函数:
抽象类里有构造函数,接口里没有;因为抽象类里有非抽象的方法需要子类实例化来加载;
2.成员属性:
抽象类里的属性可以是变量也可以是常量;接口里的属性只能是公共的静态的常量,且只能被 public static final修饰;
3.成员方法:
抽象类里的方法可以是抽象的也可以是非抽象的,但是没有静态方法(因为abstract和static相冲突),接口里的一定是抽象方法或者静态方法,且一定是被public修饰的;
4.继承:
抽象类只能被单继承(类与类之间),接口可以多继承(接口与接口之间);// 类可以多实现接口(类与接口之间);