JDK8
复习用
Java前置知识
-
JavaSE
Java Standard Edition 标准版
支持面向桌面级应用(如Windows下的应用程序)的 Java平台,提供了完整的Java核心API
此版本以前称为 J2SE
-
JavaEE
Java Enterprise Edition 企业版
一套用于企业环境下的应用程序的应用方案(包含:Servlet、Jsp),主要针对 Web应用程序的开发
此版本以前称为 J2EE
-
JavaME
Java Micro Edition 小型版
支持 Java程序 运行在移动端的版本,对 JavaAPI 有所精简,并且加入了针对移动端的支持
此版本以前称为 J2ME
-
JDK
Java Development Kit Java开发工具包
JDK = JRE + Java开发工具(java、javac、javap、javadoc等)
-
JRE
Java Runtime Environment Java运行环境
JRE = JVM + Java核心类库
-
JVM
Java Virtual Machine Java虚拟机
JVM 是一个虚拟的计算机,具有指令集并使用不同的存储区域,负责执行指令,管理数据、内存、寄存器
对于不同的平台有不同的虚拟机,屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”
-
Java运行简单的大致流程
Java文件 通过 javac 编译为 Class文件,再通过 java指令 交给 JVM虚拟机 执行
-
Java中特殊的转义字符
\r
: 光标定位到行首public class Main { public static void main(String[] args) { System.out.println("aaaaaaa\rbb"); } }
-
在 IDEA 中 会清空这一行并把光标放到这一行的首位
上方代码在IDEA中输出:bb
-
在 windows终端 中,只会帮光标定位到该行的行首
上方代码在Windows终端中输出:bbaaaaa
-
-
Java程序 中
+
号的使用- 当
+
号 的 左边和右边 都是数值型时,视为加法运算 - 当
+
号 的 左边和右边 有一方是字符串时,视为拼接运算 - 运算顺序:从左往右
System.out.println(100 + 98); // 输出: 198 System.out.println("100" + 98); // 输出:10098 System.out.println(100 + 3 + "hello"); // 输出:103hello System.out.printIn("hello" + 100 + 3); // 输出:hello1003
-
当
+
号 的 左边和右边 有一方是char字符时,视为字符编码值的运算由于 Java 中 char 底层存储的是 对应字符的Unicode码值,打印时会对照Unicode表打印出对应的字符
因此,对char字符的运算相当于对char字符对应的Unicode码值进行运算
但当有一方是字符串时,仍然视为拼接运算
System.out.println(1 + 'a'); // 98 System.out.println('a' + 1); // 98 System.out.println('a' + 'b'); // 195 (97+98=195) System.out.println('a' + "bc"); // abc
- 当
-
JavaAPI
- API : Application Programming Interface 应用程序编程接口
- JavaAPI : Java提供的基本编程接口
- JavaAPI文档 : Java语言提供了大量的基础类,Oracle公司也为这些基础类提供了相应的AP文档用于告诉开发者如何使用这些类,以及这些类里包含的方法
- switch语句 中表达式的值,必须是:byte、short、int、char、这4个基本数据类型的包装类对象、String对象、枚举对象
Java变量
不论使用哪种高级程序语言编写程序,变量都是程序的基本组成单位
-
变量三要素:
类型、名称、值
-
变量相当于内存中一个存储空间的表示,即 声明变量时就是先开辟了一定空间的空房间,变量相当于门牌号,通过 门牌号 可以找到目标房间,从而放入、取出房间中的填充物(也就是变量值),一个房间是有最大容量限制的,超过这个容量就会导致溢出
- 变量类型的不同,其占用内存空间的大小也不同
- 此区域有自己的变量名、数据类型,且可以在同一类型内不断变化
- 变量必须先声明后使用
- 变量在同一个作用域内,不能重名
-
数据类型
-
基本数据类型
-
数值型
-
整数类型
- byte : 1个字节
- short : 2个字节
- int : 4个字节
- long : 8个字节
-
浮点数类型
- float : 4个字节
- double : 8个字节
浮点数计算陷阱:
2.7 和 8.1/3 的值在计算机中会不相等
因为在二进制方面 8.1/3 是无限小数,double的精度太低,导致在内存中存储的 2.7 和 8.1/3 的值在二进制层面不相等
-
-
字符型
- char : 2个字节(Java中可以使用2个char来表示1个字符,用于弥补单个char存储空间小的限制)
-
布尔型
- boolean : 1个字节
-
-
引用数据类型
- 类
- 接口
- 数组
flowchart RL byte([byte 1字节]) short([short 2字节]) int([int 4字节]) long([long 8字节]) float([float 4字节]) double([double 8字节]) char([char 2字节]) boolean([boolean 1字节]) CLASS([类 class]) interface([接口 interface]) array([数组 arrary]) NUMBER([数值型]) CHARACTER([字符型]) BOOLEAN([布尔型]) BASIC([基本数据类型]) REFERENCE([引用数据类型]) Java([Java数据类型]) byte --> short --> int --> long --> NUMBER float --> double --> NUMBER char --> CHARACTER boolean --> BOOLEAN NUMBER --> BASIC CHARACTER --> BASIC BOOLEAN --> BASIC BASIC --> Java CLASS --> REFERENCE interface --> REFERENCE array --> REFERENCE REFERENCE --> Java -
-
字符类型
-
字符类型(char)可以表示单个字符,占用两个字节(可以存放汉字),多个字符使用字符串(String类)
-
String类底层仍然是多个char
-
Java中的char本质存储的是Unicode表中,对应字符的整数码值,因此一些特殊符号(
\n
、\t
)也可以赋值给char,使对应的char在打印时具备对应特殊符号的功能
-
-
布尔类型
- 布尔类型也叫boolean类型,booolean类型数据只允许取值true和false,不允许赋予null值
- boolean类型占1个字节
- boolean类型适于逻辑运算(if、while、do-while、for等)
-
基本数据类型的相互转换
-
自动类型转换
当java程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数据类型,这个就是自动类型转换
flowchart LR byte --> short --> int char --> int int --> long --> float --> double-
注意事项
-
有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算
-
当我们把精度(容量)大的数据类型赋值给精度(容量)小的数据类型时,就会报错,反之就会进行自动类型转换
-
(byte,short)和char之间不会相互自动转换
flowchart LR byte & short x--无法自动转换---x char -
byte、short、char他们三者可以计算,在计算时首先转换为int类型
-
boolean不参与转换
-
自动提升原则:表达式结果的类型自动提升为操作数中最大的类型
-
-
-
强制类型转换
自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型
使用时要加上强制转换符(),但可能造成精度降低或溢出,格外要注意
-
数据类型从大—>小(或多态的向下转型)时,就需要使用到强制转换
-
强转符号只针对于最近的操作数有效,实际中经常会使用小括号提升优先级
-
char类型可以保存int的常量值,但不能保存int的变量值,需要强转
char c1 = 100; //ok int m = 100; //ok char c2 = m; //错误 char c3 = (char)mi; //ok System.out.printIn(c3):/100对应的字符
-
byte、short、char类型在进行运算时,会被当做int类型处理
-
易错题
short s = 12; //ok s = s - 9; // 错误 int -> short byte b = 10;//ok b = b + 11; // 错误 int -> byte b = (byte)(b+11); //正确,使用了强转 char c = 'a'; // ok int i = 16; // ok float d = .314F; // ok double result = c i d; // ok float -> double byte b = 16;// ok short s = 14;// ok short t = s + b; // 错误 int -> short
-
-
基本数据类型 和 String类 的转换
-
转 String类
int aInt = 1; String aStr = 1 + "";
-
转 基本数据类型
String aStr = "1"; int aInt = Integer.parseInt(aStr);
-
String类 转 char
String类底层维护的就是char数组,因此只需要取出数组中的某个索引位置的值即可
String str = "Hello world!"; char e = str.charAt(1); // 数组索引下标从0开始
-
注意事项
-
在将String类型转成基本数据类型时,要确保String类型能够转成有效的数据
比如我们可以把"123",转成一个整数,但是不能把"hello"转成一个整数
-
若String类无法转换为数值基本类型,会抛出异常:java.lang.NumberFormatException
-
String类 转 boolean 时,除了字符串为
true
时,得到true
的boolean值,其他情况均会得到false
的boolean值String str1 = "Hello world!"; boolean b1 = Boolean.parseBoolean(str1); System.out.println(b1); // 输出 : false String str2 = null; boolean b2 = Boolean.parseBoolean(str2); System.out.println(b2); // 输出 : false
-
-
-
Java运算符
-
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等
Java中存在的运算符:算术运算符、关系运算符 [比较运算符]、逻辑运算符、位运算符、赋值运算符、三元运算符
-
测试算术运算符
运算符 运算 范例 结果 + 正号 + 7 7 - 负号 - 7 -7 + 加号 9 + 9 18 - 减号 9 - 9 0 * 乘号 9 * 9 81 / 除号(保留整数部分) 10 / 3 3 % 取模(保留小数部分) 10 % 3 1 ++ 自增(前):先运算后取值 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" "helloworld" - 注意:
-
取模 的 本质:
a % b = a - a / b * b
-
自增、自减 在前在后 的简化理解:看作一个整体
例如下面的例子
-
将
x++
看作一个整体,因为自增在后,所以此时x++
整体的值为 5 ,x
的值为6 -
将
++y
看作一个整体,因为自增在前,所以此时++y
整体的值为6,y
的值为6
int x = 5; int y = 5; if (x++==6 & ++y==6){ // F & T -> F x=11; } System.out.print("x -> " + x + "\t"); // 6 System.out.println("y -> " + y); // 6
-
-
- 注意:
-
关系运算符(比较运算符)
关系运算符的 结果 都是 boolean类型,即 都是 true 或 false
常用于 条件判断 中
运算符 运算 范例 结果 == 相等于 8 == 7 fasle != 不等于 8 != 7 true < 小于 8 < 7 false > 大于 8 > 7 true <= 小于等于 8 <= 7 false >= 大于等于 8 >= 7 true instanceof 检查左边(对象)是否右边(类)相等 或 子类关系 "hello world" instanceof String true -
逻辑运算符、位运算符
用于连接多个条件(多个关系表达式,关系表达式也就是前面关系运算符所连接的一串表达式)
最终的结果也是一个 boolean 值
-
& : 逻辑与
左边、右边同为 true 时,结果为 true,否则为 false
-
&& : 短路与
在满足 逻辑与 的基础上,添加了短路机制:当左边为 false 时,不会计算右边,直接返回 false
-
| : 逻辑或
左边、右边同为 false 时,结果为 false,否则为 true
-
|| : 短路或
在满足 短路或 的基础上,添加了短路机制:当左边为 true 时,不会计算右边,直接返回 true
-
^ : 逻辑异或
左边、右边 不同 时,返回 true
左边、右边 相同 时,返回 false
-
! : 取反
对 ! 右侧的 boolean值进行取反
true –> false
false –> true
a b a & b a && b a | b a || b a ^ b !a true true true true true ture false false true false false false true true true false false true false false true true true true false false false false false false false true -
复习题
//题1 public static void test01(){ int x = 5; int y = 5; if (x++==6 & ++y==6){ // F & T -> F x=11; } System.out.print("x -> " + x + "\t"); // 6 System.out.println("y -> " + y); // 6 } //题2 public static void test02(){ int x = 5; int y = 5; if (x++==5 | ++y==5){ // T | F -> T x=11; } System.out.print("x -> " + x + "\t"); // 11 System.out.println("y -> " + y); // 6 } //题3 public static void test03(){ int x = 5; int y = 5; if (x++==6 && ++y==6){ // F && (短路不运算) x=11; } System.out.print("x -> " + x + "\t"); // 6 System.out.println("y -> " + y); // 5 } //题4 public static void test04(){ int x = 5; int y = 5; if (x++==5 || ++y==5){ // T || (短路不运算) x=11; } System.out.print("x -> " + x + "\t"); // 11 System.out.println("y -> " + y); // 5 }
-
-
赋值运算
赋值运算符就是将某个运算后的值,赋给指定的变量
-
基本赋值运算符
int a = 1; // 此处的 = 号,便是基本赋值运算符,可以理解为: 有一个存储int的4字节空间,空间名称为a,现在将 1 这个值存放到这个空间中
-
复合赋值运算符
int a = 10; int b = 20; a += b; // 等价于: a = a + b; a -= b; // 等价于: a = a - b; a *= b; // 等价于: a = a * b; a /= b; // 等价于: a = a / b; a %= b; // 等价于: a = a % b;
-
赋值运算符的特点
-
运算顺序从右往左
-
赋值运算符
- 左边 只能是变量
- 右边 可以是变量、表达式、常量值
-
复合赋值运算符会进行类型转换
byte b = 3; b += 2; // 等价 b = (byte)(b + 2); b++; // b = (byte)(b+1);
-
-
-
三元运算符
-
基本语法 :
条件表达式 ? 表达式1 : 表达式2
当条件表达式为 true 时,返回 表达式1 的 结果
当条件表达式为 false 时,返回 表达式2 的 结果
-
举例 :
int a = 10; int b = 20; int res = a > b ? a++ : --b; // res -> 21 ; a -> 10 ; b -> 21 ;
-
细节
-
三元运算符返回基本数据类型时,会自动转换为表达式中精度最高的类型
int a = 10; double b = 10.1; System.out.println(true ? a : b); // 输出 : 10.0
-
-
-
运算符优先级
所谓优先级就是表达式运算中的运算顺序
下表中的运算符中,上一行运算符总优先于下一行
只有单目运算符、赋值运算符是从右向左运算的
运算方向 运算符 . () {} ; , 右 -> 左 ++ -- ~ !(data type) 左 -> 右 * / % 左 -> 右 + - 左 -> 右 << >> >>> 位移 左 -> 右 < > <= >= instanceof 左 -> 右 == != 左 -> 右 & 左 -> 右 ^ 左 -> 右 | 左 -> 右 && 左 -> 右 || 左 -> 右 xx ? xx : xx(三元运算符) 右 -> 左 = *= /= %= 右 -> 左 += -= <<= >>= 右 -> 左 >>>= &= ^= |= -
标识符的命名规范
Java对各种变量、方法和类等命名时使用的字符序列称为标识符
凡是自己可以起名字的地方都叫标识符-
标识符的命名规则(必须遵守)
-
26个英文字母大小写(Java中区分大小写)、0-9、_ 、$ 组成
最好不要用 $
-
数字 不能 开头
-
不可以 使用 保留字、关键字,但能够包含 保留字、关键字
-
长度 不受限制
-
不能包含 空格
-
-
标识符的命名规范(约定俗成)
-
包名
多单词组成时所有字母都小写
com.zhumei.study
-
类名、接口名
多单词时使用大驼峰
XxxYyyZzz
-
变量名、方法名
多单词时使用小驼峰
xxxYxxZzz
-
常量名
字母全大写
多单词时使用下划线分隔
XXX_YYY_ZZZ
-
-
Java流程控制
-
顺序控制
没什么好说的,就是从上往下按代码顺序执行
-
分支控制
-
单分支 if
if (条件表达式) { // 条件表达式的值为 ture 则执行此代码块中的代码(false则不执行) 代码; }
flowchart TD A[Start] --> B{条件表达式} B -->|true| C[代码块] C --> D[end] B -->|false| D -
双分支 if - else
if (条件表达式) { // 条件表达式的值为 ture 则执行此代码块中的代码1(false则不执行) 代码1; } else{ // 条件表达式的值为 false 则执行此代码块中的代码2(true则不执行) 代码2; }
flowchart TD A[Start] --> B{条件表达式} B -->|true| C[代码块1] C --> E[end] B -->|false| D[代码块2] D --> E -
多分枝 if - else if - … - else
if (条件表达式1) { 代码1; // 条件表达式1为true则执行代码1,并不再继续执行与条件表达式1处于同一分支的表达式与其对应的代码 } else if (条件表达式2){ 代码2; // 条件表达式2为true则执行代码2,并不再继续执行与条件表达式2处于同一分支的表达式与其对应的代码 } else if (条件表达式N){ 代码n; // 条件表达式n为true则执行代码n,并不再继续执行与条件表达式n处于同一分支的表达式与其对应的代码 } else{ 代码n+1; // 若所有的表达式都为false,则执行此代码块 }
flowchart TD A[Start] --> B{条件表达式1} B -->|true| C[代码块1] C --> D[end] B -->|false| E{条件表达式2} E -->|true| F[代码块2] F --> D E -->|false| G{条件表达式n} G -->|true| H[代码块n] G -->|false| I[代码n+1] H --> D I --> D -
嵌套分支
在 一个分支结构 中又 完整的 嵌套了 另一个完整的分支结构
建议不超过3层,嵌套层数与可读性呈负相关,层数越多可读性越差
if (条件表达式a){ 代码1; if (条件表达式b){ 代码2; }else if (条件表达式c){ 代码3; }else { 代码4 } }
-
switch分支
- 在Java中:
- switch表达式的值可以是变量(byte、short、int、char、这4个基本数据类型的包装类对象、String对象、枚举对象)
- case后的值不能为变量,必须是常量
- default语句是可选的,若没有default语句又没有匹配上对应的case中的常量,就表示不会执行switch中的任何代码(switch中的表达式是一定会执行的)
- 在case中的代码不写break,会导致case穿透
switch(表达式){ case 常量1: 代码1; break; case 常量2: 代码2; break; case 常量n: 代码n; break; default: 代码default; break; }
- 在Java中:
-
-
循环控制
-
for循环
-
for循环的四要素:
- 循环变量初始化
- 循环条件
- 循环操作(需要循环的代码)
- 循环变量迭代
-
循环变量初始化、循环条件、循环变量迭代,都可以不放在for循环的括号中
-
循环条件是一个返回布尔值的表达式,若在for循环中没有写循环条件,则默认为true
-
退出for循环的方式:
- 循环条件结果为false
- 执行break语句
- 执行return语句
for(循环变量初始化;循环条件;循环变量迭代){ 需要循环的代码; }
-
-
while循环
while循环和for循环一样,也有相同的循环四要素,只不过放的位置和for循环不一样
简单来说,可以把while循环看作另一种写法的for循环
不必纠结写法上的细枝末节,重点在于知道执行的代码在哪种情况下被循环了几次
循环变量初始化; while(循环条件){ 需要循环的代码; 循环变量迭代; }
-
do…while循环
和while循环相比,do…while循环会先执行一次寻妖循环的代码,再进行
循环变量初始化; do{ 需要循环的代码; 循环变量迭代; }while(循环条件);
-
多重循环控制
将一个循环放在另一个循环中,外层循环和内层循环均可使用for循环、while循环、do…while循环
一般建议多重循环不超过3层,层数越多,代码可读性越差
for (int i = 0; i < 10; i++) { // 外层循环 for (int j = 0; j < 20; j++) { // 内层循环 System.out.print("*"); // 需要xun } System.out.println(); // 表示换行 }
- 经典题
-
打印 九九乘法表
for (int i = 1; i <= 9; i++) { // 表示输出多少行 for (int j = 1; j <= i; j++) { // 表示每行输出多少列 System.out.print(j + "*" + i + "=" + j * i); System.out.print("\t"); } System.out.println(); // 换行 }
-
打印 实心 / 空心 三角形
//1. 实心三角 for (int i = 1; i <= 6; i++) { // 表示打印多少行 for (int j = 1; j <= 6-i; j++) { // 每一列要打印的空格 System.out.print(" "); } for (int j = 1; j <= (2*i-1) ; j++) { // 每一列要打印的*号数量 System.out.print("*"); } System.out.println(); } //2. 空心三角 for (int i = 1; i <= 6; i++) { // 表示打印多少行 for (int j = 1; j <= 6-i; j++) { // 每一列要打印的空格 System.out.print(" "); } for (int j = 1; j <= (2*i-1) ; j++) { // 每一列要打印的*号数量 //当要打印的是 最后一行 或者 这一列第一个和最后一个 时,输出* if (i==6||j==1||j==(2*i-1)){ System.out.print("*"); }else { // 其余输出空格 System.out.print(" "); } } System.out.println();// 换行 }
-
打印 空心 / 实心 菱形
//3. 实心菱形 for (int i = 1; i <= 12; i++) {// 控制打印的行数 if (i <= 6) { //打印菱形的上半部分 for (int j = 1; j <= (6 - i); j++) { System.out.print(" "); } for (int j = 1; j <= (2 * i - 1); j++) { System.out.print("*"); } } else { //打印菱形的下半部分 for (int j = 1; j <= (i - 6); j++) { System.out.print(" "); } for (int j = 1; j <= (2 * (12 - i) - 1); j++) { System.out.print("*"); } } System.out.println(); // 换行 } //4. 空心菱形 for (int i = 1; i <= 12; i++) {// 控制打印的行数 if (i <= 6) { //打印菱形的上半部分 //1. 先打印空格 for (int j = 1; j <= (6 - i); j++) { System.out.print(" "); } //2. 打印边框和内部空格 for (int j = 1; j <= (2*i-1) ; j++) { if (j == 1 || j == (2 * i - 1)){ System.out.print("*"); }else { System.out.print(" "); } } } else { //打印菱形的下部分 //1. 打印空格 for (int j = 1; j <= (i - 6); j++) { System.out.print(" "); } //2. 打印边框和内部空格 for (int j = 1; j <= (2 * (12 - i) - 1); j++) { if (j == 1 || j == (2 * (12 - i) - 1)){ System.out.print("*"); }else { System.out.print(" "); } } } System.out.println(); // 换行 }
-
- 经典题
-
break跳转控制语句
break语句用于终止某个语句块的执行,一般使用在switch或者循环[for,while,do-while]中
//使用方式1 for (int i = 0; i < 10; i++) { if (i == 5) { break; // 退出最近的某个循环体 } } //使用方式2 label1: for (int i = 0; i < 10; i++) { label2: for (int j = 0; j < 10; j++) { if (j == 5) { break label1; // 退出了label1标签所标识的for循环 } System.out.println("j -> " + j); } }
-
continue跳转控制语句
continue语句用于结束本次循环,继续执行下一次循环。
//使用方式1 for (int i = 0; i < 10; i++) { //当i=5时,不会执行后续的输出语句,而是进入下一次循环 if (i == 5) { continue; // 结束本次循环,进入下一次循环 } System.out.println("i -> " + i); } //使用方式2 label1: for (int i = 0; i < 4; j++) { label2: for (int j = 0; j < 10; i++) { if (i == 5) { continue label1; // 执行下一次 label1 标签所指的For循环(for i的循环) } System.out.println("j = " + j + "\t"); } }
-
return跳转控制语句
return 使用在方法,表示跳出所在的方法,意味着所在方法算为执行完毕
return写在主方法中,则会退出Java程序
-
易错陷阱题
-
求出1-1/2+1/3-1/4..1/100的和
易错点:
-
1/2 在Java中等于0,需要使用1.0/2才能得到0.5
所以在做
/
号时,除数与被除数必须有一个为浮点型
double res = 0.0; double temp = 0.0; for (double i = 1.0; i <= 100; i++) { if (i%2==0){ //符号为负 temp = -(1/i); }else { //符号为正 temp = 1/i; } res += temp; } System.out.println(res);
-
-
-
Java数组
-
简介
数组可以存放多个同一类型的数据
数组也是一种数据类型,是引用类型
当数组存储基本数据类型时,默认值为基本数据类型对应的默认值
当数组存储引用数据类型(如:包装类对象、String类对象)时,默认值为null
数据就是在内存空间中连续存储的同一类型数据
数组索引从0开始
注意:数组是引用数据类型,变量中存储的是数组在堆中的地址
-
数组的几种初始化方式
-
方式一
double[] a = new double[5]; // 直接分配内存空间 a[0] = 1d; double b[]; // 先声明 b = new double[5]; // 再分配内存空间 b[0] = 2d; // double[] a 和 double a[] 是一样的,建议使用 double[]
-
方式二
double[] c = {1.0,2.0,3.0}; // 静态初始化
-
-
数组反转
有很多方法,比如:
- 开辟一个同样长度的数组,便利旧数组,在新数组中从后往前放值
- 数组第一个和最后一个进行交换位置,第二个和倒数第二个,直到数组完全反转
Integer[] arrayInter = {1, 2, 3, 4, 5, 6, 7}; for (int i = 0; i < arrayInter.length / 2; i++) { Integer temp = arrayInter[i]; arrayInter[i] = arrayInter[arrayInter.length - 1 - i]; arrayInter[arrayInter.length - 1 - i] = temp; } for (int i = 0; i < arrayInter.length; i++) { System.out.print(arrayInter[i] + "\t"); }
-
数组排序/查找
有多种数组排序和查找的算法,这里演示冒泡排序
-
冒泡排序
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从后向前(从下标较大的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
举例:(从小到大)
int[] arrayInt = {41, 23, 65, 21, 78, 45}; int temp = 0; for (int i = 0; i < arrayInt.length - 1; i++) { for (int j = 0; j < arrayInt.length - 1 - i; j++) { if (arrayInt[j] > arrayInt[j+1]){ temp = arrayInt[j]; arrayInt[j] = arrayInt[j+1]; arrayInt[j+1] = temp; } } } for (int i = 0; i < arrayInt.length; i++) { System.out.print(arrayInt[i] + "\t"); }
-
二分查找
在算法笔记中
-
-
二维数组
-
几种定义方式
int[][] a
、int[] a[]
、int a[][]
都可以,但建议使用int[][] a
-
方式一
int[][] a = new int[2][3]; int[][] b; b = new int[2][3];
-
方式二
int[][] a = new int[10][]; a[1] = new int[1]; a[2] = new int[2]; a[3] = new int[3];
-
方式二
int[][] a = { {1,2,3,4}, {1,2,3}, {1,2}, {1} };
-
-
易错点
int[] x,y[]; // x是一维数组,y是二维数组
// 初学时会有点搞混匿名内部类和数组静态初始化的代码 //1. 数组的静态初始化 String[] arrayStr = new String[]{"a","b"}; //2. 匿名内部类 String arrayStr = new String(){...}; // 注意: 这里编译会报错,因为String为final类 (为了通过编译报错来确定这是创建匿名内部类的方式)
-
Java面向对象
-
Java对象在内存中的存在形式(简化)
class Cat { private String color; private String name; private int age; public Cat(String color, String name, int age) { this.color = color; this.name = name; this.age = age; } } public static void main(String[] args) { Cat cat = new Cat(new String("blue"), "tom", 6); }
-
JVM内存结构的简单分析
- 栈:存储基本数据类型(局部变量)
- 堆:存储对象
- 方法区:常量池,类文件
-
Java创建对象的流程(简化)
- 先加载对应类的信息(加载属性和方法信息,且只会加载一次)
- 在堆中分配空间,进行默认初始化
- 将对象在堆中的地址赋值给栈中的局部变量
-
成员方法传参机制
本质:都是值传递,即 拷贝一份副本然后将副本值传递给目标方法
方法返回值也是同理,本质都是值拷贝,返回的是基本数据类型时拷贝的就是其值,返回的是引用数据类型时拷贝的是就是其在堆内存中的地址
-
方法递归调用
在算法笔记中有较为详细的介绍
- 使用递归的重要知识点
-
执行一个方法时,就会创建一个新的受保护的独立空间(栈空间)
方法的局部变量是独立的,不会相互影响
-
如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引用类型的数据。
-
递归必须向退出递归的条件逼近,否则就是无限递归,出现 StackOverflowError 栈溢出,即栈空间被撑爆了
-
当一个方法执行完毕,或者遇到return就会返回,遵守谁调用,就将结果返回给谁
-
- 使用递归的重要知识点
-
方法重载
java 中允许同一个类中,多个同名方法的存在,但要求 形参列表不一致
- 重载的好处
- 减轻了 起名 、记名 的麻烦
- 重载的要求
- 方法名 必须 相同
- 形参列表 必须 不同(类型、个数、顺序 至少一个不同)(形参名无要求)
- 返回类型 没有要求
- 重载的好处
-
方法重写
方法重写(覆盖)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法重写了父类的方法
- 达成方法重写的要求
- 子类方法的 形参列表、方法名 必须 要和父类方法的 形参列表、方法名 完全一致
- 子类方法的 返回类型,必须 要和 父类方法的返回类型一致 或 是父类方法返回类型的子类
- 子类方法 不能 缩小父类方法的访问权限(public > protected > 默认 > private)
- 达成方法重写的要求
-
可变参数
java 允许将同一个类中多个同名同功能但参数个数不同的方法,通过可变参数的形式,封装成一个方法
简单点就是说方法可以传入无限多个指定类型的参数值
public String study(String... arrayStr){ String str = ""; for (int i = 0; i < arrayStr.length; i++) { str += arrayStr[i]; } return str; }
- 一些细节
- 可变参数的实参可以是 0个 或 任意多个
- 可变参数的本质 就是 数组
- 可变参数 和 普通参数 一起放在形参列表时,必须保证 可变参数类型 放在最后
- 一个形参列表中只能出现一个可变参数
- 一些细节
-
作用域
-
Java中作用域的分类
-
全局变量:也就是属性,作用域为整个类体
全局变量(属性)可以不赋值,就可以直接使用,因为有默认值
-
局部变量:除了属性之外的其他变量,作用域为定义它的代码块中
局部变量没有默认值,必须赋值后使用
-
-
一些细节
-
属性和局部变量可以重名,访问时遵循就近原则
-
在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名
-
生命周期不同
-
属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁
-
局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁
即:在一次方法调用过程中
-
-
作用域范围不同
- 全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)
- 局部变量:只能在本类中对应的方法中使用
-
修饰符不同
- 全局变量/属性可以加修饰符
- 局部变量不可以加修饰符
-
-
-
构造器
构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化
-
特点
- 方法名 和 类名 相同
- 没有返回值
- 在创建对象时,系统会自动的调用该类的构造器完成对象的初始化
-
一些细节
-
一个类可以定义多个不同的构造器,即构造器重载
-
构造器是用来完成对象初始化的,不是用于创建对象的
-
在创建对象时,系统自动的调用该类的构造方法
-
如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(默认构造器)
-
一旦定义了自己的构造器,默认的构造器就覆盖了
即:不能再使用默认的参构造器,除非显式的定义一下
-
-
-
this关键字
Java虚拟机会给每个对象分配this,代表当前对象
即:每个实例都有一个隐藏的变量this,存储了该对象在堆内存中的地址
-
JVM内存图(简化)
Cat cat = new Cat( new String("blue"), // 堆中String对象的地址 "tom", // 常量池中的地址 6 // 基本数据类型的值 );
-
一些注意事项
-
this 不能在类定义的外部使用,只能在类定义的方法中使用
-
this 关键字可以用来访问本类的属性、方法、构造器
-
this 用于区分当前类的属性和局部变量
-
访问成员方法的语法:this.方法名(参数列表)
-
访问构造器语法:this(参数列表)
即只能在构造器中访问另外一个构造器,且必须放在第一条语句
this() 和 super() 都需要放在构造器的第一行,所以无法同时使用
-
在哪个对象的方法中调用,this就代表哪个对象
看的不是编译类型,而是运行类型。
后期框架中使用了大量的设计模式,导致了很难判断this到底是指的哪个对象,最好的办法是使用IDEA的debug
-
-
-
访问修饰符
java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围)
-
四种访问修饰符
访问级别 访问修饰符 同类 同包 子类 不同包 公开 public √ √ √ √ 受保护 protected √ √ √ × 默认 没有修饰符 √ √ × × 私有 private √ × × × -
实际开发中,当某个方法需要 A类 的实例传入,但 A类 的 构造方法的protected
此时,就可以使用匿名内部类的方式
package com.test; // 注意和 MainApp类 在不同包下 public class A { protected A(){} }
package com.zhumei; // 注意和 A类 在不同包下 import com.test.A; public class MainApp { public static void main(String[] args) { MainApp mainApp = new MainApp(); mainApp.test01(new A(){}); // new A(){} 相当于创建了一个 MainApp$1 extends A 类(继承了A类),这样就突破了protected修饰符导致的无法使用不同包下类的构造器 } public void test01(A a){ System.out.println(a.getClass()); } }
-
使用修饰符的注意事项
-
修饰符可以用来修饰类中的属性,成员方法以及类
-
只有 默认修饰符 和 public修饰符 才能修饰类
-
子类继承了父类的方法、字段后,调用父类的方法、字段需要遵守父类设定的访问修饰符的规则
即:父类设置为private的字段、方法,子类无法通过正常方式进行访问使用(可以使用反射爆破的方式强行使用)
-
-
-
面向对象三大特征
-
封装
封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作(方法)封装在一起
数据被保护在内部,程序的其它部分只有通过被授权的操作(方法),才能对数据进行操作
- 封装的好处
- 隐藏具体的实现细节
- 可以对封装的数据进行校验
- 封装的实现步骤
- 将数据私有化(private)
- 提供一个公共的(public)set方法,对外提供修改数据的方式,也可对外界提供的数据进行校验
- 提供一个公共的(public)get方法,对外提供获取数据的方式,也可对外界进行权限判断来不同的值
- 封装的好处
-
继承
继承可以解决代码复用,让我们的编程更加靠近人类思维
当多个类存在相同的属性和方法时,可以从这些类中抽象出父类
在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可
-
super关键字
super代表父类的引用,用于访问父类的属性、方法、构造器(但受到父类访问修饰符的限定)
注意:
- super的访问不限于直接父类,而是所有继承的类,使用super若在父类中找不到会去找父类的父类,直到找到为止 或 所有的父类均没有而报错
- 若在父类中找到了,但由于修饰符关系无法直接访问,则会直接报错,而不会继续向更上层的父类进行寻找
-
一些细节:
-
子类会拥有父类的所有信息(字段、方法),但是会受到父类访问修饰符的影响,对父类私有、默认访问修饰符修饰的字段和方法都无法直接使用
可以通过父类提供的其他可访问方法来间接获取、修改、使用字段或方法
-
final类无法被继承
-
子类必须调用父类的构造器,完成父类的初始化
当创建子类对象时,不管使用子类的哪个构造器,默认情况 下总会去调用父类的无参构造器
如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则编译不通过
-
-
如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
-
super(参数列表)在使用时必须放在构造器第一行
super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
-
this 和 super
public class MainApp { public static void main(String[] args) { B b = new B(); } }
class A{ public A(){ System.out.println("A -> null"); // 无参构造器 } public A(String str){ System.out.println("A -> String"); // 有参构造器 } } class B extends A{ public B(){ this("str"); System.out.println("B -> null"); // 无参构造器 } public B(String str){ super(str); System.out.println("B -> String"); // 有参构造器 } }
输出:
A -> String
B -> String
B -> null
-
-
java 所有类都是 Object 类的子类, Object 是所有类的基类
-
父类构造器的调用不限于直接父类,将一直往上追溯直到 Object 类(顶级父类)
-
子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制
-
不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
-
-
创建继承类实例时,JVM内存分析(简化)
class GrandPa{ String name = "大头爷爷"; protected int age = 70; } class Father extends GrandPa{ String name = "大头爸爸"; private String job = "程序员"; public String getJob(){ return this.job; } } class Son extends Father{ String name = "大头儿子"; }
public class MainApp { public static void main(String[] args) { Son son = new Son(); // 研究创建一个有继承关系的类的实例时,JVM中的内存分布图(简化) } }
创建实例的流程(简化):
-
首先会加载类信息进入到内存中
先从顶级父类开始加载,比如这里会先加载 Object类,然后是 GrandPa类,接着是 Father类,最后是 Son类
-
在堆中开辟一个空间用于存储son实例
这个空间会分成几个部分,用于存储从不同类中继承而来的信息
比如这里主要会划出3个关键的部分:Son类、Father类、GrandPa类,每个部分都相互独立,即使有相同的字段名也不会造成冲突
-
将堆中的地址赋值给在栈中的变量
-
-
注意:
若父类方法中有获取字段值的行为,该值只会去父类本身或父类的父类去找,并不会从子类中向上找
public class MainApp { public static void main(String[] args) { B b = new B(); b.study1(); // 输出的是10 } } class A { public int number = 10; public void study() { System.out.println(this.number); // this.number 会去本类(A类),以及Object类中寻找 (若A类中没有的话) } } class B extends A { public int number = 120; }
-
-
多态
方法或对象具有多种形态
比如说你可以是大学生的同时,也可以是高中生的课外辅导班老师,具体是什么身份要看在哪一种场景中做什么事情
多态是面向对象的第三大特征,多态是建立在封装和继承基础之上的
-
实现多态的三大条件
- 必须在继承体系下
- 子类必须对父类中的方法进行重写
- 通过父类的引用调用重写的方法
-
方法的多态
重写和重载就是多态的体现
调用的方法名是同一个,但是会根据 调用方的对象不同、传入的参数不同 导致最终调用的方法也不同
-
重载:在调用方法时,传入不同的参数,就会调用不同的方法
-
重写:在调用方式时,根据对象的不同,就会调用不同的方法
-
-
对象的多态
一个编译类型能够表达多个不同的运行类型,真正运行时根据真正的运行类型来确定运行的结果,这就体现了多态
class A { public String study() { return "a"; } } class B extends A { @Override public String study() { return "b"; } } public class MainApp { public static void main(String[] args) { //这就是多态的体现,编译类型A既可以表达A实例对象也可以表达B实例对象 A a = new B(); // A是编译类型,B是运行类型 String str = a.study(); } }
-
多态的向上转型
父类的引用指向了子类的对象
即:子类的对象地址,保存在了父类类型的变量中
通过向上转型,子类可以调用父类中的所有成员(但要遵守访问权限的限制),但是不能调用子类的特有成员
最终的运行效果,仍然是看子类的具体实现,从运行类型开始查找然后调用,与之前继承的实例方法调用一致
class A { public String study() {return "a";} } class B extends A { @Override public String study() {return "b";} } public class MainApp { public static void main(String[] args) { // 父类的引用指向了子类的对象 A a = new B(); // A是编译类型,B是运行类型 } }
-
多态的向下转型
将父类的引用交给子类的类型
即:原本父类变量中保存的是子类对象的地址,现在将地址交给子类变量
只能强转父类的引用,不能强转父类的对象
要求父类的引用必须指向的是当前目标类型的对象
当向下转型后,可以调用子类类型中所有的成员
class A { public String study() {return "a";} } class B extends A { @Override public String study() {return "b";} } public class MainApp { public static void main(String[] args) { // 父类的引用指向了子类的对象 A a = new B(); // A是编译类型,B是运行类型 // 父类的引用交给子类的类型(向下转型) B b = (B)a; // B是编译类型,B是运行类型 } }
-
-
Java的动态绑定机制
- 当调用对象方法时,该方法会和该对象的内存地址(运行类型)绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明哪里使用
public class MainApp { public static void main(String[] args) { A a = new B(); System.out.println(a.study()); // 动态绑定机制,先去 子类(B类) 中寻找sutdy方法,没找到再去父类中找 System.out.println(a.number); // 直接在编译类型A类(或A类的父类)中寻找对应的字段,字段(属性)没有动态绑定机制 } } class A { public int number = 10; public String study() { return "a"; } } class B extends A { public int number = 120; @Override public String study() { return "b"; } }
-
多态的常见应用
-
多态数组
public class MainApp { public static void main(String[] args) { Person[] person = new Person[3]; person[0] = new Student(); // 编译类型 -> Person ; 运行类型 -> Student person[1] = new Teacher(); // 编译类型 -> Person ; 运行类型 -> Teacher person[2] = new Student(); // 编译类型 -> Person ; 运行类型 -> Student for (int i = 0; i < person.length; i++) { String name = person[i].name(); System.out.println(name); } } } interface Person{ String name(); } class Student implements Person{ @Override public String name() {return "学生";} } class Teacher implements Person{ @Override public String name() {return "教师";} }
-
多态参数
public class MainApp { public static void main(String[] args) { test(new Teacher()); test(new Student()); } public static void test(Person person){ String name = person.name(); // name方法的具体运行逻辑是根据运行类型决定的 System.out.println(name); } } interface Person{ String name(); } class Student implements Person{ @Override public String name() { return "学生"; } } class Teacher implements Person{ @Override public String name() { return "教师"; } }
-
-
-
-
类变量、类方法 (static)
-
类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量
任何一个该类的对象去访问它时,取到的都是相同的值
同样任何一个该类的对象去修改它时,修改的也是同一个变量
-
类变量的定义
访问修饰符 static 数据类型 变量名
(推荐)static 访问修饰符 数据类型 变量名
public static int staticInt = 1; // 推荐 static public double staticDouble = 1.0; // 不推荐
-
类变量的访问
类名.类变量名
(推荐)对象名.类变量名
public static double staticDouble = 1.0; double d = A.staticDouble; // 推荐 A a = new A(); double d2 = a.staticDouble; // 不推荐
-
一些注意事项
- 类变量 与 实例变量 区别
- 类变量是该类的所有对象共享
- 实例变量是每个对象独享
- 访问类变量时,同样需要遵守访问修饰符的限制
- 类变量是在类加载时就初始化了,即使没有创建对象,只要类加载了就可以使用类变量
- 类变量的生命周期是随类的加载开始,随着类消亡而销毁
- 类变量 与 实例变量 区别
-
-
类方法
类方法也叫静态方法
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法提高开发效率
-
类方法的定义
访问修饰符 static 数据返回类型 方法名(){}
推荐static 访问修饰符 数据返回类型 方法名(){}
public static String test(){ return "this is static method :)"; }
-
类方法的调用
类名.类方法名
推荐对象名.类方法名
public class MainApp { public static void main(String[] args) { String str = A.test(); // 调用类方法 } } class A{ public static String test(){ return "this is static method :)"; } }
-
一些注意事项
-
类方法和普通方法都是随看类的加载而加载,将结构信息存储在方法区
-
类方法中不允许使用和对象有关的关键字
如:this、super
-
类方法中只能访问:类变量、类方法
-
普通成员方法中可以访问静态成员和非静态成员
-
-
-
-
Java程序中的main方法
public static void main(String[] args) { ...;//代码 }
-
public : Java虚拟机需要调用类的main方法,所以该方法必须是public
-
static : Java虚拟机在执行main方法时不必创建对象,所以该方法必须是static
-
void : Java虚拟机只会调用该方法,并不会对该方法的返回值做出任何处理,所以该方法必须是void
-
main : Java虚拟机只认定这个方法名,因此必须是main作为方法名
-
String[] args : Java虚拟机会将执行java命令时传递的参数(以空格为分隔符)封装在String数组中,并作为实参传递给main方法
例如:
-
运行 :
java test 数据a 数据b 数据c
-
输出 :
数据a
数据b
数据c
public class MainApp { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println(args[i]); } } }
-
-
-
代码块
代码化块又称为初始化块,属于类中的成员(即是类的一部分)。类似于方法,将逻辑语句封装在方法体中,通过 { } 包围起来,但和方法不同,没有方法名,没有返回,没有参数,只有方法体
代码块不能通过对象或类显式调用,而是 加载类 或 创建对象 时 隐式调用
Java中代码块分为:静态代码块 和 普通代码块(非静态代码块)
-
代码块的定义语法
-
修饰符可选,且修饰符只能写static
-
最后的分号可以选择省略
[修饰符]{ 代码; };
-
-
普通代码块
普通代码块相当于另外一种形式的构造器,即 对构造器的补充机制(做初始化的操作)
当所有的构造器中都有重复的语句时,就可以提取到普通代码块中,提高代码的复用率
- 当一个类中有多个普通代码块时,按代码中从上到下的顺序依次执行
- 同样,类中非静态字段的初始化 与 普通代码块的优先级也是一致的,因此遵循按定义顺序执行
- 类的构造器中最前面隐含了 super() 和 调用普通代码块,super() 在行首,调用普通代码块的代码其次
-
静态代码块
普通代码块用于对实例的初始化,静态代码块则是对类进行初始化(初始化静态属性)
随着类的加载而执行,由于类只会加载一次所以静态代码块也只会执行一次
- 当一个类中有多个静态代码块时,按代码中从上到下的顺序依次执行
- 同样,类中静态字段的初始化 与 静态代码块的优先级也是一致的,因此遵循按定义顺序执
-
类加载的时机
-
创建实例对象的时候(new)
-
创建子类实例对象的时候,其父类也会被加载
-
使用类的静态成员(静态变量、静态方法)的时候
调用类中 final static 对应的常量时,底层编译器做了优化,并不会导致类的加载(注意:常量值不是通过new创建时才满足类不会被加载)
-
-
不同位置代码块的执行顺序
同级别时,按照代码中的定义顺序进行执行
- 父类的静态代码块、父类静态字段的初始化
- 子类的静态代码块、子类静态字段的初始化
- 父类的代码块、父类非静态字段的初始化
- 父类的构造器
- 子类的代码块、子类非静态字段的初始化
- 子类的构造器
public class MainApp { public static void main(String[] args) { B b = new B(); } } class A{ static { System.out.println("A -> static{}"); // 1 } public A(){ System.out.println("A -> public A{}"); //4 } { System.out.println("A -> normal{}"); // 3 } } class B extends A{ static { System.out.println("B -> static{}"); // 2 } public B(){ System.out.println("B -> public B{}"); // 6 } { System.out.println("B -> normal{}"); // 5 } }
-
-
final关键字
final 可以修饰 类、属性、方法、局部变量
-
常见使用 final 的场景
-
不希望类被继承时,使用final修饰类
final类中的方法和属性不必再加fianl修饰符
-
不希望父类中的某个方法被子类重写,可以使用final修饰父类中的这个方法
-
不希望父类中的某个属性的值被修改,可以使用final修饰父类中的这个属性
-
不希望某个局部变量被修改,可以使用final修饰
-
-
final 的一些细节和注意事项
-
final修饰的属性(叫常量),一般使用 XX_XX_XX 来命名
-
final修饰的属性在定义时,就必须赋值,且无法再修改
注意:final锁住的是目标变量空间中存储的值
-
-
如果是基本数据类型,则会锁定住基本数据类型的值
-
如果是引用数据类型,则会所定住对应的地址值(对象地址)
不会锁住目标对象封装的属性值
对 fianl属性(非静态) 赋值的位置可以是: - 定义时
-
构造器
- 代码块
对 final属性(静态)赋值的位置可以是:
- 定义时
-
静态代码块
-
final类无法被继承,但可以被实例化
-
final方法无法被重写,但是可以被继承
-
final 不能修饰 构造器
-
final 搭配 static 效率会提高,调用 final static 的常量不会导致类的加载(编译器做的优化)
注意:常量值不是通过new创建时才满足类不会被加载
-
包装类 和 String类 都是 final类
-
-
抽象类
当父类的一些方法不能确定时,可以用 abstract关键字,将这些不确定的方法交给子类去实现
用 abstract 来 修饰方法 就成为了抽象方法
用 abstract 来 修饰类 就成为了抽象类
-
抽象类的价值更多作用在于,即 设计者设计好后,让子类继承并实现
-
抽象类
一旦类中包含了抽象方法,则该类必须声明为抽象类
抽象类中,不一定包含抽象方法
public abstract class Father { }
-
抽象方法
public abstract void test(); // 抽象方法是没有主体代码块的 (即 : {...} )
-
一些细节
-
抽象类不能被实例化
-
抽象类中不一定包含abstract方法,但abstract方法一定要在抽象类中
注意:抽象类的本质还是类,普通类中有的抽象类中都可以有,唯一的区别就是抽象类无法被实例化
-
abstract 只能修饰 类、方法
-
子类继承了抽象类,则必须实现抽象类的所有方法,除非子类也是抽象类
-
abstract 不能和 private、final、static 一起使用,因为这些关键字的作用和 abstract的作用 相互违背
-
-
-
接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来
接口是更加抽象的抽象的类,接口体现了程序设计的多态和高内聚低偶合的设计思想
- JDK7 : 抽象类里的方法可以有方法体,接口里的所有方法都没有方法体
- JDK8 : 接口类可以有静态方法,默认方法
一些注意事项
-
接口不能被实例化
-
接口中所有的方法都默认被 public abstract 修饰
-
抽象类实现接口,可以不必全部实现接口中的方法;普通类实现接口,必须全部实现接口中的方法
一个类(抽象类、普通类)可以实现多个接口
-
接口中的属性,默认都被 public static final 修饰(必须初始化)
-
接口不能继承其他类,但可以继承多个别的接口
接口实现 和 类继承
- 继承:解决代码的复用性和可维护性,Java中只能单继承,必须满足 is - a 关系
- 接口:设计好各种规范(方法),Java中可多实现,只需要满足 like - a 关系
-
内部类
类的内部又完整的嵌套了另一个类结构
被嵌套的类称为 内部类(inner class),
嵌套其他类的类称为 外部类(outer class)
内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
class Outer{ // 外部类 class Inner{ // 内部类 } } class Other{ // 外部其他类 }
-
4大内部类
- 定义在局部位置上
- 局部内部类
- 匿名内部类
- 定义在成员位置上
- 成员内部类
- 静态内部类
- 定义在局部位置上
-
局部内部类
局部内部类是定义在外部类的局部位置(方法、代码块中)
局部内部类拥有类的所有特性(JDK8不支持main方法定义在局部内部类中,且不能定义静态成员)
- 局部内部类的作用域:仅仅在定义它的方法、代码块中(外部其他类无法访问局部位置)
- 外部其他类、外部类 与 局部内部类的相互访问
- 局部内部类 访问 外部类成员(直接访问,因为属于同类中)
- 局部内部类 访问 外部其他类,直接new
- 外部类 访问 局部内部类,创建局部内部类对象再访问(注意必须在作用域内创建对象)
- 外部其他类 访问 局部内部类(无法访问,因为局部内部类的地位是局部变量)
- 外部类 与 局部内部类 成员重名时,默认遵守就近原则( 外部类.this.成员 来访问外部类成员)
public class MainApp { public static void main(String[] args) { new Outer().m2(); } } //外部类 class Outer{ private int numberN1 = 123; // 成员变量(私有) private String m1(){ // 成员方法(私有) return "this is Outer class :)"; } public void m2(){ double dN2 = 1.0; // 局部变量 // 局部内部类 final class Inner{ public int numberN1 = 321; public String m1(){ return "this is Inner class :)"; } public void test(){ //访问本类中的字段(局部内部类) System.out.println(numberN1); // 321 //访问外部类中的字段 System.out.println(Outer.this.numberN1); // 123 //访问本类中的方法(局部内部类) System.out.println(this.m1()); // this is Inner class :) //访问外部类中的方法 System.out.println(Outer.this.m1()); // this is Outer class :) //访问外部其他类的方法 System.out.println(new Other().m4()); // This is Other class :) } } //外部类中访问局部内部类 Inner inner = new Inner(); inner.test(); } } //外部其他类 class Other{ // 外部其他类 无法访问局部内部类 : Inner类 public String m4(){ return "This is Other class :)"; } }
-
匿名内部类
匿名内部类定义在外部类的局部位置,比如方法中
在代码中是匿名的,但底层编译器会赋予一个名字,但对程序员来说是匿名的
注意:匿名内部类中不能定义静态成员(JDK8)
作用域:处于局部变量的作用域,因此作用域仅在定义它的方法、代码块中(外部其他类无法访问局部位置)
-
使用匿名内部类的基本语法:
语法中包含了2层含义:
- new 类/接口(参数列表) : 表示创建了一个对象
- {类体} : 表示该类拥有的成员变量、成员方法
new 类/接口(参数列表){ 类体 };
-
匿名内部类的本质:编译器创建一个class类继承自目标类,然后创建其实例对象
-
使用匿名内部类创建接口实例
interface A{ int test(); } class MainApp{ public static void main(String[] args) { A a = new A(){ @Override public int test() { return 0; } }; // 使用匿名内部类创建了一个对象 (可以简单的理解为一种语法糖) } }
本质:
interface A{ int test(); } class MainApp{ public static void main(String[] args) { class MainApp$1 implements A{ @Override public int test() { return 0; } } // 编译器创建了一个类继承了接口A A a = new MainApp$1(); // 使用内部类创建了一个对象 } }
-
使用匿名内部类创建类实例
由于匿名内部类的类是由编译器创建的,因此在类体中是无法定义构造器的,创建实例时传入的参数会默认走父类的目标构造器
class A{ public A(String str){ System.out.println(str); } } class MainApp{ public static void main(String[] args) { A a = new A("测试"){}; // 使用匿名内部类创建了一个对象 (可以简单的理解为一种语法糖) } }
本质:
class A{ public A(String str){ System.out.println(str); } } class MainApp{ public static void main(String[] args) { class MainApp$1 extends A{ public MainApp$1(String str) { super(str); } } // 编译器创建了一个类继承了接口A A a = new MainApp$1("测试"); // 使用内部类创建了一个对象 } }
-
-
访问细节
与局部内部类相同,因为地位都是局部位置
- 局部内部类 访问 外部类成员(直接访问,因为属于同类中)
- 局部内部类 访问 外部其他类,直接new
- 外部类 访问 局部内部类,创建局部内部类对象再访问(注意必须在作用域内才能获取到对象)
- 外部其他类 访问 局部内部类(无法访问,因为局部内部类的地位是局部变量)
- 外部类 与 局部内部类 成员重名时,默认遵守就近原则( 外部类.this.成员 来访问外部类成员)
-
匿名内部类常见的使用方式:作为参数传入
public class MainApp{ public static void main(String[] args) { m1(new A() { @Override public int test() { return -1; } }); } public static void m1(A a){ System.out.println(a.test()); // 这里就会使用到动态绑定机制,根据传入对象的不同而得到不同的结果 } } interface A{ int test(); }
-
-
成员内部类
成员内部类是定义在外部类的成员位置,并且没有static修饰
注意:成员内部类中不能定义静态成员(JDK8)
-
本质还是一个类,只不过位置在外部类的成员位置
作用域:整个类体
-
成员内部类 与 外部类其他类、外部类的相互访问
- 成员内部类 访问 外部类,直接访问
- 成员内部类 访问 外部其他类,(非静态成员)创建对象再访问,(静态成员)使用外部其他类名.静态成员名 访问
- 外部类 访问 成员内部类,创建内部类对象再访问
- 外部其他类 访问成员内部类,通过外部类的方法、公开的字段获取成员内部类实例再访问
public class MainApp{ public static void main(String[] args) { Other other = new Other(); other.m5(); } } //外部类 class Outer{ private int numberN1 = 123; // 成员变量(私有) private String m1(){ // 成员方法(私有) return "this is Outer class :)"; } final class Inner{ public int numberN1 = 321; public String m1(){ return "this is Inner class :)"; } public void m2(){ System.out.println(numberN1); // 321 System.out.println(Outer.this.numberN1); // 123 System.out.println(m1()); // this is Inner class :) System.out.println(Outer.this.m1()); // this is Outer class :) System.out.println(Other.i); // 100 System.out.println(new Other().m4()); // This is Other class :) } } //外部类访问成员内部类 public Inner m3(){ Inner inner = new Inner(); System.out.println(inner.numberN1); // 321 System.out.println(this.numberN1); // 123 inner.m2(); return inner; } } //外部其他类 class Other{ public static int i = 100; public String m4(){ return "This is Other class :)"; } //外部其他类访问成员内部类 public void m5(){ Outer.Inner inner = new Outer().m3(); System.out.println(inner.numberN1); // 321 } }
-
-
静态内部类
静态内部类是定义在外部类的成员位置,并且有static修饰
-
相当于静态成员,静态成员能做的事它都能做(比如条件访问修饰符),作用域是整个类
可以访问外部类中所有的静态成员文章来源:https://www.toymoban.com/news/detail-511627.html
-
静态内部类 与 外部类其他类、外部类的相互访问文章来源地址https://www.toymoban.com/news/detail-511627.html
- 静态内部类 访问 外部类(静态成员),直接访问
- 静态内部类 访问 外部其他类
- (非静态成员)创建对象再访问
- (静态成员)使用外部其他类名.静态成员名 访问
- 外部类 访问 静态内部类
- (非静态成员)创建静态内部类对象再访问
- (静态成员)使用静态内部类名.静态成员名 访问
- 外部其他类 访问 静态内部类
- (非静态成员)通过外部类的方法、公开的字段获取成员内部类实例再访问
- (静态成员)使用外部类名.静态内部类名.静态成员名 访问
public class MainApp{ public static void main(String[] args) { Other other = new Other(); other.m5(); } } //外部类 class Outer{ private static int numberN1 = 123; // 成员变量(私有) private static String m1(){ // 成员方法(私有) return "this is Outer class :)"; } static class Inner{ public int numberN1 = 321; public static double d = 21.0; public String m1(){ return "this is Inner class :)"; } public void m2(){ System.out.println(numberN1); // 321 System.out.println(Outer.numberN1); // 123 System.out.println(m1()); // this is Inner class :) System.out.println(Outer.m1()); // this is Outer class :) System.out.println(Other.i); // 100 System.out.println(new Other().m4()); // This is Other class :) } } //外部类访问静态内部类 public Inner m3(){ System.out.println(Inner.d); // 21.0 Inner inner = new Inner(); System.out.println(inner.numberN1); // 321 System.out.println(numberN1); // 123 inner.m2(); return inner; } } //外部其他类 class Other{ public static int i = 100; public String m4(){ return "This is Other class :)"; } //外部其他类访问静态内部类 public void m5(){ System.out.println(Outer.Inner.d); // 21.0 Outer.Inner inner = new Outer().m3(); System.out.println(inner.numberN1); // 321 } }
-
-
到了这里,关于Java-语法基础的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!