第6章 面向对象(下)(2)

5.抽象类

  • 抽象方法和抽象类

抽象方法和抽象类的规则如下:
1. 抽象类、抽象方法必须使用abstract修饰;抽象方法不能有方法体
2. 抽象类不能被实例化,无法使用new关键字来创建实例; 即使这个抽象类中不包括抽象方法,这个抽象类也不能创建实例
3. 抽象类可包括成员变量、方法(普通方法和抽象方法都可以)、构造器(主要用于被子类调用)、初始化块、内部类(接口、枚举)5种成分
4. 含有抽象方法的类只能被定义成抽象类
5. 抽象方法不能用private修饰,因为抽象方法要被子类重写才有意义

  • 抽象类的作用

体现了设计模式:模板模式

设计规则:
1. 抽象父类可以只定义某些要使用的方法,把不能实现的抽象成抽象方法,留给子类去实现
2. 父类中可能包含需要调用其他系列方法的方法,这些被调方法既可以由父类实现,也可以由其子类实现。父类里提供的方法只是定义了一个通用算法。

public abstract  class SpeedMeter {
    //转速
    private double turnRate;

    public SpeedMeter() {
    }
    //把返回车轮半径的方法定义成抽象方法
    public abstract double getRedius();

    public void setTurnRate(double turnRate) {
        this.turnRate = turnRate;
    }
    //定义转速的通用方法
    public double getSpeed(){
        return java.lang.Math.PI*2*getRedius()*turnRate;
    }
}

public class CarSpeedMeter extends SpeedMeter {

    @Override
    public double getRedius() {
        return 0.28;
    }

    public static void main(String[] args) {
        CarSpeedMeter cs = new CarSpeedMeter();
        cs.setTurnRate(5.0);
        System.out.println(cs.getSpeed());
    }
}

6.Java8改进的接口

  • 接口的概念

接口是从多个相似类中抽象出来的规范,接口不提供任何实现。接口体现的是规范和实现分离的设计哲学。

  • Java8中接口的定义
[修饰符] interface 接口名 extends 父接口1,父接口2...
{
    零到多个常量定义...
    零到多个抽象方法定义...
    零到多个内部类、内部接口、内部枚举定义...
    零到多个默认方法或类方法定义(Java8)...
}
修饰符可以是public或者省略

接口中可以包含的成员(接口中不能包括静态方法和初始化块):

  1. 成员变量(只能是静态常量),默认和指定都只能用 public static final修饰,接口里没有构造器和初始化块,所以只能在定义时指定默认值
  2. 方法(只能是抽象实例方法、类方法或默认方法):

    普通方法使用 public abstract修饰,且不能有方法体;

    默认方法必须有方法体,必须用default修饰,不能用static修饰;系统还会为默认方法指定public修饰符;需要用接口实现类的实例调用

    类方法必须有方法体,必须用static修饰;系统还会为默认方法指定public修饰符;可直接使用接口来调用

  3. 内部类(包括内部接口和枚举) 默认使用public static修饰

    • 接口的继承

多继承

  • 使用接口
    接口不能用于创建实例,但接口可以用于声明引用类型变量
    当使用接口来声明引用类型变量时,这个引用类型变量必须引用到其实现类的对象

接口的主要用途:
1. 定义变量,也可用于强制类型转换
2. 调用接口中定义的常量
3. 被其他类实现

一个类可以继承一个父类,并同时实现多个接口,implements必须放在extends后面

一个类最多只能有一个直接父类,包括抽象类;但一个类可以实现多个接口

接口不能显示继承任何类,但所有接口类型的引用变量都可直接赋给Object类型的引用变量,这是利用向上转型实现的

  • 接口和抽象类
  • 面向接口编程

(很多软件架构设计理论都倡导“面向接口”编程,而不是面向实现类编程,希望通过面向接口编程来降低程序的耦合)

1.简单工厂模式

(Computer类,Printer类,Output接口的例子,Computer类应组合Output接口)

简单工厂模式包括的4个部分:

组合接口的类:

被类组合的接口:

接口的实现类(可能多个):

返回接口实现类的工厂类(具体返回哪个实现类看业务决定 Printer BetterPrinter):

被类组合的接口:

public interface Interface {
 public static final int i= 1;
    int b = 2;
    void method1();
    void method2();
}

组合接口的类:

public class CombinationClass {
    private Interface iface;// 类中组合的接口
    public CombinationClass(Interface iface){
        this.iface = iface;
    }
    public void m1(){
        iface.method1();
    }
    public  void m2(){
        iface.method2();
    }
}

接口的实现类(可能多个):

public class InterfaceImpl implements Interface {
    @Override
    public void method1() {
        System.out.println("接口中method1的方法实现");
    }

    @Override
    public void method2() {
        System.out.println("接口中method2的方法实现");
    }
}

返回接口实现类的工厂类(具体返回哪个实现类看业务决定 Printer BetterPrinter):

public class InterfaceFactory {
    public Interface getInstance(){
        return new InterfaceImpl();
    }
    public static void main(String[] args){
        Interface it = new InterfaceFactory().getInstance();
        // 获取interface的实现类
        CombinationClass c = new CombinationClass(it);
        c.m1();
        c.m2();
    }
}

2.命令模式
(某个方法需要遍历数组元素,但无法确定在遍历数组时如何处理这些元素,需要在调用该方法时指定具体的处理行为)
命令模式主要包括:

接口(包含处理行为)

public interface Interface {
    int process(int a,int b);
}

业务类(包含以接口实例为参数的处理行为)

public class Service {
    int process(int a, int b, Interface it) {
        return it.process(a, b);
    }

    public static void main(String[] args) {
        Service s = new Service();
        Interface it1 = new InterfaceImplOne();
        Interface it2 = new InterfaceImplTwo();
        s.process(5, 3, it2);
        System.out.println(s.process(5, 3, it1));
        System.out.println(s.process(5, 3, it2));
    }
}

接口实现类

public class InterfaceImplOne implements Interface {

    @Override
    public int process(int a, int b) {
        return a+b;
    }
}

7.内部类

内部类

定义:定义在其他类内部(任意位置,方法中也可)的类叫内部类(嵌套类),与之对应的是外部类(宿主类)
一般作为成员内部类定义,成员内部类是与成员变量、方法、构造器、初始化块相似的类成员
局部内部类(外部类方法中定义)、匿名内部类都不是类成员

作用: 1.提供更好的封装,不允许同一包中其他类访问
内部类在在外部类中才有效,离开后没有任何意义
2.内部类成员可直接访问外部类的私有数据;外部类不能访问内部类的实现细节
3.匿名内部类适用于创建仅需要使用一次的类
4.内部类比外部类可多使用三个修饰符:private,protected,public,static
5.非静态内部类不能有静态成员

格式:

public class OuterClass
{
// 此处可以定义内部类
}

注意: 外部类的上一级程序的单元是包,所以只有两个作用域:同一包(缺省)、任何位置(public)
内部类的上一级程序单元是外部类,所以有4个作用域:同一类(private)、同一包(缺省)、父子类(protected)、任何位置(public)

  • 疑問:三个访问控制符对访问内部类有什么限制

  • 非静态内部类

非静态内部类里可以直接访问外部类的private成员,这是因为在非静态内部类对象里,保存了一个它所寄生的外部类对象的引用(Cow.this)(当调用非静态内部类的实例方法时,必须有一个非静态内部类实例,非静态内部类实例必须寄生在外部类实例里)

反过来,当外部类访问非静态内部类的private成员变量时,则必须显示的创建非静态内部类的对象来访问。但若是public修饰的成员变量,还是可以直接使用。

非静态内部类对象存在,外部类对象一定存在;外部类对象存在,非静态内部类对象不一定存在。

当在非静态内部类的方法内访问某个变量时,顺序
(因为非静态内部类中不可能包含静态变量,所以用this)
1.局部变量
2.内部类中的成员变量 this
3.外部类中的成员变量 外部类类名.this

非静态内部类中不能包含:静态成员、静态方法、静态初始化块

  • 静态内部类

static修饰
定义:静态内部类可以包含静态成员,也可以包含非静态成员

静态内部类–>外部类

静态内部类只能访问外部类的类成员。即使时静态内部类中的实例方法也不能访问外部类中的实例成员。

外部类–>静态内部类

使用静态内部类的类名作为调用者访问静态内部类的静态成员,使用静态内部类对象作为调用者访问静态内部类的实例成员。

public class AccessStaticInnerClass {
    static class StaticInnerClass {
        private static int prop1 = 5;
        private int prop2 = 9;

        private static void test() {
            System.out.println("123");
        }

        private void test2() {
            System.out.println("456");
        }
    }
    public void accessInnerProp() {
//        System.out.println(prop1);
//        System.out.println(prop2);
//上面代码出现错误,应改为如下形式:
        System.out.println(StaticInnerClass.prop1);
        StaticInnerClass.test();
//        System.out.println(StaticInnerClass.prop2);
// 编译错误,应改为如下形式
        //通过实例访问静态内部类的实例成员
        System.out.println(new StaticInnerClass().prop2);
        new StaticInnerClass().test2();
    }
}
  • 使用内部类

定义类的主要作用就是定义变量、创建实例和作为父类被继承。使用内部类定义变量和创建实例则与外部类存在一些小小的差异。

相比之下,使用静态内部类比使用非静态内部类要简单的多(定义时相同,但创建实例和创建子类时比较简单),应优先考虑。

内部类是否可以重写?不能,因为子类中的内部类名字为PackageName.SubClass.InnerClass,而父类中的内部类名字为PackageName.BaseClass.InnerClass
名字不同,不能重写

  1. 在外部类内部使用内部类

  2. 在外部类以外使用非静态内部类

定义内部类(包括静态和非静态两种)

OuterClass.InnerClass varName

创建非静态内部类实例(因为非静态内部类的对象必须寄生在外部类对象里

)

OuterInstance.new InnerConstructor()

创建非静态内部类的子类

//静态内部类创建子类
public class SubClass extends Out.In {
    public SubClass(Out out) {
        // 子类构造器一定会调用父类的构造器
        // 非静态内部类的构造器一定通过外部类的对象来调用
        out.super("hello");
    }
}
  1. 在外部类以外使用静态内部类

定义内部类(包括静态和非静态两种)

OuterClass.InnerClass varName

创建静态内部类实例

new OuterClass.InnerClassConstructor()

创建静态内部类的子类

public class StaticSubClass extends StaticOut.StaticIn {}

  • 局部内部类

类在方法中定义

不能使用访问控制符,因为上级程序单元是方法

作用域太小,在开发中很少用到

  • Java8改进的匿名内部类

匿名内部类适合创建那种只需要一次使用的类,创建匿名内部类时会立即创建一个该类的实例,
这个类定义即消失,匿名内部类不能重复使用。

定义:

new  实现接口() | 父类构造器(实参列表)
{
   //  匿名内部类的类体部分
}

匿名内部类必须继承一个父类,或实现一个接口

规则:

  1. 匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。
    因此不允许将匿名内部类定义为抽象类。

  2. 匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,匿名只有一个隐式的无参构造器。
    但创建匿名内部类最常用的方式是需要创建某个接口类型的对象。

创建某个接口类型的对象

interface Product {
    public double getPrice();
    public String getName();
}
public class AnonymousTest {
    public void test(Product p) {
        System.out.println("购买了一个" + p.getName() + ",花掉了" + p.getPrice());
    }
    public static void main(String[] args) {
        AnonymousTest ta = new AnonymousTest();
        //调用test()方法时,需要传入一个Product参数
        //此处传入其匿名实现类的实例
        ta.test(new Product() {//匿名内部类只有一个隐式的无参构造器,故new接口名后的括号里不能传入参数值
            @Override
            public double getPrice() {
                return 567.8;
            }
            @Override
            public String getName() {
                return "AGP显卡" ;
            }
        });
    }
}

通过继承父类来创建内部类(父类可以是抽象类也可以不是)

abstract class Device {
    private String name;

    public abstract double getPrice();

    public Device() {
    }

    public Device(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class AnonymousInner {
    public void test(Device d) {
        System.out.println("购买了一个" + d.getName() + ",花掉了" + d.getPrice());
    }

    public static void main(String[] args) {
        AnonymousInner ai = new AnonymousInner();
        //调用有参数的构造器创建Device匿名实现类的对象
        ai.test(new Device("电子显示器") {
            @Override
            public double getPrice() {
                return 67.8;
            }
        });
        //调用无参构造器来创建Device匿名实现类的对象
        Device d = new Device() {
            //初始化块
            {
                System.out.println("匿名内部类的初始化块...");
            }
            //实现抽象方法
            @Override
            public double getPrice() {
                return 56.2;
            }
            //重写父类的是理发方法
            public String getName(){
                return "键盘";
            }
        };
        ai.test(d
        );
    }
}

Java8之后,如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了final修饰。

8.Java8新增的Lambda表达式

  • Lambda表达式入门

Lambda表达式允许使用更简介的代码来创建只有一个抽象方法的接口(函数式接口)的实例

  • Lambda表达式与函数式接口

  • 方法引用与构造器引用

  • Lambda表达式与匿名内部类的联系和区别

  • 使用Lambda表达式调用Arrays的类方法

9.枚举类

实例有限而且固定的类,在Java里被称为枚举类(例如季节类)

  • 手动实现枚举类

    1. 第一阶段:通过定义静态常量
    2. 第二阶段:通过定义类(通过private隐藏构造器,实例用public static final修饰,提供一些静态方法)
    3. 第三阶段:JDK1.5之后提供支持
  • 枚举类入门

Java5新增了一个enum关键字(它与class,interface地位相同),用来定义枚举类。

一个Java源文件最多只能定义一个public访问权限的枚举类,且名字与Java源文件相同。

与普通类的区别:

  1. 不能显示继承其他父类(因为使用enum定义的枚举类默认继承了java.lang.Enum类)
  2. 不能派生子类(使用enmu定义,非抽象的枚举类默认会用final修饰)
  3. 枚举类的构造器只能用private修饰
  4. 枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。
    列出这些实例时,系统会自动添加public static final修饰
  5. 枚举类提供了一个values方法,可以遍历所有枚举值
public  enum SeasonEnum{
    // 在第一行列出4个枚举实例
    SPRING,SUMMER,FALL,WINTER;
}
public class EnumTest {
    public void judg(SeasonEnum s) {
        switch (s) {
            case SPRING:
                System.out.println("春天");
                break;
            case SUMMER:
                System.out.println("夏天");
                break;
            case FALL:
                System.out.println("秋天");
                break;
            case WINTER:
                System.out.println("冬天");
                break;
        }
    }
    public static void main(String[] args) {
        // 枚举类默认有一个values()方法,返回该枚举类的所有实例
        for (SeasonEnum s : SeasonEnum.values()) {
            System.out.println(s);
        }
        new EnumTest().judg(SeasonEnum.SPRING);
    }
}
  • 枚举类的成员变量、方法和构造器

几种获取枚举类实例的方法

System.out.println(SeasonEnum.SPRING.getName());
     System.out.printf(SeasonEnum.valueOf("FALL").getName());
        System.out.println(SeasonEnum.valueOf(SeasonEnum.class,"WINTER").getName());

枚举类通常应该设计成不可变类,因此建议成员变量都用private final修饰。

所以应在构造器中(也可在定义时、初始化块中,不过不常见)初始化。

public enum Gender3 {
    //    public static final Gender3  MALE = new Gender3("男");
    //    public static final Gender3 FEMALE = new Gender3("女");

    MALE("男"), FEMALE("女");
    private final String name;
    Gender3(String name) {
        this.name = name;
    }
    String getName(){
        return  this.name;
    }
}
class Gender3Test{
    public static void main(String[] args) {
        Gender3 g = Gender3.valueOf("MALE");
        System.out.println(g.getName());
    }
}
  • 实现接口的枚举类

    1. 由枚举类来实现接口
    2. 由每个枚举值提供不同的实现方式

下面的程序编译后生成Gender.class Gender 1.classGender 2.class(MALE和FEMALE实际上是Gender匿名子类的实例,而不是Gender类的实例)

public interface GenderDesc {
    void info();
}

public enum  Gender4 implements GenderDesc{
    MALE{
        @Override
        public void info() {
            System.out.println("MALE的info方法");
        }
    },FEMALE{
        @Override
        public void info() {
            System.out.println("FEMALE的info方法");
        }
    };
    Gender4(){
        System.out.println("构造器");
    }
}
class Test{
    public static void main(String[] args) {
        Gender4 g = Gender4.valueOf("MALE");
        g.info();
        Gender4 g1 = Gender4.valueOf("FEMALE");
        g1.info();
    }
}
  • 包含抽象方法的枚举类

非抽象的枚举类系统默认使用final修饰,对于一个抽象的枚举类,只要它包含了抽象方法,它就是抽象枚举类,系统会默认使用abstract修饰

枚举类定义抽象方法时不能使用abstract关键字将枚举类定义为抽象类(因为系统会自动添加abstract关键字),
但因为枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。

public enum Operation {
    PLUS {
        @Override
        public int eval(int a, int b) {
            return a+b;
        }
    },MINUS {
        @Override
        public int eval(int a, int b) {
            return a-b;
        }
    },TIMES {
        @Override
        public int eval(int a, int b) {
            return a*b;
        }
    },DIVIDE {
        @Override
        public int eval(int a, int b) {
            return a/b;
        }
    };
    public abstract int eval(int a,int b);
}
class TestOperation{
    public static void main(String[] args) {
        Operation o = Operation.DIVIDE;
        Operation o1 = Operation.valueOf("DIVIDE");
        System.out.println(o.eval(3,4));
        System.out.println(o1.eval(3,4));
    }
}

10.对象的垃圾回收

(垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据连接、网络IO等资源)

程序无法精确控制垃圾回收机制的运行,
垃圾回收会在合适的时候运行。当对象永久的失去引用后,系统就会在合适的时候回收它所占的内存,

在垃圾回收机制回收任何对象之前,
总会先调用它的finalize()方法,该方法可能使对象重新复活(让一个变量重新引用该对象),从而导致垃圾回收机制取消回收。

  • 对象在内存中的状态

    1. 可达状态:该对象有一个以上的引用变量引用
    2. 可恢复状态:该对象不再有引用变量引用,它就进入可恢复状态。
    3. 不可达状态:可恢复状态的对象,再调用finalize()方法之后,若该对象没有变成可达状态,则变为不可达状态。
      只有一个对象变为不可达状态时,系统才会真正回收该对象所占有的资源。
  • 强制垃圾回收

程序无法强制控制Java垃圾回收的时机,但依然可以强制系统进行垃圾回收。

两种方式(作用完全相同):

  1. System.gc()
  2. Runtime.getRuntime().gc()

    • fianlize方法

是Object类的方法

有4个特点:
1. 永远不要主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用
2. finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会被执行的方法。
3. 当JVM执行可恢复对象的finalize()方法时,可能使该对象或系统中的其他对象重新变成可达状态。
4. 当JVM执行finalize()方法时出现异常时,垃圾回收机制不会报告异常,程序正常执行。

调用顺序:
1. 首先调用System.gc()/Runtime.getRuntime().gc()(只有当程序认为需要更多的额外内存时,垃圾回收机制才会进行垃圾回收)
2. 垃圾回收机制调用finalize()方法,但何时调用是透明的。(System.gc()调用后调用,但何时调用不确定)

public class FinalizeTest {
    private static  FinalizeTest ft = null;

    public void info() {
        System.out.println("实例方法");
    }

    public  void finalize() {
            ft = this;
    }
    public static void main(String[] args) {
        // 只执行①不执行②则垃圾回收机制可能不立即执行finalize方法,可能空指针异常
        // 不执行①只执行②通常程序不会立即进行垃圾回收,那么就不会调用finalize方法,会空指针异常
        new FinalizeTest();
        System.gc();// ①
        System.runFinalization();// ②
        ft.info();
    }
}
  • 对象的软、弱和虚引用(待详细)

引用的几种方式(位于java.lang包下):

  1. 强引用(StrongReference):最常见
  2. 软引用(SoftReference)
  3. 弱引用(WeakReference)
  4. 虚引用(PlantomReference)

11.修饰符的适用范围

  1. strictfp:含义时FP-strict,也就是精确浮点的意思,修饰后Java的编译器和运行时环境会按照浮点规范IEEE-754来执行。
    修饰:外部类/接口、方法、成员内部类
  2. native: 主要用于修饰一个方法,使用native修饰方法后,则该方法通常使用C语言来实现。
    一旦Java中包含了native方法,那么这个程序将失去跨平台的功能。

  3. 4个访问控制符是互斥的,最多出现其中之一

  4. abstract和final永远不能同时使用
  5. abstract和static不能同时修饰方法,但可以同时修饰内部类
  6. abstract和private不能同时修饰方法,但可以同时修饰内部类
  7. private和final修饰方法,虽然语法没问题。但是没有必要,因为private修饰的方法不可能被子类重写。

12.适用JAR文件

  • jar命令详解

JAR包

  • 创建可执行的JAR包
  • 关于JAR包的技巧

13.本章小结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值