面向对象是在面向过程之后出现的。当事件比较简单的时候,利用面向过程,注重的是事件的具体的步骤/过程,注重的是过程中的具体的行为,以函数为最小单位,考虑怎么做。
而面向对象注重找“参与者”,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做,然后通过这个对象来使用这个对象所具有的方法属性等(比如:人(类或对象)有眼睛鼻子(属性),也可以吃饭(方法).所以我们说面向对象有一句通俗的话叫做万物皆对象)。
下面我们就来详细介绍一下面向对象的概念和使用。
1 面向对象基本使用和相关关键字 1.1 类和对象的概念和使用类的定义和代码结构
- 类是对一个实体(也就是对象)向上抽取出抽象的部分(公共的部分),这样的抽象实体就是类。类是抽象的,是一个模板。
- 具体来说类里面有两种特性,即属性和方法。属性是类的静态特征,方法是类的动态特征。
注意:第一次使用类的时候,会进行类的加载,初始化创建对象的时候,对象的属性没有给赋值,会有默认的初始化的值。而第二次使用这个类就不会进行类加载了
完整代码结构:
//类的定义
[修饰符] class 类名{
//属性的定义
[修饰符] 属性类型 属性名 = [默认值] ;
//方法的定义
[修饰符] 方法返回值类型 方法名(形参列表) {
// n条语句
}
}
对象的定义和代码结构
- 对象是一个类的具体实例。(EG:定义一个人的类,类里面有吃饭的方法。而对象是张三(一个具体的人),张三是站着吃饭的(具体的吃饭的方法)。)
- 创建一个对象需要有类的存在,通过类就可以创建一个对象。用new关键字来实现。
完整代码结构是:
[修饰符] 类名 类对象名 = new 类名(实参列表);
使用类和对象的实例
1 创建一个类,抽象出人这个实体。
2 用这个类来构造一个对象,并且使用这个对象的方法。
public class Person {
//名词---》属性---》成员变量---》放在类中方法外(注意:我们只把有需要的内容写到代码里,不相关的东西不要放在代码中)
int age ;//年龄
String name;//姓名
double height;//身高
double weight;//体重
//动词---》方法
//吃饭
public void eat(){
int num = 10;//局部变量:放在方法中
System.out.println("我喜欢吃饭");
}
//睡觉:
public void sleep(String address){
System.out.println("我在"+address+"睡觉");
}
//自我介绍:
public String introduce(){
return "我的名字是:"+name+",我的年龄是:"+age+",我的身高是:"+height+",我的体重是:"+weight;
}
}
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建一个人类的具体的对象/实例:
//创建一个对象,对象的名字叫:zhangsan
//Person 属于 引用数据类型
//第一次加载类的时候,会进行类的加载,初始化创建对象的时候,对象的属性没有给赋值,有默认的初始化的值。
Person zs= new Person();
zs.name = "张三";
zs.age = 19;
zs.height = 180.4;
zs.weight = 170.4;
//再创建一个对象:名字叫:lisi
//再次创建类的时候,就不会进行类的加载了,类的加载只在第一次需要的时候加载一次
Person ls = new Person();
ls.name = "李四";
ls.age = 18;
ls.height = 170.6;
ls.weight = 160.5;
//对属性值进行读取:
System.out.println(zs.name); // 张三
System.out.println(ls.age); // 18
//对方法进行操作:
//不同的对象,属性有自己的特有的值,但是方法都是调用类中通用的方法。
//属性:各个对象的属性是独立的,
//方法:各个对象的方法是共享的。
zs.eat(); // 我喜欢吃饭
ls.eat(); // 我喜欢吃饭
zs.sleep("教室"); // 我在教室睡觉
System.out.println(zs.introduce()); // 我的名字是:张三,我的年龄是:19,我的身高是:180.4,我的体重是:170.4
}
}
1.2 局部变量和成员变量的区别
-
区别1:代码中位置不同
局部变量:方法中定义的变量
成员变量:类中(方法外)定义的变量 -
区别2:代码的作用范围不同
局部变量:当前的方法内
成员变量:作用于当前类的所有方法中 -
区别3:变量是否有默认值
局部变量:无
成员变量:有,↓为成员变量的默认值
-
区别4:变量是否要初始化
局部变量:一定需要,不然使用的时候会报错
成员变量:不需要,可以使用的时候对它进行赋值 -
区别5:内存中的位置不同
局部变量:栈内存
成员变量:堆内存 -
区别6:作用时间不同
局部变量:当前方法从开始到执行结束
成员变量:当前类的对象从创建到销毁的整个过程
在创建对象的过程中,是通过类内的一个特殊方法(构造器)来实现创建的。
具体过程见例子:
class Test {
public static void main(String[] args) {
//创建一个Person类的具体的对象/实例/实体:
Person p = new Person();
System.out.println(p.age);
System.out.println(p.name);
System.out.println(p.height);
Person p2 = new Person();
System.out.println(p2.age);
System.out.println(p2.name);
System.out.println(p2.height);
}
}
构造器是一个特殊方法,所以也可以重载。但是当你显示的写了一个类的重载构造器之后,默认的空参构造器就需要显示写出来,不然会报错。
想要指向类对象中的属性时,使用this关键字。this代表你创建的那个对象
public class Person {
//属性:
String name;
int age;
double height;
//空构造器
public Person(){
}
public Person(String name,int age,double height){
//当形参名字和属性名字重名的时候,会出现就近原则:
//在要表示对象的属性前加上this.来修饰 ,因为this代表的就是你创建的那个对象
this.name = name;
this.age = age;
this.height = height;
}
public Person(String a,int b){
name = a;
age = b;
}
//方法:
public void eat(){
System.out.println("我喜欢吃饭");
}
}
1.4 简单代码的内存分析
public class Person {
int id;
int age;
String school;
public Person (int a,int b,String c){
id=a;
age=b;
school=c;
}
public static void main(String args[]){
Person p= new Person(1,20, "");
}
}
点击运行时从main函数开始的:
(1)先开辟三个空间:栈,堆,和方法区(方法区内有一个地方为常量池)。
(2)在栈中开辟一个空间main方法。
(3)首先看到new Person,在方法区中加载这个Person字节码文件(只有第一次会加载),然后在堆中开辟一个空间来创建一个Person对象(这个对象有一个地址)。这个对象也会反过来指向它的字节码文件,用来指定自己是依靠谁来创建的。
(4)创建好了对象之后,new Person(1,20,"海淀")就去执行它的构造方法。会在栈中开辟一个空间Person构造器方法。
(5)在栈实参传入构造器方法的过程中。
如果是基本数据类型,那么直接传的就是这个值本身(值传递)。
如果是引用数据类型,比如String,那么会先在常量池中开辟一个空间来放置"海淀"这个字符串,然后把地址传给c(地址传递)。
(6)构造器执行完毕后,就会在栈中销毁,然后转入main方法。
(7)Person p = 。。。 之后把这个对象的地址赋值给p。(之后就可以通过p来操作这个对象)
| 整个程序运行的内存图解 |
|---|
this :指的是当前对象
当属性名字和形式参数发生重名的时候,或者属性名字和局部变量的名字重名的时候,都会发生就近原则,所以我们如果要直接使用属性的话,前面就要用this修饰这个变量。(如果没有重名就无所谓使用了)
| this关键字使用的图解 |
|---|
static关键字特点
- static可以修饰:属性,方法,代码块,内部类(没有类)
- 在方法区内有一个区域为静态域,用于存放static修饰的东西
- 用它修饰会在加载某类字节码文件时同时加载到静态域内。先于对象存在。
- 应用场景:某些特定的数据想要在内存中共享时就使用static来修饰即可
代码实例:
public class Demo1 {
int id;
static int sid;
public void a(){
System.out.println(id);
System.out.println(sid);
System.out.println("------a");
}
// 1.static和public都是修饰符,并列的没有先后顺序,先写谁后写谁都行
static public void b(){
//System.out.println(id); // 2.在静态方法中不能访问非静态的属性
//a(); // 3.在静态方法中不能访问非静态的方法
//System.out.println(this.id); // 4.在静态方法中不能使用this关键字
System.out.println(sid);
System.out.println("------b");
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//5.非静态的方法可以用对象名.方法名去调用
Demo1 d = new Demo1();
d.a();
//6.静态的方法可以用 对象名.方法名去调用 也可以用 类名.方法名 (推荐)
Demo1.b();
d.b();
}
}
代码块的特点
- 类的组成是:属性,方法,构造器,代码块和内部类
- 代码块的分类是:普通块,构造块,构造器块,静态块,同步块(多线程)
- 代码块的执行顺序是:
1.最先执行静态块,而且只在类加载的时候执行一次。(一般用于执行一些全局性的初始化操作)
2.然后执行构造块
3.再执行构造器方法块
4.最后执行方法中的普通块 - 代码块详细:
普通块:就是普通方法内的内容
构造块:在类中单独一个大括号内部的内容
构造器块:特殊用于构造类对象的方法
静态块:在类中用一个static{ }结构包裹的内容
代码实例:
public class Demo1 {
//属性
int a;
static int sa;
//方法
public void a(){
System.out.println("我是普通方法a");
{
//普通块限制了局部变量的作用范围
System.out.println("我是普通方法里面的普通块");
}
}
public static void b(){
System.out.println("我是静态方法b");
}
//构造块
{
System.out.println("我是类下的构造块,{}");
}
//静态块
static{
System.out.println("我是类下的静态块,static{}");
//在静态块中只能方法:静态属性,静态方法
System.out.println("我是类下的静态块,只能调用静态属性"+sa);
}
//构造器
public Demo1(){
System.out.println("我是类下的空构造器");
}
public Demo1(int a){
this.a = a;
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Demo1 t = new Demo1();
t.a();
System.out.println("------------------第二次---------------");
Demo1 t2 = new Demo1();
t2.a();
}
}
1.6 包,import
包的定义和特点
- 包的作用:就是为了分类,然后也为了解决重名问题和权限问题(包其实就相当于电脑盘符的目录)
- 包的命名:
(1)名字全部小写
(2)中间用 . 隔开
(3)一般都是公司域名倒着写 : com.jd com.uestc
(4)加上模块名字:com.jd.login com.jd.register
(5)不能使用系统中的关键字:nul con com1至com9…
(6)包声明的位置一般都在非注释性代码的第一行: - 想使用同一个包下的类不需要导包,可以直接使用。使用不同包下的类要需要导包: import **.**.**; 例如:import java.util.Date;
- 在java.lang包下的类,可以直接使用无需导包。
System.out.println(Math.random());
- IDEA中导包快捷键:alt+enter
- import 。。。.*代表所有
import java.util.*
- 导包中的目录没有包含和被包含的关系
import com.jayden.* //只代表jayden这一层的类,如果里面有还有包,那么这个包并没有加入 import com.jayden.sub.D //因为如果jayden下如果分别有两个包,两个包都有sub方法就会导致歧义
- 包可以静态引用
//静态导入:
import static java.lang.Math.*;
//导入:java.lang下的Math类中的所有静态的内容
public class Test {
public static void main(String[] args) {
System.out.println(random());
System.out.println(PI);
System.out.println(round(5.6));
}
//在静态导入后,同一个类中有相同的方法的时候,会优先走自己定义的方法。
public static int round(double a){
return 1000;
}
}
2 三大特性
面向对象的三大特性是指:封装,继承,多态。
2.1 封装(Encapsulation)封装的定义:封装就是具体的内部不给你看,只提供有限的接口供你使用的一种设计思想。
封装的特点
- 设计程序是要求“高内聚,低耦合”。
高内聚:类的内部数据操作细节自己完成,不允许外部干涉。
低耦合:仅对外暴露少量的方法用于使用。
隐藏对象内部的复杂性,只对外提供有限的接口。方便外界调用,从而提高系统的安全性、可扩展性、可维护性。通俗来说就是该隐藏的隐藏,该暴露的暴露。 - 代码中通过private权限修饰符来限制
- 权限修饰符
一般属性用private修饰,方法用public修饰
代码实例:
public class Girl {
//属性: private:私有化,外界不可以直接访问
private int age;
//读取年龄: public:外界可以直接访问
public int getAge(){
return age;
}
//设置年龄:
public void setAge(int age){
if(age >= 30 ){
this.age = 18;
}else{
this.age = age;
}
}
}
public class Demo1 {
public static void main(String[] args) {
//创建一个Girl类的对象:
Girl g = new Girl();
//设置年龄:
g.setAge(31);
//读取年龄:
System.out.println(g.getAge());
}
}
上面的代码,对于属性age来说,我加了修饰符private,这样外界对它的访问就受到了限制,现在我还想加上其他的限制条件,但是在属性本身上没有办法再加了,所以我们通过定义方法来进行限制条件的添加。
以属性为案例:
进行封装:
(1)将属性私有化,被private修饰(加入权限修饰符)
一旦加入了权限修饰符,其他人就不可以随意的获取这个属性
(2)提供public修饰的方法让别人来访问/使用
(3)即使外界可以通过方法来访问属性了,但是也不能随意访问,因为咱们在方法中可以加入限制条件。
继承的定义:继承就是对类的进一步抽象,多个类可以能有相同的属性和方法,这个时候就可以对这几个类抽取出所谓的父类。
EG: **学生类**:Student: 属性:姓名,年龄,身高,学生编号 方法:吃饭,睡觉,喊叫,学习 **教师类**:Teacher: 属性:姓名,年龄,身高,教师编号 方法:吃饭,睡觉,喊叫,教学 共同的东西:**人类**:Person 属性:姓名,年龄,身高 方法:吃饭,睡觉,喊叫
学生和老师都是人,如果还有100种职业的话,比如都有属性姓名属性和吃饭的方法,那么每一个类中都要重新写一遍就太麻烦了,所以定义它们的父类Person,只要用字段entend关键字继承Person,就会获得Person的所有共有属性和方法。
继承的特点
- 继承的好处:提高了代码的复用性和可扩展性。类定义的内容,子类可以直接拿过来用就可以了,不用代码上反复重复定义了。也是多态的前提。
- 继承只能有一个直接父类
//报错,只能有一个直接父类
public class Student extends Person,Boss{
}
- 继承具有传递性:EG:Student继承自Person,Person继承自Object(所有的类都直接或者间接的继承自Object)
内存分析:在new一个子类对象的时候,会先加载这个子类所有父类的字节码,然后再加载这个子类的字节码。
注意:父类private修饰的内容,子类实际上也继承,只是因为封装的特性阻碍了直接调用,但是提供了间接调用的方式,可以间接调用。
方法重写的定义和特点:
- 发生在子类和父类中,当子类对父类提供的方法不满意的时候,要对父类的方法进行重写。
- 子类的方法名字和父类必须一致,参数列表(个数,类型,顺序)也要和父类一致。
代码实例:
class Person {
public void eat(){
System.out.println("吃食物");
}
}
class Student extends Person {
@Override
public void eat(){
System.out.println("我喜欢吃小龙虾喝啤酒。。");
}
}
public class Demo1 {
public static void main(String[] args) {
//创建一个Student类的对象:
Student s = new Student();
//打印的是子类Student中重写的的eat方法,而不是父类Person的
s.eat(); // 我喜欢吃小龙虾喝啤酒。。
}
}
重载和重写的区别:重载(Overload)是在同一个类中方法名相同,单参数列表不同的多个方法构成的结构。而重写(Override)是在不同类中,方法名和参数名都必须相同(注意:重写中父类的修饰符权限一定要低于子类的,返回值类型要大于子类的)
super关键字的定义和特点
- 在子类的方法中,可以通过 super.属性 super.方法 的方式,显示的去调用父类提供的属性,方法。在通常情况下,super.可以省略不写。
- 在特殊情况下,当子类和父类的属性和方法重名时,你要想使用父类的属性,必须加上修饰符super.,只能通过super.属性来调用。
- 其实我们平时写的构造器的第一行都有(super();)代码。作用是调用父类的空构造器(只是我们一般都省略不写)。如果构造器中已经显示的调用super父类构造器,那么它的第一行就没有默认分配的(super();)了
- 在构造器中,super调用父类构造器和this调用自身构造器只能存在一个。(因为super和this修饰构造器都要放在第一行)
public Student(int age,String name,double score){
super(age,name);
this(score);//调用子类的public Student(double score)构造器报错
}
2.3 多态(Polymorphism)
多态的定义:多态就是对同一个方法的不同实现。(继承是多态的前提)
多态的特点
- 多态只和方法有关,和属性无关。
- 多态的好处:提高了代码的可扩展性,符合面向对象的设计原则:开闭原则(可以扩展,但是原来存在的不准修改)
代码实例:多态的应用
class Animal {//父类:动物:
public void shout(){
System.out.println("我是小动物,我可以叫。。。");
}
}
class Cat extends Animal{//子类1:猫
//喊叫方法:
public void shout(){
System.out.println("我是小猫,可以喵喵叫");
}
}
class Dog extends Animal{//子类2:狗
int age;
int weight;
//喊叫:
public void shout(){
System.out.println("我是小狗,我可以汪汪叫");
}
public void eat(){
System.out.println("我是小狗,我爱吃东西。。");
}
}
class Girl { //多态应用1:原本要写两个方法现在只需要写一个
//跟猫玩耍:
//跟狗玩耍:
//跟小动物玩耍:
public void play(Animal an){
an.shout();
}
}
public class Demo1 {
public static void main(String[] args) {
//eg1:转化为父类根据对象使用对应的方法
Cat cat = new Cat();
Dog dog = new Dog();
//Animal an = ?
Animal animal = dog;//转型:向上转型(Dog转成Animal)
//多态应用2:下面代码就不用变了
animal.shout();
//注意an只能用Animal中存在的方法,虽然它的对象是Dog,但在编译期的时候因为Animal没有eat属性所以会报错
// an.eat();
//eg2:转化为子类后可以用子类的方法
Dog dog2 = (Dog)animal ;//转型:向下转型(Animal转为Dog)
dog2.eat();
}
}
2.4 简单工厂模型(多态应用)
多态的应用中,不仅可以使用父类做方法的形参,还可以使用父类做方法的返回值类型,真实返回的对象可以是该类的任意一个子类对象。
这种方法是简单工厂模式的实现,它是解决大量对象创建问题的一个解决方案。将创建和使用分开,工厂负责创建,使用者直接调用即可。简单工厂模式的基本要求是
(1)定义一个static方法,通过类名直接调用
(2)返回值类型是父类类型,返回的可以是其任意子类类型
(3)传入一个字符串类型的参数,工厂根据参数创建对应的子类产品
代码实例:
class PetStore {//宠物店 ---》工厂类
//方法:提供动物
public static Animal getAnimal(String petName){//多态的应用场合(二)
Animal an = null;
if("猫".equals(petName)){//petName.equals("猫") --》这样写容易发生空指针异常
an = new Cat();
}
if("狗".equals(petName)){
an = new Dog();
}
return an;
}
}
class Demo2 {
public static void main(String[] args) {
Girl g = new Girl();
//Cat c = new Cat();
//Dog d = new Dog();
Animal an = PetStore.getAnimal("狗");
g.play(an); // 我是小狗,我可以汪汪叫
}
}



