以下内容根据 狂神说Java 的视频,个人整理的笔记
Java
Java诞生及其发展
创建Java的这帮人,希望Java拥有以下特点
- 语法有点像C
- 没有指针
- 没有内存管理
- 真正的可移植性,编写一次,到处运行(为了实现真正的跨平台,在每个操作系统之上增加了一个抽象层叫Java虚拟机,统称JVM。所有的平台上只要安装了JVM,它们就可以跑Java程序)
- 面向对象
- 类型安全
- 高质量的类库
- …
Java拓展
Java 2 标准版(J2SE):去占领桌面端(逐渐不受待见)
Java 2 移动版(J2ME):去占领手机端(逐渐不受待见)
Java 2 企业版(J2EE):去占领服务器端(赶上互联网发展,逐渐受欢迎),三高:高可用,高性能,高并发。大哥人才们基于Java开发了巨多的平台,系统,工具。后来把程序员从事务管理,安全管理,权限管理等方面解放出来,让他们能够专注于业务开发。
Java特性和优势
- 简单性
- 面向对象(万物皆对象)
- 可移植性
- 高性能
- 分布式
- 动态性(通过反射机制)
- 多线程
- 安全性
- 健壮性
Java三大版本
JavaSE:标准版(桌面程序,控制台开发…)
JavaME:嵌入式开发(手机,小家电,…)(现在几乎没人学)
JavaEE:E企业级开发(web端,服务器开发,…)
JDK、JRE、JVM
JDK:Java Development Kit
JRE:Java Runtime Environment
JVM:Java Virtual Machine(通过JVM屏蔽了底层系统的一些差异,从而实现可移植性)
Java开发环境搭建
卸载JDK
- 删除系统相关的环境变量(删除JAVA_HOME,删除Path下关于Java的目录)
- 删除JDK安装目录
- 打开Dos窗口,输入命令,检查是否删除成功
java -version
-
JDK下载与安装(JDK8是主流)
-
配置环境变量(此处为JDK8的操作说明)
- 在系统变量中添加名称为 JAVA_HOME ,值为 JDK安装目录地址 的变量
- 在系统变量Path中添加两个值, %JAVA_HOME%\bin 和 %JAVA_HOME%\jre\bin
- 打开Dos窗口,输入命令,检查是否配置成功
java -version
- JDK目录介绍
- 下载IDE(集成开发环境)应用程序,例如 IDEA,Eclipse等等
为什么要配置环境变量(补充知识)
要搞解开这个疑惑,还是得搞清楚环境变量到底是干什么的,为什么要配置它。所谓环境变量,其实就是操作系统中用来指定操作系统运行环境的一些参数,比如临时文件夹位置和系统文件夹位置等。当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还应到环境变量的PATH中指定的路径值去找。也就是说当执行可执行文件找不到位置的时候,就会去PATH中配置的路径去找。这里你可以做个小测试,就是任意位置新建一个哪怕普通文本文件,将其路径配置成环境变量,然后通过cmd命令行输入文件名,都能打开那个文件,这就环境变量的功劳。因此可以设想如果直接在可执行文件所在文件夹取执行,那不就是找得到吗?为了证明,博主先打开cmd执行编译命令javac,返回不是内部或外部命令,当我们切换到JDK的bin目录C:\Program Files\Java\jdk1.8.0_162\bin路径去执行javac命令的时候,就能够成功执行,好像配置了环境变量一样——
既然可以执行javac,那么我们就在该目录下测试编译一个Java文件。先用记事本编写测试代码 public class Test { public static void main(String[] args) { System.out.println(“博客园 陈本布衣”); } } ,文件名必须和类名保持一致Test.java,然后我们执行编译命令并运行文件——
可是正常情况下我的Java文件是不可能放在bin目录下的,所以我们接着配置好环境变量,使我们能够在任意目录编译Java文件——
配置环境变量的目的似乎达到了,仅仅是为了不用切换到JDK的bin目录就能执行编译命令的方便,可是,现在都是用集成开发环境如idea或eclipse等开发并编译Java程序,如果不是使用命令行javac的方式来编译Java程序,那么,广大的程序员们,你在搭建开发环境的时候,配置环境变量到底是因为什么?
Java书写规范
项目名:字母全部小写
包名:字母全部小写
类名:首字母大写和驼峰原则,例如 HelloWorld
类成员变量(除了常量):首字母小写和驼峰原则,例如 monthSalary
补充说明:.java 文件是不可以直接运行,它需要通过 javac 去编译生成 .class 文件后JVM(Java虚拟机)才能运行,然后通过 java 去运行 .class 文件
Java程序运行机制
- 编译型(把源代码一次性全部转换成计算机可以直接执行的代码,负责编译的程序称为编译器)优点:速度快
- 解释型(运行一句代码编译一句代码)优点:对速度要求不高的脚本可以用
Java既是编译型也是解释型
Java基础语法
注释、标识符、关键字
注释:
- 单行注释
//
- 多行注释
/*
*/
- 文档注释
/**
*
*
*/
文档注释有一定的功能(初学者用得比较少)
标识符:类名、变量名以及方法名都被称为标识符(类里面有变量和方法)Java所有的组成部分都需要名字。
不能使用Java规定好的关键字作为变量名或方法名。以下为关键字
提醒:String(字符串)不是关键字,它是一个类
标识符注意点:
- 只能以字母,下划线(_),美元符($)开头
- 首字符之后可以是字母,下划线(_),美元符($),数字的任意组合
数据类型
Java是一门强类型语言(要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用)
Java的数据类型分为两大类:基本类型(八大基本类型),引用类型
long类型要在数字后面加个L
long num1 = 30L;
float类型要在数字后面加个F
float num2 = 50.1F;
字符类型定义
char name1 = 'A';//正确
char name3 = '曹';//正确
char name2 = 'error';//错误,单引号里面只能是一个字符
整数拓展
int i1 = 0b10; //二进制0b
int i2 = 10;
int i3 = 010; //八进制0
int i4 = 0x10; //十六进制0x 0~9,A~F
浮点数拓展
float f = 0.1f; //数值为0.1
double d = 1.0/10; //数值为0.1
System.out.println(f==d); //输出为false
float f1 = 23131312313123123f;
float f2 = f1 + 1;
System.out.println(f1==f2); //输出为true
//原因:float 有限 离散 舍入误差 大约 接近但不等于
//最好完全使用浮点数进行比较
//因此银行业务不用float,而用BigDecimal类
//BigDecimal 数学工具类
字符拓展
char c1 = 'a';
char c2 = '中';
System.out.println(c1);
System.out.println((int)c1); //强制转换
System.out.println(c2);
System.out.println((int)c2); //强制转换
//所有的字符本质还是数字
//编码 Unicode 两个字节 最多表示65536个字符(2的16次方)
//Unicode 表:97=a 65=A
//转义字符 \
// \t 制表符
// \n 换行
String sa = new String("hello world");
String sb = new String("hello world");
System.out.println(sa==sb);//输出为false
String sc = "hello world";
String sd = "hello world";
System.out.println(sc==sd);//输出为true
布尔值拓展
boolean flag = true;
if(flag==true) {}//新手
if(flag) {}//老手,代码要精简易读
类型转换
由于Java是强类型语言,所以要进行有些运算的时候,需要用到类型转换
char本质上是数字
运算中,不同类型的数据先转化为同一类型,然后进行运算。
int i = 128;
byte b = (byte)i; //byte 最大值为127,此处内存溢出
//强制转换 (类型)变量名
System.out.println(i);
System.out.println(b); //输出-128
强制类型转换
int i = 128;
byte b = (byte)i;//强制类型转换 (类型)变量名
- 从高级别转到低级别(高→低)
自动类型转换
- 从低级别转到高级别(低→高)
注意点
- 不能对布尔值进行转换
- 不能把对象类型转换为不相干的类型
- 在把高容量转换到低容量的时候,强制转换。反之,不用动
- 转换的时候可能存在内存溢出,或者精度问题
int money = 10_0000_0000;//JDK7新特性,数字之间可以用下划线分割,仍然表示10亿
System.out.println(money);
int years = 20;
int total = money*years;//计算的时候溢出
System.out.println(total);//输出-1474836480
long total2 = money*years;
//此处转换成long类型没有用,因为money*years结果为int,并且此处已经出现问题。如果在转换前出现问题则转换无效。
System.out.println(total2);//输出-1474836480
long total3 = money*((long)years);//此时先把一个数转换成long,则输出为200亿
System.out.println(total3);
变量、常量
变量:可以变化的量。可以通过变量操作内存中的数据
Java变量是程序中最基本的存储单元,其要素包括变量类型,变量名和作用域
变量类型可以是基本类型,也可以是引用类型
变量名必须是合法的标识符
变量声明是一条完整的语句,因此每一个声明都必须以分号结束
变量作用域
- 类变量
- 实例变量
- 局部变量
public class Variable {
static int allClicks = 0; //类变量(全局变量),写在类里面,要加一个static关键词,若要在同一个类里面任何位置使用这个类变量则可以直接使用
String str = "hello world"; //实例变量,写在类里面,没有static关键词,如果不进行初始化则为这个类型的默认值。实例变量从属于对象,若要在其它地方使用,则需要实例化这个值(即new这个值)
/*
所有的数值类型默认值一般都是 0 或 0.0 u0000(char的默认值)
布尔值默认值是false
除了基本类型,其余的默认值都是null
*/
public void method() {
int i = 0; //局部变量,写在方法里面,必须 声明 和 初始化值
}
}
类里面默认有一个main方法,类里面除了方法还能有属性(即变量)。除了main方法还可以有其他方法。
常量:初始化后不能再改变值!不会变动的值
常量可以理解为一个特殊的变量,它的值被设定后,在程序运行过程中不允许被改变
final double PI = 3.14;//常量要有一个final关键词
static final double A = 3.14;
final static double B = 3.15;//变量类型前面的修饰符不区分前后
常量名一般使用大写字符
变量的命名规范
- 类名、变量、方法:见名知意
- 类名:首字母大写和驼峰原则,例如 GoodMan
- 方法名,类成员变量(除了常量),局部变量:首字母小写和驼峰原则,例如 monthSalary
- 常量:使用大写字母,例如 MAX _VALUE
运算符
Java语言支持如下运算符
- 算术运算符: +, -, *, /, %(模运算,即取余), ++, –
- 赋值运算符: =
- 关系运算符: >, <, >=, <=, ==, !=, instanceof
- 逻辑运算符: &&, ||, !
- 位运算符: &, |, ^, ~, >>(右移), <<(左移), >>>
- 条件运算符: ?, :
- 扩展赋值运算符: +=, -=, *=, /=
++ 说明( – 情况跟 ++ 类似)
int a = 3;
int b = a++; //a++ 即 a = a + 1。先赋值再自增
System.out.println(a);//a输出为4
int c = ++a; //++a 即 a = a + 1。先自增再赋值
System.out.println(a);//a输出为5
System.out.println(b);//b输出为3
System.out.println(c);//c输出为5
幂运算
double pow=Math.pow(2,3);//使用了Math工具类,表示2的3次方
System.out.println(pow);//pow输出值为8.0
逻辑运算符
boolean a = true;
boolean b = false;
System.out.println("a && b:" + (a&&b));
System.out.println("a || b:" + (a||b));
System.out.println("!(a && b):" + !(a&&b));
int c = 5;
boolean d = (c<4)&&(c++<4);//c++没有被执行
System.out.println(d);
System.out.println(c);//c的输出值为5
位运算
/*
A = 0011 1100
B = 0000 1101
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001(对应位相同为0,不同为1)
~B = 1111 0010
2*8 = 16 2*2*2*2
位运算效率极高
<< 相当于*2
>> 相当于/2
*/
System.out.println(2<<3);//即 0000 0010 相当于3个*2
扩展赋值运算符
int a = 10;
int b = 20;
a+=b;//即 a = a + b
System.out.println(a);
a-=b;//即 a = a - b
System.out.println(a);
System.out.println(""+a+b);//输出1020,。在""后面,在 + 运算符两侧只要出现了String类型,它就会把其它操作数都转换成String再进行连接
System.out.println(a+b+"");//输出30,在""前面的依旧进行运算
三元运算符
// x ? y : z
//如果x==true,则结果为y,否则结果为z
int score = 50;
String type = score < 60 ? "不及格" : "及格";
System.out.println(type);
运算符的优先级:()
包机制、JavaDoc
包的本质是文件夹
一般利用公司域名倒置作为包名,例如:com.baidu.www
为了能够使用某一个包的成员,我们需要在Java程序中明确导入该包。使用 import 语句完成
注意:package 必须放到最上面,放在 import 语句前面。尽量不要让包里面的名字重复
package kuang_test;
import operator.*;//表示导入该包下的所有类
import operator.Demo08;//此处会报错,不要让包里面的名字重复
public class Demo08 {
public static void main(String[] args) {
}
}
JavaDoc命令是用来生成自己API文档的
参数信息
- @author 作者名
- @version 版本号
- @since 指明需要最早使用的jdk版本
- @param 参数名
- @return 返回值情况
- @throws 异常抛出处理
package kuang_test;
//类注释
/**
*
* @author KuangShen
* @version 1.0
* @since 1.8
*/
public class Doc {
//String name;
//方法注释
/**
* @author KuangShen
* @param name
* @return
* @throws Exception
*/
public String test(String name) throws Exception{
return name;
}
}
格式:javadoc 参数 Java文件
把java类文件编译成一份文档阅读
Java流程控制
用户交互Scanner
Java提供了一个工具类 java.util.Scanner 我们可以通过 Scanner 类来获取用户的输入
基本语法
Scanner s = new Scanner(System.in);//通过 System.in 接受用户的输入
通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串,在读取前我们一般需要使用 hasNext() 与 hasNextLine() 判断是否还有输入的数据
Scanner s = new Scanner(System.in);//从键盘接收数据
System.out.println("使用nextLine()方式接收");
if (s.hasNextLine()) {//判断是否还有输入
String str = s.nextLine();//等待用户输入
System.out.println("输出的内容为:" + str);
}
s.close();//凡是属于IO流的类如果不关闭会一直占用资源,要养成好习惯用完就关闭
next() :
- 一定要读取到有效字符后才可以结束输入
- 对输入有效字符之前遇到的空白,next() 方法会自动将其去掉
- 只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符
- next() 不能得到带有空格的字符串
nextLine():
- 以 Enter 为结束符,即 nextLine() 方法返回的是输入回车之前的所有字符
- 可以获取空白
顺序结构
语句与语句之间,框与框之间是按从上到下的顺序进行的,它是由若干个依次执行的处理步骤组成的,它是任何一个算法都离不开的一种基本算法结构。
选择结构
if单选择结构
语法:
if(布尔表达式){
//如果布尔表达式为true将执行的语句
//如果布尔表达式不写则默认为true
}
例子:
Scanner s = new Scanner(System.in);
System.out.println("请输入内容");
String str = s.nextLine();
if (str.equals("Hello")) {//equals 判断字符串是否等于 Hello
System.out.println(str);
}
System.out.println("End");
s.close();
if双选择结构
例子:
Scanner s = new Scanner(System.in);
System.out.println("请输入成绩:");
int score = s.nextInt();
if(score >= 60) {
System.out.println("及格");
}else {
System.out.println("不及格");
}
s.close();
if多选择结构
例子:
Scanner s = new Scanner (System.in);
System.out.println("请输入成绩");
int score = s.nextInt();
if (score==100) {
System.out.println("满分");
}else if(score<100 && score>=90) {
System.out.println("A");
}else if(score<90 && score>=80) {
System.out.println("B");
}else if(score<80 && score>=70) {
System.out.println("C");
}else if(score<70 && score>=60) {
System.out.println("D");
}else if(score<60 && score>=0) {
System.out.println("不及格");
}else {
System.out.println("成绩不合法");
}
s.close();
注意点:
- if 语句至多有1个 else 语句,else 语句在所有的 else if 语句后
- if 语句可以有若干个 else if 语句
嵌套的if结构
语法:
if(布尔表达式1){
if(布尔表达式2){
}
}
switch多选择结构
switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支
语法:
switch(expression){
case value:
//语句
break;//可选
case value:
//语句
break;//可选
//你可以有任意数量的 case 语句
default://可选
//语句
}
switch 语句中的变量类型可以是:
- byte、short、char 或者 int
- 从 Java SE 7 开始,switch 支持字符串 String 类型
- 同时 case 标签必须为字符串常量或字面量
//case穿透 若没有break则当前匹配成功后,继续输出下语句面直至碰到break
//所以每写完一个 case 建议都加上 break ,以防止case穿透
//switch 匹配一个具体的值
char grade = 'B';
switch(grade) {
case 'A':
System.out.println("优秀");
break;
case 'B':
System.out.println("良好");
//break;
case 'C':
System.out.println("及格");
//break;
case 'D':
System.out.println("再接再厉");
break;
case 'E':
System.out.println("挂科");
//break;
default:
System.out.println("未知等级");
}
//JDK7的新特性,表达式结果可以是字符串
//字符的本质还是数字
//反编译 java-->class(字节码文件)-->反编译,为了看懂class(字节码文件)相当于看源码,反编译工具可以上网搜
String name = "秦疆";
switch(name) {
case "秦疆":
System.out.println("秦疆");
break;
case "狂神":
System.out.println("狂神");
break;
default:
System.out.println("弄啥嘞!");
}
循环结构
while 循环
语法:
while(布尔表达式){
//循环内容
}
注意:
- 大多数情况需要让一个表达式失效的方式来结束循环,避免死循环,否则影响程序性能或者造成程序卡死崩溃
- 少部分情况需要循环一直执行,比如服务器的请求响应监听等
do while 循环
语法:
do{
//代码语句,首先至少执行一次
}while(布尔表达式);//这里不要忘了分号 ;
for 循环
语法:
for(初始化;布尔表达式;更新){
//代码语句
}
注意:
-
for循环语句是支持迭代的一种通用结构,是最有效,最灵活的循环结构
-
for循环执行的次数是在执行前就确定的
-
最先执行初始化步骤,可以声明一种类型,可初始化一个或多个循环控制变量,也可以是空语句
//死循环
for( ; ; ) {
}
int a = 1;//初始化条件
while (a<=100) {//条件判断
System.out.println(a);//循环体
a += 2;//迭代
}
System.out.println("while循环结束!");
//初始化//条件判断//迭代
for(int i=1;i<=100;i++) {
System.out.println(i);//循环体
}
System.out.println("for循环结束!");
在Java5中引入了一种主要用于数组的增强型for循环
增强for循环
说明
- 主要用来遍历 数组 和 集合对象
语法:
for(声明语句 : 表达式){
//代码语句
}
例子:
int[] numbers = {10,20,30,40,50};//定义了一个数组
for(int i=0;i<=4;i++) {
System.out.println(numbers[i]);
}
System.out.println("===============================");
//遍历数组的元素
for(int x:numbers) {//把numbers数组的每一项遍历出来赋值给x
System.out.println(x);
}
break & continue
break:break在任何循环语句的主体部分(也在switch语句中使用),均可用break控制循环流程。break用于强行退出循环,不执行循环中剩余的语句。只是跳出循环,并不是终止程序
continue:continue语句用在循环语句体中,用于终止某次循环过程,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判定
例子:
int i = 0;
while (i<100) {
i++;
if (i%10==0) {
System.out.println();
continue;//所有跟10相关的都没有输出
}
System.out.print(i);
}
关于goto关键字
- goto关键字很早就在程序设计语言中出现,尽管goto仍是Java的一个保留字,但并未在语言中得到正式使用。Java没有goto。然而,在break和continue这两个关键字的身上,我们仍然能看出一些goto的影子—带标签的break和continue
- “标签”是指后面跟一个冒号的标识符,例如:label:
- 对Java来说唯一用到标签的地方是在循环语句之前,而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环,由于break和continue关键字通常只中断当前循环,但若随同标签使用,它们就会中断到存在标签的地方
例子:(了解即可)
//打印101~150之间所有的质数
//质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数
int count = 0;
outer:for(int i=101;i<150;i++) {
for(int j=2;j<i/2;j++) {
if(i%j==0) {
continue outer;//带标签的continue
}
}
System.out.print(i+" ");
}
练习
//打印三角形
for(int i=1;i<=5;i++) {
for(int j=5;j>=i;j--) {
System.out.print(" ");
}
for(int j=1;j<=i;j++) {
System.out.print("*");
}
for(int j=1;j<i;j++) {
System.out.print("*");
}
System.out.println();
}
Java方法详解
何谓方法
System.out.println():
System是一个系统的类,out是一个对象,println()是一个方法。
即调用系统类(System)里面的标准输出对象(out)中的方法(println())
- Java方法是语句的集合,它们在一起执行一个功能
- 方法是解决一类问题的步骤的有序组合
- 方法包含于类或对象中
- 方法在程序中被创建,在其它地方被引用
- 设计方法的原则:方法的本意是功能块,就是实现某个功能的语句块的集合。我们设计方法的时候,最好保持方法的原子性,就是一个方法只完成1个功能,这样有利于我们后期的扩展
例子:
public class Demo01 {
public static void main(String[] args) {//main方法,交给程序本身去管理
int sum = add(5,8);
System.out.println(sum);
}
public static int add(int a,int b) {//需要加一个static修饰符,让它成为一个类变量,main方法才能使用它
return a+b;
}
}
方法的定义及调用
Java的方法类似于其它语言的函数,是一段用来完成特定功能的代码片段,一般情况下,定义一个方法包含以下语法:
语法:
修饰符 返回值类型 方法名(参数类型 参数名){
...
方法体
...
return 返回值;//若返回值类型为void,则无return。否则必须有return
}
- 修饰符:可选。告诉编译器如何调用该方法,定义了该方法的访问类型
- 返回值类型:方法可能会返回值。returnValueType是方法返回值的数据类型。有些方法执行所需的操作,但没有返回值,此时returnValueType是关键字void
- 方法名:是方法的实际名字。方法名和参数表共同构成方法签名。
- 参数类型:可选。参数像是一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。
- 形式参数:在方法被调用时用于接收外界输入的数据
- 实参:调用方法时实际传给方法的数据
- 方法体:方法体包含具体的语句,定义该方法的功能
Java支持两种调用方法的方式,根据方法是否返回值来选择
- 当方法返回一个值的时候,方法调用通常被当做一个值。例如:
int larger = max(30,40);
- 如果方法返回值是void,方法调用一定是一条语句。例如:
System.out.println("Hello,kuangshen!");//这是输出方法
例子:
public class Demo02 {
public static void main(String[] args) {
int maxnum = max(20,20);
System.out.println("最大值为" + maxnum);
}
public static int max(int num1,int num2) {
int result = 0;//局部变量必须声明和初始化
if(num1==num2) {
System.out.println("num1==num2");
return 0;//终止方法,即结束当前方法
}
if(num1>num2) {
result = num1;
}else {
result = num2;
}
return result;
}
}
方法调用:对象名.方法名(实参列表)
拓展:值传递(Java)和引用传递
方法重载
重载就是在一个类中,有相同的函数名称,但形参不同(个数不同或类型不同)的函数
例子:
public class Demo02 {
public static void main(String[] args) {
double maxnum = max(10.0,20.0);//JVM的编译器会根据方法的样子,方法的参数类型判断到底执行哪个方法。所以名字可以相同
System.out.println("最大值为" + maxnum);
}
public static int max(int num1,int num2) {
int result = 0;//局部变量必须声明和初始化
if(num1==num2) {
System.out.println("num1==num2");
return 0;//终止方法,即结束当前方法
}
if(num1>num2) {
result = num1;
}else {
result = num2;
}
return result;
}
//方法重载
public static double max(double num1,double num2) {
double result = 0;//局部变量必须声明和初始化
if(num1==num2) {
System.out.println("num1==num2");
return 0;//终止方法,即结束当前方法
}
if(num1>num2) {
result = num1;
}else {
result = num2;
}
return result;
}
}
方法重载的规则:
- 方法名称必须相同
- 参数列表必须不同(个数不同,或类型不同,或参数排列顺序不同等等)
- 方法的返回类型可以相同也可以不相同
- 仅仅返回类型不同不足以成为方法的重载
实现原理:
方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错
命令行传参
有时候你希望运行一个程序的时候再传递给它消息。这要靠传递命令行参数给main()函数实现
public class Demo03 {
public static void main(String[] args) {
for(int i=0;i<args.length;i++) {
System.out.println("args[" + i + "]:" + args[i]);
}
}
}
可变参数
-
JDK1.5开始,Java支持传递同类型的可变参数给一个方法
-
在方法声明中,在指定参数类型后加一个省略号(…)
-
一个方法中只能指定一个可变参数,它必须是方法得到最后一个参数。任何普通的参数必须在它之前声明
例子:
public class Demo04 {
public static void main(String[] args) {
Demo04 demo04 = new Demo04();//创建Demo04类对象
demo04.test(1,2,3,4,5,62);//对象.方法
demo04.test(new int[] {23,42,3,4,5,6});//这样也可以
}
public void test(int... i) {//不加static修饰符,用另外一种方法在main()中实现
System.out.println(i[0]);
System.out.println(i[1]);
System.out.println(i[2]);
System.out.println(i[3]);
System.out.println(i[4]);
System.out.println(i[5]);
}
}
递归
递归就是:A方法调用A方法!就是自己调用自己
利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。
递归结构包括两个部分:
- 递归头:什么时候不调用自身方法。如果没有头,将陷入死循环
- 递归体:什么时候需要调用自身方法
例子:
public static void main(String[] args) {
System.out.println(f(4));
}
public static int f(int n) {
if (n==1) {
return 1;
}else {
return n*f(n-1);
}
}
Java是使用栈机制的,最底层是main()方法。对于嵌套比较深的情况,递归会显得力不从心,物理上造成内存崩溃,因此能不用递归就不用递归
数组
数组概述
数组的定义:
- 数组是相同类型数据的有序集合
- 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成
- 其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们(数组下标从零开始)
数组声明创建
- 首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:
dataType[] arrayRefVar;//首选方法
dataType arrayRefVar[];//效果相同,但不是首选方法
- Java语言使用new操作符来创建数组,语法如下:
dataType[] arrayRefVar = new dataType[arraySize];
- 数组的元素是通过索引访问的,数组的索引从0开始
- 获取数组的长度:
arrayRefVar.length
int[] nums;//声明一个数组
//int nums2[];//不建议这样定义
nums = new int[10];//创建一个数组,这里面 int[10] 的10表示可以存放10个int类型的数字
int[] nums2 = new int[10];//这里声明和创建一起完成
nums[0] = 1;
nums[1] = 2;
nums[2] = 3;
nums[3] = 4;
nums[4] = 5;
nums[5] = 6;
nums[6] = 7;
nums[7] = 8;
nums[8] = 9;
nums[9] = 10;
System.out.println(nums[9]);
int sum = 0;
for(int i=0;i<nums.length;i++) {
sum = sum + nums[i];
}
System.out.println("总和为:" + sum);
Java内存分析:
数组的三种初始化状态
- 静态初始化
int[] a = {1,2,3,4,5};//静态初始化:创建+赋值
System.out.println(a[4]);
Man[] mans = {new Man(),new Man()}//此处 Man[] 为引用类型里面的数组
- 动态初始化
//动态初始化:包含默认初始化
int[] b = new int[10];
b[0] = 10;
b[1] = 10;
System.out.println(b[0]);
System.out.println(b[1]);
System.out.println(b[2]);//打印结果为0
System.out.println(b[3]);//打印结果为0
- 数组的默认初始化
数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化
数组的四个基本特点
- 长度确定,一旦被创建,大小就不可以改变
- 其元素必须是相同类型,不允许出现混合类型
- 数组中的元素可以是任何数据类型,包括(八大)基本类型和引用类型
- 数组变量属于引用类型,数组也可以看成是对象(因为它是new出来的),数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的
数组边界
- 下标的合法区间:[0,length-1],如果越界就会报错,ArrayIndexOutOfBoundsException:数组下标越界异常!
数组使用
- 普通的For循环
int[] arrays = {1,2,3,4,5};
int max = arrays[0];
for(int i=1; i<arrays.length; i++) {
if (arrays[i]>max) {
max = arrays[i];
}
}
System.out.println("max=" + max);
- For-Each 循环
int[] arrays = {1,2,3,4,5};
//增强型for循环,没有下标
for(int array : arrays) {
System.out.println(array);
}
- 数组作方法入参
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
printArray(arrays);
}
public static void printArray(int[] arrays){
for(int i=0; i<arrays.length; i++) {
System.out.print(arrays[i] + " ");
}
}
- 数组作返回值
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
int[] resultshow = reverse(arrays);
for(int a : resultshow) {
System.out.println(a);
}
}
public static int[] reverse(int[] arrays) {
int[] result = new int[arrays.length];
//反转操作
for(int i=0,j=arrays.length-1; i<arrays.length; i++,j--) {
result[j] = arrays[i];
}
return result;
}
多维数组
- 多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组
- 二维数组
//二维数组定义格式
int a[][] = new int[2][5];//该数组可以看成 两行五列 的数组
例子:
public static void main(String[] args) {
int[][] array = {{1,2},{2,3},{3,4},{4,5}};
//[4][2] 相当于 四行两列
/*
1,2 array[0]
2,3 array[1]
3,4 array[2]
4,5 array[3]
*/
printArray(array[0]);
System.out.println();
System.out.println(array[0][0]);
System.out.println(array[0][1]);
for(int i=0; i<array.length; i++) {
for(int j=0; j<array[i].length; j++) {
System.out.println(array[i][j]);
}
}
}
public static void printArray(int[] arrays) {
for(int i=0; i<arrays.length; i++) {
System.out.print(arrays[i] + " ");
}
}
Arrays 类
- 数组的工具类 java.util.Arrays (这个类包含用于操作数组的各种方法,例如排序和搜索,等等)
- Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,而“不用”使用对象来调用(注意:是“不用”而不是“不能”)
例子:
public static void main(String[] args) {
int[] a = {1,2,3,4,9090,31231,543,21,3,23};
System.out.println(a);//[I@52e922
//打印数组元素 Arrays.toString()
System.out.println(Arrays.toString(a));//[1, 2, 3, 4, 9090, 31231, 543, 21, 3, 23]
printArray(a);
//数组进行排序 Arrays.sort()
Arrays.sort(a);
System.out.println();
System.out.println(Arrays.toString(a));//[1, 2, 3, 3, 4, 21, 23, 543, 9090, 31231]数组排序:升序
//数组填充
Arrays.fill(a, 0);
System.out.println(Arrays.toString(a));//[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Arrays.fill(a, 2, 4, 1);//下标2跟4之间填充,不包括4
System.out.println(Arrays.toString(a));//[0, 0, 1, 1, 0, 0, 0, 0, 0, 0]
}
//自己也可以写一个打印数组元素的方法,但不建议重复造轮子
public static void printArray(int[] a) {
for(int i=0; i<a.length; i++) {
if(i==0) {
System.out.print("[");
}
if(i==a.length-1) {
System.out.print(a[i] + "]");
}else {
System.out.print(a[i] + ", ");
}
}
}
冒泡排序
-
冒泡排序是最为出名的排序算法之一,总共有八大排序!
-
冒泡的代码,两层循环,外层冒泡轮数,里层依次比较
例子:
public static void main(String[] args) {
int[] a = {32,54,13,87,456,234,112,568,32};
System.out.print(Arrays.toString(sort(a)));//[568, 456, 234, 112, 87, 54, 32, 32, 13]
}
//冒泡排序
//1.比较数组中相邻的元素,如果第一个数比第二个数大,我们就交换它们的位置
//2.每一次比较都会产生出一个最大或最小的一个数
//3.下一轮则可以少一次排序
//4.依次循环,直至结束
public static int[] sort(int[] array) {
int temp = 0;
//外层循环,判断这个要走多少次
for(int i=0; i<array.length-1; i++) {//length-1防止数组溢出
//内层循环,比较两个数,如果第一个数,比第二个数大,则交换它们两个位置
for(int j=0; j<array.length-1-i; j++) {//每一次比较都会产生出一个最大或最小的一个数,因此要减i
if(array[j+1]>array[j]) {//若要从小到大排序,则把 > 改为 <
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
return array;
}
思考一下如何优化
稀疏数组
-
当一个数组中大部分元素为0,或者为同一值的数组时,可以使用稀疏数组来保存该数组
-
稀疏数组的处理方式是:
- 记录数组一共有几行几列,有多少个不同值
- 把具有不同值的元素和行列及值记录在一个小规模的数组中,从而缩小程序的规模(实现压缩)
-
如下图:左边是原始数组,右边是稀疏数组
上图五子棋的例子:
public static void main(String[] args) {
//1.创建一个二维数组 11*11 0:没有棋子 1:黑棋 2:白棋
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][3] = 2;
System.out.println("输出原始的数组");
for(int[] ints : array1) {
for(int anInt : ints) {
System.out.print(anInt + "\t");
}
System.out.println();
}
System.out.println("=========================");
//转化为稀疏数组保存
//获取有效值的个数
int sum = 0;
for(int i=0; i<11; i++) {
for(int j=0; j<11; j++) {
if(array1[i][j]!=0) {
sum++;
}
}
}
System.out.println("有效值的个数:" + sum);
//2.创建一个稀疏数组的数组
int[][] array2 = new int[sum+1][3];//sum+1 表示要用sum+1行去记录。列固定为3列,行(row),列(col),值(value)三列
array2[0][0] = 11;//11行
array2[0][1] = 11;//11列
array2[0][2] = sum;//有sum个值
//遍历二维数组,将非零的值,存放到稀疏数组中
int count = 0;
for(int i=0; i<array1.length; i++) {
for(int j=0; j<array1[i].length; j++) {
if(array1[i][j]!=0) {
count++;
array2[count][0] = i;//存横坐标
array2[count][1] = j;//存纵坐标
array2[count][2] = array1[i][j];//存值
}
}
}
//输出稀疏数组
System.out.println("稀疏数组:");
for(int i=0; i<array2.length; i++) {
System.out.println(array2[i][0] + "\t"
+ array2[i][1] + "\t"
+ array2[i][2] + "\t");
/*
=========================
有效值的个数:2
稀疏数组:
11 11 2 11行11列,有2个有效个数
1 2 1 第一个有效个数的坐标在 1 2 它的值是1
2 3 2 第二个有效个数的坐标在 2 3 它的值是2
*/
}
System.out.println("===============================");
System.out.println("还原稀疏数组");
//1.读取稀疏数组的值
int[][] array3 = new int[array2[0][0]][array2[0][1]];
//2.给其中的元素还原它的值
for(int i=1; i<array2.length; i++) {
array3[array2[i][0]][array2[i][1]] = array2[i][2];
}
//3.打印还原
System.out.println("输出还原的数组");
for(int[] ints : array3) {
for(int anInt : ints) {
System.out.print(anInt + "\t");
}
System.out.println();
}
}
面向对象编程
Java的核心思想就是OOP(面向对象编程)
初识面向对象
-
面向过程思想
- 步骤清晰简单,第一步做什么,第二部做什么…
- 面向过程适合处理一些较为简单的问题
-
面向对象思想
- 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索(宏观上面向对象,微观上面向过程)
- 面向对象适合处理复杂的问题,适合处理需要多人协作的问题
-
对于描述复杂的事物,为了从宏观上把握,从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍需要面向过程的思路去处理(面向对象和面向过程不可分割)
什么是面向对象
- 面向对象编程(OOP)
- 面向对象编程的本质就是:以类的方式组织代码,以对象的形式组织(封装)数据
- 核心思想:抽象
- 三大特性
- 封装(封装数据,对外提供一个小口,让外部访问。比如保险柜装钱,留一个口让人取钱)
- 继承(比如儿子继承父亲的东西)
- 多态(同一事物多种形态。比如我们都是人,但我们表现出来的形态都不一样)
- 从认识论角度考虑是先有对象后有类(比如先有具体的一个一个人,然后有些人当老师,这些人即为教师这个类别的。有些人当学生,这些人即为学生这个类别的。)。对象,是具体的事物。类,是抽象的,是对对象的抽象
- 从代码运行角度考虑,则是先有类后有对象。类是对象的模板。(先有总设计,再去实现)
方法回顾和加深
方法的定义
- 修饰符
- 返回类型
- break 和 return 的区别
break:跳出switch、结束整个循环(continue是结束一次循环)
return:方法结束、return的返回值的类型 要和 返回值的类型相同
- 方法名
方法名注意规范就OK,驼峰命名法(首字母小写),见名知意
- 参数列表
参数类型 参数名
- 异常抛出
例子:
package oop;
import java.io.IOException;
//Demo01 类
public class Demo01 {
//main 方法
public static void main(String[] args) {
}
/*
修饰符 返回值类型 方法名(...){
//方法体
return 返回值;
}
*/
public String sayHello() {
return "hello,world";
}
/*public void hello() {
return;
}*/
public int max(int a,int b) {
return a>b ? a : b;//三元运算符
}
//数组下标越界:Arrayindexoutofbounds
public void readFile(String file) throws IOException {
}
}
方法的调用
- 静态方法
例子:
package oop;
public class Demo02 {
public static void main(String[] args) {
Student.say();//静态方法调用 类名.方法名
}
//静态方法 static
//非静态方法 没有static
}
package oop;
//学生类
public class Student {
//静态方法
public static void say() {
System.out.println("学生说话了");
}
}
- 非静态方法
例子:
package oop;
public class Demo02 {
public static void main(String[] args) {
//非静态方法调用 实例化这个类 new
//new Student().say();
//对象类型 对象名字 = 对象值;
Student student = new Student();
//调用 对象名字.方法
student.say();
}
}
package oop;
//学生类
public class Student {
//非静态方法
public void say() {
System.out.println("学生说话了");
}
}
拓展:
package oop;
public class Demo02 {
public static void main(String[] args) {}
//这里的方法a是和类 class Demo02 一起加载的
public static void a() {
b(); //一个已经存在的去调用不存在的,所以这里会报错
}
//类实例化之后才存在
public void b() {}
}
- 形参和实参
例子:
public static void main(String[] args) {
//实际参数和形式参数的类型要对应
Demo03 demo03 = new Demo03();//调用非静态方法
int sum = demo03.add(1, 2);//这里的 1 2 是实际参数
System.out.println(sum);
}
public int add(int a,int b) {//这里的 a b 是形式参数
return a+b;
}
- 值传递和引用传递
例子:
package oop;
//值传递
public class Demo04 {
public static void main(String[] args) {
int a = 1;
System.out.println(a);
Demo04.change(a);
System.out.println(a);//结果为1,因为Java是值传递
}
//返回值为空
public static void change(int a) {
a = 10;
}
}
package oop;
//引用传递:对象,本质还是值传递
//要理解好 对象 和 内存
public class Demo05 {
public static void main(String[] args) {
Person person = new Person();//这个叫实例化过程
System.out.println(person.name);//null
Demo05.change(person);
System.out.println(person.name);//秦疆
}
public static void change(Person person) {
//person是一个对象:指向的是 ---> Person person = new Person();这是一个具体的人,可以改变属性!
person.name = "秦疆";
}
}
//一个类里面只能有一个 public class ,但可以有多个 class
//定义了一个类 Person ,有一个属性 name
class Person{
String name;//String 类型的默认值为null
}
- this 关键字
例子:
package oop2;
//学生类
public class Student {
//属性:字段
String name;
int age;
//方法
public void study() {
System.out.println(this.name + "在学习");//this代表当前这个类
}
}
类与对象的关系
-
类是一种抽象的数据类型,它是对某一事物整体描述/定义,但是并不能代表某一个具体的事物(生活中比如动物,植物,手机,电脑…;类中比如Person类,Pet类,Car类…,这些类都是用来描述/定义某一类具体的事物应该具备的特点和行为)
-
对象是抽象概念的具体实例
- 比如张三就是人的一个具体实例,张三家里的旺财就是狗的一个具体实例
- 能够体现出特点,展现出功能的是具体的实例,而不是一个抽象概念
创建与初始化对象
- 使用new关键字创建对象
- 使用new关键字创建的时候,除了分配空间之外,还会给创建好的对象进行默认的初始化,以及对类中构造器的调用
例子:
package oop2;
//学生类
public class Student {
//属性:字段
String name;//默认null
int age;//默认0
//方法
public void study() {
System.out.println(this.name + "在学习");//this代表当前这个类
}
}
package oop2;
//一个项目应该只存在一个main方法
public class Application {
public static void main(String[] args) {
//类,是抽象的,需要实例化
//类实例化后会返回一个自己的对象!
//xiaoming 对象就是一个 Student 类的具体实例
Student xiaoming = new Student();
Student xiaohong = new Student();
xiaoming.name = "小明";
xiaoming.age = 3;
System.out.println(xiaoming.name);
System.out.println(xiaoming.age);
xiaoming.study();
xiaohong.name = "小红";
xiaohong.age = 7;
System.out.println(xiaohong.name);
System.out.println(xiaohong.age);
}
}
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
例子:
package oop2;
//java编译后生成class文件,我们可以去查看以下class文件
public class Person {
//一个类即使什么都不写,它也会存在一个方法,即构造方法
//显示的定义构造器
String name;
/*
构造器的作用
1.使用new关键字,本质是在调用构造器,因此必须要有构造器
2.用来初始化值
*/
//无参构造器
public Person() {//默认的构造器
this.name = "qinjiang";
}
//有参构造器
//注意:一旦定义了有参构造,无参构造就必须显示定义。否则有参构造无效!!!
public Person(String name) {//重载
this.name = name;
}
}
package oop2;
//一个项目应该只存在一个main方法
public class Application {
public static void main(String[] args) {
//new 实例化了一个对象
Person person = new Person("kuangshen");//即使Person这个类里面什么都没写,但它依旧能new出来一个方法,这说明存在一个 Person() 这样的方法
System.out.println(person.name);
}
}
对象的创建分析
例子结合图像分析:
package oop2;
import oop03.Pet;
//一个项目应该只存在一个main方法
public class Application {
public static void main(String[] args) {
Pet dog = new Pet();
dog.name = "旺财";//不在同一个包下的属性,想直接调用需要public修饰符修饰才可以
dog.age = 3;
dog.shout();
System.out.println(dog.name);
System.out.println(dog.age);
Pet cat = new Pet();
}
}
package oop03;
public class Pet {
public String name;
public int age;
//默认存在无参构造
public void shout() {
System.out.println("叫了一声");
}
}
对象是通过引用来操作的:栈—>堆
栈存放引用,堆存放具体的对象
面向对象三大特性
- 封装
- 该露的露,该藏的藏
- 我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合就是仅暴露少量的方法给外部使用
- 封装(数据的隐藏)
- 通常应禁止直接访问一个对象中数据的实际表示,而应该通过操作接口来访问,这称为信息隐藏
- 记住这句话:属性私有,get/set
例子:
package oop04;
/*
封装的意义
1.提高程序安全性,保护数据
2.隐藏代码的实现细节
3.统一接口,这里的例子接口都为 get set
4.提高系统可维护性
*/
//类
public class Student {
//通过private关键字来保证数据对象只能被方法访问,这里通过get、set方法访问
//属性私有
private String name;//名字
private int id;//学号
private char sex;//性别
private int age;
//提供一些可以操作这些属性的方法
//提供一些 public 的 get、set 方法
//get 获得这个数据
public String getName() {
return this.name;
}
//set 给这个数据设置值
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {//提高程序安全性,保护数据
if(age>120 || age<0) {
this.age = 3;
}else {
this.age = age;
}
}
}
import oop04.Student;
//一个项目应该只存在一个main方法
public class Application {
public static void main(String[] args) {
Student s1 = new Student();
s1.setName("秦疆");//隐藏代码的实现细节
System.out.println(s1.getName());
/*s1.setAge(999);//不合法,人的年龄不能为999岁
System.out.println(s1.getAge());*/
s1.setAge(999);
System.out.println(s1.getAge());//输出为3
}
}
- 继承
- 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模(比如汽车类中又分为吉普车类,卡丁车类,大巴车类等等)
- extends 的意思是“扩展”。子类是父类的扩展
- JAVA中类只有单继承,没有多继承!(相当于一个儿子只能有一个爸爸,但是一个爸爸可以有多个儿子。直接的继承只能为一个,间接的继承可以为多个)
- 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等
- 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,用关键字 extends 来表示
- 子类和父类之间,从意义上讲应该具有“is a”的关系
例子:
package oop05;
//在Java中,所有的类,都 默认 直接或者间接继承 Object 类
//Person 人 :基类或父类
public class Person /*extends Object*/{//extends Object 写不写都一样
/*
以下四个修饰符,优先级最高的是 public
1.public 公共的
2.protected 受保护的,不能跨包调用
3.什么都不写则是 default 默认的
4.private 私有的的东西无法继承
属性一般是私有的 private
*/
private int money = 10_0000_0000;//JDK7的新特性,表示十个亿
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void say() {
System.out.println("说了一句话");
}
}
package oop05;
//Student is 人 :派生类或子类
//子类继承了父类,就会拥有父类的全部方法!
public class Student extends Person{
}
package oop05;
//Teacher is 人 :派生类或子类
public class Teacher extends Person{
}
package oop05;
public class Application {
public static void main(String[] args) {
Student student = new Student();
student.say();//因为 Student类 继承了 Person类
student.setMoney(10);
System.out.println(student.getMoney());//10
}
}
super:子类调用父类的方法和属性
super注意点
- super调用父类的构造方法,必须在构造方法的第一个(第一行)
- super必须只能出现在子类的方法或者构造方法中。若在类中调用则会调用到Object类里面去
- super和this(this调用自身有参构造方法)不能同时调用构造方法!
super 和 this 比较
-
代表的对象不同
- this:调用本身对象
- super:调用父类对象
-
前提
- this:没有继承也可以使用
- super:只能在继承条件下才能使用
-
构造方法:
- this(); 本类的构造
- super(); 父类的构造
例子:
package oop05;
public class Person /*extends Object*/{//extends Object 写不写都一样
public Person() {
System.out.println("Person的无参构造器执行了");
}
protected String name = "kuangshen";
//私有的东西无法被继承!
public void print() {
System.out.println("Person");
}
}
package oop05;
public class Student extends Person{
private String name = "qinjiang";
public Student() {
//隐藏代码:super(); 默认调用了父类的无参构造器。 如果父类的无参构造器被有参构造器替代则这里会出错!因此子类将无法写无参构造器,只能写有参构造器去调用父类的有参构造器
super();//默认存在,调用父类的构造器,必须要在子类构造器的第一行
System.out.println("Student的无参构造器执行了");
}
public void print() {
System.out.println("Student");
}
public void test1() {
print();//调用当前这个类的 print()
this.print();//还是调用当前这个类的 print()
super.print();//调用父类的 print()
}
public void test(String name) {
System.out.println(name);//秦疆
System.out.println(this.name);//qinjiang
System.out.println(super.name);//kuangshen
}
}
package oop05;
public class Application {
public static void main(String[] args) {
Student student = new Student();
/*Person的无参构造器执行了
Student的无参构造器执行了*/
student.test("秦疆");
/*输出结果为
秦疆
qinjiang
kuangshen
*/
student.test1();
/*输出结果为
Student
Student
Person
*/
}
}
方法重写(跟 重载 是不一样的)
方法重写的注意点:
-
前提:需要有继承关系,而且是 子类 重写 父类 的方法!
-
方法名必须相同
-
参数列表必须相同
-
修饰符:范围可以扩大,不能缩小 public>protected>default(>private)注意private无法被继承
-
抛出的异常:范围可以被缩小,但不能扩大(举个例子:ClassNotFoundException(小)–>Exception(大))
-
子类的方法和父类必须要一致,方法体不同
思考:为什么需要方法重写!
- 父类的功能子类不一定需要或不一定满足
例子:
package oop05;
//重写都是方法的重写,和属性无关
public class B {
public /*static*/ void test() {
System.out.println("B=>test()");
}
}
package oop05;
public class A extends B{
@Override //这里的@Override是注解,注解:有功能的注释!Override的中文意思是 重写
public /*static*/ void test() {
System.out.println("A=>test()");
}
}
package oop05;
public class Application {
//静态的方法和非静态的方法区别很大!
//如果是 静态方法 的调用,则方法的调用只和定义的数据类型有关,跟右边的new没有关系
//如果是 非静态方法 ,则为重写。
//只有非静态的方法,且为 public 而不能是 private 才可以重写
public static void main(String[] args) {
A a = new A();
a.test();
//父类的引用指向子类的类型
B b = new A();//子类重写了父类的方法
b.test();
}
}
- 多态
多态:
- 即同一方法可以根据发送对象的不同而采取多种不同的行为方式
- 一个对象的实际类型是确定的,但指向对象的引用类型可以有很多(父类或有关系的类)
多态注意点:
-
多态是方法的多态,属性没有多态
-
父类和子类,有联系(类型转换异常!ClassCastException!)
-
多态存在条件
-
继承关系
-
方法需要重写(方法不重写,则多态无意义。有些方法不能重写)
不能重写的方法:
- static 方法,属于类,和类一起加载。不属于实例
- final 常量
- private 方法 私有的 不能重写
-
父类引用指向子类对象(Father f1 = new Son();)
-
例子:
package oop06;
public class Person {
public void run() {
System.out.println("run");
}
}
package oop06;
public class Student extends Person{
@Override//重写
public void run() {
System.out.println("son");
}
public void eat() {
System.out.println("eat");
}
}
package oop06;
public class Application {
public static void main(String[] args) {
//一个对象的实际类型是确定的
//Student student = new Student();
//Person person = new Person();
//可以指向的引用类型就不确定了
//引用类型Student的对象 能调用的方法都是自己的或者继承父类的
Student s1 = new Student();
//Person 父类型,可以指向子类,但不能调用子类独有的方法
Person s2 = new Student();//这个叫父类的引用指向子类的类型
//可以这么理解 s1 s2 都是学生,但它们表现出来的是不同状态
Object s3 =new Student();//因为 Person 和 Object 都是 Student 的父类
//这里的实际类型都是 Student ,但引用类型不确定,可以是 Student 的父类型
s2.run();//子类重写了父类的方法
s1.run();
//对象能执行哪些方法,主要看对象左边的引用类型,和右边的实际类型关系不大
//s2.eat();//报错,因为Person类中没有eat()方法
((Student) s2).eat();//这样是把 s2 强制转换(高转低)成 Student
s1.eat();
//父类指向子类,但不能调用子类的方法!!!
}
}
instanceof
instanceof 关键字可以判断一个对象是什么类型,判断两个类之间是否存在父子关系
例子:
package oop06;
public class Application {
public static void main(String[] args) {
//Object > String
//Object > Person > Teacher
//Object > Person > Student
Object object = new Student();
//System.out.println(X instanceof Y);是否编译通过,取决于X和Y之间是否存在父子关系
System.out.println(object instanceof Student);//true
System.out.println(object instanceof Person);//true
System.out.println(object instanceof Object);//true
System.out.println(object instanceof Teacher);//false (Teacher跟object有关系但跟Student无关)
System.out.println(object instanceof String);//false (String跟object有关系但跟Student无关)
System.out.println("======================================");
Person person = new Student();
System.out.println(person instanceof Student);//true
System.out.println(person instanceof Person);//true
System.out.println(person instanceof Object);//true
System.out.println(person instanceof Teacher);//false (Teacher跟person有关系但跟Student无关)
//System.out.println(person instanceof String);//编译报错 (String跟person没有任何关系)
System.out.println("======================================");
Student student = new Student();
System.out.println(student instanceof Student);//true
System.out.println(student instanceof Person);//true
System.out.println(student instanceof Object);//true
//System.out.println(student instanceof Teacher);//没有任何关系,编译报错
//System.out.println(student instanceof String);//没有任何关系,编译报错
}
}
package oop06;
public class Person {
public void run() {
System.out.println("run");
}
}
package oop06;
public class Student extends Person{
}
package oop06;
public class Teacher extends Person{
}
类型转换
例子:
package oop06;
public class Application {
public static void main(String[] args) {
//类型之间的转换:基本类型转换 高低(64 32 16 8)
//类型之间的转换:父(高) 子(低)
//高 低
Person student = new Student();
//将student这个对象转换为Student类型,我们就可以使用Student类型的方法了
((Student) student).go();//这里 高转低 为 强制转换
//student.go();编译错误
//子类转换为父类,可能丢失自己本来的一些方法
}
}
package oop06;
public class Person {
public void run() {
System.out.println("run");
}
}
package oop06;
public class Student extends Person{
public void go() {
System.out.println("go");
}
}
子类转换为父类,向上转型
父类转换为子类,向下转型,需要强制转换
类型转换能够方便 方法的调用,减少重复的代码!代码简洁
static 关键字
例子:
package oop07;
public class Student {
private static int age;//静态的变量
private double score;//非静态的变量
public void run() {
go();//非静态的方法可以直接访问这个类中的静态方法,反之则不行,因为static跟类一起加载,优先于 非静态 存在
}
public static void go() {}
public static void main(String[] args) {
Student student = new Student();
System.out.println(Student.age);
//System.out.println(Student.score);//编译报错,非静态的变量不能这样调用
System.out.println(student.age);
System.out.println(student.score);
//Student.run();编译报错
Student.go();
}
}
package oop07;
public class Person {
/*
{
//代码块(匿名代码块)
}
static {
//静态代码块
}*/
{
System.out.println("匿名代码块");//可以赋初始值
}
static {
System.out.println("静态代码块");
}
public Person() {
System.out.println("构造方法");
}
//测试执行顺序
public static void main(String[] args) {
Person person1 = new Person();
/*
执行结果显示:
静态代码块
匿名代码块
构造方法
*/
System.out.println("====================");
Person person2 = new Person();
/*
执行结果显示:
静态代码块
匿名代码块
构造方法
====================
匿名代码块
构造方法
*/
//说明static只执行了一次
}
}
static扩展:
package oop07;
//静态导入包
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
System.out.println(random());//输出随机数
System.out.println(PI);
}
}
抽象类和接口
- 抽象类
abstract 修饰符可以修饰 类(抽象类) 和 方法(抽象方法)
package oop08;
//abstract 抽象类 JAVA的类只能单继承,接口可以多继承
public abstract class Action {
//约束,有人帮我们实现
//abstract 抽象方法,只有方法名字,没有方法的实现!
public abstract void doSomething();
//不能new这个抽象类,只能靠子类去实现它:约束!
//抽象类里面可以写普通方法
//抽象方法则必须在抽象类中
//抽象的抽象:约束
public void hello() {
}
//虽然抽象类不能new对象,但存在构造器
//抽象类存在的意义,节省代码的开发,提高开发效率,提高可扩展性
//抽象类用得比较少
}
package oop08;
//抽象类的所有方法,继承它的子类必须实现它的方法。除非子类也是个抽象类
public class A extends Action {
@Override
public void doSomething() {
}
}
- 接口
普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有
接口:只有规范!自己无法写方法,专业的约束!约束和实现分离:面向接口编程(比抽象类还要抽象)
接口就是规范,定义的是一组规则
接口的本质是契约,就像我们人间的法律一样,制定好后大家都遵守
接口是OO(面向对象)的精髓,是对对象的抽象。(为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如c++、java、c#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象)
声明类的关键字是:class
声明接口的关键字是:interface
实现接口的关键字是:implements
接口的作用:
- 约束
- 定义一些方法,让不同的人实现
- 接口不能被实例化,接口中没有构造方法
- 一个类可以实现多个接口
- 必须要重写接口中的方法
例子:
package oop09;
//类实现接口关键字: implements
//实现了接口的类,必须要重写接口中的方法
//类可以实现多个接口,利用接口实现了多继承
public class UserServiceImpl implements UserService,TimeService{
@Override
public void run(String name) {
}
@Override
public void add(String name) {
}
@Override
public void delete(String name) {
}
@Override
public void update(String name) {
}
@Override
public void query(String name) {
}
@Override
public void timer() {
}
}
package oop09;
//锻炼抽象的思维很关键,架构师抽象思维非常好
//interface 定义的关键字
//接口都需要有实现类
public interface UserService {
//接口中所有属性,默认都是 public static final
/*public static final */int AGE = 99;
//接口中所有定义的方法,默认都是 public abstract
/*public abstract */void run(String name);//public abstract 就算不写也默认有
void add(String name);
void delete(String name);
void update(String name);
void query(String name);
}
package oop09;
public interface TimeService {
void timer();
}
内部类及OOP实战
- 内部类
内部类就是在一个类的内部再定义一个类。比如,A类中定义一个B类,那么B类相对于A类来说就称为内部类,而A类相对于B类来说就是外部类
- 成员内部类
例子:
package oop10;
public class Outer {
private int id = 10;
public void out() {
System.out.println("这是外部类的方法");
}
//成员内部类
public class Inner{
public void in() {
System.out.println("这是内部类的方法");
}
//成员内部类可以获得外部类的私有属性,和方法
public void getID() {
System.out.println(id);
}
}
}
package oop10;
public class Application {
public static void main(String[] args) {
Outer outer = new Outer();
//通过外部类来实例化内部类
Outer.Inner inner = outer.new Inner();
inner.in();
inner.getID();
}
}
- 静态内部类
例子:
package oop10;
public class Outer {
private int id = 10;
public void out() {
System.out.println("这是外部类的方法");
}
//静态内部类,无法访问外部类非静态的方法和属性
public static class Inner{
public void in() {
System.out.println("这是内部类的方法");
}
}
}
- 局部内部类
例子:
package oop10;
public class Outer {
//局部内部类
public void method() {
class Inner{
public void in() {
}
}
}
}
//一个JAVA类中可以有多个class,但只能有一个 public class
class A{
public static void main(String[] args) {
}
}
- 匿名内部类
例子:
package oop10;
public class Test {
public static void main(String[] args) {
Apple apple = new Apple();
//没有名字去初始化类,不用将实例保存到变量中
new Apple().eat();//匿名内部类调用
/* new UserService() {
@Override
public void hello() {
}
};*/
}
}
class Apple{
public void eat() {
System.out.println("1");
}
}
interface UserService{
void hello();
}
异常机制
什么是异常
异常:Exception
异常体系结构
异常的三大类型:
- 检查性异常
- 运行时异常
- 错误ERROR:错误不是异常,而是脱离程序员控制的问题
异常处理框架
- Java把异常当作对象来处理,并定义一个基类 java.lang.Throwable 作为所有异常的超类
- 在 Java API 中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception
Error
- Error类对象有Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关
- Java虚拟机运行错误,当JVM不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机一般会选择线程终止
- 还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况
Exception
- 在Exception分支中有一个重要的子类RuntimeException(运行时异常),这里面的异常都是程序写好的。这些异常不是检查异常,程序中可以选择捕获处理,也可以不处理
- 这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生
Error和Exception的区别:
- Error通常是灾难性的致命的错误,是程序无法控制处理的,当出现这些异常时,JVM一般会选择终止线程
- Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常
Java异常处理机制
- 抛出异常
- 捕获异常
处理异常
异常处理五个关键字:
- try
- catch
- finally
- throw
- throws
例子一:
public class Test {
public static void main(String[] args) {
int a = 1;
int b = 0;
try {//try 监控区域
System.out.println(a/b);
}catch(ArithmeticException e){//catch 捕获异常
System.out.println("程序出现异常,变量b不能为0");
}finally {//finally 处理善后工作,不管出不出现异常都会执行
System.out.println("finally");
}//finally 可以不要,通常用于资源关闭
}
}
例子二:
package exception;
public class Test {
public static void main(String[] args) {
int a = 1;
int b = 0;
//假设要捕获多个异常:从小到大!最大的为Throwable
try {//try 监控区域
//new Test().a();
System.out.println(a/b);
}catch(Error e){//catch(想要捕获的异常类型,最高为Throwable) 捕获异常
System.out.println("Error");
}catch(Exception e) {
System.out.println("Exception");
}catch(Throwable t) {
System.out.println("Throwable");
}finally {//finally 处理善后工作,不管出不出现异常都会执行
System.out.println("finally");
}
//finally 可以不要,通常用于资源关闭
}
public void a() {
b();
}
public void b() {
a();
}
}
package exception;
public class Test2 {
public static void main(String[] args) {
int a = 1;
int b = 0;
try {
System.out.println(a/b);
}catch(Exception e){
e.printStackTrace();//打印错误的栈信息
}finally {
}
}
}
例子三:
package exception;
public class Test {
public static void main(String[] args) {
int a = 1;
int b = 0;
try {
new Test().test(a, b);
}catch(ArithmeticException e){
e.printStackTrace();
}
//用 try catch 可以让程序继续执行,不会让程序停止
}
//假设这个方法中处理不了这个异常,则在方法上抛出异常,throws
public void test(int a,int b) throws ArithmeticException{
if(b==0) {
throw new ArithmeticException();//我们可以主动地抛出异常 throw,一般用在方法里面
}
System.out.println(a/b);
}
}
自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。
在程序中使用自定义异常类,大概步骤:
- 创建自定义异常类
- 在方法中通过throw关键字抛出异常对象
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作
- 在出现异常方法的调用者中捕获并处理异常
例子:
package exception02;
//自定义的异常类
public class MyException extends Exception{
//传递数字,数字大于10抛出异常
private int detail;
public MyException(int a) {
this.detail = a;//可以实现new的同时赋值
}
//toString:异常的打印信息
@Override
public String toString() {
return "MyException [detail=" + detail + "]";
}
}
package exception02;
public class Test {
public static void main(String[] args) {
try {
test(11);
} catch (MyException e) {
// TODO Auto-generated catch block
System.out.println("MyException=>" + e);
}
}
//可能会存在异常的方法
static void test(int a) throws MyException {
System.out.println("传递的参数为:" + a);
if(a>10) {
throw new MyException(a);//抛出
}
System.out.println("OK");
}
}
实际应用中的经验总结:
- 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
- 在多重catch块后面,可以加一个最大的异常的类 catch(Exception e) 来处理可能会被遗漏的异常
- 对于不确定的代码,也可以加上 try-catch ,处理潜在的异常
- 尽量去处理异常,切忌只是简单地调用默认的 printStackTrace() 去打印输出
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定
- 尽量添加finally语句块去释放占用的资源