13.Java对象

13.Java对象
转载请保留原文链接: http://dashidan.com/article/java/basic/13.html

Java是`OOP`(Object Oriented Programming)面向对象编程语言. java面向对象的三大特点: 封装, 继承和多态.

① Java中万事万物皆对象?

真相只有一个

Java中万事万物皆对象?
这个说法的由来, 是因为java中所有的类默认都继承自Object类. 但这个说法是不准确的. 很多资料中这样说, 曾经给我带来过很大的困扰.Java属于强类型语言, 方法作为属性存在于对象中, 不作为独立的对象存在. 所以说在Java中万事万物皆对象的说法是不准确的, 方法和基本类型变量都不是对象的概念.在一些弱类型语言比如javascript, 方法也可以作为对象的概念来使用, 作为参数传递, 这一点和java是有区别的.

② 面向对象编程思想

提到面向对象, 不得不提到面向过程编程. 以C语言为例, 每个方法作为一个执行过程存在, 面向过程编程是一种面向计算机思维, 从计算机执行的角度思考编写代码的编程方式.

从面向过程编程的基础上, 发展出来了一种新的编程思想, 面向对象编程, 来应对更复杂的应用场景.面向对象编程是一种面向的逻辑思维的编程方式, 将代码中的通用性抽象出来, 模拟现实世界的归类, 建立程序模型, 编写代码的过程. 对于程序员更加友好, 对于复杂度更高的程序更易于控制.

举个栗子

在生物分类学中, 分为界门纲目科属种, 按照生物的种群和等级划分.

猫:

动物界->脊索动物门->哺乳纲->哺乳纲->食肉目->猫科->猫属->猫种

虎:

动物界->脊索动物门->哺乳纲->哺乳纲->食肉目->猫科->豹属->虎种

同处于猫科, 是猫科子类中的一种, 具有猫科的全部属性. 用Java面向对象的思想来编程可以这样表达:
猫科作为父类, 作为子类, 继承自猫科拥有父类猫科的全部属性, 独有的属性写在子类(猫类或虎类)中.

面向对象编程思想与人类的逻辑思维很接近.

③ 构造函数

构造函数是一种特殊的函数. 构造函数与类名相同, 可以有参数也可以无参数, 在对象被创建时自动执行.

1.构造函数的作用:

  • 在创建对象时完成完成对象数据成员的初始化
  • 为对象数据成员开辟内存空间
  • 为对象创建标识符

2.默认构造函数

当用户没有显式的定义构造函数时, 编译器会为类生成一个默认的构造函数, 称为默认构造函数. 默认构造函数名与类名相同, 没有参数.例:

public class Demo1 {
    // 默认构造函数
    public Demo1(){
    }
}

默认构造函数在代码中看不到, 在编译时自动生成.

3.构造函数的特点

无论是用户自定义的构造函数还是默认构造函数都主要有以下特点:

  • 在对象被创建时自动执行.
  • 构造函数的函数名与类名相同.
  • 没有返回值类型、也没有返回值.
  • 构造函数不能被显式调用.

4.构造函数的执行

通常是通过new关键字创建对象时执行.

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 构造函数的执行
 */
public class Demo1 {

    public static void main(String args[]) {
        System.out.println("Demo1运行开始");
        TestStructure testStructure = new TestStructure(1, "大屎蛋教程网", "dashidan.com");
        int id = testStructure.getId();
        String url = testStructure.getUrl();
        String name = testStructure.getName();
        System.out.println("id " + id + " url " + url + " name " + name);
        System.out.println("运行完毕");
    }
}

/**
 * 测试构造方法类
 */
class TestStructure {
    private int id;
    private String name;
    private String url;

    /**
     * 构造函数
     */
    public TestStructure(int id, String name, String url) {
        /** 构造函数初始化成员变量*/
        System.out.println("执行TestStructure构造函数");
        this.id = id;
        this.name = name;
        this.url = url;
        System.out.println("id " + id + " name " + name + " url " + url);
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getUrl() {
        return url;
    }
}

输出:

Demo1运行开始
执行TestStructure构造函数
id 1 name 大屎蛋教程网 url dashidan.com
id 1 url dashidan.com name 大屎蛋教程网
运行完毕

c++除了构造函数,还有析构函数的概念. 析构函数也是一种特殊的成员函数. 它执行与构造函数相反的操作,通常用于撤消对象时的一些清理任务,如释放分配给对象的内存空间等. Java中只有构造函数,而没有析构函数.

④ 参数的值传递与引用传递

Java中方法中给变量赋值有2种方式: 值传递和引用传递.传递基本类型,String参数时是值传递. 传递引用类型参数时, 是引用传递.

1.值传递

传递的是值的拷贝. 也就是说传递参数时, 将值拷贝了一份, 与原有变量不再有关系. 基本类型字符串传递采用的是值传递.

示例代码:

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 值传递
 */
public class Demo2 {

    public static void main(String[] args) {
        int a = 1;
        String name = "大屎蛋";
        changeValue(a, name);
        /** 原值不变*/
        System.out.println("原值 a : " + a);
        /** 原字符串不变*/
        System.out.println("原值 name : " + name);
    }

    public static void changeValue(int num, String str) {
        num++;
        /** 输出改变的值, 改变了参数的值, 原值不变*/
        System.out.println("changeValue num : " + num);
        /** 添加字符串, 改变了作为参数传进来的字符串的值, 原字符串不变*/
        str += "dashidan.com";
        System.out.println("changeValue str : " + str);
    }
}

输出:
    changeValue num : 2
    changeValue str : 大屎蛋dashidan.com
    原值 a : 1
    原值 name : 大屎蛋

2.引用传递

引用传递是指给索引对象赋值时, 是将对象内存的地址作为参数传递. 新的对象与原有指向同一份数据. 当修改对象的变量时, 原有指向改数据的对象都发生改变.

代码示例:

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 引用传递
 */
public class Demo3 {
    public static void main(String[] args) {
        TestObject testObject1 = new TestObject();
        testObject1.setName("大屎蛋教程网");
        /** 对象赋值,这里是引用传递,testObject1 将对象地址赋值给 testObject2, 他们指向同一个对象*/
        TestObject testObject2 = testObject1;
        System.out.println("修改前 testObject1 name " + testObject1.getName());
        System.out.println("修改前 testObject2 name " + testObject2.getName());
        /** 改变 testObject2 的name,testObject1的name也发生变化,因为他们指向同一个数据*/
        testObject2.setName("dashidan.com");
        System.out.println("修改后 testObject1 name " + testObject1.getName());
        System.out.println("修改后 testObject2 name " + testObject2.getName());
    }
}

/**
 * 引用传递测试对象
 */
class TestObject {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
输出:
    修改前 testObject1 name 大屎蛋教程网
    修改前 testObject2 name 大屎蛋教程网
    修改后 testObject1 name dashidan.com
    修改后 testObject2 name dashidan.com

⑤ 对象的封装

1.隐藏实现过程

封装是把过程和数据隐藏起来,对数据的访问只能通过已定义的接口. 面向对象计算始于这个基本概念. 通过private关键字可以将变量或者方法设置为只有本类可以访问, 其他类无法访问该数据, 这样便实现了数据的隐藏.

举个栗子

你有一只宠物, 宠物的名字你不希望别人随便改, 可以将宠物的名字设置为私有private.

package com.dashidan.lesson5;

/**
 * 大屎蛋教程网-dashidan.com
 *
 * Java教程进阶篇: 5.Java对象(4):封装
 */
public class Pet {
    /** 私有属性名字*/
    private String name;
}

2.公开数据访问接口

你这只宠物的名字编程私有之后, 你希望给他改个名字, 可以将改名的方法设置为公开方法. 这样就能通过统一的开放接口来设置宠物的名字, 并且可以开放一个获取名字的接口, 来获得这个私有的属性值.

示例代码:

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 引用传递
 */
public class Demo3 {
    public static void main(String[] args) {
        TestObject testObject1 = new TestObject();
        testObject1.setName("大屎蛋教程网");
        /** 对象赋值,这里是引用传递,testObject1 将对象地址赋值给 testObject2, 他们指向同一个对象*/
        TestObject testObject2 = testObject1;
        System.out.println("修改前 testObject1 name " + testObject1.getName());
        System.out.println("修改前 testObject2 name " + testObject2.getName());
        /** 改变 testObject2 的name,testObject1的name也发生变化,因为他们指向同一个数据*/
        testObject2.setName("dashidan.com");
        System.out.println("修改后 testObject1 name " + testObject1.getName());
        System.out.println("修改后 testObject2 name " + testObject2.getName());
    }
}

/**
 * 引用传递测试对象
 */
class TestObject {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
输出:
    哈拆迁

⑥ 对象的继承

Java继承的本质是子类继承父类, 拥有父类的属性和方法, 是代码复用基础. 子类可以有自己的方法和属性.

1.继承方式

通过关键字extends.我们以猫和狗继承是宠物, 宠物都有的行为. 猫有吃鱼的行为, 狗有吃骨头的行为为例实现这个继承关系.

父类Pet:

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 对象的继承
 * Pet类 作为 父类
 */
public class Pet {

    public void walk() {
        System.out.println("Pet walk");
    }

    public void eat() {
        System.out.println("Pet eat");
    }
}
子类`Cat`:
package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 对象的继承
 * Cat 继承自 Pet类
 */
public class Cat extends Pet {
    public void eatFish() {
        System.out.println("Cat eatFish.");
    }

    @Override
    public void eat() {
        System.out.println("Cat eat");
    }
}
子类`Dog`:
package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 对象的继承
 * Dog 继承自 Pet类
 */
public class Dog extends Pet {
    public void eatBone() {
        System.out.println("Dog eatBone.");
    }

    @Override
    public void eat() {
        System.out.println("Dog eat");
    }
}
执行类
package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 对象的继承
 */
public class Demo5 {

    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        /** 调用父类的方法*/
        cat.walk();
        dog.walk();
        /** 调用子类的方法*/
        cat.eatFish();
        dog.eatBone();
    }

}
输出:
    Pet walk
    Pet walk
    Cat eatFish.
    Dog eatBone.

java单根继承是指单个类只能继承一个父类, 在c++中是多根继承, 一个类可以同时继承多个父类.

3.对象静态绑定与动态绑定

绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来. java绑定分为静态绑定和动态绑定.

  • 静态绑定

静态绑定也称作前期绑定. java中的方法只有final, static, private构造方法静态绑定. 静态绑定发生在编译时, 静态绑定用信息来完成.

  • 动态绑定

动态绑定(dynamic binding)也称作后期绑定. java程序运行时根据具体对象的类型进行绑定. 动态绑定发生在运行时. 动态绑定则需要用对象信息来完成.

⑦ 对象的多态

1.多态的前提

当子类和父类存在同一个方法, 子类覆写了父类的方法, 程序在运行时调用父类的方法还是子类覆写的方法呢?
Java的多态解决了整个问题. 运行时根据对象类型来判断执行对应对象的方法.

多态的前提条件
1. 有继承关系.
2. 子类重写父类的方法.
3. 父类引用指向子类.

举个栗子

猫和狗都继承自宠物类, 宠物有吃的行为. 猫吃鱼狗吃骨头, 他们吃的行为不一样. 我们可以采用多态的方式来实现.

代码示例:

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程进阶篇: 7.Java对象(6):静态绑定动态绑定与多态
 */
public class Demo6 {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        /**
         * cat 和 dog 覆写了 父类的方法 eat 调用eat方法 时动态判断对象类型
         * cat 对象 执行了 Cat中的 eat方法
         * dog 对象 执行了 Dog中的 eat方法
         */
        cat.eat();
        dog.eat();
    }
}
输出:
    Cat eat
    Dog eat

⑧ 引用当前对象与父对象

java中使用用this来引用当前对象, super引用当前对象的父对象.

1.this

使用this获取当前对象的索引.如果省略this. 系统会默认自动添加加上this.来指向当前对象.

以下代码中的setName方法, 该方法中的传入的参数name与成员变量name重名, 这时需要用this.name来表明引用成员变量.

代码示例:

package com.dashidan.lesson8;

/**
 * 大屎蛋教程网-dashidan.com
 *
 * Java教程进阶篇: 8.Java对象(7):引用当前对象与父对象:this, super
 * Pet类
 */
public class Pet {
    /**
     * 私有成员变量name
     */
    private String name;

    public String getName() {
        /** 默认可以省略this. 系统会自动加上this.*/
        return name;
    }

    public void setName(String name) {
        /** this.name 通过this来表明引用当前对象成员变量name*/
        this.name = name;
    }
}

2.super

super引用当前对象的父对象. 需要访问父类方法或者变量的时候, 可以使用super.来访问.示例代码:

cat类:

在调用Cat类的eat()方法时, 方法体中通过super.eat()调用了父类的eat()方法. 故父类的eat()方法被执行, Pet eat.输出到控制台.

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 对象的继承
 * Cat 继承自 Pet类
 */
public class Cat extends Pet {
    public void eatFish() {
        System.out.println("Cat eatFish.");
    }

    @Override
    public void eat() {
        System.out.println("Cat eat");
    }

    /**
     *调用父类方法
     */
    public void petEat() {
        super.eat();
    }
}
运行程序:
package com.dashidan.lesson8;

/**
 * 大屎蛋教程网-dashidan.com
 *
 * Java教程进阶篇: 8.Java对象(7):引用当前对象与父对象:this, super
 */
public class Demo1 {

    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
    }
}
输出:
    Pet eat.

3.this与super在构造函数中的使用

  • superthis均需放在构造方法内第一行. 否则编译不通过.

每个子类构造方法的第一条语句, 都是隐式调用super(), 如果父类没有这种形式的构造函数, 那么在编译的时候就会报错.

  • thissuper不能同时出现在一个构造函数里面.

因为this必然会调用其它的构造函数, 其它的构造函数必然也会有super语句的存在. 所以在同一个构造函数里面有相同的语句, 就失去了语句的意义, 编译器也不通过.

static环境不能使用this, super。this和super都指的是对象, 是动态绑定, 所以都不能在static环境中使用(静态绑定)包括: static变量, static方法, static语句块.

⑨ 抽象方法与抽象类

1.抽象方法

抽象方法是一种特殊的方法, 只有声明, 而没有具体的实现. 抽象方法必须用abstract关键字进行修饰. 抽象方法的声明格式为:

public abstract void eat();

2.抽象类

如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰.只要包含抽象方法, 必须命名为抽象类. 抽象类也可以包含普通方法.

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 抽象方法与抽象类
 * AbstractPet类 作为 父类 抽象类
 */
public abstract class AbstractPet {
    public abstract void sleep();
}
`Fish`类作为子类:
package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 抽象方法与抽象类
 * Fish 类 继承自 AbstractPet类
 */
public class Fish extends AbstractPet {
    /**
     *实现抽象方法 sleep
     */
    @Override
    public void sleep() {
        System.out.println("Never sleep.");
    }
}
运行类
package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 抽象方法与抽象类
 */
public class Demo9 {
    public static void main(String[] args) {
        Fish fish = new Fish();
        fish.sleep();
    }
}
输出:
    Never sleep.
抽象类与普通类的区别: 1. 抽象方法必须为`public`或`protected`(`private`不能被子类继承,子类无法实现该方法), 缺省情况下默认为`public`. 2. 如果一个类继承于一个抽象类, 则子类必须实现父类的抽象方法. 如果子类没有实现父类的抽象方法, 则必须将子类也定义为为`abstract`类.

3.抽象类创建对象

真相只有一个

抽象类不能用来创建对象吗?
答案是:抽象类可以被用new的方式创建. 通过匿名内部类方式.

附带真相的代码:

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 抽象类创建对象
 */
public class Demo10 {
    public static void main(String[] args) {
        AbstractPet abstractPet = new AbstractPet() {
            @Override
            public void sleep() {
                System.out.println("AbstractPet sleep");
            }
        };
        abstractPet.sleep();
    }
}
输出:
    AbstractPet sleep.

虽然可以用new的方式创建抽象类, 但不建议这么用, 这样做失去了抽象的意义.

示例代码中出现了@Override标签.
Java中@Override标签作用

⑩ 接口

接口interface在软件工程中, 泛指供其他人调用的方法或者函数. 从这里可以体会到Java语言设计者的初衷-对行为的抽象.

1.接口的声明

interface关键字声明接口.接口中可以含有变量和方法. 但是要注意, 接口中的变量会被隐式地指定为public static final变量. 接口中的方法必须都是抽象方法.

public interface Pet{
...
}

2.接口的实现

一个类实现特定的接口需要使用implements关键字. 需要实现该接口中定义的方法, 如果没有全部实现, 需要将该类声明为abstract.

public class Bird implements IPet {
...
}
示例代码: `IPet`接口:
package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 对象的继承
 * 接口 IPet
 */
public interface IPet {
    void eat();
}

`Bird`类:
package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 对象的继承
 * 接口
 */
public class Bird implements IPet {
    @Override
    public void eat() {
        System.out.println("Bird eat.");
    }
}

运行类:
package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 对象的继承
 * 接口
 */
public class Demo11 {
    public static void main(String[] args) {
        Bird bird = new Bird();
        bird.eat();
    }
}
输出: Bird eat.

3.抽象类和接口的区别

  • 抽象类可以提供成员方法的实现细节, 而接口中只能存在public abstract方法.
  • 抽象类中的成员变量可以是各种类型, 而接口中的成员变量只能是public static final类型.
  • 接口中不能含有静态代码块以及静态方法, 而抽象类可以有静态代码块和静态方法.
  • 一个类只能继承一个抽象类, 而一个类却可以实现多个接口.
  • 抽象类作为很多子类的父类, 它是一种模板式设计, 而接口是一种行为规范.
  • 抽象类和接口所反映的设计理念是不同的, 抽象类所代表的是is-a的关系, 而接口所代表的是like-a的关系.

⑪ 对象实例类型判断

Java中的instanceof运算符是用来在运行时判断对象是否是指定类的实例.子类实例instanceof父类, 返回ture. 因为继承关系, 子类的实例也是父类的实例.instanceof是二目运算符, 返回一个布尔值, 表示该对象是否是指定类的实例.用法:

boolean result = object instanceof class

示例代码:

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 对象实例类型判断
 */
public class Demo12 {
    public static void main(String[] args) {
        Pet pet = new Pet();
        boolean isInstance = pet instanceof Pet;
        System.out.println("pet instanceof Pet: " + isInstance);
        Cat cat = new Cat();
        /** 子类也属于父类的一个实例*/
        isInstance = cat instanceof Pet;
        System.out.println("cat instanceof Pet: " + isInstance);
    }
}
输出: pet instanceof Pet: true cat instanceof Pet: true

⑫ 对象向上转型和向下转型

java中子类对象转为父类对象称为向上转型, 反之称为向下转型.

1.向上转型

向上转型时, 子类独有的方法会遗失, 只保留父类拥有的方法. 如果子类覆写了父类的方法则调用子类的这个方法. 向上转型不用强制转型.

2.向下转型

父类引用的对象转换为子类类型称为向下转型. 向下转型需要强制转型。示例类型与转型类型需要一致, 不一致会报错.

示例代码:

package com.dashidan.lesson12;

/**
 * 大屎蛋教程网-dashidan.com
 * <p>
 * Java教程基础篇: 12.Java对象
 * 对象向上转型和向下转型
 */
public class Demo13 {
    public static void main(String[] args) {
        Cat cat = new Cat();
        /**
         * testUpcasting 需要传入父类Pet实例,这里传入的是子类cat实例的引用
         * 自动向上转型
         * 由于子类Cat覆写了父类的cat()方法,执行子类的eat()方法
         */
        testUpCasting(cat);


        /** 父类对象的引用,指向子类实例*/
        Pet pet = new Cat();
        /**
         * testUpcasting 需要传入父类Pet实例,这里传入的是父类Pet的引用
         * 强制向下转型
         * 由于子类Cat覆写了父类的cat()方法,执行子类的eat()方法
         */
        testDownCasting((Cat) pet);
    }

    /**
     * 测试向上转型
     */
    public static void testUpCasting(Pet pet) {
        pet.eat();
    }

    /**
     * 测试向下转型
     */
    public static void testDownCasting(Cat pet) {
        pet.eat();
    }
}

输出:

    Cat eat.
    Cat eat.

⑬ 相关文章

Java教程入门到精通

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值