目录
JAVA
一、Java概述
1995年诞生
1>什么是JDK
JAVA开发工具包
该工具从 Oracle | Cloud Applications and Cloud Platform 下载 下载11
选择源代码,路径(别安在桌面)环境配置
需要配置的环境变量:
1.新增一个变量JAVA_HOME,值为安装JDK的路径,C:\Java\jdk-XX.XX.XX
2.修改path变量,新增一个值为%JAVA_HOME%\bin 注意是修改,不要去除原来的配置
打开命令窗口
javac -version java - version
2>JAVA包括三大类
--JAVASE (JAVA标准版) 基础
--JAVAEE (JAVA企业版) 主攻方向
--JAVAME (JAVA微型版)
3>JAVA语言特性(开源,免费,纯面向对象,跨平台)
*简单性 例如:JAVA中不再支持多继承,c++是支持多继承的;C++中有指针,JAVA无
*面向对象
*可移植性 :一次编译,到处运行(可运行至windows或者Linux操作系统)
结论:显然JAVA程序不能直接和操作系统打交道,所以让JAVA在虚拟机(JVM:Java虚拟机)上运行
---Java虚拟机分为可适用于windows版的和Linux版的
*多线程 *健壮性 *安全性
二、Java的加载与执行
*Java程序的运行分为两个阶段:编译和运行
编译:
--编译阶段主要任务是检查Java源程序是否符合Java语法,若符合Java语法规则会生成正常的字节码文件(.java 文件编译成 .class文件);若不符合无法生成字节码文件
--字节码文件中不是纯粹的二进制,这种文件无法在操作系统当中直接执行
编译阶段的过程:
*程序员需要在硬盘的某个位置<位置随意>新建一个.java扩展名的文件,该文件被称为Java源文件,源文件中编写的是Java源代码/源程序。而这个源程序是不能随意编写的,必须符合Java语法规则
*Java程序员需要使用JDK当中自带的javac.exe命令进行Java程序的编译
Javac怎么用的?在哪用?
--在DOS命令窗口中使用 --javac的使用规则: javac java源文件的路径
javac是一个Java编译器工具/命令
*一个Java源文件可以生成多个.class文件
*编译结束后可将class文件拷贝到其他操作系统当中运行『跨平台』
运行:
--JDK安装之后,除了自带一个javac.exe之外,还有另一个工具/命令,叫做java.exe,主要负责运行阶段
--Java.exe 怎么用?在哪用
在DOS窗口上使用
用法:java 类名
例如:如果硬盘上有一个A.class,那么就这样用: java A (注意:不能写成 java A.class 错误的)
运行步骤:
*输入:java A
*java.exe命令会启动java虚拟机(JVM),JVM会启动类加载器classLoader
*ClassLoader会去硬盘上搜索A.class文件,找到该文件将该字节码文件装载到JVM当中
*JVM将A.class字节码文件解释成二进制010001001这样的数据
*然后操作系统执行二进制和底层硬件平台进行交互
三、开始一个JAVA程序
1.安装JDK,在官网下载
2.JDK,JRE,JVM的关系搞清楚
3.JDK目录的介绍
JDK/bin:该目录下存放很多命令,例如javac.exe和Java.exe javac.exe负责编译 java.exe负责运行
4.开发helloworld.java源程序
public class HelloWorld{ public static void main (string[] args) { system.out.println("Hello World!") } }
5.将helloworld.java源程序通过javac工具进行编译:
--首先需要解决的问题是:javac命令是否可用
--打开dos命令窗口,直接输入javac,回车
补充:
cmd:windows操作系统如何搜索硬盘上某个命令?
1.首先会从当前目录下搜索 2.如果目录搜索不到的话,会从环境变量path指定的路径中搜索某个命令 3.如果都搜索不到,就会报错‘不是内部或外部命令’
6.Javac命令怎么用
注释
--出现在Java源程序当中,对Java源程序的解释说明,增强程序的可读性
--单行注释: //,只注释当前行 --多行注释: /* */ --javadoc注释:/** * */ 其中数据可被javadoc.exe提取出来,比较专业的注释
public class和class的区别
字面值
*字面值
--10、100 --3.14 --“abc" --true false
*字面值就是数据
*字面值是java源程序的组成部分之一,包括标识符和关键字他们都是源程序的组成部分。
*数据在现实世界中是分门别类的,所以数据在计算机编程语言中也是有类别的,称为数据类型
*注意:Java语言当中所有的字符串型字面值必须使用双引号括起来
Java语言当中所有的字符型字面值必须用单引号括起来
变量
1.什么是变量? 字面值是数据类型的一部分
变量本质上来说是内存上的一块空间,这块空间有数据类型、名字、字面值
变量包含三个部分:数据类型、名称、字面值
变量是内存中存储数据的最基本的单元
2.数据类型的作用
不同的数据有不同的数据类型,不同的数据类型底层分配不同大小的空间
数据类型是指导程序在运行阶段应该分配多大的内存空间
3.变量要求:变量中存储的具体的数据必须和变量的数据类型一致,当不一致的时候,编译报错
4.声明/定义变量的语法格式: 数据类型 变量名;
变量名:只要是合法的标识符就行,规范要求:首字母小写,后面每个单词首字母大写。
5.变量名声明之后怎么赋值?
语法格式: 变量名 = 字面值;
要求:字面值的数据类型必须和变量的数据类型一致。 等号是一个运算符。
6.声明和赋值可以放在一起完成。
7.变量赋值之后,可以重复赋值,变量的值可以变化;
int i = 10; System.out.println(i); //10
int i = 20; System.out.println(i); //20
8.有了变量的概念之后,内存空间得到了重复的使用:
9.通常访问一个变量包括两种访问形式:
第一种:读取变量中保存的具体数据 get/数据
第二章:修改变量中保存的具体数据 set/数据
10.变量在一行上可以声明多个: int a,b,c;
11.变量必须先声明再赋值才能访问
12.在方法体中的Java代码是遵守自上而下的顺序依次执行,逐行运行的
特点:第二行的代码必须完整的执行完之后第三行才正式执行
13.在同一个“作用域”当中,变量名不能重名,但是变量可以重新赋值。
14.关于作用域:
*什么是作用域?
描述的就是变量的有效范围,在什么范围之内是可以被访问的,只要出了这个范围该变量就无法访问了。
*变量的作用域只要记住一句话:
出了大括号就不认识了
如果需要使用具体的上下限取值,可以通过包装类中的常量进行使用
System.out.println(Integer.MAX_VALUE); System.out.println(Integer.MIN_VALUE);
进制转换
int k = 123; String ob = Integer.toBinaryString(k); //将十进制转换成二进制的字符串 System.out.println(ob); //1111011 String oo = Integer.toOctalString(k); //将十进制转换成8进制的字符串 System.out.println(oo); //173 String oh = Integer.toHexString(k); //将十进制转换成16进制的字符串 System.out.println(oh); //7b
数据类型
1.数据类型的作用是什么? 程序当中有很多数据,每一个数据都是有相关类型的,不同类型的数据占用空间大小不同。数据类型的作用是指导JVM在运行程序的时候给该数据分配多大的内存空间。
2.Java中的数据类型包括两种:
基本数据类型和引用数据类型
3.关于基本数据类型:
基本数据类型包括四大类八小种:
第一类:整数型 byte short int long
第二类:浮点型 float double
第三类:布尔型 boolean
第四类:字符型 char
4.字符串不属于基本数据类型,属于引用数据类型,但是字符属于基本数据类型
5.八种数据类型各自占用空间大小是多少?
基本数据类型 占用空间大小「单位:字节」 ————————————————————————————
byte 1 short 2 int 4 long 8 float 4 double 8 boolean 1 char 2
6.计算机在任何情况下都只能识别二进制 『现代的计算机底层采用交流电的方式,接通和断开两种状态』
7.什么是二进制? 一种数据的表示形式
8.字节(byte):
1 byte = 8 bit (1个字节=8个比特位) 1个比特位表示一个二进制位:1/0 1 KB = 1024 Byte 1 MB = 1024 KB 1 GB = 1024 MB 1 TB = 1024 KB
9.整数型当中的byte类型占用1个字节,所以byte类型的数据占用8个比特位,那么byte类型的取值范围是什么?
*关于Java中的数字类型,数字都是有正负之分的,所以在数字的二进制当中有一个二进制位被称为“符号位”,并且这个“符号位”在所有二进制位的最左边,0表示正数,1表示负数。
*byte类型最大值:01111111~
---字符型 单引号括起来,里面只能有一个字符
一个中文占用2个字节,所以char可以存储一个中文字符
数据类型之转义字符 \(课本P19)
转义字符出现在特殊字符之前,会将特殊字符转移成普通字符
在控制台上输出一个普通的单引号字符
//char a = ' ' java中不允许这样编写程序,编译报错
char a = ' ' '; 报错 第一个单引号和第二个配对,第三个单引号找不到另一半
正确用法:
char a = '\''; System.out.println(a);
输出"HelloWorld!"
System.out.println("\"HelloWorld!\"");
//JDK中自带的native2ascii.exe命令,可以将文字转换成unicode编码形式
---怎么使用这个命令:
在命令行输入native2ascii,回车,然后输入文字之后回车即可得到Unicode编码
char n = '\u4e2d'; //中国的中对应的Unicode编码是 4e2d \u转义。 System.out.println(n);
\u联合一串数字是某个文字的Unicode编码
关于Java语言中的整数型 byte short int long
1.java语言当中的”整数型字面值“被默认当作int类型来处理。要让这个”整数型字面值“被当作long类型来处理的话,需要在”整数型字面值“后面添加I/L,建议使用大写的L
2.java语言中的整数型字面值有三种表示方式:
1、十进制「是一种缺省默认的方式
2、八进制「在编写八进制整数型字面值的时候需要以0开始」
3、十六进制「在编写十六进制整数型字面值时需要以0x开始」零
数据类型强制类型转换(窄化操作)
大转小:
char a=(long) b; //关于double float f1 = 1234.56//语法报错,因为系统默认带小数点的数据类型为double类型 float f1 = (float)1234.56; float f1 = 1234.56f;
运算符
普通运算符
浮点数无法精确存放
public static void main(String[] args) { double d = 10.12; double res = d % 3; System.out.println(res); //res = 1.1199999999999992 }//返回值应该为1.12,由于浮点数不能精确存放,所以返回值是一个类似值
求余计算中,负数符号位不参与计算
10./3=3.333... 10/3=3
除法分母不能为0;
%求余运算
*单目运算符 ++ --
i++ i = i+1
i-- i = i-1
i++是先取值后加1 ++i是先加1后取值
int k = 1; int res = k+++k+++k; System.out.println(k); // k=3 System.out.println(res); //res = 6 1+2+3
需求:用户输入身高和体重,计算出BMI值
import java.util.Scanner; public class test9 { public static void main(String[] args) { //键盘输入身高体重 System.out.println("请输入身高(单位为米):"); Scanner sc = new Scanner(System.in); double height = sc.nextDouble(); System.out.println("请输入体重(单位为千克):"); double weight = sc.nextDouble(); //算法问题 double bmi = height / weight /weight; System.out.println("您的bmi的指数为:"+bmi); } }
赋值运算符*
/* * 重要结论:扩展类的赋值运算符不改变运算结果类型,假设最初这个变量的类型是byte类型,无论怎么进行追加或者追减,最终 * 该变量的数据类型还是byte类型 * */ public class OperatorTest01{ public static void main (String[] args) { //基本的赋值运算符 int i = 5; //i= i + 5; i += 5; //+=可以翻译为“追加、累加” System.out.println(i); i -= 5; //等同于i = i-5; System.out.println(i); i *= 5; System.out.println(i); //25 i /= 5; System.out.println(i); //5 i %= 5; System.out.println(i); //0 //---------------------------------- //10没有超出byte取值范围,可以直接赋值 byte b = 10; //编译错误 因为编译器只检查语法,不运行程序,编译器发现b+5的类型是int类型,而b是byte类型, //b = b + 5; //编译正确 b = (byte)(b + 5); System.out.println(b); byte x = 10; x += 5; //等同于 x = (byte)(x+5); 不等同于 x = x+5; System.out.println(x); byte y = 0; y += 128; //等同于 y = (byte)(y + 128); System.out.println(y); //-128 [精度损失] } }
注意:+= 符号可以自动转换类型
k++也可自动转换类型
k=k+1 不能自动转换
*字符串连接运算符
/* * 关于java中的+运算符: * 1.+运算符在Java语言当中有两个作用: * *加法运算,求和 * *字符串的连接运算 * 2.当“+”运算符两边的数据是数字的话,一定是进行加法运算。 * 3.当“+”运算符两边的数据只要有一个数据是字符串,一定会进行字符串运算。并且,连接运算之后的结果还是一个字符串类型 * 4。在一个表达式当中可以出现多个+,在没有添加小括号的前提下,遵循自左至右的顺序依次执行运算 */ public class OperatorTest02{ public static void main(String[] args) { int a = 10; int b = 20; System.out.println(10 + 20 );//这里的加号是求和 System.out.println(10+20+"30"); //输出3030 //要求在控制台上输出“10+20=30” System.out.println("10 + 20 = " + a + b); //输出1020 System.out.println("10 + 20 = " + (a + b)); //输出30 System.out.println(a + "+ b" + "=" + (a + b)); //输出 10 + b =30; // a = 1100; // b = 299; System.out.println(a + "+" + b + "=" + (a + b)); } }
*关系运算符 > < >= <= == !=
用于判断两个数据之间的大小关系,计算结果为boolean,如果成立返回为true,不成立返回为false
注意:由于浮点数无法精确存放,所以判断k==1.0这个写法是错误的 正确写法: a - b 的绝对值小于1e-6
java提供了一个工具类Math,其中包含了一个abs()的方法实现求绝对值 Math.abs(d1-1)<1e-6 判断浮点类型的变量d1的值是否为1
*三目运算符/三元运算符 ? :
*(逻辑运''算符&& || ! ) 短路计算
&& 与 同真则真,其余为假
|| 或 同假则假
//左边为假,右边为假,结果为假,循环结束后,age自增1,所以结果为101 public static void main(String[] args) { int age = 100; char cc='A'; if(cc>='B' || age++<=200) { System.out.println("zzzzzz"); } System.out.println(age); //age=101 }
//左边为假,右边为真,结果为假,短路,age++不执行 public static void main(String[] args){ int age = 100; char cc = 'A'; if(cc>='B' && age++<200 ){ System.out.println("zzzzzz"); } System.out.println(age);//输出100,因为短路的原因,导致age++没有得到执行 }
逻辑非: !布尔表达式 !true !false
关于& (按位与)
如果左右两边都是布尔类型,那么也相当于逻辑运算
和&&的区别:这里不能进行短路求值
位运算符:
&按位与 有0则0
|按位或 有1则1
^ 按位异或 对应位相同为0,不同为1
0和任何数异或,结果为任何数
按位取反 1变0,0变1
移位运算符
左移 <<
0000 1011 11 0000 1011 << 1 0001 0110 22 11*2^1 左移是右边补0 0000 1011 << 2 0010 1100 44 11*2^2
右移>>
0000 1011 >> 1 0000 0101 5 11/2^1 右移是补符号位 正数补0,负数补1 0000 1011 >> 2 0000 0010 2 11/2^2
>>> 无符号右移 统统左边补0 没有无符号左移这个概念
女朋友生气的时候不能讲道理,当女朋友不开心的时候她就是规则,你就要想想你女朋友为什么不开心。 例如:博哥媳妇生气的时候,博哥会在旁边傻笑,或者买点她喜欢吃的东西
如果是异地恋 ,最好的情况是当你在学习的时候,要让你的男朋友也在学习,这样大家都在忙的时候就不会有时间吵架,而是共同成长
常见数学计算:**
*Math.sqrt();计算平方根
int k = 16; double res = Math.sqre(k); System.out.println(res);
*Math.pow(a,b) 计算a的b次方
*Math.max(a,b)计算最大值
*Math.min(a,b)计算最小值
*Math. abs() 计算绝对值
*Math.ceil()天花板的意思,就是返回大的值;floor的地板的意思
四、结构化编程
任何复杂的问题都可以用三种基本算法结构来描述:顺序,选择,循环
条件分支:if switch
if:
package come2; import java.util.Scanner; public class test02 { public static void main(String[] args) { //要求输入成绩,如果>85,显示优秀,如果>70,显示良好,>60,及格,否则不及格 Scanner sc = new Scanner(System.in); System.out.println("请输入成绩"); int score=sc.nextInt(); //从键盘上获取数值 if(score<0 || score>=100 ) { System.out.println("输入成绩不合法"); }else { if(score>=85) { System.out.println("优秀"); }else if( score>=70) { System.out.println("良好"); }else if(score>=60) { System.out.println("及格"); }else { System.out.println("不及格"); } } } }
开关分支语句switch:
语法:switch(表达式)
case 1: case 2: ...... break;
default: ...... break;
package come2; import java.util.Scanner; public class test03 { public static void main(String[] args) { // 需求:输入年月份,显示对应本月的天数 System.out.println("请输入年份:"); Scanner sc = new Scanner(System.in); int year = sc.nextInt(); System.out.println("请输入月份:"); int month=sc.nextInt(); int days = 0; if(month >=12 || month < 1) { System.out.println("您输入的月份错误!"); }else{ switch(month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: days = 31; break; case 4: case 6: case 9: case 11: days=30; break; default: if(year%4==0 && year%100 !=0 || year % 400 ==0) { days = 29; }else { days = 28; break; } } System.out.println(year + "年" + month + "月有" + days + "天"); } sc.close(); } }
循环
while循环 (未知循环次数时使用)
语法: while(条件){循环体;} 当条件成立时反复执行循环体,每执行一次判断一次条件
*break是立即终止循环,进入循环结构的后续代码继续执行,循环体执行结束
*continue是终止本次循环,进入下次循环,循环并没有执行结束
必须让while的条件执行慢慢的逼近于假,否则循环永远执行
do/while循环(不知道循环多少次,但至少循环一次)
do{循环体;}while{条件;}
先执行循环体,然后进行条件判断,如果条件为true,则执行下次循环;如果条件为false则终止循环
*不同于while循环的点:do/while循环至少执行一次循环体,而while循环有可能一次都不执行。
必须让while的条件执行慢慢的逼近于假,否则循环永远执行
for循环 (已知循环次数时使用)
for(表达式1;表达式2;表达式3){循环体;}
int res = 0; for(int i=1;i<101;i=i+2){ res+=i; } System.out.println("1+3+5+....+99="+res);
练习题
1.计算输入数据的阶乘值
System.out.println("请输入整数:"); Scanner sc = new Scanner(System.in); int kk = sc.nextInt(); int res = 1; for(int k = 1; k<kk;k++){ res*=k; } System.out.println(kk+"!="+res);
2.互换两个数的值(不允许使用中间变量)
//使用了中间值 package come2; import java.util.Scanner; public class test05 { public static void main(String[] args) { System.out.println("请输入一个数:"); Scanner sc = new Scanner(System.in); int num1 = sc.nextInt(); System.out.println("请输入第二个数:"); int num2 = sc.nextInt(); System.out.println("您输入的数据为("+num1+","+num2+"),更改后为:"); int tmp = num1; num1 = num2; num2 = tmp; System.out.println("("+num1+","+num2+")"); sc.close(); } }
//第一个数为10,第二个数为6 num1 = num1+num2; //num1 = 16 num2 = 6 num2 = num1 - num2; num1 = num1 - num2;
位运算法
3.输出三个数中的最大值和最小值
public static void main(String[] args) { //1.输入数字 Scanner sc = new Scanner(System.in); System.out.println("请输入第一个数:"); int a = sc.nextInt(); System.out.println("请输入第二个数:"); int b = sc.nextInt(); System.out.println("请输入第三个数:"); int c = sc.nextInt(); //2.算法思想 int max = 0; int min = 0; if(a>b) { max = a; else { max = b; } if(c>max) max = c; min = a<b?a:b; min = c<min?c:min; System.out.println("最大的数为:"+max); System.out.println("最小的数为:"+min); }
4.输出1-100的奇数(每行输出6个)
5.1-100求和(for while以及do/while的写法)
6.1-100奇数求和
7.1~100可以被三整除的数
8.求100以内所有能被3整除但不能被5整除的数
9.打印出所有水仙花数
10.判断一个数是否是质数
11.编程求出自然数101-205中的所有质数
12.输入两个正整数m和n,求其中最大公约数和最小公倍数
13.100~50000之间有多少整数,其各位数字之和为5分别是哪些数(例如1324的各位数字之和为1+3+2+4等于10(不为5),并统计满足条件的整数有多少个
衡量一个代码的优劣
1.正确性
2.可读性
3.健壮性
4.高效率与低存储:时间复杂度、空间复杂度(衡量算法的好坏)
五、数组
一维数组的使用
1>一维数组的声明和初始化
数组的使命是用来存储一组相同类型数据的数据的
2>如何调用数组的指定位置的元素
3>如何获取数组的长度
System.out.println(array.length);
public class Test { public static void main(String[] args) { int[] array = {1,2,3,4,5,6}; for (int i = 0; i < array.length ; i++) { System.out.println(array[i]); } System.out.println("========="); for(int z : array){ //for-each for(数组中每个元素的类型和变量 : 数组名) System.out.println(z); } System.out.println("=========="); Arrays.sort(array); //给数组排序 String ret = Arrays.toString(array); //如果只打印,可以用这个方法 System.out.println(ret); }
4>如何遍历数组
5>数组元素的默认初始化值
6>数组的内存解析
package shuzu; public class test01 { public static void main(String[] args) { //1.一维数组的声明和初始化 int num=10; int id = 1001; //静态初始化:数组的初始化和数组元素的赋值操作同时进行 int[] ids;//声明 ids = new int[] {1001,1002,1003,1004};//初始化 //动态初始化:数组的初始化和数组元素的赋值操作分开进行 String[] names = new String[5]; //错误的写法 //1.int[] arr1 = new int[]; //2.int[5] arr2 = new int[5]; //总结: //数组一旦初始化完成,其长度就确定出来了 //2.如何调用数组的指定位置的元素:通过角标的方式调用 //Java中的角标(或索引)从0开始,到数组长度-1结束 names[0] = "大桥"; names[1]="孙策"; names[2]="刘健"; names[3]="张三"; names[4]="李四"; //只给了五个空间,所以最多只能建立五个 //3.如何获取数组的长度 //属性:length System.out.println(names.length); //5 System.out.println(ids.length); //4 //4.如何遍历数组元素 /*System.out.println(names[0]); System.out.println(names[1]); System.out.println(names[2]); System.out.println(names[3]); System.out.println(names[4]);*/ for(int i = 0;i < names.length; i++) { System.out.println(names[i]); } //5.数组元素的默认初始化值 /* >数组元素是整形:0 >数组元素是浮点型:0.0 >数组元素是char类型:0或‘\u0000’ >数组元素是boolean型:false >数组类型是引用数据类型:null*/ int [] arr = new int[4]; for(int a = 0;a < arr.length;a++) { System.out.println(arr[a]); //0 0 0 0 } short [] arr1 = new short[4]; for(int a = 0;a < arr.length;a++) { System.out.println(arr1[a]); //0 0 0 0 } char [] arr2 = new char[4]; for(int a = 0;a < arr.length;a++) { System.out.println(arr2[a]); //四个空格 } String[] arr3 = new String[4]; System.out.println(arr[0]); //6.数组的内存解析 } }
数组是引用类型
计算机中有五块内存:
java虚拟机栈、本地方法栈、方法区、堆、程序计数器
局部变量 内存就是在栈上开辟的
引用变量 会new一个对象,所以内存在堆上开辟
数组初始化 null是一个零号地址,是受保护的范围,所以当给数组初始化为null之后再给数组赋值会出现空指针异常
int[] array = null; //array这个引用不指向任何对象,所以在书写的时候直接给赋值null
空指针异常:
原因:使用了一个值为null的引用
根据提示检查引用,看一下为什么是null。
通过其中任何一个引用,修改这个对象的值,另一个引用去访问的时候,也是会被改变的
public static void main(String[] args) { int[] array1 = {1,2,3,4}; //把array1赋值给array2,此时就没有人引用这个对象了,就会被系统回收 int[] array2 = {11,22,33,44}; array1 = array2; //两个数组指的是同一块内存地址 array1[0] = 1888; System.out.println(array1); //1888 22 33 44 System.out.println(array2); //1888 22 33 44 }
public static void main2(String[] args) { int[] array = new int[100]; for (int i = 0; i < 100; i++) { //遍历数组 array[i] = i + 1; } System.out.println(Arrays.toString(array)); } public static void main3(String[] args) { int[] array1 = {1,2,3,4}; //把array1赋值给array2,此时就没有人引用这个对象了,就会被系统回收 int[] array2 = {11,22,33,44}; array1 = array2; //两个数组指的是同一块内存地址 array1[0] = 1888; System.out.println(array1); //1888 22 33 44 System.out.println(array2); //1888 22 33 44 } //数组的拷贝 //法一: public static void main4(String[] args) { int[] array = {1,2,3,4}; //可以当扩容来用 int[] copy = new int[array.length]; //src:从哪里拷贝 srcPos:从拷贝数组的哪个位置开始拷贝 //dest:拷贝到哪里 destPos:从拷贝数组的拷贝到哪个位置 //length:就是拷多长 System.arraycopy(array,1,copy,0,array.length-1); //支持部份拷贝,可以指定区间拷贝 System.out.println(Arrays.toString(array)); System.out.println(Arrays.toString(copy)); } //法二: public static void main5(String[] args) { int[] array = {1,2,3,4}; int[] ret = Arrays.copyOf(array,array.length); System.out.println(Arrays.toString(array)); System.out.println(Arrays.toString(ret)); ret[0] = 199; System.out.println(Arrays.toString(array)); //1 2 3 4 System.out.println(Arrays.toString(ret)); //199 2 3 4 } public static void main6(String[] args) { int[] array1 = {1,2,3,4}; int[] array2 = {1,2,3,4}; //System.out.println(array1 == array2); //错误写法 System.out.println(Arrays.equals(array1,array2)); //比较两个数组里面的值是否相等,输出结果为true或者false } //数组克隆 (产生副本) public static void main(String[] args) { int[] array1 = {1,2,3,4}; int[] array2 = array1.clone(); System.out.println(Arrays.toString(array1)); System.out.println(Arrays.toString(array2)); array2[0] = 199; //改变array2[0]的值,不会影响到array1的值 此时叫做深拷贝 System.out.println(Arrays.toString(array1)); //1,2,3,4 System.out.println(Arrays.toString(array2)); //199,2,3,4 }
四个常用方法:sort() toString() fill() copyOf()
//数组逆序 public static void swap(int[] array,int i,int j){ int tmp = array[i]; array[i] = array[j]; array[j] = tmp; } public static void reverse(int[] array){ int left = 0; int right = array.length-1; while(left < right){ swap(array,left,right); left++; right--; } } public static void main(String[] args) { int[] array = {6,5,4,4,2}; reverse(array); System.out.println(Arrays.toString(array)); }
一维数组的练习题:
package shuzu; import java.util.Scanner; //数组练习题 /* * 从键盘读取学生成绩,找出最高分,并输出学生成绩等级 * 成绩>=最高分-10 等级为‘A’ * 成绩>=最高分-20 等级为‘B’ * 成绩>=最高分-30 等级为‘C’ * 其余 等级为‘D’ * * 提示:先读入学生人数,根据人数创建int数组,存放学生成绩 * */ public class test02 { public static void main(String[] args) { //1.使用Scanner,读取学生个数 Scanner sc = new Scanner(System.in); System.out.println("请输入学生个数:"); int number = sc.nextInt(); //2.创建数组,存储学生成绩,动态初始化 int[] scores = new int[number]; //3.遍历数组,给数组中的元素赋值 System.out.println("请输入"+number+"个学生成绩:"); for(int i = 0;i < scores.length;i++) { scores[i] = sc.nextInt(); //给数组元素赋值 } //4.获取数组中元素的最大值:最高分 int maxScore=0; for(int i = 0;i < scores.length;i++) { //这一步的遍历可以和上一步的遍历合二为一 if(maxScore < scores[i]) { maxScore = scores[i]; } } //5.根据每个学生成绩与最高分的差值,得到每个学生的等级,并输出等级和成绩 for(int i = 0;i < scores.length;i++) { if(maxScore - scores[i] <=10) { System.out.println("student" + i +"score is:"+scores[i]+",grade is “A"); }else if(maxScore - scores[i] <=20) { System.out.println("student" + i +"score is:"+scores[i]+",grade is “B"); }else if(maxScore - scores[i] <=30) { System.out.println("student" + i +"score is:"+scores[i]+",grade is “C"); }else { System.out.println("student" + i +"score is:"+scores[i]+",grade is “D"); } } } }
多维数组的使用
1.理解:对于二维数组的理解,我们可以看做是一维数组array1又作为另一个一维数组array2的元素而存在 其实,从数组底层的运行机制来看,其实没有多维数组
2.1>一维数组的声明和初始化
2>如何调用数组的指定位置的元素
3>如何获取数组的长度
4>如何遍历数组
5>数组元素的默认初始化值
6.>数组的内存解析
package shuzu; public class test03 { public static void main(String[] args) { //1. 二维数组的声明和初始化 int[] arr = new int[] {1,2,3}; //一维数组 //二维数组静态初始化 int[][] arr1 = new int[][] {{1,2,3},{4,5,6},{7,8}}; int[] arr4[] = new int[][] {{1,2,3},{4,5,6,10},{7,8}}; int[] arr5[] ={{1,2,3},{4,5,6},{7,8}}; //二维数组动态初始化(两种方式) String[][] arr2 = new String[3][2]; String[][] arr3 = new String[3][]; //二维中的元素个数不同 //2.如何调用数组的指定位置的元素 System.out.println(arr1[0][1]); //2 System.out.println(arr2[1][1]); //null arr3[1] = new String[4]; System.out.println(arr3[1][0]); //如果不写上一行代码就会报错 //3.获取二维数组的长度 System.out.println(arr4.length ); //3 System.out.println(arr4[0].length); //3 System.out.println(arr4[1].length); //4 //4.如何遍历二维数组 for(int i = 0;i < arr4.length;i++) { for(int j = 0;j < arr4[i].length;j++) { System.out.println(arr4[i][j] + " "); } System.out.println(); } //5.二维数组的使用 /* * 规定:二维数组分为外层数组元素,内层数组元素 * eg: int[][] arr = new int[4][3]; * 外层元素: arr[0],arr[1]等 * 内层元素:arr[0][0],arr[1][2]等 * */ } }
数组中常见的算法
1.数组元素的赋值(杨辉三角、回形数等)
2.求数值型元素中的最大值、最小值、平均数、总和等
3.数组的复制、反转、查找(线性查找、二分法查找)
4.数组元素的排序算法
比特之二维数组
//二维数组的定义 public static void main(String[] args) { int[][] array= {{1,2,3,4},{5,6,7,8}}; for (int i = 0; i < 3; i++) { for(int j = 0;j < 4; j++){ System.out.println(array[i][j]+" "); } System.out.println(); } }
//法二: public static void main(String[] args) { int[][] array = {{1,2,3,4},{5,6,7,8}}; for(int i = 0;i<array.length;i++){ for(int j = 0;j < array[i].length;j++){ System.out.println(array[i][j]+" "); } System.out.println(); } System.out.println("======================"); for(int[] ret : array){ for(int x : ret){ System.out.println(x+" "); } System.out.println(); } System.out.println("======================="); System.out.println(Arrays.deepToString(array)); }
2
六、方法的使用
1.方法的定义以及使用
一个方法从写好到用起来
1.定义方法,决定这个方法返回值为什么类型?方法名叫啥?形参有几个?什么类型?什么顺序?
2.使用这个方法,调用这个方法。方法名()->看一下有几个参数,都是啥类型?都是啥顺序
3.方法有返回值吗?要不要接收?拿什么类型接收?接收了返回值,我用返回值干啥?
方法结束后,add方法开辟的栈帧,就被系统回收了
总结:1.方法的调用是在栈上的
2.当方法遇到return或者遇到右括号代表当前方法结束了。对应方法开辟的栈帧回收了
//方法定义: 修饰符 返回值类型 方法名称 ([参数类型 形参 ...]){ 方法体代码; [return 返回值]; }
在Java中方法的位置灵活,可在main函数的上方,也可在下方
方法名采用小驼峰命名
该返回值为int类型,方法体中必须含有return;
public class Test { //计算两个数之和 public static int add(int a,int b){ int c = a+b; return c; } public static void main(String[] args) { int a = 10; int b = 20; int ret1 = add(2,3); //接收形参的值 System.out.println(ret1); int p = ret1*5; System.out.println(p); int ret2 = add(a,b); //这里只调用了方法,参数为主函数中定义的 System.out.println(ret2); }
若返回类型为void(无返回值类型),方法体中不用写return; 主函数中也不需要接收参数,直接调用即可
public static void add2(int a , int b){ System.out.println((a+b)*5); } public static void main(String[] args) { add2(1,2); }
注意:1。类型和个数,顺序都要匹配
2.--匹配!!!!
//main方法求阶乘 public static void main3(String[] args) { int sum = 0; for (int j = 0; j <= 7 ; j++) { int ret = 1; for (int i = 1; i <= j; i++) { ret *= i; } sum += ret; } System.out.println(sum); } //调用方法求阶乘 public static int facNum(int num){ int sum = 0; for (int j = 1; j <= num ; j++) { int ret = 1; for (int i = 1; i <= j; i++) { ret *= i; } sum += ret; } // System.out.println(sum); return sum; } public static void main4(String[] args) { int ret = facNum(5); System.out.println(ret); } //调用方法改进 public static int facNum1(int num){ int sum = 0; for (int j = 1; j <= num ; j++) { int ret = fac(j); sum += ret; //sum += fac(j); } // System.out.println(sum); return sum; } public static int fac(int n){ int ret = 1; for (int i = 1; i <= n; i++) { ret *= i; } return ret; } public static void main(String[] args) { System.out.println(facNum1(5)); }
2.掌握方法传参
在Java中,实参的值永远都是拷贝到形参中,形参和实参的本质是两个实体
//两个数交换 做不到 public static void swap(int a,int b){ int temp = a; a = b; b = temp; } public static void main(String[] args) { int a = 10; int b = 20; swap(a,b); System.out.println(a); System.out.println(b); }
做不出来这道题,因为形参的值不会影响实参的值,学习了类和对象之后可以解决
3.掌握方法重载
1.方法的名称必须相同
2.方法的参数列表顺序不同(顺序,个数,类型)
3.和返回值没有关系
public static int sum(int a,int b ){ return a+b; } public static int sum(int a,int b,int c ){ return a+b+c; } public static double sum(double a,double b ){ return a+b; }
4.掌握递归
自身中包含了自身
把原问题分解为小问题,然后小问题的解决方式和大问题的解决方式是一样的
递归满足两个条件:
1.自己调用自己
2.有一个终止条件(起始条件)
最难的地方在于如何确定这个递推公式
public static int fac(int n){ if(n == 0){ return 1; } int tmp = n * fac(n-1); return tmp; } public static void main(String[] args) { System.out.println(fac(5)); }
所有的递归过程都在栈上
//练习一:按顺序打印一个数字的每一位(例如:1234打印出1 2 3 4) public static void func(int n){ if(n <= 9){ System.out.println(n % 10); return; //终止条件,必须要有 } func(n/10); System.out.println(n % 10); } public static void main(String[] args) { func(1234); }
//计算某一个数的阶乘 public static int sum(int n){ if(n == 1){ return 1; } return n + sum(n-1); } public static void main(String[] args) { System.out.println(sum(10)); }
循环写和递归写有什么区别
递归的好处:代码少
坏处:不好理解,不好书写
循环的好处:容易理解
坏处:代码多
递归浪费的空间的比较多,可能会将栈溢出
//写一个递归方法,输入一个非负整数,返回组成他的数字之和。 //例如:输入1729,则应返回1+7+2+9,它的和是19 public static int func1(int n){ if(n <= 9){ return n; } return n%10 + func1(n/10); } public static void main(String[] args) { System.out.println(func1(123)); }
//斐波那契数列 public static int fib(int n){ if(n == 1 || n == 2){ return 1; } int ret = fib(n-1)+fib(n-2); return ret; } public static void main(String[] args) { System.out.println(fib(5)); }
七、类和对象
方法的定义
calss 类名{ 属性、成员属性 成员变量 行为、成员方法 }
class phone{ public String brand; //品牌 属性 public String type; //型号 public double weight; //重量 //。。。。。。。。。。。。 public void movie(){ System.out.println("看电影"); // 动作 } public void call(){ System.out.println("打电话"); } }
一般来说,一个Java文件只写一个public类
类:可以说是用户自定义的一个类型
一个类可以实例化多个对象,通过关键字new
注意事项:
类名注意采用大驼峰定义
成员前写法统一为public,后面会详细解释
此处写法的方法不带static关键字,后面会详细解释
课堂练习:定义一个狗类
public class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.color="黄色"; dog.name="圆圆"; System.out.println(dog.color+" "+dog.name); dog.barks(); dog.eat(); } } class Dog{ //属性 public String name; public String color; //行为 public void eat(){ System.out.println("狗狗吃东西"); } public void barks(){ System.out.println("汪汪汪"); } }
注意事项:
new关键字用于创建一个对象的实例
使用.来访问对象中的属性和方法
同一个类可以创建多个实例
类在方法区,不占内存
public class Test2 { public static void main1(String[] args) { Person1 person1 = new Person1(); System.out.println(person1.age); System.out.println(person1.name); //此时name和age并没有赋值,但是此时不会报错,因为他们属于成员变量,所以这里编译器会给他们默认值 //如果是引用类型,那么为null;int float 对应的0值;boolean默认值flase;char默认值'\u0000' person1.show(); } public static void main2(String[] args) { //引用指向null代表person1不指向任何对象 Person1 person1 = null; person1.name = "张三"; person1.age = 19; person1.show();//空指针异常 } public static void main3(String[] args) { //引用可以指向引用吗? Person1 person1 = new Person1(); Person1 person2 = new Person1(); person1 = person2; //不能 //这个代表person这个引用指向了person2这个引用的对象 } public static void main(String[] args) { //一个引用能不能同时指向多个对象 Person1 person1 = new Person1(); person1 = new Person1(); person1 = new Person1(); person1 = new Person1(); person1 = new Person1(); //最后person1只是指向了一个对象 //不能!!! } } class Person1{ public String name; public int age; public void eat(){ System.out.println("吃东西!"); } public void show(){ System.out.println("姓名:"+name+"年龄:"+age); } }
this引用
public class DateUtil { public int year; //成员变量 public int month; public int day; public void setDate(int y,int m,int d ){ year = y; month = m; day = d; } public void show(){ System.out.println(year+"年"+month+"月"+day+"日"); } public static void main(String[] args) { DateUtil dateUtil = new DateUtil(); dateUtil.setDate(2022,4,8); dateUtil.show(); } }
this引用调用的是成员方法的对象
this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
this只能在“成员方法”中使用
在“成员方法”中,this只能引用当前对象,不能再引用其他对象
this还有另外两种用法:
this访问构造方法
this访问成员方法
对象的构造和初始化
什么是构造方法?
构造方法非常特殊,这个方法没有返回值,而且方法名和类名一致
(public) DateUtil(){ System.out.println("不带参数的构造方法,这个方法如果没有写,Java会提供一个这样的方法"); }
如果自己写了就会调用自己写的,自己没写就会自动调用默认的,不带参数的方法 只会被调用一次
方法的重载
public DateUtil(int y,int m,int d){ this.year = y; //加上this来表示给当前的对象的属性赋值 this.month = m; this.day = d; System.out.println("调用了带有3个参数的构造方法!"); } public DateUtil(){ System.out.println("不带参数的构造方法,这个方法如果没有写,Java会提供一个这样的方法"); }
不可以加void,不然就不叫构造方法了
当我们调用完成构造方法,那么对象就生成了
构造方法的步骤:
1.为对象分配内存
2.调用合适的构造方法
this访问构造方法:
public static void main(String[] args) { DateUtil dateUtil = new DateUtil(); dateUtil.show(); } public DateUtil(int y,int m,int d){ this.year = y; //加上this来表示给当前的对象的属性赋值 this.month = m; this.day = d; System.out.println("调用了带有3个参数的构造方法!"); } public DateUtil(){ this(1999,4,8); //this调用当前对象的其他构造方法 //1.this只能放在第一行 //2.只能在构造方法内部才能使用 System.out.println("不带参数的构造方法,这个方法如果没有写,Java会提供一个这样的方法"); } public void show(){ System.out.println(year+"年"+month+"月"+day+"日"); } //输出:调用了带有3个参数的构造方法! 不带参数的构造方法,这个方法如果没有写,Java会提供一个这样的方法 1999年4月8日
不能形成环:
public Date(){ this(1900,1,1);//1 } public Date(int year,int month,int day){ this(); //2 } //1和2只能写一个
this访问成员方法
public DateUtil(int y,int m,int d){ this.show(); this.year = y; //加上this来表示给当前的对象的属性赋值 this.month = m; this.day = d; System.out.println("调用了带有3个参数的构造方法!"); }
this.data:访问当前对象的成员变量(成员变量是指:在类的内部,方法的外部定义的变量)
this.func():访问当前对象的成员方法------>this不能调用静态的成员方法和属性
this():调用当前对象的其他构造方法
//整合代码 import java.util.Date; public class DateUtil { public int year; public int month; public int day; public void setDate(DateUtil this,int y,int m,int d ){ //DateUtil this是隐式参数,每个成员方法第一个参数默认是this this.year = y; //加上this来表示给当前的对象的属性赋值 this.month = m; this.day = d; } public DateUtil(int y,int m,int d){ this.show(); this.year = y; //加上this来表示给当前的对象的属性赋值 this.month = m; this.day = d; System.out.println("调用了带有3个参数的构造方法!"); } public DateUtil(){ this(1999,4,8); //this调用当前对象的其他构造方法 //1.this只能放在第一行 //2.只能在构造方法内部才能使用 System.out.println("不带参数的构造方法,这个方法如果没有写,Java会提供一个这样的方法"); } public void show(){ System.out.println(year+"年"+month+"月"+day+"日"); } public static void main(String[] args) { DateUtil dateUtil = new DateUtil(); dateUtil.show(); } public static void main2(String[] args) { DateUtil dateUtil = new DateUtil(2022,11,11); dateUtil.show(); } public static void main1(String[] args) { DateUtil dateUtil = new DateUtil(); //实例化对象,此时会调用对象的构造方法 dateUtil.setDate(2022,4,8); dateUtil.show(); DateUtil dateUtil2 = new DateUtil(); //方法前面是哪个引用就调用哪个对象的方法 dateUtil2.setDate(2022,5,8); dateUtil2.show(); DateUtil dateUtil3 = new DateUtil(); dateUtil3.setDate(2022,5,8); dateUtil3.show(); //静态方法里面不能用this,会报错 // System.out.println(this.day); } }
成员变量的生命周期:对象创建出生,对象销毁就结束
局部变量的生命周期:进入方法创建,出方法结束
八、封装
面向对象的三大特性:封装、继承、多态
什么是封装?封装有什么意义?
--- 封装是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互 ( 把类的实现细节进行了隐藏,对外只提供一些交互的接口就可以)
访问修饰限定符--->访问权限
目前已知的权限有:
private public protected 默认权限
每种权限 到底是什么样的权限?
private 被修饰之后,只能在当前类当中才能使用
class Student{ // private String name; //被修饰之后,只能在当前类当中才能使用 public String name; public int age; public void show(){ System.out.println("姓名:"+name); } } public class Test { public static void main(String[] args) { Student student = new Student(); student.name="xtx"; student.show(); } }
一般情况下,成员变量是private,成员方法是public 但这不是固定的
包的概念
包是对类、接口等的封装机制的体现,是一种对类或接口等的很好的组织方式
在一个工程中,允许存在相同名称的类,只要在不同的包中即可
import java.util.*; *是通配符,如果有两个以上的类在util包里面,就可以简写 *不是把里面所有的导进来,而是用啥取啥
import java.util.*; import java.sql.*; Date date = new Date(); //报错,因为util和sql里面都有Date类,这个时候编译器不知道用哪个里面的类 java.util.Date date = new java.util.Date(); //正确写法。手动导包
自定义包
在src下右键选择new下的package
package com.bite.www; //package声明当前类是在哪个包底下 public class TestDemo { public static void main(String[] args) { com.bite.www2.TestDemo testDemo = new com.bite.www2.TestDemo(); } }
包的访问权限:
private:同一个包的同一个类
default 同一个包的同一个类,同一个包的不同类
protected:同一个包的同一个类,同一个包的不同类,不同包中的子类
public:哪里都能访问
import java.util.*; import java.sql.*; class Student{ // private String name; //被修饰之后,只能在当前类当中才能使用 public String name; public int age; public void show(){ System.out.println("姓名:"+name); } } public class Test { public static void main(String[] args) { Student student = new Student(); student.name="xtx"; student.show(); java.util.Date date = new java.util.Date(); } }
private
class Person{ //name和eat这两个方法都已经被封装起来了,使用private关键字进行了封装,此时,name的权限就变小了。 //它只能在当前类中被访问 private String name; public int age; //构造方法: public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name){ this.name = name; } public String getName(){ //提供公开的接口 return this.name; } public void setName(String name){ this.name=name; } public Person(){ } private void eat(){ System.out.println("吃饭!"); } public void show(){ System.out.println(name+" "+age); } } public class Test { public static void main(String[] args) { Person person = new Person("zhangsan",10); person.setName("xutianxin"); person.show(); System.out.println(person.getName()); // person.name = "xtx"; // person.eat(); } }
static静态成员变量
在Java中,被static修饰的成员,称之为静态成员,也可以称之为类成员,其不属于某个具体的对象,是所有对象所共享的
成员变量:
1.静态成员变量 /类变量或者类成员
2.非静态成员变量/普通成员变量
class Student{ private String name; private int age; private static String classRoom = "107期"; //构造方法 public Student(String name, int age) { this.name = name; this.age = age; } public void doClass(){ System.out.println(name+" "+"在上课"); } } public class Test2 { public static void main(String[] args) { Student student1 = new Student("xtx",18); Student student2 = new Student("xtx1",17); Student student3 = new Student("xtx2",16); student1.doClass(); } }
class Student{ private String name; private int age; public static String classRoom = "107期"; //构造方法 public Student(String name, int age) { this.name = name; this.age = age; } public void doClass(){ System.out.println(name+" "+"在上课"); } } public class Test2 { public static void main(String[] args) { System.out.println(Student.classRoom); //静态的成员变量不属于对象,所以不用通过对象的引用来访问,直接可以通过类名访问 } }
static静态成员方法
成员方法:
静态成员方法/类方法
非静态成员方法
//在静态方法的内部不能使用非静态的数据成员 //只要是非静态的数据成员,都需要通过对象的引用去访问它 public static void func(){ System.out.println(this.name); //静态方法里面不能使用this System.out.println(name); System.out.println("staticFunc()"); }//eror public static void func(){ Student student = new Student(); System.out.println(student.name); System.out.println("staticFunc()"); }
我们建议:获取静态的成员变量或者是设置静态的成员变量,此时最好的方法是静态的,否则要是非静态的,还得实例化对象。
对于静态成员变量初始化:
1.直接赋值
2.默认初始化
3.可以通过提供get和set方法来进行初始化
4.在构造对象的时候可以在构造方法中进行赋值
5.通过代码块进行赋值
代码块
普通代码块 定义在方法内部的代码块
构造块 类里面,方法的外面
静态块
同步代码块
带有两个参数的代码块:
{ name = "caocao"; System.out.println("非静态代码块/实例化代码块/构造代码块!-》初始化非静态的数据成员") }//一般不写实例化代码块,一般初始化不这样写 public Student(String name,int age){ this.name = name; this.age = age; System.out.println("带有两个参数的构造方法!") }
不带参数的构造方法:
{ name = "caocao"; System.out.println("非静态代码块/实例化代码块/构造代码块!-》初始化非静态的数据成员") } public Student(){ System.out.println("不带参数的构造方法!") }
public String name = "wusuowei"; //一般初始化这样写 { name = "caocao"; System.out.println("非静态代码块/实例化代码块/构造代码块!-》初始化非静态的数据成员") }//输出caocao ---------------------------------------------------------------------------------------- { name = "caocao"; System.out.println("非静态代码块/实例化代码块/构造代码块!-》初始化非静态的数据成员") } public String name = "wusuowei"; //输出wusuowei
总结:如果都是非静态的,那么看定义顺序。谁在后,最后就是哪个值
编译器编译好代码后,会把非静态代码块的东西放到构造方法里面的最前面
static{ System.out.println("静态代码块->初始化静态的数据成员/提前准备一些数据") }
不管static静态代码块放在哪里,一定是先执行的
只要类被加载,静态代码块一定会执行,并且只被执行一次(不管写多少次)
先执行静态的,再执行非静态的,最后执行构造方法
对于你想输出一个对象的引用的值的时候,如果你没有自己写一个toString方法,那么就会调用Object这个类的方法。。。。如果自己写了,就调用自己写的
//自己写toString class Test{ public String toString(){ ....................... } }
继承和多态
继承:
对共性进行抽取,从而实现代码的复用
eg:Dog extends Animal
子类 父类
class Animal { public String name; public int age; public void eat(){ System.out.println(name+"正在吃饭"); } } class Dog extends Animal{ public void wangwang(){ //子类特有的方法 System.out.println(name +"汪汪叫"); } } class Cat extends Animal{ public void miaomiao(){ System.out.println(name +"喵喵叫"); } } public class Test { public static void main(String[] args) { Dog dog = new Dog(); dog.name = "坦克"; dog.wangwang(); Cat cat = new Cat(); cat.name = "圆圆"; cat.eat(); } }
子类中访问父类的成员变量
class Base{ public int a; public int b; public int c = 10; } class Derived extends Base{ public int c = 100; public void func(){ //子类有 那么就拿子类的 子类没有 就拿父类的 System.out.println(a); System.out.println(b); System.out.println(c); //如果子类和父类有同名的成员变量,优先访问子类自己的 System.out.println(super.c); //super暂且理解为代表父类的引用 //正确的理解:super只是一个关键字。最大的作用是写代码或者读代码的时候,体现出更好的可读性! } } public class Test2 { public static void main(String[] args) { Derived derived = new Derived(); System.out.println(derived.a); derived.func(); //c = 100 } }
super:
1.super.data 在子类当中访问父类的成员变量
2.super.func() 在子类当中访问父类的成员方法
注意:
只能在非静态方法中使用
在子类方法中,访问父类的成员变量和方法
重载(Overloading)和重写的区别
重载:一个类里面有两个方法,方法的形参不同,返回值类型不同
重写:有继承关系的两个类,具有相同的方法名,返回值类型,形式参数列表,子类修改父类的属性或方法
子类构造方法
在继承关系上,当我们在构造子类的时候,一定要先帮助父类进行构造
class Cat extends Animal{ int size; public Cat(String name, int age,int size){ super(name,age); //初始化父类,如果不初始化,就报错 this.size = size; //初始化自己 }
super在子类构造方法内,调用父类构造方法的时候,一定要放在第一行
public Cat(String name, int age){ super(name,age); this("1",2,3) //报错 super和this不能同时出现 }
protect类
同一个包的同一个类,同一个包的不同类,不同包的子类
多态
-
向上转型(upcasting)(子转为父)
-
向下转型(downcasting)(父转为子),需要加强制类型转换符
-
注意:向下转型和向上转型的前提条件是
两种类型之间必须存在继承关系
,否则编译器报错 -
多态指的是父类型引用指向子类型对象,编译运行两种状态
-
编译阶段:静态绑定父类的方法
-
运行阶段:动态绑定子类型对象的方法
-
多种形态:编译时候一种形态,运行时候另一种形态
-
-
必须向下转型的情况:访问的是子类对象中“特有”的方法。此时必须进行向下转型
编译阶段
对于编译器来说,编译器只知道一个变量的类型,只知道这个类型有哪些方法。编译器检查语法的时候,发现变量对应的类型有那个方法,所以编译通过。这个过程称为静态绑定方法
运行阶段
堆内存当中创建的Java对象是类的子类对象,所以调用方法的时候,真正执行的是子类对象的方法,所以运行阶段会动态执行子类对象的方法,这个过程属于运行阶段绑定。运行阶段绑定属于动态绑定
满足的条件:
1.必须在继承体系下
2.子类必须对父类方法进行重写
3.通过父类的引用调用重写的方法
向上转型
把子类对象给父类
eg.Animal animal = dog;
理论上来说,符号两边的数据类型必须一致,否则赋值会出错
当发生转型之后,此时通过父类的引用只能访问父类自己的成员。不能访问到子类特有的成员。
父类引用指向子类对象
动态绑定:
在编译时,不能确定方法的行为,需要等到程序运行时,才能具体确定调用哪个类
静态绑定:
在编译时,根据用户传递实参类型就确定了具体调用了哪个方法,典型代表:函数重载
当父类引用 引用的对象不一样的时候表现出的行为是不一样的
重写需要注意的点:
1.private修饰的方法不能被重写
2.static修饰的方法不能被重写
3.子类的访问修饰限定权限要大于等于父类的权限private < 默认的 <protect<public
4.被final修饰的方法不能被重写,此时这个方法被称作密封方法
优点:让代码实现更简单灵活
缺点:不能调用到子类特有的方法
向下转型
Dog dog = (Dog)animal1; dog.wangwang();
父类的类型给到子类,但是向下转型非常的不安全
if(animal1 instanceof Bird){ //改正 Bird bird = (Bird)animal1; bird.fly(); }
instanceof:判断animal1的引用是否指向Bird对象,如果指向了才能转型。
public class Test4 { public static void main(String[] args) { Animal1 animal1 = new Bird1(); Animal1 animal2 = new Dog1(); if (animal1 instanceof Dog1){ Dog1 dog1 = (Dog1)animal1; dog1.eat(); } if (animal1 instanceof Bird1){ Bird1 bird1 = (Bird1)animal1; bird1.fly(); } } } class Animal1{ } class Bird1 extends Animal1{ public void fly(){ System.out.println("鸟儿在飞"); } } class Dog1 extends Animal1{ public void eat(){ System.out.println("狗狗在吃饭"); } }
class Shape{ public void draw(){ System.out.println("画圆形!"); } } class Rect extends Shape{ public void draw(){ System.out.println("画矩形!"); } } class Cycle extends Shape{ public void draw(){ System.out.println("画圆形!"); } } class Flower extends Shape{ public void draw(){ System.out.println("❀"); } } public class Test3 { public static void drawMap(Shape shape){ shape.draw(); } public static void main(String[] args) { Rect rect = new Rect(); Cycle cycle = new Cycle(); drawMap(rect); drawMap(cycle); drawMap(new Flower()); } }
public class Test3 { public static void drawMap(Shape shape){ shape.draw(); } public static void main(String[] args) { Rect rect = new Rect(); Cycle cycle = new Cycle(); Flower flower = new Flower(); Shape[] shapes = {cycle,rect}; for (Shape shape: shapes) { shape.draw(); } } }
避免在构造方法中调用重写的方法
注意:
当在父类的构造方法当中去调用父类和子类重写的方法的时候,会调用子类的。
抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象,如果一个类没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
1.抽象类使用abstract来定义类
2.抽象类不能实例化对象
3.此时在抽象类当中,可以有抽象方法,或者非抽象方法
4.什么是抽象方法,一个方法被abstract修饰,没有具体的实现。只要包含抽象方法,那么这个类必须是抽象类
5.当一个普通类继承了这个抽象类,必须重写抽象类中的抽象方法
6.抽象类存在的最大意义就是为了被继承
7.抽象方法不能被private,final,static修饰,所以一定要满足重写的规则
8.当一个子类没有重写抽象的父类的方法,可以把当前子类变为abstract修饰
9.抽象类当中不一定包含抽象方法
抽象类和普通类有什么区别?
1.抽象类不能被实例化
2.抽象类当中,可以包含非抽象方法和抽象方法,但是普通类只能包含非抽象方法!
abstract class Shape{ public void draw(){ System.out.println("画圆形!"); } public abstract void func(); }
抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
抽象类的作用:
抽象类本身不能被实例化,想要使用,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法
九、接口
什么是接口?
1.使用interface关键字来定义接口
2.接口不能被实例化
3.接口中的成员变量默认都是public static final的
4.接口当中的方法不写也是默认为 public abstract的
5.接口当中的方法不能有具体的实现,但是从jdk8开始可以写一个default修饰的方法
6.接口当中不能有构造方法
7.接口需要被类实现使用关键字implements
8.接口当中可以有static修饰的方法!
interface IShape{ public abstract void draw(); //void draw(); //二者等价 } class Rect implements IShape{ public void draw(){ System.out.println("矩形!"); } } class Flower implements IShape{ public void draw(){ System.out.println("❀"); } } public class Test { public static void drawMap(IShape shape){ shape.draw(); } public static void main(String[] args) { // IShape shape = new Ishape(); //接口不能实例化 IShape shape = new Rect(); //向上转型 IShape shape1 = new Flower(); drawMap(shape); drawMap(shape1); } }
接口使用:
接口不能直接使用,必须要有一个“实现类”来“实现”该接口,实现接口中的所有抽象方法
public class 类名称 implements 接口名称{ }
类可以继承一个普通类 类可以继承一个抽象类 类可以继承一个普通类/抽象类 同时实现多个接口 implements A,B
接口可以通过extends 拓展多个接口的功能
接口的使用案例
给对象数组排序 Comparable
package demo2; import java.util.Arrays; class Student implements Comparable<Student>{ public String name; public int age; public int score; public Student(String name, int age, int score) { this.name = name; this.age = age; this.score = score; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", score=" + score + '}'; } @Override public int compareTo(Student o) { if(this.age > o.age){ return 1; }else if(this.age < o.age){ return -1; }else return 0; //如果是姓名比较 // if(this.name.compareTo(o.name) > 0){ // return 1; // }else if(this.name.compareTo(o.name) < 0){ // return -1; // }else // return 0; } } public class Test { public static void sort(Comparable[] array){ for (int i = 0; i < array.length-1; i++) { for (int j = 0; j < array.length-1-i; j++) { // if(array[j] > array[j+1]){ // 交换 // } if(array[j].compareTo(array[j+1]) > 0){ Comparable tmp = array[j]; array[j] = array[j+1]; array[j+1] = tmp; } } } } public static void main(String[] args) { Student[] student = new Student[3]; student[0] = new Student("zhangsan",19,59); student[1] = new Student("lisi",20,69); student[2] = new Student("wangwu",4,83); //System.out.println(student[0].compareTo(student[1])); Arrays.sort(student); System.out.println(Arrays.toString(student)); } public static void main1(String[] args) { int[] array = {1,5,3,8,10,4,3}; Arrays.sort(array); System.out.println(Arrays.toString(array)); } }
接口间的继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口和接口之间可以多继承。即:用接口可以达到多继承的目的
Clonable接口和深拷贝
Object 类中存在一个clone方法,调用这个方法可以创建一个对象的拷贝,但是要想合法调用clone方法,必须要先实现Clonable接口,否则就会抛出CloneNotSupportedException异常
Clonable接口是一个空接口,它的作用就是表示当前对象是可以被克隆的。
1.对自定义类型进行拷贝
class Student implements Cloneable{ public String name; @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class Test { public static void main(String[] args) throws CloneNotSupportedException { Student student1 = new Student(); student1.name = "xtx"; Student student2 = (Student)student1.clone(); System.out.println(student1); System.out.println(student2); } }
浅拷贝和深拷贝
浅拷贝:
深拷贝:克隆了一个文件,修改另一个文件不会影响另一个,这种就叫深拷贝
Object类
hashCode()
暂时理解为算了一个具体对象的位置
结论:1.Object类默认的hashcode()方法用来确定对象在内存中存储的位置是否相同。如果子类将hashCode方法重写,那么hashCode方法的返回值就是子类实例变量转成int类型整数的结果
2.事实上hashcode()在散列表中才能用,在其他情况下没用。在散列表中hashcode()的作用是捕获对象的散列码,进而确定该对象在散列表中的位置
十、内部类
-
实例内部类
-
静态内部类
-
局部内部类
-
匿名内部类(重点)
实例内部类
public class InnerClassTest { int a; int b; InnerClassTest(int a, int b){ this.a = a; this.b = b; } private class InnerClass{ int i1 = 1; int i2 = 2; int i3 = a; int i4 = b; } public static void main(String[] args) { InnerClass innerClass = new InnerClassTest(100, 200).new InnerClass(); System.out.println(innerClass.i1); System.out.println(innerClass.i2); System.out.println(innerClass.i3); System.out.println(innerClass.i4); } }
-
第一:内部类可以使用private和protected修饰,外部类不可以
-
第二:实例内部类要使用,必须先实例化外部类对象
-
第三:实例内部类不能有静态声明(jdk1.8)
静态内部类
public class InnerClassTest { static int a = 100; int b = 200; static class InnerClass{ // 可以定义实例变量 int i1 = 1; int i2 = 2; // 可以定义静态变量 static int i3 = 300; // 可以引用外部类的静态变量 static int i4 = a; // 报错,不能直接引用外部类的实例变量 // int i5 = b; // 可以通过外部类的引用访问外部类的实例变量 int i5 = new InnerClassTest().b; } public static void main(String[] args) { System.out.println(new InnerClassTest.InnerClass().i1); System.out.println(new InnerClassTest.InnerClass().i2); System.out.println(InnerClassTest.InnerClass.i3); System.out.println(InnerClassTest.InnerClass.i4); System.out.println(new InnerClassTest.InnerClass().i5); } }
-
第一:静态内部类对象创建时可以不用创建外部类对象
-
第二:静态内部类可以直接访问外部类的静态变量
-
第三:静态内部类如果要访问外部类的实例变量就需要实例化外部类,通过外部类对象的引用来访问
-
第四:静态内部类可以声明实例成员也可以声明静态成员
局部内部类
public class InnerClassTest { private int a = 200; // 局部变量在内部类中使用必须加final关键字修饰 public void method(final int temp){ class InnerClass{ int i1 = 100; // 可以访问外部类的成员变量 int i2 = a; int i3 = temp; } // 只能在局部内部类所在的方法体内使用内部类 InnerClass innerClass = new InnerClass(); System.out.println(innerClass.i1); System.out.println(innerClass.i2); System.out.println(innerClass.i3); } public static void main(String[] args) { InnerClassTest innerClassTest = new InnerClassTest(); innerClassTest.method(300); } }
-
第一:局部内部类使用外部类的局部变量时,外部类的局部变量需要使用final修饰
-
第二:在局部内部类中可以访问外部类的成员变量
-
第三:局部内部类只在当前方法有效,方法结束后局部内部类就消失了
匿名内部类
public class InnerClassTest { public static void main(String[] args) { // 没有用匿名内部类 MyInterface m1 = new MyInterfaceImpl(); m1.add(); // 用了匿名内部类 MyInterface m2 = new MyInterface() { @Override public void add() { System.out.println("add..."); } }; m2.add(); } } interface MyInterface{ void add(); } class MyInterfaceImpl implements MyInterface{ @Override public void add() { System.out.println("add..."); } }
-
第一:匿名内部类可以直接new接口,在大括号里写具体实现
-
第二:上面这种写法可以使用lambda表达式替代
MyInterface m2 = () -> System.out.println("add..."); m2.add();
十一、String类
String类提供的构造方法有很多,例如以下三种:
public static void main(String[] args) { //1.使用常量串构造 String s1 = "Hello bit"; System.out.println(s1); //2.直接newString对象 String s2 = new String("Hello bit"); System.out.println(s2); //3.使用字符数组进行构造 char[] array = {'H','e','l','l','o','b','i','t'}; String s3 = new String (array); System.out.println(s3); }
String中有两个成员变量
value和hash
value中存的一个数组
方法:
1.求字符串的长度: str1.length()
2.判空方法:str2.isEmpty()
String str2 = " "; //这个引用指str2对象指向的地址里面为空
String str3 = null; //指这个引用不指向任何对象
String类的比较
两种场景:1.相不相同 2.大小
public static void main(String[] args) { String str1 = new String("Hello"); String str2 = new String("Hello"); System.out.println(str1 == str2); //false //比较两个引用所指向的对象当中的内容是否相同 System.out.println(str1.equals(str2)); //true }
按照字母顺序比较大小
str1 > str2 返回正数
str1 < str2 返回负数
str1 = str2 返回0
public static void main(String[] args) { String str1 = new String("Hello"); String str2 = new String("Healo"); System.out.println(str1.compareTo(str2)); //11 //忽略大小写进行比较 System.out.println(str1.compareToIgnoreCase(str2)); }
十一、异常
有继承关系
都继承了Throwable这个类
Throwable 下有两个类:Exception,RuntimeException
编译时期/受查 运行时期/非受查
异常的捕获:
异常处理的五个关键字:
try,catch,finally,throw,throws
注意事项:
异常一旦抛出,其后的代码不会执行
finally
一般我们不建议在finally中写return(被编译器当作一个警告)
面试题:
1.throw和throws的区别?
throw是抛出一个异常 throws是声明一个异常
2.finally中的语句一定会执行吗?
答:一定会执行
异常处理
-
Exception的直接子类是受控异常
-
Exception的子类RuntimeException的子类是非受控异常
-
异常对象的getMessage()方法输出结果是创建异常对象时传入的参数
-
异常对象的printStackTrace()方法输出异常堆栈信息,帮助定位发生异常的位置排查错误
-
throw关键字是抛出异常对象
-
throws关键字是写在方法定义位置上,向调用者抛出
-
try...catch语句块中,try发生异常,后续代码不执行,直接进入对应的catch代码块中
-
如果没有catch异常,发生异常时候后续代码将不会执行
-
finally代码块一定会执行,并且在return之前执行
-
如果有多个catch将自上而下匹配,所以写的时候异常从小到大写