【Java2】面向对象



2016年06月12日    Author:Guofei

文章归类: Java    文章编号: 12002

版权声明:本文作者是郭飞。转载随意,但需要标明原文链接,并通知本人
原文链接:https://www.guofei.site/2016/06/12/java2.html


方法

public/protected/private stastic void/int methodName(int inp){
  return inp
}
访问修饰符 本类 同包 跨包子类 其它
private      
默认(不加修饰符)    
protected  
public

案例:

public class HelloWorld {
    // 1. 无参数无返回值方法
    public void printStar() {
        System.out.println("*********");
    }
    // 2. 有参数有返回值方法
    public float myFunc(float a,float b){
        printStar(); // 3. 不在主方法中调用方法,就可以不用 new 一个
        return a+b;
    }

    public static void main(String[] args) {
        // 4. 在main中调用方法:
        HelloWorld helloWorld = new HelloWorld();
        helloWorld.printStar();
        float c= helloWorld.myFunc(1.2f,1.5f);
        System.out.println(c);
        helloWorld.printStar();
    }
}

重载

方法的重载:

  • 方法名相同,但入参列表不同。
  • 入参列表不同指的是入参的类型不同(顺序、个数、类型)。如果按顺序看类型和个数相同但参数名不同,是不能重载的。例如 func(String a,int b)func(String c, int d) 认为是入参相同。
  • 返回值可以不同,访问修饰符可以不同。
// 一个重载的例子
public class D {
    public void func(int a) {
        System.out.println("int");
    }

    public void func(String a) {
        System.out.println("String");
    }

    public static void main(String[] args) {
        D d = new D();
        d.func(1);
        d.func("1");
    }
}

可变参数类型

可变参数列表

public class HelloWorld {
    public int get_sum(int... n) { // 可变参数列表
        int sum = 0;
        for (int i : n) {
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) {
        HelloWorld helloWord = new HelloWorld();
        System.out.println(helloWord.get_sum(1));
        System.out.println(helloWord.get_sum(1, 2, 3));
    }
}

可变参数类型2

public class HelloWorld {
    public boolean isIn(int a, int... n) { // 可变参数必须放到最后。这个n可以传入数组,因此不能再写一个同名方法重载另一个n是数组的方法了。
        for (int i : n) {
            if (a == i) {
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        HelloWorld helloWord = new HelloWorld();
        System.out.println(helloWord.isIn(1, 1, 2, 3, 4));
        System.out.println(helloWord.isIn(5, 1, 2, 3));
    }
}

可变参数列表:

  • 可变参数必须放到最后。
  • 可变参数位置的可以传入数组。因此不能再写一个同名方法重载另一个n是数组的方法了(因为已经有了这样一个方法了)。
  • 多个同名方法同时满足时,可变参数最后被调用。例如,两个方法 func(int a, int b), func(int... a),那么调用 func(1,2) 会优先调用第一个。

方法的传值问题:在方法中改变一个变量的值,方法外会怎样?

  • (与 Python 一样)
  • 如果传入int等,方法中改变值后,方法外不变
  • 如果传入数组,方法外会跟着变
  • 如果传入自定义对象,方法外会跟着变

初始化

// 下面是一个声明+初始化对象的语言。
Cat one = new Cat();

以上语句完成了下面3给步骤

  1. 声明一个对象:在栈里面开辟一个空间,内容为 null
  2. 初始化一个对象:在堆里面开辟一个空间,初始化一个对象。
  3. 通过赋值操作,把栈和堆关联起来:堆空间的内存号存入到栈空间。

构造方法

  • 构造方法的方法名必须与类名相同
  • 构造方法没有返回值
  • 构造方法可以有参数或无参数,因此可以重载。
  • 构造方法只能在实例化时被调用。不能在其它地方被调用,不能被普通方法调用。
  • 如果没有显式指定构造方法,会自动指定一个无入参的构造方法
  • 构造方法可以用相互调用,用 this(args)
  • (不推荐)可以写一个同名的普通方法,不会有语法错误,但不推荐这么干。

package com.guofei.learnjava;

public class Cat {
    // 属性:
    String name;
    int month;
    double weight;
    String species = "中华田园猫";

    public Cat() { // 无参数的构造方法
        System.out.println("无参构造一个猫"); // 构造方法不能有返回值
    }

    // 构造方法可以多态
    public Cat(String name) { // 有参数的构造方法
        this(); // 构造方法可以相互调用,必须放第一行
        System.out.println("有参构造一个猫");
        this.name = name; // (没啥用:如果不加this,会自动寻找最近的name赋值,就不是属性那个name,改名也可以解决此问题)
        this.speak(); // 构造方法可以引用普通方法。这里 this 可以省略
    }

    //方法
    public void speak() {
        System.out.println(name + ": 喵喵喵");
    }
}

使用:(下面两个会执行不同的构造方法)

Cat one = new Cat();
Cat two = new Cat("花花");

封装

隐藏某些信息,以接口的方式提供给外界使用。

  • 属性设为 private
  • 创建 getter/setter 方法,方法名如 setName
  • 在 getter/setter 中做属性访问控制
  • 如果构造函数中给隐藏的属性赋值了,建议调用set方法赋值,以复用变量检查的代码。
  • getter/setter 可以分别省略,以达到只读/只写效果

public class Cat {
    private String name;
    private int month;
    public void setMonth(int month) {
        if (month <= 0) {
            System.out.println("年龄不能为负");
        } else {
            this.month = month;
        }
    }
    public int getMonth() {
        return this.month;
    }
}

static

static 归属于类。

  • 静态属性。在一个对象中改了静态属性后,所有对象以及类的属性都会变。(这与Python有些区别,Python的静态属性在赋值后不按照静态属性的逻辑来)
  • 静态方法。更推荐用类来调用静态方法,而不是用对象来调用。

要点

  • 方法内不能定义 static 属性,只能定义 finnal 属性。
  • 方法可以对 static 做赋值/读取。
  • 静态方法不能调用动态属性,也不能调用动态方法。(因为动态属性和动态方法是属于对象的)
  • 解决方案:可以实例化后调用。

动态代码块和静态代码块


public class Cat {
    public Cat() {
        System.out.println("宠物猫来了!");
    }

    {
        System.out.println("构造代码块,在构造的时候会被执行");
    }

    static {
        System.out.println("静态代码块,比动态代码块先执行,且只会被调用一次");
    }

}

// 调用:
Cat cat1=new Cat();
Cat cat2=new Cat();

静态代码块,比动态代码块先执行,且只会被调用一次 构造代码块,在构造的时候会被执行 宠物猫来了! 构造代码块,在构造的时候会被执行 宠物猫来了!

要点:

  • 普通代码块:在方法中的代码块,在代码块中定义的变量,作用范围仅限于当前代码块。
  • 类代码块在构造前执行,包括静态代码块、动态代码块。
  • 静态代码块先执行(与位置无关)
  • 动态代码块每次构造都被执行,静态代码块只会执行一次。
  • 与方法一样,静态代码块只能调用静态属性和静态方法。

继承

继承

// A.java
public class A {
    private int i;
    protected int j;

    public void func1() {
        System.out.println("Class A");

    }
}



// B.java
public class B extends A {
    public int z;

    public void func2(){
        super.func1(); // 注意这个 super 方法
        System.out.println("Class B");

    }

}

// C.java
public class C {

    public static void main(String []args){
        A a=new A();
        B b=new B();

        System.out.println("Hello world!");
        System.out.println(a instanceof A);
        System.out.println(b instanceof B);
        b.func1();

    }
}

注意

  • 只能继承一个类

重写 (Override):子类可以写一个同名的方法,覆盖掉父类的同名方法。

  • 方法名必须相同。如果不同,其实是重载了。
  • 返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  • 参数列表与被重写方法的参数列表必须完全相同。
  • 权限 子类方法的访问权限必须大于或等于父类方法的访问权限。(修饰符和权限的关系见于上表)例如:如果父类的 public 在子类中重写不能是 protected
  • final 方法不能被重写。
  • static 方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 构造方法:子类构造方法必须调用父类构造方法。如果不显式声明如何调用父类构造方法,会默认调用父类的无参构造方法。如果显式声明,会按照显式声明来。(见代码)。显式声明必须放到第一行。
  • this()super() 都必须在第一行,因此在不能同时出现在同一个构造方法中
  • 如果不能继承一个方法,则不能重写这个方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 执行顺序: 父类静态属性 -> 父类静态代码块 -> 子类静态代码块 -> 父类构造代码块->父类构造方法 -> 子类构造代码块 -> 子类构造方法
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个方法,则不能重写这个方法。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

重载 (overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型呢?可以相同也可以不同。

3

// Animal.java
public class Animal {
    public String name;

    public Animal() {
        System.out.println("构造一个 Animal");
    }

    public Animal(String name) {
        System.out.println("构造了一个 Animal:" + name);
        this.name = name;
    }

    public void eat() {
        System.out.println(this.name + " 最近没有食欲!");
    }

    public void eat(String food) {
        System.out.println(this.name + " 最近没有食欲!");
    }
}




// Dog.java
public class Dog extends Animal {//只能继承一个类

    public Dog() {
        System.out.println("构造一个 Dog");
    }

    public Dog(String name) {
        // 子类构造方法必须调用父类构造方法。
        //  super(); // 如果不显式声明会默认调用父类的无参构造方法。
        super(name); // 如果显式声明,会按照显式声明来。
        // 显式声明必须放到第一行。
    }

    public void eat(String food) { // 方法名、返回值类型必须相同。
        super.eat(); // 可以用 super 调用 父类的 eat()
        System.out.println("dog" + this.name + " 吃" + food);
    }

    public int eat(String food, int volume) { // 这个其实不是重写,而是重载
        this.eat(food);
        return volume - 1;
    }
}

使用:

Dog dog1 = new Dog();
dog1.eat(); // Dog 类并没有重写此种参数的 eat(),因此调用的是父类
System.out.println("=========");
Dog dog2 = new Dog("点点");
dog2.eat("饼干");
int left = dog2.eat("牛奶", 10);
System.out.println("牛奶剩下"+left);

输出:

构造一个 Animal 构造一个 Dog null 最近没有食欲! ========= 构造了一个 Animal:点点 点点 最近没有食欲! dog点点 吃饼干 点点 最近没有食欲! dog点点 吃牛奶 9

object类

object 类是所有类的父类。在 java.lang 这个包里面。

常用方法

  • equals 默认是判断内存指向是否一致。可以重写,例如 String 类型的 equals 就重写为判断内容是否相同(但是字符串的 == 还是比较的是内存指向)
// 重写 equals的案例
public boolean equals(Object obj) {
    if (obj == null) {
        return false;
    }
    Animal tmp = (Animal) obj;
    return this.name.equals(tmp.name);
}
// 还可以这样重写
public boolean equals(Animal obj) {
    if (obj == null) {
        return false;
    }
    Animal tmp = (Animal) obj;
    return this.name.equals(tmp.name);
}
  • toString(),对应 print(dog2), print(dog2.toString()),默认打印一个类似 com.guofei.work2.Dog@a09ee92 这样的东西。
    public String toString() {
      return "Animal{" +
              "name='" + name + '\'' +
              '}';
    }
    
  • hashCode() 对象的哈希代码值
  • getClass() 返回对象所属的类

final

package com.guofei.work2;

public final class Cat {// final class:不允许被继承
    final String name; //只能:1. 定义时赋值 2. 在构造函数中赋值,3. 构造代码块中被赋值。
    final static String author="guofei9987"; //final static 连用,可以防止被随意篡改。

    public Cat(){
        this.name="name"; // final 一个属性后,必须赋值。
    }

    public final void eat(){ // final 方法:不允许子类被重写,但可以被子类使用。
        final int a=1; //final 修饰局部变量:第一次赋值后不允许再更改。
        System.out.println("");
    }
}
  • 可以修饰类,表示类不能被继承
  • 可以修饰方法,表示方法不能被重写。但是可以被子类继承和使用。final不能修饰构造方法。
  • 可以修饰类的成员变量,只有3种赋值方法: 1. 定义时赋值 2. 在构造函数中赋值,3. 构造代码块中被赋值。赋值后不允许更改。
  • 可以修饰局部变量,第一次赋值后不允许再更改。

命名规则:

  • 小写
  • 按照域名倒叙

包文件管理,例如,我们创建了一个 Cat 类,然后想要另创建一个机器猫也叫 Cat. 这就需要另建一个包。

建立两个package,分别是如下内容:

// com.guofei.animal
package com.guofei.animal;

public class Cat {
    public Cat(){
        System.out.println("宠物猫来了!");
    }
}

// com.guofei.robot
package com.guofei.robot;

public class Cat {
    public Cat(){
        System.out.println("机器猫来了!");
    }
}

调包有以下几种:

import com.guofei.robot.Cat; // 方法1:加载指定类
import com.guofei.animal.*; // 方法2:加载所有类,会被精确指定的(上一条语句)覆盖,这个覆盖与 import 的顺序无关

// 方法3:使用时指定:
com.guofei.animal.Cat cat2=new com.guofei.animal.Cat();

// 一个坑:import 只能找到指定目录下的类,而找不到子文件夹的类。

多态

//  向上转型
Animal one = new Animal();
Animal two = new Cat();
Animal three = new Dog();

one.eat();
two.eat();
three.eat();

//  two.run();  // 报错!因为是 Animal 类型,需要向下转型:
Cat four = (Cat) two;
four.run();

System.out.println(two.getClass()); // Cat
System.out.println(two instanceof Cat); // true
System.out.println(two instanceof Animal); // true

多态的2个例子:


public class Master {
    // 多态例子1,主人喂宠物后,又根据宠物类型不同 做另一个动作。
    public void feed(Animal obj) {
        obj.eat();
        if (obj instanceof Dog) {
            Dog tmp = (Dog) obj;
            tmp.sleep();
        }
    }

    // 多态例子2,主人根据自己时间养不同的宠物。
    public Animal pet;

    public Animal adoptPet(boolean hasTime) {
        if (hasTime) {
            pet = new Dog();
        } else {
            pet = new Cat();
        }
        return pet;
    }
}

抽象类

public abstract class Animal{ // 如此,就不能 new Animal 了。
  public abstract void play(); // 抽象方法。
  // 1. 子类必须重写,除非子类也是抽象类。
  // 2. 所在类必须是抽象类。  

}


设计模式

单例模式

使用场景

  • 创建对象占用资源较多。
  • 对某个资源要求统一读写,如配置信息、打印机
  • 多个实例会引起逻辑错误,如primary_key 生成器。

饿汉式:

public class Printer {
    // 1. 私有构造
    private Printer() {
    }

    // 2. 私有静态实例
    private static Printer instance = new Printer();

    // 3. 提供一个接口,返回静态实例对象
    public static Printer getInstance() {
        return instance;
    }

}

// 测试:
Printer one=Printer.getInstance();
Printer two=Printer.getInstance();
System.out.println(one==two);

懒汉式:(使用时才会创建)

public class Printer {
    // 1. 私有构造
    private Printer() {
    }

    // 2. 私有静态实例
    private static Printer instance = null;

    // 3. 提供一个接口,返回静态实例对象
    public static Printer getInstance() {
        if (instance == null) {
            instance = new Printer();
        }
        return instance;
    }

}

比较

  • 饿汉用空间换时间
  • 懒汉用时间换空间
  • 懒汉存在多线程风险。

接口

问题

  • 前面说了,只能继承一个
  • 很多时候,没有继承关系的类,想让他们又联系。例如 猫和机器猫都能跑,我们想把这个跑的动作关联起来。
public class Animal {
}


public interface IRunner {
    // 每个动作消耗卡路里。
    int calorie = 20; // 属性,默认public static final
    public void run(); // 必须被重写
    default void jump(){ // 加上 default 后,就不必被重写了。
        System.out.println("跳起");
    }
    static void stop(){
        System.out.println("停下来");
    }
}



public interface IPlayer {
    public void run();
}


public class Cat extends Animal implements IRunner, IPlayer { // 多个 interface
    @Override
    public void run() {
        System.out.println("猫咪跑跑");
    }

    @Override
    public void jump() {
        System.out.println("猫咪会跳");
    }
}

public class RobotCat implements IRunner{
    @Override
    public void run() {
        System.out.println("机器猫用轮子跑");
    }

}

内部类

可以在类、方法中定义另一个类。

成员内部类

public class Person {
    int age;

    // 1. 成员内部类
    class Heart {
        public String beat() {
            return "心脏在跳动";
        }
    }

    public Heart getHeart() {
        return new Heart();
    }
}

// 获取:
Person.Heart myHeart;
// 获取方法1:
myHeart = new Person().new Heart();
// 获取方法2:
myHeart = p1.new Heart();
// 获取方法3:
myHeart = p1.getHeart();

内部类也可以添加修饰符 public/private 等。

内部类可以调用外部类的字段、方法:

public class Person {
    int age;

    public void eat() {
        System.out.println("人会吃东西");
    }

    class Heart {
        public void beat() {
            eat(); // 内部类可以使用外部类的方法
            System.out.println(age + "岁的心脏在跳动"); //内部类可以使用外部类的字段
            // Person.this.age // 如果有同名歧义,可以精确指定。
        }
    }
}


Person p1 = new Person();
p1.age = 10;
Person.Heart myHeart = p1.new Heart();
myHeart.beat();

静态内部类

public class Person {
    public void eat() {
        System.out.println("人会吃东西");
    }
    public static void run(){
        System.out.println("人会跑");
    }

    static class Heart {
        public void beat() {
            run(); // 静态内部类只能调用外部类的静态方法
            // eat(); // 报错,静态内部类不能调用外部类的非静态方法。
            new Person().eat(); // 作为解决,还可以 new 一个对象来调用
        }
    }
}

// 调用:
Person.Heart myHeart = new Person.Heart();

方法内部类

定义在方法内的类。

  • 作用范围在方法内
  • 方法内的成员,不能是 public、private、protected、static
  • 可以 final,abstract

匿名内部类

有点儿想匿名函数,使用时定义一个类。

  • 不能用 public、private、protected、static、abstract 修饰(这么修饰也没意义)
  • 不能用构造方法,可以用构造代码块
  • 不能有静态成员(也没有意义)
// Person.java
public abstract class Person {
    public abstract void eat() ;
}


// Test.java
public class Test {
    public void func(Person person) {
        person.eat();
    }

    public static void main(String[] args) {
        Test tst = new Test();
        tst.func(new Person() {

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




您的支持将鼓励我继续创作!