Java学习笔记(三)
抽象abstract
概念:如果父类当中的方法不确定如何进行{}方法体实现,那么着应该是一个抽象方法
抽象方法和抽象类的格式
抽象方法:就是加上 abstract 关键字,然后去掉大括号,直接分号结束
抽象类:抽象方法所在的内,必须是抽象类才行,在 class 之前写上 abstract 关键字即可
//抽象类
public abstract class Animal{
//抽象方法,代表吃东西具体吃什么(大括号的内容)不确定
public abstract void eat();
}
抽象方法和抽象类的使用
如何使用抽象类和抽象方法:
-
不能直接new抽象类对象
-
必须用一个子类来继承抽象父类
-
子类必须覆盖重写抽象父类当中所有的抽象方法
覆盖重写(实现):子类去掉抽象方法的 abstract 关键字,然后补上方法体大括号
-
创建子类对象进行使用
public abstract class Animal{
public abstract void eat();
}
public class Cat extends Animal{
@Override
public void eat(){
System.out.println("猫吃鱼");
}
}
public class Demo{
public static void main(String[] args){
Cat cat = new Cat();
cat.eat();//猫吃鱼
}
}
抽象方法和抽象类的注意事项
关于抽象类的使用,一下语法上要注意的细节,虽然条目很多,但如果理解抽象的本质,无需死记硬背
-
抽象类不能创建对象,如果创建,编译无法通过而报错。只能拆功能键其非抽象子类对象
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有就具体的方法体,没有意义
-
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
理解:子类的构造方法中,有默认的super(),需要访问父类的构造方法
-
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定时抽象类
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计
-
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类
理解:假设不重写所有抽象方法,则类中可能包含抽象方法,那么创建对象后,调用抽象的方法,没有意义
接口interface
接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用。
Java中的接口更多的体现在对行为的抽象!
接口是一个应用数据类型,最重要的内容就是其中的抽象方法
接口定义的基本格式
/*
备注:换成了关键字interface之后,编译生成的字节码文件仍然是:.java ---> .class
如果时Java 7,那么接口中可以包含内容有:
1、常量
2、抽象方法
如果时Java 8,还可以额外包含有:
3、默认方法
4、静态方法
如果时Java 9,还可以额外包含有:
5、私有方法
*/
public interface 接口名称{
//接口内容
}
接口的抽象方法定义和使用
在任何版本的Java中,接口都能定义抽象方法
格式:
public interface 类名称{
//这是一个抽象方法
public abstract 返回值类型 方法名称(参数列表);
.....
}
/*
注意事项:
1、接口中的抽象方法,修饰符必须时两个固定的关键字:public abstract
2、这两个关键字修饰符,可以选择性省略
*/
public interface Demo{
//这是一个抽象方法
public abstract void method1();
//这是一个抽象方法
abstract void method2();
//这是一个抽象方法
public void method3();
//这是一个抽象方法
void method4();
}
接口使用步骤:
-
接口不能直接使用,必须有一个实现类来实现该接口
-
接口的实现类必须覆盖重写(实现)接口中所有的抽象方法
实现:去掉 abstract 关键字,加上方法体大括号
-
创建实现类的对象,进行使用
实现类的格式:
public class 实现类名称 implements 接口名称{
//.....
}
注意事项:如果实现类并没有覆盖重写接口中的所有抽象方法,那么这个实现类自己就必须时抽象类
接口中的默认方法定义和使用
Java 8开始,接口里允许定义默认方法
格式:
public default 返回值类型 方法名称(参数列表){
//方法体
}
备注:接口中的默认方法,可以解决接口升级问题
public interface MyInterfaceDefault{
//抽象方法
public abstract void methodAbs();
//新添加的方法,改为默认方法
public default void methodDefault(){
System.out.println("这是新添加的默认方法");
}
}
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 Demo{
public static void main(String[] args){
MyInterfaceDefaultA a = new MyInterfaceDefaultA();
a.methodAbs();
//调用默认方,如果实现类当中没有,会向上找接口
a.methodDefault();
MyInterfaceDefaultB b = new MyInterfaceDefaultB();
b.methodAbs();
//调用默认方,如果实现类当中没有,会向上找接口
b.methodDefault();
}
}
注意事项:
- 接口的默认方法,可以通过接口实现类对象,直接调用
- 接口的默认方法,也可以被接口实现类进行覆盖重写
接口的静态方法定义和使用
从Java 8开始,接口当中允许定义静态方法
格式:
public static 返回值类型 方法名称(参数列表){
//方法体
}
/*
注意事项:不能通过接口实现类的对象来调用接口中的静态方法
正确用法:通过接口名称,直接调用其中的静态方法
*/
public interface MyInterfaceStatic{
public static void methodStatic(){
System.out.println("这是接口的静态方法!");
}
}
public class MyInterfaceStaticImpl implements MyInterfaceStatic{
//接口中没有抽象方法,没有必要覆盖重写
}
public class Demo{
public static void main(String[] args){
//直接通过接口名称调用静态方法
MyInterfaceStatic.methodStatic();
}
}
接口的私有方法定义和使用
从Java 9开始,接口中允许定义私有方法
-
普通私有方法:解决多个默认方法之间重复代码问题
格式:
private 返回值类型 方法名称(参数列表){ //方法体 }
-
静态私有方法:解决多个静态方法之间重复代码问题
格式:
private static 返回值类型 方法名称(参数列表){ //方法体 }
public interface MyInterfacePrivateA{ public default void methodDefault1(){ System.out.println("静态方法1"); methodCommon(); } public default void methodDefault2(){ System.out.println("静态方法2"); methodCommon(); } //实现类访问不到methodCommon方法 private void methodCommon(){ System.out.println("AAA"); } } public interface MyInterfacePrivateB{ public static void methodStatic1(){ System.out.println("静态方法1"); methodStaticCommon(); } public static void methodStatic2(){ System.out.println("静态方法2"); methodStaticCommon(); } 实现类访问不到methodStaticCommon方法 private static void methodStaticCommon(){ System.out.println("AAA"); } }
接口的常量定义和使用
接口中也可以定义“成员变量”,但是必须使用 public static final 三个关键字进行修饰
从效果上看,这其实是接口的常量
格式:
pubilc static final 数据类型 常量名称 = 数据值;
/* 备注:一旦使用final关键字进行修饰,说明不可改变 注意事项: 1、接口中的常量,可以省略public static final 2、接口中的常量,必须进行赋值 3、接口中常量的名称,使用完全大写的字母,用下划线进行分割 */ public interface MyInterfaceConst{ //这其实就是一个常量,一旦赋值不可修改 public static final int NUM_OF_MY_CLASS = 10; } public class Demo{ public static void main(String[] args){ System.out.println(MyInterfaceConst.NUM_OF_MY_CLASS); } }
接口的内容小结([]里的内容可选)
在Java 9+版本中,接口的内容可以有:
-
成员变量其实是常量,格式:
[public] [static] [final] 数据类型 常量名称 = 数据类型;
注意:
常量必须进行赋值,而且一旦赋值不能改变
常量名称完全大写,用下划线进行分割
-
接口中最重要的就是抽象方法,格式:
[public] [abstract] 返回值类型 方法名称(参数列表);
注意:实现类必须覆盖重写接口中所有的抽象方法,除非实现类是抽象类
-
从Java 8开始,接口中允许定义默认方法,格式:
[public] default 返回值类型 方法名称(参数列表) { 方法体 }
注意:默认方法也可以被覆盖重写
-
从Java 8开始,接口中允许定义静态方法,格式
[public] static 返回值类型 方法名称(参数列表) { 方法体 }
注意:应该通过接口名称调用,不能通过实现类对象调用接口静态方法
-
从Java 9开始,接口中允许定义私有方法,格式:
//普通私有方法 private 返回值类型 方法名称(参数列表) { 方法体 } //静态私有方法 private static 返回值类型 方法名称(参数列表) { 方法体 }
注意:private 的方法只有接口自己才能调用,不能被实现类或别人调用
使用接口的时候,需要注意:
-
接口是没有静态代码块或者构造方法的
-
一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
格式:
public class MyInterfaceImpl implements MyInterfacerA,MyInterfacerB{ //覆盖重写所有的抽象方法 }
-
如果实现类所实现的多个接口中,存在重复的抽象方法,那么只需要覆盖重写一次即可
-
如果实现类美哟i覆盖重写所有接口当中的所有抽象方法,那么实现类必须是一个抽象类
-
如果实现类所实现的多个接口中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写
-
一个类如果直接父类当中的方法和接口当中的默认方法产生冲突,优先使用父类当中的方法
-
接口之间的多继承
特点:
- 类与类之间是单继承的,直接父类只有一个
- 类与接口之间是多实现的,一个类可以实现多个接口
- 接口与接口之间是多继承的
注意事项:
- 多个父接口当中的抽象方法如果重复,没关系
- 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,而且带着default 关键字
多态
面向对象三大特征:封装性、继承性、多态性
extends继承或者implements实现是 多态 的前提
概念:
-
什么是多态
同一个对象,在不同时刻表现出来的不同形态
-
多态的前提
- 要有继承或实现关系
- 要有方法的重写
- 要有父类引用指向子类对象
多态的格式和使用
代码当中体现多态性,其实就是一句话,父类引用指向子类对象
格式:
父类对象 对象名 = 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 Demo{
public static void main(String[] args){
//使用多态的写法
//父左侧父类的引用,指向了右侧子类的对象
Fu obj = new Zi();
obj.method();//子类方法
obj.methodFu();//父类特有方法
}
}
多态中成员变量的使用特点
访问成员变量的两种方式:
- 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找
- 间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找
public class Fu /*extends Object*/{
int num = 10;
public void showNum(){
System.out.println(num);
}
}
public class Zi extends Fu{
int num = 20;
int age = 16;
}
public class Demo{
public static void main(String[] args){
//使用多态的写法,父类引用指向子类对象
Fu obj = new Zi();
System.out.println(obj.num);//10
//父类当中没有age变量会向上找,而Fu类的父类是Object,Object当中没有age变量,所以错误
//System.out.println(obj.age);//错误写法
//子类没有覆盖重写,就是父类方法,结果为10
//如果子类覆盖重写了showNum()方法,则运行子类方法,结果为20
obj.showNum();
}
}
多态中成员方法的使用特点
在多态的代码中,成员方法的访问特点是:
看 new 的是谁,就优先用谁,没有则向上找
口诀:编译看左边,运行看右边
对比:
- 成员变量:编译看左边,运行还看左边
- 成员方法:编译看左边,运行看右边
public 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{
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.println("子类特有方法");
}
}
public class Demo{
public static void main(String[] args){
Fu obj = new Zi();//多态
//编译时看左边:左边是Fu类里面有method()方法编译通过,运行看右边:右边是Zi类,Zi类中有method()方法
obj.method();//父子类都有,优先用子,因为new的是子类
//编译时看左边:左边是Fu类里面有methodFu()方法编译通过,运行看右边:右边Zi类中没有,向上找Fu类,Fu类当中有methodFu()方法
obj.methodFu();//子类没有,父类有,向上找到父类
//编译看左边,左边是Fu,Fu类中没有methodZi()方法,所以编译出错
//obj.methodZi();//编译出错
}
}
使用多态的好处
无论右边new的对象到时候换成那个子类对象,等号左边调用方法都不会变化
对象的向上转型
对象的向上转型,其实就是多态的写法,就是父类引用指向子类对象
格式:
父类名称 对象名 = new 子类名称();
含义:右侧创建一个子类对象,把它当作父类来看待使用
注意:向上转型一定是安全的,从小范围转向了大范围
//创建了一只猫,当作动物看待是没问题的,从小范围的猫,向上转换成大范围的动物
Animal animal = new Cat();
对象的向下转型
向上转型一定是安全的,没有问题的,正确的,但是有一个弊端
对象一旦向上转型为父类,那么就无法调用子类原本特有的内容
解决方案:用对象的向下转型(还原)
格式:
子类名称 对象名 = new (子类名称)父类对象;
含义:将父类对象还原成为本来的子类对象
Animal animal = new Cat();//本来是猫,向上转型成为动物
Cat cat = (Cat)animal;//本来是猫,已经被当作动物了,还原回来成为原本的猫
注意:
- 必须保证对象本来创建的时候就是猫,才能向下转型成为猫
- 如果对象创建的时候本来不是猫,现在向下转型成为猫,就会报错(ClassCastExcetion类转换异常)
public abstract class Animal{
public abstract void eat();
}
public class Cat extends Animal{
@Override
public void eat(){
System.out.println("猫吃鱼");
}
//子类特有方法
public void catchMouse(){
System.out.ptintln("猫抓老鼠");
}
}
public class Dog extends Animal{
@Override
public void eat(){
System.out.pritnln("狗吃骨头");
}
public void watchHouse(){
System.out.pritnln("狗看家");
}
}
public class Demp{
public static void main(String[] args){
//对象的向上转型
Animal animal = new Cat();
animal.eat();//猫吃鱼
//对象一旦向上转型为父类,那么就无法调用子类原本特有的内容
animal.catchMouse();//错误写法
//对象的向下转型
Cat cat = (Cat)animal;
cat.catchMouse();//猫抓老鼠
//错误的向下转型
//本来new的时候是一直猫,现在非要当作狗
Dog dog = (Dog)animal;//错误写法,编译不会报错,运行出错 ClassCastExcetion类转换异常
}
}
Instanceof关键字的使用
如何才能知道一个父类引用的对象,本来是什么子类?
格式:
对象 instanceof 类名称
这将会得到一个boolean值得结果,也就是判断前面得对象能不能当作后面类型得实例
public class Demo{
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)animal;
cat.catchMouse();//猫抓老鼠
}
giveMeAPet(new Dog());
}
//传入的都是动物,通过instanceof判断到底是狗还是猫
public static void giveMeAPet(Animal animal){
if(animal instanceof Dog){
Dog dog = (Dog)animal;
dog.watchHouse();//狗看家
}
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.catchMouse();//猫抓老鼠
}
}
}
向下转型使用 instanceof 进行判断避免出现类转换异常