Java基础编程
Java基础编程
1、Java语言概述
1.1 课程内容概述
第一部分:Java语言核心结构,变量、基本语法、分支、循环、数组...
第二部分:面向对象的核心逻辑,OOP、封装、继承、多态、接口...
第三部分:开发Java SE高级应用程序,异常、集合、I/O、多线程、反射机制、网络编程...
第四部分:项目实战
1.2 Java语言概述
基础常识
软件:一系列按照特定顺序组织的计算机数据和指令的集合
- 系统软件:Windows、Linux、Mac OS...
- 应用软件:word、idea、ps...
人机交互方式:图形化界面 VS 命令行方式
应用程序 = 算法 + 数据结构
Windows命令行常用DOS命令:Windows+R,输入cmd打开
命令 | 说明 |
---|---|
dir | 列出当前目录下的文件以及文件夹 |
md | 创建目录 |
rd | 删除目录 |
cd | 进入指定目录 |
cd.. | 退回到上一级目录 |
cd\ | 退回到根目录 |
del | 删除文件 |
exit | 退出DOS命令行窗口 |
echo | 输出字符 |
计算机语言的发展迭代史
- 第一代:机器语言
- 第二代:汇编语言
- 第三代:高级语言
- 面向过程:C、Pascal、Fortran...
- 面向对象:Java、JS、Python、Scala...
java语言版本迭代概述
不同版本
- JavaSE(J2SE):开发基于C/S架构桌面应用程序
- JavaEE(J2EE):开发基于B/S架构企业办公系统
- JavaME(J2ME):开发移动端
Java语言应用的领域
- Java Web开发:后台开发
- 大数据开发
- Android应用程序开发:客户端开发
Java语言的特点
- 面向对象性
- 两个要素:类、对象
- 三个特征:封装、继承、多态
- 健壮性:
- 去除了C语言中的指针
- 自动的垃圾回收机制(仍然会出现内存泄漏、内存溢出)
- 跨平台性:write once,run anywhere:一次编译,到处运行
JVM是Java Virtual Machine(Java虚拟机),虚拟出来的计算机,本质上是一个程序
1.3 开发环境搭建
JDK、JRE、JVM的关系
JDK下载和安装
下载(需要注册Oracle账号):https://www.oracle.com/java/technologies/downloads/
安装:JDK(编译需要)和JRE(运行需要),安装路径中不能出现中文、空格
配置环境变量
path环境变量:Windows操作系统执行命令时查找的路径
配置环境变量希望Java的开发工具(javac.exe、java.exe)在任何文件路径下都能执行成功
1.4 第一个Java程序
Java程序执行过程
用记事本写一个HelloWorld程序,保存为.java文件类型
打开命令行窗口
javac HelloWorld.java #编译
java HelloWorld #运行
1.5 注释与API文档
注释:Comment
//单行注释
/*多行注释 */
/**文档注释 */
单行、多行注释的内容不参与编译,多行注释不可以嵌套使用
API文档
将语言提供的类库都称为API
API文档:类库使用的说明书
良好的编程风格
- 正确注释和注释风格
- 正确缩进和空白
- 块的风格:行尾还是下一行
2、基本语法
2.1 关键字与标识符
关键字
被Java语言赋予了特殊含义的字符串,都是小写
保留字
现Java版本未使用,以后可能会作为关键字使用
goto、const
标识符
可以自己起名字的,包名、类名、接口名、变量名、方法名
必须遵守
应该遵守
应该见名知意,代码简洁之道
2.2 变量的使用
变量的分类
- 按数据类型分
整型:
① byte(1字节=8bit),范围:-128~127
② short(2字节)
③ int(4字节),通常定义整型变量使用int,默认也是int
④ long(8字节),必须以l或L结尾
浮点型:表示带小数点的数字
① float(4字节),表示的数值范围比long还大,变量要以f或F结尾
② double(8字节),浮点型通常定义为double,默认也是double
字符型:单引号‘’,内部只能写一个字符,
① char(1字符=2字节),声明一个字符,转义字符,直接使用Unicode值来表示字符型常量
布尔型:
① boolean 条件判断,循环结构中使用,只能取true或false
- 按声明位置分类
定义变量的格式
int temp = 10;
或
int temp;
temp=10;
变量使用注意点
① 变量必须先声明,后使用
② 变量只在其作用域内有效
③同一个作用域内不可以声明两个同名的变量
基本数据类型变量间运算规则
-
自动类型转换
- 容量小的和容量大的做运算,结果自动提升为容量大的数据类型
-
byte、char、short --> int --> long --> float --> double
- 当byte、char、short三种类型的变量做运算时,结果为int类型
-
强制类型转换
- 使用强转符:(),可能导致精度损失
-
String与基本数据类型运算
- String属于引用数据类型,双引号“”表示字符串
- 可以和基本数据类型做+运算
2.3 进制
进制
二进制:0,1,满2进1,以0b或0B开头
八进制:0-7,满8进1,以0开头表示
十进制:0-9,满10进1
十六进制:0-9及A-F,满16进1,以0x或0X开头,A-F不区分大小写
二进制
计算机底层数据存储都以二进制补码方式存储
正数:三码合一
负数:
原码:二进制,最高位为符号位
反码:符号位为1,其他按原码取反
补码:反码加1
进制间的转换
2.4 运算符
算术运算符
+ - * / % ++ --
特别的,取前面的数的符号
int m3 = 12;
int n3 = -5;
System.out.println("m3 % n3 = " + m3 % n3);//2
int m4 = -12;
int n4 = -5;
System.out.println("m4 % n4 = " + m4 % n4);//-2
赋值运算符
= += -= *= /= %=
特别的
short s1 = 10;
//s1 = s1 + 2;//编译失败
s1 += 2;//结论:运算的结果不会改变变量本身的数据类型
System.out.println(s1);
- 比较运算符
== != > < >= <= instanceof
比较运算的结果是boolean类型
逻辑运算符
& && | || ! ^
&和&&的区别
① 运算结果相同
② 左边是true时,都会执行右边
③ 左边是false时,&继续执行右边,&&则不会
|和||的区别
① 运算结果相同
② 左边是false时,都会执行右边
③ 左边是true是,|继续执行右边,||则不会
操作的都是boolean类型的变量,结果也是boolean的
位运算符
<< 每向左移1位,相当于*2
>> 每向右移1位,相当于/2
>>> 二进制补码右移
& 二进制进行与运算
| 二进制进行或运算
^ 二进制进行异或运算
~ 二进制取反运算
三元运算符
a > b ? 10:20;
2.5 流程控制
分支结构
- if-else条件判断
if(a>b){
}else if{
}else{
}
else是可选的,多个条件表达式之间有包含关系,需要将范围小的声明在范围大的上面
- switch-case选择结构
switch(a){
case 1: a=1; break;
case 2: break;
default: break;
}
依次匹配case,匹配成功则进入case结构中,直到遇到break
case后只能是常量,switch中可以是如下的
byte 、short、char、int、枚举类型(JDK5.0新增)、String类型(JDK7.0新增)
循环结构
- for循环
for(①;②;④){
③
}
执行过程:① - ② - ③ - ④ - ② - ③ - ④ - ... - ②
- while循环
①
while(②){
③;
④;
}
执行过程:① - ② - ③ - ④ - ② - ③ - ④ - ... - ②
- do-while循环:至少执行一次
①
do{
③;
④;
}while(②);
执行过程:① - ③ - ④ - ② - ③ - ④ - ... - ②
- 无限循环
while(true)
for(;;)
- 嵌套循环
九九乘法表
for(int i=1;i<=9;i++){ //i控制行数
for(int j=1;j<=i){ //j控制列数
System.out.print(i+"*"+j+"="+i*j);
}
System.out.println();
}
100以内的质数
break和continue
- break:一般在switch-case和循环中使用,表示结束当前循环,后面不能跟执行语句
- continue:在循环中使用,表示结束当次循环,后面不能跟执行语句
Scanner类的使用
从键盘获取输入
import java.util.Scanner; //导入依赖包
class ScannerTest{
public static void main(String[] args){
Scanner scan = new Scanner(System.in); //调用构造方法创建对象
System.out.println("请输入姓名:");
String name = scan.next(); //调用Scanner类的方法
}
}
scan.nextInt();
scan.nextDouble();
scan.nextBoolean();
scan.next();
scan.nextLine(); //next方法不能得到带空格的字符串,而nextLine()方法返回的是Enter键之前的所有字符
项目一
家庭收支记账软件
3、数组
3.1 数组概述
Array:多个相同类型的数据有序排列的集合,可重复,通过下标管理
int[] array={1,2,3,4};
元素可以是基本类型和引用类型,创建数组对象后会在内存中开辟一块连续空间,长度一旦确定就不能修改
3.2 一维数组
- 声明和初始化
int[] ids; //声明
ids = new int[]{1,2,3,4,5}; //初始化
String[] names = new String[5];
int[] arr4 = {1,2,3,4,5}; //类型推断
错误的
int[] arr1 = new int[];
int[5] arr2 = new int[5];
int[] arr3 = new int[3]{1,2,3};
- 元素的引用
通过下标调用,从0开始到长度-1结束
names[0] = "张三";
- 属性length:长度固定
names.length;
- 遍历
for(int i=0;i<names.length;i++){
System.out.println(names[i]);
}
- 默认初始化
整型:0
浮点型:0.0
char型:0或'\u0000'
boolean:false
引用类型:null
Unicode编码中\u0000代表的是空字符,属于不可显字符
空格的编号是\u0020
System.out.println("\u0000".length());//1
System.out.println("".length());//0
"\u0000"就是一个包含一个字符的字符串
- 内存解析
3.3 二维数组
二维数组:一维数组的元素是一维数组类型
- 声明与初始化
int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}};
String[][] arr2 = new String[3][2];
String[][] arr3 = new String[3][];
int[] arr4[] = new int[][]{{1,2,3},{4,5},{6,7,8}};
int[] arr5[] = {{1,2,3},{4,5},{6,7,8}};
错误的
String[][] arr4 = new String[][4];
String[4][3] arr5 = new String[][];
int[][] arr6 = new int[4][3]{{1,2,3},{4,5},{6,7,8}};
- 调用
System.out.println(arr1[0][1]);
System.out.println(arr3[0]);
- 属性length
arr4.length;
arr4[1].length;
- 遍历
for(int i=0;i<arr4.length;i++){
for(int j=0;j<arr4[i].length;j++){
System.out.print(arr4[i][j]+" ");
}
System.out.println();
}
- 默认初始化
int[][] arr = new int[4][3];
外层元素:arr[0],arr[1]
等
内层元素:arr[0][0],arr[1][2]
等
int[][] arr = new int[4][3];
外层初始化值:地址值
内层初始化值:与一维数组情况相同
int[][] arr = new intjjj[4][];
外层初始化:null
内层初始化:不能调用,否则报错
- 二维数组内存解析
3.4 数组的常见算法
数组的创建与元素赋值
- 杨辉三角
每一行首尾都是1,第n行有n个数,下面的数等于上面两个数之和
杨辉三角打印10行
public class Yanghui{
public static void main(String[] args){
int[][] a = new int[10][10];
for(int i=0;i<10;i++){
for(int j=0;j<=i;j++){ //第n行有n个数
if(j==0||i==j){
a[i][j]=1; //首尾赋值为1
}else{
a[i][j]=a[i-1][j-1]+a[i-1][j]; //等于上面两个数之和
}
System.out.print(a[i][j]+" ");
}
System.out.println();
}
}
}
数值型的数组
赋值与复制
- 赋值
将地址赋值,共同指向堆空间中的数组
- 复制
开辟新空间
array2 = new int[array1.length];
for(int i = 0;i < array2.length;i++){
array2[i] = array1[i];
}
数组元素反转
//方式一
for(int i=0;i<arr.length/2;i++){
String temp = arr[i];
arr[i] = arr[arr.length-i-1];
arr[arr.length-i-1] = temp;
}
//方式二
for(int i=0,j=arr.length-1;i<j;i++,j--){
String temp = arr[j];
arr[i] = arr[j];
arr[j] = temp;
}
搜索和检索
线性查找
public int inearSearch(int a,int[] array){
int index = -1;
for(int i=0;i<array.length;i++){
if(array[i]==a){
index=i;
}
}
return index;
}
二分查找(折半查找):数组需有序
public int halfFind(int[] arr,int key){
int min = 0;
int max = arr.length-1;
int mid;
while(min<=max){
mid = (max+min)/2;
if(key>arr[mid]){
min = mid+1;
}else if(key<arr[mid]){
max = mid-1;
}else{
return mid;
}
}
return -1;
}
冒泡排序
public void bubbleSort(int[] arr){
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
3.5 Arrays工具类的使用
在java.util包下Arrays工具类提供了操作数组的方法
boolean isEquals = Arrays.equals(arr1,arr2); //判断两个数组是否相等
Arrays.toString(arr1); //输出数组信息
Arrays.fill(arr1,10); //将指定值填充到数组中,替换所有值
Arrays.sort(arr2); //排序
Arrays.binarySearch(arr1,10); //二分查找
源码
public static boolean equals(long[] a, long[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false;
int length = a.length;
if (a2.length != length)
return false;
for (int i=0; i<length; i++)
if (a[i] != a2[i])
return false;
return true;
}
public static String toString(long[] a) {
if (a == null)
return "null";
int iMax = a.length - 1;
if (iMax == -1) //new long[0]的情况
return "[]";
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {
b.append(a[i]);
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}
public static void fill(long[] a, long val) {
for (int i = 0, len = a.length; i < len; i++)
a[i] = val;
}
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); //双基准快速排序
}
public static int binarySearch(long[] a, long key) {
return binarySearch0(a, 0, a.length, key);
}
// Like public version, but without range checks.
private static int binarySearch0(long[] a, int fromIndex, int toIndex,
long key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1; //无符号右移
long midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
3.6 数组的常见异常
角标界异常
int[] arr = new int[]{1,2,3,4,5};
System.out.println(arr[-2]);
空指针异常
int[] arr1 = new int[]{1,2,3,4,5};
arr1 = null;
System.out.println(arr1[0]);
int[][] arr2 = new int[4][];
System.out.println(arr2[0][0]);
String[] arr3 = new String[]{"AA","BB","CC"};
arr3[0] = null;
System.out.println(arr3[0].toString());
4、面向对象-上
4.1 类与对象
学习主线
大处着眼,小处着手
-
Java类及类的成员:属性、方法、构造器、代码块、内部类
-
面向对象的三大特征:封装性、继承性、多态性、(抽象性)
-
其他关键字:this、super、static、final、abstract、interface等
面向对象与面向过程
- 面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做
- 面向对象:强调具备了功能的对象,以类\对象为最小单位,考虑谁来做
类和对象
类:对一类事务的描述,是抽象的、概念上的定义,学生、老师
对象:是实际存在的该类事物的每个个体,也称为实例(instance),张三、李华
关系:对象是由类new出来的,派生出来的
面向对象实现
- 创建类,设计类的成员
- 创建类的对象
- 通过(对象.属性)或(对象.方法)调用对象的结构
属性 = 成员变量 = field = 域、字段
方法 = 成员方法 = 函数 = method
创建类的对象 = 类的实例化 = 实例化类
对象的创建与对象的内存解析
如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性(非static的)
如果我们修改一个对象的属性a,则不影响另一个对象属性a的值
匿名对象
创建对象时,没有赋值给一个变量名,匿名对象只能调用一次
class PhoneMall{
public void show(Phone phone){
phone.sendEmail();
phone.playGame();
}
}
//使用
PhoneMall phoneMall = new PhoneMall();
phoneMall.show(new Phone());
JVM内存结构
编译完程序后,生成字节码文件,通过类加载器加载到内存中
虚拟机栈:局部变量
堆:new出来的结构,数组、对象、对象的属性
方法区:类的加载信息、常量池、静态域
4.2 属性
属性和局部变量
- 相同点
- 定义变量的格式:数据类型 变量名 = 变量值;
- 先声明,后使用
- 变量都有其对应的作用域
- 不同点
- 类中声明的位置
- 属性:直接定义在类的一对{}内
- 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
- 权限修饰符
- 属性:声明属性时指明权限,private、缺省、protected、public
- 局部变量:不可以使用权限修饰符
- 默认初始化
- 属性:默认初始化
- 整型(byte、short、int、long:0)
- 浮点型(float、double:0.0)
- 字符型(char:0或'\u0000')
- 布尔型(boolean:false)
- 引用类型(类、数组、接口:null)
- 局部变量:没有默认初始化,调用之前要显式初始化
- 属性:默认初始化
- 在内存中加载的位置
- 属性:加载到堆空间中(非static)
- 局部变量:加载到栈空间中
- 类中声明的位置
变量的分类
- 按数据类型
- 按声明位置
4.3 方法
public String getName(String name){
System.out.println(name);
return name;
}
return
①结束方法
②返回数据
return后面不可以声明执行语句
方法的重载
同一个类中(或子类重载父类的同名不同参的方法),方法名相同,参数个数或参数类型不同
跟方法的权限修饰符、返回值类型、形参变量名、方法体都没关系
构成重载
public void getSum(int i,int j){
System.out.println("1");
}
public void getSum(double d1,double d2){
System.out.println("2");
}
public void getSum(String s ,int i){
System.out.println("3");
}
public void getSum(int i,String s){
System.out.println("4");
}
不构成重载
public int getSum(int i,int j){
return 0;
}
public void getSum(int m,int n){
}
private void getSum(int i,int j){
}
可变个数形参的方法
jdk5.0新增,可变个数形参声明在末尾,最多只能声明一个可变形参
public void show(int i){
}
public void show(String s){
System.out.println("show(String)");
}
public void show(String ... strs){
System.out.println("show(String ... strs)");
for(int i = 0;i < strs.length;i++){
System.out.println(strs[i]);
}
}
//不能与上一个方法同时存在
// public void show(String[] strs){
//
// }
调用时可传递0个或多个参数
Java的值传递机制
变量是基本数据类型,赋值的是变量所保存的数据值
变量是引用数据类型,赋值的是变量所保存的数据的地址值
例题1
例题2
递归方法
一个方法体内调用它自身
// 例1:计算1-n之间所有自然数的和
public int getSum(int n) {// 3
if (n == 1) {
return 1;
} else {
return n + getSum(n - 1);
}
}
// 例2:计算1-n之间所自然数的乘积:n!
public int getSum1(int n) {
if (n == 1) {
return 1;
} else {
return n * getSum1(n - 1);
}
}
//例3:已知一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
//其中n是大于0的整数,求f(10)的值。
public int f(int n){
if(n == 0){
return 1;
}else if(n == 1){
return 4;
}else{
// return f(n + 2) - 2 * f(n + 1);
return 2*f(n - 1) + f(n - 2);
}
}
4.4 封装性
程序设计追求
高内聚:类的内部数据操作细节自己完成,不允许外部干涉
低耦合:仅对外暴露少量的方法用于使用
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性
封装性体现
①私有化类的属性,提供get和set方法访问和设置该属性
private double radius;
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius(){
return radius;
}
②不对外暴露私有的方法
③单例模式(将构造器私有化)
④如果不希望类在包外被调用,可以将类设置为缺省的
四种权限修饰符
修饰类只能用缺省或public
4.5 构造器
作用
- 创建对象
- 初始化对象的信息
说明
- 如果没有显式定义类的构造器,系统默认提供一个空参构造器,定义了则不提供
- 定义多个构造器构成重载
- 一个类中至少有一个构造器
- 方法名和类名相同
//构造器
public Person(){
System.out.println("Person().....");
}
public Person(String n){
name = n;
}
public Person(String n,int a){
name = n;
age = a;
}
属性赋值的先后顺序
①默认初始化
②显式初始化
③构造器中初始化
④通过(对象.方法)或(对象.属性)的方式赋值
JavaBean
- 类是公共的
- 有一个无参的构造器
- 属性,且有对应的get、set方法
4.6 this
this:当前对象或当前正在创建的对象
在类的方法(包括构造方法)中,使用this.属性
或this.方法
调用当前对象的属性或方法,可以省略this
如果方法的形参和类的属性同名时,显式使用this.变量
的方式,表明变量是属性的,而不是形参
class Person{
private String name;
public void SetName(String name){
this.name = name;
}
}
this调用构造器
①在类的构造器中,可以使用this(形参列表)
调用本类中指定的其他构造器
②构造器中不能通过this(形参列表)
方式调用自己
③如果一个类中有n个构造器,则最多有n-1个构造器中使用了this(形参列表)
④this(形参列表)
必须声明在当前构造器的首行
⑤构造器内部,最多只能声明一个this(形参列表)
,用来调用其他的构造器
4.7 package/import
package:包
JDK中的主要包
import:导入
使用import导入指定包下的类、接口,也可用*表示导入所有
5、面向对象-中
5.1 继承性
①减少了代码的冗余,提高了代码的复用性
②便于功能的扩展
③多态性的前提
class A extend B{
}
子类A继承父类B后,就获取了父类B中声明的所有属性和方法(包括私有结构,因为封装性,子类不能直接调用而已)
extend:扩展、延伸
子类继承父类后,还可以声明自己的属性或方法
Object类
没有显式的声明一个类的父类,此类默认继承java.lang.Object类,所有的java类都直接或间接的继承与java.lang.Object类,因此所有的java类都具有java.lang.Object类声明的功能
public class Object {
private static native void registerNatives();
static { //静态代码块,加载类时执行,注册本地方法,调用操作系统
registerNatives();
}
public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
protected native Object clone() throws CloneNotSupportedException;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
protected void finalize() throws Throwable { }
}
5.2 方法的重写
override:子类继承父类后,重写了一个和父类中同名同参数的方法,进行覆盖
这样通过子类对象调用该同名同参数的方法时,执行的是子类中重写的方法
class Circle{
public double findArea(){}//求面积
}
class Cylinder extends Circle{
public double findArea(){}//求表面积
}
重写规则
①子类重写的方法和父类中的方法名和形参列表相同
②子类重写的方法权限修饰符≥父类中方法的权限修饰符
子类不能重写父类中声明为private权限的方法(重写后通过对象调用会报错)
③返回值类型
父类 | 子类 |
---|---|
void | void |
基本数据类型 | 相同的基本数据类型 |
A类型 | A类或A类的子类 |
④子类重写的方法抛出的异常类型≤父类被重写的方法
⑤子父类中同名同参数方法都声明为非static的(都声明为static的不是重写,早绑定,没有多态)
面试题:重载和重写的区别
①重载和重写的概念
②重载和重写的规则
③重载:不表现为多态性,调用地址在编译期就绑定了,早绑定
重写:表现为多态性,方法调用时解释器才会确定要调用的具体的方法,晚绑定(多态)
5.3 super
super:父类的
在子类的方法中,可以使用super显式的调用父类中声明的属性或方法,可以省略super
如果子类和父类中声明了同名的属性或方法,要显式的使用super.属性
或super.方法
,表明调用父类中的结构
super调用构造器
在子类构造器中使用super(形参列表)调用父类中指定的构造器,必须声明在首行
this(形参列表)和super(形参列表)只能二选一,没显式声明默认调用空参构造器super()
5.4 子类对象实例化过程
- 从结果上看:
子类继承父类后,就获取了父类中声明的属性和方法
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性
- 从过程上看:
通过子类构造器创建对象时,会直接或间接的调用父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中的空参构造器,因此加载了父类的结构,可以在内存中看到父类的结构,子类对象才可以进行调用
创建子类对象时,调用了父类的构造器,但是只创建了一个对象
5.5 多态性
多态性:一个事物的多种形态
对象的多态性:父类的引用指向子类的对象
Person p = new Man();
Object obj = new Date();
在编译期,只能调用父类中声明的方法,在运行期,实际执行的是子类重写父类的方法
编译看左边,运行看右边
多态性的使用前提
①类的继承关系
②方法的重写
//举例一:
public void func(Animal animal){//Animal animal = new Dog();
animal.eat();
animal.shout();
}
//举例二:
public void method(Object obj){
}
//举例三:
class Driver{
public void doData(Connection conn){ //conn = new MySQlConnection();
// conn = new OracleConnection();
//规范的步骤去操作数据
// conn.method1();
// conn.method2();
// conn.method3();
}
}
对象的多态性只适用于方法,不适用于属性(对于属性,编译和运行都看左边)
向上转型和向下转型
- 向上转型:多态
- 向下转型:()
- 对象的多态性,内存中是加载了子类特有的属性和方法,但由于变量声明为父类类型,编译时,只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用
- 使用强转时,可能会出现ClassCastException异常(两个类型转换不兼容)
- 需要先进行
instanceof
判断,返回true就可以进行向下转型
instanceof的使用
① a instanceof A
判断对象a是否是类A的实例(对象),是,返回true,否则返回false
②class A extend B
如果a instanceof A
返回true,则a instanceof B
也返回true,类B是A的父类
③要求a所属的类和A必须是子类和父类的关系,否则编译错误
面试题:对多态性的理解?
①实现代码的通用性
Object类中定义的public boolean equals(Object obj){}
方法
JDBC:使用Java程序操作数据库(MySQL、Oracle、DB2、SQL Server)
抽象类、接口的使用体现了多态性(抽象类、接口不能实例化)
多态是运行时行为
5.6 Object类
java.lang.Object类的说明
①Object类是所有java类的根父类
②在类的声明中没有使用extends指定父类,则默认父类是java.lang.Object类
③Object类中的方法可以通用
④Object类只声明了一个空参构造器
equals()方法
①只能适用于引用数据类型
②Object类中,equals()的定义
public boolean equals(Object obj) {
return (this == obj);
}
Object类中的equals()方法和==的作用是相同的,比较两个对象的地址值是否相同,两个引用是否指向了同一个对象
③String、Date、File、包装类等都重写了该equals()方法,重写以后比较的是两个对象的实体内容是否相同
重写equals()方法
比较两个对象的实体内容是否相同
class User{
String name;
int age;
public boolean equals(Object object){
if(object == this){
return true;
}
if(object instanceof User){ //object是否是User类的对象
User user = (User)object;
return this.age == user.age&&this.name.equals(user.name);
}
return false;
}
}
==运算符
①可以使用在基本数据类型和引用类型变量中
②基本数据类型,比较两个变量保存的数据是否相等(不一定类型要相同)
引用类型,比较两个对象的地址值是否相同,两个引用是否指向同一个对象实体(类型相同)
toString方法
①输出一个对象的引用时,就是调用对象的toString()方法
②Object类中toString()方法的定义
public String toString(){
return getClass().getName()+"@"+Integer.toHexString(hashCode());
}
③String、Date、File、包装类等都重写了Object类中的toString()方法,调用对象的toString()时,返回实体内容
重写toSting()
@Override
public String toString(){
return "Customer [name="+name+",age="+age+"]"
}
面试题
①final、finally、finalize的区别
②== 和equals()区别
- final修饰符(关键字)
final修饰类:此类不能再派生出子类,不能作为父类而被子类继承,因此一个类不能既被abstract修饰,又被final修饰
final修饰变量或方法:保证在使用过程中不被修改,final修饰的变量必须给出初始值,只能读取;final修饰的方法不能重写
- finally
在异常处理时提供finally来执行清除操作,不管有没有异常被抛出、捕获,finally都会被执行
- finalize方法
使用finalize()方法在垃圾收集器中将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
5.7 单元测试
5.8 包装类
包装类:使基本数据类型的变量具有类的特征
基本数据类型对应的包装类
基本数据类型、String、包装类之间的转换
① 基本数据类型<--->包装类:JDK 5.0新特性:自动装箱和自动拆箱
② 基本数据类型、包装类--->String:调用String重载的valueOf(Xxx xxx)
方法
③ String--->基本数据类型、包装类:调用包装类的parseXxx(String s)
转换时,可能会报NumberFormatException
//String类中的一个valueOf方法
public static String valueOf(int i) {
return Integer.toString(i);
}
应用举例
① Vector类中添加元素,只定义了形参为Object类型的方法
v.addElement(Object obj); //基本数据类型--->包装类--->使用多态
6、面向对象-下
6.1 static
static:静态的,主要用来修饰属性、方法、代码块、内部类
static修饰属性:静态变量(类变量)
静态变量(类变量):类的多个对象共享同一个静态变量,当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的(不是对象独有的)
实例变量(不用static修饰的,非静态变量):类的每个对象都独立的拥有一套类中的非静态属性,当修改其中一个对象中的非静态属性时,不会导致其他对象中的属性值被修改
说明:
① 静态变量随着类的加载而加载,可以通过类名.静态变量
的方式调用
② 静态变量的加载要早于对象的创建
③ 由于类只会加载一次,则静态变量在内存中也只会存在一份,存在方法区的静态域中
常见静态属性:System.out
;Math.PI
;
静态变量内存解析
static修饰方法
静态方法、类方法
① 随着类的加载而加载,可以通过类名.静态方法
进行调用
② 静态方法中,只能调用静态的方法或属性
非静态方法中,既可以调用非静态的,也可以调用静态的属性或方法
③ 静态方法内,不能使用this、super关键字
因为this代表的是调用这个函数的对象的引用,而静态方法是属于类的,不属于对象,静态方法成功加载后,对象还不一定存在
使用static
- 属性
属性是可以被多个对象所共享的,不会随着对象的不同而不同的
类中的常量也常常声明为static
- 方法
操作静态属性的方法,通常设置为static的
工具类中的方法,习惯上声明为static的,如:Math、Arrays、Collections等工具类
举例
-
单例模式
class Circle{
private double radius;
private int id;
private static int total; //记录创建圆的个数
private static int init = 1001; //从1001开始计数
public Circle(){
id = init++;
total++;
}
public Circle(double radius){
this();
this.radius = radius;
}
public double findArea(){
return 3.14*radius*radius;
}
public double getRadius(){
return radius;
}
public void setRadius(double radius){
this.radius = radius;
}
public int getId(){
return id;
}
public static int getTotal(){
return total; //获取静态属性
}
}
单例模式
保证某一个类只能存在一个对象
- 饿汉式1
class Bank{
private Bank(){ //1.私有构造器
}
private static Bank instance = new Bank(); //2.创建对象,静态的,私有的
public static Bank getInstance(){ //公共方法,返回对象
return instance;
}
}
- 饿汉式2
class Order{
private Order(){ //1.私有构造器
}
private static Order instance = null; //2.声明对象,未初始化,静态的,私有的
static{ //静态代码块,创建对象,随着类的加载执行
instance = new Order();
}
public static Order getInstance(){ //3.公共方法返回静态对象
return instance;
}
}
- 懒汉式
class Order{
private Order(){ //1.私有构造器
}
private static Order instance = null; //2.声明对象,未初始化,静态的私有的
public static Order getInstance(){ //3.公共方法返回对象
if(instance == null){
instance = new Order();
}
return instance;
}
}
两种方式的比较
饿汉式 | 懒汉式 | |
---|---|---|
坏处 | 对象加载时间过长 | 目前写法线程不安全 |
好处 | 线程安全的 | 延迟对象的创建 |
6.2 main()
public static void main(String[] args){
}
① main()方法作为程序的入口
② main()方法也是一个普通的静态方法
③main()方法可以作为我们与控制台交互的方式
通过控制台运行java程序时传递参数给main()方法
java HelloWorld "Tom" "Jerry" "123" "true"
6.3 代码块
代码块:用来初始化类、对象的信息
只能用static修饰(或不修饰)
- 静态代码块(static):
- 随着类的加载而执行,而且只执行一次
- 初始化类的信息
- 一个类中定义了多个静态代码块,按声明的先后顺序执行
- 静态代码块的执行要优先于非静态代码块的执行
- 静态代码块内只能调用静态的属性和方法,不能调用非静态结构
- 非静态代码块
- 随着对象的创建而执行
- 每创建一个对象,就执行一次非静态代码块
- 可以在创建对象时,对对象的属性等进行初始化
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态或非静态的结构
6.4 final
① final修饰类:此类不能被其他类所继承,如String类、System类、StringBuffer类等
② final修饰方法:此方法不可以被重写,如Object类中的getClass();
③ final修饰变量:此时变量就是一个常量
- final修饰属性:可以显式初始化、代码块中初始化、构造器中初始化
- final修饰局部变量:final修饰形参时,表明此形参是一个常量。当调用此方法时,给常量形参赋值实参,赋值以后,就只能在该方法体内使用此形参,且不能重新赋值
④ static final 修饰属性:表示全局常量(属于类的)
6.4 abstract
abstract:抽象的,修饰类、方法
- 修饰类:抽象类
- 此类不能直接new实例化对象(可以用
B b = new A()
创建子类对象,B为抽象类,A为子类) - 抽象类中一定有构造器(提供给子类创建对象的时候初始化父类的属性的),便于子类实例化时调用
- 开发中,都会提供抽象类的子类,让子类对象实例化(继承性是前提)
- 此类不能直接new实例化对象(可以用
- 修饰方法:抽象方法
- 只有方法的声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类。抽象类中可以没有抽象方法
- 子类重写了父类中所有的抽象方法后,此子类方可实例化
- 若子类没有重写所有抽象方法,则此子类也是抽象类,用
abstract
修饰
abstract 不能用来修饰:属性、构造器等
abstract 不能用来修饰:私有方法、静态方法、final的方法、final类
举例:
abstract class GeometricObject{
public abstract double findArea(); //抽象方法
}
class Circle extends GeometricObject{
private double radius;
public double findArea(){ //具体方法
return 3.14 * radius * radius;
}
}
模板方法的设计模式
模板模式:软件开发中,整体步骤很固定、通用,这些步骤已经在父类中写好,但某些部分易变部分可以抽象出来,供不同子类实现
举例
public class Main {
public static void main(String[] args) {
SubTemplate t = new SubTemplate();
t.spendTime();
}
}
abstract class Template{
public void spendTime(){ //计算某段代码执行所需要花费的时间
long start = System.currentTimeMillis();
this.code(); //不确定的部分
long end = System.currentTimeMillis();
System.out.println("花费时间为:"+(end-start));
}
public abstract void code();
}
class SubTemplate extends Template{
@Override
public void code(){ //求1000以内的质数(素数)
for(int i=2;i<=1000;i++){
boolean isFlag = true;
for(int j = 2;j<=Math.sqrt(i);j++){
if(i%j==0){
isFlag = false;
break;
}
}
if(isFlag){
System.out.println(i);
}
}
}
}
6.5 interface
interface接口:接口和类是两个并列的结构
定义接口中的成员
- JDK7及之前:只能定义全局常量和抽象方法
- 全局常量:
public static final
,可以省略不写,默认加上 - 抽象方法:
public abstract
- 全局常量:
- JDK8:除了全局常量和抽象方法,还可以定义静态方法、默认方法
- 不能定义构造器(不能实例化)
使用接口:通过类实现(implements)接口
- 如果覆盖了接口中所有的抽象方法,此类可以实例化
- 没有覆盖接口中所有的抽象方法,此类仍是抽象类
java类可以实现多个接口(最多继承一个类)
class A extends B implements C,D,E{
}
接口和接口之间可以继承,而且可以多继承
举例
class Computer{ //电脑类,包含数据传输的方法,参数为usb
public void transferData(USB usb){//USB usb = new Flash();
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{ //usb接口
//常量:定义了长、宽、最大最小的传输速度等
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{ //打印机
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
总结:
- 接口使用上也满足多态性
- 接口,实际上就是定义了一种规范
- 开发中,体会面向接口编程
面向接口编程的思想
应用程序访问不同厂商的数据库,调用的都是JDBC中定义的接口,不会出现具体某个数据库厂商的API
JDK8中接口新规范
- 接口中定义的静态方法,只能通过接口来调用
- 通过实现类的对象,可以调用接口中的默认方法
- 如果实现类重写了接口中的默认方法,调用的是重写以后的方法
- 子类继承的父类和实现的接口中声明了同名同参数的默认方法,子类没有重写的情况下,默认调用的是父类中的方法(类优先原则)
- 实现类实现了多个接口,这多个接口中定义了同名同参数的默认方法,实现类没有重写时,会报错(接口冲突),需要在实现类中重写此方法
- 在子类中调用父类、接口中被重写的方法
public void myMethod(){
method3(); //自己重写的方法
super.method3(); //父类中的方法
CompareA.super.method3(); //接口CompareA中的方法
CompareB.super.method3();
}
面试题
抽象类和接口的异同?
- 相同点:不能实例化;都可以包含抽象方法
- 不同点:
① 接口中只有定义,方法不能在接口中实现(实现该接口的类实现其方法),抽象类可以有定义和实现
② 接口需要实现(implements),抽象类需要继承(extends),一个类可以实现多个接口,只能继承一个抽象类,使用接口可以达到多重继承的目的
③ 接口强调特定功能实现,是has a
的关系,抽象类强调所属关系,是is a
的关系
④
接口 | 抽象类 | |
---|---|---|
成员变量 | 默认(只能)是public static final,必须赋初值 | 默认default,也可以定义为private、protected、public |
方法 | 所有都是public abstract | 抽象方法不能用private、static、synchronized、native等修饰,不带花括号,分号结尾 |
功能 | 不需要积累时用接口 | 功能需要积累时用抽象类 |
⑤ 接口被运用于实际比较常用的功能,便于日后维护或添加删除方法;抽象类更倾向于冲淡公共类的角色,不适用于日后重新对里面的代码进行修改
代理模式
给对象提供代理,让代理来访问对象
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
// server.browse();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
interface NetWork{
public void browse();
}
//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse() {
check();
work.browse();
}
}
6.6 内部类
内部类:将一个类声明在另一个类中
分类
- 成员内部类(静态、非静态)
- 局部内部类(方法内、代码块内、构造器内)
理解
- 作为外部类的成员
- 调用外部类的结构
- 可以被static修饰
- 可以被4种不同的权限修饰
- 作为一个类
- 类内可以定义属性、方法、构造器等
- 可以被final修饰,表示此类不能被继承,不使用final,就可以被继承
- 可以被abstract修饰
成员内部类
- 创建静态内部类对象(类名.结构)
Person.Dog dog = new Person.Dog();
- 创建非静态的内部类对象
Person p = new Person();
Person.Bird bird = p.new Bird();
在成员内部类中调用外部类的结构
public class Main {
public static void main(String[] args) {
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.display("张三");
}
}
class Person{
String name = "小明";
public void eat(){
System.out.println("吃饭");
}
//非静态成员内部类
class Bird{
String name = "杜鹃";
public void display(String name){
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
Person.this.eat();
}
}
}
局部内部类
方式一:
public Comparable getComParable(){ //返回对象
class MyComparable implements Comparable{ //局部内部类,实现Comparable接口,重写其方法
@Override
public int compareTo(Object o){
return 0;
}
}
return new MyComparable();
}
方式二:
public Comparable getComParable(){ //返回对象
return new Comparable(){
@Override
public int compareTo(Object o){
return 0;
}
};
}
局部内部类访问他所在方法中的局部变量必须用final修饰
因为局部变量是随着方法的调用而调用,随着调用完毕而消失
但是我们调用内部类时创建的对象依旧在堆内存中,并没有被回收,如果访问的局部变量不是用final修饰的,就是当方法调用完毕后,依旧存在于堆内存中的对象找不到局部变量的问题
而此时被final修饰的变量可以看成是一个常量,存在于常量池中,不会被立刻回收。
jdk1.8之后,即使你不加也会默认加上的
成员内部类和局部内部类,在编译后都会生成字节码文件
成员内部类:外部类$内部类名.class
局部内部类:外部类$数字 内部类名.class
7、异常处理
7.1 异常
异常体系结构
- java.lang.Throwable
- java.lang.Error:一般不编写针对性的代码进行处理
- java.lang.Exception:可以进行异常的处理
- 编译时异常(checked)
- IOException
- FileNotFoundException
- ClassNotFoundException
- IOException
- 运行时异常(unchecked,RuntimeException)
- NullPointerException:空指针
- ArrayIndexOutOfBoundsException:数组下标越界
- ClassCastException:类类型不兼容
- NumberFormatException:数字格式异常
- InputMismatchException:输入不匹配
- ArithmeticException:算术运算异常(分母为0)
- 编译时异常(checked)
编译和运行时异常
编译时异常:执行javac.exe命令时,可能出现的异常
运行时异常:执行java.exe命令时,出现的异常
异常举例
- 编译时异常
@Test
public void test7(){
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
}
- 运行时异常
//ArithmeticException:算术运算异常
@Test
public void test6(){
int a = 10;
int b = 0; //除数为0
System.out.println(a / b);
}
//InputMismatchException
@Test
public void test5(){
Scanner scanner = new Scanner(System.in);
int score = scanner.nextInt();
System.out.println(score);
scanner.close();
}
//NumberFormatException
@Test
public void test4(){
String str = "123";
str = "abc";
int num = Integer.parseInt(str);
}
//ClassCastException
@Test
public void test3(){
Object obj = new Date();
String str = (String)obj;
}
//IndexOutOfBoundsException
@Test
public void test2(){
//ArrayIndexOutOfBoundsException
// int[] arr = new int[10];
// System.out.println(arr[10]);
//StringIndexOutOfBoundsException
String str = "abc";
System.out.println(str.charAt(3));
}
//NullPointerException
@Test
public void test1(){
// int[] arr = null;
// System.out.println(arr[3]);
String str = "abc";
str = null;
System.out.println(str.charAt(0));
}
7.2 异常处理
抓抛模型
- 过程一(抛):
- 程序在正常执行过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出,一旦抛出,其后的代码就不再执行
- 异常对象的产生:
- ① 系统自动生成的异常对象
- ② 手动的生成一个异常对象,并抛出(throw)
- 过程二(抓):
- 异常的处理方式
- ①try-catch-finally
- ②throws
- 异常的处理方式
try-catch-finally
try{
String str = "123";
str = "abc";
int num = Integer.parseInt(str);
}catch(NumberFormatException e1){
System.out.println("数据格式异常"); //异常处理
}catch(InputMismatchException e2){
}catch(ClassCastException e3){
}finally{
//一定会执行的代码,关闭资源等
}
① finally是可选的
② try将可能出现异常的代码包装起来,执行过程中一旦出现异常,就会生成一个对应的异常类对象,根据对象类型去catch中匹配
③ 匹配到catch中的异常,进入catch中进行异常处理,处理完成,执行finally中的代码,跳出该结构,继续执行后面的代码
④ catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面,否则报错,不满足则无所谓
⑤ 常用的异常对象处理的方式:① String getMessage() ② printStackTrace()
⑥ 在try结构中声明的变量,出了try结构后,就不能再被调用
⑦ try-catch-finally结构可以嵌套使用
体会(有点懵)
① 使用try-catch-finally处理编译时异常时,是要程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现
② 开发中,运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对编译时异常,一定要考虑异常的处理
finally
① finally是可选的
② finally中声明的是一定会被执行的代码,即使catch中又出现异常了,try中return语句,catch中return语句等情况
③ 数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动的进行资源的释放。此时资源释放就需要声明在finally中
throws
throws+异常类型 写在方法的声明处。指明此方法执行时,可能会抛出的异常类型,一旦方法体执行时出现异常,就会在异常代码处生成一个异常类的对象,此对象满足throws之后的异常类型时,就会被抛出。异常代码后续的代码,就不再执行
public void transfer() throws NumberFormatException{
String str = "123";
str = "abc";
int num = Integer.parseInt(str);
System.out.println("执行到此处");
}
对比
try-catch-finally:真正的将异常给处理掉了(catch中写了处理方式)
throws的方式只是将异常抛给了方法的调用者,并没有真正将异常处理掉
如何选择
① 如果父类中被重写的方法没有throws处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中出现异常,必须使用try-catch-finally方式处理异常
② 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。建议这几个方法使用throws方式处理异常,而执行的方法a可以考虑使用try-catch-finally方式处理异常
补充:子类重写的方法抛出的异常类型≤父类中被重写的方法抛出的异常类型
7.3 手动抛出异常对象
throw:手动抛出一个异常类的对象
throw和throws的区别
throw 表示手动抛出一个异常类对象,生成异常对象的过程,声明在方法体内
throws 属于异常处理的一种方式,声明在方法的声明处
class Student{
private int id;
public void regist(int id) throws Exception {
if(id > 0){
this.id = id;
}else{
//手动抛出异常对象
// throw new RuntimeException("您输入的数据非法!");
// throw new Exception("您输入的数据非法!");
throw new MyException("不能输入负数");
}
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
}
7.4 自定义异常类
- 继承于现有的异常类:RuntimeException、Exception
- 提供全局常量:serialVersionUID
- 提供重载的构造器
public class MyException extends Exception{ //1、继承
static final long serialVersionUID = -7034897193246939L; //2、提供全局常量:
public MyException(){ //3、提供重载的构造器
}
public MyException(String msg){
super(msg);
}
}