抽象、接口
抽象
使用场景
多个子类的共性方法存在差别,父类无法具体描述时~使用抽象方法和抽象类(抽象方法所在的类)
定义方式
- 抽象方法: 用abstract修饰且没有方法体,所在类必须为抽象类
不能被 static 、private、final 修饰,只能被 abstract 、public 、 protected、[default]修饰(默认不写即 default 也可以)
- 抽象类: 用abstract修饰且包含抽象方法
可以 没有抽象方法
可以有 非抽象方法
不能实例化
应当是一个父类
可以有 构造函数 和重载构造函数
public abstract class 类名{
public abstract 返回类型 方法名(参数1,参数2... ...);
}
使用方式
- 第一种
创建自定义类继承抽象类
重写父类的所有抽象方法
创建子类对象,调用方法 - 第二种
创建自定义抽象类继承抽象类
权限修饰符
修饰对象:成员方法/变量( 不能修饰局部 )
可访问范围 | 假设:demo包中的A类的一个成员方法/变量 | public | protected | [default] | private |
---|---|---|---|---|---|
同一个类中 | demo包中的A类中的成员方法中,尝试直接【用变量名/方法名】调用A的变量/方法 | √ | √ | √ | √ |
同包不同类中 | demo包中的B类中,尝试【创建A类的对象,对象.变量 或者 对象.方法 】调用A的变量/方法 | √ | √ | √ | × |
不同包中子类 | Test包中的B类中,B extends A,尝试直接【用变量名/方法名】调用A的变量/方法 | √ | √ | × | × |
不同包中无关类 | Test包中的B类中,尝试【创建A类的对象,对象.变量 或者 对象.方法 】调用A的变量/方法 | √ | × | × | × |
总结 | public>protected>[default]>private | 同工程可见 | 有关系(继承/同包)可见 | 包内可见 | 仅类内可见 |
使用场景 | ①希望对外提供的(如私有属性的set/get方法) ②工具类中的方法(如Math.PI、Math.pow()) | 需要确保子类的访问的内容 (抽象类的成员访问权限至少是protected) | 较少使用 包内各类间关联性强,需要互相访问,而包外不允许访问时 | ① 属性的封装(类外通过get/set安全访问)一般不修饰方法(一旦私有化类外完全无法访问) ②工具类中的辅助数据:当变量不是服务于前端调用者,而只是功能实现的过程时,需对其私有化避免被随意调用 |
匿名
String[] names={"张三","李四","王五"};
System.out.println(names[new Random().nextInt(names.length)]);
//Random类的对象没有对象名,即匿名的(只有地址值);
优点
- 节省代码
缺点
- 一次性
只能使用一次,无法再次找到这个对象,对其操作(关闭资源、修改值等) - 代码可读性差
使用场景
- 推荐用于 想要调用某个对象的方法(仅调用一次的)
- 不推荐用于属性
接口 interface
概述
-
≈ 仅包含抽象方法的抽象类
-
优点
提高扩展性
对外提供了统一的标准和准则 -
格式
//定义一个接口 public interface 接口名{ } //实现一个接口 public class 类名 implements 接口名{ }
-
特点
成员 | 特点 |
---|---|
成员变量 | 默认 public、static、final的 |
成员方法 | 默认 public、abstract的 java1.8及以后可以有非抽象方法(使用default声明,不写默认public)——为实现类提供共性方法 java1.8及以后可以有静态的方法(使用static声明,不写默认public)——为了方便调用 java1.9及以后可以有私有方法(使用private声明)——为了辅助本接口功能的运行(为了本接口的静态/普通方法的实现) |
接口 | 不能被实例化(抽象) |
默认修饰符的原因
static、abstract、final——强制调用者必须重写、变量统一,即遵循接口的规则;
使用
- 自定义实现类,实现接口
- 实现接口的所有抽象方法
- 创建实现类对象,调用方法(接口的成员变量可以直接用接口名调用)
public interface D {//定义一个接口
int i=0;//接口的成员变量,默认public static final的
void f() {//接口的成员方法,默认public abstract的
//System.out.println("D接口的f方法"); //不能写,会报错,大括号都不能写
}
default void e() {//接口的成员方法,java1.8及以后可以手动声明非抽象方法,可以被重写,默认public
System.out.println("java1.8及以后的版本,D接口也可以有default静态方法了");
}
static void g() {//接口的成员方法,java1.8及以后可以手动声明静态方法,不能被重写,默认public
System.out.println("java1.8及以后的版本,D接口也可以有静态方法了");
}
private void h() {//接口的成员方法,java1.9及以后可以手动声明私有方法,不能被重写
System.out.println("java1.9及以后的版本,D接口也可以有私有方法了");
}
}
public class T implements D{//实现类实现接口
System.out.println(D.i);//可以直接调用接口的成员方法
System.out.println(D.i++);//final的变量i,不能做++运算
@Override//必须实现接口的抽象方法
public void f() {//只能是public的,因为接口的方法默认public的,重写的范围修饰符只能大于等于原方法;
System.out.println("实现类重写/实现接口的抽象方法");
}
}
public class Test1 {
public static void main(String[] args) {
D d=new T();//多态写法,一个接口无法创建对象,但一个接口的实现类的对象也是接口的对象
d.e();
d.g();
//d.h();//会报错
}
}
多层继承/实现关系
接口可以实现多“继承”
一个实现类实现多个接口
一个接口可以同时继承多个接口
实现类可以继承类的同时实现接口
定义接口D
-----------------------------------------------------------------------
public interface D {
int i=1;
void f();
void g();
void a();
default void h(){};
default void j(){};
}
定义接口E
-----------------------------------------------------------------------
public interface E {
int i =2;
void f();
default void h(){};
}
定义父类A
-----------------------------------------------------------------------
public class A {
int i=3;
public void a() {//父类中已经有方法与接口的抽象方法同名,子类继承到了这个方法,等于已经实现了那个抽象方法,故无需重写D的a方法
}
}
定义实现类E实现接口D和E
-----------------------------------------------------------------------
public class T extends A implements D,E{
static int i=0;
public static void main(String[] args) {
System.out.println(D.i);
System.out.println(E.i);
System.out.println(i);
}
@Override//抽象方法重名,重写一次就好,反正没有方法体
public void f() {
System.out.println("实现类T重写的(D、E接口的重名抽象方法)F方法");
}
@Override//抽象方法不重名,也需要重写
public void g() {
System.out.println("实现类T重写的(D接口的抽象方法)G方法");
}
@Override//默认普通方法不重名不需要重写,重名才需要重写
public void h() {
System.out.println("实现类T重写的(D接口的默认方法)H方法");
D.super.h();//如果想要使用重名普通方法,需明确使用的是哪个父类的,在前面加D.或者E.
}
}
测试类创建实现类对象查看调用情况
-----------------------------------------------------------------------
public class Test1 {
public static void main(String[] args) {
D td=new T();
E te=new T();
T t=new T();
System.out.println(td.i);//1
System.out.println(te.i);//2
System.out.println(t.i);//0
td.f();//实现类T重写的(D、E接口的重名抽象方法)F方法
td.g();//实现类T重写的(D接口的抽象方法)G方法
td.h();//实现类T重写的(D接口的默认方法)H方法
td.j();//无反应。D接口的j默认方法没有写方法体内容
td.a();//无反应。父类A的a默认方法没有写方法体内容
te.f();//实现类T重写的(D、E接口的重名抽象方法)F方法
//te.g();//编译报错,编译看左边,E类没有g方法
te.h();//实现类T重写的(D接口的默认方法)H方法
//te.j();//编译报错,编译看左边,E类没有j方法
//te.a();//编译报错,编译看左边,E类没有a方法
t.f();//实现类T重写的(D、E接口的重名抽象方法)F方法
t.g();//实现类T重写的(D接口的抽象方法)G方法
t.h();//实现类T重写的(D接口的默认方法)H方法
t.j();//无反应。D接口的j默认方法没有写方法体内容
t.a();//无反应。父类A的a默认方法没有写方法体内容
}
}
实现类/子接口实现的多个接口间存在:
- 重名方法
- 实现类将抽象方法重写一次即可;子接口无需重写(等其实现类来重写)
- 非抽象方法实现类/子接口都需要重写(因为会引起歧义)(子接口被实现类调用时,会根据就近原则,默认调用到最外层的接口中的方法)
- 非重名方法
- 抽象方法需重写
- 非抽象方法无需重写
实现类继承的父类与实现的接口间存在:
- 重名方法
- 抽象方法与父类方法重名,无需重写
- 默认方法与父类方法重名,建议重写(不重写不会报错,默认用父类的,如想要使用接口的默认方法,需重写,调用 接口名.super.(); )
抽象类和接口的应用选择
理论区别
1.抽象类可以有构造方法,接口中不能有构造方法。(抽象类的匿名内部子类是无法重载构造函数的,只能super父类的构造函数,因为没有类名所以无法创建与类名相同的构造函数并重载它)
2.抽象类中可以有普通成员变量,也可以有静态变量;接口中只有默认即为public static final类型的变量
3.一个类可以实现多个接口,但只能继承一个抽象类。
4.抽象类中抽象、普通方法权限修饰符默认不写视为default,接口中抽象、普通方法权限修饰符默认不写视为public;
应用区别
5.继承抽象类,表示继承这一体系内容,用于共性内容的抽取;实现接口,表示功能有所拓展(该功能不一定仅服务于这一个体系,也可能服务于其他体系);
5.抽象类中可以包含非抽象的普通方法/静态方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法/静态方法。
(1.8 1.9版本新特性,接口已经可以有默认、静态和私有方法了)
应用选择
功能拓展推荐使用接口(可多继承)
当属性变量的值需要灵活变化时,使用抽象类
推荐继承抽象类的同时实现接口
匿名内部类
会被 eclipse自动 编译 成.class的二进制字节码文件,文件命名为“所在类名$内部类序号.class”;
应用场景:需要重写父类A的方法a时,使用过程繁琐
- 尤其是抽象类、接口必须这种重写的
- 尤其当需要子类对象并调用方法,但不需要该对象使用第二次的
- 在线程、IO流文件写入写出、网络编程场景常用(因为线程的开启和关闭只需要执行一次)
子类继承方式
创建子类继承抽象类,重写抽象方法,创建子类对象,调用重写后的方法
public abstract class A {
int age=0;
abstract void a();
abstract void a1();
public int a2(){
return 2;
}
}
public class B extends A {
public void a() {
System.out.println("子类重写的a方法"+age);
}
public void a1() {
System.out.println("子类重写的a1方法"+age);
}
}
public class c {
public staic void main(String[] args) {
B b=new b;
b.age=22;
b.a();//子类重写的a方法
b.a1();//子类重写的a1方法
System.out.println(b.a2());//返回2
}
}
匿名内部类方式
原始方式
直接在类内部new一个父类对象,并重写抽象方法,在对象后直接调用方法;
public class InnerClass {
public static void main(String[] args) {
new A(){
@Override
void a() {
System.out.println("内部重写的a方法"+age);
}
@Override
void a1() {
System.out.println("内部重写的a1方法"+age);
}
}.a();//内部重写的a方法0
//弊端1:写一个内部匿名类对象,只能调一次a或者a1或者a2;
//弊端2:输出的age值永远为0,无法赋值,要赋值需要调用属性,调用属性就无法调用方法
}
}
改良方式
多态方式,即找一个父类引用变量aa指向子类匿名对象new A()(这个子类没有类名,所以也只能以多态方式找父类引用变量接收)
public class InnerClass {
public static void main(String[] args) {
A aa = new A(){
@Override
void a() {
System.out.println("内部重写的a方法"+age);
}
@Override
void a1() {
System.out.println("内部重写的a1方法"+age);
}
};
aa.age=33;
aa.a();//内部重写的a方法33
aa.a1();//内部重写的a方法33
//找一个A类的变量来接收A类的子类对象,照样内部重写
System.out.println(aa.a2);//2
}
}
final最终的
修饰变量 | 修饰对象 | 修饰类 | 修饰方法 |
---|---|---|---|
可以 必须初始化 不能再被赋值 | 不可以再指向新的地址 但对象的属性可以变化 | 不能被继承 不能修饰接口、抽象类 | 可以修饰静态方法 不能修饰abstract 方法; 不能被继承重写 |
相关词:
--------------------------------------------
final 关键字,不可变的
finally 异常处理,try catch finally,finally后的代码必定被执行
finalize 垃圾回收GC执行的最后一个功能