运用数据抽象的思想编写代码(定义和使用数据类型,将数据类型的值封装在对象中)的方式称为面向对象编程。
数据类型指的是一组值和一组对值的操作的集合。对象是能够存储任意该数据类型的值的实体,或数据类型的实例。
类与对象的关系
从类创建对象,有好几种说法——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,报错
}
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23175776/1641709219331-a0a6f735-b32b-4ae3-aea3-986ca77f8937.png#clientId=uc103dc5c-cf76-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ufbf1e9ae&margin=%5Bobject%20Object%5D&name=image.png&originHeight=286&originWidth=1153&originalType=url&ratio=1&rotation=0&showTitle=false&size=49070&status=done&style=none&taskId=u9ebaad25-c790-455c-a183-7fda85607b0&title=)
<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 />![image.png](https://cdn.nlark.com/yuque/0/2022/png/23175776/1641715307453-4c1c47de-d718-4d2a-96a8-4d26cab0ceee.png#clientId=u4749a98d-e8f1-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uca9155c9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=234&originWidth=1277&originalType=url&ratio=1&rotation=0&showTitle=false&size=47830&status=done&style=none&taskId=uac8946e5-f9e3-4084-b1cf-e5810566163&title=)<br />**下面对于子类调用父类的方法举一个例子(属性因为很简单就不举例了)**<br />定义一个父类Person:
```java
public 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{ //继承Student
public 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. 重载和重写的比较