Java 学习笔记
一、java简介
云海天:基础
1. java两大核心机制
1.1 java虚拟机(Java Virtual Machine)
JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域,负责执行指令、管理数据、内存、寄存器。
对于不同的平台有不同的虚拟机,这使得java程序可以跨平台运行。
1.2 垃圾收集机制(Garbage Collection)
问:GC是什么?为什么有GC?
答:内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。
问:垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
答:对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。当GC确定一些对象为”不可达”时,GC就有责任回收这些内存空间。可以。程序员可以手动执行System.gc()
,通知GC运行,但是Java语言规范并不保证GC一定会执行。
注意:即使有GC存在,Java程序仍会出现内存泄漏和内存溢出问题。
2. java中JDK、JRE、JVM
- JDK = JRE + 开发工具集
- JRE = JVM + JAVA SE 标准类库
3. API文档
API(Application Programming Interface, 应用程序编程接口)是 Java 提供的基本编程接口。
Java SE API文档下载
二、变量与运算符
云海天:变量
云海天:运算符
1. 关键字
关键字
2. 标识符
2.1 命名规范
- 由汉字、字母、数字、下划线、美元符号组成
- 不能以数字开头
- 不能包含空格
- 严格区分大小写
注意:由于Java采用Unicode字符集,因此可以使用汉字,但不建议使用。
3. 变量
3.1 按数据类型分类
基本数据类型
整数类型:byte、short、int、long
浮点类型:float、double
字符型:char
布尔型:boolean引用数据类型
类:class
接口:interface
数组:[]
3.2 按声明位置分类
成员变量: 在类中定义;有初始值;可使用所有的修饰符
- 实例变量:不以static修饰
- 类变量:以static修饰
局部变量: 没有初始值;只能使用final修饰
- 形参:在方法、构造器中定义;可不用初始化
- 方法局部变量:在方法内定义
- 代码块局部变量:在代码块内定义
3.3 各种数据类型
数据类型 | 占用空间 | 数值范围 |
---|---|---|
byte | 1Byte | -128 ~ 127 |
short | 2Byte | -215 ~ 215-1 |
int | 4Byte | -231 ~ 231-1 |
long | 8Byte | -263 ~ 263-1 |
float | 4Byte | -3.4E38 ~ 3.4E38 |
double | 8Byte | -1.79E308 ~ 1.79E308 |
char | 2Byte | 使用Unicode编码,可用单引号("a" )、转义字符(" " )、十六进制数("uXXXX" ) |
boolean | – | true、false |
4. 字符串类型String
String不是基本数据类型,而是引用数据类型。
4.1 基本数据类型与字符串相互转换
// TestPrimitiveWithString.java
class TestPrimitiveWithString {
public static void main(String args[]) {
float f_num = 12.333f;
String str = "3.141592653589793";
// float转String
String f2s_1 = String.valueOf(f_num);
System.out.println(f2s_1);
String f2s_2 = Float.toString(f_num);
System.out.println(f2s_2);
// String转double
double d_num = Double.parseDouble(str);
System.out.println(d_num);
}
}
执行结果:
12.333
12.333
3.141592653589793
5. 运算符
5.1 算术运算符
System.out.println(5 % 2); // 1
System.out.println(-5 % -2); // -1
System.out.println(-5 % 2); // -1
System.out.println(5 % -2); // 1
// 结论:余数与第一个操作数的符号相同。
5.2 三元运算符
// TestTernaryOperator.java
class TestTernaryOperator {
public static void main(String[] args) {
char x = "x";
int i = 10;
System.out.println(true ? x : i);
System.out.println(true ? "x" : 10);
}
}
执行结果:
120
x
解释:
- 如果其中有一个是变量,则按照自动类型转换规则处理成一致的类型。
- 如果都是常量,若其中一个是char,另一个在整数[0, 65535]间,则按照char处理;若一个是char,另一个是其他,则按照自动类型转换规则处理成一致的类型。
5.3 比较 + 与 +=
short s1 = 1;
s1 = s1 + 1;
s1 += 1;
执行结果:报错
解释:
其中,s1 + 1
运算结果为int类型,需要强制类型转换;而s1 += 1
结果会自动进行类型转换。
5.4 进制转换
int num = 60;
// 十进制转二进制
String bin_str = Integer.toBinaryString(num);
// 十进制转十六进制
String hex_str = Integer.toHexString(num);
6. 流程控制
6.1 switch语句有关规则
- 条件表达式的类型必须为:byte、short、int、char、枚举 (jdk 5.0)、String (jdk 7.0)。
- case子句中的值必须是常量,不能是变量名或不确定的表达式值。
- 同一个switch语句,所有case子句中的常量值互不相同。
- break语句用来在执行完一个case分支后使程序跳出switch语句块;如果没有break,程序会顺序执行到switch结尾。
- default子句是可任选的,位置也是灵活的。当没有匹配的case时,执行default。
6.2 switch语句与if语句对比
- 当判断数值类型为byte、short、int、char、枚举、String时,使用switch效率稍高。
- 当判断数值类型为boolean时,使用if。
- 使用switch-case的都可改成if-else,反之不成立。
6.3 return、break与continue
- return 并非专门用于结束循环,而是用于结束一个方法
- break 只能用于switch语句和循环语句
- continue 只能用于循环语句
- return、break、continue之后不能有其他语句
6.4 例子
例子说明 | 涉及内容 | 文件名 |
---|---|---|
完数 | for循环 | TestPerfectNumber.java |
求一元二次方程的根 | if-else | LinearEquation.java |
求某年的生肖 | switch-case | ChineseZodiacOfYear.java |
示例代码:LinearEquation.java
package com.atguigu.exam;
import java.util.Arrays;
import java.util.Scanner;
/**
* 求一元二次方程的根 ax^2 + bx + c = 0
*/
public class LinearEquation {
private double a, b, c;
private double x1, x2;
public LinearEquation(double a, double b, double c) {
this.a = a;
this.b = b;
this.c = c;
}
public double [] solve() {
if(a != 0) {
double delta = b * b - 4 * a * c;
if(delta == 0) {
x1 = - b / (2 * a);
System.out.println("这是一元二次方程,有两个相同的解。");
double [] result = {x1};
return result;
} else if(delta > 0) {
x1 = (-b + Math.sqrt(delta)) / (2 * a);
x2 = (-b - Math.sqrt(delta)) / (2 * a);
System.out.println("这是一元二次方程,有两个不同的解。");
double [] result = {x1, x2};
return result;
} else {
System.out.println("这是一元二次方程,但在实数上无解。");
return null;
}
} else {
if(b != 0) {
x1 = -c / b;
System.out.println("这是一元一次方程,有一个解。");
double [] result = {x1};
return result;
} else {
if(c == 0) {
System.out.println("这不是方程,是一个等式,等式成立。");
} else {
System.out.println("这不是方程,是一个等式,等式不成立。");
}
return null;
}
}
}
public static void main(String [] args) {
Scanner scan = new Scanner(System.in);
System.out.println("解一元二次方程 ax^2 + bx + c = 0");
System.out.print("请依次输入a、b、c:");
double a = scan.nextDouble();
double b = scan.nextDouble();
double c = scan.nextDouble();
LinearEquation le = new LinearEquation(a, b, c);
double[] res = le.solve();
if(res != null) {
System.out.println(Arrays.toString(res));
}
}
}
示例代码:ChineseZodiacOfYear.java
package com.atguigu.exam;
import org.junit.Test;
/**
* 计算xx年的生肖
*/
public class ChineseZodiacOfYear {
private static int year;
public ChineseZodiacOfYear(int year) {
this.year = year;
}
public static String zodiac() {
// 依据:2020年是鼠年 2020 % 12 == 4
switch(year % 12) {
case 0:
return "猴年";
case 1:
return "鸡年";
case 2:
return "狗年";
case 3:
return "猪年";
case 4:
return "鼠年";
case 5:
return "牛年";
case 6:
return "虎年";
case 7:
return "兔年";
case 8:
return "龙年";
case 9:
return "蛇年";
case 10:
return "马年";
case 11:
return "羊年";
}
return "";
}
@Test
public void test() {
int year = 2050;
String zadiac = new ChineseZodiacOfYear(year).zodiac();
System.out.println(year + "年是" + zadiac);
}
}
三、数组
1. 数组概述
- 数组本身是引用数据类型,而数组的元素可以是任意数据类型。
- 创建数组对象时会在内存中开辟一块连续的空间,数组名保存的是连续空间的首地址。
- 数组的长度一旦确定,就不能修改。
2. 数组元素默认初始化值
数组元素类型 | 元素默认初始值 |
---|---|
byte、short、int | 0 |
long | 0L |
float | 0.0F |
double | 0.0 |
char | 0(“u0000”) |
引用类型 | null |
2. 数组初始化
2.1 一维数组定义
// 声明: type [] varname; 或 type varname [];
String [] name;
// 动态初始化: varname = new type[length];
name = new String[3];
name[0] = "jack";
name[1] = "jock";
name[2] = "juck";
// 静态初始化: type [] varname = new type {xxx};
String [] name = new String {"jack", "jock", "juck"};
String [] name = {"jack", "jock", "juck"};
2.2 二维数组定义
// 动态初始化: type [][] varname = new type[length][length];
int [][] socre = new int[30][2];
// 动态初始化: type [][] varname = new type[length][];
int [][] matrix = new int[3][];
matrix[0] = new int[1];
matrix[1] = new int[5];
matrix[2] = new int[9];
// 静态初始化: type [][] varname = new type[][] {};
int [][] code = new int [][] {{2, 5, 8}, {1, 3}, {9, 7, 8, 6, 2}};
2.3 数组的属性
arr.length; 返回数组arr的长度
3. Arrays工具类
3.1 包名
java.util.Arrays
方法 | 说明 |
---|---|
static int binarySearch(Object[] a, Object key) | 使用二分查找算法搜索key在数组a的位置 |
static T[] copyOf(T[] original, int, newLength) | 复制指定的数组 |
static void fill(int[] a, int val) | 将指定值填充到数组中 |
static void sort(int[] a) | 按照数字顺序排列指定的数组 |
static String toString(int[] a) | 返回指定数组的字符串形式 |
static String deepToString(Object[] a) | 返回指定数组的字符串形式,用于多维数组 |
static boolean equals(int[] a, int[] b) | 判断两个对象数组是否相等 |
static boolean deepEquals(Object[] a, Object[] b) | 判断指定数组是否相等,用于多维数组 |
3.2 ==、equals与Arrays.equals
==
比较的是内容是否相等,对于数组对象比较的是内存地址,对于基本数据类型比较的是数值。Object.equals()
比较的是内容是否相等,由于Object.equals()
返回的是==
的判断,因此与==
运算比较相同。Arrays.equals()
比较的是两个数组元素的内容是否相等。
4. 练习题
4.1 6个不同取值的随机数
示例代码:6个不同取值的随机数
import org.junit.Test;
/**
* 取值为1-30的6个不同取值的随机值
*/
public class DifferentRandomValue {
@Test
public void test() {
int [] nums = new int[6];
for(int i = 0; i < nums.length; i++) {
nums[i] = (int)(Math.random() * 30) + 1;
for(int j = 0; j < i; j++) {
if(nums[i] == nums[j]) {
i--;
break;
}
}
}
for(int i: nums) {
System.out.print(i + " ");
}
}
}
4.2 回形数格式方阵
示例代码:回形数格式方阵
import java.util.Scanner;
/**
* 回形数
*/
public class CircularNumberMatrix {
private static int n;
public CircularNumberMatrix(int n) {
this.n = n;
}
public static void showMatrix() {
int [][] arr = new int[n][n];
int end = n * n;
/**
右:k = 1
下:k = 2
左:k = 3
上:k = 4
*/
int k = 1;
int i = 0, j = 0;
for(int num = 1; num <= end; num++) {
switch(k) {
case 1:
if(j < n && arr[i][j] == 0) {
arr[i][j++] = num;
} else {
k = 2;
i++;
j--;
num--;
}
break;
case 2:
if(i < n && arr[i][j] == 0) {
arr[i++][j] = num;
} else {
k = 3;
i--;
j--;
num--;
}
break;
case 3:
if(j >= 0 && arr[i][j] == 0) {
arr[i][j--] = num;
} else {
k = 4;
i--;
j++;
num--;
}
break;
case 4:
if(i >= 0 && arr[i][j] == 0) {
arr[i--][j] = num;
} else {
k = 1;
i++;
j++;
num--;
}
break;
}
}
// 打印
for(int[] row: arr) {
for(int x: row) {
System.out.printf("%4d ", x);
}
System.out.println("");
}
}
public static void main(String [] args) {
Scanner scan = new Scanner(System.in);
System.out.println("回形数矩阵");
System.out.print("请输入矩阵边长:");
int n = scan.nextInt();
new CircularNumberMatrix(n).showMatrix();
}
}
四、设计模式
1. 设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、
以及解决问题的思考方式。
创建型模式,共5种
工厂方法模式、抽象工模式、单例模式、建造者模式、原型模式。
结构型模式,共7种
适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共11种
策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
2. 单例设计模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一一个取得其对象实例的方法。
2.1 饿汉式
一上来就造对象。
public class HungryStyle {
// 1. 私有化构造器
private HungryStyle() {}
// 2. 创建类的对象
// 4. 属性需要设置静态化,否则getInstance()无法调用
private static HungryStyle instance = new HungryStyle();
// 3. 创建公共方法用来获取实例
public static HungryStyle getInstance() {
return instance;
}
}
class TestHungryStyle {
public static void main(String[] args) {
HungryStyle instance01 = HungryStyle.getInstance();
HungryStyle instance02 = HungryStyle.getInstance();
System.out.println(instance01 == instance02);
}
}
2.2 懒汉式
不急,对象要用再造。
public class SluggardStyle {
// 1. 私有化构造器
private SluggardStyle() {}
// 2. 声明类的对象
// 4. 属性需要设置静态化,否则getInstance()无法调用
private static SluggardStyle instance = null;
// 3. 创建公共方法获取类的实例
public static SluggardStyle getInstance() {
if(instance == null) {
instance = new SluggardStyle();
}
return instance;
}
}
class TestSluggardStyle {
public static void main(String[] args) {
SluggardStyle instance01 = SluggardStyle.getInstance();
SluggardStyle instance02= SluggardStyle.getInstance();
System.out.println(instance01 == instance02);
}
}
2.3 饿汉式VS懒汉式
饿汉式的好处是线程安全,坏处是会使加载类的时间加长。
懒汉式的好处是延迟对象的创建,坏处是当前的写法线程不安全,安全写法见线程章节。
2.4 单例模式的应用场景
- 网站的计数器
- 应用程序的日志应用
- 数据库连接池
- 项目中读取配置文件的类
- 项目的Application应用
- Windows下的任务管理器和回收站等
3. 模板方法设计模式(TemplateMethod)
在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现(抽象类),这就是一种模板模式。
示例代码:测试模板方法
示例代码:第一种计算素数的方法
public class Prime01 extends Template {
@Override
public void code() {
boolean isPrime;
for(int i = 2; i < 1000000000; i++) {
isPrime = true;
for(int j = 2; j < i; i++) {
if(i % j == 0) {
isPrime = false;
break;
}
}
if(isPrime) {
// 是素数
}
}
}
}
示例代码:第二种计算素数的方法
public class Prime02 extends Template {
@Override
public void code() {
boolean isPrime;
for(int i = 2; i < 1000000000; i++) {
isPrime = true;
for(int j = 2; j <= Math.sqrt(i); i++) {
if(i % j == 0) {
isPrime = false;
break;
}
}
if(isPrime) {
// 是素数
}
}
}
}
示例代码:模板抽象类
public abstract class Template {
public void speedTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println("Time Used: " + (end - start));
}
public abstract void code();
}
示例代码:测试模板方法
import org.junit.Test;
public class TestTemplate {
@Test
public void test() {
new Prime01().speedTime();
new Prime02().speedTime();
}
}
执行结果
Time Used: 1969
Time Used: 6688
4. 代理模式(Proxy)
代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
4.1 分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)—— 涉及反射
4.2 应用场景
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:用户代理类处理远程方法调用(RMI)。
- 延迟加载:先加载轻量级的代理对象,需要时再加载真实对象。
- 如当存在大图片时,先加载其他内容,当需要查看图片时,再用代理来打开图片。
4.3 例子
示例代码:测试代理模式
示例代码:NetWork接口
public interface NetWork {
// 上网
public abstract void browse(String url);
}
示例代码:代理类
// 代理类
public class ProxyServer implements NetWork {
private NetWork work;
public ProxyServer(NetWork work) {
this.work = work;
}
private void check() {
System.out.println("联网之前的检查工作...");
}
@Override
public void browse(String url) {
check();
work.browse(url);
}
}
示例代码:被代理类
// 被代理类
public class Server implements NetWork {
@Override
public void browse(String url) {
System.out.println("真实的服务器在访问网站[" + url + "]");
}
}
示例代码:测试代理模式
public class TestProxy {
@Test
public void test() {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse("https://baidu.com");
}
}
执行结果
Connected to the target VM, address: "127.0.0.1:8320", transport: "socket"
联网之前的检查工作...
真实的服务器在访问网站[https://baidu.com]
Disconnected from the target VM, address: "127.0.0.1:8320", transport: "socket"
5. 工厂设计模式
工厂设计模式实现了创建者和调用者的份力,即吵架呢对象的过程屏蔽隔离起来,达到提高灵活性的目的。
5.1 分类
- 无工厂模式
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
5.2 例子
5.2.1 测试使用到的类与接口
示例代码:测试工厂设计模式
示例代码:Car接口
public interface Car {
void run();
}
示例代码:奥迪汽车类
public class Audi implements Car {
@Override
public void run() {
System.out.println("奥迪在跑...");
}
}
示例代码:比亚迪汽车类
public class BYD implements Car {
@Override
public void run() {
System.out.println("比亚迪在跑...");
}
}
5.2.2 无工厂设计模式
示例代码:无工厂设计模式
import org.junit.Test;
/**
* 无工厂模式
* 包含了创建者和调用者
*/
public class TestNoFactory {
@Test
public void test() {
Car a = new Audi();
Car b = new BYD();
a.run();
b.run();
}
}
执行结果
奥迪在跑...
比亚迪在跑...
5.2.3 简单工厂模式
示例代码:测试简单工厂模式
import org.junit.Test;
/**
* 简单工厂模式
*/
class CarFactory {
public static Car getCar(String type) {
if("audi".equals(type)) {
return new Audi();
} else if("byd".equals(type)) {
return new BYD();
} else {
return null;
}
}
}
public class TestSimpleFactory {
@Test
public void test() {
Car a = CarFactory.getCar("audi");
Car b = CarFactory.getCar("byd");
a.run();
b.run();
}
}
执行结果
奥迪在跑...
比亚迪在跑...
5.2.4 工厂方法模式
示例代码:测试工厂方法模式
import org.junit.Test;
/**
* 工厂方法模式
*/
interface Factory {
Car getCar();
}
class AudiFactory implements Factory {
@Override
public Car getCar() {
return new Audi();
}
}
class BYDFactory implements Factory {
@Override
public Car getCar() {
return new BYD();
}
}
public class TestFactoryMethod {
@Test
public void test() {
Car a = new AudiFactory().getCar();
Car b = new BYDFactory().getCar();
a.run();
b.run();
}
}
执行结果
奥迪在跑...
比亚迪在跑...
五、面向对象
云海天:方法
云海天:包
云海天:访问权限
云海天:jar打包
云海天:文档生成器
云海天:类与对象
云海天:new、this、static
1. 面向对象的三大特征
- 封装(Encapsulation):符合JavaBean规范
- 继承(Inheritance):子类与父类
- 多态(Polymorphism):方法重载、方法重写
2. 类与对象
2.1 了解类与对象
- 类:对一类事物的抽象定义。
- 对象:一个实际存在的个体,也称为实例(instance)。
- 类的成员:属性、方法。
2.2 创建类
修饰符 class class_name {
// 属性
修饰符 type var_name = default_value;
// 方法
修饰符 type fun_name(...) {
...
}
}
注:修饰符可为public、protected、缺省(default)、private、static、final。
2.3 创建对象
// 创建对象
class_name instance_name = new class_name(...);
// 访问属性
instance_name.var_name;
// 调用方法
instance_name.fun_name(...);
2.3.1 内存解析
- 堆(Heap): 此内存区域存放对象实例和数组。
- 栈(Stack): 这里指虚拟机栈(VM Stack),存放局部变量,当方法执行完后自动释放。
- 方法区(Method Area): 存储已被虚拟机及加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
2.4 类的访问机制
- 在同个类中,类中的方法可以直接访问类中的成员变量。
- 在不同类间,需要先创建类的对象,再用对象访问类中定义的成员。
注:static方法不能调用或访问非static的方法或变量。
2.4 方法
- Java里的方法不能独立存在,所有的方法必须定义在类里。
2.4.1 普通方法
// 修饰符可为:public、protected、缺省(default)、private、static、final
修饰符 type fun_name(...) {
...
}
2.4.2 方法重载(overload)
方法重载:在同个类中,允许存在同名的方法,只要参数列表不同,即参数个数、参数类型。
int add(int x, int y) {return x + y;}
double add(double x, double y) {return x + y}
int add(int x, int y, int z) {return x + y + z;}
2.4.3 可变个数参数
// jdk 5.0 之前
public static void test(int a, String[] books);
// jdk 5.0 之后
public static void test(int a, String ... books);
注意:
- 传入可变个数参数变量的个数可为任意个。
- 可变个数参数的方法与同名方法构成重载。
- 可变个数参数需要放在参数列表的最后。
- 在方法参数列表中,最多只能存在一个可变个数参数。
2.4.4 方法参数传递机制
Java里方法的参数传递方式只有一种:值传递。即将实际参数值的副本传入方法内,而参数本身不受影响。
对于基本数据类型,传递的是数据值;对于引用数据类型,传递的是地址值。
2.4.5 考察题
(1)疑似考察参数传递?
// TestMethodTransferValue.java
class TestMethodTransferValue {
public static void main(String [] args) {
int a = 10;
int b = 10;
// 要求在method方法调用之后,仅打印出a=100, b=100,请写出method方法的代码。
method(a, b);
System.out.println("a =" + a + ", b=" + b);
}
static void method(int a, int b) {
// 正解
a = 100, b = 200;
System.out.println("a =" + a + ", b=" + b);
System.exit(0);
}
}
(2)参数传值
class Value{
int i = 15;
}
class Test{
public static void main(String argv[]) {
Test t = new Test();
t.first();
}
public void first() {
int i = 5;
Value v = new Value();
v.i = 25;
second(v, i);
System.out.print(v.i);
}
public void second(Value v, int i) {
i = 0;
v.i = 20;
Value val = new Value();
v = val;
System.out.print(v.i + " " + i + " ");
}
}
// A. 15 0 20
// B. 15 0 15
// C. 20 0 20
// D. 0 15 20
A is correct!
(3)对方法的了解
// TestMethodAbout.java
class TestMethodAbout {
public static void main(String [] args) {
int [] iarr = new int[10];
System.out.println(iarr); // 输出什么? 地址√
char [] carr = new char[10];
System.out.println(carr); // 输出什么? 内容√
// 解释:PrintStream.println(char[]); 这个方法会直接打印出char数组的内容
}
}
2.5 构造器
修饰符 class_name(...) {
...
}
2.5.1 作用
创建对象,给对象进行初始化。
2.5.2 特征
- 构造器也称为构造方法,方法名与类名相同。
- 构造器不声明返回类型,方法体内不带有return语句。
- 修饰符不能使用static、final、abstract、synchronized、native。
- 当没有显式定义构造器时,系统会默认提供一个无参的构造器,其修饰符与所属类的修饰符一致。
- 构造器可以被重载,即可存在多个构造器。
- 子类不继承父类的构造器,故不能被重写override,但可通过
super()
调用父类的构造器。
2.6 UML类图
- 修饰符:public(+)、protected(#)、private(-)
- 属性:修饰符 var_name: type
- 方法:修饰符 fun_name(param: type): return_type
3. 继承性(inheritance)、多态性(polymophrism)
3.1 继承的了解
- 继承的出现让类与类之间产生了关系。
- Java只支持单继承和多层继承,不允许多重继承。
- 一个子类只能有一个父类。
- 一个父类可以有多个子类。
- Java中,使用关键字
extends
使子类继承父类。 - 子类继承父类,就继承了父类的方法和属性。
- 子类不能直接访问父类中私有的成员变量和方法。
注:不用仅为了获取其他类中某个功能而去继承。
class sub_class extends super_class {
...
}
3.2 方法重写(override)
本质:子类的方法覆盖从父类继承的方法。
要求:
- 子类重写的方法必须和父类被重写的方法具有相同的方法名、参数列表。
- 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型。
- 子类重写的方法的访问权限不能小于父类被重写的方法的访问权限。
- 子类重写的方法抛出的异常不能大于父类被重写的方法的异常。
- 子类不能重写父类中private、final修饰的方法。
- 如果子类中存在与父类相同方法名和参数列表的静态方法时,子类只是隐藏了父类的方法,并不是重写。
此处应该还有…
3.3 多态
Java引用变量有两种类型:编译时类型和运行时类型。
- 编译时类型由声明该变量时使用的类型决定,即编译看左边。
- 运行时类型由实际赋给改变了的对象决定,即运行看右边。
若编译时类型与运行时类型不一致,就出现了对象的多态性,即父类的引用指向子类的对象,此时该对象也称为上转型(upcasting)对象。
前提:
- 存在继承或实现的关系
- 有方法的重写
Object obj = new Person();
// 就此而言,obj在编译时是Object类型,在运行时是Person类型。
Person per = new Student();
// 上述的变量obj指向了Person类型的对象,per变量执行了Student类型的对象。
// 因此,变量obj、per都可称为上转型对象。
3.4 上转型对象
- 上转型对象不能访问子类中新增的属性和方法。
- 上转型对象调用的方法或属性是从父类继承的或子类重写的。
3.5 例子
示例代码:测试多态性
import com.atguigu.learn.bean.Man;
import com.atguigu.learn.bean.Person;
import com.atguigu.learn.bean.Women;
import org.junit.Test;
/**
* 测试多态性
*/
public class TestPolymorphism {
@Test
public void test() {
Person person = new Person();
person.eat();
System.out.println("==================================");
Man man = new Man();
man.walk();
man.setAge(25);
man.earnMoney();
System.out.println("==================================");
Person person2 = new Man(); // 多态: person2上转型对象
person2.eat();
// person2.earnMoney(); // 上转型对象不能调用新增的方法
Man man2 = (Man)person2; // man2下转型对象
man2.earnMoney();
System.out.println("==================================");
System.out.println("
===========下转型问题============");
// 问题1:编译不过
// Man m1 = new Woman();
System.out.println("1. 编译错误!");
// 问题2:编译通过,运行不通过
try {
Object o1 = new Women();
man = (Man)o1;
} catch (ClassCastException e) {
System.out.println("2. 运行错误!");
}
// 问题3:编译通过,运行通过
Object o2 = new Women();
person2 = (Person)o2;
System.out.println("3. 没有错误!");
}
}
执行结果
人:吃饭
==================================
男人:霸气走路
男人:挣钱养家
==================================
男人:吃很多,长肌肉
男人:挣钱养家
==================================
===========下转型问题============
1. 编译错误!
2. 运行错误!
3. 没有错误!
3.6 包装类(Wrapper)
// TestWrapper.java
class TestWrapper {
public static void main(String [] args) {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // false
Integer m = 1;
Integer n = 1;
System.out.println(m == n); // true
Integer x = 128;
Integer y = 128;
System.out.println(x == y); // false
/* 这里因为在Integer类内部定义了IntegerCache的内部类,在其中保存了从-128到127的缓存数组,
* 当出现在其中的数值时,直接在此数组中查找。
* 因此上述的m和n的地址是相同的,当超出范围时需要另外new对象,因此上述x和y的地址不同。
*/
Object obj1 = true ? new Integer(1) : new Double(2.0);
// 这里因为编译时需要确定对象的类型,所以都会统一类型为double型,故第一个数会自动转化为1.0
System.out.println(obj1); // 1.0 !!!
Object obj2;
if(true)
obj2 = new Integer(1);
else
obj2 = new Double(2);
System.out.println(obj2); // 1
}
}
3.7 类中的代码块
3.7.1 静态代码块:用static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次。
3.7.2 非静态代码块:没有static修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。
class TestStaticBlock {
public int id;
public static int total;
TestStaticBlock() {}
TestStaticBlock(int id) {
this.id = id;
}
static {
// 静态代码块:只在第一次加载类时执行一次
total = 101;
System.out.println("static block: init total = " + total);
}
{
// 代码块:每次新建对象都会执行,且在构造器之前执行。
id = total++;
System.out.println("block: init id = " + id);
}
public static void main(String [] args) {
TestStaticBlock test01 = new TestStaticBlock();
TestStaticBlock test02 = new TestStaticBlock();
TestStaticBlock test03 = new TestStaticBlock();
TestStaticBlock test04 = new TestStaticBlock(99);
// 由于代码块在构造器之前执行,因此新建对象之后test04的id为99
System.out.println("test04 id = " + test04.id);
}
}
运行结果:
static block: init total = 101
block: init id = 101
block: init id = 102
block: init id = 103
block: init id = 104
test04 id = 99
4. 抽象(abstract)
4.1 抽象类与抽象方法
- 抽象类体现的就是模板方法设计模式。
- abstract关键字不能修饰变量、代码块、构造器。
- abstract关键字不能修饰私有方法、静态方法、final方法、final类。
4.2 抽象类
- 抽象类使用
abstract
关键字:abstract class className {...}
- 抽象类中仍含有构造器,便于子类实例化对象,可使用
super()
。 - 抽象类可含有非抽象方法、成员变量。
- 抽象类不能被实例化,抽象类只能被继承,且继承的类必须重写实现抽象方法。
4.3 抽象方法
- 抽象方法使用
abstract
关键字:修饰符 abstract type fun_name();
。 - 抽象方法只有声明,没有方法的实现,以分号结束。
- 含有抽象方法的类一定是抽象类。
5 接口
接口是抽象方法和常量值定义的集合,接口主要用途是被类实现。接口可以认为是特殊的抽象类。在Java中,接口和类是并列的两个结构。
5.1 特点
- 接口中没有构造器,意味着不可实例化。
- 类实现接口时使用
implements
关键字。 - 接口采用多继承机制,即一个类能实现多个接口。
- 接口可以看作是一种规范。
- 所有的成员变量都是
public static final
。 - 所有的抽象方法都是
public abstract
。 - 默认方法使用
public default
修饰符(JDK8)。 - 静态方法使用
public static
修饰符(JDK8)。
5.2 定义接口
在JDK7及之前,只能定义全局常量和抽象方法;在JDK8及之后,可以额外定义默认方法和静态方法。
5.2.1 JDK7及之前
- 全局常量:
public static final
修饰,修饰符可以省略。 - 抽象方法:
public abstract
修饰。
interface Flyable {
// 全局常量:都是public static final修饰的
public static final int MAX_SPEED = 7900;
int MIN_SPEED = 1;
// 抽象方法:都是public abstract修饰的
public abstract void fly();
void stop();
}
5.2.2 JDK8新特性
- 静态方法:
public static
修饰,只能通过接口调用。 - 默认方法:
public default
修饰,可被实现类的对象调用或使用接口.super
调用,也被实现类重写。
(1)静态方法
- 只能通过接口调用,实现类、实现类的对象都无法调用。
(2) 默认方法
-
若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的方法,当子类(实现类)继承父类同时实现接口且子类未重写该方法时,调用的是父类的方法。因为遵守类优先原则,接口中的默认方法会被忽略。
-
若一个接口中定义了一个默认方法,而另一个接口也定义了同名同参数的方法(无论是否为默认方法),在实现类同时实现这两个接口时会出现接口冲突。解决方法:实现类必须重写同名同参数的方法来解决冲突。
示例代码:实现类实现两个接口时出现接口冲突
/* 第一种情况:实现类实现两个接口时出现接口冲突 */
interface Filial {
// 孝顺的
default void help() {
System.out.println("老妈,我来救你了...");
}
}
interface Spoony {
// 痴情的
default void help() {
System.out.println("媳妇,别怕,我来了...");
}
}
class Man implements Filial, Spoony {
/* 解决方法:重写方法 */
@Override
public void help() {
System.out.println("我该怎么办?");
Filial.super.help();
Spoony.super.help();
}
}
- 在子类(实现类)中调用父类、接口的方法
示例代码:避免接口冲突
interface IA {
public static void method1() {
System.out.println("IA: static method1");
}
public default void method2() {
System.out.println("IA: default method2");
}
}
interface IB {
public default void method2() {
System.out.println("IB: default method2");
}
}
class SuperClass {
public void method2() {
System.out.println("SuperClass: default method2");
}
}
class SubClass extends SuperClass implements IA,IB {
// 避免接口冲突,实现类需要重写method2()方法
public void method2() {
System.out.println("SubClass: default method2");
}
public void myMethod() {
method2(); // 调用自己定义的重写方法
super.mrthod2(); // 调用父类的方法
IA.method1(); // 调用接口的静态方法
IB.super.method2(); // 调用接口的默认方法
}
}
5.4 抽象类与接口
区别点 | 抽象类 | 接口 |
---|---|---|
定义 | 包含抽象方法的类 | 主要是抽象方法和全局常量的集合 |
组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法、(jdk8:默认方法default、静态方法static) |
使用 | 子类继承抽象类(extends) | 子类实现接口(implements) |
关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
常见设计模式 | 模板方法 | 简单工厂、工厂方法、代理模式 |
对象 | 都通过对象的多态性产生实例化对象 | |
局限 | 抽象类有单继承的局限 | 接口没有此局限 |
实际应用 | 作为一个模板 | 作为一个标准或表示一种能力 |
选择 | 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限 |
- 类与类之间是单继承的关系。
class A extends B {}
- 类与接口之间是多实现的关系。
class A implements B,C {}
- 接口与接口之间是多继承的关系。
interface A extends B,C {}
5.5 面试题
5.5.1 父类和接口有同名的变量
interface A {
// public static final int x = 0;
int x = 0;
}
class B {
int x = 1;
}
class TestInterface extends B implements A {
public void getX() {
System.out.println(x);
}
public static void main(String [] args) {
new TestInterface().getX();
}
}
解析:TestInterface类中,getX()方法访问变量x编译不通过,因为父类A中和接口B中的变量x都匹配。可以使用
super.x
访问父类的x,使用A.x
访问接口的x。
5.5.2 父类和接口有同名的方法
interface Playable {
void play();
}
interface Bounceabele {
void play();
}
interface Rollable extends Playable,Bounceable {
Ball ball = new Ball("PingPang");
}
class Ball implements Rollable {
private String name;
public Ball(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void play() {
ball = new Ball("Football");
System.out.println("打" + ball.getName());
}
}
解析:Ball类实现了Rollable接口,其中重写的play()方法可以认为是对Playable接口和Bounceable接口中play()方法的实现。错误在于实现的play方法中,ball是常量,不可重新赋值。
6. 内部类
Java中允许将一个类A声明在另一类B中,则类A称为内部类。
内部类按声明的位置可分为成员内部类(静态、非静态)、局部内部类(方法、代码块)。
6.1 成员内部类
6.1.1 特点
- 成员内部类作为一个成员,可以使用public、protected、private、默认值修饰。
- 外部类只能使用public、默认值修饰。
- 成员内部类可以访问外部类的成员,包括私有成员,通过
外部类.this.xxx
或直接xxx
调用。 - 成员内部类可以使用static修饰。
- 静态成员内部类不能调用外部类的非static成员;非静态成员内部类不能定义静态成员变量。
- 成员内部类作为一个类,可以定义属性、方法、构造器等结构。
- 成员内部类可以声明为abstract类,可以被内部类继承,不能被实例化。
- 成员内部类可以声明为final类,不能被继承。
6.1.2 基本使用
- 实例化成员内部类的对象
- 静态成员内部类:
new 外部类.内部类();
- 非静态成员内部类:
new 外部类().new 内部类();
- 静态成员内部类:
- 如何在成员内部类中区分调用外部类的结构
- 访问内部类的成员变量:
this.xxx
- 访问外部类的成员变量:
外部类.this.xxx
- 访问内部类的成员变量:
6.2 局部内部类
- 局部内部类不能使用static、public、protected、private修饰。
- 只能在声明它的方法或代码块中使用,且必须先声明后使用。
- 局部内部类可以访问外部类的成员,包括私有成员。
- 局部内部类可以访问外部方法的局部变量,但此局部变量必须被final修饰。
- JDK7及之前的版本需要显式声明
final
局部变量,JDK8之后可以省略final
。
- JDK7及之前的版本需要显式声明
6.3 匿名内部类
- 匿名内部类不能定义任何静态成员。
- 匿名内部类只有一个对象。
- 匿名内部类对象只能使用多态形式引用。
- 匿名内部类没有构造器。
- 匿名内部类不能继承其他类。
// TestInnerclass.java
interface Person {
public abstract void sayHello();
}
class TestInnerclass {
public static void main(String [] args) {
Person per = new TestInnerclass().getPerson();
per.sayHello();
}
public static Person getPerson() {
// 方法一:局部内部类
/*
class MyPerson implements Person {
public void sayHello() {
System.out.println("你好!");
}
}
return new MyPerson();
*/
// 方法二:匿名内部类
return new Person() {
public void sayHello() {
System.out.println("你好!");
}
};
}
}
六、异常类
云海天:异常类
1. 异常的体系结构
在Java中,将程序执行中发生的不正常情况称为“异常”。
- Error: Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽错误等,一般不编写针对性的修复代码。
- Exception: 因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理,如:空指针访问、试图读取的文件不存在、网络连接中断、数组访问越界等。
- 编译时异常(checked): IOException、ClassNotFoundException、CloneNotSupportedException
- 运行时异常(unchecked): RuntimeException(ArithmeticException、ClassCastException、IllegalArgumentException、IllegalStateException、IndexOutOfBoundsException、NoSuchElementException)
示例代码:测试Error
public class TestError {
public static void main(String[] args) {
// 1. 栈溢出: java.lang.StackOverflowError
// main(args);
// 2. 堆溢出: java.lang.OutOfMemoryError
// Integer[] arr = new Integer[1024*1024*1024];
}
}
示例代码:测试Exception
import org.junit.Test;
import java.util.Date;
import java.util.Scanner;
/**
* 测试Exception
*/
public class TestException {
/**
* 运行时异常
*/
@Test
// NullPointerException
public void test1() {
int[] arr = null;
System.out.println(arr[3]);
String str = "abc";
str = null;
System.out.println(str.charAt(3));
}
@Test
// IndexOutOfBoundsException
public void test2() {
// ArrayIndexOutOfBoundsException
int[] arr = new int[3];
System.out.println(arr[10]);
// StringIndexOutOfBoundsException
String str = "abc";
System.out.println(str.charAt(3));
}
@Test
// ClassCastException
public void test3() {
// 编译时异常
// String str = new Date();
Object obj = new Date();
String str = (String) obj;
}
@Test
// NumberFormatException
public void test4() {
String str = "123";
str = "abc";
int num = Integer.parseInt(str);
}
@Test
// InputMismatchException
public void test5() {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
System.out.println(num);
}
@Test
// ArithmeticException
public void test6() {
int a = 10;
int b = 0;
System.out.println(a / b);
}
/**
* 编译时异常
*/
@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();
*/
}
}
2. 异常处理机制
异常的处理采用“抓抛模型”。
- “抛”指程序在正常执行的过程中,一旦出现异常,就会生成对应异常的对象并抛出。一旦抛出对象,抛出位置其后的代码就不再执行。
- “抓”就是程序对异常的处理方式。
2.1 try-catch-finally
2.1.1 结构
try {
// 可能出现异常的代码
} catch(异常类1 对象1) {
// 处理异常类1的方式
} catch(异常类2 对象2) {
// 处理异常类1的方式
} ...
finally {
// 一定会执行的代码
}
2.1.2 注意点
finally
部分是可选的。finally
中声明的是一定会被执行的代码,即使try中含有return语句、catch中含有catch语句、catch中又出现异常。- 一旦try中抛出的异常对象在catch中匹配,就进入catch中进行处理,完成之后跳出try-catch结构,并继续执行后续代码。
- 若catch的异常类型存在子父类关系,则子类先声明,否则报错;反之不在意顺序。
- catch的异常类对象常用方法:
String getMessage()
、void printStakeTrace()
。 - 使用try-catch-finally结构处理编译时异常时,只是延迟程序报错的时间,程序运行时仍可能报错。
- 开发中,通常不针对运行时异常进行异常处理,而对于编译时异常,一定要考虑异常处理。
2.2 throws
- 结构:
throws + 异常类型
- 结构声明在方法的声明处,表明该方法执行时可能会出现的异常类型。一旦出现异常,后续代码将不再执行。
- 此处理方式并没有真正的处理异常,只是将异常抛给了方法的调用者。
2.3 异常处理注意点
- 重写父类的方法中,子类重写的方法抛出的异常不能大于父类的异常。
- 若父类被重写的方法没有抛异常,则子类重写的方法也不能抛出异常。
- 若几个方法是递进关系且被另一方法A调用,则建议这几个方法采用
throws
处理方式,方法A采用try-catch-finally
处理方式。
2.4 例子
示例代码:测试异常处理
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* 测试异常处理
*/
public class ExceptionHandling {
@Test
public void main() {
try {
method1();
} catch (IOException e) {
e.printStackTrace();
}
}
public void method1() throws IOException {
method();
}
public void method() throws FileNotFoundException, IOException {
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while (data != -1) {
System.out.println((char)data);
data = fis.read();
}
fis.close();
}
}
3. 异常的产生
3.1 异常产生方式
异常对象的产生方式有两种:
- 系统自动抛出的,被调用的方法含有异常。
- 手动抛出异常(throw)
- 抛出现有异常类型(Exception、RuntimrException)
- 抛出自定义异常类型
示例代码:异常产生方式
public class TestThrow {
@Test
public void test() {
Student s = new Student();
// 编译时异常:Exception
try {
s.register02(-1002);
} catch (Exception e) {
System.out.println(e.getMessage());
}
// 运行时异常:RuntimeException
s.register01(-1001);
}
}
class Student {
private int id;
public void register01(int id) {
if(id > 0) {
this.id = id;
} else {
// 抛出运行时异常时,方法不用throws异常
throw new RuntimeException("您输入的学号有误!");
}
}
public void register02(int id) throws Exception {
if(id > 0) {
this.id = id;
} else {
// 抛出编译时异常时,方法必须throws异常
throw new Exception("您输入的学号有误");
}
}
}
3.2 自定义异常类
- 继承现有的异常结构:Exception、RuntimeException
- 提供全局常量:serialVersionUID
- 提供重载的构造器
示例代码:自定义异常类
class MyException extends Exception {
static final long serialVersionUID = 13465653435L;
private int idnumber;
public MyException(String message, int id) {
super(message);
this.idnumber = id;
}
public int getId() {
return idnumber;
}
}
class TestMyException {
public void regist(int num) throws MyException {
if(num < 0)
throw new MyException("人数为负值, 不合理", 3);
else
System.out.println("登记人数:" + num + " 登记成功");
}
public void manager() {
try {
regist(100);
regist(-20);
} catch(MyException e) {
System.out.println("登记失败,出错种类:" + e.getId());
}
System.out.println("本次登记操作结束");
}
public static void main(String [] args) {
TestMyException tse = new TestMyException();
tse.manager();
}
}
面试题 —— 区别
- final、finally、finalize
- throw、throws
- Collection、Collections
- String、StringBuffer、StringBuilder
- ArrayList、LinkedList
- HashMap、LinkedHashMap
- 重写、重载
- 抽象类、接口
- ==、equals()
- sleep()、wait()
项目 —— TeamSchdule
- JavaBean UML图
classDiagram
Equipment — PC : 实现
Equipment — Printer : 实现
Equipment — NoteBook : 实现
Employee <|– Programmer : 继承
Programmer <|– Designer : 继承
Designer <|– Architect : 继承
class Equipment {
<<interface>>
+getDescripment() String
}
class PC {
-Stirng model
-String display
}
class Printer {
-String name
-String type
}
class NoteBook {
-String model
-double price
}
class Employee {
-int id
-String name
-int age
-double salary
+getDetails() Stirng
}
class Programmer {
-int memberId
-Status status
-Equipment equipment
+getTeamDetails() Stirng
+getTeamString() String
}
class Designer {
-souble bouns
}
class Architect {
-int stock
}