- 抽象类的应用: 模板类的设计模式
// 抽象类的应用: 模板方法的设计模式
public class TemplateTest {
public static void main(String[] args) {
Template t = new SubTemplate();
t.spendTime();
}
}
abstract class Template{
// 计算某段代码执行所花费的时间
public void spendTime() {
// 这里还是执行父类的方法
long start = System.currentTimeMillis(); // 执行开始的时间记为start
// 到这里开始执行子类的方法
code(); // 不确定的部分,易变的部分
long end = System.currentTimeMillis(); // 结束的时间记为end
System.out.println("花费时间为: " + (end - start));
}
// 不确定的那段代码的方法,将其抽象化
public abstract void code();
}
class SubTemplate extends Template {
// 重写了父类的抽象方法
@Override
public void code() {
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;
}
}
// 如果isFlag没变过,说明没进过上面if判断,说明就是质数
if (isFlag) {
System.out.println(i);
}
}
}
}
- 抽象类练习:


import java.time.Month;
import java.util.Calendar;
import java.util.Scanner;
public class PayrollSystem {
public static void main(String[] args) {
// 方式一: 手动获取
// 输入本月月份值,如果本月某个Employee对象生日,输出增加工资信息
/*Scanner scan = new Scanner(System.in);
System.out.println("输入当前月份: ");
int month = scan.nextInt();*/
// 方式二: 自动获取
// 获取日历的实例
Calendar calendar = Calendar.getInstance();
// 获取当前实例的月份
int month = calendar.get(Calendar.MONTH);
System.out.println(month);
// 这里只是在堆中开辟空间来保存这个类型的对象,但是没有new对象
// 数组中存放这个引用类型的地址,但是没有调用Employee构造器生成对象,
// 栈中的emps变量指向堆空间造的数组长度是 ,每一个位置声明为Employee类型,但是没有new对象,而是new数组,是数组元素暂时声明为抽象类
Employee[] emps = new Employee[2];
// 数组里new对象就不能放抽象类的对象 ,生日是自定义类所以要new一个自定义类
// 体现多态性,数组声明是父类,实际放的是子类对象
emps[0] = new SalariedEmployee("马森", 1002, new MyDate(1992, 3, 2),10000);
emps[1] = new HourlyEmployee("马云", 2001, new MyDate(1998, 3, 6), 60, 240);
for (int i = 0; i < emps.length; i++) {
System.out.println(emps[i]);
// 打印工资 编译时时抽象类型,执行后是子类重写的方法
double salary = emps[i].earnings();
System.out.println("月工资为: " + salary);
if (month + 1 == emps[i].getBirthday().getMonth()) {
System.out.println("生日快乐,奖励100元");
salary += 100;
}
}
}
}
public abstract class Employee {
private String name;
private int number;
private MyDate birthday;
public Employee(String name, int number, MyDate birthday) {
super();
this.name = name;
this.number = number;
this.birthday = birthday;
}
public abstract double earnings();
// 重写Object类toString方法,因为自己是抽象类,所以不能造对象调用,所以只能让子类调用
// 需要做点修改; 父类当中定义了非抽象方法,就该让子类用,否则就没意义了
@Override
public String toString() {
return "name='" + name + '\'' +
", number=" + number +
", birthday=" + birthday.toDateString();
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setNumber(int number) {
this.number = number;
}
public int getNumber() {
return this.number;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
public MyDate getBirthday() {
return this.birthday;
}
}
public class MyDate {
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
super();
this.year = year;
this.month = month;
this.day = day;
}
// 注意不要调成toString方法,不然输出是地址值
public String toDateString() {
return year + "年" + month + "月" + day + "日";
}
public void setYear(int year) {
this.year = year;
}
public int getYear() {
return year;
}
public void setMonth(int month) {
this.month = month;
}
public int getMonth() {
return month;
}
public void setDay(int day) {
this.day = day;
}
public int getDay() {
return day;
}
}
public class SalariedEmployee extends Employee{
private double monthSalary; // 月工资
public SalariedEmployee(String name, int number, MyDate birthday) {
super(name, number, birthday);
}
public SalariedEmployee(String name, int number, MyDate birthday, double monthSalary) {
super(name, number, birthday);
this.monthSalary = monthSalary;
}
@Override
public double earnings() {
return monthSalary;
}
// 重写toString方法,免去get的麻烦,直接调父类的toString方法
@Override
public String toString() {
return "SalariedEmployee{" +
"monthSalary=" + super.toString() +
'}';
}
public void setMonthSalary(double monthSalary) {
this.monthSalary = monthSalary;
}
public double getMonthSalary() {
return monthSalary;
}
}
public class HourlyEmployee extends SalariedEmployee{
private int wage; // 每小时工资
private int hour; // 月工作小时数
public HourlyEmployee(String name, int number, MyDate birthday) {
super(name, number, birthday);
}
public HourlyEmployee(String name, int number, MyDate birthday, int wage, int hour) {
super(name, number, birthday);
this.wage = wage;
this.hour = hour;
}
@Override
public double earnings() {
return wage * hour;
}
@Override
public String toString() {
return "HourlyEmployee{" +
super.toString() +
'}';
}
}
6.6.接口(interface)
接口的使用
1.接口使用interface来定义
2.Java中,接口和类是并列的两个结构
3.如何定义接口,定义接口中的成员(类也是定义类中的成员)
3.1.jdk7及以前,只能定义全局常量和抽象方法
全局常量声明: public static final的,书写中可省略不写 (接口当中属性都是public的)
抽象方法声明: public abstract的
3.2.jdk8: 除了可以定义全局常量和抽象方法外,还可以定义静态方法,默认方法
4.接口中不能定义构造器! 意味着接口不能实例化
5.java开发中,接口通过让类去实现(implements)的方式来使用(类实现接口)
如果实现类实现(覆盖)了接口中的所有抽象方法,则此实现类就可以实例化
如果实现类没有实现(覆盖)接口中所有的抽象方法,则此实现类仍为一个抽象类
子类覆盖普通父类的方法叫重写,子类覆盖抽象类的抽象方法叫实现,抽象方法本身没有东西,把它实现了一下
实现类覆盖接口中的抽象方法叫实现
6.Java类可以实现多个接口 --> 弥补了Java单继承性的局限性
格式: class AA extends BB implements CC,DD,EE
7.接口与接口之间可以继承,而且可以多继承
8.接口的具体使用,体现多态性
9.接口,实际上可以看做是一种规范
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);
System.out.println(Flyable.MIN_SPEED);
Plane plane = new Plane();
// 类实现接口
plane.fly();
}
}
// 定义接口
interface Flyable {
// 接口成员
// 全局常量
public static final int MAX_SPEED = 7900;
int MIN_SPEED = 1; // 因为都是全局常量,所以public static final关键字可省略不写
// 抽象方法
public abstract void fly();
// 可以省略关键字
void stop();
// 接口中不能定义构造器
/*public Flyable(){
}*/
}
// 具有攻击性
interface Attackable {
// 声明抽象方法
void attack();
}
// Plane类实现接口,就具有相关功能
class Plane implements Flyable {
// 实现接口相当于把接口中的抽象方法拿过来了,相当于也定义了两个抽象方法,类似于子类继承抽象类
@Override
public void fly() {
System.out.println("通过引擎起飞");
}
@Override
public void stop() {
System.out.println("驾驶员减速停止");
}
}
// 实现多个接口,想实例化类就要实现接口中所有的抽象方法
// 先继承父类,再实现具体的接口
class Bullet extends Object implements Flyable, Attackable, CC{
@Override
public void fly() {
}
@Override
public void stop() {
}
@Override
public void attack() {
}
@Override
public void method1() {
}
@Override
public void method2() {
}
}
// 没有实现接口中所有抽象方法还是抽象类
abstract class Kite implements Flyable {
@Override
public void fly() {
}
}
// 接口与接口之间实现多继承
interface AA {
// 抽象方法
void method1();
}
interface BB{
// 抽象方法
void method2();
}
// 既然继承了,那么就有父接口的所有抽象方法
interface CC extends AA, BB{
}
接口的使用
1.接口使用上也满足多态性
2.接口,实际上就是定义了一种规范
3.开发中,体会面向接口编程
// 驱动可以理解为: 接口好多实现类的集合,放的就是接口的实现类,可以看做是接口的实现
public class USBTest {
public static void main(String[] args) {
// 用电脑传输数据,造一个电脑对象
Computer com = new Computer();
// 拿着电脑对象调用传输数据的方法,方法形参需要传入接口类型的对象,
// 接口不能造对象,只能造接口实现类的对象,相当于体现了多态性
// 1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
// 2.创建了接口的非匿名实现类的匿名对象
com.transferData(new Print());
// 3.创建了接口的匿名实现类的非匿名对象,无类名借USB接口名充当一下
USB phone = new USB() {
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机停止工作");
}
};
com.transferData(phone);
// 4.创建匿名实现类的匿名对象
com.transferData(new USB() {
@Override
public void start() {
System.out.println("mp3开启工作");
}
@Override
public void stop() {
System.out.println("mp3停止工作");
}
});
}
}
// 定义一个电脑类
class Computer{
// 定义一个传输数据的方法,需要接触到外部设备,方法需要遵循USB接口的规范
// 从形参的角度看,声明的是USB接口,实际new的是实现USB的实现类 USB usb = new Flash();
// 要用接口必须通过多态方式用
public void transferData(USB usb){ // 传入USB的对象,接口不能创建对象,此时需要传入接口的实现类,抽象类也是如此,体现了多态性
// 传输数据,首先开启实现USB接口
// 不同设备功能不同,提供不同的抽象方法,这里只做两个简单的抽象方法例子
// 编译的时候,会认为调的是接口的抽象方法,实际执行的是实现类的对象重写的方法
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
// 定义一个USB接口,体现了一种规范,只要是跟电脑数据传输的一个规范,就定义成USB了,谁要是想个上面这个电脑传输数据,就都得实现USB这个接口,把这个规范明确一下
interface USB {
// 可以定义常量: 定义了长,宽,最大最小的传输速度等
// 抽象方法: USB功能
void start();
void stop();
}
// 定义一个U盘类实现USB接口
class Flash implements USB{
// 重写接口的抽象方法
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Print implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
- 接口应用: 代理模式(proxy)

// 静态代理: 通过一个类去操作另一个类
public class NetWorkTest {
public static void main(String[] args) {
// 创建一个被代理类的对象作为实现类对象传给代理类作为形参
Server server = new Server();
// 因为被代理类的形参是NetWork接口类型,而接口不能造对象,所以只能传入实现类的对象
// 体现多态性, 声明NetWork接口的引用指向实现接口的实现类对象,相当于多态性的,父类引用指向子类的对象
ProxyServer proxyServer = new ProxyServer(server); // NetWork work = new Server();相当于把server赋给了work
// 从形式上看,调用代理类浏览方法,实际上代理类对象调用方法的时候包含了被代理类对象重写的方法的调用
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("联网之前的检查工作");
}
// 在调browse方法的时候先做校验(调校验方法)
@Override
public void browse() {
check();
// 校验以后就可以联网,但真正联网的是,代理类只是作为一个代理去操作,代理类的对象
// 通过代理类的对象调用browse方法
work.browse();
}
}
- 代理模式练习:
public class StaticProxyTest {
public static void main(String[] args) {
/* //造被代理类对象
RealStar realStar = new RealStar();*/
// 造代理类对象,对外不暴露被代理类对象
Proxy proxy = new Proxy(new RealStar());
proxy.confer();
proxy.bookTicket();
proxy.singContract();
proxy.collectMoney();
proxy.sing();
}
}
// 定义一个明星接口: 明星可以实现的功能
interface Star{
void confer(); // 面谈
void singContract(); // 签合同
void bookTicket(); // 订票
void sing(); // 唱歌
void collectMoney(); // 收钱
}
// 被代理类: 可以实现接口的方法,但自己不想做
class RealStar implements Star{
// 大部分方法由代理类代替做,所以可以不用写
@Override
public void confer() {
}
@Override
public void singContract() {
}
@Override
public void bookTicket() {
}
@Override
public void sing() {
System.out.println("明星唱歌");
}
@Override
public void collectMoney() {
}
}
// 代理类: 被代理类不想做的事,由代理帮做
class Proxy implements Star{
// 通过接口调用代理类
public Star real;
public Proxy(Star real){
this.real = real;
}
@Override
public void confer() {
System.out.println("经纪人面谈");
}
@Override
public void bookTicket() {
System.out.println("经纪人订票");
}
@Override
public void singContract() {
System.out.println("经纪人签合同");
}
@Override
public void collectMoney() {
System.out.println("经纪人收钱");
}
// 代理类通过接口调用被代理类,经纪人叫明星来唱歌
@Override
public void sing() {
// 方法中可以调用方法
real.sing();
}
}
-
接口应用: 工厂模式
-
接口练习: 比较对象大小
public class ComparableCircleTest {
public static void main(String[] args) {
ComparableCircle c1 = new ComparableCircle(3.4);
ComparableCircle c2 = new ComparableCircle(3.6);
int compareValue = c1.CompareTo(c2);
if (compareValue > 0){
System.out.println("c1对象大");
}else if (compareValue < 0){
System.out.println("c2对象大");
}else{
System.out.println("一样大");
}
int comPareValue1 = c1.CompareTo(new java.lang.String("AA"));
System.out.println(comPareValue1);
}
}
public interface CompareObject {
// 若返回值是0,代表相等,若为正数,代表当前对象大,负数代表当前对象小
public int CompareTo(Object o);
}
public class Circle {
// 把double类型改为包装类型
private Double radius;
public Circle(){
}
public Circle(Double radius){
this.radius = radius;
}
public void setRadius(Double radius) {
this.radius = radius;
}
public Double getRadius() {
return this.radius;
}
}
public class ComparableCircle extends Circle implements CompareObject{
// 提供带半径的构造器
public ComparableCircle(Double radius){
super(radius);
}
@Override
public int CompareTo(Object o) {
if (this == o){
return 0;
}
if (o instanceof ComparableCircle){
ComparableCircle c = (ComparableCircle) o;
// 错误写法,有漏洞 精度有损失 假如2.3-2.1=0.2会被误判为0
//return (int) (this.getRadius() - c.getRadius());
// 正确方式一:
/*if (this.getRadius() > c.getRadius()){
return 1;
}else if (this.getRadius() < c.getRadius()){
return -1;
}else{
return 0;
}*/
// 如果把radius类型全部改为Double包装类型
// 在Double包装类源码中,implements了comparable接口判断Double封装的double类型的值谁大谁小,就不用自己写比较方法了
// 当属性radius声明为Double类型时,可以调用包装类的方法,可以自动帮你比较大小
//正确的方式二:
return this.getRadius().compareTo(c.getRadius());
}else{ // 如果不是comparableCirlcle类型
// return 0; // 暂且
// 抛出一个异常类的对象
throw new RuntimeException("传入数据类型不匹配"); // 代替return 0;
}
}
}
public class ComparableCircleTest {
public static void main(String[] args) {
ComparableCircle c1 = new ComparableCircle(3.4);
ComparableCircle c2 = new ComparableCircle(3.6);
int compareValue = c1.CompareTo(c2);
if (compareValue > 0){
System.out.println("c1对象大");
}else if (compareValue < 0){
System.out.println("c2对象大");
}else{
System.out.println("一样大");
}
int comPareValue1 = c1.CompareTo(new java.lang.String("AA"));
System.out.println(comPareValue1);
}
}
- Java8中接口新特性
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
// 实现类和实现类的对象都不能调用接口的静态方法
// s.method1(); // 报错
// SubClass.method1(); // 报错
// 知识点1: 接口中定义的静态方法,只能通过接口来调用
// 说白了接口的静态方法不是让其他实现类调用的,而是自己用的,就像是工具类
CompareA.method1();
// 知识点2: 通过实现类的对象可以调用接口中的默认方法
// 如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的的方法
// 这个默认方法和在父类拿那个方法一样
s.method2(); // 以后见到接口中的默认方法,子类对象可以直接调
// 知识点3: 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
// 那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法 -- 类优先原则
// 如果有间接父类,则就近原则
// 同名方法类优先,同名属性显示区分
// 知识点4: 如果实现类没继承父类而是实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
// 那么在实现类没有重写此方法的情况下,报错. --> 接口冲突
// 既然冲突了但就是想同时实现两个接口,这就要必须在实现类中重写此方法
// 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的抽象方法,
// 那么在实现类将被认为同时重写多个接口的同名同参数的抽象方法
s.method3();
// SubClass.method2();// 报错,method2不是静态的
// System.out.println(s.x); // 调用父类和接口的同名变量会报错,编译器无法判断是谁的变量,属性不能被覆盖,要显示区分
}
}
public class SuperClass {
// int x = 2;
public void method3(){
System.out.println("superclass: 北京");
}
}
public interface CompareA {
// int x = 1;
// 静态方法: 可以直接通过接口调用
public static void method1(){
System.out.println("CompareA: 北京");
}
// 默认方法: 通过接口的实现类调用
public default void method2(){
System.out.println("CompareA: 上海");
}
// public关键字可以省略,而不是方法的权限变成缺省,类似于switch循环里的default
// 就像实现类的对象调用的普通非静态方法一样
default void method3(){
System.out.println("CompareA: 广州");
}
}
public interface compareB {
default void method3(){
System.out.println("compareB: 上海");
}
}
// 实现类实现接口
// 没有报错,因为接口没有抽象方法,都有方法体
class SubClass extends SuperClass implements CompareA,compareB{
// 重写接口的默认方法
@Override
// 覆盖的方法有方法体都叫重写,覆盖的方法没有方法体(抽象方法)就叫实现
public void method2() {
System.out.println("subclass: 上海");
}
public void method3(){
System.out.println("重写接口: 北京");
}
// 知识点5: 如何实现子类(或实现类)的方法中调用父类,接口中被重写的方法
// 定义一个特有的普通方法
public void myMethod(){
method3(); // 调用自己定义的重写方法
super.method3(); // 调用父类中声明的方法
// 调用接口的默认方法
CompareA.super.method2();
compareB.super.method3();
}
}
- 接口新特性练习:
// 实现类没有重写实现了多个接口的同名同参数的方法,会接口冲突
// 重写后不会报错
// 子类在同时继承父类和实现接口的情况下,且子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法 -- 类优先原则
// 子类重写方法后,优先调用自己的
public class Man extends Father implements Filial,Spoony {
public void help(){
System.out.println("我该救谁呢");
Filial.super.help(); // 救妈的
Spoony.super.help(); // 救媳妇的
}
}
class Father{
public void help(){
System.out.println("儿子就我媳妇");
}
}
interface Filial{ // 孝顺
default void help(){
System.out.println("老妈我来救你");
}
}
interface Spoony{
default void help(){
System.out.println("媳妇我来救你");
}
}
6.7.类的成员之五: 内部类
类的内部成员之五: 内部类
1.Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
2.内部类的分类: 成员内部类(静态,非静态) vs 局部内部类(方法内,代码块内,构造器内)
3.成员内部类:
一方面,作为外部类的成员:
可以调用外部类的结构
另一方面,作为一个类:
类内可以定义属性,方法,构造器等
可以被final关键字修饰,表示此类不能被继承.不用final,就可以被继承
可以被abstract关键字修饰,表示不能被实例化
可以被static关键字修饰
可以被4种权限修饰符修饰: public,default,protected,private
4.关注如下的3个问题
4.1.如何实例化成员内部类的对象
4.2.如何在成员内部类中区分调用外部类的结构
4.3.开发中局部内部类的使用
public class InnerClassTest {
public static void main(String[] args) {
// 4.1.
// 创建Dog实例(静态的成员内部类)
Person.Dog dog = new Person.Dog(); // 通过外部类.成员内部类的构造器来创建内部类对象
dog.show();
// 创建Bird实例(非静态的成员内部类)
Person p = new Person(); // 调用非静态的结构要先实例化类
Person.Bird bird = p.new Bird(); // 通过外部类对象.new成员内部类的构造器造内部类的对象
bird.sing();
bird.display("黄鹂");
}
}
// 正常的外部类只能是public和缺省权限修饰符修饰
class Person{
String name = "小明";
int age;
public void eat(){
System.out.println("人吃饭");
}
// 静态成员内部类
abstract class Cat{
}
static class Dog{
String name;
int age;
public void show(){
System.out.println("卡拉是只狗");
// 静态类整体来看是静态的,静态的加载时间早,非静态方法时间晚,所以不能被调用
// eat(); // 静态类不能调用非静态方法
}
}
// 非静态成员内部类
class Bird{
String name = "杜鹃";
// 非静态成员内部类的构造器
public Bird(){
}
public void sing(){
System.out.println("我是一只小鸟");
// Person类的对象的eat方法,其中Person.this.可以省略
Person.this.eat(); // 调用外部类的非静态属性
eat(); // 和上一条一样
System.out.println(age); // 内部类和外部类属性没有重名的就没有冲突,直接可以省略去调
}
// 4.2.
// 内部类和外部类有重名属性的调用方法
public void display(String name){
System.out.println(name); // 方法的形参的name属性
System.out.println(this.name); // 内部类的属性: display方法所在类的name变量
System.out.println(Person.this.name); // 外部类的属性: 当前Person类的对象的name变量
}
}
public void method(){
// 局部内部类
class AA{
}
}
{
// 局部内部类
class BB{
}
}
public Person(){
// 局部内部类
class CC{
}
}
}
public class InnerClassTest1 {
// 4.3.
// 开发中很少见
public void method(){
// 局部内部类
class AA{
}
}
// 返回一个Comparable接口的类的对象
public Comparable getComparable(){
// 方式一:
// 创建一个实现了Comparable接口的有名实现类: 局部内部类
/*class MyComparable implements Comparable{
// 重写接口中的方法
@Override
public int compareTo(Object o) {
return 0;
}
}
// 返回有名实现类的匿名对象
return new MyComparable();*/
// 方式二: 创建实现Comparable接口的匿名实现类(匿名内部类)的匿名对象
// 返回一个接口的匿名实现类的匿名对象
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
- 局部内部类使用的一个注意点:
- 在局部内部类的方法中(比如: show)如果调用外部类所声明的方法(比如: method)中的局部变量的话(比如: num),
要求此局部变量声明为final的.- 外部类生成一个字节码文件,内部类也是独立的字节码文件,两个文件就对应两个类,因为两个两个文件存储空间和生命周期都不同,外部类传进去内部类的属性
相当于只是副本(复制的),所以局部内部类无法修改外部类的属性- jdk7及之前版本: 要求此局部变量显式的声明为final的
- jdk8及以后的版本: 可以省略final的声明
public void method(){
int num = 10; // 省略了final关键字
class AA{
public void show(){
// num = 20; // 报错,不能修改
System.out.println(num);
}
}
}
7.异常处理
throw和throws的区别?
- throw表示抛出一个异常类的对象,生成异常类对象的过程,生命在方法体中
- throws属于处理异常的一种方式,声明在方法的声明处
7.1.异常概述与异常体系结构
常见异常举例
一.异常体系结构,都是继承关系
顶级的异常类: java.lang.Throwable
有以下子类:
java.lang.Error: 一般不编写针对性的代码进行处理.
java.lang.Exception: 可以进行异常的处理
分为:
编译时异常(受检异常)(checked)
IOException
FileNotFoundException
ClassNotFoundException
运行时异常(非受检异常)(unchecked/RuntimeException)
NullPointerException
ArrayIndexOutOfBoundsException
ClassCastException
NumberFormatException
InputMmismatchException
ArithmeticException: 算术异常
面试题: 常见的异常类有哪些? 举例说明
public class ExceptionTest {
// -----以下是编译时异常-----
// 编译时就报错,在命令行里操作的话,用javac.exe编译这个文件就报错,
// 意味着执行完后对应的字节码文件不会生成
@Test
public void test7() {
// 读数据操作
// 指明file来自于hello,类需要导包
File file = new File("hello.txt");
// 创建一个文件流
// FileInputStream fis = new FileInputStream(file);
// fis做read操作,让他在文件当中读一个字节,并存储起来
// int data = fis.read();
// 判断数据是否不等于-1,涉及到read方法的使用,如果是-1表示文件到末尾了,不是-1,文件就还有数据
/*while (data != -1) {
// 输出数据,这里读的是一个字节
System.out.println((char) data); // 假如文件里写的a,这里就输出97,因此把它强转成char类型
// 迭代条件,看下一个是不是-1,到文件末尾就退出循环
data = fis.read();
}*/
}
// fis资源流,不会像上面的file对象执行完就自动会被垃圾回收器回收掉
// 资源想scanner一样需要显式的做关闭操作
// fis.close();
// -----以下是运行时异常-----
// 尝试放到一个源文件当中,用命令行运行,执行javac时编译是不报错的,
// 也会生成对应的字节码文件,当执行java时去解释运行才会报错,实际上就是运行时异常
// ArithmeticException 算术异常: 一般有除0操作
// 任何一个数除以0都会得到无限大的结果
@Test
public void test6() {
int a = 10;
int b = 0;
System.out.println(a / b);
}
// InputMismatchException 输入不匹配异常
@Test
public void test5() {
// scanner没关闭可能会内存泄露
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);
}
@Test
// ClassCastException 类型转化异常
public void test3() {
//Object obj = new Date(); // 向上转型/多态
// String str = (String) obj; // 向下转型/强转0
}
// 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 = null;
System.out.println(str.charAt(3));*/
}
}
7.3.异常处理机制一: try-catch-finally
-
异常处理方式的概述:
异常处理: 抓抛模型
- 过程一:
- "抛": 程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象.(java都是面向对象编程,每个异常也都定义成一个类了)
并将此对象抛出,抛给程序的调用者.- 一旦抛出对象异常对象以后,其后的代码就不再执行
- 过程二:
- "抓": 可以理解为异常的处理方式: ①try-catch-finally ②throws
-
处理异常: try-catch方式:
二. try-catch-finally的使用
try{
// 可能出现异常的代码
}catch(异常类型1 变量名1){
// 处理异常的方式1
}catch(异常类型2 变量名2){
// 处理异常的方式2
}catch(异常类型3 变量名3){
// 处理异常的方式3
}
....
finally{ // 关键字,就想switch循环中的default一样,不一定要写
// 一定会执行的代码
}
说明:
1.finally是可选的.
2.使用try将可能出现异常代码用大括号包起来,在执行过程中,一旦出现异常,
就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
3.一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理.一旦处理完成,
就跳出当前的try-catch结构,即使下面还有符合的catch结构也不会往下执行(在没有写finally的情况),继续执行其后的代码
4.catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓.
catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面.否则报错
5.常用的异常对象处理的方式:
①String getMessage(),打印获取异常的信息
②printStackTrace(), 打印整个堆栈的信息
6.在try结构中声明的变量,再出了try结构后,就不能再被调用
7.try-catch-finally结构可以嵌套使用
总结: 如何看待代码中的编译时异常和运行时异常?
体会1:
用try-catch-finally处理编译时异常,使得程序编译时不再报错,但是运行时仍可能报错.
相当于用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现.
体会2:
开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了.
针对于编译时异常,就一定要考虑异常的处理.因为编译时异常都不处理,编译都过不了了,字节码文件都生成不了
public class ExceptionTest1 {
// 处理编译时异常
@Test
public void test2(){
try {
File file = new File("hello.ext");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while (data != -1){
System.out.println((char) data);
data = fis.read();
}
fis.close();
// 运行也不一定过,如果没有hello文件,照样会报运行异常
// FileNotFoundException是IOException的子类,所以两个异常处理的位置不能颠倒
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
@Test
public void test1(){
// 出去旅行
String str = "123";
str = "abc";
int num = 0; // 先赋值为0 避免外部调用报错,不赋值的话编译器担心try里面没赋值上,外部变量相当于没初始化,所以外部调用会报错
try { // 把可能出现异常的代码包起来
// 突然到这生病了,生病以后病毒(异常对象)就出现了
num = Integer.parseInt(str); // 这里出现异常
// 一旦上面一条语句出现异常,就会生成一个对应异常类型的对象,然后抛出
System.out.println("hello-1");
// 然后在catch这里捕获,正好类型匹配就进去了,所以输出里面的语句
// 输出完后相当于把异常对象处理掉了
// 并列写了两个结构
//NullPointerException和NumberFormatException没有关系,是一个父类不同的两个子类,之间没有继承关系
}catch (NullPointerException e){ // e变量只在当前catch中有效,父类变量可以接收他任何子类的对象
// 处理异常的方式
// 吃药
System.out.println("出现空指针异常");
}catch (NumberFormatException e){
// catch中常调的两个方法
// getMessage(); 返回String类型,打印获取异常的信息
// System.out.println(e.getMessage());
// (更常用)printStackTrace(); 没有返回值, 打印整个堆栈的信息
e.printStackTrace();
// System.out.println("出现数值转换异常");
}catch (Exception e){
System.out.println("出现异常");
}
// 处理完后程序就能正常继续往下走了
// 恢复正常
System.out.println("hello-2");
// 在try外部声明,在try内部赋值使用,就不会报错
System.out.println(num); //在try里声明的变量出了try大括号就不能用
}
}
- finally的使用:
try-catch-finally中的finally的使用:
1.finally是可选的
2.finally中声明的是一定会被执行的代码.即使catch中又出现异常了,
try中有return语句,catch中有return语句
3.像在数据库连接,输入输出流,网络编程Socket等资源,jvm是不能自动的回收的,我们需要手动的进行资源的释放.
此时的资源释放,就需要声明在finally中. 为什么要声明在finally里呢? 以防关闭的操作或释放资源的操作受到上面的代码有异常的干扰导致让他不能执行了,不管有没有异常,资源是一定要被释放的,所以才会声明在finally中
public class FinallyTest {
@Test
public void test2(){
FileInputStream fis = null; // 如果在try结构中声明和初始化变量,出了try结构就不能被调用了
try {
File file = new File("hello.txt"); // 文件路径属于相对路径
fis = new FileInputStream(file); // 有可能不报异常,创建成功
int data = fis.read(); // 有可能报异常
while (data != -1) {
System.out.println(data);
fis.read();
}
// fis.close(); // 假如上面 int data = fis.read();才报异常,会直接跳到下面获取IOException,会忽略这里的关闭操作,所以不能写在这
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally {
try {
if (fis != null) // 判断fis空间是否创建成功,如果创建失败,则fis为空指针,无需释放资源,加个判断处理掉空指针异常
fis.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
@Test
public void testMethod(){
int num = method();
System.out.println(num);
}
// 返回int类型的方法
public int method(){
try {
int[] arr = new int[10];
// System.out.println(arr[10]);
return 1;
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
return 2; // 万一上面输出数组元素出异常,就会跳到捕获异常,所以这里也要返回一下
}finally {
System.out.println("我一定会在退出方法前执行");
// return 3;
}
}
@Test
public void test1(){
// int a = 10;
// int b = 0;
// System.out.println(a / b); // 不通过异常处理直接输出会报算术异常
try {
int a = 10;
int b = 0;
System.out.println(a / b);
}catch (ArithmeticException e){
// e.printStackTrace();
int[] arr = new int[10]; // 假如异常没有处理(用try包起来),他出现异常之后就会直接退出test1方法结束
System.out.println(arr[10]);
}catch (Exception e){
e.printStackTrace();
}finally {
// 报异常一定是退出方法前才一定会执行的代码
System.out.println("我好帅啊");
}
// 报异常退出就不会执行,程序出现异常方法就终止了
System.out.println("我好帅啊!!");
}
}
- 编译时异常和运行时异常的不同处理
体会2: 开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了.
针对于编译时异常,就一定要考虑异常的处理.因为编译时异常都不处理,编译都过不了了,字节码文件都生成不了
7.4. 处理异常: throws方式
异常处理的方式二: throws + 异常类型
1."throws + 异常类型"写在方法的声明处.指明此方法执行时,可能会抛出的异常类型.
一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后面的异常类型时,就会被抛出.(相当于甩锅给别人)
和try-catch-finally有点区别,没有一个所谓finally的结构了,异常代码后续的代码,就不再执行!
2.体会:try-catch-finally: 真正的将异常个处理掉了
throws的方式只是将异常抛给了方法的调用者.并没有真正将异常处理掉
public class ExceptionTest2 {
public static void main(String[] args) {
// 把异常处理掉了,到main方法就不能再往上抛了
try {
method2();
}catch (IOException e){
e.printStackTrace(); // 如果处理方式不同,throws异常就写多个,如果处理方式一样,还有异常处理是子父类关系,就没必要写多个
}
}
// 假如在方法中处理掉异常的方法
public static void method3(){
try {
method2();
}catch (IOException e){
e.printStackTrace();
}
}
// method1抛给调他的主体,现在问题归集到method2上
// method2可以继续往上抛,处理都一样的异常,合成一个父类异常就好
public static void method2() throws IOException{
method1(); // method1的异常就抛到method2了,抛的是编译时异常
}
// 本质上没有把异常catch住干掉,而是采取向上抛的策略,让他上一级处理
public static void method1() throws FileNotFoundException, IOException {
File file = new File("hello.txt"); // 读取文件数据
FileInputStream fis = new FileInputStream(file); // 创建文件流
int data = fis.read(); // 做read操作,读取字节信息并且存储下来
while (data != -1){
System.out.println((char) data); // 转成字符型打印文件中每一个字节
data = fis.read(); // 继续读取下一个字节
}
fis.close();
// System.out.println("hahaha!"); // 异常后续的代码不再执行
}
}
- 重写异常方法抛出的规则
方法重写的规则之一:
子类重写的方法抛出的异常类型不大于(小于等于)父类被重写的方法抛出的异常类型
public class OverrideTest {
public static void main(String[] args) {
OverrideTest test = new OverrideTest();
test.display(new SubClass());// new一个子类对象,多态性
}
// 形参传入一个父类的声明
public void display(SuperClass s){
// method方法出现的IO异常处理掉
try {
// 多态的时候通过s对象调方法,实际调的是子类重写的方法
s.method();
// 子类重写的方法恰好也抛了异常,如果子类抛的异常比父类还大,那这里catch的就罩不住了,明明把异常处理掉了还出现异常
// 这里catch的异常类型就是父类抛出的异常类型
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 父类
class SuperClass{
// 如果父类的方法中没有用throws抛异常,那子类绝对不能用throws抛异常
public void method() throws IOException{
}
}
// 子类
class SubClass extends SuperClass{
// 这算重写,子类的异常可以比父类小到一个极限,没有也是可以的
public void method() throws FileNotFoundException {
}
}
- 开发中如何选择哪种方式处理异常
3.开发中如何选择使用try-catch-finally还是使用throws?
3.1.如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能用throws,
意味着如果子类重写的方法中有异常,必须用try-catch-finally方式处理
3.2.执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的.建议这几个方法用throws的方式进行处理.而执行的方法a可以考虑用try-catch-finally方式进行处理
递进关系,前面出异常了,后面也不要执行了,没有意义
在一个方法里try-catch-finally和throws选择一种处理异常就好,两种都用,throws就没意义了.(好比病好了,还要上级汇报)
真实出现运行时异常还是要修改代码
7.5.手动抛出异常对象: throw
try自动抛,throw手动抛,catch抓住处理,throws让别人处理
public class StudentTest {
public static void main(String[] args) {
try { // 其他方法向上(throws)抛出后,就要在main方法里解决
Student s = new Student();
s.regist(-1001); // 出现Exception异常,下面的输入语句不执行
System.out.println(s); // 没有被赋值成功,输出的还是初始值
}catch (Exception e){
System.out.println(e.getMessage()); // 这里相当于异常类的对象的message的get方法
}
/*Student s = new Student();
s.regist(-100);
System.out.println(s);*/
}
}
class Student{
private int id;
public void regist(int id) { // 这里体现异常的处理方式,在方法声明处,向上抛出异常类型对象
if (id > 0){
this.id = id;
}else{
// System.out.println("输入的数据非法");
// id为负数时候手动抛出(创建)异常对象
// 异常处理有个方法叫getMessage,相当于这个异常类通过构造器给里面的message属性做了赋值,该方法就是这个属性的get方法,这里写的啥,下边就能拿到了
// 运行时异常,编译时不会报错,运行后才报错
// throw new RuntimeException("输入的数据非法"); // 这个异常有个构造器是带形参的
// 如果想处理编译时异常,就创建Exception对象,其中包括运行时异常和编译时异常
// 构造器里的形参就是message
// throw抛出异常对象后就要处理抛出的这个对象
throw new RuntimeException("输入的数据非法"); // 这里体现手动生成一个异常对象,是在方法内
// throw new Exception(); // 如果想处理编译时异常就创建Exception,处理可以try-catch也可以在方法声明处throws + 异常类型
// throw new MyException("不能输入负数"); // 必须要抛异常类的对象才可以
// 错误的,只有异常体系的才能throw,其他的对象都不能throw
// throw new String("不能输入负数");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
7.6.用户自定义异常类
如何自定义异常类?
1.继承于现有的异常结构: RuntimeException(运行时异常: 可以考虑不用显式的去处理), Exception(必须考虑显式的处理编译时异常
2.提供全局常量: serialVersionUID: 序列号: 跟类共存的,只有一份,可以理解为类的唯一标识
3.提供重载的构造器
// 想变成一个异常,继承现有的异常体系就成了; 继承关系就是谁 is a 什么(父类)
public class MyException extends Exception{
static final long serialVersionUID = -703489719074523439L;
// 定义一个空参构造器
public MyException(){
}
// 定义一个带参构造器,形参里的message不是自己定义的属性,而是父类里有的
public MyException(String msg){
super(msg);// 所以直接做一个关于父类的调用
}
}
- 异常处理练习: 基本使用
public class ReturnExceptionDemo {
public static void main(String[] args) {
try {
methodA();
} catch (Exception e) {
System.out.println(e.getMessage());// 把message属性的值拿到了
}
methodB();
}
public static void methodA() {
try {
System.out.println("进入方法A"); //1
// 通过异常类的构造器给message的属性赋值
throw new RuntimeException("制造异常"); //3
}finally{
System.out.println("用A方法的finally"); //2
}
}
public static void methodB(){
try {
System.out.println("进入B方法");
return;
// 万一try里有运行时异常,运行时异常可以不处理,可以不用写catch,finally还是会保证代码一定会执行
}finally{
System.out.println("调用B方法的finally");
}
}
}
-异常处理综合练习:
public class EcmDef {
// 接收命令行的参数,从main方法的形参接收
public static void main(String[] args) {
try {
// 因为args是String型所以要类型转换
// 可能转换出问题,
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
int result = ecm(i, j);// 调方法要传入实体值,此时就要拿main的形参处理
System.out.println(result);
}catch(NumberFormatException e){
System.out.println("数据类型不一致");
}
// 缺少命令行参数,少写或一个都没写
catch (ArrayIndexOutOfBoundsException e){
System.out.println("缺少命令行参数");
}catch (ArithmeticException e){
System.out.println("除0");
}catch (EcDef e){
System.out.println(e.getMessage());
}
}
// 在主类中定义一个异常方法(ecm)完成两数相除功能
public static int ecm(int i, int j) throws EcDef{
if (i < 0 || j < 0){
// 创建自定义异常对象,此对象为非非运行时异常,就要在方法声明处做异常处理
throw new EcDef("分子或分母为负数了");
}
return i / j;
}
}
// 自定义异常类
public class EcDef extends Exception{
static final long serialVersionUID = -3387515324124229948L;
// 提供空参构造器
public EcDef(){
}
// 提供带参构造器,
public EcDef(String msg){
super(msg); // 调用父类的属性
}
}
网友评论