七、多态思想
1、接口
接口是一种约定规范,是多个抽象方法的集合。仅仅只是定义了应该有哪些功能,本身不实现功能,至于每个功能具体怎么实现,就交给实现类完成。
接口中的方法是抽象方法,并不提供功能实现,体现了规范和实现相分离的思想,也体现了组件之间低耦合的思想。
所谓耦合度,表示组件之间的依赖关系。依赖关系越多,耦合性越强,同时表明组件的独立性越差,在开发中往往提倡降低耦合性,可提高其组件独立性,举一个低耦合的例子。
电脑的显卡分为集成显卡和独立显卡:
- 集成显卡:显卡和主板焊死在一起,显卡坏了,只能换主板
- 独立显卡:显卡和主板相分离,显卡插到主板上即可,显卡坏了,只换显卡,不用换主板
接口也体现的是这种低耦合思想,接口仅仅提供方法的定义,却不提供方法的代码实现。那么得专门提供类并去实现接口,再覆盖接口中的方法,最后实现方法的功能,在多态案例中再说明。
接口定义和多继承性
接口可以认为是一种特殊的类,但是定义类的时候使用class关键字,定义接口使用interface关键字。
public interface 接口名{ //抽象方法1(); //抽象方法2(); //抽象方法2(); }
接口表示具有某些功能的事物,接口名使用名词,有人也习惯以I打头如IWalkable.java。
接口定义代码:
public interface IWalkable { void walk(); }
接口中的方法都是公共的抽象方法,等价于:
public interface IWalkable { public abstract void walk(); }
拓展: 从Java8开始, Java支持在接口中定义有实现的方法, 如:
public interface IWalkable { public abstract void walk();//抽象方法 default void defaultMethod(){ System.out.println("有默认实现的方法, 属于对象"); } static void defaultMethod(){ System.out.println("有默认实现的方法, 属于类"); } }
类可以继承类,但是只能单继承的,接口也可以继承接口,但是却可以继承多个接口,也就是说一个接口可以同时继承多个接口,如两栖动物可以行走也可以拥有。
2、多态时方法调用问题
把子类对象赋给父类变量,此时调用方法:
Animal a = new Cat(); a.shout();
那么a对象调用的shout方法,是来自于Animal中还是Cat中?判断规则如下:
image.png
文字解释,先判断shout方法是否在父类Animal类中:
- 找不到:编译报错
- 找 到:再看shout方法是否在子类Cat类中:
- 找不到:运行父类方法
- 找 到:运行子类方法(这个才是真正的多态方法调用)
多态小结
//Dog d = new Dog(); Cat c = new Cat(); Animal a = new Cat(); a.xxx(); a.ooo(); IEmployeeService service = new EmployeeServiceImpl2(); service.save(); service.delete();
八、this关键字
什么是this:表示当前对象
this主要存在于两个位置:
- 在构造器中:表示当前被创建的对象
- 在 方法中:哪一个对象调用this所在的方法,此时this就表示哪一个对象
public class Cat { private String name; private int age; public Cat() { System.out.println("构造器中:" + this); //当前创建的对象 } public void say() { System.out.println("say方法中:" + this); //当前调用say方法的对象 System.out.println("名字=" + this.name + ",年龄=" + this.age); } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } }
测试代码:
public class ThisDemo { public static void main(String[] args) { Cat c1 = new Cat(); //此时构造中的this就是当前创建的c1对象 c1.setName("加菲"); c1.setAge(5); c1.say(); //此时say方法中的this就是c1对象 Cat c2 = new Cat();//此时构造中的this就是当前创建的c2对象 c2.setName("Tom"); c2.setAge(3); c2.say(); //此时say方法中的this就是c2对象 } }
运行测试:
构造器中 :cn.wolfcode._01_this.Cat@15db9742 say方法中 :cn.wolfcode._01_this.Cat@15db9742 名字=加菲,年龄=5 构造器中 :cn.wolfcode._01_this.Cat@6d06d69c say方法中 :cn.wolfcode._01_this.Cat@6d06d69c 名字=Tom,年龄=3
什么时候需要使用this:
- 解决局部变量和成员变量之间的二义性,此时必须使用
- 同一个类中非static方法间互调(此时可以省略this,但是不建议省略)
- 构造器重载的互调
public class Dog { private String name; private int age; public Dog() { } public Dog(String name) { this(name, 0);// 调用两个参数的构造器,必须放在构造器第一行 // TODO其他操作 } public Dog(String name, int age) { this.name = name; this.age = age; } public void say() { String name = "局部变量"; System.out.println(name); // 访问局部变量 System.out.println(this.name); // 访问成员变量 this.other();// 调用当前类中非static方法 } public void other() { System.out.println(this.age);//此时的this是谁 } }
九、super关键字
什么是super:
this :表示当前对象,谁调用this所在的方法,this就是哪一个对象
super :当前对象的父类对象
在创建子类对象时,在子类构造器的第一行会先调用父类的构造器。
什么时候使用super:
- 在子类方法中,调用父类被覆盖的方法,此时必须使用super
- 在子类构造器中,调用父类构造器,此时必须使用super语句
父类代码:
public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public void doWork() { System.out.println("Person...doWork..."); } }
子类代码:
public class Student extends Person { private String sn; public Student(String sn) { super();// 隐式调用父类无参数构造器,必须作为构造第一行 this.sn = sn; } public Student(String name, int age, String sn) { super(name, age);// 显示去调用父类无参数构造器,必须作为构造第一行 this.sn = sn; } public void doWork() { super.doWork(); // ?此时调用谁的方法 this.doWork(); // ?此时调用谁的方法 System.out.println("Student...doWork..."); } }
十、static修饰符
static修饰的字段和方法直接属于类,不属于该类的对象。记住:字段和方法属于谁,就让谁来调用。
- 使用static修饰的成员: 属于类 直接使用类名调用即可
- 没有使用static修饰的成员: 属于对象 必须先创建对象,再调用
注意:static方法不能使用super和this:
因为static是类级别的,super和this是对象级别的,存在类的时候不一定存在对象,也就说使用类名调用static方法时,此时可能是没有对象的。
测试代码:
public class Dog { public static int totalNumber = 100; public int age; public void m1() { System.out.println("实例方法"); } public static void m2() { System.out.println("静态方法"); } }
public class StaticDemo { public static void main(String[] args) { Dog d1 = new Dog(); d1.age = 5; Dog d2 = new Dog(); d2.age = 15; //调用代码再后面 } }
内存分析:
static修饰的成员变量(字段),随着所在类被加载进JVM,也同时存储在方法区中,被所有对象共享。
图片 1_7.png
实例成员和类成员的访问规则:
d1.m1();// 实例方法 d2.m1();// 实例方法 // Dog.m1(); 语法报错 d1.m2();//静态方法 底层使用类名访问 d2.m2();//静态方法 底层使用类名访问 d1.m2();//静态方法 底层使用类名访问 Dog.m2(); System.out.println(d1.age);// 5 System.out.println(d2.age);// 15 // System.out.println(Dog.age); 语法报错 System.out.println(d1.totalNumber);//100 底层使用类名访问 System.out.println(d2.totalNumber);//100 底层使用类名访问 System.out.println(Dog.totalNumber);//100
使用对象访问static方法或成员变量,底层依然使用类名访问的。
一般的,static方法访问的成员变量必须使用static修饰。
最后记住结论:
- 类 成 员:使用static修饰的字段和方法: 属于类 直接使用类名调用即可
- 实例成员:没有使用static修饰的字段和方法: 属于对象 必须先创建对象,再调用
十一、final修饰符
继承关系最大弊端是破坏封装,子类可以继承父类的实现细节,也可以通过方法覆盖的形式修改功能实现细节。那么怎么来限制某个类不能有子类,不能覆盖方法?——final修饰符。
final的含义是最终的,不可改变的,可以修饰类、方法、变量。
- final修饰的类:表示最终的类, 该类不能再有子类
final public class Super {}public class Sub extends Super{ //此行语法报错}
final修饰的方法:最终的方法,该方法不能被子类覆盖
public class Super { final public void doWork() { } } public class Sub extends Super{ public void doWork() { //此行语法报错 } }
final修饰的变量:表示常量,该变量只能赋值一次,不能再重新赋值。
- 基本数据类型:表示的值不能改变
- 引用数据类型:所引用的地址值不能改变
final int age = 17; age = 100; //此行语法报错 final Dog d = new Dog(); d.setAge(5); //d的字段值是可以改变的 d = new Dog(); //此行语法报错
十二、代码块
什么是代码块:直接使用{}括起来的一段代码区域。
代码块里变量属于局部变量,只在自己所在区域{}内有效。
存在三种形式:
局部代码块: 直接定义在方法内部的代码块,一般的,不会直接使用局部代码块的,结合if、while、for等关键字使用,表示一块代码区域。
public class CodeBlockDemo { public static void main(String[] args) { System.out.println("begin..."); { //直接使用代码块,一般不用 int age = 17; } System.out.println(age); //此行报错,超出age作用范围,就不能访问到了 if (100 > 5) { System.out.println("100 > 5"); } System.out.println("end..."); } }
- 初始化代码块(构造代码块):直接定义在类中。每次创建对象的时候都会执行初始化代码块,开发中不使用初始化代码块,即使要做初始化操作,可以直接在构造器中完成即可。
- 静态代码块:使用static修饰的初始化代码块,当该代码块的类的字节码被加载进JVM,就执行static代码块代码。在开发中,用来做加载资源、加载配置文件等操作。
分析下面代码执行顺序:
public class Fish { { System.out.println("初始化代码块..."); } static { System.out.println("静态代码块..."); } public Fish() { System.out.println("构造器"); } public static void main(String[] args) { System.out.println("主方法..."); new Fish(); new Fish(); }
运行结果:
静态代码块... 主方法... 初始化代码块... 构造器 初始化代码块... 构造器``` ##十三、内部类和匿名内部类 什么是内部类,把一个类定义在另一个类的内部,把里面的类称之为内部类,把外面的类称之为外部类。 ![图片 2_7.png](https://upload-images.jianshu.io/upload_images/15616626-a8e424a607a5ddc5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 内部类可以看作和字段、方法一样,是外部类的成员,而成员可以有static修饰。 - 静态内部类:使用static修饰的内部类,那么访问内部类直接使用外部类名来访问 - 实例内部类:没有使用static修饰的内部类,访问内部类使用外部类的对象来访问 - 局部内部类:定义在方法中的内部类,一般不用 - 匿名内部类:特殊的局部内部类,适合于仅使用一次使用的类 对于每个内部类来说,Java编译器会生成独立.class文件。 - 静态和实例内部类:外部类名$内部类名字- - 局部内部类:外部类名$数字内部类名称 - 匿名内部类:外部类名$数字 . 匿名内部类 如果这一个Print类只需要使用一次的话,就完全没有必要单独定义一个Java文件,直接使用匿名内部类来完成。 匿名内部类,可以使用父类构造器和接口名来完成。 针对类,定义匿名内部类来继承父类(使用较少):
针对接口,定义匿名内部类来实现接口(使用较多):
new 接口名称(){ //匿名内部类的类体部分 //定义成员变量 //定义成员方法 //覆盖接口中的所有抽象方法 }
注意:这里不是根据接口创建对象,而是一种语法而已。
board.plugin(new IUSB() { public void swapData() { System.out.println("打印...打印..."); } });
其实匿名内部类,底层依然还是创建了一份字节码文件USBDemo$1,其反编译代码为: ![图片 3_6.png](https://upload-images.jianshu.io/upload_images/15616626-668f077277d3e36c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)