目录
多态性引入
动态编译:对象的类型只有在程序执行的时候才能确定,通过多态可以让对象的可扩展性更强。
正常情况下,一个对象的实际类型是确定的,new 啥就是啥数据类型;例:
Student s1 = new Student();
但是可以指向的引用类型就不确定了;例:
Person s2 = new Student();
Object s3 = new Student();
这叫父类的引用指向子类的类型,也就是父类的引用指向子类的对象。
编译时类型由声明 该变量时使用的类型决定 (声明为左边Person类类型),运行时类型由实际赋给该变量的对象决定(实际赋给该变量的对象类型是右边Student类类型) 。若编译时类型和运行不一致 ,就出现了对象的多态性 (Polymorphism)
多态性:即同一方法可以根据发送对象的不同而采用多种不同行为方式
多态性下关于方法的调用
多态性下关于方法的调用又叫虚拟方法调用。
拿Student类和Person类来说,Person类是父类,Student类是子类,子类继承父类的全部方法。
Student s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();
对象s1的类型是Student,对象s2的类型是Student,但Person类de引用指向对象s2,对象s3的类型是Student,但Object类的引用指向对象s3。
如果Student类中有run方法:
public class Person {
public void run(){
System.out.println("people run");
}
}
在测试类中执行
s2.run();
因为Student类是子类,它继承了Person类的全部方法,所以执行s2对象的run方法的时候会打印people run。
然后子类Student类对父类中的run方法进行重写,并且在子类Student类中声明eat()方法:
public class Student extends Person {
public void run(){
System.out.println("student run");
}
public void eat(){
System.out.println("student eat");
}
}
那么在测试类中执行
//s2的类型是Person类,子类重写了父类的方法,执行子类的方 法。打印student run
s2.run();
//s1的类型是子类,执行自己重写父类的方法,打印student run
s1.run();
//s1的类型是子类,子类执行自己的特有方法,打印student eat
s1.eat();
//s2的类型是Person类,没有eat方法,会报错。
s2.eat();
总结:
编译时s2为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的run()方法。——动态绑定
对象能执行的方法主要看new对象的时等号左边的类型,和右边关系不大,子类对象类型能调用的方法有1.子类自己的方法2.继承到父类的方法。父类对象类型能调用的方法有1,父类自己的方法。如果父类对象类型想要调用子类的方法,就必须强制转换类型。例:
((Student)s2).eat();
多态使用注意事项
-
多态性的使用前提:
-
左边声明的类和右边实际赋值的类具有继承关系,也就是父类引用指向子类对象。
-
子类和父类中必须有重写的方法
2.对象的多态性的适用范围:
-
只适用于方法,不适用于属性(编译和运行都看左边)
关键字instanceof和类型转换
向下转型的的使用:
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。 如何才能调用子类特有的属性和方法?向下转型:使用强制类型转换符。
instanceof关键字
a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。如果a与A不存在父子关系,则编译不通过
使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
如果,类B是类A的父类。
a instanceof A返回true,则 a instanceof B也返回true.
类型转换
数据类型转换
byte(8位),short(16位),char(16位),int(32位),long(64位),float(32位),double(64位)
强制转换:从高到底
自动转换:从低到高
类型之间的转换
父类代表高,子类代表低
强制转换:从父类到子类
自动转换:从子类到父类
父类Person类:
public class Person {
}
子类Student类:
public class Student extends Person {
public void go(){
System.out.println("student go");
}
}
测试类:
Person student = new Student();
student.go();//报错
我们只有把student对象转换为Student类型,我们才可以使用student对象中Student类型的方法。
Person student = new Student();
//进行强制转换
Student student1 = (Student)student;
student1.go();
//另一种写法
((Student)student).go();
关键字static
static:静态的
static关键字的引入
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
使用static修饰属性
属性按照是否使用static修饰,可分为:静态属性( 类变量)和非静态属性(实例变量)
-
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
-
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
static修饰属性的其他说明:
-
静态变量随着类的加载而加载,实例变量随着对象的创建而加载。静态变量可以通过"类.静态变量"的方式进行调用
-
静态变量的加载要早于对象的创建,因为类的加载早于对象的创建。
-
由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
-
类变量(静态变量)可以用类调用,也可以用对象调用
实例变量不可以用类调用,但是可以用对象调用。
使用static修饰方法
静态方法
-
随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
-
静态方法可以用类调用,也可以用对象调用
非静态的方法不可以用类调用,但是可以用对象调用。
-
静态方法中,只能调用静态的方法或属性;非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
static关键字使用注意点
-
关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
-
在静态的方法内,不能使用this关键字、super关键字
代码块
一个类里会有属性(变量),方法,构造器,代码块,内部类;其中前三种最常用。
代码块可分为静态代码块和非静态代码块(匿名代码块)。
static{
静态代码块
}
{
非静态代码块
}
静态代码块
-
随着类的加载而执行,而且只执行一次
-
作用:初始化类的信息
-
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
-
静态代码块的执行要优先于非静态代码块的执行
-
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
非静态代码块
-
内部可以有输出语句
-
随着对象的创建而执行
-
每创建一个对象,就执行一次非静态代码块
-
作用:可以在创建对象时,对对象的属性等进行初始化
-
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
-
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
代码块与构造器执行的优先级
//总结:由父及子,静态先行
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
//找父类Objcet
super();
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
//通过super调用父类中无参数的构造器
super();
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg){
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:" + msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf(){
//通过super调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf(); //先找Lesf类的构造器
System.out.println();
new Leaf();//因为静态代码块随着类的加载而执行,而且只执行一次,所以第二次执行new Leaf()的时候不会再执行静态方法块
}
}
•
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:我是参数值
Leaf的普通初始化块
Leaf的构造器
•
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:我是参数值
Leaf的普通初始化块
对属性赋值的优先级
对属性可以赋值的位置:
①默认初始化
②显式初始化/在代码块中赋值
③构造器中初始化
④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值
具体代码实现参考java基础~面向对象之封装~构造器
抽象类(关键字abstract)
abstract可以用来修饰的结构:类、方法
abstract修饰类:抽象类
-
此类不能实例化,就是不能拿抽象类new对象。
-
抽象类中一定有构造器,用abstract修饰的类不能再实例化,但是它的子类如果没有拿abstract修饰的子类可以实例化,所有抽象类的构造器是便于子类实例化时调用(涉及:子类对象实例化的全过程)
-
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
-
抽象类的所有方法,继承了父类的子类都必须向要实现(重写)父类的方法,除非它的子类也是抽象类(用abstract修饰的类),那就让它子类的子类去实现它的方法。
-
如果一个类中有抽象方法,那么这个方法所在类一定得是抽象类。
-
抽象类中可以写普通的方法。
修饰方法:抽象方法
-
抽象方法只有方法的声明,没有方法体
-
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
-
如果父类是抽象类,并且有抽象方法,子类去继承父类的时候,要么把子类也设置成抽象类,要么重写父类中所有的抽象方法。
-
若子类重写了父类中的所有的抽象方法后,此子类方可实例化。
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰。
abstract使用上的注意点
-
abstract不能用来修饰:属性、构造器,代码块等结构
-
abstract不能用来修饰私有方法、静态方法、final的方法、final的类
接口(inferface)
接口的引入:
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有子父类的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
又如运动员类可以有篮球运动员子类和跨栏运动员子类,学生类可以有大学生子类和中学生子类,但是跨栏运动员和大学生可能都有跨栏的行为。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
接口的使用(代码实现)
TimeService接口:
public interface TimeService {
void timer();
}
UserService接口:
public interface UserService {
//interface定义的关键字:接口都需要实现类
//接口中的所有定义的方法其实都是抽象的 public abstract
//接口中定义的属性都是常量,public static final
//只是定义了四个方法增删改查,但是没有实现,接口都需要实现类,实现类的类名一般用Impl结尾
void add(String name);
void delete(String name);
void update(String name);
void query(String name);
}
两个接口的实现类:
//抽象类:extends 只能单继承
//实现类 可以实现接口 implements 接口 多继承
//实现可接口的类,就需要重新接口中的方法
//多继承:利用接口实现多继承
public abstract class UserServiceImpl implements UserService,TimeService{
@Override
public void timer() {
}
@Override
public void add(String name) {
}
@Override
public void delete(String name) {
}
@Override
public void update(String name) {
}
@Override
public void query(String name) {
}
}
接口使用注意点
-
接口中只是定义了方法,但是没有实现,接口都需要实现类,实现类的类名一般用Impl结尾。
-
表面上看,接口是一种特殊的抽象类,但是类是类,接口是接口,是并列的关系。
-
接口中所有方法都必须是抽象的。(1.8之后允许接口定义非抽象方法)
-
接口中方法定义默认为public abstract类型,成员变量默认为public static final 类型。(如果省略,系统会默认补全)。
接口的作用
1.约束
2.定义一些方法,让不同的人实现
3.方法都是public abstract
4.常量都是 public static final
5.接口中没有构造方法(它都不是类)
6.可以实现多个接口
7.必须重写接口中的方法
抽象类与接口
抽象类用关键字abstract去定义,只能用extends去继承,并且是单继承。
接口用关键字inferface去定义,子类(实现类)通过implements实现多个类,重写接口里的方法。
抽象类:
-
一个类中有抽象方法,这个类就变成了抽象类。
-
抽象类中class的前面必须有abstract修饰符。
-
抽象类中可以有普通方法,也可以有抽象方法,而抽象方法的个数可以是0个,也可以是多个。
-
子类继承父类,必须重写全部的抽象方法,除非这个类也变成了抽象类。抽象类的所有方法,继承了父类的子类都必须向要实现(重写)父类的方法,除非它的子类也是抽象类(用abstract修饰的类),那就让它子类的子类去实现它的方法。
接口:
-
表面上看,接口是一种特殊的抽象类,但是类是类,接口是接口,是并列的关系。
-
接口中所有方法都必须是抽象的。(1.8之后允许接口定义非抽象方法)
-
接口中方法定义默认为public abstract类型,成员变量默认为public static final 类型。(如果省略,系统会默认补全)。
-
接口中只是定义了方法,但是没有实现,接口都需要实现类,实现类的类名一般用Impl结尾。
两者关系:
-
相同点:抽象类和接口都不能被实例化
-
一个类只能继承一个抽象类,而一个类可以实现多个接口。
-
抽象类可以有构造方法,接口中不能有构造方法。
-
抽象类中可以有成员变量,接口中没有成员变量。(被final修饰变成了常量)
-
抽象类中可以有普通方法,接口中所有方法都必须是抽象的。(1.8后允许接口定义非抽象方法)
-
抽象类中抽象方法的访问类型可以是public,protected,但接口中抽象方法的访问类型只能是public,并且默认为public abstract(省略则自动默认补全)。
-
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
-
抽象类中可以有静态代码块和静态方法,接口中不能含有静态代码块以及静态方法
内部类
内部类就是在一个类的内部定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对于B类来说就是外部类了。
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
现有一个Person类,里面有name,age属性,我们想在Person类中加一个brain(大脑)的类,因为把brain设置成属性太简单,所以我们就把它设置成内部类,Person是外部类,brain是内部类。
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
内部类分类
-
成员内部类
内部类可以访问外部类的私有属性和私有方法!!!
-
静态内部类
-
局部内部类
-
匿名内部类
成员内部类
public class Outer {
private int id;
public void out(){
System.out.println("这是外部类方法");
}
class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
//获取外部类的私有属性
public void getID(){
System.out.println(id);
}
}
}
//测试
public static void main(String[] args) {
Outer outer = new Outer();
outer.out();
//通过这个外部类来实例化内部类
Outer.Inner inner = new Outer().new Inner();
//获取外部类的私有属性
inner.getID();
}
静态内部类
public class Outer {
private int id;
public void out() {
System.out.println("这是外部类方法");
}
static class Inner {
public void in() {
System.out.println("这是内部类方法");
}
}
}
//测试
public static void main(String[] args) {
Outer outer = new Outer();
outer.out();
Outer.Inner inner = new Outer.Inner();
inner.in();
}
局部内部类
public class Outer {
private int id;
public void method() {
class Inner {
public void in() {
System.out.println("这是局部内部类");
}
}
Inner inner = new Inner();
inner.in();
}
}
//测试
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}