typora-root-url: ./
初识Java
Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的高级程序设计语言
Java 可运行于多个平台,如 Windows, Mac OS 及其他多种 UNIX 版本的系统
简介
Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称。由 James Gosling和同事们共同研发,并在 1995 年正式推出。
后来 Sun 公司被 Oracle (甲骨文)公司收购,Java 也随之成为 Oracle 公司的产品。
Java分为三个体系
-
JavaSE(J2SE)(Java2 Platform Standard Edition,java平台标准版)
-
JavaEE(J2EE)(Java 2 Platform,Enterprise Edition,java平台企业版)
-
JavaME(J2ME)(Java 2 Platform Micro Edition,java平台微型版)
特性
-
Java 语言是简单的
-
Java 语言是面向对象的
-
Java语言是分布式的
-
Java 语言是健壮的
-
Java语言是安全的
-
Java 语言是体系结构中立的
-
Java 语言是可移植的
-
Java 语言是解释型的
-
Java 是高性能的
-
Java 语言是多线程的
环境配置
1、下载JDK
-
选择对应的操作系统
-
下载完成之后进行环境配置安装 - 安装步骤很简单无脑操作 - 安装路径可以更改不过一般都是默认
-
环境配置在此电脑右击“点击属性”
-
找到高级系统设置
-
点击“环境变量”
-
在系统变量那里点击“新建”,输入变量名“JAVA_HOME”,再输入变量值“jdk安装路径”,默认路径:C:\Program Files\Java\jdk文件夹名称
-
找到Path点击编辑弹出窗口后,点击新建输入:%JAVA_HOME%bin
-
完成配置
-
win+R 输入cmd,java -version 测试
-
2、Idea安装
-
软件下载官网: IntelliJ IDEA: The Capable & Ergonomic Java IDE by JetBrains
-
我的网盘地址下载链接[永久有效]:百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固,支持教育网加速,支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.baidu.com/s/1208FebFpNTpza1-_B2qL7w?pwd=rp2w 提取码:rp2w
-
安装步骤参考: IDEA安装教程_夜未央5788的博客-CSDN博客_安装idea的步骤)
-
创建项目 New Project 选择默认第一个
-
点Next下一步到ProjectName 输入项目名称
-
第二项是项目存放地址
编写Java第一个程序
public class HelloWorld {
/*
* 第一个Java程序
* 它将输出字符串 Hello World
*/
public static void main(String[] args) {
System.out.println("Hello World"); // 输出 Hello World
}
}
基本点
-
类名:对于所有的类来说,类名的首字母应该大写,如果类名由若干单词组成,那么每个单词的首字母应该大写,例如:HelloTest
-
四要素:public static void main(String[] args){} 固定不变
数据类型
-
关键字与保留字
-
标识符
-
数据类型与转换
-
运算符
一、关键字与保留字
用于定义数据类型的关键字 | ||||
---|---|---|---|---|
class | interface | enum | byte | short |
int | long | float | double | char |
boolean | void | |||
用于定义流程控制的关键字 | ||||
if | else | switch | case | default |
while | do | for | break | continue |
return | ||||
用于定义访问权限修饰符的关键字 | ||||
private | protected | public | ||
用于定义类,函数,变量修饰的关键字 | ||||
abstract | final | static | synchronized |
用于定义类与类之间关系的关键字 | ||||
---|---|---|---|---|
extends | implements | |||
用于定义建立实例及引用实例,判断实例的关键字 | ||||
new | this | super | instanceof | |
用于处理异常的关键字 | ||||
try | catch | finally | throw | throws |
用于包的关键字 | ||||
package | import | |||
其他修饰符的关键字 | ||||
native | strictfp | transient | volatile | assert |
两个保留字 | ||||
goto | const |
官方规定文档:Chapter 3. Lexical Structure
-
关键字(keyword)的定义和特点
-
定义:被Java语言赋予了特殊含义,用专门用途的字符串(单词)
-
特点:关键字中所有字母都为小写
-
-
保留字
-
Java保留字:现有Java版本尚未使用,但以后版本可能会作为关键字使用,自己命名标识符时要避免使用这些保留字,goto , const
-
二、标识符
-
变量三要素
-
数据类型 变量名 = 值
-
例如:int number = 123;
-
-
变量名定义规则
-
只能使用26个英文字母大小写,0-9的数字,下划线__,美元符号$
-
不能含有空格
-
不能以数字开头
-
避免使用关键字:public int static 等
-
避免使用汉字
-
-
变量名定义规范:见名知意、推荐使用驼峰命名法
Java中的名称命名规范
-
Java中的名称命名规范
-
包名:多单词组成时所有字母都小写:xxxyyyzzz
-
类名,接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
-
变量名,方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz
-
常量名:所有字母都大写,多单词时每个单词用下划线连接:XXX_YYY_ZZZ
-
-
注意1:在起名字时,为了提高阅读性,要尽量有意义,“见名知意”
-
注意2:Java采用Unicode字符集,因此标识符也可以使用汉字声明,但是不建议使用
三、变量
变量时存储数据值的容器
-
变量的概念
-
内存中一个存储区域
-
该区域的数据可以在同一类型范围内不断变化
-
变量时程序中最基本的存储单元,包含变量类型,变量名和存储的值
-
-
变量的作用
-
用于在内存中保存数据
-
-
使用变量注意
-
Java中每个变量必须先声明,后使用
-
使用变量名来访问这块区域的数据
-
变量的作用:其定义所在的一对{}内
-
变量只有在其作用域内才有效
-
同一个作用域内,不能定义重名的变量
-
八大基本数据类型
类型 | 关键字 | 占**用内存** | 存储范围 |
---|---|---|---|
字节型 | byte | 1个字节 | -27~27-1(-128~127) |
短整型 | short | 2个字节 | -215~215-1 |
整型 | int | 4个字节 | -231~231-1 |
长整型 | long | 8个字节 | -263~263-1 |
单精度浮点型 | float | 4个字节 | 大约±3.4e38次方 |
双精度浮点型 | double | 8个字节 | 大约±1.79e308次方 |
字符型 | char | 2个字节 | 一个字符占两个字节 |
布尔型 | boolean | 1位 | 两个值true和false |
bit: 计算机中的最小存储单位。byte:计算机中基本存储单元
变量的创建(声明)
要创建变量,必须指定类型并为其赋值 语法如下:
public class Test{
public static void main(String[] args){
//type variable = value;
//整型创建
int number = 123;
short numbers = 123;
byte numberb = 123;
long numberl = 12312312321L;
//浮点型创建
float numberf = 123.222F;
double numberd = 123.123;
//字符型创建
char c = '1';
char c2 = 12;
char c3 = '中';
char c4 = '\n';
char c5 = '\t';
char c6 = '\u0024';
int i = 'a';
//布尔型创建
boolean isPass = true;
System.out.println();
}
}
数据类型转换
-
自动类型转换
-
精度小的类型自动转换为精度大的数据类型
-
简单说明 大的类型可以自然接收小的类型
-
转换曲线:byte 、short 、 char ---> int ---> long ---> float ---> double
-
-
强制类型转换
-
将容量大的数据类型转换为容量小的数据类型
-
简单说明 小的类型要接收大的类型
-
语法:int num = (int) 20 + 20;
-
注意:使用时要加上强制转换符(),但是可能会有精度损失或者溢出情况
-
public class Test{
public static void main(String[] args){
//创建变量
int i1 = 20;
byte b1 = 5;
short s1 = 10;
long l1 = 30;
char c1 = 20;
//自动类型转换
int i2 = b1 + s1;
//byte short char在运算后不可自己接收 - 自动转换到int
//byte b2 = c1 + b1;
//char c2 = i1 + c1;
//short s2 = c1 + l1;
//强制类型转化
byte b2 = (byte) (c1 + b1);
char c2 = (char) (i1 + c1);
short s2 = (short) (c1 + l1);
}
}
运算符
-
算术运算符
-
关系运算符
-
逻辑运算符
-
赋值运算符
-
三元运算符
算术运算符
+:是运算符,并且是算术运算符。 a + b:是表达式,由于+是算术运算符,所以这个表达式叫算术表达式
运算符 | 运算 | 示例 | 结果 |
---|---|---|---|
+ | 正号 | +10 | 10 |
- | 负号 | -8 | -8 |
+ | 加 | 5+5 | 10 |
- | 减 | 10-2 | 8 |
* | 乘 | 5*6 | 30 |
/ | 除 | 5/5 | 1 |
% | 取模(求余) | 7%5 | 2 |
++ | 自增(前):先运算后取值 | a=2;b=++a; | a=3;b=3 |
自增(后):先取值后运算 | a=2;b=a++; | a=3;b=2 | |
-- | 自减(前):先运算后取值 | a=2;b=--a; | a=1;b=1 |
自减(后):先取值后运算 | a=2;b=a--; | a=1;b=2 | |
+ | 字符串拼接 | "hello,"+"world" | "hello,world" |
注意: /和%的区别:两个数据做除法,/取结果的商,%取结果的余数。 整数操作只能得到整数,要想得到小数,必须有浮点数参与运算
int a = 10;
int b = 3;
System.out.println(a / b); // 输出结果3
System.out.println(a % b); // 输出结果1
字符的“+”操作 char类型参与算术运算,使用的是计算机底层对应的十进制数值。需要我们记住三个字符对应的数值: ‘a’ – 97 a-z是连续的,所以’b’对应的数值是98,'c’是99,依次递加
‘A’ – 65 A-Z是连续的,所以’B’对应的数值是66,'C’是67,依次递加
‘0’ – 48 0-9是连续的,所以’1’对应的数值是49,'2’是50,依次递加
// 可以通过使用字符与整数做算术运算,得出字符对应的数值是多少
char ch1 = 'a';
System.out.println(ch1 + 1); // 输出98,97 + 1 = 98
char ch2 = 'A';
System.out.println(ch2 + 1); // 输出66,65 + 1 = 66
char ch3 = '0';
System.out.println(ch3 + 1); // 输出49,48 + 1 = 49
算术表达式中包含不同的基本数据类型的值的时候,整个算术表达式的类型会自动进行提升。
提升规则:
byte类型,short类型和char类型将被提升到int类型,不管是否有其他类型参与运算。
整个表达式的类型自动提升到与表达式中最高等级的操作数相同的类型
等级顺序:byte,short,char --> int --> long --> float --> double
byte b1 = 10;
byte b2 = 20;
// byte b3 = b1 + b2; // 该行报错,因为byte类型参与算术运算会自动提示为int,int赋值给byte可能损失精度
int i3 = b1 + b2; // 应该使用int接收
byte b3 = (byte) (b1 + b2); // 或者将结果强制转换为byte类型
//-------------------------------
int num1 = 10;
double num2 = 20.0;
double num3 = num1 + num2; // 使用double接收,因为num1会自动提升为double类型
在开发程序中很少使用倒byte或short类型定义整数,也很少会用到char类型定义字符,而会使用String字符串类型
赋值运算符
赋值运算符的作用是将一个表达式的值赋给左边,左边必须是可修改的,不能是常量
符号 | 作用 | 说明 |
---|---|---|
= | 赋值 | a=10 ,将10赋值给变量a |
+= | 加后赋值 | a+=b ,将a+b的值给a |
-= | 减后赋值 | a-=b ,将a-b的值给a |
*= | 乘后赋值 | a*=b ,将a×b的值给a |
/= | 除后赋值 | a/=b ,将a÷b的商给a |
%= | 取余后赋值 | a%=b ,将a÷b的余数给a |
注意: 扩展的赋值运算符隐含了强制类型转换
short s = 10;
s = s + 10; // 此行代码报出,因为运算中s提升为int类型,运算结果int赋值给short可能损失精度
s += 10; // 此行代码没有问题,隐含了强制类型转换,相当于 s = (short) (s + 10);
关系运算符
运算符 | 运算符含义 | 示例 | 结果 |
---|---|---|---|
== | 相等比较 | 8==7 | false |
!= | 不等于 | 8!=7 | true |
< | 小于 | 8<7 | false |
> | 大于 | 8>7 | true |
<= | 小于等于 | 8<=7 | false |
>= | 大于等于 | 8>=7 | true |
注意事项: 关系运算符的结果都是boolean类型,要么是true,要么是false。千万不要把“”误写成“=”,"“是判断是否相等的关系,”="是赋值
int a = 10;
int b = 20;
System.out.println(a == b); // false
System.out.println(a != b); // true
System.out.println(a > b); // false
System.out.println(a >= b); // false
System.out.println(a < b); // true
System.out.println(a <= b); // true
// 关系运算的结果肯定是boolean类型,所以也可以将运算结果赋值给boolean类型的变量
boolean flag = a > b;
System.out.println(flag); // 输出false
逻辑运算符
把各个运算的关系表达式连接起来组成一个复杂的逻辑表达式,以判断程序中的表达式是否成立,判断 的结果是 true 或 false
符号 | 作用 | 说明 |
---|---|---|
& | 逻辑与 | a&b ,a和b都是true,结果为true,否则为false |
| | 逻辑或 | a|b ,a和b都是false,结果为false,否则为true |
^ | 逻辑异或 | a^b ,a和b结果不同为true,相同为false |
! | 逻辑非 | !a ,结果和a的结果正好相反 |
&& | 短路与 | 作用和&相同,短时有短路效果 |
|| | 短路或 | 作用和|相同,短时有短路效果 |
//定义变量
int i = 10;
int j = 20;
int k = 30;
//& “与”,并且的关系,只要表达式中有一个值为false,结果即为false
System.out.println((i > j) & (i > k)); //false & false,输出false
System.out.println((i < j) & (i > k)); //true & false,输出false
System.out.println((i > j) & (i < k)); //false & true,输出false
System.out.println((i < j) & (i < k)); //true & true,输出true
System.out.println("--------");
//| “或”,或者的关系,只要表达式中有一个值为true,结果即为true
System.out.println((i > j) | (i > k)); //false | false,输出false
System.out.println((i < j) | (i > k)); //true | false,输出true
System.out.println((i > j) | (i < k)); //false | true,输出true
System.out.println((i < j) | (i < k)); //true | true,输出true
System.out.println("--------");
//^ “异或”,相同为false,不同为true
System.out.println((i > j) ^ (i > k)); //false ^ false,输出false
System.out.println((i < j) ^ (i > k)); //true ^ false,输出true
System.out.println((i > j) ^ (i < k)); //false ^ true,输出true
System.out.println((i < j) ^ (i < k)); //true ^ true,输出false
System.out.println("--------");
//! “非”,取反
System.out.println((i > j)); //false
System.out.println(!(i > j)); //!false,,输出true
在逻辑与运算中,只要有一个表达式的值为false,那么结果就可以判定为false了,没有必要将所有表达式的值都计算出来,短路与操作就有这样的效果,可以提高效率。同理在逻辑或运算中,一旦发现值为true,右边的表达式将不再参与运算
-
逻辑与&,无论左边真假,右边都要执行
-
短路与&&,如果左边为真,右边执行;如果左边为假,右边不执行。
-
逻辑或|,无论左边真假,右边都要执行
-
短路或||,如果左边为假,右边执行;如果左边为真,右边不执行
int x = 3;
int y = 4;
System.out.println((x++ > 4) & (y++ > 5)); // 两个表达都会运算
System.out.println(x); // 4
System.out.println(y); // 5
System.out.println((x++ > 4) && (y++ > 5)); // 左边已经可以确定结果为false,右边不参与运算
System.out.println(x); // 4
System.out.println(y); // 4
三元运算符
语法格式
条件表达式 ? 表达式1 : 表达式2; 代码: boolean max = 20 > 30[判断条件] ? true[是] : false[不是];
解释:问号前面的位置是判断的条件,判断结果为boolean型,为true时调用表达式1,为false时调用表达式2。其 逻辑为:如果条件表达式成立或者满足则执行表达式1,否则执行第二个。
流程控制
在程序中,程序运行的流程控制决定程序是如何执行的,分为三大流程控制
-
顺序结构
-
选择结构(if-else ,switch-case)
-
循环结构(for,while,do-while)
顺序结构介绍
程序从上往下执行,中间没有任何判断和跳转顺序控制,有序执行代码
选择结构
选择结构分为 if-else ------ switch-case
-
if-else
-
单分支 单 if 语句
-
双分支 if-else 语句
-
多分枝 if-else if-else 语句
-
嵌套if
-
-
switch-case
if选择结构使用
public class Test{
public static void main(String[] args){
//单结构 if
/*
* 单一条件判断 如果表达式成立则执行 {}体内的语句
* 否则跳过执行
*/
int i = 10;
if(i == 10){
System.out.println(i);
}
//if-else使用
/*
* 双分支if判断 ---- 相当于二选一执行
* 条件成立则执行if语句代码块,否则执行else语句代码块
*/
int i = 10;
if(i == 10){
System.out.println(i);
}else{
System.out.println(i);
}
//多重if选择结构
/*
* 多条件判断 ---- 多选一执行
*
*/
int i = 10;
if(i == 10){
System.out.println("i等于10了");
}else if(i == 20){
System.out.println("i等于20了");
}else{
System.out.println("i小于");
}
}
}
嵌套if
在if结构层内写入if
-
先进入外层if判断,通过进入内层、否则进入else或者执行其他代码
-
外层条件通过之后进入内层if判断
-
成功则执行内层语句
语法格式:
if(条件表达式1){ if(条件表达式2){ //执行语句 } }
代码格式
public class Test{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
System.out.print("当前100米比赛在几秒内完成:");
int num = scan.nextInt();
if(num < 10){
System.out.println("成功进入决赛");
System.out.print("请输入性别:");
String sex = scan.next();
if(sex.equals("男")){
System.out.println("成功进入男子组");
}else{
Sytem.out.println("成功进入女子组");
}
}
}
}
switch选择结构
根据表达式判断常量,常量相等则执行一些代码,不相等则不执行这些代码
语法格式:
switch(表达式){ case 常量1: 语句体; break; case 常量2: 语句体; break; ...... default: 语句体; break; }
执行流程:
-
首先判断 表达式结果常量
-
拿表达式内的常量值与case常量是否相同 - 相同则执行对应case块语句体
-
如果表达式的结果常量与case内的常量都不相同,则会到default里执行,[default可省略]
public class Test{
public static void main(String[] args){
//键盘扫描仪
Scanner scan = new Scanner(System.in);
System.out.print("今天周几?:");
int day = scan.nextInt();
switch(day){
case 1:
System.out.println("周一");
break;
case 2:
System.out.println("周二");
break;
case 3:
System.out.println("周三");
break;
case 4:
System.out.println("周四");
break;
case 5:
System.out.println("周五");
break;
default:
System.out.println("无");
break;
}
}
}
注意:每一个case写完语句体后必须要写break,如果没有写break他会进行渗透,不仅输出你本case内的语句体,还会输出下一个case内的语句体,直到有break才会停下来
switch和多重if比较
相同点
-
都是用来处理多分枝条件的结构
不同点
-
switch选择结构:只能处理等值条件判断情况,而且条件必须是整型变量或者字符串型变量
-
多重if选择结构:没有switch选择结构的限制,特别适合某个变量处于某个连续区间时的情况
-
if 在应用中更为常见,有时switch 结构更清晰
凡是可以使用switch-case的结构,都可以使用if-else;反之不成立
循环结构
循环特点
-
循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码
-
被称为循环体语句,当反复执行这个循环体时,需要在合适的时候把循环判断条件修改为false,从而结束循环,否则循环将一直执行下去,形成死循环
循环语句的组成部分:
-
初始化部分
-
循环条件部分
-
循环体部分
-
迭代部分
for循环
语法格式:
for(初始化部分1;循环条件部分2;迭代部分4){ 循环体部分3 }
执行过程
初始化 --> 循环条件 --> 循环体 --> 迭代 --> 循环条件 --> 循环体 --> 迭代 ... 循环条件
public class Test{
public static void main(String[] args){
//for循环
for(int i = 0; i < 5; i++){
System.out.println(i);
}//不小于5时跳出
System.out.println("结束");
}
}
while循环
特点
-
先判断再执行
-
条件表达式返回值类型为boolean类型,只要条件表达式的结果为true,则一直执行语句块
语法结构:
循环变量初始化; while(循环条件){ 循环体(代码块); 循环遍历迭代; }
int i = 0; //①
while(i < 5){ //②
System.out.println("hello"+ i);//③
i++;//④
}
注意:不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环,for循环和while循环可以相互转换,区别点:for循环和while循环的初始化变量作用域不同
do-while循环
特点
-
先执行再判断
-
先执行do对应的语句块,然后再判断循环条件,只要循环条件满足,则一直执行循环体,否则结束循环
语法结构:
循环变量初始化; do{ 循环体(代码块); 循环遍历迭代; }while(循环条件);
int i = 0;//①
do{
System.out.println(i);//②
i++;//③
}while(i < 5);//④
案例
-
经过几天的学习,老师给张浩一道测试题,让他先上机编写程序完成
-
然后老师检查是否合格。如果不合格,则继续编写
public class Test{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
String answer = "";//循环初始化
do{//循环体
System.out.println("上机编写程序!");
System.out.println("合格了吗?(y/n):");
answer = scan.next();
}while(answer.equals("n"));
System.out.println("恭喜你通过了测试!");
}
}
三种循环对比
执行顺序
-
先判断后执行:while和for
-
先执行后判断:do-while
适用
-
循环次数确定:一般使用for
-
循环次数不确定:一般使用while和do-while
循环嵌套
将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for ,while ,do…while均可以作为外层循环或内层循环
实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环
设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次
语法结构
for(int i = 0; i < 5; i++){
for(int j = 0; j < 5; j++){
System.out.println("i="+ i + ",j="+ j);
}
}
练习
public class Test{
public static void main(String[] args){
//打印五颗星
for(int i = 0; i < 5; i++){
System.out.print("*");
}
System.out.println("\n");
/*
* 打印五次五颗星,如果定义五次上述代码,显然不太好;
* 那么久把上述代码,再放到一个循环结构中,嵌套循环
* *****
* *****
* *****
* *****
* *****
*/
for(int i = 0; i < 5; i++){
for(int j = 0l j < 5; j++){
System.out.print("*");
}
System.out.println();
}
}
}
总结规律:
-
第一个(外层)for循环作用:控制行数
-
第二个(内层)for循环作用:控制列数
流程跳转
break 用于do-while、while、for、switch中时,可跳出循环而执行循环后面的语句 continue 用在循环中,跳过本次循环,执行下一次循环
break
-
break语句用于终止某个语句块的执行
-
break通常在循环中与条件语句一起使用
-
使用循环,但是循环的次数不知道
-
break:当某个条件满足时,终止循环
for(int i = 0; i < 10; i ++){
if(i == 3){
break;
}
System.out.println("i = "+ i);
}
continue
-
continue只能使用在循环结构中
-
continue语句用于跳过其所在循环语句块的一次执行,继续下一次循环
-
continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环
for(int i = 0; i < 10; i ++){
if(i == 3){
continue;
}
System.out.println("i = "+ i);
}
return
-
return使用在方法,表示跳出所在的方法
学习类的方法时详细介绍
三者对比
break和continue对比
使用场合不同
-
break可用于switch结构和循环结构中
-
continue只能用于循环结构中
作用不同
-
break语句终止某个循环,程序跳转到循环块外的下一条语句
-
continue跳出本次循环,进入下一次循环
continue、break、return三者对比
-
continue:跳过本次循环,继续下一次循环
-
break:结束所在的循环。
-
return: 结束整个方法
数组
数组(Array),是多个相同类型数据按一定顺序排列 的集合,并使用一个名字命名,并通过编号的方式 对这些数据进行统一管理
常见的概念
-
数组名
-
下标[索引]
-
元素
-
数组长度[length]
概述
-
数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型
-
创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是 这块连续空间的首地址
-
数组的长度一旦确定,就不能修改
-
我们可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快
分类
-
维数:一维数组,二维数组,多维数组....
-
按照数据的元素类型,基本数据类型元素数组,引用数据类型元素数组
-
常用维数在一维数组居多,二维偏少
一维数组
基本要素
使用数组三步骤
-
声明数组 - 数据类型[] 数组名;
-
分配空间 - 数组名= new 数据类型[ArraySize];
-
赋值 - 数组名[下标] = 值;
数组创建
public class Test{
public static void main(String[] args){
//声明数组
int[] arr;
//分配空间
arr = new int[5];
//赋值
arr[0] = 10;
//可 - 一步简写
int[] arr = new int[5];
arr[0] = 10;
//或
int[] arr = {10,20,30,40,50};
}
}
声明一个变量就是再内存空间划出一块合适的空间 声明一个数组就是再内存空间划出一串连续的空间
初始化方式
public class Test{
public static void main(String[] args){
//动态初始化
int[] arr = new int[5];
int[] arr1;
arr1 = new int[5];
//静态初始化
int[] arr2 = new int[]{1,2,3,4,5};
int[] arr3 = {1,2,3,4,5};
}
}
数组的使用
-
引用方式通过 下标[索引]方式进行调用
-
int[] arr = {1,2,3,4,5};
-
下标对应着数组的每一个值
-
arr[1]; 引用数组的第二个元素值
public class Test{
public static void main(String[] args){
//创建数组
int[] arr = {1,2,3,4,5};
//引用数组 --- 使用下标进行调用
System.out.println("元素数据:"+ arr[1]);
//使用循环引用数据
for(int i = 0; i < arr.length; i++){
//遍历迭代数组元素
System.out.println(arr[i]);
}
}
}
数组的默认值
类型 | 默认值 | 类型 | 默认值 |
---|---|---|---|
byte | 0 | char | \u0000 / 0 |
short | 0 | boolean | false |
int | 0 | float | 0.0 |
long | 0 | double | 0.0 |
二维数组
二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组
创建方式
int[][] arr = new int[2][5];
解析:以上二维数组a可以看成一个两行五列的数组
public class TwoArrayTest {
public static void main(String[] args) {
int[][] arr = {{7,2},{3,4},{4,6},{5,9}};
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] +"\t");
}
}
}
}
Arrays类详解
由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作
常用功能
-
给数组赋值:通过fill方法
-
对数组排序:通过sort方法,按升序
-
比较数组:通过equals方法比较数组中元素值是否相等
-
查找数组元素:通过binarySearch方法能对排序好的数组进行二分查找法操作
int[] arr1 = {1,2,4,35,22,12,5,7,8,10};
System.out.println(arr1);
//打印数组元素Arrays.toString();
System.out.println(Arrays.toString(arr1));
//数组排序:升序
Arrays.sort(arr1);
//调用方法printArray
printArray(arr1);
System.out.println();
//数组赋值
Arrays.fill(arr1, 2, 4, 0);//3 =< x < 5之间的元素被填充0
System.out.println(Arrays.toString(arr1));
Arrays.fill(arr1, 5);//所有元素被5填充
System.out.println(Arrays.toString(arr1));
面向对象
编程原则 -- 编程思想
面向过程与面向对象
-
二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做
-
面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等
面向对象的三大特征
-
封装(Encapsulation):核心思想就是“隐藏细节”、“数据安全”,将对象不需要让外界访问的成员变量和方法私有化,只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定
-
继承(Inheritance):子类可以继承父类的属性和方法,并对其进行拓展
-
多态(Polymorphism):同一种类型的对象执行同一个方法时可以表现出不同的行为特征。通过继承的上下转型、接口的回调以及方法的重写和重载可以实现多态
类和对象
概念
-
类:类是一个模板,它描述一类i对象的行为和状态
-
对象:对象是类的实例,有状态和行为,
-
例如:一条狗是一个对象,它的状态有:颜色,名字,品种;[属性],行为有:摇尾巴,叫,吃等[方法]
类定义格式
[public][abstract|final] class 类名{ 数据类型 成员变量1; ... 数据类型 成员方法名1(参数列表){ 方法体 } }
public:定义类的访问权限,如果一个类被声明为public,则与不在同一个包中的其它类可以通过引用它所在的包来使用这个类,否则这个类只能被同一包中的其它类使用
abstract:表示该类为抽象类,不能创建对象实例
final:表示该类为最终类,不能再派生出新的子类
修饰符 | 同一类 | 同一个包中的类 | 同一个包中的类,不同包的子类 | 所有类 |
---|---|---|---|---|
private | 可以 | 不可以 | 不可以 | 不可以 |
default(缺省) | 可以 | 可以 | 不可以 | 不可以 |
protected | 可以 | 可以 | 可以 | 不可以 |
public | 可以 | 可以 | 可以 | 可以 |
类的定义
类的组成是由属性和行为两部分组成,在Java中我们把属性成为成员变量,把行为称为成员方法
-
成员变量:和以前定义变量几乎是一样的,只不过位置发生了改变,在类中,方法外
-
成员方法:和以前写的main方法格式类似,只不过功能和形式更丰富了,在类中,方法外
-
FieId = 属性 = 成员变量
-
Method = 成员方法 = 函数
/**
* 电动车类
*/
public class Electric {
//成员变量
String brand; //品牌
String endurance;//续航
String color;//颜色
//成员方法
public void run(){
System.out.println("品牌:"+ brand +"的续航能力:"+ endurance +"颜色是:"+ color +"正在骑行");
}
}
对象的使用
创建对象格式
new 类名(); //创建对象 类名 对象名 = new 类名();//给创建对象命名,或者说把创建的对象用一个引用数据类型的变量保存起来
调用格式
对象名.成员变量; 对象名.成员方法();
public class Test {
public static void main(String[] args) {
//实例化 -- 电动车类
Electric e = new Electric();
//调用成员变量 e = 对象名
e.brand = "爱玛";
e.endurance = "40KM";
e.color = "红色";、
//调用成员方法
e.run();
}
}
Java内存的结构分析
-
栈:一般存放基本数据类型(局部变量)
-
堆:存放对象(Person p1,数组等)
-
方法区:常量池(常量,比如字符串常量),类加载信息
成员变量
实例变量:没有static修饰,也叫对象属性,属于某个对象的,通过对象来使用
类变量:有static修饰,也叫类变量,属于整个类的,不是属于某个实例
[修饰符] class 类名{ //成员变量 [修饰符] 数据类型 属性名; //属性有默认值 [修饰符] 数据类型 属性名 = 值; //属性有初始值 }
说明:属性的类型可以是Java的任意类型,包括基本数据类型,引用数据类型(类、接口、数组等)
成员变量和局部变量的区别
-
类中位置不同:成员变量(类中方法外) 局部变量(方法内部或方法声明上)
-
内存中位置不同:成员变量(堆内存)局部变量(栈内存)
-
声明周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,随着方法调用完毕而消失)
-
初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)
成员方法
成员变量是用来存储对象的数据信息,如果要对象的行为功能则需要通过方法的方式实现
成员方法的好处
-
提高代码的复用性
-
可以将实现的细节封装起来,每次需要的时候直接调用
方法的定义
public [返回数据类型] 方法名字(形参列表...){ 语句; [return 返回值;] }
-
参数列表:表示成员方法的输入
-
数据类型(返回类型):表示成员方法输出,void表示没有返回值
-
方法主体:表示实现某一功能代码块
-
return语句不是必须的
方法重载
同一类中的方法名可以相同,前提是名称相同的各方法之间在参数个数或参数类型等方面要存在差异
只要它们的参数列表不同即可,与修饰符和返回值类型无关
重载的好处
-
减轻起名的麻烦
-
功能相似的可以让方法名相同
重载实现
必要点
-
同一个类中
-
方法名相同
辅点
-
参数类型不同
-
参数个数不同
-
参数顺序不同
返回值类型和参数名:不要求
public class TestOverLoad{
//定义方法
/*
* 在同一个类中才能实现重载现象
* 1、方法名相同
* 2、参数类型不同
* 3、参数列表不同
* 4、参数顺序不同
* 与返回值类型无关
*/
public void add(Object o){}
public void add(int i){}
public void add(String name){}
public String add(){}
}
构造方法/构造器
构造器说明
-
构造器不是必须写的,有默认
-
构造器没有返回值
-
方法名和类名必须一样
-
参数列表和成员方法一样的规则
-
构造器的调用由JVM完成
构造方法又称构造器(Constructor),是类的一种特殊方法,它的主要作用是完成对新对象的初始化
-
一个类可以定义多个不同的构造器,即为构造重载
-
构造器是完成对象的初始化,不是创建对象
-
在创建对象时,JVM会调用该类的构造方法
-
如果类中没有定义构造方法,系统会给类生成一个默认的无参构造器
-
一旦自己定义了构造器,系统不会再提供默认的无参构造器,如果想要有无参构造器,就要自己手动声明
public class Test{
public static void main(String[] args){
//测试类实现 ---> 初始化操作
Person p1 = new Person(); //实现无参构造
Person p2 = new Person("小",20);//实现有参构造
}
}
class Person{
//成员变量
String name;
int age;
//构造方法 -- 无参
public Person(){
this.name = "晓";
this.age = 10;
System.out.println("无参构造");
}
//有参
public Person(String name,int age){
this.name = name;
this.age = age;
System.out.println("有参构造");
}
}
this关键字
三个应用
-
调用本类中的属性,也就是成员变量
-
调用本类中其它的方法
-
调用本类中其它的构造方法,调用时要放再构造方法的首行
this关键字访问类的成员变量和成员函数时不受访问权限的控制,可以访问本类中所有的成员变量和方法,包括private的成员变量和方法
对象数组
就是指包含了一组相关的对象,但是在对象数组的使用中一定要清楚一点:数组一定要先开辟空间,但是因为其是引用数据类型,所以数组里面的每一个对象都是null值,则在使用的时候数组中的每一个对象必须分别进行实例化操作
创建方式
-
先创建类
-
创建对象
-
把创建对象放到数组
类名称[] 对象数组名 = new 类名称[长度]
注意: 在声明对象数组后 ,必须对每个数组成员进行实例化话 才能直接使用,否则报空指针异常!
封装
将抽象出的数据(属性)和对数据操作(方法)封装在一起,数据被保护在内部,程序其它部分只有通过被授权的操作(操作),才能对数据进行操作
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
好处
-
只能通过规定的方法访问数据
-
隐藏类的实例细节,方便修改和实现
实现步骤
-
修改属性的可见性
-
创建getter/serter方法
-
在getter/setter方法中加入属性控制语句
注意: 对封装的属性不一定要通过get/set方法,其他方法也可以对封装的属性进行操作。当然最好使用get/set方法,比较标准
访问修饰符
修饰符 | 本类 | 同包 | 子类 | 其它 |
---|---|---|---|---|
private(私有) | √ | × | × | × |
default(默认) | √ | √ | × | × |
protected(受保护) | √ | √ | √ | × |
public(公开) | √ | √ | √ | √ |
实现封装
/**
* 封装Person类
*/
public class Person{
//修改属性可见性
private String name; //名字
private int age; //年龄
private String sex; //性别
//将以上的属性-加上私有访问修饰符 -
/*
* 以前写法是不会添加上访问修饰的权限以默认形式使用
* 这样无法保证数据的安全性-无法进行保护
* 需要对数据进行安全处理 - 则需进行封装
*/
//创建getter/setter方法 - 读取 - 写入
/*
* 需要访问数据的时候使用 get / set
* 读取数据使用 getter方法
* 写入数据使用 setter方法
*/
public String getName(){
return this.name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return this.age;
}
public void setAge(int age){
this.age = age;
}
public String getSex(){
return this.sex;
}
public void setSex(String sex){
this.sex = sex;
}
/**
* 打印方法
*/
public void print(){
System.out.println("姓名:"+ this.name +",年龄:"+ this.age +",性别:"+ this.sex);
}
}
/**
* 创建Person测试类 - Test
* 实现方式和创建类和实例化对象是一样的
*/
public class Test{
public static void main(String[] args){
//实例化对象 -- Person
Person p1 = new Person();
//调用封装方法 -- 这时候调用属性的方法是不可用的
//读取值 - 调用getter , 如果当前是未赋值状态-打印出来则是默认值
System.out.println("名字:"+ p1.getName() +",年龄:"+ p1.getAge() +",性别:"+ p1.getSex());
//写入值 - 调用setter
p1.setName("小明");
p1.setAge(18);
p1.setSex("男");
//调用打印方法
p1.print();
}
}
继承
是类与类的一种关系,是一种“is a”的关系。比如“狗”继承“动物”,这里动物类是狗类的父类或者基类,狗类是动物类的子类或者派生类
注:java中的继承是单继承,即一个类只有一个父类
继承可以解决代码复用问题,让编程更接近人类思维。当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,再父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可
语法格式
只要在子类加上extends关键字继承相应的父类就可以了
class 子类 extends 父类 {
}
好处
-
实现代码的复用
-
代码的扩展性和维护性提高了
关键字
-
super
-
super()作用是调用父类的构造方法,调用构造方法时需注意
-
只能出现再子类的构造方法中,并且是第一行代码
-
super()中的参数,决定了调用哪个构造方法
-
-
在子类中,以super作为父类对象来调用父类属性和方法
-
-
this
-
this()的作用是调用本类的构造方法,调用构造方法时需注意
-
只能写在构造方法的第一行代码
-
使用这两个关键字时需要注意再同一个构造方法中super()和this()不能同时出现
-
-
继承后的子类,可以将this当做子类对象来调用父类的属性和方法
-
继承实现
父类定义了分别定义一个属性和方法
子类继承父类后,子类具备父类所有属性和方法
子类可以定义自己的属性和方法,也可以重写父类方法[@Override 重写注解 ]
父类
public class 父类{
private int num = 1;
public void 父类方法(){
System.out.println("数据:"+ num);
}
}
继承的子类
public class 子类 extends 父类 {
@Override //重写,覆盖父类方法
public void 父类方法(){
super.父类方法();
System.out.println("子类");
}
}
测试
public class Test{
public static void main(String[] args){
子类 子 = new 子类();
子.父类方法();
}
}
重写和重载(两个完全不同的概念)
重写Override
子类继承父类后,子类对父类的方法进行重写,除访问权限可以不一致外,都必须与父类一样
特征:
-
重写的返回值,方法名,参数(个数,顺序,类型)都必须与父类一致
-
访问权限修饰符必须与父类相同或者更大
-
如果子类要抛出异常,异常范围必须比父类范围小或者相同
重载Overload
在一个类中(不一定要继承),存在多个方法名相同,但参数列表(参数个数,类型,顺序)存在其中一样不同时,这些方法就称为方法的重载
特征:
-
方法名相同
-
参数列表不同(个数,类型,顺序)
多态
指同一个实体同时具有多种形式,即同一个对象,在不同时刻,代表的对象不一样,指的是对象的多种形态
多态就是对象的多种形态
-
一个对象的编译类型和运行类型可以不一致
-
编译类型在定义对象时就确定了
-
运行类型可以变化
-
编译类型看定义语句 =号的左边,运行类型看 =号的右边
以封装、继承为基础--要实现多态则需先实现前面两点
好处
-
多态可以不用关心某个对象到底是什么类型,就可以使用该对象的某些方法
-
提高程序的可扩展性和可维护性
引用多态
-
父类的引用可以指向本类的对象
-
父类的引用可以指向子类的对象
public class Test{
public static void main(String[] args){
Animal a1 = new Animal(); //指向本类
Animal a2 = new Dog(); //指向子类
//Dog d1 = new Animal(); //相反则不行
}
}
为什么子类的引用不能用来指向父类的对象?
“狗是一种动物”,但是不能说“动物是一种狗”,狗和动物是父类和子类的继承关系,它们的从属是不能颠倒的
向上转型
(隐式/自动类型转换),是小类型转换到大类型
-
本质:父类的引用指向子类的对象
-
语法:父类类型 变量名 = new 子类类型();
-
特点:
-
编译看左边,运行看右边
-
可以调用父类中的成员
-
不可以调用子类的特有成员
-
运行时从子类开始向上查找
-
Dog dog = new Dog();
Animal animal = dog; //自动类型提升 向上类型转换
向下转型
(强制类型转换),是大类型转换到小类型(有风险,可能出现数据溢出)
-
语法:子类类型 变量名 = new (子类类型)父类引用;
-
不会改变堆当中的类型
-
父类引用指向的是该子类对象才能强转
-
可以调用子类类型中所有成员
Dog dog = new Dog();
Animal animal = dog; //自动类型提升 向上类型转换
//Dog dog2 = animal; //异常报错 - 类型不兼容 - 无法自动转换
Dog dog2 = (Dog) animal;//向下类型转换,强制类型转换
instanceof运算符
来解决引用对象的类型,避免类型转换的安全性问题
多态练习案例
public class TestDemo {
public static void main(String[] args) {
//6.创建“纯纯的”对象用于测试
Animal a = new Animal();
Cat c = new Cat();
Dog d = new Dog();
a.eat();//小动物Animal吃啥都行~调用的是父类自己的功能
c.eat();//小猫爱吃小鱼干~调用的是子类重写后的功能
d.eat();//小狗爱吃肉骨头~调用的是子类重写后的功能
/*2.父类对象不可以使用子类的特有功能*/
//a.jump();//报错,Animal类里并没有这个方法
//a.run();//报错,Animal类里并没有这个方法
c.jump();//小猫Cat跳的老高啦~,子类可以调用自己的功能
d.run();//小狗Dog跑的老快啦~,子类可以调用自己的功能
//7.创建多态对象进行测试
/*3.口诀1:父类引用指向子类对象
* 解释:创建出来的子类对象的地址值,交给父类类型的引用类型变量来保存*/
Animal a2 = new Cat();//Cat类对象的地址值交给父类型变量a2来保存
Animal a3 = new Dog();//Dog类对象的地址值交给父类型变量a3来保存
//8.测试多态对象
/*4.口诀2:编译看左边,运行看右边
* 解释:必须要在父类定义这个方法,才能通过编译,把多态对象看作是父类类型
* 必须要在子类重写这个方法,才能满足多态,实际干活的是子类*/
a2.eat();//小猫爱吃小鱼干~,多态对象使用的是父类的定义,子类的方法体
}
}
/*1.多态的前提:继承+重写*/
//1.创建父类
class Animal{
//3.创建父类的普通方法
public void eat(){
System.out.println("小动物Animal吃啥都行~");
}
}
//2.1创建子类1
class Cat extends Animal{
//4.1添加重写的方法
public void eat(){
System.out.println("小猫爱吃小鱼干~");
}
//5.1添加子类的特有功能
public void jump(){
System.out.println("小猫Cat跳的老高啦~");
}
}
//2.2创建子类2
class Dog extends Animal{
//4.2添加重写的方法
@Override
public void eat(){
System.out.println("小狗爱吃肉骨头~");
}
//5.2添加子类的特有功能
public void run(){
System.out.println("小狗Dog跑的老快啦~");
}
}
static
在类中,用static声明的成员变量为静态成员变量,也成为类变量。类变量的生命周期和类相同,在整个应用程序执行期间都有效
-
static修饰的成员变量和方法,从属于类 - 静态成员
-
普通变量和方法从属于对象
-
静态方法不能调用非静态成员,编译会报错
static关键字用途
方便在没有创建对象的情况下进行调用(方法/变量)
被static关键修饰的方法或者变量不需要依赖于对象来进行访问,只要被类加载了,就可以通过类名进行访问
static可以修饰类的成员方法、变量,也可以编写static代码块来优化程序性能
注意:
-
不能修饰类
-
静态实例只能调用静态实例
-
非静态实例可以调用静态实例和非静态实例
-
静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)
public class StaticTest{
private static String name = "张三"; //非静态成员变量
private String address = "中国"; //静态成员变量
//非静态实例可以调用静态实例和非静态实例
public void test1(){//非静态实例调用静态实例和非静态实例
System.out.println(name); //编译通过
System.out.println(address); //编译通过
test2(); //编译通过
}
//静态实例只能调用静态实例
public static void test2(){//静态实例调用静态实例和非静态实例
System.out.println(name); //编译通过
System.out.println(address); //编译失败
test1(); //编译失败
}
}
说明: static方法是属于类的,非实例对象,在JVM加载类时,就已经存在内存中,不会被虚拟机GC回收掉,这样内存负荷会很大,但是非static方法会在运行完毕后被虚拟机GC回收掉,减轻内存压力
static代码块
静态初始化块作用:提升程序性能
public class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
isBornBoomer是用来这个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成这样效率会更好
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行
说明: 静态初始化块可以置于类中的任何地方,类中可以有多个静态初始化块。在类初次被加载时,会按照静态初始化块的顺序来执行每个块,并且只会执行一次
final
定义:Java中,final表示最终,最终的形态,不可改变
用途:final用于类,方法和变量意义不同,本质不变
注意:
-
final修饰变量,表示变量值不可变,称为常量 - 如同身份证定义之后固定不变
-
final修饰方法,表示方法不可再被子类重写
-
final修饰类,表示该类不能有子类,不可被继承
修饰变量
public static final String NAME = "张三"; //final经常和static关键字一起使用 - 只读不可重写 // NAME = "李四"; // 编译失败
修饰方法
Java 中非私有的成员方法默认都是虚方法,而虚方法就可以在派生类中被覆写
为保证某个类上的某个虚方法不在派生类中被进一步覆写,就需要使用 final 修饰符来声明,让编译器(例如 javac)与 JVM 共同检查并保证这个限制总是成立
public class User{
public finall String getName(){
return "张三";
}
}
class Reader extends User{
//@Override
//pubic final String getName(){
// return "李四";
//} 编译失败
}
修饰类
final类通常是功能完整的,不能被继承,Java 中有许多类是 final 的,比如 String, Interger 以及其他包装类
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
好处:
-
final关键字,提高了性能,JVM和Java应用都会缓存final变量
-
final变量,可以安全的再多线程环境下进行共享,不需要额外的同步开销
单例模式
是 Java 中最简单的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
饿汉式
-
线程安全
-
懒加载
-
直接创建实例
public class SingletonTest {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);
}
}
//饿汉式
class Bank{
//私有化类的构造
private Bank(){
}
//内部创建类的对象
//要求次对象也必须声明为静态
private static Bank instance = new Bank();
//提供公共的静态方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
缺点:
-
类加载就初始化,浪费内存
优点:
-
没有加锁,执行效率高,线程安全的实例
懒汉式
在类初始化不会创建实例,只有被调用时才会创建实例
-
线程不安全
-
懒加载
public class SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);
}
}
//懒汉式
class Order{
//私有化类的构造器
private Order(){
}
//声明当前类对象,没有初始化
//声明为静态的类对象
private static Order instance = null;
//声明静态的返回当前类对象的方法
public static Order getInstance(){
//每次调用时是否为空
if(null == instance){
instance = new Order();
}
return instance;
}
}
实例在调用才会被创建实例
优点:
-
不占内存,
-
单线程模式下,线程安全
缺点:
-
多线程模式下多个线程同时执行if(ehSingleton == null) 都为true,会创建多个实例, 线程不安全
说明
-
饿汉式是立即加载的方式,无论是否会用到这个对象,都会加载
-
如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候感觉稍微有些卡顿
-
懒汉式,是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量
-
使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢
看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式
abstract
用来修饰类和方法,表示抽象类和抽象方法作用是将子类的公共属性和不能确认的方法提取到抽象类,确认的方法也可以写到抽象类,子类继承抽象类,简化代码操作。是模板设计模式
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一 般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父 类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类
说明:
-
含有抽象方法的类必须被声明为抽象类
-
抽象方法不具备方法体[没有大括号{}],没有具体的实现
-
抽象类不能被实例化,抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体,若没有重写全部的抽象方法,仍为抽象类
注意:
-
不能用abstract修饰变量,代码块,构造方法
-
不能用abstract修饰私有方法,静态方法,final方法,final类
车都可以跑(run)。但有几个轮子,怎么跑,对于不同的车有不同的结果。自行车需要人踩着跑,汽车发动机推动跑等等,那么我们可以车表达为抽象类
/**
* 车类 -- 抽象类
*/
public abstract class Car {
//抽象方法
public abstract void run();
}
/**
* 自行车
*/
class Bicycle extends Car{
@Override
public void run() {
System.out.println("人踩着跑。。。");
}
}
/***
* 汽车
*/
class Automobile extends Car{
@Override
public void run() {
System.out.println("发动机驱动跑。。。");
}
}
接口
Java接口是一系列方法的声明,一些特征方法的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)
接口可以理解为一个特殊的类,里面只有全局常量和公共的抽象方法所组成,解决Java无法多继承状态,作用是用于定做标准,接口就是一种能力的体现
概述:
-
接口就是规范,定义的是一组规则,体现了显示世界中“如果你是/要...则必须能...”的思想。继承是一个“是不是”的关系,接口实现则是“能不能”的关系
-
接口的本质是契约,标准,规范,就像我们的法律一样,指定好之后大家要遵守
特点:
-
只有抽象方法和常量-所声明的方法只能是抽象方法,变量只能是常量
-
只能被类实现,不能被继承,而接口可以继承接口,但不能实现
-
一个接口就是描述着一种能力
-
实现接口的类必须重写接口全部的抽象方法
使用
-
定义接口使用 interface关键字
public interface USB{
//int num = 20; 编译失败 - 只能定义常量
final int A = 20; //编译成功
//public void display(){} 编译失败 - 接口只能定义抽象方法
public void display(); //编译成功
}
实现
-
实现接口使用 implements关键字
-
实现类 implements 接口
-
实现类重写接口抽象方法
public class USBImpl implements USB{
@Override
public void display(){
System.out.println("Geek");
}
}
class Test{
public static void main(String[] args){
//父类的引用指向子类的实现
USB usb = new USBImpl();
usb.display(); //调用接口方法
System.out.println(A);//引用常量
}
}
进阶转换一下
public interface USB{
//读取
void read();
//写入
void write();
}
class UP implements USB{
@Override
public void read(){
System.out.println("U盘正在通过USB功能读取数据");
}
@Override
public void write(){
System.out.println("U盘正在通过USB功能写入数据");
}
}
class KeyBoard implements USB{
@Override
public void read(){
System.out.println("键盘正在通过USB功能读取数据");
}
@Override
public void write(){
System.out.println("键盘正在通过USB功能写入数据");
}
}
class Test{
public static void main(String[] args){
//生成实现可USB接口(标准)的U盘对象
USB up = new UP();
//调用U盘的read()方法读取数据
up.read();
//调用U盘的write()方法写入数据
up.write();
//生成实现可USB接口(标准)的键盘对象
USB kb = new KeyBoard();
kb.read();
kb.write();
}
}
实操
/**
* 飞
*/
public interface Fiy {
void fiy();
}
/**
* 攻击
*/
public interface Attack{
void attack();
}
/**
* 飞机
*/
public class Plane implements Fiy {
@Override
public void fiy(){
System.out.println("飞机可以飞");
}
}
/**
* 鸟
*/
public class Bird implements Fiy {
@Override
public void fiy() {
System.out.println("鸟可以飞");
}
}
/**
* 气球
*/
public class Balloon implements Fiy{
@Override
public void fiy() {
System.out.println("气球可以飞");
}
}
/**
* 子弹
*/
public class Bullet implements Fiy,Attack{
@Override
public void fiy() {
System.out.println("子弹可以飞");
}
@Override
public void attack() {
System.out.println("子弹可以攻击");
}
}
/**
* 测试
*/
public class Test {
public static void main(String[] args) {
//多态实现
Fiy f1 = new Plane();
f1.fiy();
Fiy f2 = new Balloon();
f2.fiy();
Fiy f3 = new Bullet();
f3.fiy();
Attack a = new Bullet();
a.attack();
}
}
异常
异常指的是在程序的运行过程中,出现非正常情况的问题,最终会导致JVM非正常停止
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象,Java处理异常的方式是中断处理
异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,不能运行
异常事件分为两类:
-
Error:Java虚拟机无法解决的严重问题,
-
如:JVM系统内部错误,资源耗尽等严重情况
-
StackOverflowError和OOM,一般编写针对性的代码进行处理
-
-
Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理
-
空指针异常
-
试图读取不存在文件异常
-
类型转换异常
-
数据角标越界异常
-
网络中断异常
-
-
检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
-
运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
-
错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
常见异常
-
RuntimeException
-
ClassCastException
-
ArrayIndexOutOfBoundsException
-
NullPointerException
-
ArithmeticException
-
NumberFormatException
-
InputMismatchException
-
等
-
-
IOException
-
FileNotFoundException
-
EOFException
-
-
ClassNotFoundException
-
InterruptedException
-
FileNotFoundException
-
SQLException
public class Test{
public static void main(String[] args){
//NullPointerException
int[] arr = null;
System.out.println(arr[0]);
//ArrayIndexOutOfBoundsException
int[] arr = new int[3];
System.out.println(arr[3]);
//ArithmeticException
System.out.println(20 / 0);
//ClassCastException
Object obj = new Date();
Order order = (Order) obj;
System.out.println(order);
//NumberFormatException
String str = "abc";
System.out.println(Integer.parseInt(str));
//InputMismatchException
Scanner scan = new Scanner(System.in);
int score = scan.nextInt();//输入数值以外报异常
System.out.println(score);
}
}
异常处理方式
try-catch-finally
-
try尝试执行可能会发生异常的代码,如果发生异常则将异常代码抛出
-
try抛出异常之后不会再执行直接跳过
-
搭配方式
-
try{}catch(){}finally{}
-
try{}catch(){}
-
try{}finally{}
-
-
-
catch接住try抛出的异常,进行处理
-
catch块支持写多个
-
注意:如果异常类型如果没有子父类关系,则谁声明在上,在下无所谓
-
注意:如果异常类型存在子父类关系,则要求子类一定是声明在父类上面,否则编写会错误
-
-
finally最后执行,try或catch执行完毕之后到finally块执行
-
可选finally -- 可有可无
-
经常用于释放资源
-
无论怎么样都会执行
-
public class Test{
public static void main(String[] args){
try{//将可能会发生异常的代码放入 try
int[] arr = new int[3];
System.out.println(arr[3]);
}catch(ArrayIndexOutOfBoundsException e){
//如果发生数组角标越界则catch块会进行捕获
e.printStackTrace();//打印异常信息
}catch(NullPointerException e){
//空指针异常
e.printStackTrace();//打印异常信息
}catch(Exception e){
//异常
e.printStackTrace();//打印异常信息
}finally{
//最后
}
}
}
throws+异常类型
-
声明异常 throws + 异常类型
-
修饰在方法中,将方法中的编译异常进行声明
-
谁调用了这个方法则谁来处理这个异常
public class Test {
public static void main(String[] args) {
try {
method01();//直接引用需要进行异常处理
} catch (IOException e) {
e.printStackTrace();
}
method02();//引用method02()不需再进行异常处理
}
public static void method02(){
try {//调用method01()方法进行异常处理
method01();
} catch (IOException e) {
e.printStackTrace();
}
}
//将方法中的编译异常的内容进行声明抛出
public static void method01() throws IOException {
File file = new File("hello.txt");
//Unhandled exception
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.println((char) data);
data = fis.read();
}
fis.close();
}
}
自定义异常
-
继承现有的异常结构
-
提供全局常量 static final long serialVersionUID = -7034897190745766939L;
-
提供重载构造器
-
使用自定义异常用法不变
public class MyException extends RuntimeException {
static final long serialVersionUID = -7034897190745766939L;
public MyException(){}
public MyException(String msg){
super(msg);
}
}