一阶段:第6天:抽象类和接口+内部类和设计模式(7.30)
一.抽象类和接口
1.1抽象类(abstract )
- 使用abstract修饰类变成抽象类:不能实例化,只能被继承。
抽象类的构造方法不能直接使用,只能被子类调用。 - 抽象方法,没有方法体,需要使用分号表示声明结束,抽象方法所在的类必须是抽象类,抽象方法被子类重写
- 子类必须重写父类的抽象方法,除非子类也是抽象类
- 抽象类中可以包含抽象方法,也可以不包含抽象方法。
抽象类中有构造方法,但是不能创建对象,构造方法可以在子类中会被调用。
抽象类的存在就是为了被继承,子类必须重写父类的抽象方法,除非子类也是抽象类。 - 思考1:final和abstract是否可以连用?
1)两个关键字修饰方法时,final修饰的方法特点:可以被继承不能被重写;abstract修饰的方法特点:必须被重写;所以这两个关键字不能同时修饰同一个方法
2)两个关键字修饰类时:final修饰的类特点:不能被继承;abstract修饰的类特点:必须被继承;所以这两个关键字不能同时修饰同一个类
综上所述:final和abstract不可以连用。
- 思考2:final的类中能否有abstract方法?不能
- 思考3:abstract类中能否有final方法?可以
- static,final,abstract总结:
1.2接口(interface)
- 使用接口的好处
(1)扩展类的功能,保持对外接口一致
(2)接口实现了多重继承,完成类和任何实现接口子类的通信和交互
(3)降低代码之间的耦合性 - 接口的特点:接口可以多继承,用逗号隔开
(1)接口不能创建对象,而且接口中没有构造方法
(2)接口中的方法一般都是共有抽象方法:public abstract
(3)接口中所有的属性都是共有静态常量属性:pulbic static final
在一个接口中声明方法时,若没有声明访问权限,默认也是public,若没其他修饰默认也是abstract;声明属性时,若没有声明访问权限和静态常量,默认也是public static final - 一个类实现某个接口之后,可以存在父类,实现接口和继承类不冲突
注意:若一个类有父类同时也实现接口,声明类时,必须先继承再实现接口 - 接口的分类:
(1)普通接口:在接口中可以声明抽象方法,和静态常量属性
(2)常量群接口:在接口中只声明一组静态常量属性
(3)标志性接口:在接口中没有抽象方法,也没有静态常量,作用为了标记某个类具有某个功能 - 接口中特殊的方法:
(1)jdk1.8之后接口中使用static关键字修饰的方法有方法体
静态方法需要有方法体 ,注意只能通过接口名.方法名调用,因为不能被继承
(2)jdk1.8之后接口中使用default关键字修饰的方法有方法体
可以被继承,通过实现类调用 - 抽象类和接口区别
(1)语法:
a.抽象类使用abstract,接口使用interface
b.抽象类中可以包含抽象方法,也可以包含非抽象方法,接口中只能包含抽象方法和静态常量,jdk1.8之后接口可以包含静态方法和默认方法。
c.抽象类和接口都不能实例化。
d.抽象类可以包含构造方法,接口中没有构造方法。
(2)功能:
a.抽象类一般用来表示同类事物,接口可以表示不同类事物。
b.抽象类可以实现代码的重用,也可以约束子类的功能。接口就是约束实现类的功能,降低代码之间的耦合性。
(3)使用场景:
a.程序或模块内部使用抽象类
b.程序架构或模块之间使用接口
二.内部类和设计模式
2.1内部类
内部类:一个类中嵌套(包含)另外一个类。包含的类叫内部类,外层类叫外部类。包括:成员内部类,局部内部类,静态内部类,匿名内部类
2.1.1成员内部类
- 作为外部类的成员存在,与成员变量和成员方法平级关系。
- 成员内部类的访问权限:任意的
- 由于成员内部类作为外部类的成员存在,若想访问类成员需要通过对象,所以成员内部类对象需要通过外部类对象创建
- 如何在成员内部类中访问外部类的成员:
(1)当外部类的属性和内部类属性不同名时,可以直接访问
(2)当外部类属性与内部类属性同名时,格式:
外部类名.this.属性名
通过以上格式在内部类中访问外部类的同名属性 - 成员内部类中不能包含静态成员,但可以包含静态常量
- 成员内部类的字节码文件名:外部类名$内部类名.class
public class Test {
public static void main(String[] args) {
Outer outer=new Outer();
outer.show();
Outer.Inner inner=outer.new Inner();
inner.show();
System.out.println("-----------");
Outer.Inner inner1=new Outer().new Inner();
inner1.show();
}
}
public class Outer {
int n=10;
String name="zz";
public void show(){
System.out.println("外部方法");
}
//成员内部类
public class Inner{
int n1=20;
String name="haha";
public void show(){
System.out.println("内部"+n1+name);
System.out.println("外部"+n+Outer.this.name);
Outer.this.show();
}
}
}
运行结果:
外部方法
内部20haha
外部10zz
外部方法
-----------
内部20haha
外部10zz
外部方法
2.1.2局部内部类
- 作为局部成员存在,和局部变量平级
- 局部内部类的访问权限:只能是默认
- 如何创建局部内部类对象:直接在局部内部类所在的方法中创建对象并调用方法
- 如何在局部内部类中访问外部类的属性:
(1)不同名,直接访问
(2)同名,外部类名.this.属性名
(3)局部内部类中不能包含静态成员。
(4)局部内部类中访问局部变量,局部变量必须是final 常量 ,从jdk1.8之后 - 字节码文件:
外部类名$编号内部类名.class
public class Test {
public static void main(String[] args) {
Outer outer=new Outer();
outer.show();
}
}
public class Outer {
int n=10;
String name="zz";
public void show(){
int n1=20;
class Inner{
String name="haha";
public void show(){
System.out.println("局部内部类"+Outer.this.name);
Outer.this.print();
System.out.println("局部变量"+n1);
}
}
Inner inner=new Inner();
inner.show();
}
public void print(){
System.out.println("啦啦啦");
}
}
运行结果:
局部内部类zz
啦啦啦
局部变量20
2.1.3静态内部类
- static关键字用法:修饰成员变量,成员方法,代码块
static关键字的第四个用法,修饰内部类
static修饰的内部类是静态内部类
使用static修饰的成员内部类是静态内部类 - 访问权限:任意的,一般使用public
使用static修饰的内部类,自动提升为普通类,相当于一个独立的类,和外部类级别相同 - 访问外部类的成员:
(1) 静态内部类能直接访问外部类的静态成员
(2) 非静态的成员只能通过创建外部类对象访问
(3) 静态内部类中可以包含静态成员 - 字节码文件格式:外部类名$内部类名.class(与成员内部类一样)
public class Test {
public static void main(String[] args) {
//可以不创建外部类对象,直接创建静态内部类对象
InnerClass i = new InnerClass();
i.fun();
}
}
public class OuterClass {
static String s = "hello";
int a = 10;
public void fun() {
System.out.println("外部类的fun");
}
//静态内部类
public static class InnerClass{
int b = 20;
public void fun() {
System.out.println(s);
System.out.println();
System.out.println("内部类的fun");
}
}
}
2.1.4匿名内部类(重点)
- 由于接口和抽象类不能创建对象,若一个接口的实现类只需要使用一次,或一个抽象类的子类只需要使用一次,可以使用匿名内部类,匿名内部类只能创建一个对象
- 匿名内部类中必须把抽象方法全部实现
- 匿名内部类中可以声明独有的属性和方法,但是由于接口引用不能访问实现类中独有的属性和方法,所以一般不在匿名内部类中声明独有的属性和方法。
- 匿名对象:若在匿名内部类中声明了独有的方法或属性,可以使用匿名对象访问,不能通过对象名.方法名()访问。
public class Test {
public static void main(String[] args) {
int n=10;
String name="zz";
//局部内部类
class Upan implements Usb{
@Override
public void server() {
System.out.println("U盘连接成功,开始工作");
}
}
Usb usb=new Upan();
usb.server();
//简化局部内部类:匿名内部类
Usb usb1=new Usb(){
@Override
public void server() {
System.out.println("U盘连接成功,开始工作");
}
};
usb1.server();
//再简化:lambda表达式(要求:接口中只有一个方法---要实现)
Usb usb2=()-> System.out.println("U盘连接成功,开始工作");
usb2.server();
}
}
2.2设计模式
总体来说设计模式分为三大类:
-
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
-
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
-
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
2.2.1单例设计模式
在程序运行,一个类只需要一个实例,不能出现多个实例,就叫单例。
步骤:
(1)私有化构造方法;
(2)在类内创建对象;
(3)提供一个共有的方法,用来获取本类对象
实现方式:
单例分为饿汉式和懒汉式
*(1)私有化构造方法
*(2)在单例类中创建对象
*(3)提供公共的方法,返回这个对象
* 饿汉式写法:
* 优点:不会出现线程安全问题
* 缺点:浪费空间,声明周期长
public class SingleTon {
//(1)
private SingleTon(){
}
//(2)
private static final SingleTon INSTANCE=new SingleTon();
//(3)
public static SingleTon getInstance(){
return INSTANCE;
}
}
* 懒汉式写法---面试经常问
* 第一次使用时,初始化
* 好处:节省空间 缺点:多线程不安全
public class SingleTon2 {
private SingleTon2(){
}
private static SingleTon2 instance=null;
public static SingleTon2 getInstance(){
if (instance==null){
instance=new SingleTon2();
}
return instance;
}
}
2.2.2简单工厂设计模式
简单工厂解决创建对象的问题。
1.原理:多态
生活中的工厂:手机厂,电视厂,服装厂…
2.动机:
考虑一个简单的软件应用场景,一个软件系统可以提供多个外观不同的按钮(如圆形按钮、矩形按钮、菱形按钮等),这些按钮都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以呈现不同的外观,如果我们希望在使用这些按钮时,不需要知道这些具体按钮类的名字,只需要知道表示该按钮类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的按钮对象,此时,就可以使用简单工厂模式。
3.优缺点:
优点:工厂类包含产品的判断逻辑,可以决定创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
缺点:由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。使用简单工厂模式将会增加系统中类的个数,在一定程度上增加了系统的复杂度和理解难度。系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
4.简单工厂四个角色:
1 工厂角色(ClothesFactory):负责创建具体的产品。 2 父类产品(Clothes): 作为所有产品的父类,使用抽象类表示
3 子类产品(Jacket,Trousers,Tshirt):具体的产品
4 客户程序(Test):使用工厂和产品的程序
public class ClothesFactory {
public static Clothes create(int type){
Clothes clothes=null;
if (type==1){
clothes=new Trousers();
}else if (type==2){
clothes=new Tshirt();
}else if(type==3){
clothes=new Jacket();
}
if (clothes!=null){
clothes.prepare();
clothes.make();
clothes.box();
}
return clothes;
}
}
public abstract class Clothes {
public abstract void prepare();
public abstract void make();
public abstract void box();
}
public class Jacket extends Clothes {
@Override
public void prepare() {
System.out.println("准备制作夹克的布料");
}
@Override
public void make() {
System.out.println("开始制作");
}
@Override
public void box() {
System.out.println("包装完毕");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("----欢迎来到zz服装厂-----");
System.out.println("---1.裤子 2.T恤 3.夹克----");
System.out.println("请选择购买的衣服");
Scanner input=new Scanner(System.in);
int n=input.nextInt();
Clothes clothes=ClothesFactory.create(n);
if (clothes!=null){
System.out.println("购买成功");
}else {
System.out.println("购买失败");
}
}
}
2.2.3面向对象的设计原则
七大原则:(开 口 里 合 最 单 依)
1、总则:开闭原则(Open Close Principle,OCP)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。
2、单一职责原则(Single Responsibility Principle,SRP)
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。
3、里氏替换原则(Liskov Substitution Principle,LSP)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。
里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
4、依赖倒置原则(Dependence Inversion Principle,DIP)
面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
5、接口隔离原则(Interface Segregation Principle,ISP)
每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
6、迪米特法则(最少知道原则)(Demeter Principle,DP)
一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
7、合成复用原则(Composite Reuse Principle,CRP)
原则是尽量首先使用合成/聚合的方式,而不是使用继承。