运用数据抽象的思想编写代码(定义和使用数据类型,将数据类型的值封装在对象中)的方式称为面向对象编程。
数据类型指的是一组值和一组对值的操作的集合。对象是能够存储任意该数据类型的值的实体,或数据类型的实例。
类与对象的关系

从类创建对象,有好几种说法——1. 创建一个对象。2. 实例化一个对象。3. 把类实例化。
对象内存布局

首先会在方法区里加载类的信息。然后对象的地址存放在栈中,真正的对象放在堆里。如果对象里的属性是基本数据类型,那么就存放在堆里,如果是引用数据类型,那么在堆中存放地址,数据存放在方法区里。
从概念或叫法上看:成员变量 = 属性 = 字段(field),属性是类的一个组成部分,一般是基本数据类型,也可是引用数据类型(对象,数组等)。
类里的方法也叫实例方法,类里的属性也叫实例变量。
内存
堆区:只存放类对象,线程共享,类中的成员变量都存放在堆区。
方法区:又叫静态存储区,存放class文件和静态数据,线程共享。
栈区:存放方法的局部变量,基本类型变量区。线程不共享。
对象创建流程
class Person {int age = 90;String name;Person(String n, int a) {name = n;age = a;}}//主函数 Person p = new Person("小王",20);
- 加载Person类信息,只会加载一次(具体可以在反射中查看)。
2. 在堆中分配空间(地址)。
3. 完成对象初始化——(1)默认初始化,age = 0,name = null。(2)显式初始化,age = 90,name = null。(3)构造器的初始化,age = 20,name = 小王。
4. 把对象在堆中的地址,返回给p(p是对象名,也可以理解成是对象的引用)。
name中的值在常量池中
对象的三个主要特性
行为:可以对对象完成哪些操作,或者可以对对象应用哪些方法。同一个类的所有对象实例,由于支持相同的行为而具有家族式的相似性。
状态:描述当前状况的信息。对象状态的改变必须通过调用方法实现。
标识:每个对象都有一个唯一的标识,用于区分具有相同行为与状态的不同对象(比如在订单系统中,即使两个订单货物完全相同,那也是不同的订单)。类之间的关系
依赖(uses-a):如果一个类的方法使用或操纵另一个类的对象,我们就说一个类依赖于另一个类。应该尽可能地将相互依赖的类减至最少。
聚合(has-a):类A的对象包含类B的对象。
继承(is-a):如果类A继承类B,那么类A不但包含从类B继承的方法,还会有一些额外功能。(比如有一个RushOrder类,继承自Order类,包含了一些用于优先处理的特殊方法,而其他的一般性方法都继承自Order类)包
包的作用
1. 区分相同名字的类,可以在不同的包中定义相同名字的类。
2. 当类很多时,可以很好的管理类(类似Java API文档)。
3. 控制访问范围。基本语法
其中package为关键字,表示引入包。com.hespedu表示包名。package com.hspedu;
本质分析
包实际上就是创建不同的文件夹来保存类文件。
创建一个包:

com.xiaoming,其中这个 ‘.’ 表示分级,com为一级目录,xiaoming为二级目录。
举例:import com.shang.Person;public class HelloWorld {public static void main(String[] args) {Person a = new Person(); //导入的可以直接使用com.xiaoming.Person b = new com.xiaoming.Person();//没有导入就要写全名字a.showName();b.showName();}}
去除import后自动隐藏

把 “Optimize imports on the fly” 取消即可。命名
命名规则:
只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字。
命名规范:
com.公司名.项目名.业务模块名,全部用小写字母。com.sina.crm.uese; //用户模块com.sina.crm.order; //订单模块com.sina.crm.utils; //工具类
常用包
包的使用细节
- package语句的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package。
2. import指令放在package的下面,在类定义前面,可以有多句并且没有顺序要求。我们用 import 引入包,主要是为了使用包中的类。不建议使用 * 引入全部的类。访问修饰符
访问范围


注意子类说的是不同包里的子类,同一个包里的子类是可以访问父类的默认属性的。public class A{protected String name = "ss"; //保护String age = "18"; //默认}//在不同包public class B extends A{public static void main(String[] args){B b = new B(); //可以访问 name, 但是不可以访问 age}}
注意事项
- 修饰符可以用来修饰类中的 属性,成员方法以及类。成员方法与属性都符合上图的范围。
2. 只有默认和public才能修饰类,默认类只有在同一个包中才能调用,在不同包中,即使使用了import导入该类,也不能用。而public修饰的类不仅在本包中可以调用,也可以在不同包中进行调用,但是需要import导入类。按值调用与按引用调用
按值调用表示方法接收的是调用者提供的值。按引用调用表示方法接收的是调用者提供的变量地址。 方法可以修改按引用传递变量的值,而不能修改按值传递变量的值(其实就是C++的形参与实参的关系,引用就是加上了&)。
按值调用
假如在Employee类中加入一个方法——
public void add(int x) {x += 10;}
然后在主函数中调用,分析一下过程:1.x初始化为num的一个副本(形参,也就是10) 2.x加上10后等于20,但是num仍为10。 3.方法结束后,参数变量x不再使用。
public static void main(String[] args) {int num = 10;Employee a = new Employee(10);System.out.println("调用add函数前为:"+num);a.add(num);//这个就是按值调用System.out.print("调用add函数后为:"+num);}
按引用调用
给Employee类增加另外一个方法,这时传入一个类。
public void add(Employee one) {one.id += 10;}
在主函数进行调用——1.one初始化为a的一个副本,这里就是一个对象引用(其实就是one初始化为与a相同的指针)。 2.add方法应用于这个对象引用。one和a同时引用一个Employee对象。 3.方法结束后,参数变量one不再使用,但是a做出了修改。
public static void main(String[] args) {Employee a = new Employee(10);System.out.print("调用add函数前——");a.showId();a.add(a);System.out.print("调用add函数后——");a.showId();}

下面这个例子更能体会到按引用调用:传入实参x,y,swap会创造形参 x,y,与实参指向的对象相同,然后执行代码。我们可以发现,全程就是形参xy在互换指向的对象(理解为指针),最终形参x指向实参y指向的对象,形参y指向实参x指向的对象。但是完全没有影响到实参。
public static void swap(Employee x,Employee y) {Employee temp = x;x = y;y = temp;}
其实只要把对象参数理解为指针就OK了,它并不是真正的对象,只是指向对象,因此可以修改对应的属性,但是不能交换等。
类的设计技巧
1.一定要保证数据私有。2.一定要对数据进行初始化(最好不要依赖于系统的默认值)。 3.不要在类中使用过多的基本类型。 4.不是所有的字段都需要单独的字段访问器和字段更改器。5.分解有过多职责的类。 6.类名和方法名要能够体现它们的职责。 7.优先使用不可变的类。
this关键字
图示
Dog dog1 = new Dog("大壮",3);Dog dog2 = new Dog("大黄",2);
使用细节
- this关键字可以用来访问本类的属性、方法、构造器。
2. this可以用于区分当前类的属性和局部变量(起到一个定位作用)。
3. 访问成员方法的语法:this.方法名(参数列表); (在方法中访问)。class Person {String name = "milan";public void showName() {String name = "shang";System.out.println(name);//输出局部变量 "shang" (就近原则)System.out.println(this.name);//输出属性 "milan"}}
4. 访问构造器语法:this(参数列表),注意只能在一个构造器中访问另一个构造器,并且必须放在第一句中。class Person {Person() {this("wang");//调用另一个构造器}Person(String name) {this.name = name;}}//下面的构造器会报错Person() {System.out.println("不放在第一句");this("wang");}

5. this不能在类定义的外部(比如本类的main方法)使用,只能在类定义的方法中使用。 ```java public static void main(String[] args) { Person a = new Person(); this.name; //在主函数中调用this,报错
}
<a name="dfgu0"></a>## **super关键字**<a name="mJ2T4"></a>### **基本介绍**super代表父类的引用,用于访问父类的属性、方法、构造器。<a name="yNS7G"></a>### 基本语法**1. 访问父类的属性,但不能访问父类的private属性:super.属性名;**<br />**2. 访问父类的方法,但不能访问父类的private方法:super.方法名(参数列表);**<br />3. 访问父类的构造器(前面介绍过):super(参数列表); 只能放在构造器的第一句,只能出现一句<a name="vfZBC"></a>### 细节/好处1. **super调用父类构造器,可以使分工明确**。父类属性由父类初始化,子类的属性由子类初始化。 <br />2. **当子类中由和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super**。**如果没有重名,使用super、this、直接访问是一样的效果(只不过this和直接访问是从本类开始找,super是从父类开始找)。 调用类的属性的规则在上面已经说明过了,调用类的方法的规则与其相同,只不过把属性换成了方法。**<br />**子类确实可以写一个与父类完全相同的方法,虽然左边会有隐患提示,但是编译没有问题。**<br /><br />**下面对于子类调用父类的方法举一个例子(属性因为很简单就不举例了)**<br />定义一个父类Person:```javapublic class Person { //有四个不同修饰符的方法public void showName1(){System.out.println("调用Person的public方法");}void showName2(){System.out.println("调用Person的无修饰符方法");}protected void showName3(){System.out.println("调用Person的protected方法");}private void showName4(){System.out.println("调用Person的private方法");}}
定义一个子类Student,先测试在main方法中调用方法(必须用声明的变量调用):
public class Student extends Person{public static void main(String[] args) {Student student = new Student();student.showName1(); //成功调用student.showName2(); //成功调用student.showName3(); //成功调用student.showName4(); //报错,不能调用父类的private方法}}


然后在子类的方法中继续测试,由于 this 和 super 都不能在主方法中调用,因此声明一个test方法,在该方法中测试this和super。
public void test(){this.showName1(); //成功调用this.showName2(); //成功调用this.showName3(); //成功调用this.showName4(); //报错,不能调用private方法super.showName1(); //成功调用super.showName2(); //成功调用super.showName3(); //成功调用super.showName4(); //报错,不能调用private方法}
当然,该方法还是需要用main方法中声明的变量调用。
同样的,最重要的一点——在子类调用一个方法时,如果子类没有该方法,会一直向上寻找,如果找到Object类还没有找到,就会报错(super直接忽略本类)。如果在中间找到了一个private的方法,即使上面还有该方法,也不会再寻找了,而是直接报错。
举一个例子:
private void showName1(){ //在Student类中加入一个与Person类同名但不同修饰符的函数 System.out.println("调用Student的private函数");}
public class Pupil extends Student{ //继承Studentpublic static void main(String[] args) {Pupil pupil = new Pupil();pupil.showName1(); //直接报错,寻找到Student就停了,而不是继续向Person寻找}}

3. 如果多个父类中有相同的成员,那么super的访问根据就近原则。
4. super和this的区别
方法重写/覆盖(override)
基本介绍
方法覆盖就是子类有一个方法和父类的某个方法 名称、返回类型、参数一样,这样我们就说子类的这个方法覆盖了父类的那个方法。
使用细节
1. 子类方法的 形参列表,方法名称 要和父类的完全一样。
2. 子类方法的 返回类型 可以和父类方法的返回类型一样,或者是父类返回类型的子类(必须是自己写的继承关系,Object那种自带的继承不算)。
public Object showName(){System.out.println("返回值为Object的父类方法");return "OK";} //父类的方法public String showName(){System.out.println("返回值为String的子类方法");return "OK";} //子类的方法public static void main(String[] args) {Student student = new Student();student.showName(); //调用}


public String showName(){System.out.println("返回值为Object的父类方法");return "OK";}public Object showName(){System.out.println("返回值为String的子类方法");return "OK";} //报错

3.子类方法不能缩小父类方法的访问权限(但扩大可以):public > protected > 默认 > private
public void showName(){System.out.println("修饰符为public的父类方法");}protected void showName(){System.out.println("修饰符为protected的子类方法");} //报错,减小了范围


4. 重载和重写的比较


