一.抽象方法与抽象类
问题情景: 假如学生,老师都继承于Person类,Person类中有方法叫work 对于学生和老师来说,工作的内容都不一样,所以需要重写方法work 如果有人故意不在学生类中重写(或者说是忘记了),程序就有可能出问题: 老师类和学生类调用work方法时依然是父类的方法,不是老师与学生类各自的方法 这里就引出了今天的:
抽象方法: 多个对象中把共性的内容抽取到父类之后,如果每一个子类的执行内容不一样,导致父类中无法确定具体方法,就可以把这个方法定义为抽象方法 对于父类中的抽象方法,子类必须重写(强制性的) 抽象类: 抽象方法所在的类就是抽象类! 如果一个类中有一个及以上抽象方法,那就必须定义为抽象类 抽象方法格式: 抽象方法不用写方法体{},用分号;结束 public abstract 返回类型 方法名(......); 抽象类格式: public abstract class 类名{} 抽象方法优点: 适合多人开发,规定好方法名,返回类型,参数,以便让所有人写的方法都是一个样式的,可以规范写法,强制子类必须按照一种格式进行重写
注意事项: 1.抽象类不能够创建对象(重写里面的方法的话还是可以创建对象的) 2.有抽象方法就一定是抽象类,但是抽象类中不一定有抽象方法,这样的抽象类的作用就是不能创建抽象类的对象 3.抽象类中可以有构造方法———作用:给子类的属性进行赋值 4.抽象类的子类:要么这个子类重写抽象类中的所有抽象方法,要么这个子类也是一个抽象类,一般采取第一种办法
案例代码:
public class Test {
public static void main(String[] args) {
//注意事项1:在这里不能够new Animal抽象类的对象
System.out.println("子类对象调用其抽象父类的非抽象方法");
new Frog().drink();
System.out.println("子类对象调用其重写的其父类的抽象方法");
new Dog().eat();
System.out.println("用子类的构造方法(使用super调用抽象父类中的构造方法)创建子类对象,再调用其重写的其父类的抽象方法");
new Sheep("羊羊",5).eat();
}
}
abstract class Animal{
private String name;
private int age;
public abstract void eat();
public void drink(){
System.out.println("小动物在喝水");
}
public Animal(){};
//注意事项3:抽象类中也可以有构造方法
public Animal(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Dog extends Animal{
public Dog(){}
public Dog(String name,int age){
super(name, age);
}
@Override
public void eat(){
System.out.println("小狗狗吃骨头");
}
}
class Frog extends Animal{
public Frog(){}
public Frog(String name,int age){
super(name, age);
}
@Override
public void eat(){
System.out.println("小青蛙吃虫子");
}
}
class Sheep extends Animal{
public Sheep(){}
public Sheep(String name,int age){
super(name, age);
}
@Override
public void eat(){
System.out.println("小山羊吃草");
}
}
abstract class Cat extends Animal{
//注意事项4:抽象类的子类抽象类:可以不用重写方法
}
运行结果:
二.接口
问题情景: 假设有三个类继承于Animal 狗,兔兔,青蛙: 对于吃饭和喝水的方法:三者都可以有 对于游泳的方法:兔兔不能有,青蛙和狗狗(多个类)可以有 但是这样就不能够在父类中使用abstract限定他们的方法了,只能分开来写,这样就有可能造成方法的不统一(如方法的名字) 这个时候就可以定义一个接口,接口里面定义游泳的abstract方法, 这样就可以创造一个规则,可以让青蛙类和狗狗类按照此规则书写代码。
定义接口格式(注意接口和类不一样,接口不能够写在类里面!): public interface 接口名{} 接口由类实现,通过implement表示
接口注意事项 1.接口不能创建对象(和抽象类一样) 2.implement某个接口的类一般称为实现类,在实现类要么实现所有的抽象方法(多数情况),要么是抽象类(少数情况) 3.一个类可以实现多个接口,也就是说会有多种规则(下面的案例中只实现了最多一个),这和继承就不一样了,一个子类只能继承于一个父类 4.实现类在继承一个类的同时也能够实现接口 5.接口和类不一样,接口里面的方法默认使用public abstract修饰:可以不写,接口方法不能用protected修饰! 在写好类名,继承与接口的时候可以点点IDEA的红色波浪线:会帮你自动生成方法的
案例代码:
public class Test {
public static void main(String[] args) {
//注意事项1:在这下面不能够new Swim();
System.out.println("调用对象实现的接口方法");
new Dog().swim();
}
}
abstract class Animal{
private String name;
private int age;
public Animal(){};
public Animal(String name, int age){
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public abstract void eat();
}
//接口名记得大写
interface Swim{
//注意事项5:接口里面的方法默认使用public abstract修饰:可以不写
public abstract void swim();
}
//注意事项4:实现类在继承一个类的同时也能够实现接口
class Dog extends Animal implements Swim{
public Dog() {
}
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat(){
System.out.println("吃骨头");
}
@Override
public void swim(){
System.out.println("狗刨");
}
}
class Rabbit extends Animal{
public Rabbit() {
}
public Rabbit(String name, int age) {
super(name, age);
}
@Override
public void eat(){
System.out.println("吃胡萝卜");
}
}
abstract class Car extends Animal implements Swim{
//注意事项2:在实现类也可以是抽象类,可以不用实现抽象方法
}
运行结果:
对于接口中的所有成员变量:默认使用public static final修饰,不写默认为这个
JDK8之后的接口内的方法
在JDk7以前,接口中只能定义抽象方法 到JDK8之后,接口中就可以定义有方法体的方法:(默认与静态方法) JDK9以后就可以用private修饰方法
三.接口中的default成员方法
父类中能够定义方法让其实现类继承,接口中也应该这样 接口升级:添加接口方法,这个时候实现类不需要立马修改,用到规则再重写就行,可以使用default方法 在接口中定义,需要用default修饰(不是什么都不写) 定义default方法的格式: public default void show()不写public也会默认public 该方法不强制被重写:如果重写就在其实现类中去掉default 如果实现多个接口,这些接口中存在名字相同的default方法,就需要重写防止混淆
public class Test {
public static void main(String[] args) {
InterImpl interImpl = new InterImpl();
interImpl.method();
interImpl.show();
}
}
class InterImpl implements Inter1,Inter2{
@Override
public void method(){
System.out.println("抽象方法需要被重写");
}
@Override
public void show() {
//IDEA自动生成调用某个接口的方法,这里一定要指定接口名
Inter2.super.show();
}
}
interface Inter1{
void method();//接口里面不写修饰符默认用public abstract修饰 什么都不写就是隐式指定public abstract
default void show(){
System.out.println("这个是接口中的default方法,不需要强制重写");
}
}
interface Inter2{
default void show(){
System.out.println("这个是接口中的default方法,不需要强制重写,除非实现的多个接口中有相同的方法名");
}
输出结果:
四.接口中的static成员方法
接口中的静态方法(前面两个学的是无修饰的接口方法(不能有内容)与default修饰的接口方法) 接口中的静态方法只能通过类名调用 (当然一般类中也只推荐使用类名调用,但是也能够通过对象调用(不把构造方法私有化),这里接口由于不能实例化对象,所以无法通过对象调用) 接口中的静态方法格式: 例如:public static void show(){},不写public也会默认public
public class Test {
public static void main(String[] args) {
Inter.show();
InterImpl.show();
}
}
class InterImpl implements Inter{
//静态方法不能够被重写,无论是实现类(实现接口中有静态方法)还是子类(父类中有静态方法)
//不写@Override的话也不会报错:这只是这个类与接口中有重名的方法,这不叫重写
//以前也说了,重写是重写虚方法表中的,static,final,private不会被重写
//子类能够继承并且调用父类的静态方法,但是实现类不能调用接口中的static方法!只能用default!这两个修饰符不能够放一起写
public static void show(){
System.out.println("接口的实现类中的静态方法:与接口中的静态方法没有关联");
}
}
interface Inter{
static void show(){
System.out.println("接口中的静态方法");
}
}
五.接口中的private成员方法
在以前说过,如果多个类中有重复的代码,一般把这段代码单独分离出来,并设置成private 接口中的私有方法定义格式: 普通的案例:private void show(){}(注意这里不用加default,因为这个方法就是给default修饰的方法服务的) 静态的案例:private static void method(){}(和上面的那个一样,这个方法就是为public的静态方法服务的)
案例代码
public class Test {
public static void main(String[] args) {
InterImpl interImpl = new InterImpl();
interImpl.show1();
interImpl.show2();
}
}
class InterImpl implements Inter{
}
interface Inter{
default void show1(){
System.out.println("default修饰的第一个方法");
privateShow();
}
default void show2(){
System.out.println("default修饰的第二个方法");
privateShow();
}
private void privateShow(){
System.out.println("private修饰的普通方法,为default方法中的重复代码服务");
}
private static void privateStaticShow(){
System.out.println("private修饰的static方法,为public static中的重复代码服务");
}
}
六.接口的应用(多态)
问题情境 如果说有搬家方法:moveHome(Car car) 这个方法里面能放car和car的一切子类的对象 但是不能放人,或者说是搬家公司,按照之前的知识,那我只能够有多少种搬家的工具,我就要写多少方法,很麻烦 从逻辑上来讲也不能够让人,car,搬家公司继承于同一个特定父类,要的不是一个继承体系,而是遵循一种特定的功能 这时候就可以定义运输的接口,这样搬家方法就变成了moveHome(接口 interface),接口就是对行为的抽象 这样,就能够把接口的实现类(车,人,搬家公司)全部传递到moveHome里面去,这个就叫做接口多态 就像Father father = new Son();一样 接口中,也能够这样写,用实现类创建对象:Inter inter = new InterImpl(); 调用方法的时候也同样遵守:编译看左边,实现看右边的原则
案例代码
public class Test {
public static void main(String[] args) {
System.out.println("运用多态,调用Car类对象中实现接口的方法");
new Home().moveHome(new Car());
}
}
class Home{
void moveHome(Inter inter){
inter.show();
}
}
interface Inter{
public static final String name = "搬家行为的接口";
void show();
}
class Car implements Inter{
@Override
public void show() {
System.out.println("汽车搬家");
}
}
class Human implements Inter{
@Override
public void show() {
System.out.println("人力搬家");
}
}
运行结果:
七.适配器设计模式
适配器设计模式:用来解决接口与接口实现类之间的矛盾问题 问题情景: 在接口中如果有10个抽象方法:而现在我只要用实现类实现第一个抽象方法,其他的不需要,但是这样的话我就要重写其他的抽象方法。 有一种解决办法:就是创建一个类(这个类就是适配器,一般设置为抽象类,不让外界创建对象)实现接口:但是实现的方法里面全部空着不写 想要创建实现类调用方法的时候,这个类继承适配器就可以了,这样就可以重写其中某一个方法就能够使用这个类了 但是我认为这里还有一种可能,就是全写成default修饰然后里面全写空也能做到。 这里老师讲的比较浅,只是了解”适配器模式“即可
案例代码:
public class Test {
}
class InterImpl extends InterAdapter{
//在这个类当中用到哪个方法就重写什么方法,如果这个类还有继承的其他类(如Animal),可以让适配器继承那个类Animal
@Override
public void method1(){
System.out.println("只用重写一个方法即可");
}
}
abstract class InterAdapter implements Inter{
//在这个类当中实现所有方法的空方法:设置成抽象类,不需要让外界创建该类对象
@Override
public void method1() {
}
@Override
public void method2() {
}
@Override
public void method3() {
}
@Override
public void method4() {
}
@Override
public void method5() {
}
}
interface Inter{
void method1();
void method2();
void method3();
void method4();
void method5();
}
八.内部类:成员内部类
类的五大成员:变量,方法,构造方法,代码块,最后一个就是内部类 就是在一个类里面再定义其他的类 比如再ArrayList里面搜索所有成员: 看前面的字母:m是方法,f是变量,c就是内部类 需求:定义JavaBean汽车类: 汽车品牌,年龄,颜色,发动机品牌,使用年限 汽车发动机依赖于汽车,再定义一个发动机类也没有意义,故就在汽车类里面定义就可以咯
1.内部类的成员变量访问问题: 内部类也可以使用外部类的变量,包括私有 外部类不能使用内部类变量,必须创建对象
2.成员内部类可以被一些修饰符修饰的情况(这里的代码没有实验) 使用private修饰内部类,在外界(不是外部类)不能直接创建该对象 如果使用static修饰内部类,即成为静态内部类,在后面讲解 JDK16以后,成员内部类中,就能够定义静态变量
3.获取成员内部类对象方法: 1.外部类成员编写方法(当内部类被private修饰) 2.外部类.内部类 对象名 = new 外部类().new 内部类()(当内部类没有被private修饰)
案例代码:
public class Test {
public static void main(String[] args) {
//创建变量名接收内部类对象
Car.Engine engine = new Car().new Engine();
//创建外部类对象调用外部类方法
new Car().show();
//创建内部类对象调用内部类方法
new Car().getEngine().show();
}
}
class Car {
private String carName;
private int carAge;
private int carColor;
public void show() {
System.out.println("外部类想要使用内部的成员变量需要创建对象:即使它是私有的也能调用");
System.out.println("外部类方法创建新的内部类对象在调用其成员变量:"+new Engine().engineName);
}
public Engine getEngine(){
return new Engine();
}
class Engine {
static String str = ("JDK16之后,成员内部类里面就可以定义静态变量");
private String engineName;
private int engineAge;
public void show() {
System.out.println("内部类也可以使用外部类的变量:即使它是私有的"+carName);
System.out.println("内部类方法调用的本身的成员变量:"+engineName);
}
}
}
输出:
九.成员内部类中的方法对于外部类的成员变量,内部类的成员变量,方法中的局部变量的调用
在加载字节码文件的时候,这里的代码加载的是两个: 一个是Outer.class,还有一个是Outer&Inner.class,两个字节码文件相互独立 在创建外部类的时候,不会创建内部类的对象! 创建内部类的时候由于必须要创建外部类,所以两个类都需要创建。 在创建内部类的时候,内部类对象的堆空间里面会存储变量,还有一个隐藏的this变量
案例代码:
public class Test {
public static void main(String[] args) {
new Outer().new Inner().show();
}
}
class Outer{
int a = 10;
class Inner{
//在Inner内中会隐藏存储外部类的对象地址Outer this$0;
int a = 20;
public void show(){
int a = 30;
System.out.println("输出外部类的变量:Outer.this就是获取这个对象的外部类对象的地址值!:"+Outer.this.a);
System.out.println("输出内部类的变量:这里的this记录的是调用者的地址值,也就是内部类的对象:"+this.a);
System.out.println("输出方法内部的变量:"+a);
}
}
}
十.静态内部类
本案例中有:
在外界创建静态内部类对象 静态内部类对外部类对象与方法的访问 外界调用静态内部类方法 其实核心还是对于静态修饰的成员在内存中的原理:这些成员会随类名加载,理解这个下面的就很容易看懂了,不要死记硬背
案例代码:
public class Test {
public static void main(String[] args) {
//创建静态内部类的对象:因为是静态的成员:只用外部类名就能调用静态内部类(不需要像上面的成员内部类对象一样先创建外部类对象)。
Outer.Inner inner = new Outer.Inner();
//调用静态内部类中的非静态方法:需要创建对象
inner.show();
//调用静态内部类的静态方法:直接调用即可
Outer.Inner.staticShow();
}
}
class Outer{
static String a = "外部类的静态变量";
String b = "外部类的非静态变量";
static class Inner{
void show(){
System.out.println("静态类中的非静态方法");
System.out.println("静态内部类中,对于外部类:只能访问外部类当中的静态变量与方法,不能访问外部类非静态成员:"+a);
System.out.println("创建在内部类中创建外部类的对象以访问外部类的非静态变量与方法:"+new Outer().b);
}
static void staticShow (){
System.out.println("静态类中的静态方法");
}
}
}
十一.局部内部类
局部内部类:在方法里面定义的类. 1.方法外面不能访问该类,也不能够创建该类的对象:就像不能访问方法里面的局部变量一样 2.方法中使用局部内部类里面的成员需要先创建对象 3.局部内部类可以访问方法外的变量与方法内部的成员变量 以上这些也是通过Java方法中的成员的内存原理去理解就好
案例代码:
public class Test {
public static void main(String[] args) {
new Outer().show();
}
}
class Outer{
String str2= "方法外的变量";
public void show(){
String str1 = "方法内的变量";
class Inner{
String str = "方法里面的局部内部类的成员变量";
void show(){
System.out.println("方法里面的局部内部类的成员方法");
System.out.println("可以调用"+str1+"与"+str2);
}
}
Inner inner=new Inner();
System.out.println("需要在方法里面创建对象才能够调用方法里面的变量"+inner.str);
inner.show();
}
}
十二.匿名内部类
匿名内部类:没有类名的类! new 类名/接口名{ @Override 重写的方法(); }; 可以对比 有名字的类实现 与 匿名内部类中的实现
下面的代码比较乱:但是知识点都写在里面了,有时间我会再更新更加清晰的代码
public class Test {
static Swim swim = new Swim() {
@Override
public void swim() {
System.out.println("写在成员位置的匿名内部类");
}
};
public static void main(String[] args) {
Swim swim = new Swim() {
@Override
public void swim() {
System.out.println("在匿名内部类里面实现接口里面的抽象方法");
}
public void swim2() {
System.out.println("在匿名内部类里面新增自己的方法");
}
};
//多态
//如果用接口定义变量:如果选择不用接口变量接收对象,就可以调用匿名内部类里面的所有方法
//如果说定义了接口变量接收对象,那么这个对象只能调用swim方法,不能调用自己新增的方法
//在这里是不能调用Swim2的:因为这里的变量使用接口去定义的,要先看接口中有没有这个方法:如果没有就不能调用Swim2
swim.swim();
new Swim() {
@Override
public void swim() {
System.out.println("在匿名内部类里面实现接口抽象方法");
}
public void swim2() {
System.out.println("在匿名内部类里面新增自己的方法");
}
}.swim2();
Animal animal = new Animal() {
@Override
void eat() {
System.out.println("在匿名内部类里面实现父类的抽象方法");
}
};
animal.eat();
method(new Animal() {
@Override
void eat() {
System.out.println("直接使用method方法,但是传进去的是一个匿名内部类的对象!");
}
});
}
public static void method(Animal a){
a.eat();
}
}
class Student implements Swim
/**********这一段相当于是一个没有名字的类************/
{
@Override
public void swim() {
System.out.println("在有名字的类中实现接口中的抽象方法");
}
}
/*******************************************/
interface Swim {
void swim();
}
abstract class Animal {
abstract void eat();
}
运行结果: