13.抽象
- 概念例子
创建,使用示例如下:
/*
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之 前写上abstract即可。
如何使用抽象类和抽象方法:
1.不能直接创建new抽象类对象。
2.必须用一个子类来继承抽象父类。
3.子类必须覆盖重写抽象父类当中所有的抽象方法。
覆盖重写(实现):去掉抽象方法的abstract关键字,然后补上方法体大括号。
4.创建子类对象进行使用。
*/
public abstract class Animal {
//这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
public abstract void eat();
//这是普通的成员方法,抽象类中是可以具体实现成员方法的
public void normalMethod() {
}
}
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class DemoMain {
public static void main(String[] args) {
Cat cat=new Cat();
cat.eat();
}
}
- 注意事项:
1.抽象类不能创建对象.如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解: 假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
2.抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解: 子类的构造方法中,有默认的super0, 需要访问父类构造方法。
3.抽象类中,不定包含抽象方法.但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计.
4.抽象类的子类,必须重写抽象父类中所有的抽象方法,暂则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
14.接口
- 简单来说即多个类的公共规范,是一种引用数据类型,最重要的内容就是其中的抽象方法
- 定义格式
public interface 接口名称{
//接口内容
}
备注:换成了关键字interface之后,编译生成的字节码文件仍然是: .java --> .class.
如果是Java 7,那么接口中可以包含的内容有:
1.常量;2.抽象方法
如果是Jova 8,还可以额外包含有:
3.默认方法;4.静态方法.
如果是Java 9,还可以额外包含有:
5.私有方法
- 示例
/*
在任何版本的Jova中,接口都能定义抽象方法。
格式:
public abstract 返回值类型方法名称(参数列表);
注意事项:
1.接口当中的抽象方法,修饰符必须是两个固定的关键字。public. abstract
2.这两个关键字修饰符,可以选择性地省略。(今天刚学, 所以不推荐。)
3.方法的三要素,可以随意定义。
*/
public interface MyInterfaceAbstract {
//这是一个抽象方法
public abstract void methodAbs1();
//这也是抽象方法
abstract void methodAbs2();
//这也是抽象方法
public void methodAbs3();
//这也是抽象方法
void methodAbs4();
}
使用:
public class MyInterfaceAbstractImpl implements MyInterfaceAbstract{
@Override
public void methodAbs1() {
System.out.println("这是第一个方法");
}
@Override
public void methodAbs2() {
System.out.println("这是第二个方法");
}
@Override
public void methodAbs3() {
System.out.println("这是第三个方法");
}
@Override
public void methodAbs4() {
System.out.println("这是第四个方法");
}
}
/*
接口使用步骤:
1.接口不能直接使用,必须有一个“实现类“崃“实现"咳接口。
格式:
public class实现类名称implements 接口名称{
// ...
}
2.接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
实现,去掉abstract关键字,加上方法体大括号。
3.创建实现类的对象,进行使用。
注意事项:
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。
*/
public class InterfaceDemo01 {
public static void main(String[] args) {
// 错误写法!不能直接new接口对象使用。
// MyInterfaceAbstract inter = new MyInterfaceAbstract();
//创建实现类的对象使用
MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl();
impl.methodAbs1();
impl.methodAbs2();
}
}
- 接口的默认方法
从Java 8开始,接口里允许定义默认方法。
格式:
public default 返回值类型方法名称(参数列表) {
方法体
}
备注:接口当中的默认方法,可以解决接口升级的问题。即实现接口的类已经投入使用,但是接口又新添加了抽象方法,则此时实现接口的类也需要重新更改,为了解决这个问题,可以使用默认方法,避免修改。
例子:
public interface MyInterfaceDefault {
//抽象方法
public abstract void methodAbs();
//新添加了一个抽象方法
//public abstract void methodAbs2();
//新添加的方法,改成默认方法
publid default void methodDefault() {
System.out.print1n( "这是新添加的默认方法");
}
}
public class MyInterfaceDefaultA implements MyInterfaceDefault {
@Override
public void methodAbs(){
System.out.println("实现了抽象方法,AAA");
}
}
public class MyInterfaceDefaultB implements MyInterfaceDefault {
@Override
public void methodAbs(){
System.out.println("实现了抽象方法,BBB");
}
}
public class Demo02Interface {
public static void main(String[] args) {
//创建了实现类对象
MyInterfaceDefaultA a = new MyInterfaceDefaultA();
a.methodAbs(); //调用抽象方法,实际运行的是右侧实现类。
//调用默认方法,如果实现类中没有,会向上找接口
a.methodDefault();
}
}
注意事项
1.接口的默认方法,可以通过接口实现类对象,直接调用
2.接口的默认方法,也可以被接口实现类进行覆盖重写。
- 接口的静态方法
从Java 8开始,接口当中允许定义静态方法。
格式:
public static 返回值类型方法名称(参数列表) {
方法体
}
提示:就是将abstrac t或者defaul t换成static即可,带上方法体。
**使用:**与类中的静态方法一致,即“接口名称.静态方法名”
public interface MyInterfaceStatic {
public static void methodStatic(){
System.out.println("这是接口的静态方法!");
}
}
public class MyInterfaceStaticImpl implements MyInterfaceStatid {
}
/*
注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。
正确用法。通过接口名称,直接调用其中的静态方法。
格式:
接口名称.静态方法名(参数);
*/
public class Demo03Interface {
public static void main(String[] args) {
//创建了实现类对象,为了对比而实现,实际上不需要创建,直接调用接口静态方法即可
MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
//错误写法!原因:如果这个类实现多个接口,接口的静态方法可能重名
//impl.methodstatic();
//直接通过接口名称调用静态方法
MyInterfaceStatic.methodStotic();
}
}
- 接口的私有方法
当接口中需要抽取一个共有方法用来解决重复代码问题时,但又需要这个共有方法不可以被实现类使用,即需要将这个共有方法私有化。
从Java 9开始, 接口当中允许定义私有方法。
1.普通私有方法,解决多个欧认方法之间重复代码问题
格式:
private 返回值类型方法名称(参数列表) {
方法体
}
2.静态私有方法,解决多个静态方法之间重复代码问题
格式:
private static 返回值类型方法名称(参数列表) (
方法体
}
public interface MyInterfacePrivateA {
public default void methodDefault1() {
System. out . print1n("默认方法1");
methodCommon();
}
public default void methodDefault2() {
System. out .print1n(“默认方法2");
methodCommon();
}
public defJu1t void methodCommon() {
System.out.print1n("AAA");
System.out.print1n("BBB");
System.out.print1n("CCc");
}
}
public class MyInterfacePrivateAImpl implements MyInterfacePrivateA {
public void methodAnother(){
// 直接访问到了接口中的默认方法,这样是错误的!
//methodCommon();
}
}
public interface MyInterfacePrivateB {
public static void methodStatic1() {
System. out . println("静态方法1");
methodstaticCommon();
}
public static void methodStatic2() {
System. out .print1n(“静态方法2");
methodStaticCommon();
}
private static void methodStaticCommon() {
System. out . print1n("AAA");
System. out . print1n("BBB");
System.out. print1n("CCC");
}
}
public class Demo04Interface [
public static void main(String[] args) {
MyInterfacePrivateB.methodStatic1();
MyInterfacePrivateB.methodStatic2();
//错误写法!
MyInterfacePrivateB. methodStaticCommon();
}
}
- 接口的常量
接口当中也可以定义“成员变量”。但是必须使用public static final三个关键字进行修饰。
从效果上看,这其实就是接口的[常量]。
格式:
public static final 数据类型常量名称=数据值;
备注:
一旦使用final关键字进行修饰,说明不可改变。
注意
1.接口当中的常量,可以省略public static final,注意,不写也默认有这些关键字。
2.接口当中的常量,必须进行赋值;不能不赋值。
3.接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
public interface MyInterfaceConst {
//这其实就是一个常量,一旦赋值,不可以修改
public static final int NUM=10;
}
- 接口使用
使用接口的时候,需要注意:
1.接口是没有静态代码块或者构造方法的。
2.一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
格式:
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
// 覆盖重写所有抽象方法
}
3.如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
4.如果实现类没有覆盖重与所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
5.如果实现类锁实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
6.一个类如果直接父类当中的方法,和接口当中的默认方法产生了神突,优先用父类当中的方法。
public interface MyInterfaceA {
//错误写法!接口不能有静态代码块
//static { }
//错误写法!接口不能有构造方法
//public MyInterfaceA() { }
public abstract void methodA() ;
public abstract void methodAbs();
public default void methodDefault( ) {
System. out . println( "默认方法A" );
}
}
public interface MyInterfaceB {
public abstract void methodB() ;
public abstract void methodAbs();
public default void methodDefault( ) {
System. out . println( "默认方法B" );
}
}
public class MyInterfaceImpl /*extends object*/ implements MyInterfaceA, MyInterfaceB {
@Override
public void methodA() {
System.out.println( "覆盖重写了A方法");
}
@Override
public void methodB() {
System.out.println("覆盖重写了B方法");
}
@Override
public void methodAbs() {
System.out.println( "覆盖重写了AB接口都有的抽象方法");
}
@Override
public void methodDefault() {
System.out.println("对多个接口当中神突的默认方法进行了覆盖重写");
}
}
- 小结
在Java 9+版本中,接口的内容可以有: .
1.成员变量其实是常量,格式:
[public] [static] [final] 数据类型常量名称=数据值;
注意:
常量必须进行赋值,而且一旦赋值不能改变。
常量名称完全大写,用下划线进行分隔。
2.接口中最重要的就是抽象方法,格式:
[public] [abstract] 返回值类型方法名称(参数列表);
注意:实现类必须覆盖重写接口所有的抽象方法,除非实现类是抽象类。
3.从Java 8开始,接口里允许定义默认方法,格式:
[public] default 返回值类型方法名称(参数列表) {方法体}
注意:默认方法也可以被覆盖重写;default无法省略
4.从Java 8开始,接口里允许定义静态方法,格式: .
[public] static 返回值类型方法名称(参数列表) {方法体}
注意:应该通过接口名称进行调用,不能通过实现类对象调用接口静态方法
5.从Java 9开始,接口里允许定义私有方法,格式:
普通私有方法: private 返回值类型方法名称(参数列表) {方法体}
静态私有方法: private static 返回值类型方法名称(参数列表) {方法体}
注意: private的方法只有接口自己才能调用,不能被实现类或别人使用。
- 接口之间的多继承
1.类与类之间是单继承的。直接父类只有一一个。
2.类与接口之间是多实现的。一个类可以实现多个接口。
3.接口与接口之间是多继承的。
注意事项:
1.多个父接口当中的抽象方法如果重复,没关系。
2.多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,而且带着default关键字。
public interface MyInterfaceA {
public abstract void methodA( ) ;
public abstract void methodCommon( );
}
public interface MyInterfaceB {
public abstract void methodB( ) ;
public abstract void methodCommon( );
}
/*
这个子接口当中有几个方法?答: 4个。
methodA 来源于接口A
methodB 来源于接口B
methodCommon 同时来源于接口A和B
method来源于我自己
*/
public interface MyInterface extends MyInterfaceA, MyInterfaceB {
public abstract void method();
}
15.多态
- 一个对象拥有多种形态,即对象的多态性
- 格式和使用
代码当中体现多态性,其实就是一句话:父类引用指向子类对象。
格式:
父类名称对象名= new子类名称();
或者:
接口名称对象名= new 实现类名称();
public class Fu {
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类特有方法");
}
}
public class Zi extends Fu {
@Override
public void method( ) {
System.out.println("子类方法");
}
}
public class Demo01Multi {
public static void main(String[] args) {
//使用多态的写法
//左侧父类的引用,指向了右侧子类的对象
Fu obj = new Zi();
obj.method();
obj.methodFu() ;
}
}
- 多态中的成员变量使用特点
正常访问成员变量的两种方式:
1.直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。
2.间接通过成员方法访问成员变量:看该方法属于谁,优先用谁, 没有则向上找。
多态中访问成员变量的方式和正常访问方式一致(成员变量无法覆盖重写,只有方法才可以)
pub1ic class Fu {
int num = 10;
public void showNum( ) {
System. out . print ln(num);
}
}
public class Zi extends Fu {
int num = 20;
public void showNum( ) {
System.out.print ln(num);
}
}
public class Demo01MultiField {
public static void main(String[ ] args) {
// 使用多态的写法,父类引用指向子类对象
Fu obj = new Zi();
//等号左边是父类,优先找父类中的成员变量,没有则向上找,不会向下找
System.out.println(obj.num);
//子类没有覆盖重写,就是父: 10
//子类如果覆盖重写,就是子: 20
obj.showNum();
}
}
- 成员方法的使用特点
在多态的代码当中,成员方法的访问规则是:看new的是谁,就优先用谁,没有则向上找。
口诀:编译看左边,运行看右边。
对比一下:
成员变量:编译看左边,运行还看左边。
成员方法:编译看左边,运行看右边。
pub1ic class Fu {
int num = 10;
public void showNum() {
System.out.println(num);
}
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类特有方法");
}
}
public class Zi extends Fu {
int num = 20;
int age = 16;
@Override
public void showNum() {
System. out . println(num); .
}
@Override
public void method() {
System. out . println("子类方法");
}
public void methodZi() {
System. out . print1n("子类特有方法");
}
}
public class Demo02MultiMethod {
public static void main(String[] args) {
Fu obj=new Zi(); //多态
obj.method(); //父子都有,优先用子
obj.methodFu(); //子类没有,父类有,向上找到父类
//编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错。
//obj.methodZi();//错误写法
}
}
- 多态的好处
- 对象的向上转型和向下转型
1.对象的向上转型,其实就是多态写法:
格式:父类名称 对象名= new 子类名称:
含义:右侧创建一个子类对象,把它当做父类来看待使用。
注意事项:向上转型一定是安全的。
弊端:对象一旦向上转型为父类,那么就无法调用子类原本特有的内容。
public abstract klass Animal {
public abstract void eat();
}
public class Cat extends Animal {
@Override
public void eat( ) {
System.out.println( "猫吃鱼");
}
public void catchMouse(){
System.out.println( "猫捉老鼠");
}
}
public class Demo01Main {
public static void main(String[ ] args) {
//对象的向上转型,就是:父类引用指向之类对象。
Animal animal = new Cat();
animal.eat() ;
}
}
解决方案:对象的向下转型
2.对象的向下转型,其实是一个[还原]的动作。
格式:子类名称对象名= (子类名称)父类对象;
含义:将父类对象,
[还原]成为本来的子类对象。
Animal animal = new Cat0; //本来是猫,向上转型成为动物
Cat cat = (Cat) animal; //本来是猫,已经被当做动物了,还原回来成为本来的猫
注意事项:
a.必须保证对象本来创建的时候,就是猫,才能向下转型成为猫。
b.如果对象创建的时候本来不是猫,现在非要向下转型成为猫,就会报错(ClassCastException)。
这部分代码是结合上面一部分的代码的
public class Dog extends Animal {
@Override
public void eat() {
System.out.println( "狗吃SHIT");
}
public void watchHouse() {
System.out.println( "狗看家");
}
}
pub1ic class Demo01Main {
public static void main(String[] args) {
//对象的向上转型,就是:父类引用指向之类对象。
Animal animal = new Cat(); //本来创建的时候是- -只猫
animal.eat(); //猫吃鱼
//animal.catchMouse(); //错误写法!
//向下转型,进行“还原”动作
Cat cat = (Cat)animal;
cat.catchMouse();
//错误的向下转型
//错误的写法,编译不会报错,但是运行会出现异常
//java.Lang.ClassCastException.类转换异常
Dog dog = (Dog)animal;
}
}
- instanceof关键字
如何才能知道一个父类引用的对象,本来是什么子类?
格式:
对象instanceof 类型
这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例。
下面的代码块和上面两个代码块关联
public class Demo02Instanceof {
public static void main(String[] args){
Animal animal = new Cat(); //本来是- -只猫
animal.eat(); //猫吃鱼
//如果希望掉用子类特有方法,需要向下转型
//判断一下父类引用animal本来是不是Dog
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse() ;
}
//判断一下animal本来是不是Cat
if (animal instanceof Cat) {
Cat cat = (Cat) anlmal;
cat.catchMouse();
}
giveMeAPet(new Dog());
}
public static void giveMeAPet(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog . watchHouse();
}
if (animal instanceof Cat){
Cat cat = (Caf) animal;
cat . catchMouse();
}
}
}
16 final关键字
- final关键字代表最终、不可改变的。
- 常见四种用法:
1.可以用来修饰一个类
2.可以用来修饰一个方法
3.可以用来修饰一个局部变量
4.可以用来修饰一个成员变量 - 格式:
1.当final关键字用来修饰一个类的时候,格式:
public final class类名称{
//...
}
含义:当前这个类不能有任何的子类。(太监类)
注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写。但可以有父类
2.当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。
格式:
修饰符final 返回值类型方法名称(参数列表) {
//方法体
}
注意事项:对于类,方法来说,abstract关键字和final关键字不能同时使用,因为矛盾。
3.一旦使用final用来修饰局部变量,那么这个变量就不能进行更改。简记:“一次赋值,终生不变”
public class Demo01Final {
public static void main(String[] args) {
int num1 = 10;
System. out. println(num1); // 10
num1 = 20;
System. out . println(num1); // 20
final int num2 = 200;
System.out.println(num2); // 200
//num2 = 250; //错误写法!不能改变!
//num2 = 200; //错误写法!就算值一样也无法更改
//正确写法!只要保证有唯一次赋值即可
final int num3;
num3 = 30;
//对于基本类型来说,不可变说的是变量当中的数据不可改变
//对于引用类型来说,不可变说的是变量当中的地址值不可改变
Student stu1 = new Student( name: “赵丽颖");
System. out . println(stu1);
System. out . println(stu1. getName()); //赵丽颖
stu1 = new Student( name: "霍建华");
System. out . println(stu1);
System. out . println(stu1. getName()); //霍建华
System. out. print1n("=============");
final Student stu2 = new Student( name: "高圆圆");
//错误写法! final的引用类型变量,其中的地址不可改变
//stu2 = new Student( name: "赵又廷");
}
}
对于基本类型来说,不可变说的是变量当中的数据不可改变
对于引用类型来说,不可变说的是变量当中的地址值不可改变
4.对于成员变量来说,如果使用final关键字修饰, 那么这个变量也照样是不可变。
1).由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
2).对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
3).必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
public class Person {
private final String name;
//private final String name="xx";
public Person() {
name="yy";
}
public Person(String name) {
this. name = name;
}
public String getName() {
return name;
}
}
17.四种权限修饰符总结
Java中有四种权限修饰符:
Y可访问/N不可访问 | public> | protect> | (default)> | private |
---|---|---|---|---|
同一个类(自己) | Y | Y | Y | Y |
同一个包(邻居) | Y | Y | Y | N |
不同包子类(儿子) | Y | Y | N | N |
不同包非子类(陌生人) | Y | N | N | N |