java中构造方法的作用_跟光磊学Java开发-面向对象编程入门

面向对象对比面向过程

面向对象与面向过程都是一种编程思想

  • 面向过程编程(Procedure Oriented Programming)是一种以过程为中心的编程思想,关心的是实现的步骤。
    java-core-jdk-foundational模块的代码都是基于面向过程实现的。
  • 面向对象编程(Object Oriented Programming)是一种以对象为中心的编程思想。客观存在的任何一种现实事物都可以看做程序中的对象,每个对象都有自己独特的功能,在Java语言中万物皆对象。面向对象编程是基于面向过程演变而来的,将我们从执行者的位置变成了指挥者。使用面向对象的思想可以将复杂的问题简单化,相对于面向过程而言面向对象编程更容易扩展和维护,适合大型项目复杂业务场景开发。

深入理解Java中的类

类和对象的关系

类(Class)是对生活中一类具有共同属性和行为的事物的抽象,类是对事物,也就是对象的一种描述。
可以将类理解为设计图,根据设计图可以创建出具体存在的对象。例如在制造iphone之前苹果设计师都会出iphone的设计图,然后富士康工厂才能根据设计图制造iphone。
因此对象是根据类创建出来的真实存在的实体。

类由属性和行为组成
属性指的是该事物的各种特征,例如iphone的颜色,价格,重量,尺寸等等。
行为就是该事物存在的功能,例如iphone能够播放音乐,录视频,上网,拍照等等。

类的定义

和使用变量之前先定义一样,在使用类之前需要定义类,类由属性和行为组成

  • 属性在程序中通过成员变量(Member Variable)体现的,也就是在类中方法外定义的变量,成员变量从属于对象成员变量是在堆内存中存储,从属于对象,因此其生命周期是随着对象的存在而存在的,随着对象的消失而消失。成员变量由Java负责自动初始化。,不同于之前都是在方法中定义的都是局部变量(Local Variable)。局部变量是定义在方法中的变量或者是方法的形参,局部变量在栈内存中存储,从属于方法,因此其生命周期是随着方法的存在而存在,随着方法的调用完成而消失。局部变量Java程序员负责手动初始化后才能使用。
  • 行为在程序中就是成员方法(Member Method)体现的,之前定义的方法加了static关键字,属于静态方法,静态方法从属于类,而成员方法就是去掉static关键字之后的方法,成员方法从属于对象。

自定义类案例:Product类

package net.ittimeline.java.core.jdk.oop.bean;/** * 自定义类案例:Product类 * @author tony 18601767221@163.com * @version 2020/12/12 17:21 * @since JDK11 */public class Product {    /********************定义成员变量********************/    /**     * 商品名称     */    String name;    /**     * 商品颜色     */    String color;    /**     * 商品尺寸     */    double size;    /**     * 商品重量     */    int weight;    /**     * 商品价格     */    double price;    /********************定义成员方法********************/    /**     * 播放音乐     */    public void playMusic(){        System.out.println("产品正在播放音乐");    }    /**     * 指定的产品名称播放音乐     * @param productName     */    public void playMusic(String productName){        System.out.println(productName+"正在播放音乐");    }}

需要注意的是成员变量定义时通常不会手动指定初始化值,而是在创建对象时赋值。
成员方法和普通方法一样也可以重载,例如这里的playMusic()就发生了重载。

构造方法

构造方法就是创建对象时所调用的方法,当使用new关键字创建对象时就会调用构造方法。构造方法的方法名和类名一致,构造方法没有返回值,连void都没有,也不能使用return 返回数据。
虽然我们之前在创建对象时没有定义构造方法,但是系统默认会提供一个无参数的构造方法。

public Product(){    }

如果我们在类中定义了无参数的构造方法,则会覆盖系统默认提供的构造方法

定义无参数的构造方法

    /**     * Product对象无参构造器     */    public Product(){        System.out.println("执行Product对象的构造方法");    }

每次创建对象的时候都会去调用一次构造方法,构造方法不能手动调用。

package net.ittimeline.java.core.jdk.oop.bean.constructor;/** * 构造方法测试 * * @author tony 18601767221@163.com * @version 2020/12/13 12:04 * @since JDK11 */public class ConstructorTest {  public static void main(String[] args) {      //创建三个Product对象      //每次创建对象时都会调用对象的构造方法,此时会调用无参数的构造器      Product product1=new Product();      Product product2=new Product();      Product product3=new Product();  }}

程序运行结果

d02c3c0fd14745c315db630069cc9875.png

构造方法的作用就是用于初始化对象,即给对象的成员变量赋值,但是如果是在无参数的构造器给成员变量赋值不够灵活也不合理,因为每个对象的属性值可能不一样。
此时就可以使用带参数的构造器来给成员变量赋值。通过传递参数,让构造器给属性赋值时更加灵活。

有参构造方法

   /**     * Product对象 有参数构造器     * @param name     * @param price     */    public  Product(String name,double price){        this.name=name;        this.price=price;    }

有参数构造方法的调用

package net.ittimeline.java.core.jdk.oop.bean.constructor;/** * 构造方法测试 * * @author tony 18601767221@163.com * @version 2020/12/13 12:04 * @since JDK11 */public class ConstructorTest {  public static void main(String[] args) {      //通过构造方法给属性赋值      Product iphone12=new Product("iphone12",6799);      Product mate40ProPlus=new Product("mate40ProPlus",8999);      iphone12.showProductInfo();      mate40ProPlus.showProductInfo();  }}

程序运行结果

620de62c4eeff8c4e65c648288d27f3b.png

在定义和使用构造方法时需要注意如果类中没有定义构造方法,系统会提供一个不带参数的构造方法。如果类中定义了带参数的构造方法,则系统会再提供默认的无参构造方法,此时如果还想调用无参构造方法,需要自己手动实现一个无参构造方法。此时无参构造方法和有参构造方法就形成了构造方法的重载。

 /**     * Product对象无参构造器     */    public Product(){        System.out.println("执行Product对象的构造方法");    }    /**     * Product对象 有参数构造器     * @param name     * @param price     */    public  Product(String name,double price){        this.name=name;        this.price=price;    }

代码块

代码块就是使用一对{ }包含的一段代码,代码块可以分为构造代码块,静态代码块和局部代码块

  • 构造代码块就是在类中,方法外定义的代码块,代码块的定义格式就是{ },构造代码块在每次调用构造器创建对象前会执行一次,其使用场景是创建对象之前需要执行的代码可以方法在代码块中,也可以用用来统计创建对象的个数
  • 静态代码块就是在类中,方法外定义的代码块,静态代码块的格式就是static {},静态代码块会在类加载时执行,而且只会执行一次。其应用场景是只需要执行一次的代码 ,静态代码块优先于构造代码块执行,构造代码块优先于构造方法。
  • 局部代码块就是在方法内部定义的代码块,局部代码块的格式就是{},局部代码块在调用方法,执行到了局部代码块才会被执行。其应用场景是提前释放内存,节省内存空间。因为局部代码块中如果使用了基本数据类型的局部变量,该局部变量的作用域在局部代码块{}内,超过局部代码块{}作用域就会释放。但是服务器内存都是16G起步,因此局部代码块意义不大。
package net.ittimeline.java.core.jdk.oop.bean.codeblock;/** * 代码块测试 * * @author tony 18601767221@163.com * @version 2020/12/14 16:37 * @since JDK11 */public class CodeBlockTest {  public static void main(String[] args) {    UserInfo userInfo =new UserInfo();    {      int age = 10;      System.out.println("我是一个局部代码块");      System.out.println("age = " + age);    }  }}class UserInfo {  public static String defaultRegisterResource;  static {    System.out.println("我是一个静态代码块");    defaultRegisterResource = "APP";  }  private String userName;  private String password;  public String getUserName() {    return userName;  }  public void setUserName(String userName) {    this.userName = userName;  }  public String getPassword() {    return password;  }  public void setPassword(String password) {    this.password = password;  }  // 构造代码块  {    System.out.println("我是一个构造代码块");    if (this.userName==null|| this.userName.equals("")) {      this.setUserName("admin");    }    if (this.password==null || this.password.equals("")) {      this.setPassword("123456");    }  }  public UserInfo() {    System.out.println("我是一个无参构造器");    System.out.printf("我的名字是%s,我的密码是%s",this.getUserName(),this.getPassword());  }}

程序运行结果

0a2b4d7986ea803c6adb154df80365ef.png

package和import 关键字

package关键字用于定义一个包,包是Java用来组织程序结构的一种方式,不同的代码可以放在不同的包结构中。
在日常开发中,包名的命名通常都是使用公司的域名倒过来然后加上项目名,业务模块名。例如com.taobao.user。包名加类名组成全类名,JVM在加载类时会使用到全类名

ea5d8878746e5d41c019450c8133c0ae.png


包声明语句必须放在Java源文件的第一行

852952422b37f2bbb95cea31e939bf5f.png


包的本质就是文件夹

fcf7498be7be22995ac6c1ddbc3c1aa4.png

import关键字用于导入一个类,日常开发中通常会使用到大量的JDK提供的类库或者是第三方提供的类库

a727b156fb263ef9c97b5ad9c2ad3ace.png

例如之前使用Scanner类就是位于java.util包下,需要使用import关键字加上全类名导入。

86207531f12314446f3f7c0ca6aec514.png

深入理解Java中的对象

对象的创建和使用

类定义后就可以通过创建对象来使用,创建对象的语法格式是 类名 对象名 = new 类名();,例如Product iphone12 =new Product();,
创建对象后就可以使用对象的成员变量和成员方法。
使用成员变量的语法格式是 对象名.成员变量名,例如iphone12.color;
使用成员方法的语法格式是对象名.方法名,例如iphone12.playMusic();

在使用类之前先说明下java-core-jdk-oop模块的目录结构,之前在java-core-jdk-foundational中基本上都是使用一个包含main方法的类来演示说明某个语言特性,例如循环等等。
而在学习面向对象之后,通常都会编写多个类组合后演示某个语言特性,例如面向对象的三大特性:封装、继承、多态,通常都会编写一个测试类(例如这里的ProductTest)来测试某些语言特性,按照maven的项目结构约定,该测试类通常位于src/main/test目录下。而src/main/resource用于存放项目的源代码。

163f7d2dd466e2ee575ee6d7a056bcff.png
package net.ittimeline.java.core.jdk.oop.bean;/** * 测试对象的创建和使用 * * @author tony 18601767221@163.com * @version 2020/12/12 17:24 * @since JDK11 */public class ProductTest {  public static void main(String[] args) {    // 创建对象    Product iphone12 = new Product();    //打印对象的内存地址    // iphone12 =  net.ittimeline.java.core.jdk.oop.bean.Product@566776ad    // net.ittimeline.java.core.jdk.oop.clazz.Product表示全类名,即包名+类名    // @表示分隔符    // 566776ad 表示十六进制的地址    System.out.println("iphone12 = "+iphone12);    System.out.println("对象的成员变量未赋值时");    //访问对象的成员变量    //当创建对象后,Java会给成员变量赋初始值    // 例如成员变量是int,初始值就是0,成员变量类型是double,初始值就是0.0,成员变量是String,初始值是null    System.out.println("iphone12.name = " + iphone12.name);    System.out.println("iphone12.color = " + iphone12.color);    System.out.println("iphone12.size = " + iphone12.size);    System.out.println("iphone12.weight = " + iphone12.weight);    System.out.println("iphone12.price = " + iphone12.price);    //给对对象的成员变量赋值    iphone12.color="黑色";    iphone12.name="iphone12";    iphone12.size=6.1;    iphone12.weight=162;    iphone12.price=6799;    System.out.println("对象的成员变量赋值时");    System.out.println("iphone12.name = " + iphone12.name);    System.out.println("iphone12.color = " + iphone12.color);    System.out.println("iphone12.size = " + iphone12.size);    System.out.println("iphone12.weight = " + iphone12.weight);    System.out.println("iphone12.price = " + iphone12.price);    // 调用对象的重载方法    iphone12.playMusic();    iphone12.playMusic(iphone12.name);  }}

程序运行结果

e572acb82ce0c7635013aee82c104a94.png


当创建对象时,系统会根据对象成员变量的数据类型赋初始值。

对象的内存结构

单个对象的内存结构
ProductTest程序运行时的内存结构

4e5b897e5be6ef242c9ab958397740c3.png
  1. 程序运行时main方法所在的类ProductTest率先被加载到方法区,该类只有一个main方法
  2. 当main方法被JVM调用时main方法进栈
  3. 当执行到Product iphone12 = new Product();,Product.class也会被加载到方法区中,方法区中会包含该类的5个成员变量和两个重载的方法信息,使用new Product();创建对象时会在堆内存中开辟内存空间用来存储Product对象的成员变量和成员方法的地址,并且会给堆内存中的成员变量赋默认的初始化值。然后将堆内存的空间地址566776ad返回给栈内存的Product引用变量iphone12,当打印对象的地址时就会输出 net.ittimeline.java.core.jdk.oop.bean.Product@566776ad,也就是全类名@地址值。
  4. 当创建对象后打印输出成员变量值时 会打印出Java给成员变量的初始值
  5. 当执行到iphone12.color="黑色";,此时会通过iphone12的地址找到堆内存的对象,并将该对象的color属性由默认的初始值null改成字符串常量值黑色
  6. 当执行打印属性值时,通过属性名找到堆内存的属性值打印输出
  7. 当执行调用成员方法时,会根据栈内存的对象引用找到堆内存方法的地址,然后根据该地址找到方法区的方法,将该方法入栈后执行,当方法结束后会依次出栈。

多个对象的内存结构

package net.ittimeline.java.core.jdk.oop.bean;/** * 多个对象的创建和使用 * * @author tony 18601767221@163.com * @version 2020/12/13 9:25 * @since JDK11 */public class ProductMultiTest {  public static void main(String[] args) {      Product mat40proPlus = new Product();      mat40proPlus.color="黑色";      mat40proPlus.name="mate40ProPlus";      mat40proPlus.size=6.76;      mat40proPlus.weight=230;      mat40proPlus.price=8999;      System.out.println("对象的成员变量赋值时");      System.out.println("mat40proPlus = "+mat40proPlus);      System.out.println("mat40proPlus.name = " + mat40proPlus.name);      System.out.println("mat40proPlus.color = " + mat40proPlus.color);      System.out.println("mat40proPlus.size = " + mat40proPlus.size);      System.out.println("mat40proPlus.weight = " + mat40proPlus.weight);      System.out.println("mat40proPlus.price = " + mat40proPlus.price);      mat40proPlus.playMusic(mat40proPlus.name);      Product iphone12 = new Product();      iphone12.color="黑色";      iphone12.name="iphone12";      iphone12.size=6.1;      iphone12.weight=162;      iphone12.price=6799;      System.out.println("对象的成员变量赋值时");      System.out.println("iphone12 = "+iphone12);      System.out.println("iphone12.name = " + iphone12.name);      System.out.println("iphone12.color = " + iphone12.color);      System.out.println("iphone12.size = " + iphone12.size);      System.out.println("iphone12.weight = " + iphone12.weight);      System.out.println("iphone12.price = " + iphone12.price);      iphone12.playMusic(iphone12.name);  }}

程序运行结果

f35513addff25a57bcc2e38693ffcb49.png


程序运行时内存结构如下
当创建两个对象时,每个对象在堆内存中都有自己独立的内存空间,但是共享同一个Product.class字节码,也就是说无论创建多少个对象,Prdduct.class只会加载一次。

49dbf671bc9e3963f20fd5b717ada1fb.png

而当两个对象的引用指向同一个对象时,此时实际上是栈内存的两个引用指向了堆内存中的同一个对象,当使用其中一个引用变量(例如这里的iphone12Copy)修改对象的成员变量值时,另外一个引用变量获取对象的成员变量时拿到的也是修改之后的值。当对象置为null,此时两个对象的引用不再指向堆内存的对象,堆内存的对象占据的空间会被JVM的垃圾回收器在空闲的时回收。Java程序员不需要像C程序员那样手动开辟堆内存和释放堆内存。JVM的垃圾回收机制会帮我们清理堆内存中的垃圾对象,即没有引用指向的对象。如果在程序中使用没有引用指向的对象会引发程序的空指针(NullPointerException)

package net.ittimeline.java.core.jdk.oop.bean;/** * 两个对象引用指向同一个对象 * * @author tony 18601767221@163.com * @version 2020/12/13 9:54 * @since JDK11 */public class ProductAssignmentTest {  public static void main(String[] args) {      Product iphone12 = new Product();      iphone12.color="黑色";      iphone12.name="iphone12";      iphone12.size=6.1;      iphone12.weight=162;      iphone12.price=6799;      System.out.println("对象的成员变量赋值时");      System.out.println("iphone12 = "+iphone12);      System.out.println("iphone12.name = " + iphone12.name);      System.out.println("iphone12.color = " + iphone12.color);      System.out.println("iphone12.size = " + iphone12.size);      System.out.println("iphone12.weight = " + iphone12.weight);      System.out.println("iphone12.price = " + iphone12.price);      Product iphone12Copy=iphone12;      System.out.println("iphone12Copy = "+iphone12Copy);      System.out.println("iphone12Copy.name = " + iphone12Copy.name);      System.out.println("iphone12Copy.color = " + iphone12Copy.color);      System.out.println("iphone12Copy.size = " + iphone12Copy.size);      System.out.println("iphone12Copy.weight = " + iphone12Copy.weight);      System.out.println("iphone12Copy.price = " + iphone12Copy.price);      iphone12Copy.price=6699;      System.out.println("iphone12Copy.size = " + iphone12Copy.size);      System.out.println("iphone12.size = " + iphone12.size);      //当对象置为null,此时两个对象的引用不再指向堆内存的对象,堆内存的对象占据的空间会被JVM的垃圾回收器在空闲的时回收      iphone12Copy=null;      iphone12=null;      //此时iphone12已经是null,如果继续使用iphone12访问对象的属性和方法,会引发空指针异常    System.out.println(iphone12);    System.out.println(iphone12.color);  }}

程序运行结果

c6d0120d1e3b526e70c2be0b8e2b3f41.png

程序运行时内存结构如下

a33c8bc27171b843d5540c4bba101265.png

this关键字

当局部变量和成员变量重名时,Java采用就近原则,即会使用局部变量。

this关键字用于调用本类的成员变量和成员方法,解决局部变量和成员变量的重命名问题。this.成员变量 表示当前对象的成员变量this.成员方法表示当前对象的成员方法
this 代表所在类的对象引用地址,方法被哪个对象调用,this就代表哪个对象。

    public void setName(String name) {        //this表示所在类的对象的引用地址,方法被哪个对象引用,this代表哪个对象        System.out.println("Product Object setName() this = "+this);        //this关键字区分局部变量和成员变量        this.name = name;    }
package net.ittimeline.java.core.jdk.oop.encapsulation;/** * this访问成员变量测试 * * @author tony 18601767221@163.com * @version 2020/12/13 11:26 * @since JDK11 */public class ThisTest {  public static void main(String[] args) {      //创建两个产品对象      Product note20Ultra = new Product();      //调用note20Ultra对象的setName()方法      note20Ultra.setName("三星Note20Ultra");      System.out.println("note20Ultra = "+note20Ultra);            System.out.println("******楚河******汉界******");            Product mat40proPlus = new Product();      //调用mat40proPlus对象的setName()方法      mat40proPlus.setName("华为mat40proPlus");      System.out.println("mat40proPlus = "+mat40proPlus);  }}

程序运行结果

b225f2e00b9d60f895f6a20d4dde9d3b.png


当创建note20Ultra的Product对象时调用setName()方法,this关键字表示note20Utra的引用地址。
当创建mate40ProPlus的Product对象时调用setName()方法,this关键字表示mate40ProPlus的引用地址。

匿名对象

匿名对象就是没有名字的对象,之前使用new关键字创建的对象都是有名字,每次使用new关键字创建对象时都会在堆内存中开辟新的内存空间存储对象。
匿名对象只能使用一次,通常当某个方法需要传入对象作为参数时可以传入匿名对象。

package net.ittimeline.java.core.jdk.oop.bean;import net.ittimeline.java.core.jdk.oop.encapsulation.Product;/** * 匿名对象 * * @author tony 18601767221@163.com * @version 2020/12/13 14:28 * @since JDK11 */public class AnonymousObjectTest {  public static void main(String[] args) {      Product iphone12=new Product();      iphone12.setName("iphone12");      iphone12.setPrice(6799.00);      //每次使用new关键字都会创造一个新的对象      //匿名对象,使用new关键字创建对象,但是没有名字,而且只能使用一次      new Product();      //它的应用场景是当调用方法时,需要传入某个类的对象,可以传入该类的匿名对象      show(new Product());  }  public static void show(Product product){      product.showProductInfo();  }}

程序运行结果

bd2b3e8a66f89e66acb7b0fe27f53d0a.png

面向对象之封装

访问权限修饰符

Java项目采用包来组织代码,项目的权限分为类权限、同包权限、不同包的子类权限、项目权限。
Java提供了四个权限修饰符,按照权限从大到小依次是public>protected>默认>private。
通常private用于修饰成员变量和成员方法,被private修饰的成员变量和方法只能在本类当中访问,也就是类权限。那么也就意味着不能在其他类中使用对象名.成员变量名和对象名.成员方法的方式来使用对象的成员变量和方法了。此时要访问对象的成员变量之前就需要提供成员变量的get()和set()方法。get()方法用于获取成员变量的变量值,方法名加get前缀,然后跟上成员变量名。set()方法用于给成员变量赋值,方法名加set前缀,然后跟上成员变量名,该方法需要传递一个和成员变量类型一样的型参。

package net.ittimeline.java.core.jdk.oop.encapsulation;/** * 自定义类案例:Product类 * @author tony 18601767221@163.com * @version 2020/12/12 17:21 * @since JDK11 */public class Product {    /********************定义私有化成员变量********************/    /**     * 商品名称     */   private  String name;    /**     * 商品价格     */    private double price;    /********************私有化成员变量的公共get/set方法********************/    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public double getPrice() {        return price;    }    public void setPrice(double price) {        //成员变量的合法性校验        if(price<0.0){            System.out.println("价格非法");        }else{            this.price = price;        }    }    public void showProductInfo(){        System.out.printf("产品的名称是%s,产品的价格是%.2f",name,price);    }}

private关键字修饰成员变量测试

package net.ittimeline.java.core.jdk.oop.encapsulation;/** * private关键字修饰成员变量测试 * * @author tony 18601767221@163.com * @version 2020/12/13 11:09 * @since JDK11 */public class PrivateTest {  public static void main(String[] args) {    // 创建Product对象    Product product = new Product();    // 当给对象的成员变量赋一个错误的值是无法赋值成功    // product.setPrice(-4999.00);    product.setPrice(4999.00);    // 私有成员变量只能在当前类中使用    // product.name;    product.setName("小米10pro尊享版");    product.showProductInfo();  }}

程序运行结果

a53dacbc3701a38532a7982acaaa27f6.png

默认(空)修饰符是不同类,但是在同一个包中可以访问

e8014f5bf4d1454b81badc1cbb13f2e7.png

protected修饰符是不同包的子类访问权限

10a3de363d108148fb01fdc4b2a75c30.png

在日常开发中类通常是定义public,成员变量使用private,定义方法使用public和private,如果是复杂业务,可以抽取多个私有方法。然后定义一个public方法给外部调用,public方法内部调用抽取的private方法。
父类中的方法如果只想给不同包的子类使用,而不对公共访问,可以使用protected权限修饰符。

public修饰符是项目的权限,可以在任意的地方访问。

封装

封装(Encapsulation)是面向对象的三大特性(封装、继承、多态)之一,封装就是隐藏实现细节,仅对外暴露公共的访问方式。

  • 将成员变量抽取到类中,是对数据的一种封装
  • 将代码重构抽取方法,是对代码的一种封装
  • 在定义类时私有化成员变量,提供公共的get/set的访问方法就是封装的一种体现。
    通过封装方法提高了代码的复用性,通过私有化属性,提供公共的get/set方法提高了代码的安全性
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值