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或者省略
接口中可以包含的成员(接口中不能包括静态方法和初始化块):
- 成员变量(只能是静态常量),默认和指定都只能用 public static final修饰,接口里没有构造器和初始化块,所以只能在定义时指定默认值
方法(只能是抽象实例方法、类方法或默认方法):
普通方法使用 public abstract修饰,且不能有方法体;
默认方法必须有方法体,必须用default修饰,不能用static修饰;系统还会为默认方法指定public修饰符;需要用接口实现类的实例调用
类方法必须有方法体,必须用static修饰;系统还会为默认方法指定public修饰符;可直接使用接口来调用
内部类(包括内部接口和枚举) 默认使用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
名字不同,不能重写
在外部类内部使用内部类
在外部类以外使用非静态内部类
定义内部类(包括静态和非静态两种)
OuterClass.InnerClass varName
创建非静态内部类实例(因为非静态内部类的对象必须寄生在外部类对象里
)
OuterInstance.new InnerConstructor()
创建非静态内部类的子类
//静态内部类创建子类
public class SubClass extends Out.In {
public SubClass(Out out) {
// 子类构造器一定会调用父类的构造器
// 非静态内部类的构造器一定通过外部类的对象来调用
out.super("hello");
}
}
- 在外部类以外使用静态内部类
定义内部类(包括静态和非静态两种)
OuterClass.InnerClass varName
创建静态内部类实例
new OuterClass.InnerClassConstructor()
创建静态内部类的子类
public class StaticSubClass extends StaticOut.StaticIn {}
- 局部内部类
类在方法中定义
不能使用访问控制符,因为上级程序单元是方法
作用域太小,在开发中很少用到
- Java8改进的匿名内部类
匿名内部类适合创建那种只需要一次使用的类,创建匿名内部类时会立即创建一个该类的实例,
这个类定义即消失,匿名内部类不能重复使用。
定义:
new 实现接口() | 父类构造器(实参列表)
{
// 匿名内部类的类体部分
}
匿名内部类必须继承一个父类,或实现一个接口
规则:
匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。
因此不允许将匿名内部类定义为抽象类。匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,匿名只有一个隐式的无参构造器。
但创建匿名内部类最常用的方式是需要创建某个接口类型的对象。
创建某个接口类型的对象
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里被称为枚举类(例如季节类)
手动实现枚举类
- 第一阶段:通过定义静态常量
- 第二阶段:通过定义类(通过private隐藏构造器,实例用public static final修饰,提供一些静态方法)
- 第三阶段:JDK1.5之后提供支持
枚举类入门
Java5新增了一个enum关键字(它与class,interface地位相同),用来定义枚举类。
一个Java源文件最多只能定义一个public访问权限的枚举类,且名字与Java源文件相同。
与普通类的区别:
- 不能显示继承其他父类(因为使用enum定义的枚举类默认继承了java.lang.Enum类)
- 不能派生子类(使用enmu定义,非抽象的枚举类默认会用final修饰)
- 枚举类的构造器只能用private修饰
- 枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。
列出这些实例时,系统会自动添加public static final修饰 - 枚举类提供了一个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());
}
}
实现接口的枚举类
- 由枚举类来实现接口
- 由每个枚举值提供不同的实现方式
下面的程序编译后生成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()方法,该方法可能使对象重新复活(让一个变量重新引用该对象),从而导致垃圾回收机制取消回收。
)
对象在内存中的状态
- 可达状态:该对象有一个以上的引用变量引用
- 可恢复状态:该对象不再有引用变量引用,它就进入可恢复状态。
- 不可达状态:可恢复状态的对象,再调用finalize()方法之后,若该对象没有变成可达状态,则变为不可达状态。
只有一个对象变为不可达状态时,系统才会真正回收该对象所占有的资源。
强制垃圾回收
程序无法强制控制Java垃圾回收的时机,但依然可以强制系统进行垃圾回收。
两种方式(作用完全相同):
- System.gc()
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包下):
- 强引用(StrongReference):最常见
- 软引用(SoftReference)
- 弱引用(WeakReference)
- 虚引用(PlantomReference)
11.修饰符的适用范围
- strictfp:含义时FP-strict,也就是精确浮点的意思,修饰后Java的编译器和运行时环境会按照浮点规范IEEE-754来执行。
修饰:外部类/接口、方法、成员内部类 native: 主要用于修饰一个方法,使用native修饰方法后,则该方法通常使用C语言来实现。
一旦Java中包含了native方法,那么这个程序将失去跨平台的功能。4个访问控制符是互斥的,最多出现其中之一
- abstract和final永远不能同时使用
- abstract和static不能同时修饰方法,但可以同时修饰内部类
- abstract和private不能同时修饰方法,但可以同时修饰内部类
- private和final修饰方法,虽然语法没问题。但是没有必要,因为private修饰的方法不可能被子类重写。
12.适用JAR文件
- jar命令详解
JAR包
- 创建可执行的JAR包
- 关于JAR包的技巧