JAVA SE 基础篇 L11-L12

L11

1 统一与变化的和谐–抽象类

(1) 抽象方法的作用

在L8的继承中,讨论了继承是自顶向下还是自底而上的问题,最后得出的结论是:在具体编程过程中,通常采用了自底而上的方式,在众多类中抽象出类似或雷同的部分来构建基类

考虑下面的问题
对几种几何图形的属性和简单计算:

长方形
	成员:名称,长,宽
	方法:输出,周长,面积
正方形
	成员:名称,边长
	方法:输出,周长,面积
圆
	成员:名称,半径
	方法:输出,周长,面积

对于上述的3个类,通过观察可以发现它们都有名称成员,都有输出,周长,面积的计算方法,将这些共同点抽取出,可以构成一个基类Shape类,对于长方形的成员,正方形的成员,圆的成员可以在各自的类中单独定义,然后让长方形类,正方形类,圆类派生于Shape类就完成了一次自底向上的抽象过程

在Shape类的具体实现中发现,输出名称是确定无疑的,这个方法完全可以在Shape类中实现,子类直接继承Shape调用即可,但是周长和面积的计算对于3种几何图形各自不同,无法在Shape中统一实现

对于任何形状都存在着周长和面积的要求,确定每种形状都必须实现计算周长和面积,这其中存在着 统一和变化

统一:每种图形都必须有周长和面积的计算
变化:不同图形的周长和面积的计算各有不同

java为了处理这样的问题,提出了抽象方法

(2) 抽象方法和抽象类

用关键字abstract修饰一个方法,并且只声明而不具体实现这个方法,就可以生成一个抽象方法

//声明计算周长和计算面积的抽象方法
public abstract double perimeter();
public abstract double area();

包含至少一个抽象方法的类,必须用abstract修饰称为抽象类

在这里插入图片描述

抽象类是不能实例化的,因为抽象类存在未实现的方法,由这个抽象类派生出的子类,如果依然没有实现其抽象方法,则这个子类也是抽象类

由Shape类派生出的Square类:
在这里插入图片描述
由Shape类派生出的Rectangle类:
在这里插入图片描述

(3) 抽象类编程思想与实现过程

抽象类通常是在很多类的基础上进行抽象形成的,因此抽象类由确定的成分和不确定的成分组成

确定的成分:通常指那些类中固定的,固有的和确定的成员和方法这些成分在抽象类中实现,再用派生的方式让子类继承,从而实现了代码复用

不确定的成分:是那些类彼此大致相同但具体实现又不同的部分,相同点在于这些类都存在这些成分,不同点在于这些成分的具体实现方法有所不同

抽象类的实现过程:
1,通过继承抽象类,并实现其抽象方法,产生子类
2,在子类中实现抽象方法,完成具体需求

2 内部类

(1) 内部类

在一个类中,可以定义其他类,这些类称为内部类

内部类所在的类称为“外部类”,内部类可以直接引用外部类的成员和方法,不受权限修饰符的限制外部类的方法可以通过内部类的对象引用内部类的所有成员和方法

package com.mec.about_innerclass;

public class OuterClass {
	
	//定义三种不同权限修饰符的成员
	private int privateMember;
	protected int protectedMember;
	public int publicMember;
	
	public OuterClass() {
		privateMember = 1;
		protectedMember =2;
		publicMember = 3;
	}
	
	//定义三种不同权限修饰符的方法
	private void privatefun() {
		System.out.println(privateMember);
	}
	
	protected void protectedfun() {
		System.out.println(protectedMember);
	}
	
	public void publicfun() {
		System.out.println(publicMember);
	}
	
	/**
	 * OuterClass的内部类
	 * @author coisini1999
	 */
	public class InnerClass {
		
		private int innerMember;
		
		public InnerClass() {
			innerMember = 4;
		}
		
		//内部类中可以直接使用外部类的所有成员和方法
		//且不受权限修饰符的限制
		private void innerfun() {
			privateMember++;
			protectedMember++;
			publicMember++;
			
			privatefun();
			protectedfun();
			publicfun();
		}
		
	}
	
	//外部类可以定义内部类对象的成员
	private InnerClass innerObject;
	
	//外部类的方法可以通过内部类的对象引用内部类
	//的所有成员和方法
	public void fun() {
		
		innerObject = new InnerClass();
		
		innerObject.innerfun();
		System.out.println(innerObject.innerMember++);
	}
		
}

内部类.class文件存放的位置:
在这里插入图片描述

(2) 匿名内部类

先看一个简单的抽象类:
在这里插入图片描述
对于这个抽象类,现在不用再派生子类(各种品种的鸟)来完成cry的抽象方法,而是直接在此类中进行 “实例化”,这里的实例化会引出一个问题:抽象类没有完成抽象方法的具体实现是不能直接实例化的,但是可以 “非直接实例化”

在这里插入图片描述

		Bird lark = new Bird("百灵鸟") {		
			@Override
			public String shout() {
				return "婉转";
			}	
		};
		lark.print();
		
上述代码的书写格式很独特:
new 抽象类构造方法() {
	实现该抽象类的所有抽象方法
};
其本质是生成了一个类,不过这个类没有机会命名
所以该类称为 匿名内部类,但它会生成相应的类文件
		new Bird("乌鸦") {
			@Override
			public String shout() {
				return "刺耳";
			}	
		}.print();
		
上述写法的本质是先产生一个匿名内部类
这里的写法连匿名类的对象名都省略了
new关键字返回的实例的首地址就是对象
所以new的返回结果就是这个类的对象,当然可以.print()调用它的方法

在这里插入图片描述
Test是该测试类,Test$1和Test$2是Test的匿名内部类,1和2是java编译器自动加上去的,这就是匿名的名称形式,用以区分不同的匿名内部类

(3) 匿名内部类的优点

1,不需要明确地产生派生类,这对于简单问题,明显提高了编程效率
2,在需要的时候临时产生匿名内部类,实现抽象方法,这样更灵活
3,一个匿名内部类对应一种抽象方法的实现,这样更紧凑

(4) 抽象类的具体实现方式视情况而定

抽象类具体怎么使用,应根据情况而定,没有非常固定的方式,如果派生类简单,可以使用匿名内部类实现抽象类的抽象方法,如果派生类有庞杂的不同于抽象类的成员和方法,还是需要派生子类,在子类中实现抽象类的抽象方法

匿名内部类连名字都没有,就更不用提构造器了,以及在其中定义变量,这些变量都无法被初始化,更不可能被匿名内部类产生的对象调用,所以仅将匿名内部类用于简单用途

3 高度抽象–接口

(1) 接口 interface

interface即接口,交界面。在程序设计中接口可以特指“函数三要素”,即函数名称,参数和函数返回值类型,当规定了某个函数的接口,同时规定了函数的功能,但是不规定函数功能的具体实现过程(不关心内部编码),那么这个函数就被认为是 “已经确定的函数”

这样已经确定了接口的函数,可以在任何需要它的地方调用它,而不用关心它内部的实现原理(C中使用printf,scanf),这使得,基于“接口”所编写的代码,在功能实现过程发生改变时,只要接口不变,应用层代码无需变动,这十分有利于技术的更新换代,也利于代码的维护(由于应用层代码不变,就不需要测试,维护成本)

接口在java中可以与继承平起平坐,java非常多的设计技巧都基于接口和继承这两种功能,java设计原则有一条就是 面向接口编程

(2) interface的基本语法

接口类似于类,但与类相比,需要遵守两个原则:
1,接口中的成员只能是 公有,静态,常量,即成员必须是public static final联合属性

2,接口中的方法只能是 公有,抽象方法,即方法必须是
public abstract联合属性

在接口中如果不声明联合属性,会默认添加成员和方法的联合属性

在这里插入图片描述
上述两个成员和方法的本质是一样的,一般情况下
使用InterfaceOne的方式

(3) 接口的实现–implements

接口不同于类,其使用方式是通过实现来完成的,实现对应一个关键字"implements",通常建立一个类,该类实现一个或多个接口,从而达到使用接口的目的

先定义两个接口:
在这里插入图片描述
创建一个实现上述两个接口的类:
在这里插入图片描述
注意一条语句:

public class SomeClass implements Ipriceable,IShapeable

SomeClass同时实现了两个接口,在implements关键字后,两个接口用逗号分隔,这一点不同意类的继承,Java中只能单类继承,但允许实现多接口

实现接口的类,必须实现接口内的抽象方法,否则该类就是抽象类

下面给一个Demo类,演示一些类对象和接口间的“继承”关系:
在这里插入图片描述
实现了接口的类的对象,一定能调用接口规定的所有方法

在这里插入图片描述

(4) Java的单类继承和多接口实现

java在类的继承时不允许同时继承多个基类,但是可以实现多个接口

在java出现前,面向对象程序设计语言已经存在,如C++,C++虽然功能强大,但太过复杂,C++保持了C中的指针,C++允许多重继承,C++除了允许函数重载外还允许运算符重载,这些原理复杂,功能强大的手段,对使用者的要求是极高的

java开发者在开发java时,核心诉求之一就是研发一种更简单,更容易掌握,更不容易出错的面向对象程序设计语言,在java中不允许多继承,是为了避免多继承带来的基类成员冲突的问题,拒绝指针,从根源上杜绝了“参数引用传递”的手段,且避免了实例化构造方法出现递归,且java提出了GC,从根源上杜绝了内存泄漏这个问题

java的单继承虽然有部分性能上的损失,此设计了多接口实现,弥补了性能上的缺失

(5) 接口的特点

1,海纳百川 类型的统一和保护

java处处都是类,随着java程序的不断增加,类的不断生成,java的数据类型也在不断扩容,面对如此庞大的数据类型群,对于某些场合会带来灾难性的后果

如果想要将Complex类,Circle类,Square类,Gold类,Silver
类的对象按照 字符串:对象的方式采用HashMap存储到Map中,且禁止其它类的对象存储到Map中

在不用接口的情况下,可以使用Object类,描述所有对象类型,但是用Object类完成后,如果需要增加或删除一个类,那么需要更改大量的代码,完成后还需要进行各种测试

这种问题是因为“类型保护的缺失”造成的,使用接口可以轻松解决:

定义一个接口
package com.mec.interface;

public interface IMyType {

}

然后修改能进入Map的类,使它们实现这个接口,再定义一个HashMap,其值为这个IMyType接口类型:

HashMap<String,IMyType> myMap = new HashMap<>();

之后,在对myMap进行操纵时,只有值得类型满足是IMyType的派生类对象才可以进入,不要小看了空接口

2,政令统一 规范方法

凡是实现了该接口的非抽象类,就必须实现这个接口所规定的所有抽象方法

这个规则包含着规范行为的意义,比如在某种场合下,我们需要某些类实现我们固定要求实现的一些方法,那么可以将这些方法写进一个接口,让这些类实现这个接口,这时,这些类就必须实现这些方法,且全部实现,一个都不能少,一个都不能改

规范体现在:
1,指定的所有方法都必须实现,一个都不能少
2,规范了方法的名称
3,规范了方法的参数个数和参数类型
4,规范了方法的返回值类型

3,广而告之 方法的公开

从规范方法的应用角度看这个问题,我们得知某些类实现了某个接口,那么这些列就一定存在接口中的方法,就可以调用这些方法

4,面向未来 分离原则

当面对某些问题未来有不止一种实现过程时,也可以使用接口,如对不同渠道传来的数据进行排序,如从键盘输入的数据,从文件中读入的数据,从数据库中导入的数据,从网络中得到的数据

对于未来工具的使用者要处理何种渠道传来的数据,我们是不知道的,且允许数据不仅从一种渠道得到

可以使用接口设置输入数据的类型,将数据输入作为排序的一个成员,需要处理什么渠道的成员,就让它实现定好的接口

L12

在C语言中,一段具有实际应用意义的代码中往往需要大量的错误判断,处理特殊情况的代码,即程序中有着许多 if-else来处理不确定的情况,避免程序崩溃

因为具有实际意义的软件本身是复杂的,软件的运行环境也是复杂的,加之软件使用者在操作时的不确定性和不可控性,使得程序在运行时出现不正常,甚至导致程序崩溃的情况在所难免,为了尽量减少这些异常的出现,程序员需要使软件具有鲁棒性

鲁棒(robust):即健壮,强壮的意思,它是在异常和危险情况下系统生存的关键,如计算机在输入错误,磁盘崩溃,网络过载或有意攻击下,能否不死机,不崩溃就是该软件的鲁棒性

1 Java的异常处理

(1) 基本异常处理语法和过程

先看下面的例子:
在这里插入图片描述
这个程序当然不能执行,不过在控制台出现了异常提示,清楚的说明了异常类型和发现异常的位置,这说明,除0错误,是被java系统捕获并处理了,只不过java处理它的方式是输出异常的具体信息

在这里插入图片描述
上述的程序证实了当异常发生且程序员不做处理时,则之后的代码都将会停止,程序无法继续运行

对于上述的程序,我们也可以自己捕获并处理这个异常:
在这里插入图片描述
上面的例子说明了关于java异常处理的基本原则:
1,程序一旦发生异常,则异常点后的所有代码都停止执行如果不存在这种异常的捕获语句,则以后的代码都将会停止执行,程序崩溃,由JVM接手处理

2,如果存在try { } catch() { } 捕获了异常,则转而执行异常捕获语句块中的代码,接下里继续执行异常捕获语句块后的代码,程序不会崩溃

不同情况下的程序流程:
在这里插入图片描述

(2) 运行时异常和非运行时异常

java内部已经定义了很多异常,这些异常分为两大类:运行时异常 和 非运行时异常

异常类的继承结构:
在这里插入图片描述
运行时异常和非运行时异常最大的区别在于:编译器要求程序员必须对非运行时异常进行捕获,否则不能通过编译

对于先前的除0错误所属的ArithmeticException类,就属于运行时异常,编译器没有强制要求捕获这类异常,而大部分异常都是非运行时异常,编译器提示程序员必须对这类异常进行处理,这样可以消除因为程序员的遗漏而造成代码潜在的危险

我们还可以根据自己的需要,生成自己的异常类,这极大的扩展了异常的应用范围,方便了软件的开发

(3) 自定义异常

用Exception类可以派生出 非运行时异常
用RuntimeException类可以派生出运行时异常

通常建议定义非运行时异常,这样可以强迫程序员对异常进行捕获和处理,如果不需要程序员有目的的进行捕获,也可以定义运行时异常

自定义一个异常类,在定义该异常类时要继承父类的构造方法和生成序列号:
在这里插入图片描述
对于该自定义异常的使用:

package com.mec.point;

import com.mec.about_Exception.OutOfRangeException;

public class Point {
	
	private int row;
	private int col;
	
	public static final int MIN_ROW =1;
	public static final int MAX_ROW = 25;
	public static final int MIN_COL =1;
	public static final int MAX_COL = 80;
	
	public void setRow(int row) throws OutOfRangeException {
		
		if(row < MIN_ROW || row > MAX_ROW) {
			throw new OutOfRangeException(
					"行下标越界");
		}
		this.row = row;
	}
	
	public void setCol(int col) throws OutOfRangeException {
		
		if(col < MIN_COL || col > MAX_COL) {
			throw new OutOfRangeException(
					"列下标越界");
		}
		this.col = col;
	}
	
	public Point(int row, int col) throws OutOfRangeException {
		setRow(row);
		setCol(col);
	}
	
	public Point() throws OutOfRangeException {
		this(1,1);
	}
	
	public int getRow() {
		return row;
	}
	
	public int getCol() {
		return col;
	}

}

(4) 异常中的finally

异常处理中还有一个关键字:finally,它的作用是,无论异常是否发生,都要执行的块

在这里插入图片描述
finally中的关闭资源,无论是否发生了ArithmeticException异常,都要执行

finally块的存在主要为了在异常发生时,能完成一些必要的操作,如关闭资源等在某些场合尤为重要的操作

(5) 异常抛出和捕获的选择

能处理则处理,不能处理则抛出

对于异常,主要有两种处理手段:抛出和捕获

一般来说,如果对于某种异常,如果在本方法中有处理它的安排且这个异常不影响调用它的方法,那么就捕获,相当于自动响应这个异常,给出了有效处理

如果这种异常严重影响到了后续代码的逻辑和执行,甚至会影响到调用它的方法,那么就把这个异常抛出,当然直到抛到主函数那里还是需要处理一下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值