面向对象(三)
《疯狂Java讲义》学习笔记
抽象类
抽象方法和抽象类
- 抽象方法和抽象类使用
abstract
修饰:有抽象方法的类只能被定义成抽象类,抽象类可以没有抽象方法 - 规则:
- 抽象方法和抽象类使用
abstract
修饰,抽象方法不能有方法体 - 抽象类不能被实例化,无法使用
new
关键字调用抽象类的构造器创建抽象类类实例 - 抽象类可以包含:
成员变量
、方法(普通方法或抽象方法)
、构造器
、初始化块
、内部类(接口、枚举)
- 含有抽象方法的类(包括直接定义一个抽象方法、继承一个抽象父类(实现一个接口)但没有完全实现父类的抽象方法(没有完全实现接口的抽象方法))
- 使用
abstract
修饰类时:
表明这个类只能被继承;使用abstract
修饰方法时,表明这个方法只能被子类重写;而final
修饰的类不能被继承,final
修饰的方法不能被重写,因此final
和abstract
永远不可能同时使用 abstract
不能用来修饰成员变量、局部变量和构造器static
和abstract
不能同时修饰一个方法private
和abstract
不能同时修饰一个方法;
Java8改进的接口
接口里没有普通方法,所有的方法都是抽象方法,Java8对接口进行了改进,允许接口中包含默认方法,默认方法可以提供方法实现
接口的定义
接口使用 interface
关键字
[ 修饰符 ] interface 接口名 extends 父接口1,父接口2…{
零到多个常量定义…
零到多个抽象方法定义…
零到多个内部类、接口、枚举定义…
零到多个默认方法、类方法定义… //只有在Java8中才可以定义默认方法、类方法
}
一个接口可以有多个父接口,但接口只能继承接口,不能继承类
接口规范
- 接口中不能定义构造器和初始化块
- 可以包含成员变量:只能是静态常量
- 方法:只能是抽象方法、类方法、默认方法(使用
default
修饰) - 内部类、内部接口、枚举
- 关于接口内属性和方法的修饰符
接口中定义的是多个类的共同的公共行为规范,因此接口中的所有成员:包括常量、方法、内部类和枚举类都是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 "接口中的类方法";
}
}
接口的继承
接口继承和类继承不一样,接口完全支持多继承,即一个接口可以有多个直接父接口
和继承一样,子接口扩展某个父接口,会获得父接口里定义的所有常量,抽象方法
在进行强制类型转换时,互相强转的双方只要有一方是接口类型,则一定编译通过;
但是若引用中指向的实际对象类型和目标类型
使用接口
- 接口不能创建实例,但是接口可以定义引用类型的变量,使用接口定义引用类型的变量时,这个引用类型的变量必须引用到其实现类身上
- 接口的主要用途:
- 定义变量,也可以用于强制类型转换
- 调用接口中的常量
- 被其他类实现
- 一个类实现了一个或者多个接口后,必须完全实现接口中定义的抽象方法,否则这个类必须被定义成抽象类
- 实现接口时,子类重写的方法必须使用
public
修饰,因为接口中的方法都是public
修饰的 - 子类重写父类方法的原则:
两同两小一大的原则
,子类的访问修饰符必须大于等于父类的访问修饰符
面向接口编程
- 工厂模式
- 代理模式
内部类
- 含义:一个类被定义在另一个类的内部
- 作用
- 内部类提供更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类;
- 内部类成员可以直接访问外部类的私有数据,因为内部类被当外部类的成员,同一个类的成员之间可以相互访问,但外部类不能访问内部类的实现细节,例如内部类的成员变量
- 匿名内部类适合用于创建那些仅需要一次使用的类,
- 内部类可以比外部类多使用
private
、protected
、static
这三个修饰符 - 非静态内部类不可以拥有静态成员
非静态内部类
- 定义位置
在一个内的内部定义即可:此处的“类内部”包括类的任意位置,在方法中也可以定义内部类(在方法中定义的内部类被称为局部内部类
);
大部分时候内部类被作为成员内部类定义,成员内部类
是一种与成员变量
、方法
、构造器
和初始化块
相似的类成员; - 局部内部类和匿名内部类不是类成员;
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文件;即成员内部类(包括静态内部类和非静态内部类)
- 在非静态内部类的方法中访问某个变量:查找顺序
- 在该方法内查找是否存在改名字的局部变量,如果存在就是用;
- 如果不存在,就在该内部类中寻找是否存在该名字的成员变量;
- 如果不存在,就在外部类中寻找是否存在改名字的成员变量,如果不存在,编译报错;
- 如果外部类成员变量、内部类成员变量与内部类方法的局部变量同名,可以通过,
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不允许在非静态内部类中定义静态成员(包括静态成员变量、静态方法、静态初始化块)
静态内部类
- 静态内部类可以包含静态成员,也可以包含非静态成员;根据静态成员不能访问非静态成员规则,静态内部类不可以访问外部类的的实例成员,只能访问外部类的类成员
使用内部类
- 在外部类内使用内部类:与平常使用普通类没有什么区别,一样可以使用内部类类名来定义变量,通过构造器创建实例
class Outer {
private class Inner {}
public void test() {
Inner inner = new Inner();
}
}
- 在外部类以外使用内部类
①、如果希望在外部类以外使用内部类,则不能使用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();
}
}
- 在外部类以外使用静态内部类
因为静态内部类是外部类类相关的,因此在创建静态内部类内部类对象时无需创建外部类的对象
在外部类以外创建静态内部类的实例的语法:new OuterClass.InnerClass();
public class TestInnerClass {
public static void main(String[] args) {
// 创建静态内部类的实例
Outer.Inner inner = new Outer.Inner();
}
}
class Outer {
public static class Inner {}
}
- 静态内部类的子类
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 实现接口() | 父类构造器(参数列表){
//匿名实现类的类体部分
}
从上面可以看出:
- 匿名内部类必须实现一个接口、或者继承一个父类,但最多只能实现一个接口,继承一个父类
关于匿名内部类有如下两条规则:
- 匿名内部类不能是抽象类:因为系统在创建匿名内部类时会立即创建匿名内部类的实例。因此不允许把匿名内部类定义成抽象类
- 匿名内部类不能定义构造器:由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义初始化块,可以通过实例化初始化块来完成构造器需要完成的工作
//最常用的创建匿名内部类的方式是需要创建某个接口类型的对象:
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
修饰的方式使用:即第一次赋值之后,不允许再次赋值