一、概述
这里的对象泛指现实中一切事物,每种事物都具备自己的属性和行为。面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。
它区别于面向过程思想(POP),强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。
面向对象的语言中,包含了三大基本特征,即封装、继承和多态。
二、类和对象
什么是类
类是一类具有相同特征的事物的抽象描述,是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性和行为特征来描述该类事物。
属性:就是事物的状态信息。对比到java中可以看定为一类事物具有的参数属性。
行为:就是事物可以做什么。对比到java中可以看定为一类事物可调用的方法。
什么是对象
对象是一类事物的具体化,是类的一个实例,具备该类事物的所有属性和行为
比如猫
属性:姓名、体重、种类
行为:吃、睡、跑
三、类的定义和对象的创建
Java中用class描述事物:
成员变量:对应事物的属性
成员方法:对应事物的行为
类的定义格式
// 类的声明
public class ClassName {
//成员变量,在类中,方法外
//成员方法 ,在类中,方法外
}
对象的创建
new 类名()//也称为匿名对象
//给创建的对象命名
//或者说,把创建的对象用一个引用数据类型的变量保存起来
类名 对象名 = new 类名();
而对象中存储的是什么?是对象地址。
class Student{
}
public class TestStudent{
//Java程序的入口
public static void main(String[] args){
System.out.println(new Student());//Student@7852e922
Student stu = new Student();
System.out.println(stu);//Student@4e25154f
int[] arr = new int[5];
System.out.println(arr);//[I@70dea4e
}
}
//Student和TestStudent没有位置要求,谁在上面谁在下面都可以
//但是如果TestStudent类的main中使用了Student类,那么要求编译时,这个Student已经写好了,不写是不行的
//如果两个类都在一个.java源文件中,只能有一个类是public的
可以看出,类对象和数组对象在直接打印对象名时都显示“类型@对象的hashCode值",所以说类和数组都是引用数据类型,引用数据类型的变量中存储的是对象的地址,或者说指向堆中对象的首地址。
四、成员变量
1、成员变量的分类
实例变量:也叫对象属性,属于某个对象的,通过对象来使用
类变量:也叫类变量,属于整个类的,不是属于某个实例
2、实例变量的使用
(1)实例变量在本类的实例方法中,直接使用
class Circle{
double radius;
public double getArea(){
return 3.14 * radius * radius;
}
}
(2)实例变量在其他类的方法中,需要通过“对象名.实例变量"的方式使用,new一个实例对象
public class TestCircle{
public static void main(String[] args){
Circle c = new Circle();
System.out.println("c这个圆的半径是:" + c.radius);
//修改c这个圆的半径
c.radius = 1.2;
System.out.println("c这个圆的半径是:" + c.radius);
}
}
五、成员变量和局部变量的区别
1、作用范围不一样
成员变量:类中直接使用,其他类通过“对象名.实例变量"的方式使用
局部变量:在当前方法中使用
2、初始化值的不同
成员变量:有默认值
局部变量:无默认值,但定义时需实例化
3、在内存中的位置不同
成员变量:堆内存
局部变量:栈内存
4、生命周期不同
成员变量:随着对象的创建或类的加载而存在,随着对象的消失而消失;没有创建对象,就不会在堆内存中分配内存地址
局部变量: 随着方法的调用而存在,随着方法的调用完毕而消失;方法没有被调用,局部变量不会在栈中分配内存,调用一次分配一次
六、成员方法
1、概念
方法也叫函数,是一个独立功能的定义,是一个类中最基本的功能单元。
把一个功能封装为方法的目的是,可以实现代码重用,从而简少代码量。
2、方法的原则
1.必须先声明后使用
2.不调用不执行,调用一次执行一次,调用时,在栈中压入一个方法栈
3、成员方法的分类
实例方法:属于对象的方法,由对象来调用
静态方法:类方法,属于整个类,不是属于某个实例,由类名来调用。
实例方法的定义格式
修饰符 返回值类型 方法名(参数列表:参数类型1 参数名1,参数类型2 参数名, ...... ){
方法体;
return 返回值;
}
实例方法的调用:在其他方法中被调用。对象名.方法名(参数);
形参:在定义方法时方法名后面括号中的变量名称称为形式参数(简称形参),即形参出现在方法定义中。
实参:在调用方式时,需要对方法中需要的参数进行输入,即输入实际参数参与所调用方法的执行。
练习:声明圆类
包含实例变量redius为圆的半径
包含实例方法getArea求圆的面积
包含实例方法getPerimeter求圆对象的周长
包含实例方法getInfo获取圆对象的详细信息
public class Main{
public static void main(String[] agrs) {
Circle c = new Circle();
c.redius = 2.0;
System.out.println(c.getInfo());
}
}
Class Circle{
pravite double redius;
public double getArea() {
return 3.14*redius*reduis;
}
public double getPerimeter() {
return 3.14*2*redius;
}
public String getInfo() {
return "半径:" + radius + ",面积:" + getArea() + ",周长:" + getPerimeter();
}
}
七、可变参数
我们在定义一个方法时,如果形参的个数不确定,那么就可以使用可变参数
八、方法重载
方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
参数列表:数据类型个数不同,数据类型不同,数据类型顺序不同。
重载方法调用:JVM通过方法的参数列表,调用不同的方法。
我们看一下下段代码:
class Test06_Overload_Problem2{
public static void main(String[] args){
Count c = new Count();
System.out.println(c.sum(1,2));//(int a, int b)
System.out.println(c.sum(1,2,3));//(int... args)和(int a, int... args)都兼容,就有问题了
}
}
//Count类编译没问题,但是调用者有问题
class Count{
public int sum(int a, int b){
return a+b;
}
public int sum(int... args){
int sum = 0;
for(int i=0; i<args.length; i++){
sum += args[i];
}
return sum;
}
public int sum(int a, int... args){
int sum = a;
for(int i=0; i<args.length; i++){
sum += args[i];
}
return sum;
}
}
九、方法的参数传递机制
方法的形参是基本数据类型时,形参值的改变不会影响实参;
方法的形参是引用数据类型时,形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参,例如,修改数组元素的值,或修改对象的属性值。
机制一、
public class ParameterTransfer {
public static void main(String[] args) {
int num = 30;
System.out.println("调用add方法前num=" + num); // 30
add(num);
System.out.println("调用add方法后num=" + num); // 30
}
public static void add(int param) {
param = 100;
}
}
我们可以从内存模型来进行分析:
当执行了int num = 30;语句后,JVM在栈内存中开辟一块地址为0X111的内存,里面存放的值为30;当执行add(num);后,JVM又在栈内存中开辟一块地址为0X222的内存,先存放的是30,执行完param = 100;后,0X222的值变成100。但无论怎么改变,0X222中的param值与0X111中的num值没有关系,所以怒骂值不会改变。
机制二、
public class ParameterTransfer {
public static void main(String[] args) {
String[] array = new String[] {"huixin"};
System.out.println("调用reset方法前array中的第0个元素的值是:" + array[0]);
reset(array);
System.out.println("调用reset方法后array中的第0个元素的值是:" + array[0]);
}
public static void reset(String[] param) {
param[0] = "hello, world!";
}
}
当传递的是引用数据类型时,结果就不一样了。
当程序执行了String[] array = new String[] {"huixin"}后,JVM在栈内存中开辟了一块地址编号为AD9500内存空间,用于存放array[0]的引用地址,里边放的值是堆内存中的一个地址,示例中的值为BE2500,可以理解为有一个指针指向了堆内存中的编号为BE2500的地址。堆内存中编号为BE2500的这个地址中存放的才是array[0]的值:huixin。
当程序进入reset方法后,将array的值,也就是对象的引用BE2500传了进来。这时,程序在栈内存中又开辟了一块编号为AD9600的内存空间,里边放的值是传递过来的值,即AD9600。可以理解为栈内存中的编号为AD9600的内存中有一个指针,也指向了堆内存中编号为BE2500的内存地址。
而在方法reset中修改了param[0]的值为"hello, 改变了world!"时,实际上则是改变了param AD9600指向堆内存中的地址BE2500内的值。而array AD9500同样指向堆内存BE2500,因此会发生改变。
而同样这个问题也可以看作为对象类型的变量。即String 为new出的对象;但当String为直接赋值而不是new时,则可以看作是基本数据类型。然而String类型是不可变,即创建就不可修改。
https://blog.51cto.com/freej/168676
十、对象数组
数组是用来存储一组数据的容器,一组基本数据类型的数据可以用数组装,那么一组对象也可以使用数组来装。当元素是引用数据类型是,我们称为对象数组。
我们首先要创建对象数组,并且确定数组的长度。否则容易出现空指针异常。
我们通过代码来分析:
Class Preson {
private String name;
private int age;
private double length;
}
public class Main{
public static void main(String[] args) {
Preson[] preson = new Preson[4];
// 数组不需要初始化,因为数组会自动初始化为null
for(int i = 0; i < preson.length; i++) {
preson[i] = new Preson(); // 定义了属性类型为Preson,里面的元素只能存放Preson对象或子类对象
preson[i].name = "name" + i;
preson[i].age = i;
preson[i].length = 100.0 +i;
}
}
}
我们来看一下代码的内存模型