主要知识点:
- 什么叫面向对象
- 类
- 构造方法
- static与类的加载顺序
- 匿名对象
- this关键字
- 封装
- 访问权限修饰符
- 类的设计分析
- 继承
- 子类访问父类和方法覆写
- super关键字
- 多态
- 什么叫面向对象
面向对象:把数据和对数据的操作方法放在一起,作为一个相互依存的整体——对象。对同类对象抽象出其共性,形成类。类中的大多数数据,只能用本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过信息进行通信。程序流程由用户在使用中决定。面向过程:自顶向下顺序执行,逐步求精,其程序的结构按功能划分若干个模块,这些模块形成一个树状的结构,各模块之间的关系尽可能的简单,在功能上相对独立;每一个模块都是由顺序,选择和循环三种基本结构来组成;其模块化的具体方法是使用子程序。程序流程在写程序中就已决定。
面向对象的特点:
- 将复杂的事情简单化。
- 面向对象将以前过程中的执行者变成了指挥者。
- 面向对象这种思想是符合现代人们思考习惯的一种思想。
- 类
类是java语言最小的变成单位,也是设计和实现java程序的基础。
类是一组事物共性的特征和功能的描述,类是一组事物的总体描述,是按照面向对象技术设置的最小单位,也是组成项目的最基本的模块。类的概念是抽象的,类似于建筑设计中的图纸,是对于实现需要代表的具体内容的抽象,类只包含框架结构,而不包含具体的数据。所以类代表的是总体,不是代表某个特定的个体。
类是抽象的,对象是具体的。
- 构造方法
构造方法:用来构造类的实例(每一个类都默认有一个无参的构造方法,得使用new调用)
字段:类或对象所包含的数据,对类状态的一种描述。
方法:类或对象的特征或行为。
作用:给类中的字段进行初始化,可以用来创建对象。
特点:1.方法名和类名相同,不用定义返回值,不需要写return语句。
注意:多个构造方法是以重载的形式出现的。
- static与类的加载顺序
static是一个修饰符,用于修饰成员(成员变量和成员函数)。
特点:
- 被所有的对象所共享。
- 可以直接被类名调用。
- 随着类的加载而加载。
- 优先于对象存在。
弊端:
- 有些数据是对象特有的数据,是不可以被静态修饰的,因为那样的话,特有的数据会变成共享的数据。这样对事物的描述就出现了问题,在定义静态时要明确是否被共享。
- 静态方法只能访问静态成员,不可以访问非静态成员,因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。
- 静态方法中不能使用this和super关键字,因为this代表对象,而静态的时候可能没有对象,所以this无法使用。
- 主函数是静态的。
什么定义静态的成员呢?或者说,在定义成员的时候,到底要不要被静态修饰?
成员变量。(数据共享的时候静态化)
该成员变量的数据是否是所有对象一样:
如果是,那么该变量需要被静态修饰,因为是共享数据。
如果不是,那么就说这是对象的特有数据。
成员函数。(方法中没有调用特有数据的时候就定义成静态)
该函数内是否访问了特有数据。
如果有不能被修饰,没有则需要被静态修饰。
成员变量和静态变量的区别?
- 成员变量所属于对象也成为实例变量。静态变量所属于类,也被称为类变量。
- 成员变量存在于堆内存中。静态变量存在于方法区中。
- 成员变量随着对象创建而存在,随着对象被回收而消失。静态变量随着类的加载而存在,随着类的消失而消失。
- 成员变量只能被对象所调用,静态变量可以被对象调用,也可以被类名调用。
所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。
静态的注意:静态的生命周期很长。
静态代码块:就是一个有静态关键字标示的一个代码块区域,定义在类中。
作用:可以完成类的初始化,静态代码块随着类的加载而执行,而且只执行一次(new 多个对象就只执行一次)。
如果和主函数在同一个类中,优先于主函数执行。
静态代码块,构造代码块和构造函数的区别?
静态代码块:用于给类初始化,类加载时会被加载执行,只加载一次。
构造代码块:用于给对象初始换,只要创建对象该不菲就会被执行,且优先于构造函数。
构造函数:给对应对象初始化,建立对象时,选择相应的构造函数初始化对象。
创建对象时,三者被加载执行顺序:静态代码块>构造代码块>构造函数
什么时候会加载类?
使用类中的内容时加载:有三种情况。
1.创建对象,new StaticCode();
2.使用类中的静态成员 StaticCode.num = 9 ,StaticCode.show();
3.在命令行中运行 java StaticCodeDemo
类所有内容加载顺序和内存中的存放位置
利用语句分析
Person p = new Person("zhangsan",20);
该句话所做的事情:
1.在栈内存中,开辟main函数空间,建立main函数的变量p
2.加载类文件:因为new要用到Person.class,所有要先从硬盘中找到Person.class类文件,并加载到内存中。
加载类文件时,除了非静态成员变量(对象的特有属性)不会被加载,其他的都会被加载。
记住:加载,是将类文件中的一行行内容加载到内存中,并不会执行任何的语句。加载时期,即使有输出语句也不会执行。
静态成员变量(类变量) ----------》方法区的静态部分
静态方法 ----------》方法区的静态部分
非静态方法(包括构造函数) ----------》方法区的非静态部分
静态代码块 -----------》方法区的静态部分
构造代码块 ----------》方法区的静态部分
注意:在Person.class文件加载时,静态方法和非静态方法都会加载到方法区中,只不过要调用非静态方法的时候要先实例化一个对象。
对象才能调用非静态的方法,如果让类中的所有非静态方法都随着对象的实例化而建立一次,那么会消耗大量的内存资源。
所以才会让所有的对象共享这些非静态方法,然后用this关键字指向调用非静态方法的对象。
3.执行类中的静态代码块,如果有的话对person.class进行初始化。
4.开辟空间:在堆内存中开辟空间,分配内存地址。
5.默认初始化:在堆内存中建立对象的特有属性,并进行默认的初始化。
6.显示初始化:对属性进行显示初始化。
7.构造代码块:执行类中的构造代码块,对对象进行构造代码块初始化。
8.构造函数初始化:对对象进行对应的构造函数初始化。
9.将内存地址赋值给栈内存中的变量p
p.setName("lisi");
1.在栈内存中开辟setName方法的空间,里面有:对象的引用this,临时变量name
2.将p赋值给this,this就指向了堆中调用该方法的对象。
3.将“lisi”赋值给临时变量name
4.将临时变量的值赋给this的name
Person.showCountry();
1.在栈内存中,开辟showCountry()方法的空间,里面有:类名的引用Person。
2.Person指向方法区中的Person类的静态方法区的地址。
3.调用静态方法区中的country,并输出。
注意:要想使用类中的成员,必须调用。通过什么调用?有:类名,this,super
1 class Person 2 { 3 private String name; 4 private int age=0; 5 private static String country="cn"; 6 Person(String name,int age) 7 { 8 this.name=name; 9 this.age=age; 10 } 11 static 12 { 13 System.out.println("静态代码块被执行"); 14 } 15 { System.out.println(name+"..."+age); } 16 public void setName(String name) 17 { 18 this.name=name; 19 } 20 public void speak() 21 { 22 System.out.println(this.name+"..."+this.age); 23 } 24 public static void showCountry() 25 { 26 System.out.println("country="+country); 27 } 28 } 29 class StaticDemo 30 { 31 static 32 { 33 System.out.println("StaticDemo 静态代码块1"); 34 } 35 public static void main(String[] args) 36 { 37 Person p=new Person("zhangsan",100); 38 p.setName("lisi"); 39 p.speak(); 40 Person.showCountry(); 41 } 42 static 43 { 44 System.out.println("StaticDemo 静态代码块2"); 45 } 46 }
输出结果:
StaticDemo 静态代码块1
StaticDemo 静态代码块2
静态代码块被执行
null...0 //构造代码块
lisi...100 //speak()
country=cn //showCountry()
- 匿名对象
特点:对方法或者对象只进行一次调用,可作为实际参数进行传递,只在堆里面开辟存储区域。只能使用一次,使用完就被销毁了。
new Person(); //表示匿名对象,没有名字的对象 new Person().age = 17; //使用一次之后就被销毁了
- this关键字
this :代表对象,就是所在函数和所属对象的引用。
this到底代表什么呢?
哪个对象调用了this所在的函数,this就代表哪个对象,就是哪个对象的引用。
开发时,什么时候使用this呢?
在定义功能时候,如果该功能内部使用到了调用该功能的函数,这时就用 this 来代表这个对象。
this还可以用于构造函数之间的调用。
注意:用this调用构造函数,必须定义在构造函数的第一行,因为构造函数是用于初始化的,所以初始化动作一定要执行,否则编译失败。
- 封装
封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:将变化隔离,便于使用,提高重用性和安全性。
封装原则:将不需要对外提供的内容隐藏起来,把属性都隐藏,提供公共的方法对其访问。
封装机制在程序中的体现是:把描述字段的状态用字段表示,描述对象的行为用方法表示,把字段和方法定义到一个类中,并保证外界的字段不能任意的更改其内部的字段值,也不允许任意的调动其内部的功能方法。
- 访问修饰符
- 继承
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
好处:1.提高了代码的复用性。2.让类和类之间产生了关系,提供了另一个特征多态的前提。
父类的由来:其实是由多个类不断的向上抽取共性的内容而产生的。
java中只支持单继承,但是保留了这种多继承的机制,进行改良。
java为什么不支持多继承呢?
因为当一个类同时继承两个父类时,两个父类中有相同的功能,那么子类对象调用该功能的时候,运行哪一个呢?因为父类方法中存在方法体。
java支持多重继承,A继承B,B继承C,C继承D。
多重继承的出现,就有了继承体系,体系中的顶层父类是通过不断的向上抽取而来的,它里面定义了该体系最基本最共性内容的功能。
所以一个体系想要被使用,直接查阅该体系中顶层父类的功能就能知道该体系最基本的用法,那么想要使用一个体系的时候,需要建立对象。建议建立最子类的对象,因为最子 类不仅可以使用父类的功能,还可以使用子类特有的一些功能。
简单说,对于一个继承体系的使用,查阅顶层父类中的内容,创建最底层子类的对象。
子类实例化的过程
在继承操作中,对于子类对象实例化。子类对象在进行实例化之前必须首先调用父类的构造方法之后,在调用自己的构造方法。
- super()关键字和调用父类构造方法
子父类出现后,类中的成员有哪些特点:
1.成员变量
当子父类出现一样的属性时,子类类型的对象,调用该属性,值是子类的属性值。
如果想要调用父类中的属性值,需要使用一个关键字super()。
this 代表是本类类型的对象的引用。
super 代表子类所属父类中的内存空间的引用。
注意:子父类中通常是不会出现同名成员变量的,因为父类中只定义了,子类中就不用在定义了,直接继承过来就行。
2.成员函数
当子父类中出现一模一样的方法时,建立子类对象会运行子类中的方法,好像父类中的方法被覆盖掉一样。这个特性叫覆盖(复写,重写)
什么时候使用覆盖呢?当一个类的功能内容需要修改时,可以通过覆盖来实现,
3.构造函数
发现子类构造函数运行时,先运行了父类构造函数,为什么呢?
原因:子类的所有构造函数的第一行,其实都有一条隐身的语句super()
super()表示父类构造函数,并会调用于参数相对应的父类中的构造函数,而super()是在调用父类中空参数的构造函数。
为什么子类对象初始化的时候都需要调用父类的函数?
因为子类继承父类,会继承父类这中的数据,所以要看父类是如何对自己的数据进行初始化的,所以子类在进行初始化时,先调用父类构造函数。
注意:子类中所有的构造函数都会访问父类中空参数的构造函数,因为每一个子类的构造内第一行都有默认的语句super()。
如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super()语句指定要访问的父类中的构造函数。
如果子类构造函数中用this来指定调用自己的构造函数,那么被调用的构造函数一样会访问父类的空参构造函数。
问题:super()和this()是否可以同时出现在构造函数中?
两个语句都只能定义在第一行里,所以只能同时出现一个。
super()或者this()为什么一定要定义在第一行?
因为super()和this()都是调用构造函数,构造函数用于初始化,所以初始化动作要先完成。
继承的细节:
什么时候使用继承呢?
当类与类之间存在着所属关系时,才具备了继承的前提,a是b的一种,a继承b,狼是犬科中的一种,所属关系 “is a”。
注意:不仅仅是为了获取其他类中的已有成员进行继承。
判断所属关系,简单看,如果继承后,被继承中的类中的功能,都可以被子类所具备,那么继承成立,如果不是,不可以继承。
在方法覆盖时,要注意两点。
1.子类覆盖父类时,要必须保证,子类方法的权限必须大于等于父类方法的权限才可以继承,否则,编译失败。
2.覆盖时,要么都静态,要么都不静态。(静态只能覆盖静态,不静态只能覆盖不静态)
- 多态
函数本身就具备多态性,某一种事物有不同的具体的体现。
体现:父类引用或者接口的引用指向了自己的子类对象。
多态的好处:提高了程序的扩展性。
多态的弊端:当父类引用指向子类对象时,虽然提高了扩展性,但是只能访问父类中的具体方法,不可以访问子类中的特有方法。
多态的前提:1.必须要有关系,比如继承或者实现。 2.通常会有覆盖操作。
多态是一种运行期的行为,不是一种编译期的行为。
编译时的类型由声明该变量时使用的类型决定,运行时的类型有实际赋给变量的对象决定,如果编译时的类型和运行时类型不同,就出现了多态。
实现多态的机制
父类的引用变量可以指向子类的实例对象,而程序调用的方法在运行时期才能动态的绑定,就是引用变量所指向真正实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
如果想用子类对象的特有方法,如何判断对象是哪个具体的子类类型呢?
可以通过一个关键字:instanceof //判断对象是否实现了指定的接口或继承了指定的类。
格式 <对象 instanceof 类型> ,判断一个对象是否所属于指定的类型。
Student instanceof Person = true;//student 继承了 person 类
多态在子父类中成员上体现的特点:
1.成员变量:在多态中子父类成员变量同名。
编译时期:参考的是引用型变量所属的类中是否有调用的成员。(编译时不产生对象,只检查语法错误)
运行时期:也是参考引用型变量所属的类中是否有调用的成员。
再说的更容易记忆一些,成员变量 ----编译运行都看 = 左边
2.成员函数
编译时期:参考引用型变量所属的类中是否有调用的方法。
运行时期:参考的是对象所属的类中是否有调用的方法。
为什么是这样的呢?因为在子父类中,对于一模一样的成员函数,有一个特性:覆盖。
简单一句:成员函数,编译看引用型变量所属的类,运行看对象所属的类。
数 更简单:成员函数 --- 看 编译看 = = 看 左边,运行看 = = 右边。
3.静态函数。
编译时期:参考的是引用型变量所属的类中是否有调用的成员。
运行时期:也是参考引用型变量所属的类中是否有调用的成员。
为什么是这样的呢? 因为静态方法,其实不所属于对象,而是所属于该方法所在的类。
调用静态的方法引用是哪个类的引用调用的就是哪个类中的静态方法。
数 简单说:静态函数 --- 看 编译运行都看 = =