面向对象(三)— 抽象类、接口、内部类

面向对象(三)

《疯狂Java讲义》学习笔记

抽象类

抽象方法和抽象类

  • 抽象方法和抽象类使用 abstract 修饰:有抽象方法的类只能被定义成抽象类,抽象类可以没有抽象方法
  • 规则:
  1. 抽象方法和抽象类使用 abstract 修饰,抽象方法不能有方法体
  2. 抽象类不能被实例化,无法使用 new 关键字调用抽象类的构造器创建抽象类类实例
  3. 抽象类可以包含:成员变量方法(普通方法或抽象方法)构造器初始化块内部类(接口、枚举)
  4. 含有抽象方法的类(包括直接定义一个抽象方法、继承一个抽象父类(实现一个接口)但没有完全实现父类的抽象方法(没有完全实现接口的抽象方法))
  • 使用 abstract 修饰类时:
    表明这个类只能被继承;使用 abstract 修饰方法时,表明这个方法只能被子类重写;而 final 修饰的类不能被继承,final 修饰的方法不能被重写,因此 finalabstract 永远不可能同时使用
  • abstract 不能用来修饰成员变量、局部变量和构造器
  • staticabstract 不能同时修饰一个方法
  • privateabstract 不能同时修饰一个方法;

Java8改进的接口

接口里没有普通方法,所有的方法都是抽象方法,Java8对接口进行了改进,允许接口中包含默认方法,默认方法可以提供方法实现

接口的定义

接口使用 interface 关键字
[ 修饰符 ] interface 接口名 extends 父接口1,父接口2{
	零到多个常量定义…
	零到多个抽象方法定义…
	零到多个内部类、接口、枚举定义…
	零到多个默认方法、类方法定义…	//只有在Java8中才可以定义默认方法、类方法
}

一个接口可以有多个父接口,但接口只能继承接口,不能继承类

接口规范
  • 接口中不能定义构造器和初始化块
  1. 可以包含成员变量:只能是静态常量
  2. 方法:只能是抽象方法、类方法、默认方法(使用 default 修饰)
  3. 内部类、内部接口、枚举
  • 关于接口内属性和方法的修饰符
    接口中定义的是多个类的共同的公共行为规范,因此接口中的所有成员:包括常量、方法、内部类和枚举类都是 public 访问权限;因此在定义接口时可以省略访问修饰符,如果指定访问修饰符,则只能是 public
  • 对于接口中的静态常量而言
    由于接口中的静态常量是接口相关的,系统会自动给这些静态常量添加 public static final 修饰符。并且由于接口中没有构造器和初始化块,那么这些常量必须在定义时指定初始值
下面的两行代码效果完全一样
int MAX_SIZE = 50;
public static final int MAX_SIZE = 50;
接口中定义的方法
  • 只能是抽象方法、类方法或默认方法(Java8之后支持接口中有默认方法)
  • 系统会自动为普通方法添加 public abstract 修饰符
  • 普通方法不能有方法体,类方法和默认方法必须有方法体

//如下代码定义一个接口
interface InterfaceTest {
    int MAX_CACHE_LINE = 50;

    // 普通方法只能是public
    void test();

    void getData(String msg);

    // 在接口中定义的默认方法使用 default 修饰
    // 无论是否指定,系统会自动为默认方法添加 public 修饰符
    // 且默认方法不能使用 static 修饰
    // 由于默认方法没有使用static修饰,因此默认方法必须使用接口的实现类的实例来调用
    default void print(String... msg) {
        for (String string : msg) {
            System.out.println(string);
        }
    }

    // Java8允许在接口中定义类方法,类方法必须使用 static 修饰
    // 不能使用 default 修饰,系统会自动为类方法添加 public 修饰符
    // 类方法可以直接使用接口调用
    static String staticTest() {
        return "接口中的类方法";
    }
}

接口的继承

接口继承和类继承不一样,接口完全支持多继承,即一个接口可以有多个直接父接口
和继承一样,子接口扩展某个父接口,会获得父接口里定义的所有常量,抽象方法
在进行强制类型转换时,互相强转的双方只要有一方是接口类型,则一定编译通过;
但是若引用中指向的实际对象类型和目标类型

使用接口

  • 接口不能创建实例,但是接口可以定义引用类型的变量,使用接口定义引用类型的变量时,这个引用类型的变量必须引用到其实现类身上
  • 接口的主要用途:
  1. 定义变量,也可以用于强制类型转换
  2. 调用接口中的常量
  3. 被其他类实现
  • 一个类实现了一个或者多个接口后,必须完全实现接口中定义的抽象方法,否则这个类必须被定义成抽象类
  • 实现接口时,子类重写的方法必须使用 public 修饰,因为接口中的方法都是 public 修饰的
  • 子类重写父类方法的原则:两同两小一大的原则 ,子类的访问修饰符必须大于等于父类的访问修饰符
面向接口编程
  • 工厂模式
  • 代理模式

内部类

  • 含义:一个类被定义在另一个类的内部
  • 作用
  1. 内部类提供更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类;
  2. 内部类成员可以直接访问外部类的私有数据,因为内部类被当外部类的成员,同一个类的成员之间可以相互访问,但外部类不能访问内部类的实现细节,例如内部类的成员变量
  3. 匿名内部类适合用于创建那些仅需要一次使用的类,
  4. 内部类可以比外部类多使用 privateprotectedstatic 这三个修饰符
  5. 非静态内部类不可以拥有静态成员

非静态内部类

  • 定义位置
    在一个内的内部定义即可:此处的“类内部”包括类的任意位置,在方法中也可以定义内部类(在方法中定义的内部类被称为 局部内部类);
    大部分时候内部类被作为成员内部类定义,成员内部类 是一种与 成员变量方法构造器初始化块 相似的类成员;
  • 局部内部类和匿名内部类不是类成员;
public class TestInnerClass {
    public static void main(String[] args) {
        Outer out = new Outer(378.9);
        out.test();
    }
}

class Outer {
    private double weight;

    public Outer() {}

    public Outer(double weight) {
        this.weight = weight;
    }

    private class Inner {

        private double length;

        private String color;

        public Inner() {}

        public Inner(double length, String color) {
            this.length = length;
            this.color = color;
        }
		//省略 getter 和 setter 方法
        public void info() {
            System.out.println("当前牛腿的颜色:" + color + ",长度:" + length);
            // 访问外部类的私有成员变量
            System.out.println("本牛腿在牛中的重量:" + weight);
        }

    }

    public void test() {
        Inner in = new Inner(1.12, "黑白相间");
        in.info();
    }
}
  • 编译该程序会生成两个class文件,Outer.class和Outer$Inner.class,前者是外部类Outer的class文件,后者是内部类(Inner)的class文件;即成员内部类(包括静态内部类和非静态内部类)
  • 在非静态内部类的方法中访问某个变量:查找顺序
  1. 在该方法内查找是否存在改名字的局部变量,如果存在就是用;
  2. 如果不存在,就在该内部类中寻找是否存在该名字的成员变量;
  3. 如果不存在,就在外部类中寻找是否存在改名字的成员变量,如果不存在,编译报错;
  4. 如果外部类成员变量、内部类成员变量与内部类方法的局部变量同名,可以通过,this外部类类名.this 来区分
public class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.age = 10;
        Outer.Inner inner = outer.new Inner();
        inner.age = 20;
        inner.info();
    }
}

class Outer {
    public int age;//外部类实例变量

    public class Inner {
        public int age;//内部类实例变量

        public void info() {
            System.out.println("外部类实例变量age的值:" + Outer.this.age);//外部类实例变量age的值:10
            System.out.println("内部类实例变量age的值:" + age);//输出:内部类实例变量age的值:20;就近原则,默认是内部类的age
        }
    }
}
  • 非静态内部类可以直接访问外部类的 private 成员变量,而非静态内部类的成员变量外部类却无法访问。如果需要访问内部类的成员变量,外部类只能通过创建内部类的对象,通过对象调用内部类的成员变量。
  • 不能在外部类的静态成员中直接使用非静态内部类,Java不允许在非静态内部类中定义静态成员(包括静态成员变量、静态方法、静态初始化块)

静态内部类

  • 静态内部类可以包含静态成员,也可以包含非静态成员;根据静态成员不能访问非静态成员规则,静态内部类不可以访问外部类的的实例成员,只能访问外部类的类成员

使用内部类

  1. 在外部类内使用内部类:与平常使用普通类没有什么区别,一样可以使用内部类类名来定义变量,通过构造器创建实例
class Outer {
    private class Inner {}

    public void test() {
        Inner inner = new Inner();
    }
}
  1. 在外部类以外使用内部类
    ①、如果希望在外部类以外使用内部类,则不能使用 private 访问控制权限 private 修饰的内部类只能在外部类中使用
    ②、省略内部类的访问控制符的内部类:只能在与外部类处于同一个包中的其他类所访问
    ③、使用 protected 修饰的内部类:可被与外部类处于同一个包中的其他类和外部类的子类所访问
    ④、使用 public 修饰的内部类:可以在任何地方被访问
    ⑤、在外部类以外的地方定义内部类(包括静态和非静态两种)变量的语法格式如下:
    OuterClass.InnerClass varName;
    在外部类以外的地方使用内部类时,内部类的完整类名应该是OuterClass.InnerClass; 如果有包名,应该加上包名前缀
public class TestInnerClass {
    public static void main(String[] args) {
        Outer out = new Outer();
        //在外部类以外的地方创建非静态内部内的实例
        Outer.Inner inner = out.new Inner();
        Outer.Inner inner2 = new Outer().new Inner();
    }
}

class Outer {
    public class Inner {}
}

非静态内部类构造器必须使用外部类对象来调用

非静态内部类的子类:创建一个子类时,子类总会先调用父类构造器,因此在创建非静态内部类的子类时,必须保证子类构造器先调用非静态内部类的构造器;调用非静态内部类的构造器时,必须存在一个外部类的对象

public class TestInnerClass {
    public static void main(String[] args) {
        Outer out = new Outer();
        // 创建内部类子类的实例
        SubClass subClass = new SubClass(out);
    }
}

class Outer {
    public class Inner {}
}

class SubClass extends Outer.Inner {
    public SubClass(Outer out) {
        out.super();
    }
}
  1. 在外部类以外使用静态内部类
    因为静态内部类是外部类类相关的,因此在创建静态内部类内部类对象时无需创建外部类的对象
    在外部类以外创建静态内部类的实例的语法:new OuterClass.InnerClass();
public class TestInnerClass {
    public static void main(String[] args) {
        // 创建静态内部类的实例
        Outer.Inner inner = new Outer.Inner();
    }
}

class Outer {
    public static class Inner {}
}
  1. 静态内部类的子类
public class TestInnerClass {
    public static void main(String[] args) {
        // 创建静态内部类子类的实例
        StaticSubClass subClass = new StaticSubClass();
    }
}

class Outer {
    public static class Inner {}
}

class StaticSubClass extends Outer.Inner {
    public StaticSubClass() {}
}

问:既然内部类是外部类的成员,那么是否可以可以为外部类定义子类,在子类中定义一个内部类来重写父类中内部类的呢?
答:不可以!由于内部类的类名不在是由简单的内部类的类名组成,他实际上还把外部类的类名作为一个命名空间,作为内部类类名的限制。因此子类的内部类和父类的内部类类名不可能完全相同。

局部内部类

把一个类定义在方法的内部,局部内部类只在方法内部有效,和局部变量一样,其他方法或者类永远无法访问到局部内部类的成员变量,因此局部内部类不能使用访问控制修饰符和 static

局部内部类的命名格式:OuterClass$NInnerClass.class; 局部内部类比成员内部类多了一个数字 N,这是因为成员内部类的类名不可能相同,而一个类中的局部内部类可能有多个。

Java8改进的匿名内部类

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

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

从上面可以看出:

  • 匿名内部类必须实现一个接口、或者继承一个父类,但最多只能实现一个接口,继承一个父类
    关于匿名内部类有如下两条规则:
  1. 匿名内部类不能是抽象类:因为系统在创建匿名内部类时会立即创建匿名内部类的实例。因此不允许把匿名内部类定义成抽象类
  2. 匿名内部类不能定义构造器:由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义初始化块,可以通过实例化初始化块来完成构造器需要完成的工作
//最常用的创建匿名内部类的方式是需要创建某个接口类型的对象:
interface Product {
    public String getName();
    public double getPrice();
}

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

    // 使用局部内部类
    public void testInnerClass() {
        TestAnonymouse anonymouse = new TestAnonymouse();
        anonymouse.test(new Product() {

            @Override
            public double getPrice() {
                return 567.8;
            }

            @Override
            public String getName() {
                return "AGP显卡";
            }
        });
    }
}

通过接口实现匿名内部类时,匿名内部类不能显示的定义一个构造器,匿名内部类只有一个隐式的无参数构造器,故 new 接口后的括号中不能写参数;如果通过继承父类来创建匿名内部类时,匿名内部类会拥有和父类相似(指的是相同的参数列表)的构造器,

//继承父类创建匿名内部类
abstract class Device {
    private String name;

    public abstract double getPrice();

    public Device() {}

    public Device(String name) {
        this.name = name;
    }
	//省略 name 的 getter 和 setter 方法
}

//测试继承父类创建匿名内部类
class AnonymousInner {
    public void test(Device d) {
        System.out.println("购买了一个" + d.getName() + ",花掉了" + d.getPrice());
    }

    public void testAnonymousInner() {
    	//当创建以 Divice 为父类的匿名内部类时,既可以传入参数,也可以不传入参数
        new AnonymousInner().test(new Device("电子示波器") {
            @Override
            public double getPrice() {
                return 67.8;
            }
        });
    }
}

Java8之前,Java要求被局部内部类、匿名内部类访问的局部变量必须使用 final 修饰
Java8取消了这个限制,如果局部变量被匿名内部类访问,那么局部变量相当于自动使用了 final 修饰符

public class TestInnerClass {
    public static void main(String[] args) {
        TestA testA = new TestA();
        testA.testFinal();
    }
}

//Java8取消了这个限制,如果局部变量被匿名内部类访问,
//那么局部变量相当于自动使用了final修饰符
interface A {
    void test();
}

class TestA {
    public void testFinal() {
        int age = 8;// 局部变量
        A a = new A() {
            @Override
            public void test() {
                System.out.println(age);//输出8
            }
        };
        a.test();
        //如下的赋值语句会导致编译不通过
        //Variable 'age' is accessed from within inner class ,needs to be final or effectively final
        //age = 10;
    }
}

编译之后的代码

class TestA {

    TestA() { }

    public void testFinal() {
    	//对比上面的代码,会发现Java自动的为age变量添加final修饰符
        final int age = 8;
        A a = new A() {
            final int val$age;
            final TestA this$0;

            public void test() {
                System.out.println(age);
            }


            {
                this.this$0 = TestA.this;
                age = i;
                super();
            }
        };
        a.test();
    }
}

对于被匿名内部类访问的局部变量,可以使用 final 修饰,也可以不使用 final 修饰,但必须按照使用 final 修饰的方式使用:即第一次赋值之后,不允许再次赋值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值