面向对象

  1. 面向对象
  • 方法
    1. 可变参数类型
  • 参数绑定
  • 构造方法
  • 继承
  • super
  • 阻止继承
  • 多态
  • 抽象类
  • 接口
  • 静态字段和方法
  • 内部类
  • 匿名类
  • 静态内部类
  • 面向对象

    方法

    1
    2
    3
    4
    修饰符 方法返回类型 方法名(方法参数列表) {
    若干方法语句;
    return 方法返回值;
    }

    可变参数类型

    1
    2
    3
    4
    5
    6
    7
    class Group {
    private String[] names;

    public void setNames(String... names) {//表示传入的参数个数可变
    this.names = names;
    }
    }

    也可以这样写

    1
    2
    3
    4
    5
    6
    class Group {
    private String[] names;
    public void setNames(String[] names) {
    this.names = names;
    }
    }

    但在调用时就比较麻烦,需要构造一个string[]类型

    1
    2
    Group g = new Group();
    g.setNames(new String[] {"n1ng", "n2ng", "n3ng"});

    参数绑定

    1
    2
    3
    4
    5
    6
    Person n1ng = new Person();
    int n = 15; // n的值为15
    n1ng.setAge(n); // 传入n的值
    System.out.println(n1ng.getAge()); // 15
    n = 20; // n的值改为20
    System.out.println(n1ng.getAge()); //仍然是15

    可见对于基本类型的参数,传入后再进行修改变量不会改变字段的值

    1
    2
    3
    4
    5
    6
    Person n1ng = new Person();
    String[] name = new String[] { "n1ng", "n2ng" };
    p.setName(name); // 传入fullname数组
    System.out.println(n1ng.getName()); // "n1ng n2ng"
    name[0] = "Bart"; // fullname数组的第一个元素修改为"n3ng"
    System.out.println(p.getName()); // 变成了"n3ng"

    所以对于引用类型的参数,如果修改了变量的值,相应的字段也会被改变

    看这个例子

    1
    2
    3
    4
    5
    6
    Person p = new Person();
    String n1ng = "n1ng";
    p.setName(n1ng); // 传入变量
    System.out.println(p.getName()); // "n1ng"
    n1ng = "n2ng"; // bob改名为Alice
    System.out.println(p.getName()); // "n1ng"还是"n2ng"?

    答案是n2ng,这和上面的结论矛盾吗,其实并不矛盾,这涉及到了之前的知识

    之前的例子中name是一个String[]类型,是可变的对象,而这里是String类型,是不可变的对象,

    当修改name时,其实就是修改了数组中字符串的地址,但是数组的地址没变,所以p的name字段里所存储的地址也发生了改变,但是修改n1ng时,字符串的地址发生了改变,但是字段并没有改变,仍然指向原来的字符串

    构造方法

    在创建对象实例时就将内部字段进行初始化

    1
    Person p = new Person("n1ng", 21);

    构造方法,当然构造方法可以重载(Overload),即可以定义多个

    1
    2
    3
    4
    public Person(String name, int age) {//直接以类名为方法名
    this.name = name;
    this.age = age;
    }

    继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Person {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Student {
    private String name;
    private int age;
    private int score;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
    public int getScore() { … }
    public void setScore(int score) { … }
    }

    可以看到,Student类和Person类相比,只是多了一个score字段,为了避免代码的重复编写,就可以利用继承来实现代码的复用,Student继承了Person后,就拥有了Person的所有功能,如果需要对Student进行功能的增加,直接在Student中编写即可

    在Java中使用extends关键字来实现继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Person {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
    }

    class Student extends Person {
    // 不需要重复name和age字段/方法,
    // 只需要定义新增score字段/方法:
    private int score;

    public int getScore() { … }
    public void setScore(int score) { … }
    }

    需要注意的是,对于父类中已有的字段子类中不能重新定义和他重名的字段

    并且子类无法访问父类的private字段或方法,如果想要访问,可以将父类中的private关键字改为protected

    protected关键字所修饰的字段或方法可以被子类访问但不能被外部类访问

    super

    super关键字表示父类,子类引用父类的字段时,就可以用super.Name

    1
    2
    3
    4
    5
    class Student extends Person {
    public String hello() {
    return "Hello, " + super.name;//父类的name
    }
    }

    对于这段代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Main {
    public static void main(String[] args) {
    Student s = new Student("Xiao Ming", 12, 89);
    }
    }

    class Person {
    protected String name;
    protected int age;

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

    class Student extends Person {
    protected int score;

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

    在运行的时候会报错,因为所有类的构造方法第一行的语句都是调用父类的构造方法,如果没有写,系统则会自动加一句super(),但是在这段代码中Person的构造方法是有参数的,所以需要自己写一句super(name,age)

    如果父类没有默认的构造方法,子类必须显式的调用super(),并给出参数

    阻止继承

    1
    2
    3
    public sealed class Shape permits Rect, Circle, Triangle {
    ...
    }

    sealed修饰的类可以通过permits给出可以继承的类

    多态

    子类如果定义了一个和父类相同(方法签名相同,返回值也相同)的方法,被称为覆写,即override,如果要调用父类的方法,可以通过super来调用,如果父类的某个方法不允许子类覆写,则需要加上final修饰,同样的,某一个类如果不希望被继承也可以加上final关键字

    抽象类

    如果父类的方法不需要实现任何的功能,仅仅只是为了定义方法签名,供子类覆写,那么就可以把父类的方法声明位抽象方法,用abstract修饰,同样的,父类也可以用abstract修饰为抽象类,抽象类无法被实例化,只用于继承,并且,抽象类可以强迫子类实现其定义的抽象方法,相当于定义了规范,比如Person类定义了抽象方法run(),那么他的子类Student就必须覆写run()方法

    接口

    在抽象类中抽象方法实质上就是在定义规范,保证所有的子类都有相同的接口实现,如果一个抽象类没有字段,所有方法全都是抽象方法,就可以把这类抽象类改写为接口(Interface),使用interface声明。一个具体的class实现一个接口时,需要使用implements关键字

    1
    Class Student implements Person{}

    在java中一个子类只能继承一个父类,但是一个类可以实现多个接口

    1
    Class Student implements Person,Hello{}

    同时,一个接口也可以继承另一个接口

    静态字段和方法

    对于实例字段,实例字段在每一个实例中都有自己的独立的空间,与之相对的静态字段只有一个共享的空间,所有的实例共享这一个字段,调用时通过类名直接调用即可,静态字段有static修饰。相同的,静态方法也直接通过类名调用。上面提到,接口类不能定义实例字段,但是它可以有静态字段,且必须由public static final修饰

    为了解决类名的冲突,可以将类放在包下,一个类总是属于某个包,例如,定义一个Person类放在n1ng包下,完整的类名即n1ng.Person,在定义类时需要在第一行声明,同一个包内的类可以访问包作用域的字段和方法(违背public,protected,private修饰的字段,方法)

    在一个class中引用其他的class时,可以直接写出完整的类名(如n1ng.Person),也可以通过import导入完整的类名

    内部类

    内部类不能直接实例化,必须依附于外部类的一个实例,在编译时,外部类Outer被编译为Outer.class,而内部类Inner则会被编译为Outer$Inner.class

    1
    2
    Outer outer = new Outer();//创建outer实例
    Outer.Inner inner = outer.new Inner();//通过outer.new 实例化Inner

    匿名类

    另一种定义内部类的方法,在方法内部通过匿名类(Anonymous Class)来定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Outer {
    private String name;
    Outer(String name){
    this.name = name;
    }
    void asyncHello(){
    Runnable r = new Runnable() {//Runnable为接口不能实例化,实际上是定义了一个实现了Runnable接口的匿名类,并通过new实例化该匿名类然后转型为Runnable
    @Override
    public void run() {
    System.out.println("Hello, " + Outer.this.name);
    }
    };
    new Thread(r).start();
    }
    }

    匿名类和内部类一样可以访问外部类的private方法和字段,但是匿名类比内部类要少写许多代码,匿名类在编译时会被编译为Outer$1.class,如果有多个则以此类推,匿名类也可以继承自普通类

    静态内部类

    和内部类相似,但用static修饰,他不在需要依附于Outer实例,但是也无法再引用Outer.this,仍然可以访问Outer的private静态字段和静态方法

    1
    Outer.StaticNested sn = new Outer.StaticNested();

    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 3049155267@qq.com

    💰

    ×

    Help us with donation