两个基本概念:类、对象
三大特性:封装、继承、多态
吸收了C/C++语言的优点,但去掉了其影响程序健壮性的部分(如指针、内存的申请与释放等),提供了一个相对安全的内存管理和访问机制
跨平台性:通过Java语言编写的应用程序在不同的系统平台上都可以运行。“Write once , Run Anywhere”一次编写,处处运行。
原理:只要在需要运行 java 应用程序的操作系统上,先安装一个Java虚拟机 (JVM Java Virtual Machine) 即可。由JVM来负责Java程序在该系统中的运行。因为有了JVM,同一个Java 程序在三个不同的操作系统中都可以执行。这样就实现了Java 程序的跨平台性。
Java开发人员需要安装JDK。如果仅仅是运行Java程序,那么只需要按照JRE。
JDK(Java Development kits):Java开发工具包。
JRE(Java Runtime Environment):Java运行环境。
JVM(Java Virtual Machine):Java虚拟机。
JDK = JRE + 开发工具,,等)
JRE = JVM + 核心类库(常用类:String、日期时间、数学、集合、IO、网络、多线程等)
三步:
1、编辑/编写源代码
要求:源文件必须是.java文件
2、编译
目的:把源文件编译为.class字节码文件(因为JVM只认识字节码)
工具
格式:
javac 源文件名.java
3、运行
工具
格式:
java 类名
java 字节码文件名
要求:可以被运行的类,必须包含main方法
1、字符编码问题
当cmd命令行窗口的字符编码与.java源文件的字符编码不一致,如何解决?
解决方案一:
在Notepad++等编辑器中,修改源文件的字符编码
解决方案二:
在使用javac命令式,可以指定源文件的字符编码
javac -encoding utf-8 Review01.java
2、源文件名与类名一致问题?
(1)源文件名是否必须与类名一致?public呢?
如果这个类不是public,那么源文件名可以和类名不一致。
如果这个类是public,那么要求源文件名必须与类名一致。
我们建议大家,不管是否是public,都与源文件名保持一致,而且一个源文件尽量只写一个类,目的是为了好维护。
(2)一个源文件中是否可以有多个类?public呢?
一个源文件中可以有多个类,编译后会生成多个.class字节码文件。
但是一个源文件只能有一个public的类。
(3)main必须在public的类中吗?
不是。
但是后面写代码时,基本上main习惯上都在public类中。
简单的说,凡是程序员自己命名的部分都可以称为标识符。
即给类、变量、方法、包等命名的字符序列,称为标识符。
1、标识符的命名规则
(1)Java的标识符只能使用26个英文字母大小写,0-9的数字,下划线_,美元符号$
(2)不能使用Java的关键字(包含保留字)和特殊值
(3)数字不能开头
(4)不能包含空格
(5)严格区分大小写
2、标识符的命名规范
(1)见名知意
(2)类名、接口名等:每个单词的首字母都大写,形式:XxxYyyZzz,
例如:HelloWorld,String,System等
(3)变量、方法名等:从第二个单词开始首字母大写,其余字母小写,形式:xxxYyyZzz,
例如:age,name,bookName,main
(4)包名等:每一个单词都小写,单词之间使用点.分割,形式z,
例如:java.lang
(5)常量名等:每一个单词都大写,单词之间使用下划线_分割,形式:XXX_YYY_ZZZ,
例如:MAX_VALUE,PI
1、基本数据类型
8种:整型系列(byte,short,int,long)、浮点型(float,double)、单字符型(char)、布尔型(boolean)
2、引用数据类型
类、接口、数组、枚举…
1、整型系列
(1)byte:字节类型
占内存:1个字节
存储范围:-128~127
(2)short:短整型类型
占内存:2个字节
存储范围:-32768~32767
(3)int:整型
占内存:4个字节
存储范围:-2的31次方 ~ 2的31次方-1
(4)long:整型
占内存:8个字节
存储范围:-2的63次方 ~ 2的63次方-1
注意:如果要表示某个常量数字它是long类型,那么需要在数字后面加L
2、浮点型系列(小数)
(1)float:单精度浮点型
占内存:4个字节
精度:科学记数法的小数点后6~7位
注意:如果要表示某个常量数字是float类型,那么需要在数字后面加F或f
(2)double:双精度浮点型
占内存:8个字节
精度:科学记数法的小数点后15~16位
3、单字符类型
char:字符类型
占内存:2个字节
Java中使用的字符集:Unicode编码集
字符的三种表示方式:
(1)‘一个字符’
例如:‘A’,‘0’,‘尚’
(2)转义字符
n:换行
r:回车
t:Tab键
\:
":”
':
b:删除键Backspace
(3)u字符的Unicode编码值的十六进制型
例如:u5c1a代表’尚’
4、布尔类型
boolean:只能存储true或false
1、请分别用四种类型的进制来表示10,并输出它的结果:(了解)
(1)十进制:正常表示
System.out.println(10);
(2)二进制:0b或0B开头
System.out.println(0B10);
(3)八进制:0开头
System.out.println(010);
(4)十六进制:0x或0X开头
System.out.println(0X10);
2、为什么byte是-128~127?(理解)
1个字节:8位
0000 0001 ~ 0111 111 ==> 1~127
1000 0001 ~ 1111 1111 ==> -127 ~ -1
0000 0000 ==>0
1000 0000 ==> -128(特殊规定)
*解释:*计算机数据的存储(了解)
计算机数据的存储使用二进制补码形式存储,并且最高位是符号位,1是负数,0是正数。
规定:正数的补码与反码、原码一样,称为三码合一;
负数的补码与反码、原码不一样:
负数的原码:把十进制转为二进制,然后最高位设置为1
负数的反码:在原码的基础上,最高位不变,其余位取反(0变1,1变0)
负数的补码:反码+1
例如:byte类型(1个字节,8位)
25 ==> 原码 0001 1001 ==> 反码 0001 1001 -->补码 0001 1001
-25 ==>原码 1001 1001 ==> 反码1110 0110 ==>补码 1110 0111
底层是用加法代替减法:-128==》-127-1==》-127+(-1)
-127- -1 ==> -127 + 1
1、自动类型转换
(1)当把存储范围小的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围大的变量时,
byte->short->int->long->float->double
char->
int i = 'A';//char自动升级为int
double d = 10;//int自动升级为double
(2)当存储范围小的数据类型与存储范围大的数据类型一起混合运算时,会按照其中最大的类型运算
int i = 1;
byte b = 1;
double d = 1.0;double sum = i + b + d;//混合运算,升级为double
(3)当byte,short,char数据类型进行算术运算时,按照int类型处理
byte b1 = 1;
byte b2 = 2;
byte b3 = (byte)(b1 + b2);//b1 + b2自动升级为intchar c1 = '0';
char c2 = 'A';
System.out.println(c1 + c2);//113
(4)boolean类型不参与
2、强制类型转换
(1)当把存储范围大的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围小的变量时,需要强制类型转换
double->float->long->int->short->byte
->char
提示:有风险,可能会损失精度或溢出
double d = 1.2;
int num = (int)d;//损失精度int i = 200;
byte b = (byte)i;//溢出
(2)boolean类型不参与
(3)当某个值想要提升数据类型时,也可以使用强制类型转换
int i = 1;
int j = 2;
double shang = (double)i/j;
提示:这个情况的强制类型转换是没有风险的。
1、任意数据类型的数据与String类型进行“+”运算时,结果一定是String类型
System.out.println("" + 1 + 2);//12
2、但是String类型不能通过强制类型()转换,转为其他的类型
String str = "123";
int num = (int)str;//错误的
1、按照操作数个数的分类:
(1)一元运算符:操作数只有一个
例如:正号(+),负号(-),自增(++),自减(–),逻辑非(!),按位取反(~)
(2)二元运算符:操作数有两个
例如:加(+),减(-),乘(*),除(/),模(%)
大于(>),小于(<),大于等于(>=),小于等于(<=),等于(==),不等于(!=)
赋值(=,+=,-=,*=,/=,%=,>>=,<<=。。。)
逻辑与(&),逻辑或(|),逻辑异或(^),短路与(&&),短路或(||)
左移(<<),右移(>>),无符号右移(>>>),按位与(&),按位或(|),按位异或(^)
(3)三元运算符:操作数三个
例如: ? :
2、Java基本数据类型的运算符:
(1)算术运算符
(2)赋值运算符
(3)比较运算符
(4)逻辑运算符
(5)条件运算符
(6)位运算符(难)
加法:+
减法:-
乘法:*
除法:/
注意:整数与整数相除,只保留整数部分
取模:% 取余
注意:取模结果的正负号只看被模数
正号:+
负号:-
自增:++
自减:–
原则:自增与自减
++/–在前的,就先自增/自减,后取值
++/–在后的,就先取值,后自增/自减
整个表达式的扫描,是从左往右扫描,如果后面的先计算的,那么前面的就暂时先放到“操作数栈”中
代码示例:
int i = 1;
i++;//i=2int j = 1;
++j;//j=2int a = 1;
int b = a++;//(1)先取a的值“1”放操作数栈(2)a再自增,a=2(3)再把操作数栈中的"1"赋值给b,b=1int m = 1;
int n = ++m;//(1)m先自增,m=2(2)再取m的值“2”放操作数栈(3)再把操作数栈中的"2"赋值给n,n=1int i = 1;
int j = i++ + ++i * i++;
/*
从左往右加载
(1)先算i++
①取i的值“1”放操作数栈
②i再自增 i=2
(2)再算++i
①i先自增 i=3
②再取i的值“3”放操作数栈
(3)再算i++
①取i的值“3”放操作数栈
②i再自增 i=4
(4)先算乘法
用操作数栈中3 * 3 = 9,并把9压会操作数栈
(5)再算求和
用操作数栈中的 1 + 9 = 10
(6)最后算赋值
j = 10
*/
基本赋值运算符:=
扩展赋值运算符:+=,-=,*=,/=,%=…
注意:所有的赋值运算符的=左边一定是一个变量
扩展赋值运算符=右边的计算结果的类型如果比左边的大的话会强制类型转换,所以结果可能有风险。
扩展赋值运算符的计算:(1)赋值最后算(2)加载数据的顺序是把左边的变量的值先加载,再去与右边的表达式进行计算
int i = 1;
int j = 5;
j *= i++ + j++;//j = j *(i++ + j++);
/*
(1)先加载j的值“5”
(2)在计算i++
①先加载i的值“1”
②再i自增,i=2
(3)再计算j++
①先加载j的值"5"
②再j自增,j=6
(4)算 加法
i + 5 = 6
(5)算乘法
5 * 6 = 30
(6)赋值
j = 30
*/
逻辑运算符的操作数必须是布尔值,结果也是布尔值
逻辑与:&
运算规则:只有左右两边都为true,结果才为true。
例如:true & true 结果为true
false & true 结果为false
true & false 结果为false
false & false 结果为false
逻辑或:|
运算规则:只要左右两边有一个为true,结果就为true。
例如:true | true 结果为true
false | true 结果为true
true | false 结果为true
false | false 结果为false
逻辑异或:^
运算规则:只有左右两边不同,结果才为true。
例如:true ^ true 结果为false
false ^ true 结果为true
true ^ false 结果为true
false ^ false 结果为false
逻辑非:!
运算规则:布尔值取反
例如:!true 为false
!false 为true
短路与:&&
运算规则:只有左右两边都为true,结果才为true。
例如:true & true 结果为true
true & false 结果为false
false & ? 结果就为false
它和逻辑与不同的是当&&左边为false时,右边就不看了。
短路或:||
运算规则:只要左右两边有一个为true,结果就为true。
例如:true | ? 结果为treu
false | true 结果为true
false | false 结果为false
它和逻辑或不同的是当||左边为true时,右边就不看了。
开发中一般用短路与和短路或比较多
面试题:&& 和 &的区别?
&&当左边为false,右边不计算
&不管左边是true还是false,右边都要计算
? :
语法格式:
条件表达式 ? 结果表达式1 : 结果表达式2
运算规则:
整个表达式的结果:当条件表达式为true时,就取结果表达式1的值,否则就取结果表达式2的值
代码示例:
(1)boolean类型
boolean marry = true;
System.out.println(marry? "已婚" : "未婚");(2)求最值
int i = 3;
int j = 5;
int max = i>=j ? i : j;
//当i>=j时,max就赋值为i的值,否则就赋值为j的值
左移:<<
运算规则:左移几位就相当于乘以2的几次方
右移:>>
运算规则:右移几位就相当于除以2的几次方
无符号右移:>>>
运算规则:往右移动后,左边空出来的位直接补0,不看符号位
按位与:&
运算规则:
1 & 1 结果为1
1 & 0 结果为0
0 & 1 结果为0
0 & 0 结果为0
按位或:|
运算规则:
1 | 1 结果为1
1 | 0 结果为1
0 | 1 结果为1
0 & 0 结果为0
按位异或:^
运算规则:
1 ^ 1 结果为0
1 ^ 0 结果为1
0 ^ 1 结果为1
0 ^ 0 结果为0
按位取反:~
运算规则:~0就是1
~1就是0
如何区分&,|,^是逻辑运算符还是位运算符?
如果操作数是boolean类型,就是逻辑运算符,如果操作数是整数,那么就位运算符。
提示说明:
(1)表达式不要太复杂
(2)先算的使用()
语法格式:
switch(表达式){case 常量值1:语句块1;【break;】case 常量值2:语句块2;【break;】 。。。【default:语句块n+1;【break;】】
}
执行过程:
(1)入口
①当switch(表达式)的值与case后面的某个常量值匹配,就从这个case进入;
②当switch(表达式)的值与case后面的所有常量值都不匹配,寻找default分支进入;
(2)一旦从“入口”进入switch,就会顺序往下执行,直到遇到“出口”
(3)出口
①自然出口:遇到了switch的结束}
②中断出口:遇到了break等
注意:
(1)switch(表达式)的值的类型,只能是:4种基本数据类型(byte,short,int,char),两种引用数据类型(枚举、String)
(2)case后面必须是常量值,而且不能重复
示例代码:
int month = 4;
switch(month){case 3:case 4:case 5:System.out.println("春季");break;case 6:case 7:case 8:System.out.println("夏季");break;case 9:case 10:case 11:System.out.println("秋季");break;case 12:case 1:case 2:System.out.println("冬季");break;default:System.out.println("输入有误!");
}
初始化的目的:(1)确定数组的长度(2)为元素赋值
两种初始化方式:
1、动态初始化
语法格式:
//指定数组长度
数组名 = new 元素的数据类型[长度];//为元素赋值
数组名[下标] = 值; //这个值可以是个常量值,也可以是个表达式的计算结果,也可以是键盘输入的//如果每个元素的赋值比较有规律,通常使用for循环赋值
for(int i=0; i<长度; i++){数组名[下标] = 值;
}
问:如果只指定数组长度,没有为元素手动赋值,那么元素有值吗?
有默认值
(1)基本数据类型
byte,short,int,long:0
float,double:0.0
char:u0000
boolean:false
(2)引用数据类型
统统都是null
2、静态初始化
语法格式:
数组名 = new 元素的数据类型[]{值列表};//int[] arr = new int[5]{1,2,3,4,5};//错误的//更简洁
//当声明与静态初始化一起完成时,可以简化
元素的数据类型[] 数组名 = {值列表};
适用场合:
当数组的元素是已知的有限个时,可以使用静态初始化。
示例代码:
String[] weeks = {"monday","tuesday","wednesday","thursday","friday","saturday","sunday"};int[] daysOfMonths = {31,28,31,30,31,30,31,31,30,31,30,31};char[] letters = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
元素是基本数据类型的一维数组内存分析:
int[] arr = {1,2,3,4,5};
int[] arr = new int[5];
for(int i=0; i<arr.length; i++){arr[i] = i+1;
}
int[] arr = {1,2,3,4,5,6,7,8,9};//(1)计算要交换的次数: 次数 = arr.length/2
//(2)首尾交换
for(int i=0; i<arr.length/2; i++){//循环的次数就是交换的次数//首 与 尾交换int temp = arr[i];arr[i] = arr[arr.length-1-i];arr[arr.length-1-i] = temp;
}//(3)遍历显示
for(int i=0; i<arr.length; i++){System.out.println(arr[i]);
}
二维数组的标记:[][]
1、二维数组的声明
//推荐元素的数据类型[][] 二维数组的名称;//不推荐元素的数据类型 二维数组名[][];//不推荐元素的数据类型[] 二维数组名[];
面试:
int[] x, y[];
//x是一维数组,y是二维数组
2、二维数组的初始化
(1)静态初始化
二维数组名 = new 元素的数据类型[][]{{第一行的值列表}, {第二行的值列表},...{第n行的值列表}};//如果声明与静态初始化一起完成
元素的数据类型[][] 二维数组的名称 = {{第一行的值列表}, {第二行的值列表},...{第n行的值列表}};
(2)动态初始化(不规则:每一行的列数可能不一样)
//(1)先确定总行数
二维数组名 = new 元素的数据类型[总行数][];//(2)再确定每一行的列数
二维数组名[行下标] = new 元素的数据类型[该行的总列数];//(3)再为元素赋值
二维数组名[行下标][列下标] = 值;
(3)动态初始化(规则:每一行的列数是相同的)
//(1)确定行数和列数
二维数组名 = new 元素的数据类型[总行数][每一行的列数];//(2)再为元素赋值
二维数组名[行下标][列下标] = 值;
1、如何声明属性?
【修饰符】 class 类名{【修饰符】 数据类型 属性名; //属性有默认值【修饰符】 数据类型 属性名 = 值; //属性有初始值
}
说明:属性的类型可以是Java的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等)
总结:Java的数据类型
(1)基本数据类型
byte,short,int,long,float,double,char,boolean
(2)引用数据类型
①类:
例如:String、Student、Circle、System、Scanner、Math…
②接口:
③数组:
例如:int[],String[],char[],int[][]
int[] arr = new int[5];
这里把int[]看成数组类型,是一种引用数据类型,右边赋值的是一个数组的对象元素的数据类型:int
数组的数据类型:int[]
2、属性的特点
(1)属性有默认值
基本数据类型:
byte,short,int,long:0
float,double:0.0
char:u0000
boolean:false
引用数据类型:
null
(2)每一个对象的属性是独立,互不干扰
3、对象属性的内存图
class Student{String name;char gender = '男';//显式赋值
}class TestStudent{public static void main(String[] args){Student s1 = new Student();System.out.println("姓名:" + s1.name);//nullSystem.out.println("性别:" + s1.gender);//男s1.name = "小薇";s1.gender = '女';System.out.println("姓名:" + s1.name);//小薇System.out.println("性别:" + s1.gender);//女Student s2 = new Student();System.out.println("姓名:" + s2.name);//nullSystem.out.println("性别:" + s2.gender);//男}
}
Java中方法的参数传递机制:值传递
(1)形参是基本数据类型时,实参给形参传递数据值,是copy的形式,形参对值的修改不影响实参。
(2)形参是引用数据类型时,实参给形参传递地址值,形参对对象的属性的修改,会影响实参对象的属性值,因为此时形参和实参就是指向同一个对象。
示例代码:
class Test{public static void swap(int a, int b){int temp = a;a = b;b = temp;}public static void main(String[] args){int x = 1;int y = 2;swap(x,y);//调用完之后,x与y的值不变}
}
示例代码:
class Test{public static void change(MyData my){my.num *= 2;}public static void main(String[] args){MyData m = new MyData();m.num = 1;change(m);//调用完之后,m对象的num属性值就变为2}
}class MyData{int num;
}
陷阱1:
/*
陷阱1:在方法中,形参 = 新new对象,那么就和实参无关了
*/
class Test{public static void change(MyData my){my = new MyData();//形参指向了新对象my.num *= 2;}public static void main(String[] args){MyData m = new MyData();m.num = 1;change(m);//调用完之后,m对象的num属性值仍然为1}
}class MyData{int num;
}
陷阱2:见字符串和包装类部分
一维数组:
1、元素是基本数据类型
2、元素是引用数据类型,也称为对象数组,即数组的元素是对象
注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。
示例代码:
class MyDate{int year;int month;int day;
}
class Test{public static void main(String[] args){MyDate[] arr = new MyDate[3];//创建数组对象本身,指定数组的长度for(int i=0; i<arr.length; i++){arr[i] = new MyDate();//每一个元素要创建对象arr[i].year = 1990 + i;arr[i].month = 1 + i;arr[i].day = 1 + i;}}
}
对象数组的内存图:
面向对象的基本特征:
1、封装
2、继承
3、多态
1、好处:
(1)隐藏实现细节,方便使用者使用
(2)安全,可以控制可见范围
2、如何实现封装?
通过权限修饰符
面试题:请按照可见范围从小到大(从大到小)列出权限修饰符?
修饰符 | 本类 | 本包 | 其他包的子类 | 任意位置 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
权限修饰符可以修饰什么?
类(类、接口等)、属性、方法、构造器、内部类
类(外部类):public和缺省
属性:4种
方法:4种
构造器:4种
内部类:4种
3、构造器的特点:
(1)所有的类都有构造器
(2)如果一个类没有显式/明确的声明一个构造器,那么编译器将会自动添加一个默认的无参构造
(3)如果一个类显式/明确的声明了构造器,那么编译器将不再自动添加默认的无参构造,如果需要,那么就需要手动添加
(4)构造器的名称必须与类名相同
(5)构造器没有返回值类型
(6)构造器可以重载
示例代码:
public class Circle{private double radius;public Circle(){}public Circle(double r){radius = r;//为radius赋值}
}
1、this关键字:
意思:当前对象
(1)如果出现在构造器中:表示正在创建的对象
(2)如果出现在成员方法中:表示正在调用这个方法的对象
2、this的用法:
(1)this()或this(实参列表)
this()表示调用本类的无参构造
this(实参列表)表示调用本类的有参构造
this()或this(实参列表)要么没有,要么必须出现在构造器的首行
3、成员变量与局部变量的区别?
这里只讨论实例变量(关于类变量见static部分)
(1)声明的位置不同
成员变量:类中方法外
局部变量:方法中或代码中
①方法的形参列表
②方法体中局部变量
③代码块中的局部变量
(2)运行时在内存中的存储位置不同
成员变量:堆
局部变量:栈
基本数据类型的变量在栈中,引用数据类型的变量在堆中:不准确
(3)修饰符
成员变量:有很多修饰符,例如:权限修饰符
局部变量:不能加权限修饰符,唯一的能加的是final
(4)初始化
成员变量:有默认值
局部变量:没有默认值,必须手动初始化
(5)生命周期
成员变量:随着对象的创建而创建,随着对象被回收而消亡,即与对象同生共死。每一个对象都是独立的。
局部变量:方法调用时才分配,方法运行结束就没有了。每一次方法调用,都是独立的
1、包的作用:
(1)可以避免类重名
有了包之后,类的全名称就变为:包.类名
(2)分类组织管理众多的类
例如:java.lang包,java.util包,java.io包…
(3)可以控制某些类型或成员的可见范围
如果某个类型或者成员的权限修饰缺省的话,那么就仅限于本包使用
3、包的命名规范和习惯:
(1)所有单词都小写,每一个单词之间使用.分割
(2)习惯用公司的域名倒置
例如:;
建议大家取包名时不要使用“"包
4、使用其他包的类:
前提:被使用的类或成员的权限修饰符是>缺省的
(1)使用类型的全名称
例如:java.util.Scanner input = new java.util.Scanner(System.in);
(2)使用import 语句之后,代码中使用简名称
5、import语句
import 包.类名;
import 包.*;
注意:当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。
一个使用全名称,一个使用简名称
示例代码:
package st;import java.util.Scanner;public class Test{public static void main(String[] args){Scanner input = new Scanner(System.in);}
}
1、eclipse管理项目和代码的结构
workspace --> project --> 包–>类…
一个工作空间可以有多个项目。
1、为什么要继承?继承的好处?(理解)
(1)代码的复用
(2)代码的扩展
2、如何实现继承?
语法格式:
【修饰符】 class 子类 extends 父类{}
3、继承的特点
(1)子类会继承父类的所有特征(属性、方法)
可以直接使用父类public、protected方法,就像是自己的方法
但是,私有的在子类中是不能直接使用的
(2)子类不会继承父类的构造器
因为,父类的构造器是用于创建父类的对象的
(3)子类的构造器中又必须去调用父类的构造器
在创建子类对象的同时,为从父类继承的属性进行初始化用,可以借助父类的构造器中的代码为属性赋值。
(4)Java只支持单继承:一个子类只能有一个“直接”父类
(5)Java又支持多层继承:父类还可以有父类,特征会代代相传
(6)一个父类可以同时拥有很多个子类
super关键字:引用父类的,找父类的xx
用法:
(1)super.属性
当子类声明了和父类同名的成员变量时,那么如果要表示某个成员变量是父类的,那么可以加“super.”
(2)super.方法
当子类重写了父类的方法,又需要在子类中调用父类被重写的方法,可以使用"super."
(3)super()或super(实参列表)
super():表示调用父类的无参构造
super(实参列表):表示调用父类的有参构造
注意:
(1)如果要写super()或super(实参列表),必须写在子类构造器的首行
(2)如果子类的构造器中没有写:super()或super(实参列表),那么默认会有 super()
(3)如果父类没有无参构造,那么在子类的构造器的首行“必须”写super(实参列表)
1、方法的重写(Override)
当子类继承了父类的方法时,又觉得父类的方法体的实现不适合于子类,那么子类可以选择进行重写。
2、方法的重写的要求
(1)方法名:必须相同
(2)形参列表:必须相同
(3)修饰符
权限修饰符: >=
(4)返回值类型
如果是基本数据类型和void:必须相同
如果是引用数据类型:<=
在Java中我们认为,在概念范围上:子类 <父类
3、重载(Overload)与重写(Override)的区别
重载(Overload):在同一个类中,方法名相同,形参列表不同,和返回值类型无关的两个或多个方法。
(参数必须不相同,在此基础上返回值可以不同可以相同,但如果参数相同,返回值相不相同都会报错)
重写(Override):在父子类之间。对方法签名的要求见上面。
特殊的重载:
public class TestOverload {public static void main(String[] args) {B b = new B();//b对象可以调用几个a方法b.a();b.a("");//从b对象同时拥有两个方法名相同,形参不同的角度来说,算是重载}
}
class A{public void a(){//...}
}
class B extends A{public void a(String str){}
}
1、语法格式
【修饰符】 class 类名{{非静态代码块}
}
2、作用
目的:在创建的过程中,为对象属性赋值,协助完成实例初始化的过程
3、什么时候执行?
(1)每次创建对象时都会执行
(2)优先于构造器执行
1、概念描述
实例初始化过程:实例对象创建的过程
实例初始化方法:实例对象创建时要执行的方法
实例初始化方法的由来:它是有编译器编译生成的
实例初始化方法的形式:()或(形参列表)
实例初始化方法的构成:
①属性的显式赋值代码
②非静态代码块的代码
③构造器的代码
其中
①和②按顺序执行,从上往下
③在①和②的后面
因此一个类有几个构造器,就有几个实例初始化方法。
2、单个类实例初始化方法
示例代码:
class Demo{{System.out.println("非静态代码块1");}private String str = assign();//调用方法,来为str进行显式赋值public Demo(){System.out.println("无参构造");}public Demo(String str){this.str = str;System.out.println("有参构造");}{System.out.println("非静态代码块2");}public String assign(){System.out.println("assign方法");return "hello";}
}
图解:
3、父子类的实例初始化
注意:
(1)原先super()和super(实参列表)说是调用父类的构造器,现在就要纠正为调用父类的实例初始化方法了
(2)原先super()和super(实参列表)说是必须在子类构造器的首行,现在要纠正为必须在子类实例初始化方法的首行
结论:
(1)执行顺序是先父类实例初始化方法,再子类实例初始化方法
(2)如果子类重写了方法,通过子类对象调用,一定是执行重写过的方法
示例代码:
class Ba{private String str = assign();{System.out.println("(1)父类的非静态代码块");}public Ba(){System.out.println("(2)父类的无参构造");}public String assign(){System.out.println("(3)父类的assign()");return "ba";}
}
class Er extends Ba{private String str = assign();{System.out.println("(4)子类的非静态代码块");}public Er(){//super() ==>调用父类的实例初始化方法,而且它在子类实例初始化方法的首行System.out.println("(5)子类的无参构造");}public String assign(){System.out.println("(6)子类的assign()");return "er";}
}
class Test{public static void main(String[] args){new Er();//612645}
}
图解:
1、多态:
语法格式:
父类 引用/变量 = 子类的对象;
2、前提:
(1)继承
(2)方法的重写
(3)多态引用
3、现象:
编译时看左边/“父类”,运行时看右边/“子类”。
编译时,因为按父类编译,那么只能父类有的方法,子类扩展的方法是无法调用的;
执行时一定是运行子类重写的过的方法体。
示例代码:
class Person{public void eat(){System.out.println("吃饭");}public void walk(){System.out.println("走路");}
}
class Woman extends Person{public void eat(){System.out.println("细嚼慢咽的吃饭");}public void walk(){System.out.println("婀娜多姿走路");}public void shop(){System.out.println("买买买...");}
}
class Man extends Person{public void eat(){System.out.println("狼吞虎咽的吃饭");}public void walk(){System.out.println("大摇大摆的走路");}public void smoke(){System.out.println("吞云吐雾");}
}
class Test{public static void main(String[] args){Person p = new Woman();//多态引用p.eat();//执行子类重写p.walk();//执行子类重写//p.shop();//无法调用}
}
4、应用:
(1)多态参数:形参是父类,实参是子类对象
(2)多态数组:数组元素类型是父类,元素存储的是子类对象
示例代码:多态参数
class Test{public static void main(String[] args){test(new Woman());//实参是子类对象test(new Man());//实参是子类对象}public static void test(Person p){//形参是父类类型p.eat();p.walk();}
}
示例代码:多态数组
class Test{public static void main(String[] args){Person[] arr = new Person[2];//多态数组arr[0] = new Woman();arr[1] = new Man();for(int i=0; i<arr.length; i++){all[i].eat();all[i].walk();}}
}
5、向上转型与向下转型:父子类之间的转换
(1)向上转型:自动类型转换
当把子类的对象赋值给父类的变量时(即多态引用时),在编译时,这个对象就向上转型为父类。此时就看不见子类“特有、扩展”的方法。
(2)向下转型:强制转换。有风险,可能会报ClassCastException异常。
当需要把父类的变量赋值给一个子类的变量时,就需要向下转型。
要想转型成功,必须保证该变量中保存的对象的运行时类型是<=强转的类型
示例代码:
class Person{//方法代码省略...
}
class Woman extends Person{//方法代码省略...
}
class ChineseWoman extends Woman{//方法代码省略...
}
public class Test{public static void main(String[] args){//向上转型Person p1 = new Woman();//向下转型Woman m = (Woman)p1; //p1变量中实际存储的对象就是Woman类型,和强转的Woman类型一样//向上转型Person p2 = new ChineseWoman();//向下转型Woman w2 = (Woman) p2; //p2变量中实际存储的对象是ChineseWoman类型,强制的类型是Woman,ChineseWoman<Woman类型 }}
6、instanceof
表达式语法格式:
对象/变量 instanceof 类型
运算结果:true 或 false
作用:
用来判断这个对象是否属于这个类型,或者说,是否是这个类型的对象或这个类型子类的对象
示例代码:
class Person{//方法代码省略...
}
class Woman extends Person{//方法代码省略...
}
class ChineseWoman extends Woman{//方法代码省略...
}
public class Test{public static void main(String[] args){Person p = new Person();Woman w = new Woman();ChineseWoman c = new ChineseWoman();if(p instanceof Woman){//false}if(w instanceof Woman){//true}if(c instanceof Woman){//true}}}
修饰符的学习围绕三个问题:
(1)单词的意思
(2)可以修饰什么?
(3)用它修饰后有什么不同?
final:最终的
用法:
(1)修饰类(包括外部类、内部类类)
表示这个类不能被继承,没有子类
(2)修饰方法
表示这个方法不能被重写
(3)修饰变量(成员变量(类变量、实例变量),局部变量)
表示这个变量的值不能被修改
注意:如果某个成员变量用final修饰后,也得手动赋值,而且这个值一旦赋完,就不能修改了,即没有set方法
native:本地的,原生的
用法:
只能修饰方法
表示这个方法的方法体代码不是用Java语言实现的。
但是对于Java程序员来说,可以当做Java的方法一样去正常调用它,或者子类重写它。
JVM内存的管理:
方法区:类的信息、常量、静态变量、动态编译生成的字节码信息
虚拟机栈:Java语言实现的方法的局部变量
本地方法栈:非Java语言实现的方法的局部变量,即native方法执行时的内存区域
堆:new出来的对象
程序计数器:记录每一个线程目前执行到哪一句指令
static:静态的
用法:
1、成员方法:我们一般称为静态方法或类方法
(1)不能被重写
(2)被使用
本类中:其他方法中可以直接使用它
其他类中:可以使用“类名.方法"进行调用,也可以使用"对象名.方法",推荐使用“类名.方法"
(3)在静态方法中,我们不能出现:this,super,非静态的成员
2、成员变量:我们一般称为静态变量或类变量
(1)静态变量的值是该类所有对象共享的
(2)静态变量存储在方法区
(3)静态变量对应的get/set也是静态的
(4)静态变量与局部变量同名时,就可以使用“类名.静态变量"进行区分
3、内部类:
4、代码块:静态代码块
5、静态导入(JDK1.5引入)
没有静态导入
package com.atguigu.utils;public class Utils{public static final int MAX_VALUE = 1000;public static void test(){//...}
}
package st;import com.atguigu.utils;public class Test{public static void main(String[] args){System.out.println(Utils.MAX_VALUE);st();}
}
使用静态导入
package com.atguigu.utils;public class Utils{public static final int MAX_VALUE = 1000;public static void test(){//...}
}
package st;import static com.atguigu.utils.Utils.*;public class Test{public static void main(String[] args){System.out.println(MAX_VALUE);test();}
}
1、语法格式:
【修饰符】 class 类名{static{静态代码块;}
}
2、作用:
协助完成类初始化,可以为类变量赋值。
3、类初始化()
类的初始化有:
①静态变量的显式赋值代码
②静态代码块中代码
其中①和②按顺序执行
注意:类初始化方法,一个类只有一个
4、类的初始化的执行特点:
(1)每一个类的()只执行一次
(2)如果一个子类在初始化时,发现父类也没有初始化,会先初始化父类
(3)如果既要类初始化又要实例化初始化,那么一定是先完成类初始化的
1、变量按照数据类型分:
(1)基本数据类型的变量,里面存储数据值
(2)引用数据类型的变量,里面存储对象的地址值
int a = 10;//a中存储的是数据值Student stu = new Student();//stu存储的是对象的地址值
int[] arr = new int[5];//arr存储的是数组对象的地址值
String str = "hello";//str存储的是"hello"对象的地址值
2、变量按照声明的位置不同:
(1)成员变量
(2)局部变量
3、成员变量与局部变量的区别
(1)声明的位置不同
成员变量:类中方法外
局部变量:①方法的()中,即形参 ②方法体的{}的局部变量③代码块{}中
(2)存储的位置不同
成员变量:
如果是静态变量(类变量),在方法区中
如果是非静态的变量(实例变量),在堆中
局部变量:栈
(3)修饰符不同
成员变量:4种权限修饰符、static、final。。。。
局部变量:只有final
(4)生命周期
成员变量:
如果是静态变量(类变量),和类相同
如果是非静态的变量(实例变量),和所属的对象相同,每一个对象是独立
局部变量:每次执行都是新的
(5)作用域
成员变量:
如果是静态变量(类变量),在本类中随便用,在其他类中使用“类名.静态变量"
如果是非静态的变量(实例变量),在本类中只能在非静态成员中使用,在其他类中使用“对象名.非静态的变量"
局部变量:有作用域
1、java.lang.Object类是类层次结构的根父类。包括数组对象。
(1)Object类中声明的所有的方法都会被继承到子类中,那么即所有的对象,都拥有Object类中的方法
(2)每一个对象的创建,最终都会调用到Object实例初始化方法()
(3)Object类型变量、形参、数组,可以存储任意类型的对象
2、Object类的常用方法
(1)public String toString():
①默认情况下,返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
②通常是建议重写,如果在eclipse中,可以用Alt +Shift + S–>Generate toString()
③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
(2)public final Class<?> getClass():获取对象的运行时类型
(3)protected void finalize():当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法。而且这个方法只会被调用一次。子类可以选择重写。
(4)public int hashCode():返回每个对象的hash值。
规定:①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
②如果两个对象的hash值是相同的,那么这两个对象不一定相等。
主要用于后面当对象存储到哈希表等容中时,为了提高性能用的。
(5)public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”
①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
②我们可以选择重写,重写有些要求:
A:如果重写equals,那么一定要一起重写hashCode()方法,因为规定:
a:如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;
b:如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;
c:如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false
B:如果重写equals,那么一定要遵循如下几个原则:
a:自反性:x.equals(x)返回true
b:传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true
c:一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致
d:对称性:x.equals(y)与y.equals(x)结果应该一样
e:非空对象与null的equals一定是false
(6)protected native Object clone() throws CloneNotSupportedException;
在Java中本方法为native,即不是在Java中实现的。
但我们可以根据这个方法引出深拷贝和浅拷贝两个概念
值得注意的是,拷贝对象时,对象中的String类型属性和基本类型属性一样直接赋值即可,因为对象中有String属性的话,new对象会自动new 一个string ,所以把String当作基本类型就好
Java实现深拷贝
1、什么时候会用到抽象方法和抽象类?
当声明父类的时候,在父类中某些方法的方法体的实现不能确定,只能由子类决定。但是父类中又要体现子类的共同的特征,即它要包含这个方法,为了统一管理各种子类的对象,即为了多态的应用。
那么此时,就可以选择把这样的方法声明为抽象方法。如果一个类包含了抽象方法,那么这个类就必须是个抽象类。
2、抽象类的语法格式
【权限修饰符】 abstract class 类名{}
【权限修饰符】 abstract class 类名 extends 父类{}
3、抽象方法的语法格式
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
抽象方法没有方法体
4、抽象类的特点
(1)抽象类不能直接实例化,即不能直接new对象
(2)抽象类就是用来被继承的,那么子类继承了抽象类后,必须重写所有的抽象方法,否则这个子类也得是抽象类
(3)抽象类也有构造器,这个构造的作用不是创建抽象类自己的对象用的,给子类在实例化过程中调用;
(4)抽象类也可以没有抽象方法,那么目的是不让你创建对象,让你创建它子类的对象
(5)抽象类的变量与它子类的对象也构成多态引用
5、不能和abstract一起使用的修饰符?
(1)final:和final不能一起修饰方法和类
(2)static:和static不能一起修饰方法
(3)native:和native不能一起修饰方法
(4)private:和private不能一起修饰方法
1、接口的概念
接口是一种标准。注意关注行为标准(即方法)。
面向对象的开发原则中有一条:面向接口编程。
2、接口的声明格式
【修饰符】 interface 接口名{接口的成员列表;
}
3、类实现接口的格式
【修饰符】 class 实现类 implements 父接口们{}【修饰符】 class 实现类 extends 父类 implements 父接口们{}
4、接口继承接口的格式
【修饰符】 interface 接口名 extends 父接口们{接口的成员列表;
}
5、接口的特点
(1)接口不能直接实例化,即不能直接new对象
(2)只能创建接的实现类对象,那么接口与它的实现类对象之间可以构成多态引用。
(3)实现类在实现接口时,必须重写所有抽象的方法,否则这个实现类也得是抽象类。
(4)Java规定类与类之间,只能是单继承,但是Java的类与接口之间是多实现的关系,即一个类可以同时实现多个接口
(5)Java还支持接口与接口之间的多继承。
6、接口的成员
JDK1.8之前:
(1)全局的静态的常量:public static final,这些修饰符可以省略
(2)公共的抽象方法:public abstract,这些修饰符也可以省略
JDK1.8之后:
(3)公共的静态的方法:public static ,这个就不能省略了
接口中的静态方法
public interface Hello {static void sayHello(){System.out.println("Hello World!");}static void main(String[] args) {Hello.sayHello();}
}
interface TestInterface1 {static void sayHello(){System.out.println("TestInterface1 Hello");}
}
interface TestInterface2 extends TestInterface1 {}
public class Test implements TestInterface1,TestInterface2{public static void main(String[] args) {TestInterface2.sayHello();//编译报错}
}
这段代码在JDK8的环境下是能够编译通过并运行的。但是在接口中的静态方法有什么不同呢?
接口中的静态方法不能继承
interface TestInterface1 {static void sayHello(){System.out.println("TestInterface1 Hello");}
}
public class Test implements TestInterface1{public static void main(String[] args) {TestInterface1.sayHello();Test.sayHello();//编译报错}
}
原因:
因为一个类是可以实现多个接口的,如果接口中的静态方法的方法前面相同,就会发生继承冲突。所以索性就从继承这个层面阻断冲突的发生。反过来看由于接口中的字段是可以被继承的,所以实际上接口中的字段是存在继承冲突的。
interface TestInterface1 {String hello="TestInterface1";}
interface TestInterface1 {String hello="TestInterface2";
}
public class Test implements TestInterface1,TestInterface2{public static void main(String[] args) {System.out.println(Test.hello);//这里会报错}
}
总结
Java8中接口中静态字段可以被继承(默认用public static final修饰),静态方法不会继承,只能通过接口名调用。
(4)公共的默认的方法:public default,这个就不能省略了
7、default(默认)方法冲突问题
(1) 当一个实现类同时实现了两个或多个接口,这个多个接口的默认方法的签名相同。
解决方案:
方案一:选择保留其中一个
接口名.super.方法名(【实参列表】);
方案二:完全重写
(2)当一个实现类同时继承父类,又实现接口,父类中有一个方法与接口的默认方法签名相同
解决方案:
方案一:默认方案,保留父类的
方案二:选择保留接口的
接口名.super.方法名(【实参列表】);
方案三:完全重写
8、示例代码
public interface Flyable{long MAX_SPEED = 7900000;void fly();
}
public class Bird implements Flyable{public void fly(){//....}
}
9、常用的接口
(1)java.lang.Comparable接口:自然排序
抽象方法:int compareTo(Object obj)
(2)java.util.Comparator接口:定制排序
抽象方法:int compare(Object obj1 ,Object obj2)
(3)示例代码
如果员工类型,默认顺序,自然顺序是按照编号升序排列,那么就实现Comparable接口
class Employee implements Comparable{private int id;private String name;private double salary;//省略了构造器,get/set,toString@Overridepublic int compareTo(Object obj){return id - ((Employee)obj).id;}
}
如果在后面又发现有新的需求,想要按照薪资排序,那么只能选择用定制排序,实现Comparator接口
-1(<=0)表示不调换顺序,1(>0)表示需要调换
class SalaryComparator implements Comparator{public int compare(Object o1, Object o2){Employee e1 = (Employee)o1;Employee e2 = (Employee)o2;Salary() > e2.getSalary()){return 1;}else Salary() < e2.getSalary()){return -1;}return 0;}
}
1、内部类的概念
声明在另外一个类里面的类就是内部类。
2、内部类的4种形式
(1)静态内部类
(2)非静态成员内部类
(3)有名字的局部内部类
(4)匿名内部类
1、语法格式:
//在匿名子类中调用父类的无参构造
new 父类(){内部类的成员列表
}//在匿名子类中调用父类的有参构造
new 父类(实参列表){内部类的成员列表
}//接口没有构造器,那么这里表示匿名子类调用自己的无参构造,调用默认父类Object的无参构造
new 父接口名(){}
2、匿名内部类、匿名对象的区别?
System.out.println(new Student("张三"));//匿名对象Student stu = new Student("张三");//这个对象有名字,stu//既有匿名内部类,又是一个匿名的对象
new Object(){public void test(){.....}
}.test();//这个匿名内部类的对象,使用obj这个名字引用它,既对象有名字,但是这个Object的子类没有名字
Object obj = new Object(){public void test(){.....}
};
3、使用的形式
(1)示例代码:继承式
abstract class Father{public abstract void test();
}
class Test{public static void main(String[] args){//用父类与匿名内部类的对象构成多态引用Father f = new Father(){public void test(){System.out.println("用匿名内部类继承了Father这个抽象类,重写了test抽象方法")}};f.test();}
}
(2)示例代码:实现式
interface Flyable{void fly();
}
class Test{public static void main(String[] args){//用父接口与匿名内部类的对象构成了多态引用Flyable f = new Flyable(){public void fly(){System.out.println("用匿名内部类实现了Flyable这个接口,重写了抽象方法");}};f.fly();}
}
(3)示例代码:用匿名内部类的匿名对象直接调用方法
new Object(){public void test(){System.out.println("用匿名内部类的匿名对象直接调用方法")}
}.test();
(4)示例代码:用匿名内部类的匿名对象直接作为实参
Student[] all = new Student[3];
all[0] = new Student("张三",23);
all[1] = new Student("李四",22);
all[2] = new Student("王五",20);//用匿名内部类的匿名对象直接作为实参
//这个匿名内部类实现了Comparator接口
//这个匿名内部类的对象,是定制比较器的对象
Arrays.sort(all, new Comparator(){public int compare(Obeject o1, Object o2){Student s1 = (Student)o1;Student s2 = (Student)o2;Age() - s2.getAge();}
});
1、语法格式
【修饰符】 class 外部类名 【extends 外部类的父类】 【implements 外部类的父接口们】{【其他修饰符】 static class 静态内部类 【extends 静态内部类自己的父类】 【implements 静态内部类的父接口们】{静态内部类的成员列表;}外部类的其他成员列表;
}
2、 使用注意事项
(1)包含成员是否有要求:
可以包含类的所有成员
(2)修饰符要求:
(3)使用外部类的成员上是否有要求
(4)在外部类中使用静态内部类是否有要求
(5)在外部类的外面使用静态内部类是否有要求
(1)如果使用的是静态内部类的静态成员外部类名.静态内部类名.静态成员
(2)如果使用的是静态内部类的非静态成员①先创建静态内部类的对象外部类名.静态内部类名 对象名 = new 外部类名.静态内部类名(【实参列表】);②通过对象调用非静态成员对象名.xxx
(6)字节码文件形式:外部类名$静态内部类名.class
3、示例代码
class Outer{private static int i = 10;static class Inner{public void method(){//...System.out.println(i);//可以}public static void test(){//...System.out.println(i);//可以}}public void outMethod(){Inner in = new Inner();in.method();}public static void outTest(){Inner in = new Inner();in.method();}
}
class Test{public static void main(String[] args){st();Outer.Inner in = new Outer.Inner();in.method();}
}
1、语法格式
【修饰符】 class 外部类名 【extends 外部类的父类】 【implements 外部类的父接口们】{【修饰符】 class 非静态内部类 【extends 非静态内部类自己的父类】 【implements 非静态内部类的父接口们】{非静态内部类的成员列表;}外部类的其他成员列表;
}
2、 使用注意事项
(1)包含成员是否有要求:
不允许出现静态的成员
(2)修饰符要求
权限修饰符:4种
其他修饰符:abstract,final
(3)使用外部类的成员上是否有要求
都可以使用
(4)在外部类中使用非静态内部类是否有要求
在外部类的静态成员中不能使用非静态内部类
(5)在外部类的外面使用非静态内部类是否有要求
//使用非静态内部类的非静态成员
//(1)创建外部类的对象
外部类名 对象名1 = new 外部类名(【实参列表】);//(2)通过外部类的对象去创建或获取非静态内部类的对象
//创建
外部类名.非静态内部类名 对象名2 = 对象名1.new 非静态内部类名(【实参列表】);//获取
外部类名.非静态内部类名 对象名2 = 对象名1.get非静态内部类对象的方法(【实参列表】);//(3)通过非静态内部类调用它的非静态成员
对象名2.xxx
(6)字节码文件形式:外部类名$非静态内部类名.class
3、示例代码
class Outer{private static int i = 10;private int j = 20;class Inner{public void method(){//...System.out.println(i);//可以System.out.println(j);//可以}}public void outMethod(){Inner in = new Inner();in.method();}public static void outTest(){// Inner in = new Inner();//不可以}public Inner getInner(){return new Inner();}
}
class Test{public static void main(String[] args){Outer out = new Outer();Outer.Inner in1 = w Inner(); //创建 hod();Outer.Inner in2 = Inner(); //获取hod();}
}
1、语法格式
【修饰符】 class 外部类名 【extends 外部类的父类】 【implements 外部类的父接口们】{【修饰符】 返回值类型 方法名(【形参列表】){【修饰符】 class 局部内部类 【extends 局部内部类自己的父类】 【implements 局部内部类的父接口们】{局部内部类的成员列表;}} 外部类的其他成员列表;
}
2、 使用注意事项
(1)包含成员是否有要求
不允许出现静态的成员
(2)修饰符要求
权限修饰符:不能
其他修饰符:abstract、final
(3)使用外部类的成员等上是否有要求
①使用外部类的静态成员:随便用
②使用外部类的非静态成员:能不能用要看所在的方法是否是静态的
③使用所在方法的局部变量:必须 final修饰的
(4)在外部类中使用局部内部类是否有要求
有作用域
(5)在外部类的外面使用局部内部类是否有要求
没法使用
(6)字节码文件形式:外部类名$编号局部内部类名.class
3、示例代码
class Outer{private static int i = 10;private int j = 20;public void outMethod(){class Inner{public void method(){//...System.out.println(i);//可以System.out.println(j);//可以}}Inner in = new Inner();in.method();}public static void outTest(){final int k = 30;class Inner{public void method(){//...System.out.println(i);//可以System.out.println(j);//不可以System.out.println(k);//可以}}Inner in = new Inner();in.method();}
}
1、枚举(JDK1.5引入的)
枚举类型的对象是有限、固定的几个常量对象。
2、语法格式
//形式一:枚举类型中只有常量对象列表
【修饰符】 enum 枚举类型名{常量对象列表
}//形式二:枚举类型中只有常量对象列表
【修饰符】 enum 枚举类型名{常量对象列表;其他成员列表;
}
说明:常量对象列表必须在枚举类型的首行
回忆:首行
(1)super()或super(实参列表):必须在子类构造器的首行
(2)this()或this(实参列表):必须在本类构造器的首行
(3)package 包; 声明包的语句必须在源文件.java的代码首行
(4)枚举常量对象列表必须在枚举类型的首行
3、在其他类中如何获取枚举的常量对象
//获取一个常量对象
枚举类型名.常量对象名//获取一个常量对象
枚举类型名.valueOf("常量对象名")//获取所有常量对象
枚举类型名[] all = 枚举类型名.values();
4、枚举类型的特点
(1)枚举类型有一个公共的基本的父类,是java.lang.Enum类型,所以不能再继承别的类型
(2)枚举类型的构造器必须是私有的
(3)枚举类型可以实现接口
interface MyRunnable{void run();
}
enum Gender implements MyRunnable{NAN,NV;public void run(){//...}
}
//或
enum Gender implements MyRunnable{NAN{public void run(){//...}},NV{public void run(){//...}};}
5、父类java.lang.Enum类型
(1)构造器
protected Enum(String name, int ordinal):由编译器自动调用
(2)String name():常量对象名
(3)int ordinal():返回常量对象的序号,第一个的序号是0
(4)String toString():返回常量对象名,如果子类想重写,需要手动
(5)int compareTo(Object obj):按照常量对象的顺序比较
1、注解
它是代码级别的注释
2、标记符号:@
3、系统预定义的三个最基本的注解:
(1)@Override:表示某个方法是重写的方法
它只能用在方法上面,会让编译器对这个方法进行格式检查,是否满足重写的要求
(2)@SuppressWarnings(xx):抑制警告
(3)@Deprecated:表示xx已过时
4、和文档注释相关的注解
(1)文档注释
/**
文档注释
*/
(2)常见的文档注释
@author:作者
@since:从xx版本加入的
@see:另请参考
@param:形参
@return:返回值
@throws或@exception:异常
5、JUnit相关的几个注解
(1)@Test:表示它是一个单元测试方法
这个方法需要是:public void xxx(){}
(2)@Before:表示在每一个单元测试方法之前执行
这个方法需要是:public void xxx(){}
(3)@After:表示在每一个单元测试方法之后执行
这个方法需要是:public void xxx(){}
(4)@BeforeClass:表示在类初始化阶段执行,而且只执行一次
这个方法需要是:public static void xxx(){}
(3)@AfterClass:表示在类的“卸载”阶段执行,而且只执行一次
这个方法需要是:public static void xxx(){}
6、元注解
(1)@Target(xx):用它标记的注解能够用在xx位置
(xx):由ElementType枚举类型的10个常量对象指定,例如:TYPE,METHOD,FIELD等
例如:
@Target(ElementType.TYPE)@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
import static java.lang.annotation.ElementType.*;@Target({TYPE,METHOD,FIELD})
(2)@Retention(xx):用它标记的注解可以滞留到xx阶段
(xx):由RetentionPolicy枚举类型的3个常量对象指定,分别是:SOURCE,CLASS,RUNTIME
唯有RUNTIME阶段的注解才能被反射读取到
例如:
@Retention(RetentionPolicy.RUNTIME)
(3)@Documentd:用它标记的注解可以读取到API中
(4)@Inherited:用它标记的注解可以被子类继承
7、自定义注解
@元注解
【修饰符】 @interface 注解名{}@元注解
【修饰符】 @interface 注解名{配置参数列表
}
配置参数的语法格式:
数据类型 配置参数名();或数据类型 配置参数名() default 默认值;
关于配置参数:
(1)配置参数的类型有要求:
八种基本数据类型、String、枚举、Class类型、注解、它们的数组。
(2)如果自定义注解声明了配置参数,那么在使用这个注解时必须为配置参数赋值,除非它有默认值
@自定义注解名(配置参数名1=值,配置参数名2=值。。。)//如果配置参数类型是数组,那么赋值时,可以用{}表示数组
@自定义注解名(配置参数名1={值},配置参数名2=值。。。)
(3)如果配置参数只有一个,并且名称是value,那么赋值时可以省略value=
(4)如果读取这个注解时,要获取配置参数的值的话,可以当成方法一样来访问
自定义注解对象.配置参数();
例子
public class TestAnnotation {public static void main(String[] args) {//涉及到反射Class clazz=myclass.class;myAnotion my=(myAnotion) Annotation(myAnotion.class);String s=my.value();System.out.println(s);}
}
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)//只能有一个
@interface myAnotion{String value() default "a";
}
@myAnotion("bb")
class myclass{}
1、异常系列的超父类:java.lang.Throwable
(1)只有它或它子类的对象,才能被JVM或throw语句“抛”出
(2)也只有它或它子类的对象,才能被catch“捕获”
2、Throwable分为两大派别
(1)Error:严重的错误,需要停下来重新设计、升级解决这个问题
(2)Exception: 一般的异常,可以通过判断、检验进行避免,或者使用try…catch进行处理
3、Exception又分为两大类
(1)运行时异常:
它是RuntimeException或它子类的对象。
这种类型的异常,编译器不会提醒你,要进行throws或try…catch进行处理,但是运行时可能导致崩溃。
(2)编译时异常:
异常除了运行时异常以外的都是编译时异常。
这种类型的异常,编译器是强制要求你,throws或try…catch进行处理,否则编译不通过。
4、列出常见的异常类型
(1)运行时异常
RuntimeException、NullPointerException(空指针异常),ClassCastException(类型转换异常),ArithmeticException(算术异常),NubmerFormatException(数字格式化异常),IndexOutOfBoundsException(下标越界异常)(ArrayIndexOutOfBoundsException(数组下标越界异常)、StringIndexOutOfBoundsException(字符串下标越界异常))、InputMisMatchException(输入类型不匹配异常)。。。。
(2)编译时异常
FileNotFoundException(文件找不到异常)、IOException(输入输出异常)、SQLException(数据库sql语句执行异常)。。。
1、在当前方法中处理:try…catch…finally
//形式一atch
try{可能发生异常的代码
}catch(异常类型 异常名e){处理异常的代码(一般都是打印异常的信息的语句)
}catch(异常类型 异常名e){处理异常的代码(一般都是打印异常的信息的语句)
}。。。//形式二finally
try{可能发生异常的代码
}finally{无论try中是否有异常,也不管是不是有return,都要执行的部分
}//形式三atch..finally
try{可能发生异常的代码
}catch(异常类型 异常名e){处理异常的代码(一般都是打印异常的信息的语句)
}catch(异常类型 异常名e){处理异常的代码(一般都是打印异常的信息的语句)
}。。。
finally{无论try中是否有异常,也不管catch是否可以捕获异常,也不管try和catch中是不是有return,都要执行的部分
}
执行特点:
(1)如果try中的代码没有异常,那么try中的代码会正常执行,catch部分就不执行,finally中会执行
(2)如果try中的代码有异常,那么try中发生异常的代码的后面就不执行了,找对应的匹配的catch分支执行,finally中会执行
2、finally与return混合使用时
(1)如果finally中有return,一定从finally中的return返回。
此时try和catch中的return语句,执行了一半,执行了第一个动作。所以,finally中的return语句会覆盖刚刚的返回值
return 返回值; 语句有两个动作:(1)把返回值放到“操作数栈”中,等当前方法结束后,这个“操作数栈”中的值会返回给调用处(2)结束当前方法的执行
(2)如果finally中没有return,finally中的语句会执行,但是不影响最终的返回值
即try和catch中的return语句两步拆开来走,先把(1)把返回值放到“操作数栈”中,(2)然后走finally中的语句(3)再执行return后半个动作,结束当前方法
3、在当前方法中不处理异常,明确要抛给调用者处理,使用throws
语法格式:
【修饰符】 返回值类型 方法名(【形参列表】) throws 异常列表{}
此时调用者,就知道需要处理哪些异常。
方法的重写的要求:
(1)方法名:相同
(2)形参列表:相同
(3)返回值类型:
基本数据类型和void:相同
引用数据类型:<=
(4)修饰符:
权限修饰符:>=
其他修饰符:static,final,private不能被重写
(5)throws:<=
方法的重载:
(1)方法名:相同
(2)形参列表:必须不同
(3)返回值类型:无关
(4)修饰符:无关
(5)throws:无关
throw 异常对象;//例如:
throw new AccountException("xxx");
throw抛出来的异常对象,和JVM抛出来的异常对象一样,也要用try…catch处理或者throws。
如果是运行时异常,编译器不会强制要求你处理,如果是编译时异常,那么编译器会强制要求你处理。
1、必须继承Throwable或它的子类
我们见到比较多的是继承RuntimeException和Exception.
如果你继承RuntimeException或它的子类,那么你自定义的这个异常就是运行时异常。编译器就不会提醒你处理。
如果你继承Exception,那么它属于编译时异常,编译器会强制你处理。
2、建议大家保留两个构造器
//无参构造
public 自定义异常名(){}//有参构造
public 自定义异常名(String message){super(message);
}
3、自定义异常对象,必须手动抛出,用throw抛出
(1)e.printStackTrace():打印异常对象的详细信息,包括异常类型,message,堆栈跟踪信息。这个对于调试,或者日志跟踪是非常有用的
(2Message():只是获取异常的message信息
关于异常信息的打印:
用打印和用e.printStackTrace()都是会标记红色的突出。
用System.out打印,当成普通信息打印。
这两个打印是两个独立的线程,顺序是不能精确控制的。
本文发布于:2024-01-28 16:41:02,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/17064312688804.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |