一起来理解一下Java类和对象

一、面向对象简述

面向对象是一种现在最为流行的程序设计方法,几乎现在所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的,在70年代的Smaltalk语言之中进行了应用,后来根据面向对象的设计思路,才形成了C++,而由C++产生了Java这门面向对象的编程语言。
但是在面向对象设计之前,广泛采用的是面向过程,面向过程只是针对于自己来解决问题。面向过程的操作是以程序的基本功能实现为主,实现之后就完成了,也不考虑修改的可能性,面向对象,更多的是要进行子模块化的设计,每一个模块都需要单独存在,并且可以被重复利用,所以,面向对象的开发更像是一个具备标准的开发模式。
在面向对象定义之中,也规定了一些基本的特征:

  • 封装:保护内部的操作不被破坏。
  • 继承:在原本的基础之上继续进行扩充。
  • 多态:在一个指定的范围之内进行概念的转换。

对于面向对象的开发来讲也分为三个过程:OOA(Object Oriented Analysis:面向对象分析),OOD(Object Oriented Design:面向对象设计),OOP(Object Oriented Programming: 面向对象编程)。

二、类与对象的基本概念

类与对象是整个面向对象中最基础的组成单元。
类:是抽象的概念集合,表示的是一个共性的产物,类之中定义的属性和行为(方法)。
对象:对象是一种个性的表示,表示一个独立的个体,每个对象拥有自己独立的属性,依靠属性来区分不同的对象。

可以一句话来总结出类和对象的区别:类是对象的模板,对象是类的实例。类只有通过对象才可以使用,而在开发之中应该先产生类,之后再产生对象。类不能直接使用,对象是可以直接使用的。

三、类与对象的定义和使用

在Java中定义类,使用关键字class完成,语法如下:

class 类名称{
      属性(变量);
      行为(方法);
}

范例:定义一个Person类

class Person{
      String name;
      int age;
      public void tell(){
             System.out.println("姓名:"+name+",年龄:"+age);
      }
}

类定义完成之后,肯定无法直接使用。如果要使用,必须依靠对象,那么由于类属于引用数据类型,所以对象的产生格式如下:

  1. 声明并实例化对象:
    类名称 对象名称=new 类名称();
  2. 先声明对象,然后实例化对象:
    类名称 对象名称=null;
    对象名称=new 类名称();

引用数据类型与基本数据类型最大的不同在于,引用数据类型需要内存的分配和使用,所以,关键字new的主要功能就是分配内存空间,也就是说,只要使用引用数据类型,就要使用关键字new来分配内存空间。
当一个实例化对象产生之后,可以按照如下的方式进行类的操作:
对象.属性:表示调用类之中的属性。
对象.方法:表示调用类之中的方法。
使用对象操作类:

class Person{
      String name;
      int age;
      public void get(){
             System.out.println("姓名:"+name+",年龄:"+age);
      }
}
public class TestDemo{
        public static void main(String[] args){
                Person per=new Person();//声明并实例化对象
                per.name="张三";//操作属性内容
                per.age=30;//操作属性内容
                per.get();//调用类中的get()方法
        }
}

以上完成了一个类和对象的操作关系,下面换另外一个操作来观察一下:

class Person{
      String name;
      int age;
      public void get(){
             System.out.println("姓名:"+name+",年龄:"+age);
      }
}
public class TestDemo{
        public static void main(String[] args){
                Person per=null;
                per=new Person();//声明    //实例化对象
                per.name="张三";//操作属性内容
                per.age=30;//操作属性内容
                per.get();//调用类中的get()方法
        }
}

以上两种不同的实例化方式有什么区别呢?
我们从内存的角度分析,首先,给出两种内存空间的概念:

  1. 堆内存:保存对象的属性内容,堆内存需要用new关键字来分配空间;
  2. 栈内存:保存的是堆内存的地址(这里为了分析方便,可以简单理解为栈内存保存的是对象的名字)
    在这里插入图片描述
    任何情况下,只要看见关键字new,都表示要分配新的堆内存空间,一旦堆内存空间分配了,里面就会有类中定义的属性,并且属性内容都是其对应数据类型的默认值。
    于是,上面两种对象实例化对象方式内存表示如下:
    在这里插入图片描述
    两种方式区别在于①②,第一种声明并实例化的方式实际就是①②组合在一起,而第二种先声明后实例化是吧①和②分步骤来。
    另外,如果使用了没有实例化的对象,结果会如何?
class Person{
      String name;
      int age;
      public void get(){
             System.out.println("姓名:"+name+",年龄:"+age);
      }
}
public class TestDemo{
       public static void main(String[] args){
              Person per=null;
              //per=new Person();
              per.name="张三";
              per.age=30;
              per.get();
       }
}

结果:

Exception in thread "main" java.lang.NullPointerException
      at com.lixiaogang.TestDemo.main(TestDemo.java:15)

此时,程序只声明了Person对象,但并没有实例化Person对象(只有了栈内存,并没有对应的堆内存空间),则程序在编译的时候不会出现任何的错误,但是在执行的时候出现了上面的错误信息。这个错误信息表示的是“NullPointerException(空指针异常)”,这种异常只要是引用数据类型都有可能出现。

四、对象引用传递初步分析

引用传递的精髓:同一块堆内存空间,可以同时被多个栈内存所指向,不同的栈可以修改同一块堆内存的内容。
下面通过若干个程序,以及程序的内存分配图,来进行代码的讲解。

class Person{
      String naem;
      int age;
      public void tell(){
             System.out.println("姓名:"+name+",年龄:"+age);
      }
}
public class TestDemo{
       public static void main(String[] args){
              Person per1=new Person();
              per1.name="张三";
              per1.age=20;
              Person per2=per1;
              per2.name="李四";
              per2.tell();
       }
}

对应的内存分配图如下:
在这里插入图片描述
另一个:

class Person{
      String name;
      int age;
      public void tell(){
             System.out.println("姓名:"+name+",年龄:"+age);
      }
}
public class TestDemo{
public static void main(String[] args){
       Person per1=new Person();
       Person per2=new Person();
       per1.name="张三";
       per1.age=20;
       per2.name="李四";
       per2.age=30;
       per2=per1;
       per2.name="王五";
       per1.tell();
}
}

内存分配图:
在这里插入图片描述
垃圾:指的是在程序开发之中没有任何对象所指向的一块堆内存空间,这块空间就成为垃圾,所有的垃圾将等待GC(垃圾收集器)不定期的进行回收与空间的释放。

五、面向对象的封装性

封装(Encapsulation)又叫隐藏现实(Hiding the implementation),就是只公开代码单元的对外接口,而隐藏其具体实现。比如手机,手机的键盘屏幕,听筒等,就是其对外接口。你只需知道如果按键就可以使用手机,而不需要了解手机内部的电路是如何工作的。封装机制就像手机一样只将对外接口暴露,而不需要用户去了解其内部实现。
示例:

class Book{
      String title;
      double price;
      public void getInfo(){
             System.out.println("图书的名称:"+title+"图书的价格:"+price);
      }
}
public class TestDemo{
       public static void main(String[] args){
       Book book=new Book();
       book.title="Java开发";
       book.price=-89.9;
       book.getInfo();
       }
}

以上代码没有任何的语法错误,却存在一个业务逻辑的错误,因为图书的价格不能为负数。造成这种情况的原因在于,对象可以在一个类的外部直接访问属性。
如何解决?我们需要将Book类中的属性设置为对外不可见(只能在本类访问),可以使用private关键字来定义属性。
修改代码:

class Book{
      private String title;
      private double price;
      public void getInfo(){
             System.out.println("书名是:"+title+"价格是:"+price);
      }
}
public class TestDemo{
       public static void main(String[] args){
              Book book=new Book();
              book.title="Java开发";
              book.price=-89.9;
              book.getInfo();
       }
}

运行:

Exception in thread "main" java.lang.Error: Unresolved compilation problems:
       The field Book.title is not visible
       The field Book.price is not visible
       at com.lixiaogang.TestDemo.main(TestDemo.java:16)

我们发现在访问属性的时候,外部的对象无法再直接调用类中的属性了,此时相当于Book类的属性对外部不可见。
但是,要想让程序可以正常运行,那么必须让外部可以操作Book类的属性。在Java开发中,针对属性有这样的定义,在类中定义的属性都要求用private声明,如果属性需要被外不使用,那么按照要求,定义属性相应的setter和getter方法,以Book类的String title为例:

  1. setter方法是设置属性内容,有参数:
    public void setTitle(String t)
  2. getter方法是取得属性内容:
    public String getTitle()

为Book类的封装属性设置setter和getter方法

class Book{
      private String title;
      private double price;
      public String getTitle{
             return title;
      }
      public void setTitle(double price){
             if(price>0){
                   this.title=title;
             }
      }
      public double getPrice{
             return price;
      }
      public void setPrice{
             this.price=price;
      }
      public void getInfo(){
             System.out.println("图书的名称:"+title+"图书的价格:"+price);
      }
}
public class TestDemo{
       public static void main(String[] args){
               Book book=null;
               book=new Book();
               book.setTitle("Java开发");
               book.setPrice("89");
               book.getInfo();
       }
}

对于数据验证,在Java标准开发中应该由副主代码完成。在实际开发中,setter往往只是简单的设置属性内容,getter只是简单的取得属性内容。
开发建议:在以后定义类的时候,所有的属性都要编写private封装,封装之后的属性如果需要被外部操作,则编写setter,getter。

六、类中的构造方法

先来看对象的产生格式:
①类名称 ②对象名称 =③new ④类名称();MyThread myThread=new MyThread();
①类名称 :规定了对象的类型。即:对象可以使用哪些属性和方法都是由类定义的。
②对象名称 :如果需要使用对象,需要有一个名称,这是一个唯一的标记。
③new:分配新的堆内存空间。
④类名称():调用了名称和类名称相同的方法,这是构造方法。

实际上,构造方法一直被我们调用,但我们并没有去定义它,为什么能够使用呢?这是因为在整个Java开发中,为了保证程序可以正常执行,即便用户没有定义任何构造方法,也会在程序编译后自动为类增加一个没有参数,方法名称与类名称相同,没有返回值的构造方法。
构造方法的定义:方法名称和类名称相同,没有返回值声明。

public Book(){
}

如果在Book类中没有定义以上的构造方法,那么也会自动生成一个无参,无返回值的构造方法。

class Book{
      public Book{
             System.out.println("无参构造方法");
      }
}
public class TestDemo{
       public static void main(String[] args){
              Book book=null;
       }
}

运行什么也没有打印,
在主方法中加入实例化对象:

public class TestDemo{
       public static void main(String[] args){
              Book book=null;
              book=new Book();
       }
}

运行:
无参构造方法

以上说明,构造方法是在对象使用关键字new实例化的时候被调用。
构造方法与普通方法最大的区别是:
构造方法在实例化对象(new)的时候只调用一次,而普通方法是在实例化对象之后可以随意调用多次。
在实际开发中,构造方法的作用是在类对象实例化的时候设置属性的初始化内容,范例如下:

class Book{
      private String title;
      private double price;
      public Book(String title,double price){
             this.title=title;
             this.price=price;
      }
      public String getTitle(){
             return title;
      }
      public void setTitle(String title){
             this.title=title;
      }
      public double getPrice(){
              return  price;
      }
      public void setPrice(double price){
              this.price=price;
      }
      public void getInfo(){
              System.out.println("书名:"+title+"图书价格:"+price);
      }
}
public class TestDemo{
       public static void main(String args[]){
              Book book=new Book("java开发",89.9);
              book.getInfo();
       }
}

运行结果:
书名:java开发 图书价格:89。9
如果一个类中已经明确定义了一个构造方法,则无参构造方法将不会自动生成,而且,一个类之中至少存在一个构造方法。另外,既然构造方法也属于方法,那么构造方法也可以重载,但是由于构造方法的特殊性,所以在构造方法重载时注意其参数的类型及参数的个数即可。

class Book{
      private String title;
      private double price;
      public Book(){
             System.out.println("无参的构造方法");
      }
      public Book(String title){
              this.title=title;
              System.out.println("有一个参数的构造方法");
      }
      public Book(String title ,double price){
              this.title=title;
              this.price=price;
              System.out.println("有两个参数的构造方法")}
      public void getInfo(){
              System.out.println("书名:"+title+"图书价格:"+price);
      }
}
public class TestDemo{
       public static void main(String[] args){
               Book book1=new Book();
               book1.getInfo();
               Book book2=new Book("Android开发");
               book2.getInfo();
               Book book3=new Book("java开发",89.9);
               book3.getInfo();
       }
}

运行结果:

无参的构造方法
书名:null 图书价格:0.0
有一个参数的构造方法 书名:Android开发 图书价格:0.0
有两个参数的构造方法
书名:Java开发 图书价格:89.9

在进行构造方法重载时有一个编写建议:所有重载的构造方法按照参数的个数由多到少或由少到多排列
如果在类中为属性设置默认值,结果会怎么样?

class Book{
      private String title="Android开发";
      private double price=199.9;
      public Book(){
             System.out.println("无参的构造方法");
      }
      public Book(String title){
              this.title=title;
              System.out.println("有一个参数的构造方法");
      }
      public Book(String title ,double price){
              this.title=title;
              this.price=price;
              System.out.println("有两个参数的构造方法")}
       public void getInfo(){
              System.out.println("书名:"+title+"图书价格:"+price);
      }
}
public class TestDemo{
       public static void main(String[] args){
              Book book1=new Book();
              book1.getInfo();
              Book book2=new Book("Java开发");
              book2.getInfo();
              Book book3=new Book("java开发",89.9);
              book3.getInfo();
       }
}

运行结果:
无参的构造方法
书名:Android开发 图书价格:199.9
有一个参数的构造方法
书名:Java开发 图书价格:199.9
有两个参数的构造方法
书名:Java开发 图书价格:89.9

从上面可以发现:如果在类中为属性直接设置默认值,而且这个默认值是在构造方法执行完后才会设置的,且属性内容为对应数据类型的默认值时才设置类中定义的这个默认值。但是,要注意的是在构造方法没有执行前,属性内容都是对应数据类型的默认值。

七、类中的匿名对象

没名字的对象称为匿名对象,对象的名字按照之前的内存关系来讲,在栈内存之中,而对象的具体内容在堆内存之中保护,这样,没有栈内存指向堆内存空间,就是一个匿名对象。
范例:

class Book{
      private String title;
      private double price;
      public Book(String title,double price){
             this.title=title;
             this.price=price;
             System.out.println("有两个参数的构造方法");
      }
      public void getInfo(){
             System.out.println("图书的名称:"+title+"图书价格:"+price);
      }
}
public class TestDemo{
       public static void main(String[] args){
              new Book("Java开发",89.9).getInfo();
       }
}

运行结果:
有两个参数的构造方法
图书的名称:Java开发 图书价格:89.9

匿名对象由于没有对应的栈指向内存,所以只能用一次,之后就将成为垃圾,并且等待被GC回收释放。

我只是优秀博客的搬用工,尊重感谢原创作者——志见。
原作地址:
https://blog.csdn.net/wei_zhi/article/details/52745268
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值