一、abstract:抽象的
1.为什么要写抽象类、抽象方法
一个父类中,一般有些方法能够满足子类要求,不需要重写,有些不满足子类要求,需要重写。
那么对于那些需要重写的方法,父类中的方法体就不需要写了,因为没有意义。
但是对于不写方法体的方法,IDEA会报错,
要求改方法必须有方法体或者是抽象方法,于是这里我们在方法前面加上abstract。
2.抽象类与抽象方法的编写规则
抽象类的作用是为子类提供一个通用的方法的模板,子类重写这些方法。这样可以使子类的设计更加严格。
1)一个类中如果要写抽象方法,那么这个类必须定义为抽象类,但是抽象类中可以不写抽象方法
2)抽象类可以被继承,但子类必须重写父类中所有的抽象方法(也可以将子类写为抽象类,但一般不会这么写)
3)不能创建抽象类的对象,但可以创建抽象类的子类对象(但是抽象类中有构造器,因为在创建子类的对象时默认会先去调父类的构造器)
4)抽象类不能被final修饰,因为抽象类的设计目的就是给子类提供模板,如果被funal修饰则不能继承
抽象类可以应用在多态中
public abstract class Person {//一个类中如果有抽象方法,那么这个类必须是抽象类
public void eat(){
System.out.println("eat");
}
public abstract void say();
}
class Student extends Person{
@Override
public void say() {
System.out.println("子类说话");
}
}
class Test{
public static void main(String[] args) {
//父类的引用指向子类的对象
Person p=new Student();
p.say();
}
}
再例如可以改写为多态章节中的代码:Animal类中的shout方法体可以不写,将Animal类定义为抽象类,shout定义为抽象方法,然后依然可以实现父类的引用指向子类的对象。
二、接口
1.接口的定义
普通类:具体实现
抽象类:具体实现和规范(抽象方法)都有
接口:只有规范,自己无法写方法,约束和实现分离
接口的作用:接口就是规范,定义一组规则,制定好后大家都遵守。
接口关键字:interface(类关键字:class)
在IDEA 中类的标志是C、接口的标志是I
2.接口的写法
1)新增一个接口UserService,写法是将声明类的关键字class更改为声明接口的关键字interface
接口中可以定义常量(一般不在接口中定义常量)和方法,只有定义,没有实现,定义的方法默认都是public abstract修饰
public interface UserService {
//接口中只有定义,没有实现,且定义都是抽象的,默认是public abstract
public abstract void run();
void eat();
//接口中定义常量,一般不用,默认是public static final
public static final int AGE=99;
}
2)创建一个接口的实现类,UserServiceImpl,用implements关键字来实现UserService这个接口
在接口的实现类中,必须要重写接口中的方法,因为在接口中的方法是只有定义没有实现的。快捷键:Alt+Insert
//用UserServiceImpl这个类来实现接口,必须要重写接口里面的方法
public class UserServiceImpl implements UserService {
@Override
public void run() {
}
@Override
public void eat() {
}
}
3)Java是单继承,多实现。只能继承一个类,可以实现多个方法
public interface TimeService {
void timer();
}
同样需要在接口中重写TimeService的方法
//用UserServiceImpl这个类来实现接口,与抽象类不同,它可以实现多个接口,必须要重写接口里面的方法
public class UserServiceImpl implements UserService, TimeService {
@Override
public void run() {
}
@Override
public void eat() {
}
@Override
public void timer() {
}
}
为什么接口中只有单继承,但是有多实现?
因为:例如A、B类中都有eat()方法,里面是不同的方法体,如果子类可以继承A、B两个类,那么父类的方法都可以拿过来用,那么子类调用eat()方法时,是用A、还是B类的方法,会造成混淆。
而对于实现,A、B两个接口中都有eat()方法,但是都是抽象方法,没有方法体,那么对于实现类C调用eat()方法,方法体由自己编写,不存在混淆。
必须先写继承,再写实现
3)接口指向实现类
class Test{
public static void main(String[] args) {
//接口不能创建对象,但可以接口指向实现类
UserService us =new UserServiceImpl();
//访问类中的方法
us.eat();
}
}
3.JDK1.8之后接口新增功能
JDK1.8之后接口新增了非抽象方法和静态方法
非抽象方法:默认public default修饰,default必须要写,有方法体。被重写时default修饰符消失
静态方法:默认public static修饰,static必须要写有方法体。与重写无关
编写抽象方法,非抽象方法,静态方法并对它们进行调用
package com.rzd.test.demo04;
public interface TestInterface01 {
//抽象方法
public abstract void a();
//非抽象方法(JDK1.8之后新增)
//非抽象方法必须要写default修饰符,且必须有方法体
public default void b(){
System.out.println("接口中的非抽象方法");
}
//静态方法(JDK1.8之后新增)
//静态方法必须要写static修饰符,且必须有方法体
public static void c(){
System.out.println("接口中的静态方法");
}
}
//实现类
class Test implements TestInterface01{
@Override
public void a() {
System.out.println("重写了TestInterface01的抽象方法a");
}
//也可以对非抽象方法进行重写,重写时不能加default修饰符
@Override
public void b() {
//重写可以直接调用接口中的非抽象方法b,也可以重写
TestInterface01.super.b();
//System.out.println("重写了TestInterface01的非抽象方法b");
}
//静态方法c不存在重写,IDEA没有重写标志
public void c(){
//两种调用接口中的非抽象方法的方式
b();
TestInterface01.super.b();
//接口中的静态方法可以直接调用
TestInterface01.c();
}
}
class A{
public static void main(String[] args) {
Test test = new Test();
test.a();
test.b();
test.c();
//调用接口中的方法,只能调用静态方法
TestInterface01.c();
}
}
如果重写了非抽象方法b,那么在程序中创建实现类的对象后,调用b方法时,调用的是实现类中重写的b方法,而不是接口中的b方法。
为什么要在接口中定义非抽象方法?
因为如果有100个类实现了接口A,如果在接口A中新增一个抽象方法,那么100个类都需要重写这这个方法,改动太大,如果新增的是非抽象方法,则不需要改变实现类的内容,实现类可以选择是否要重写这个非抽象方法。
4.继承与实现的使用场景选择
1)继承:is 例如:手机是电子产品
2)实现:has 手机有拍照功能,手机有发短信功能
5.多态的应用场景
1)父类作为方法的形参,传入具体子类的对象
2)父类作为方法的返回值,返回具体子类的对象
3)接口作为方法的形参,传入具体实现类的对象
4)接口作为方法的返回值,返回具体实现类的对象
三、4种内部类
1.成员内部类
在一个类中再写一个类,Inner就是成员内部类、Outer就是外部类
class Outer{ class Inner{ } }
创建一个外部类Outer,定义私有属性和私有方法,在Outer内定义一个内部类Inner,在内部类中定义私有属性和方法,并且在外部类中创建访问内部类的方法getin(),在内部类中创建访问外部类的方法getout()。
public class Outer {
//外部类的私有属性和方法
private String name="外部类属性";
private void out(){
System.out.println("外部类方法");
}
public void getin(){
Inner inner = new Inner();
System.out.println(inner.id);
inner.in();
}
//内部类
public class Inner{
private int id=10;
private void in(){
System.out.println("内部类方法");
}
//成员内部类可以无条件访问外部类的任何属性和方法,就连私有的也可以访问。
public void getOut(){
System.out.println(name);
out();
}
}
可以看到,成员内部类可以无条件的访问外部类的所有属性和方法,但是外部类要访问内部类需要先new内部类,对内部类进行初始化。
那么如何在main方法中初始化内部类呢?需要先初始化外部类,再利用外部类来初始化内部类Outer.new Inner();
public class Application {
public static void main(String[] args) {
//先实例化一个内部类
Outer outer = new Outer();
//通过外部类来实例化内部类Outer.new Inner(); Alt+回车
Outer.Inner inner = outer.new Inner();
//内部类调用内部类里访问外部类的方法
inner.getOut();
//外部类调用外部类里访问内部类的方法
outer.getin();
}
}
补充:一个java文件里可以有多个class类,但是只能有一个public class类,一般用于写测试类,里面可以写main方法
2.静态内部类
静态内部类无法访问外部类的属性和方法
public class Outer2 {
private String name="外部类属性";
private void out(){
System.out.println("外部类方法");
}
//如果内部类是静态内部类,那么就不能访问外部类的属性和方法了。
//原因是static是和类一起加载的,在加载外部类Outer2的时候就已经加载Inner了,但是此时外部类中的属性和方法并没有加载,所以无法访问
public static class Inner{
public void getOut(){
System.out.println(name); //报错
out(); //报错
}
}
}
3.局部内部类
写在方法中的类叫局部内部类,和局部变量一样,前面不能加访问修饰符以及static修饰符。和成员内部类的区别在于局部内部类的访问权限仅限于方法或作用域内。
public class Outer3 {
//方法
void test(){
//局部内部类
class Inner{
}
}
}
4.匿名内部类
指的是初始化类的时候不把初始化的实例保存到变量中
public class Outer4 {
public static void main(String[] args) {
//在这个main方法里初始化Inner类,但是不将实例保存到变量中,直接调用Inner类的方法
new Inner().eat();
//也可以匿名接口,这是一个实现了接口的类,但是是一个匿名的类
new UserService(){
@Override
public void eat() {
}
};
}
}
class Inner{
void eat(){
System.out.println("eat");
}
}
interface UserService{
void eat();
}