抽象类
抽象方法和抽象类
抽象方法和抽象类必须使用abstract修饰符来定义,有抽象方法的类只能被定义为抽象类,抽象类里可以没有抽象方法;
规则如下:
- 抽象类与抽象方法必须使用abstract修饰符来修饰,抽象方法不能有方法体;
- 抽象类不能被实例化。即使抽象类里不包含抽象方法,这个抽象类也不能创建实例;
- 抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要用于被其子类调用;
- 含有抽象方法的类(包括直接定义了一个抽象方法、继承了一个抽象父类,但没有完全实现父类包含的抽象方法、实现了一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义为抽象类;
public abstract class Shape
{
//初始化块
{
System.out.println("执行Shape的初始化块...");
}
//成员变量
private String color;
//定义一个计算周长的抽象方法
public abstract double calPerimeter();
//定义一个返回形状的抽象方法
public abstract String getType();
//普通方法
public void test()
{
System.out.println("普通方法");
}
//定义Shape的构造器,该构造器并不是用于创建Shape对象,而是用于被子类调用
public Shape(){}
public Shape(String color)
{
System.out.println("执行Shape的构造器...");
this.color = color;
}
public void setColor(String color)
{
this.color = color;
}
public String getColor()
{
return this.color;
}
}
抽象类不能用于创建实例,只能当作父类被其他子类继承:
public class Triangle extends Shape
{
//定义三角形的三边
private double a;
private double b;
private double c;
public Triangle(String color , double a , double b , double c)
{
super(color);
this.setSides(a , b , c);
}
public void setSides(double a , double b , double c)
{
if (a >= b + c || b >= a + c || c >= a + b)
{
System.out.println("三角形两边之和必须大于第三边");
return;
}
this.a = a;
this.b = b;
this.c = c;
}
//重写Shape类的的计算周长的抽象方法
public double calPerimeter()
{
return a + b + c;
}
//重写Shape类的的返回形状的抽象方法
public String getType()
{
return getColor() + "三角形";
}
}
当使用 abstract 修饰类时,表明这个类只能被继承;当使用 abstract 修饰方法时,表明这个方法必须由子类提供实现(即重写)。而 final 修饰的类不能被继承,final 修饰的方法不能被重写。因此, final 和 abstract 永远不能同时使用;
除此之外,当使用 static 修饰一个方法时,表明这个方法属于该类本身,即通过类就可以调用该方法,但如果该方法被定义为抽象方法,则将导致通过该类来调用该方法时出现错误(调用了一个没有方法体的方法肯定会引起错误)。因此 static 和 abstract 不能同时修饰某个方法,即没有所谓的类抽象方法;
注意:static 和 abstract 并不是绝对互斥的,static 和 abstract 虽然不能同时修饰某个方法,但它们可以同时修饰内部类;
注意:abstract 关键字修饰的方法必须被其子类重写才有意义,否则这个方法将永远不会再有方法体,因此abstract 方法不能被定义为 private 访问权限,即 private 和 abstract 不能同时修饰方法;
Java8的接口(interface)
简介
接口里不能包含普通方法,接口里的所有方法都是抽象方法。Java8对接口进行了改进,允许在接口中定义默认方法,默认方法可以提供方法实现;
接口的定义
- 修饰符可以是 public 或者省略,如果省略了 public 访问控制符,则默认采用包权限访问控制符,即只有在相同包结构下才可以访问该接口;
- 接口名与类名采用相同的命名规则;
- 一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类;
接口里不能包含构造器和初始化块定义。接口里可以包含成员变量(只能是静态常量)、方法(只能是抽象方法实例、类方法或默认方法)、内部类(包括内部接口、枚举)定义;
接口里的所有成员,包括常量、方法、内部类和内部枚举都是 public 访问权限。定义接口成员时,可以省略访问控制修饰符,如果指定访问控制修饰符,则只能使用 public 访问控制修饰符;
对于接口里定义的静态常量而言,系统会自动为这些成员变量增加 static 和 final 两个修饰符。也就是说,在接口中定义成员变量时,不管是否使用 public static final 修饰符,接口里的成员变量总是使用这三个修饰符来修饰。而且接口里没有构造器和初始化块,因此接口里定义的成员变量只能在定义时指定默认值;
//系统自动为接口里定义的成员变量增加 public static final 修饰符
int MAX_SIZE = 50;
public static final int MAX_SIZE = 50;
接口里定义的方法只能是抽象方法、类方法、默认方法,因此如果不是定义默认方法,系统将自动为普通方法增加 abstract 修饰符;定义接口里的普通方法不管是否使用 public abstract 修饰符,接口里的普通方法总是使用 public abstract 来修饰。接口里的普通方法不能有方法实现(方法体);但类方法、默认方法都必须有方法实现(方法体);
public interface Output
{
//接口里定义的属性只能是常量
int MAX_CACHE_LINE = 50;
//接口里定义的只能是public的抽象实例方法
void out();
void getData(String msg);
//在接口中定义默认方法,需要使用 default 修饰,默认方法总是使用 public 修饰---没有指定,系统会自动添加;
default void print(String...msgs)
{
for(String msg : msgs)
{
System.out.println(msg);
}
}
//在接口中定义默认方法,需要使用 default 修饰
default void test()
{
System.out.println("默认的 test() 方法");
}
//在接口里定义类方法,需要使用 static 修饰,类方法总是使用 public 修饰---没有指定,系统会自动添加;
static String staticTest()
{
return "接口里的类方法";
}
}
接口的继承
接口的继承和类继承不一样,接口完全支持多继承,即一个接口可以有多个接口父接口;
interface interfaceA
{
int PROP_A = 5;
void testA();
}
interface interfaceB
{
int PROP_B = 6;
void testB();
}
interface interfaceC extends interfaceA, interfaceB
{
int PROP_C = 7;
void testC();
}
public class TestInterfaceExtends
{
public static void main(String[] args)
{
System.out.println(interfaceC.PROP_A);
System.out.println(interfaceC.PROP_B);
System.out.println(interfaceC.PROP_C);
}
}
使用接口
一个类可以实现一个或多个接口,实现使用 implements 关键字;
一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类;
接口和抽象类
接口和抽象类很像,它们具有如下特征:
- 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承;
- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类必须实现这些抽象方法;
接口和抽象类在用法上存在如下差别:
- 接口里只能包含抽象方法、默认方法和类方法;抽象类则完全可以包含普通方法;
- 接口里只能定义静态常量,不能定义普通成员变量;抽象类则既可以定义普通成员变量,也可以定义静态常量;
- 接口里不包含构造器;抽象类则可以包含构造器,抽象类里的构造器不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作;
- 接口里不能包含初始化块;但抽象类则完全可以包含初始化块;
- 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口;
内部类
把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,包含内部类的类也被称为外部类;
定义内部类与定义外部类存在如下两点差别:
- 内部类比外部类可以多使用三个修饰符:private、protected、static-----外部类不可以使用这三个修饰符;
- 非静态内部类不能拥有静态成员;
非静态内部类
大部分时候,内部类都被作为成员内部类定义,而不是作为局部内部类(方法里定义的内部类被称为局部内部类)。成员内部类是一种与成员变量、方法、构造器和初始化块相似的类成员;局部内部类和匿名内部类则不是类成员;
成员内部类分为两种:
- 静态内部类:使用 static 修饰的成员内部类是静态内部类;
- 非静态内部类:没有使用 static 修饰的成员内部类是非静态内部类;
public class Cow
{
private double weight;
//外部类的两个重载的构造器
public Cow(){}
public Cow(double weight)
{
this.weight = weight;
}
//定义一个内部类
private class CowLeg
{
//内部类的两个属性
private double length;
private String color;
//内部类的两个重载的构造器
public CowLeg(){}
public CowLeg(double length , String color)
{
this.length = length;
this.color = color;
}
public void setLength(double length)
{
this.length = length;
}
public double getLength()
{
return this.length;
}
public void setColor(String color)
{
this.color = color;
}
public String getColor()
{
return this.color;
}
//内部类方法
public void info()
{
System.out.println("当前牛腿颜色是:" + color + ", 高:" + length);
//直接访问外部类的private属性:weight
System.out.println("本牛腿所在奶牛重:" + weight);
}
}
public void test()
{
CowLeg cl = new CowLeg(1.12 , "黑白相间");
cl.info();
}
public static void main(String[] args)
{
//Cow cow = new Cow(378.9);
//cow.test();
CowLeg c = new Cow(378.9).new CowLeg(1.12 , "黑白相间"); //两种方式都可以,第二种更直观
c.info();
}
}
在外部类里使用非静态内部类时,与平常使用普通类并没有太大的区别;
编译上面程序,看到在文件所在路径生成了两个 class 文件,一个是 Cow.class,另一个是 Cow $ CowLeg.class,前者是外部类 Cow 的 class 文件,后者是内部类 CowLeg 的 class 文件,即成员内部类(包括静态内部类、非静态内部类)的 class 文件总是这种形式:OuterClass$InnerClass.class;
在非静态内部类里可以直接访问外部类的 private 成员。这是因为在非静态内部类对象里,保存了一个它所寄生的外部类对象的引用;
当在非静态内部类的方法内访问某个变量时。系统优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量;如果不存在,则到该方法所在的内部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;如果不存在,则到该内部类所在的外部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;如果依然不存在,系统将出现编译错误:提示找不到该变量;
因此,如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可通过使用 this、外部类类名.this 作为限定来区分:
public class DiscernVariable
{
private String prop = "外部类属性";
private class InClass
{
private String prop = "内部类属性";
public void info()
{
String prop = "局部变量";
//通过 外部类类名.this.varName 访问外部类实例属性
System.out.println("外部类的属性值:" + DiscernVariable.this.prop);
//通过 this.varName 访问外内部类实例的属性
System.out.println("内部类的属性值:" + this.prop);
//直接访问局部变量
System.out.println("局部变量的属性值:" + prop);
}
}
public void test()
{
InClass in = new InClass();
in.info();
}
public static void main(String[] args)
{
new DiscernVariable().test();
}
}
非静态内部类的成员可以访问外部类的 private 成员,但反过来就不成立了。如果外部类需要访问非静态内部类的成员,必须显式创建非静态内部类对象来调用访问其实例成员:
public class Outer
{
private int outProp = 9;
class Inner
{
private int inProp = 5;
public void acessOuterProp()
{
//内部类可以直接访问外部类的成员
System.out.println("外部类的outProp属性值:" + outProp);
}
}
public void accessInnerProp()
{
//外部类不能直接访问内部类属性,下面代码出现编译错误
//System.out.println("内部类的inProp属性值:" + inProp);
//如需访问内部类成员,必须显式创建内部类对象
System.out.println("内部类的inProp属性值:" + new Inner().inProp);
}
public static void main(String[] args)
{
//执行下面代码,只创建了外部类对象,还未创建内部类对象
Outer out = new Outer();
}
}
Java不允许在非静态内部类里定义静态成员:
public class InnerNoStatic
{
private class InnerClass
{
/*
下面三个静态声明都将引发如下编译错误:
非静态内部类不能有静态声明
*/
static
{
System.out.println("==========");
}
private static int inProp;
private static void test(){};
}
}
非静态内部类里不能有静态方法、静态成员变量、静态初始化块;
静态内部类
使用 static 修饰的内部类称为静态内部类;
静态内部类可以包含静态成员,也可以包含非静态成员。
根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员;
public class TestStaticInnerClass
{
private int prop1 = 5;
private static int prop2 = 9;
static class StaticInnerClass
{
private static int age;
public void accessOuterProp()
{
//下面代码出现错误:静态内部类无法访问外部类的实例成员
System.out.println(prop1);
//下面代码正常
System.out.println(prop2);
}
}
}
外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员:
public class AccessStaticInnerClass
{
static class StaticInnerClass
{
private static int prop1 = 5;
private int prop2 = 9;
}
public void accessInnerProp()
{
//System.out.println(prop1);
//上面代码出现错误,应改为如下形式:通过类名访问静态内部类的类成员
System.out.println(StaticInnerClass.prop1);
//System.out.println(prop2);
//上面代码出现错误,应改为如下形式:通过实例访问静态内部类的实例成员
System.out.println(new StaticInnerClass().prop2);
}
}
Java允许在接口里定义内部类,接口里定义的内部类默认使用 public static 修饰,也就是说,接口内部类只能是静态内部类;
如果为接口内部类指定访问控制符,则只能指定 public 访问控制符;如果定义接口内部类时省略访问控制符,则该内部类默认是 public 访问控制权限;
使用内部类
1:在外部类内部使用内部类
通过 new 调用内部类构造器来创建实例;
唯一存在的一个区别是:不要在外部类的静态成员(包括静态方法和静态初始化块)中使用非静态内部类,因为静态成员不能访问非静态成员;
2:在外部类以外使用非静态内部类
在外部类以外的地方访问内部类(包括静态和非静态两种),则内部类不能使用 private 访问控制权限, private 修饰的内部类只能在外部类内部使用。对于其他访问修饰符修饰的内部类,则能在访问控制符对应的访问权限内使用;
- 省略访问控制符的内部类:只能被与外部类处于同一包中的其他类所访问;
- protected 修饰的内部类:可被与外部类处于同一包中的其他类和外部类的子类所访问;
- public 修饰的内部类:可以在任何地方被访问;
在外部类以外的地方使用内部类时,内部类完整的类名应该是:OuterClass.InnerClass;
由于非静态内部类的对象必须寄生在外部类的对象里,因此创建非静态内部类对象之前,必须先创建其外部类对象。在外部类以外的地方创建非静态内部类实例的语法:
OuterInstance.new InnerConstructor()
class Out
{
//定义一个非静态内部类,不使用访问控制符,即同一个包中其他类可访问该内部类
class In
{
public In(String msg)
{
System.out.println(msg);
}
}
}
public class CreateInnerInstance
{
public static void main(String[] args)
{
Out.In in = new Out().new In("测试信息");
/*
上面代码可改为如下三行代码:
使用OutterClass.InnerClass的形式定义内部类变量
Out.In in;
创建外部类实例,非静态内部类实例将寄存在该实例中
Out out = new Out();
通过外部类实例和new来调用内部类构造器创建非静态内部类实例
in = out.new In("测试信息");
*/
}
}
3:在外部类以外使用静态内部类
因为静态内部类是外部类类相关的,因此创建静态内部类对象时无须创建外部类对象;
new OuterClass.InnerConstructor()
class StaticOut
{
//定义一个静态内部类,不使用访问控制符,即同一个包中其他类可访问该内部类
static class StaticIn
{
public StaticIn()
{
System.out.println("静态内部类的构造器");
}
}
}
public class CreateStaticInnerInstance
{
public static void main(String[] args)
{
StaticOut.StaticIn in = new StaticOut.StaticIn();
/*
上面代码可改为如下两行代码:
使用OutterClass.InnerClass的形式定义内部类变量
StaticOut.StaticIn in;
通过new来调用内部类构造器创建静态内部类实例
in = new StaticOut.StaticIn();
*/
}
}
局部内部类
方法里定义的内部类称为局部内部类。局部内部类也不能使用访问控制符和 static 修饰符修饰;
public class LocalInnerClass
{
public static void main(String[] args)
{
//定义局部内部类
class InnerBase
{
int a;
}
//定义局部内部类的子类
class InnerSub extends InnerBase
{
int b;
}
//创建局部内部类的对象
InnerSub is = new InnerSub();
is.a = 5;
is.b = 8;
System.out.println("InnerSub对象的a和b属性是:" + is.a + "," + is.b);
}
}
匿名内部类
匿名内部类适合创建那种只需要一次使用的类;
匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,或实现一个接口;
匿名内部类有如下两条规则:
- 匿名内部类不能是抽象类;
- 匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义初始化块,可以通过实例初始化块来完成构造器需要完成的事情;
interface Product
{
public double getPrice();
public String getName();
}
public class TestAnonymous
{
public void test(Product p)
{
System.out.println("购买了一个" + p.getName() + ",花掉了" + p.getPrice());
}
public static void main(String[] args)
{
TestAnonymous ta = new TestAnonymous();
//调用test方法时,需要传入一个Product参数,此处传入其匿名实现类的实例
ta.test(new Product()
{
public double getPrice()
{
return 567.8;
}
public String getName()
{
return "AGP显卡";
}
});
}
}
对于上面创建Product实现类对象的代码,可以拆分成如下代码:
class AnonymousProduct implements Product
{
public double getPrice()
{
return 567.8;
}
public String getName()
{
return "AGP显卡";
}
}
ta.test(new AnonymousProduct());
通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器(拥有相同的形参列表);
Java8之前,Java要求被局部内部类、匿名内部类访问的局部变量必须使用 final 修饰,从Java8开始这个限制被取消了,Java8更加智能:如果局部变量被匿名内部类访问,俺么该局部变量相当于自动使用了 final 修饰:
interface A
{
void test();
}
public class TestA
{
public static void main(String[] args)
{
int age = 0;
A a = new A()
{
public void test()
{
//在Java8以前下面语句将提示错误: age 必须使用 final 修饰
//从Java8开始,匿名内部类、局部内部类允许访问非 final 的局部变量
System.out.println(age);
}
};
}
}
这次的笔记可真多,得慢慢消化吧!!!
书籍:疯狂Java讲义