类与对象基础
1、面向对象概述
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一一实现,使用的时候依次调用就可以了。
面向对象则是把构成问题的事务按照一定规则划分为多个独立的对象,然后通过调用对象的方法来解决问题。当然,一个应用程序会包含多个对象,通过多个对象的相互配合即可实现应用程序所需要的功能,这样当应用程序功能发生变动时,只需要修改个别的对象就可以了。
面向对象的特点可以概括为:封装、继承和多态。
笔记整理思路?
why?为什么会出现
what?具体是什么
how?怎样做
封装
封装是面向对象的核心思想,将对象的属性和行为封装起来,不需要让外界知道具体实现细节
继承
继承主要描述的就是类与类之间的关系,通过继承,可以在无须重新编写原有类的情况下,对原有类的功能进行扩展
多态
多态指的是在一个类中定义的属性和功能被其他类继承后,当把子类对象直接赋值给父类引用变量时,相同引用类型的变量调用同一个方法所呈现出的多种不同行为特性
2、类 ,与对象
类用于描述多个对象的共同特征,他是对象的模板;而对象用于描述现实中的个体
2.1、类
类是构造对象的模板或蓝图
由类构造(construct)对象的过程称为创建类的实例(instance)
2.2、对象
对象的几个重要概念:
- 对象的行为----可以对对象施加哪些操作,或可以对对象世家哪些行为?
- 对象的状态–当世家那些方法时,对象如何响应?
- 对象的标志–如何辨别具有相同行为与状态的不同对象
2.3、类之间的关系(这部分可参考设计模式)
在类之间,最常见的关系有:
- 依赖(“use -a”)
- 聚合(“has-a")
- 继承(“is-a”)
依赖:如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另外一个类
聚合:例如,一个Order对象包含一些Item对象。聚合意味着类A对象包含类B的对象
继承:为了实现复用代码
2.4、对象与对象变量
要想使用对象,就必须首先构造对象,并指定其初始状态。然后,对对象应用方法
创建对象
Date birthday = new Date();
在对象与对象变量之间有一个重要的区别:
- 对象变量不是一个对象,实际上也没有引用对象。不能将任何方法应用于这个变量上
- 必须首先初始化变量;可以用新构造的对象初始化这个变量,也可以让这个变量引用一个已存在的对象
一个对象变量并没有实际包含一个对象,而是仅仅应用一个对象
若对一个没有引用任何对象变量进行操作则会报空指针异常错误
2.5、JVM内存分析
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动和结束而建立和销毁
2.5.1、程序计数器(类似于80X86中的PC指针)
程序技术器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器,字节码解释器工作时,就是通过改变这个技术器的值来选取吓一跳需要执行的字节码指令。
为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为"线程私有"的内存
如果线程正在执行的是一个Java方法,这个技术器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器的值为空。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemory情况的区域。
2.5.2、Java虚拟机栈
与程序技术器一样 ,Java虚拟机栈也是线程私有的,它的生命周期与线程相同。
虚拟机栈你描述的是Java方法执行的内存模型:每个方法再执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
局部变量表中存放来了编译期可知的各种基本数据类型(boolean、byte、short、long、float、double、char)、对象引用(referencxe类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)
其中64位长度的long和double类型的数据只占用2个局部变量空间(Slot),其余的数据类型只占用一个。局部变量所需的内存空间在编译期间完成,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变、
可能会抛出:*Error和OutOfMemoryError
2.5.3、本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。本地方法。
可能会抛出:*Error和OutOfMemoryError
2.5.4、Java堆
对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理内存中最大的一块。Java堆是被所有线程所共享的一块内存区域,在虚拟机启动是自动创建。==此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。==这一点在Java虚拟机规范中的描述是:所有对象实例及数组都要在堆上分配
Java堆是垃圾收集管理的主要区域、
从内存回收的角度来看,由于现在收集器基本都采用分代收集算法:所以Java堆中还可以细分为:新生代和老年代、
根据Java虚拟机规范的规定,Java堆上可以处于物理上不连续的内存空间中,只要逻辑上是连续的菊科,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是扩展的。
2.5.5、方法区
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的 类信息、,常量,静态变量、,即时编译后的代码等数据。
运行时常量池是方法区的一部分。Class文件中除有嘞的版本信息、字段、方法、接口、静态变量等描述信息外,还有常量池(用于存放编译期生成的各种字面量和符号引用)
JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池
2.6、JVM创建对象内存模型分析
Java将内存分成两种,即栈内存和堆内存。其中栈内存用于存放基本类型的变量和对象引用(如Person p),堆内存用于存放由new创建的对象和数组
/**
方法区内存:在类加载对象时,class字节码代码片段被加载到该内存空间当中
栈内存(局部变量):方法代码片段执行的时候,会给gauge方法分配内存空间,在栈内存中压栈
堆内存:new 对象在堆内存中存储
局部变量在栈内存中存储;
成员变量中的实例变量在堆内存的java对象内部存储
实例变量是一个对象一份,100个对象有100份
**/
什么是对象?new运算符在堆内存中开辟的内存空间称为对象
什么是引用?引用时一个变量,只不过这个变量中保存了另外一个对象的地址
java语言当中,程序员不能直接操作堆内存,java中么有指针,不像C语言
java语言当中,程序员只能通过“引用”去访问堆内存中对象内部的实例变量
2.6.1、对象的创建JVM分析
Java是一门面面向对象的编程语言,在Java程序运行过程中无时无刻都有对象被创建出来。在语言层面,创建对象(例如克隆,反序列化)通常仅仅是一个New关键字而已,而在虚拟机中,独享的创建又是怎样一个过程呢?
- 虚拟机遇到一条new指令时,首先将去检查这个指令的参数书否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过,如果没有,那必须先执行相应的类加载过程
- 在类加载检查通过后,接下来虚拟机将为新生对象分配内存(在堆上)。对象所需内存的大小在类加载完成后便可确定。为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来
- 内存分配完成后,虚拟机将分配到的内存空间都初始化为零值、
- 对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中。根据虚拟机当前状态的不同,如是否启用偏向锁等,对象头会有不同的设置;到此,一个新的对象已经产生,但从Java程序的视角,对象的创建才干那个刚开始=
- 执行方法,把对象按照程序员的医院进行初始化,这样一个真正可用的对象才算完全生产出来。
2.6.2、对象的内存布局
对象的内存中存储的布局可以分为3块区域:对象头(Header),实例数据(Instance Data)和对齐填充。
虚拟机的对象头包括两部分信息
第一部分用于存储对象自身的运行时数据:如哈希码,GC分代年龄、锁状态标志,线程所持有的锁,偏向线程ID.
第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
实例数据部分是对象真正存储的有效信息,也就是在程序代码中所定义的各种类型的字段内容、无论是从父类继承下来的,还是在子类中定义的,都需要记录。
2.6.3、对象的定位访问
使用对象时,通过栈上的reference数据来操作堆上的具体对象
通过句柄访问
Java堆中会分配一块内存作为句柄池,refence存储的是句柄地址
使用直接指针访问
2.6.4、实例分析1
定义一个User类,其成员含有基本数据类型和对象
/**
* @Author Zhou jian
* @Date 2019 ${month} 2019/12/30 0030 11:22
*/
public class User {
int no;
String name;
//家庭住址
Address addr;
}
Address类为
/**
* @Author Zhou jian
* @Date 2019 ${month} 2019/12/30 0030 11:20
*/
public class Address
{
String city;
String street;
//邮编
String zipCode;
}
测试方法为
public static void main(String[] args) {
//创建user对象
//u是局部变量
//u是一个引用
//u保存指向堆内存地址的引用
User user = new User();
user.name="jack";
user.no = 110;
user.addr = new Address();
user.addr.city="北京";
user.addr.street="朝阳";
user.addr.zipCode="111111";
}
其内存结构分析如下
传对象引用的分析
public static void main(String[] args) {
//u是引用
//u是局部变量
User u = new User();
//上一个版本中写的
//u.addr = new Address;
//a是引用
//a是局部变量
Address a = new Address();
u.addr = a;
a.city = "天津";//通过修改引用的对象,下面一个输出的影响
System.out.println(u.addr.city);//天津
}
2.6.5、实例分析2–对象的互相引用
妻子类
public class wife {
String name;
//妻子对象当w中含有丈夫引用
Husband husband;
}
丈夫类
public class Husband {
String name;
//丈夫对象当w中含有妻子引用
wife w;
}
测试类
public class OOTEST04 {
public static void main(String[] args) {
//创建一个丈夫对象
Husband husband = new Husband();
husband.name="黄晓明";
//创建一个妻子对象
wife w = new wife();
w.name="baby";
//结婚(能通过丈夫找到妻子)
husband.w=w;
w.husband=husband;
//得到以上丈夫妻子的名称
System.out.println(husband.w.name);
}
内存分析图
3、自定义类
3.1、类的形式
class ClassName{
field1;
field2;
.....
construct1;
construct2;
.....
method1;
method2;
}
3.2、属性
3.2.1、属性的访问控制权限
一般情况下处于封装的考虑,域的访问控制权限设置为private
属性的初始化值
构建对象的过程中,若没有对属性进行赋值操作或更改属性的值,那么这些成员变量为初始化值:==数值型初始化值为0,布尔值初始化值为false,类对象的初始化值为Null==
3.2.2属性的操作
往往需要获得或设置实例域的值。因此,应该提供下面的三项内容:
- 一个私有的数据域
- 一个公有的域访问器方法
- 一个公有的域更改器方法
当然为了进行新旧数据之间的转换,访问器方法和更改器方法有可能需要做很多工作。这会带来:更改器方法可以执行错误和检查,然而直接对域赋值就不会进行这些处理
note:
不要编写返回引用可变对象的访问器方法;如果需要返回一个可变对象的引用,就应该首先对它进行克隆。对象Clone是指存放在另一个位置上对象的副本
3.3、方法
c成员方法也成称为方法,它主要用于描述对象的行为。
3.3.1、方法执行内存分析
- 在栈帧存放基本数据类型和对象的引用
- 在堆中存放对象和数组
- 在方法区中存放类的相关信息
Demon01
**
* @Author Zhou jian
* @Date 2019 ${month} 2019/12/30 0030 16:15
*/
public class MethodTest01 {
public static void main(String[] args) {
int a = 10;
int b = 20;
int returnValue = sumInt(a,b);
System.out.println(returnValue);
}
public static int sumInt(int i,int j){
int result = i+j;
int num =3;
int returnValue = divide(result,num);
return returnValue;
}
public static int divide(int x,int y){
int z = x/y;
return z;
}
}
其内存模型如下
Demono2
public static void main(String[] args) {
int i =10;
method(i);
System.out.println(i);
}
public static void method(int i)
{
i++;
System.out.println(i);
}
Demon03
Student类
public class Student {
int no;
int age;
boolean sex;
String name;
String addr;
}
测试方法
public static void main(String[] args) {
Student s = new Student();
s.no = 10;
s.age = 20;
s.sex = true;
s.name="jack";
s.addr = "北京";
Student stu = new Student();
}
内存模型分析
3.3.2、语法格式
[修饰符][返回值类型]方法名( [参数类型 参数名],[参数类型 参数名] )
-
修饰符:有对访问控制权限进行限定的(如public,protected,private,default),有静态修饰符static,还有最终修饰符final
-
返回值类型:用于限定方法返回值的数据类型,如果不需要返回值,可以使用void关键字
-
参数类型:用于限定调用方法时传入参数的数据类型
-
参数名:是一个变量,泳衣接手调用方法时传入的数据7
-
return关键字:用于结束方法以及返回方法指定类型的值;我们就可以使用return结束方法
方法访问控制权限的选择
在实现一个类时,由于公有数据非常危险,所以应该将所有的数据域都设置为私有的。
然而方法又该如何设计呢?
绝大多数情况下,方法都会被设计公有的。有时希望将他们设计为私有的。有时可能就昂一个计算代码划分成若干个独立的辅助反方。通常这些辅助方法不应该成为公有接口的一部分
3.3.3、方法的值传递
按值传递表示方法接收的是调用者提供的值。
按引用传递表示方法接收的是调用者提供的变量地址。
一个方法可以修改传递引用所对应的变量值;不能修改传递值调用所对应的变量值。
Java程序设计语言总是采用按值调用。也就是说,得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容
基本数据类型的传递
public static void main(String[] args) {
TestValue testValue = new TestValue(10);
System.out.println("调用值传递之后x的值"+testValue.x);//10
testValue.enlarge(testValue.x);
System.out.println("调用值传递之后x的值"+testValue.x);//10
}
private int x;
public TestValue(int x) {
this.x = x;
}
public void enlarge(int x){//传递变量为基本数据类型
x = x*10;
}
执行过程
- x被初始化为testValyue.x的值的拷贝
- x被乘以10后为100
- 这个方法结束后,参数变量x不再使用
参数为对象
Employee employee = new Employee("zhouhjian",1000f,1996,2,5);
System.out.println("值传递之前的数据"+employee.getSalary()); //值传递之前的数据1000.0
employee.tripleSalary(employee);
System.out.println("值传递之后的数据"+employee.getSalary()); //值传递之后的数据1100.0
public void raiseSalary(double byPercent){
double raise = salary*byPercent/100;
salary +=raise;
}
public void tripleSalary(Employee e){//传递对象的引用
e.raiseSalary(200);
}
具体的执行过程分析如下:
- e被初始化为employee值的拷贝,这里是一个对象的引用
- raiseSalary方法应用于这个对象的引用。x和employee同时引用的那个Employee对象的薪金提高了200%
- 方法结束后,参数变量x不再使用。当然,对象变量harry继续引用那个薪金增加至3被雇员队形
小结:基本数据类型拷贝的是数据就是值,而对象数据类型拷贝的是引用变量的值(不可更改的是引用变量的值,答案是可以操作所引用的对象
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
- 一个方法可以改变对象参数的状态
- 一个方法不能让对象参数引用一个新的对象
3.3.4、方法的重载
为了解决不同情况下放大的执行,Java允许在一个程序中定义多个名称相同,但是参数或类型个数不同的方法=,这就是方法的重载
需要注意的是,方法的重载与返回值类型无关;它只需要满足两个条件:一是方法名相同,二是参数个数或类型不同
3.3.5、静态方法
静态方法是一种不能像对象施加操作的方法。
静态方法不能访问实例域,因为他不能操作对象,但是静态对象可以访问自身类中的静态域
3.3.6、最终的方法
3.4、构造器
构造器作用
在构造对象时。构造器就会运行,以便将实例域初始化为所希望的状态。(对象的创建必须经过构造器)
初始化域的属性
构造器语法
[修饰符] 方法名 ([参数列表]){
方法体
}
上述语法格式所定义的构造方法需要满足以下三个条件
- 修饰符:一般为public,但也可以使用private(在单例模式中就使用到了)
- 方法名与类名相同
- 在方法名前没有返回值类型的声明
- 在方法中不能使用return语句返回一个值,但是可以单独写return语句来作为方法的结束
- 每个类可以有一个以上的构造器
- 构造器可以有0个、1个或多个参数
- 构造器器总是伴随着new操作一起调用
3.4.1、构造器的重载
- Java中每个类都至少有一个构造方法,如果在一个类中没有显示的定义构造方法,系统会自动为这个类创建一个默认的构造方法。
- 当我们自己写一个构造方法后,系统就不再为我们提供默认构造方法此时若调用无参构造器往往报错,因此可以自己再写一个无参构造器
3.4.2、构造器的调用
关键字this应用法不能发的隐士参数,然而这个关键字i还要另外一个含义
如果构造器的第一个语句形如this(…),,这个构造器将调用同一个类的另外一个构造器奥。
采用这种方式使用this关键字非常有用,这样对公狗的构造器代码部分只编写一次即可
在使用this调用类的构造方法时,应注意以下几点:
- 只能在构造方法中使用this调用其他构造方法,不能在成员方法中使用
- 在构造方法中,使用this调用构造方法的语句必须是该方法的第一条执行语句,且只能出现一次
3.4.3、初始化块
龙宫三种初始化数据域的方法:
- 在构造器中设置值
- 在声明中赋值
- 初始化块,在一个类的声明中可以包含多个代码块。只要构造类的对像,这些块就会执行。
由于初始化数据域有多种途径,所以列出构造过程的所有途径可能相当混乱。下面是调用构造器的具体处理步骤
- 所有数据域被初始化为默认值(0、false或null)
- 按照在类声明中出现的次序,一次执行所有域初始化语句和初始化块
- 如果构造器第一行调用了第二个构造器,则执行第二个构造器主题
- 执行这个构造器主题
对于静态域,可以采用静态初始化块的方式
static{
//对静态变量赋值
}
3.5、访问控制符
在Java中,针对类、成员变量和属性提供了四种范文级别的 控制符,分别是private,default,protected,和,public
-
private(当前类访问级别):如果这个类的成员被private修饰,则这个成员只能被该类的其他成员访问,其他类无法直接访问。类的良好封装就是通过private关键字来实现的
-
default(包访问级别):如果一个类或者类的成员不使用任何访问控制修饰符,则它为默认访问控制级别,这个类或者类的成员只能被本包中的其他类访问
-
protected(子类访问级别):如果一个类的长远被protected访问控制符修饰,则这个成员技能被同一包下的其他类访问,也能被不同包下该类的子类访问
-
public(公共访问级别):这时一个最宽松的访问控制级别,如果一个类或者类的成员被public访问控制符修饰,那么这个类或者类的成员能被所有的类访问
4、类的封装
封装(encapsulation,有时也称为数据隐藏)是与对象有关的一个重要概念。从形式上看,封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。
对象中的数据称为实例域,操纵数据的过程称为方法。
对于特定的类实例(对象)都有一组特定的实例域值。
实现封装的关键在于绝对不让类中的方法直接访问其他类的实例域。程序仅通过对象的方法与对象数据进行交互
5、类的继承
参考继承
6、类的多态
参考多态
7、关键字
关键字的分析要从:
- 这个关键字能作用在类的哪些成员上
- 作用在类的 某个成员上的作用
- 关键字何时使用
这几个角度进行总结。
7.1、this
- 通过this关键字调用成员变量,解决与局部变量名称冲突的问题
- 通过this关键字调用成员方法
- 通过this关键字调用构造方法
7.2、static(内容存储在方法区当中)
- 修饰变量:静态变量。在定义一个类时,只有在描述某类事务的特征和行为,并没有产生具体的数据。只有通过new关键字创建该类的实例域对象后,系统才会为每个对象分配内存对象空间,存储个自己的数据。有时,希望某些特定的数据在内存空间中只存在一份,而且能够被一个类的所有实例对象共享
- ==修饰方法:静态方法 。==希望在不创建对象的情况下就可以调用某个反复噶吧,这种情况下就可以使用静态方法。在一个静态方法中只能访问用static修饰的成员,原因在于没有被static修饰的成员需要先创建对象才能访问
- ==修饰代码块:静态代码块。==当类被加载时,静态代码块会执行,由于类只能加载一次,因此静态代码块也只执行一次,在程序中,通常会使用静态代码块来对类的成员变量进行初始化。
上述的所有static修饰的内容存储在方法区当中
7.3、super
7.4、final
上一篇: 轻便爬虫+OCR 第二部分
推荐阅读
-
python基础知识(安装、数据类型、list列表、字典、函数、类、继承、文件操作)
-
在OneProxy的基础上实行MySQL读写分离与负载均衡
-
PHP学习记录之面向对象(Object-oriented programming,OOP)基础【类、对象、继承等】
-
PHP学习记录之面向对象(Object-oriented programming,OOP)基础【接口、抽象类、静态方法等】
-
java面向对象基础_final详细介绍
-
C#基础语法:结构和类区别详解
-
Javascript创建类和对象详解
-
Java抽象类概念与用法实例分析
-
mysql 基础教程之库与表的详解
-
JVM核心教程之JVM运行与类加载全过程详解