Java06-面向对象01三大特性

NiuMT 2020-06-03 20:58:30
Java

面向对象

面向对象的三大特征

万事万物皆对象

  1. 在java语言范畴中,将功能、结构等封装到类中,通过实例化调用具体的功能结构
  2. 涉及到java语言与前端HTML、后端的额数据库交互时,前后端的结构在java层面交互时,都提现为类、对象。

Java类及类的成员

image-20201006214617787

也可以不定义对象的句柄,而直接调用这个对象的方法。这
样的对象叫做匿名对象。如: new Person().shout(); 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。我们经常将匿名对象作为实参传递给一个 方法。

类中属性

成员变量 局部变量
声明的位置 直接声明在类中 方法形参、代码块、构造器
修饰符 private、public、static、final等 可用final
初始化值 有默认初始化值 必须显示赋值
内存加载位置 堆空间 栈空间

类中方法

Java 里的方法不能独立存在,所有的方法必须定义在类里。

修饰符 返回值类型 方法名 (参数类型 形参1, 参数类型 形参2,...){
    方法体程序代码
    return 返回值
}

方法重载

同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。与权限修饰符、返回值类型、形参无关

可变形参

  1. 声明格式: 方法名 (参数的类型名… 参数名)
  2. 可变参数:方法参数部分指定类型的参数个数是可变 多 个: 0 个, 1 个或多个
  3. 可变个数形参的方法与本类中同名,形参不同的方法之间彼此构成重载
  4. 可变参数方法的使用与本类中同名,形参为数组的方法之间不构成重载,无法共存
  5. 方法的参数部分有可变形参,需要放在形参声明的最后
  6. 在 一个方法的形参位置,最多只能声明一个可变个数形参

参数值传递

Java
里方法的参数传递方式只有一种: 值传递 。 即将实际参数值的副本
(复制品)传入方法内,而参数本身不受 影响 。

class  Value{
    int i=15;
}

public class TestArray {
    public static void main(String[] args) {
        TestArray testArray = new TestArray();
        testArray.first();
    }

    public void first(){
        int i =5;
        Value v = new Value();
        v.i = 25;
        second(v, i);
        System.out.println(v.i);  // 20
    }

    public void second(Value v , int i ){
        i = 0;
        v.i=20; // 局部变量指向的内存中类的属性被修改
        Value val = new Value();
        v=val;  // 局部变量指向了新的内存
        // ==存的是地址值,改的也是地址值==
        System.out.println(v.i+" "+i);  // 15  0
    }
}
int[] arr1 = new int[]{1,2,3};
char[] arr2 = new char[]{'1','2','3'};
System.out.println(arr1);// [I@1b6d3586
System.out.println(arr2);// 123  
// println()重载

三大特性一:封装

把该隐藏的隐藏起来,该暴露的暴露出来 。 这就是封装性的设计思想。

“高内聚,低耦合”:类的内部数据操作细节自己完成,不允许外部干涉;仅对外暴露少量的方法。

封装性的体现:

四种权限修饰符:

从小到大:private—>缺省—>protected—>public

修饰符 类内部 同一个包 不同包的子类 同一个工程
private yes
缺省 yes yes
protected yes yes yes
public yes yes yes yes

构造器

构造器的作用:创建对象;给对象进行初始化。

构造器的特点:

注 意:

赋值的位置及顺序:

  1. 默认初始化;int i;
  2. 显式初始化;int i=9;
  3. 构造器中初始化;
  4. 通过“对象 属性“或“对象 方法”的方式赋值;

image-20201008105338180

this、package、import关键字

this

class Person{
    private int age=99;
    public void setAge(int age){
        //形参和属性重名,需使用this区分,不重名则无要求。
        //形参=形参, 赋值失败
        age = age;
        //可以赋值
        this.age=age;
    }
}

注意:

可以 在类的构造器中使用 “ 形参列表 的方式,调用本类中重载的其他的构造器!

明确 :构造器中不能通过 “形参列表 “的方式调用自身构造器

如果 一个类中声明了 n 个构造器,则最多有 n-1 个构造器中使用了
“ 形参列表

“this 形参列表 必须声明在类的构造器的==首行==!

在类的一个构造器中,最多只能声明一个 “形参列表”

class Person{
    private int age;
    private String name;

    public Person(){
        System.out.println("新对象实例化");
    }

    public Person(String name){
        this();
        this.name = name;
    }

    public Person(String name, int age){
        this(name);
        this.age = age;
    }
}

package

import

注意:

  1. 在源文件中使用 import 显式的导入指定包下的类或接口
  2. 声明在包的声明和类的声明之间。
  3. 如果需要导入多个类或接口,那么就并列显式多个 import 语句即可
  4. 举例 :可以使用 java.util.* 的方式,一次性导入 util 包下所有的类或接口。
  5. 如果导入的类或接口是 java.lang 包下的,或者是当前包下的,则可以省略此 import 语句。
  6. 如果在代码中使用不同包下的同名的类。那么就需要使用类的==全类名==的方式指明调用的是哪个类。
  7. 如果已经导入 java.a 包下的类。那么如果需要使用 a 包的子包下的类的话,仍然需要导入。
  8. import static 组合的使用:调用指定类或接口下的静态的属性或方法。

三大特性二:继承

作用:

  1. 继承的出现减少了代码冗余,提高了代码的复用性。
  2. 继承的出现,更有利于功能的扩展。
  3. 继承的出现让类与类之间产生了关系 ,提供了多态的前提

Java 只支持==单继承==和多层继承,不允许多重继承
:一个子类只能有一个父类;一个父类可以派生出多个子类。

方法重写

super关键字

super和 this 的用法相像,this 代表本类对象的引用,super 代表父类的内存空间的标识。

  1. 在子类的方法或者构造器中,通过使用”super.属性”和”super.方法”,显示地调用父类中声明的属性或方法,一般省略super
  2. 特殊情况:当子父类出现同名属性或方法时可以用 super 表明调用的是父类(父类的父类)中的属性或方法,使用this表明调用的是子类中的属性或方法。
  3. super可以调用父类的构造器。在子类的构造器中显式地使用”super(形参列表)”的方式调用父类中指定的构造器。且必须声明在子类构造器的首行。

    • 子类中所有的构造器默认都会访问父类中空参数的构造器,在类的多个构造器汇总,至少有一个调用了”super(形参列表)”.
    • 当父类中没有空参数的构造器时,子类的构造器必须通过 this(参数列表) 或者 super(参数列表) 语句指定调用本类或者父类中相应的构造器 。 同时==只能二选一,且必须放在构造器的首行==。
    • 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错。

image-20201010133728761

明确:虽然创建子类时,调用了父类的构造器,但是自始至终就创建过一个对象,即new的对象。

三大特性三:多态

使用前提:1. 类的继承关系;2. 方法重写

对象的多态性:父类的引用指向子类的对象

多态的使用:虚拟方法调用

在编译器,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写的父类方法,不能调用子类特有的属性和方法。==编译看左边,运行看右边==。

多态性不适用属性!!!子父类中同名的属性,多态时调用父类的属性。

image-20201012152328064

小结:方法的重载与重写

  1. 二者的定义细节: 略
  2. 从编译和运行的角度看:
    重载,是指允许存在多个同名方法,而这些方法的参数不同
    。 编译器根据方法不
    同的参数表, 对同名方法的名称做修饰。对于编译器而言,这些同名方法就成 了不同的方法。 它们的调用地址在编译期就绑定了 。 Java 的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
    所以:
    对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,
    这称为 “早绑定”或“静态绑定。而对于多态,只有等到方法调用的那一刻 解释运行器才会确定所要调用的具体方法,这称为 “晚绑定”或“动态绑定 。
  3. 重载不表现为多态性,重写表现为多态性。

instanceof 、Casting 操作符

instanceof

有了对象的多态性后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类,导致编译时只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用。

如何才能调用子类特有的属性和方法?答:使用强制类型转换!但可能会出现异常。

Persion p = new Man();
Man m = (Man)p; // 可以调用Man特有的属性和方法
Woman m = (Woman)p; // 出现异常ClassCastException
a  instanceof A : 判断对象a是否为类A的实例:如果是,返回true,否则,返回false。
// 为了避免在向下转型时出现ClassCastException异常,所以需要先判断类型
// 编译通过,运行不过
Person p = new Woman();
Man m = (Man)p;

Person p = new Person();
Man m = (Man)p;

// 编译通过,运行通过
Object obj = new Woman();
Person p = (Person)obj;

// 编译不通过
Man m = new Woman();
class Base {
    int count = 10;
    public void display() {
        System.out.println(this.count);
    }
}

class Sub extends Base {
    int count = 20;
    public void display() {
        System.out.println(this.count);
    }
}

public class FieldMethodTest {
    public static void main(String[] args){
        Sub s = new Sub();
        System.out.println(s.count);//20
        s.display();//20
        Base b = s;//多态
         // == 对于引用数据类型,比较的是两个引用数据类型的地址是否相等
        System.out.println(b == s);// true
        System.out.println(b.count);// 10
        b.display();//20
    }
}

面试题:多态是编译时行为还是运行时行为?

import java.util.Random;

//面试题:多态是编译时行为还是运行时行为?
//证明如下:
class Animal  {
    protected void eat() {
        System.out.println("animal eat food");
    }
}

class Cat  extends Animal  {
    protected void eat() {
        System.out.println("cat eat fish");
    }
}

class Dog  extends Animal  {
    public void eat() {
        System.out.println("Dog eat bone");
    }
}

class Sheep  extends Animal  {
    public void eat() {
        System.out.println("Sheep eat grass");
    }
}

public class InterviewTest {
    public static Animal  getInstance(int key) {
        switch (key) {
        case 0:
            return new Cat ();
        case 1:
            return new Dog ();
        default:
            return new Sheep ();
        }
    }
    public static void main(String[] args) {
        int key = new Random().nextInt(3);
        System.out.println(key);
        Animal  animal = getInstance(key);
        animal.eat(); 
    }
}
//考查多态的笔试题目:
public class InterviewTest1 {

    public static void main(String[] args) {
        Base base = new Sub();
        base.add(1, 2, 3); // sub_1, 多态

        Sub s = (Sub)base;
        s.add(1,2,3);// sub_2
    }
}

class Base {
    public void add(int a, int... arr) {
        System.out.println("base");
    }
}

class Sub extends Base {
    public void add(int a, int[] arr) {
        System.out.println("sub_1");
    }
    public void add(int a, int b, int c) {
        System.out.println("sub_2");
    }
}

Object 类

==操作符与 equals 方法

==:

  1. 基本类型比较值:只要两个变量的值相等,即为 true 。
  2. 引用类型比较引用 (是否指向同一个 对象):只有指向同一个对象时,才返回 true

用==进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错

equals():

  1. 是一个方法,而非运算符。只适用于引用数据类型

  2. 默认equals():object类中的equals()的定义:和 == 一致、

    public boolean equals(Object obj)}{
        return (this == obj);
    }
    
  3. 特例 :当用 equals() 方法进行比较时对类 File 、 String 、 Date 及包装类Wrapper Class 来说是比较类型及内容而不考虑引用的是否是同一个对象

  4. 当自定义使用 equals() 时,可以重写。用于比较两个对象的内容是否都相等。重写equals()方法的原则:

    • 对称性:如果 x.equals(y) 返回是 true,那么 y.equals(x) 也应该返回是true 。
    • 自反性:x.equals(x) 必须返回是 true
    • 传递性:如果 x.equals(y) 返回是 true,而且 y.equals(z) 返回是 true,那么 z.equals(x) 也应该返回是 true 。
    • 一致性:如果 x.equals(y) 返回是 true,只要 x 和 y 内容一直不变,不管你重复 x.equals(y) 多少次,返回都是 true.
    • 任何情况下 x.equals(null) 永远返回是 false
    • x.equals(和x不同类型的对象), 永远返回是false 。

    先用==比较地址是否相同,然后再比较属性值是否全部相同。

面试题:== 和 equals 的区别

1 == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型,就是比较内存地址

2 equals 的话,它是属于 java.lang.Object 类里面的方法,如果该方法没有被重写过默认也是==,我们可以看到 String 等类的 equals 方法是被重写过的,而且 String 类在日常开发中用的比较多,久而久之,形成了 equals 是比较值的错误观点。

3 具体要看自定义类里有没有重写 Object 的 equals 方法来判断。

4 通常情况下,重写 equals 方法,会比较类中的相应属性是否都相等。

toString() 方法

  1. 输出一个对象的引用时,实际上是调用当前对象的toString()。”System.out.println(ClassA);”

  2. Object类中的toString()方法:返回类名和它的引用地址

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    
  3. String、Data、File、包装类等都重写了Object类中的toString()方法,使得在调用toString方法时,返回“实体内容”信息。进行 String 与其它类型数据的连接操作时,自动调用 toString() 方法

包装类 (Wrapper)和单元测试

JUnit单元测试:

  1. add libraries —- JUnit 4
  2. 创建Java类,进行单元测试:1)Java类是public的,2)此类提供公共的无参构造器
  3. 此类中声明单元测试方法:public,void,无形参。
  4. 单元测试方法上需要声明注解:@Test,需要导入包:import org.junit.Test
  5. 声明好单元测试方法后,在方法体内测试相关代码

包装类

基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Charactor

image-20201012142326963

面试题:

Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);// 1.0   :前后要求类型一致

Object o2;
if (true)
    o2 = new Integer(1);
else
    o2 = new Double(2.0);
System.out.println(o2);//1
public void method() {
    Integer i = new Integer(1);
    Integer j = new Integer(1);
    System.out.println(i == j);// false  比较地址值

    Integer m = 1;
    Integer n = 2;
    System.out.println(m == n);// true  
    // Integer内部类IntegerCache中定义了Integer[],保存了-128 -- +127
    // 如果使用自动装箱的方法,给Integer赋值的访问在-128 -- +127范围内时,可以直接使用数组中的元素,不需要new, 即地址值一样

    Integer x = 128;
    Integer y = 128;
    System.out.prinltn(x == y);// false new的对象,地址值不一样
}