抽象类
抽象类声明
abstract class 类名 { }
用关键字 abstract 修饰的类,就是抽象类
- 抽象类使用abstract来修饰,抽象类不能实例化对象。
- 抽象类中是可以写非静态的成员的,这时候这些非静态成员是可以继承给子类的。
- 抽象类中是可以包含构造方法的。
抽象方法声明
用关键字 abstract 修饰的方法,就是抽象方法,抽象方法,只能够写在抽象类中。
public abstract 返回值类型 方法名(参数);
特点
- 抽象方法:抽象方法使用 abstract 来修饰,只有声明,没有实现。
- 结合抽象类和抽象方法:非抽象子类在继承一个抽象父类时,要实现父类中所有的抽象方法。
//研发工程师
abstract class Developer {public abstract void work();//抽象函数。需要abstract修饰,并分号;结束
}//JavaEE工程师
class JavaEE extends Developer{public void work() {System.out.println("正在研发淘宝网站");}
}//移动端工程师
class Android extends Developer {public void work() {System.out.println("正在研发某款app");}
}
抽象类的特点
- 抽象类与抽象方法都必须使用 abstract来修饰。
- 抽象类不能直接创建对象。
- 抽象类中可以有抽象方法,也可以没有抽象方法。
- 抽象类的子类,实现了抽象方法的具体类或者抽象类。
接口
接口概念
接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的”类”。
接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类来完成。这样将功能的定义与实现分离,优化了程序设计。
请记住:一切事物均有功能,即一切事物均有接口。
接口的特点
- 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
- 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
- 接口中的方法访问权限修饰符都是public 。
- 接口中的方法都是公有的。
- 接口中的属性,默认的修饰符是 public static final 。
- 接口中不能写构造方法 。
声明接口
Interface 关键字用来声明一个接口。
访问权限修饰符 interface 接口名 {
抽象方法1;
抽象方法2;
抽象方法3;
}
接口的实现
接口不能实例化。
按照多态的方式,由具体的实现类实例化。其实这也是多态的一种,接口多态。
类使用 implements 关键字实现接口。在类声明中,Implements 关键字放在 class 声明后面。
public class XX extends YY implements ZZ { XX }
- 一个非抽象类在实现接口后,需要实现接口中所有的抽象方法。
- 一个类在继承自一个父类后,还可以再去实现接口。如果同时有父类和接口,那么继承父类在前,实现接口在后 。
- 一个类可以实现多个接口。如果一个类实现的多个接口中有相同的方法,这个方法在实现类中只需要实现一次即可。
- 接口之间是有继承关系的,而且接口之间的继承是多继承。
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{public void eat(){System.out.println("Mammal eats");}public void travel(){System.out.println("Mammal travels");} public int noOfLegs(){return 0;}public static void main(String args[]){MammalInt m = new MammalInt();m.eat();m.travel();}
}
以上实例编译运行结果如下:
Mammal eats
Mammal travels
重写接口中声明的方法时,需要注意以下规则
- 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
- 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
- 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。
在实现接口的时候,也要注意一些规则
- 一个类可以同时实现多个接口。
- 一个类只能继承一个类,但是能实现多个接口。
- 一个接口能继承另一个接口,这和类之间的继承比较相似。
接口的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
下面的Sports接口被Hockey和Football接口继承:
// 文件名: Sports.javapublic interface Sports
{public void setHomeTeam(String name);public void setVisitingTeam(String name);
}// 文件名: Football.javapublic interface Football extends Sports
{public void homeTeamScored(int points);public void visitingTeamScored(int points);public void endOfQuarter(int quarter);
}// 文件名: Hockey.javapublic interface Hockey extends Sports
{public void homeGoalScored();public void visitingGoalScored();public void endOfPeriod(int period);public void overtimePeriod(int ot);
}
Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。
相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。
接口的多继承
在Java中,类的多继承是不合法,但接口允许多继承。
在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:
public interface Hockey extends Sports, Event
以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可以定义或是继承相同的方法
标记接口
最常用的标记接口是没有包含任何方法的接口。
标记接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。
标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。
例如:java.awt.event 包中的 MouseListener 接口继承的 java.util.EventListener 接口定义如下:
package java.util;
public interface EventListener {}
没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的
- 建立一个公共的父接口:
正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
- 向一个类添加数据类型:
这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。
内部类
什么是内部类?
- 在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。
内部类的分类
静态内部类
- 定义在类内部的静态类,就是静态内部类。
public class Outer {private static int radius = 1;static class StaticInner {public void visit() {System.out.println("visit outer static variable:" + radius);}}
}
- 静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;静态内部类的创建方式,
new 外部类.静态内部类(),如下:
Outer.StaticInner inner = new Outer.StaticInner();
inner.visit();
成员内部类
- 定义在类内部,成员位置上的非静态类,就是成员内部类。
public class Outer {private static int radius = 1;private int count =2;class Inner {public void visit() {System.out.println("visit outer static variable:" + radius);System.out.println("visit outer variable:" + count);}}
}
- 成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式外部类实例.new 内部类(),如下:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.visit();
局部内部类
- 定义在方法中的内部类,就是局部内部类。
public class Outer {private int out_a = 1;private static int STATIC_b = 2;public void testFunctionClass(){int inner_c =3;class Inner {private void fun(){System.out.println(out_a);System.out.println(STATIC_b);System.out.println(inner_c);}}Inner inner = new Inner();inner.fun();}public static void testStaticFunctionClass(){int d =3;class Inner {private void fun(){// System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量System.out.println(STATIC_b);System.out.println(d);}}Inner inner = new Inner();inner.fun();}
}
- 定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的创建方式,在对应方法内,new 内部类(),如下:
public static void testStaticFunctionClass(){class Inner {}Inner inner = new Inner();}
匿名内部类
- 匿名内部类就是没有名字的内部类,日常开发中使用的比较多。
public class Outer {private void test(final int i) {new Service() {public void method() {for (int j = 0; j < i; j++) {System.out.println("匿名内部类" );}}}.method();}}//匿名内部类必须继承或实现一个已有的接口 interface Service{void method();
}
- 除了没有名字,匿名内部类还有以下特点:
- 匿名内部类必须继承一个抽象类或者实现一个接口。
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
- 匿名内部类创建方式:
new 类/接口{ //匿名内部类实现部分 }
内部类的优点
我们为什么要使用内部类呢?因为它有以下优点:
- 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
- 内部类不为同一包的其他类所见,具有很好的封装性;
- 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
- 匿名内部类可以很方便的定义回调。
内部类有哪些应用场景
- 一些多算法场合
- 解决一些非面向对象的语句块。
- 适当使用内部类,使得代码更加灵活和富有扩展性。
- 当某个类除了它的外部类,不再被其他的类使用时。
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
先看这段代码:
public class Outer {void outMethod(){final int a =10;class Inner {void innerMethod(){System.out.println(a);}}}
}
以上例子,为什么要加final呢?是因为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
内部类变量
public class Outer {private int age = 12;class Inner {private int age = 13;public void print() {int age = 14;System.out.println("局部变量:" + age);System.out.println("内部类变量:" + this.age);System.out.println("外部类变量:" + Outer.this.age);}}public static void main(String[] args) {Outer.Inner in = new Outer().new Inner();in.print();}}
运行结果:
局部变量:14
内部类变量:13
外部类变量:12
枚举类
枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一年的 12 个月份,一个星期的 7 天,方向有东南西北等。
Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。
实现步骤
- 构造器私有化
- 本类内部创建一组对象
- 对外暴露对象(通过为对象添加public final static修饰符)
- 提供get方法,但是不提供set方法
注意事项和细节
- 不需要提供setxxx 方法,因为枚举对象值通常为只读
- 枚举对象/属性使用 final + static 共同修饰,实现底层优化
- 枚举对象名通常使用全部大写,常量的命名规范
- 枚举对象根据需要,也可以有多个属性
例如定义一个颜色的枚举类。
enum Color {
RED, GREEN, BLUE;
}
以上枚举类 Color 颜色常量有 RED, GREEN, BLUE,分别表示红色,绿色,蓝色。
使用实例:
enum Color
{RED, GREEN, BLUE;
}public class Test
{// 执行输出结果public static void main(String[] args){Color c1 = Color.RED;System.out.println(c1);}
}
执行以上代码输出结果为:
RED
内部类中使用枚举
枚举类也可以声明在内部类中:
public class Test
{enum Color{RED, GREEN, BLUE;}// 执行输出结果public static void main(String[] args){Color c1 = Color.RED;System.out.println(c1);}
}
执行以上代码输出结果为:
RED
每个枚举都是通过 Class 在内部实现的,且所有的枚举值都是 public static final 的。
以下的枚举类 Color 转化在内部类实现:
class Color
{public static final Color RED = new Color();public static final Color BLUE = new Color();public static final Color GREEN = new Color();
}
迭代枚举元素
可以使用 for 语句来迭代枚举元素:
enum Color
{RED, GREEN, BLUE;
}
public class MyClass {public static void main(String[] args) {for (Color myVar : Color.values()) {System.out.println(myVar);}}
}
执行以上代码输出结果为:
RED
GREEN
BLUE
在 switch 中使用枚举类
枚举类常应用于 switch 语句中:
enum Color
{RED, GREEN, BLUE;
}
public class MyClass {public static void main(String[] args) {Color myVar = Color.BLUE;switch(myVar) {case RED:System.out.println("红色");break;case GREEN:System.out.println("绿色");break;case BLUE:System.out.println("蓝色");break;}}
}
执行以上代码输出结果为:
蓝色
枚举类成员
枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。
枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。
enum Color
{RED, GREEN, BLUE;// 构造函数private Color(){System.out.println("Constructor called for : " + this.toString());}public void colorInfo(){System.out.println("Universal Color");}
}public class Test
{ // 输出public static void main(String[] args){Color c1 = Color.RED;System.out.println(c1);c1.colorInfo();}
}
执行以上代码输出结果为:
Constructor called for : RED
Constructor called for : GREEN
Constructor called for : BLUE
RED
Universal Color
接口与类
接口与类相似点
- 一个接口可以有多个方法。
- 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
- 接口的字节码文件保存在 .class 结尾的文件中。
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
接口与类的区别
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
访问权限修饰符
- 和类一样,只能有 public 和默认的default权限。
- 接口不是类,不能实例化对象。
- 接口,暂时和类写成平级的关系。
- 接口名字是一个标识符,遵循大驼峰命名法
抽象类与接口比较
- 抽象类是用来捕捉子类的通用特性的。接口是抽象方法的集合。
- 从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
- 接口的设计是自上而下的。
- 抽象类的设计是自下而上的。
相同点
- 接口和抽象类都不能实例化
- 都位于继承的顶端,用于被其他实现或继承
- 都包含抽象方法,其子类都必须覆写这些抽象方法
不同点
参数 | 抽象类 | 接口 |
声明 | 抽象类使用 abstract 关键字声明 | 接口使用 interface 关键字声明 |
实现 | 子类使用 extends 关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现 | 子类使用 implements 关键字来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
访问修饰符 | 抽象类中的方法可以是任意访问修饰符 | 接口方法默认修饰符是 public。并且不允许定义为 private 或者 protected |
多继承 | 一个类最多只能继承一个抽象类 | 一个类可以实现多个接口 |
字段声明 | 抽象类的字段声明可以是任意的 | 接口的字段默认都是 static 和 final 的 |
备注:
- JDK 1.8 以后,接口里可以有静态(static)方法和方法体了。
- JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。
- JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。
- JDK 14 以后,引入sealed 接口(仅在某些子类中使用),进一步增强接口的功能。
现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。
- 接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:
- 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
- 选择抽象类的时候通常是需要定义子类的行为,又要为子类提供通用的功能。
类、抽象类、接口
三者比较
- 类与类:继承关系,只能单继承,但是可以多层继承(生物-》动物=》猫类)。
- 类与接口:实现关系,可以单实现,也可以多实现。还可以在继承一个类的同时实现多个接口。
- 接口与接口:继承关系,可以单继承,也可以多继承。
三者区别
- 成员区别
- 抽象类:变量、常量、有抽象方法、抽象方法、非抽象方法
- 接口:常量、抽象方法
- 类:变量、常量、方法
- 关系区别
- 类与类:单继承
- 类与接口:单实现、多实现
- 接口与接口:单继承、多继承
- 设计理念区别
- 接口的设计是自上而下的。
- 抽象类的设计是自下而上的。