一个class包含多个field,但是直接把field直接用public暴露给外部可能会破坏封装性,

  1. class Person{
  2. public String name;
  3. public int age;
  4. }
  5. Person ming = new Person();
  6. ming.name = "xiao ming";
  7. ming.age = -99;

显然,直接操作field,容易造成逻辑混乱。为了避免外部代码直接去访问field,可以用private修饰field 拒绝外部访问

  1. class Person{
  2. private String name;
  3. private int age;
  4. }
  5. public class Main{
  6. public static void main(Stringp[] args){
  7. Person ming = new Person();
  8. ming.name = "xiao ming";
  9. ming.age = 12;
  10. }
  11. }

实际上,上述代码会编译报错。

拒绝外部访问,那我们定义这些field有什么用?怎么才能给它赋值?怎么才能读取它的值?
所以我们需要使用方法(Method)来让外部代码可以间接的修改field

public class Main{
    public static void main(String[] args){

    }
}




class Person{
    private String name;
    private int age;

    public void setName(String name){
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void setAge(int age) {
        if(age < 0 || age >120){
            throw new IllegalArgumentExeception("invalid age value");
        }

        this.age = age;
    }

    public int getAge(){
        return this.age;
    }

}

虽然外部代码不能直接修改private字段,但是可以通过调用getName()setName()来设置或获取private字段。在方法内部,我们就有机会检查输入法是否合法,外部就没有机会把age设置成不合理的值。

setName() 方法同样可以做检查,例如,不允许传入null和字符串

public void setName(String name) {
    if(null == name || name.isBlank()) {
        throw new IllegalArgumentException()"invalid name");
    }
    this.name = name.strip();
}

定义方法

从上面的代码可以看出,定义方法的语法是:

修饰符 方法返回类型 方法名(方法参数列表) {
    方法语句..
        return 方法返回值;
}

如果没有返回值,返回类型设置为void,可以省略return

private方法

定义private方法的理由是 内部方法可以调用private方法。


public class Main{
    public static void main(String[] args){
        Person ming = new Person();
        ming.setBirth(2008);
        System.out.println(ming.getAge());
    }
}


class Person{
    private String name;
    private int birth;
    private final int CURRENT_YEAR = 2021;

    public void setBirth(int birth){
        this.birth = birth;
    }

    public int getAge() {
        return calcAge();
    }

    private int calcAge(){
        return this.CURRENT_YEAR - this. birth;
    }

}

此外,我们还注意到,这个Person类只定义了birth字段,并没有age字段。获取age时,通过方法getAge()返回的是一个实时计算的值,并非存储在某个字段的值。这说明方法可以封装一个类的对外接口,调用方不需要知道,也不关心Person实例内部到底有没有age字段。

this变量

在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。
如果没有命名冲突,可以省略this

class Person {
    private String name;

    public String getName(){
        return name;
    }
}

但是,如果有局部变量和字段重名,那么局部变量优先级会更高,必须要加this

class Person{
    private String name;

    public void setName(String name) {
        this.name = name;
    }
}

方法参数

方法可以包含0个或任意个参数。方法参数用于接收传递给方法的变量值。调用方法时,必须严格按照参数的定义一一传递。

class Person{
    ....
    public void setNameAndAge(String name, int age){

        ...
        this.name = name;
        this.age = age;
    }
}

调用这个setNameAndAge时,必须要有两个参数,且第一个参数类型必须是String,第二个参数类型必须是int

Person ming = new Person();

ming.setNameAndAge("xiao ming");//编译错误,参数个数不对
ming.setNameAndAge(12,"xiaoming");//编译错误,参数类型不对

可变参数

可变参数用类型...定义,可变参数相当于数组类型

class Group{
    private String[] names;

    public void setNames(String... names) {

        this.names = names;
    }

}

Group g = new Group();

g.setNames("Xiao Ming", "Xiao Hong", "Xiao Jun"); // 传入3个String
g.setNames("Xiao Ming", "Xiao Hong"); // 传入2个String
g.setNames("Xiao Ming"); // 传入1个String
g.setNames(); // 传入0个String

可变参数可以保证无法传入null因为传入0个参数时, 实际上接收到的是一个空数组。

参数绑定

调用方把参数传递给实例方法时,调用时传递的值会按参数位置一一绑定。
那什么是参数绑定?

我们先观察一个基本类型参数的传递

public class Main{
    public static void main(String[] args){
        Person p = new Person();
        int n = 15;
        p.setAge(n);
        System.out.println(p.getAge());// 15
        n= 20;
        System.out.println(p.getAge());//15还是20?
    }
}

class Person{

    private int age;

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }
}

运行代码,从结果可知,修改外部的局部变量n,不影响实例page字段,原因是setAge()方法获得的参数,复制了n的值,因此,p.age和变量n互不影响。

结论:基本类型参数的传递,是调用方值的复制,双方各自的修改,互不影响。

我们再看一个传递引用参数的例子


public class main{
    public static void main(String[] args){
        Person p= new Person();

       String[] fullname = new String[] {"Homer","Simpson"};
        p.setName(fullname);
        System.out.println(p.getName());
        fullname[0] = "Bart";
        System.out.println(p.getName()); //"Homer Simpson"  or  "Bart Simpson"?
    }
}


class Person {
    private String[] name;
    public String[] getName(){
        return this.name;
    }
    public void setName(String[] name) {
        this.name = name;
    }
}

结论:引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方。
有了上面的结论,我们再看一个例子



public class Main{
    public static void main(String[] args){
        Person p = new Person();
        String bob = "bob";
        p.setName(bob);
        System.out.println(p.getName());
        bob = "Alice";
        System.out.println(p.getName());// bob 还是 alice?
    }

    class Person{
        private String name;

        public String getName() {
            return name;
        }
        public  void setName(String name) {
            this.name =name;
        }
    }
}

试解释为什么上面的代码两次输出的都是"bob"

我们来复习一下字符串这一章image.png String 是引用类型,但是 String 不可变,给bob重新赋值, 改变了 bob 的内存地址,指向了另一个对象.

小结:

  • 方法可以让外部代码安全地访问实例字段
  • 方法是一组执行语句,并且可以执行任意逻辑
  • 方法内部遇到return时返回,void表示不返回任何值
  • 外部代码通过public方法操作实例,内部代码可以调用 private方法
  • 参数绑定