Java学习笔记(十):面向对象基础

class

class Person {
    private String name;
    private int age;

    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return this.age;
    }
    public void setAge(int age) {
        if (age < 0 || age > 100) {
            throw new IllegalArgumentException("invalid age value");
        }
        this.age = age;
    }
}

this变量

  • 如果没有命名冲突,可以省略this
  • 如果有局部变量和字段重名,那么局部变量优先级更高,就必须加上this

参数绑定

  • 基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。
  • 引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方

例1

public class Hello {
    public static void main(String[] args) {
        Person p = new Person();
        String[] fullname = new String[] { "Homer", "Simpson" };
        p.setName(fullname); // 传入fullname数组
        System.out.println(p.getName()); // "Homer Simpson"
        fullname[0] = "Bart"; // fullname数组的第一个元素修改为"Bart"
        System.out.println(p.getName()); // "Homer Simpson"还是"Bart Simpson"?
    }
}
class Person {
    private String[] name;
    public String getName() {
        return this.name[0] + " " + this.name[1];
    }
    public void setName(String[] name) {
        this.name = name;
    }
}

output:

Homer Simpson
Bart Simpson

例2

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        String bob = "Bob";
        p.setName(bob); // 传入bob变量
        System.out.println(p.getName()); // "Bob"
        bob = "Alice"; // bob改名为Alice
        System.out.println(p.getName()); // "Bob"还是"Alice"?
    }
}
class Person {
    private String name;
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

两次结果都是“Bob”。bob指向“Bob”时,将“Bob”的地址传递给了p.setname方法,因为字符串具有不可变性,之后名字的改变并不是将传递地址中的“Bob”变为了“Alice”,而是将重新开辟了一个地址存储“Alice”。之前传递的地址仍然指向“Bob”。

构造方法

  • 构造方法的名称就是类名
  • 构造方法的参数没有限制
  • 构造方法没有返回值
  • 如果一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句
  • 如果自定义了一个构造方法,那么编译器就不再自动创建默认构造方法
  • 如果既要能使用带参数的构造方法,又想保留不带参数的构造方法,那么只能把两个构造方法都定义出来
  • 可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分
  • 构造方法可以调用其他构造方法,调用其他构造方法的语法是this(…)
class Person {
    private String name;
    private int age;

   public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person(String name) {
        this(name, 18); // 调用另一个构造方法Person(String, int)
    }
    public Person() {
        this("Unnamed"); // 调用另一个构造方法Person(String)
    }
    public String getName() {
        return this.name;
    }
    public int getAge() {
        return this.age;
    }
}

方法重载

  • 参数的类型和个数不同,返回值相同,方法名相同

继承

  • Java使用extends关键字来实现继承
  • 在Java中,没有明确写extends的类,编译器会自动加上extends Object。所以,任何类,除了Object,都会继承自某个类
  • Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。
  • 为了让子类可以访问父类的字段,我们需要把private改为protected
  • super关键字表示父类(超类),子类引用父类的字段时,可以用super.fieldName
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) {
        super(name, age); // 调用父类的构造方法Person(String, int)
        this.score = score;
    }
}

向上转型

  • 父类类型的变量指向子类类型的实例,对它进行操作是没有问题的。
  • 向上转型实际上是把一个子类型安全地变为更加抽象的父类型
Student s = new Student();
Person p = s; // upcasting, ok
Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok

向下转型

Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!

instanceof

为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型

Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false

Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true

Student n = null;
System.out.println(n instanceof Student); // false

多态

  • 在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,称为覆写(override)
  • 加上@Override可以让编译器帮助检查是否进行了正确的覆写。
public class Hello {
    public static void main(String[] args) {
        Person p = new Student();
        p.run(); // 应该打印Person.run还是Student.run?
    }
}

class Person {
    public void run() {
        System.out.println("Person.run");
    }
}

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

output:

Student.run

Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。这个非常重要的特性在面向对象编程中称之为多态。它的英文拼写非常复杂:Polymorphic。

覆写Object方法

因为所有的class最终都继承自Object,而Object定义了几个重要的方法:

  • toString():把instance输出为String
  • equals():判断两个instance是否逻辑相等
  • hashCode():计算一个instance的哈希值
    可以覆写Object的这几个方法
class Person {
    ...
    // 显示更有意义的字符串:
    @Override
    public String toString() {
        return "Person:name=" + name;
    }

    // 比较是否相等:
    @Override
    public boolean equals(Object o) {
        // 当且仅当o为Person类型:
        if (o instanceof Person) {
            Person p = (Person) o;
            // 并且name字段相同时,返回true:
            return this.name.equals(p.name);
        }
        return false;
    }

    // 计算hash:
    @Override
    public int hashCode() {
        return this.name.hashCode();
    }
}

调用super

在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用

class Person {
    protected String name;
    public String hello() {
        return "Hello, " + name;
    }
}

Student extends Person {
    @Override
    public String hello() {
        // 调用父类的hello()方法:
        return super.hello() + "!";
    }
}

final

  • 如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override
  • 如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final
  • 对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改

抽象类

如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。使用abstract修饰的类就是抽象类。无法实例化一个抽象类。

public class Main {
    public static void main(String[] args) {
        Person p = new Student();
        p.run();
    }
}

abstract class Person {
    public abstract void run();
}

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

面向抽象编程

  • 上层代码只定义规范
  • 不需要子类就可以实现业务逻辑(正常编译)
  • 具体的业务逻辑由不同的子类实现,调用者并不关心
Person s = new Student();
Person t = new Teacher();
// 不关心Person变量的具体子类型:
s.run();
t.run();
// 同样不关心新的子类是如何实现run()方法的:
Person e = new Employee();
e.run();

接口

在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。如果一个抽象类没有字段,所有方法全部都是抽象方法,就可以把该抽象类改写为接口:interface。

abstract class Person {
    public abstract void run();
    public abstract String getName();
}
interface Person {
    void run();
    String getName();
}

接口定义的所有方法默认都是public abstract。当一个具体的class去实现一个interface时,需要使用implements关键字。

class Student implements Person {
    private String name;

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

    @Override
    public void run() {
        System.out.println(this.name + " run");
    }

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

在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface.

class Student implements Person, Hello { // 实现了两个interface
    ...
}
比较 abstract class interface
继承 只能extends一个class 可以implements多个interface
字段 可以定义实例字段 不能定义实例字段
抽象方法 可以定义抽象方法 可以定义抽象方法
非抽象方法 可以定义非抽象方法 可以定义default方法

default方法

实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。

interface Person {
    String getName();
    default void run() {
        System.out.println(getName() + " run");
    }
}

静态字段

  • static修饰的字段
  • 静态字段只有一个共享“空间”,所有实例都会共享该字段
  • 推荐用类名来访问静态字段。

静态方法

  • 用static修饰的方法称为静态方法
  • 静态方法内部,无法访问this变量
  • 静态方法无法访问实例字段,它只能访问静态字段。
  • 静态方法经常用于工具类,如Arrays.sort(),Math.random()

接口的静态字段

因为interface是一个纯抽象类,所以它不能定义实例字段。但是,interface是可以有静态字段的,并且静态字段必须为final类型。

public interface Person {
    public static final int MALE = 1;
    public static final int FEMALE = 2;
}

实际上,因为interface的字段只能是public static final类型,所以我们可以把这些修饰符都去掉,上述代码可以简写为:

public interface Person {
    // 编译器会自动加上public statc final:
    int MALE = 1;
    int FEMALE = 2;
}

  • 使用package解决命名冲突
  • 在定义class时,在第一行声明这个class属于哪个包
  • 包没有父子关系,com.apache和com.apache.abc是不同的包
package ming; // 申明包名ming

public class Person {
}

包作用域

  • 在同一个包的类,可以访问包作用域的字段和方法
  • import导入

作用域

public

  • 定义为public的class,interface可以被其他任何类访问
  • 定义为public的字段,方法可以被其他类访问,前提是首先具有访问class的权限

private

  • 定义为private的字段,方法无法被其他类访问
  • private访问权限被限定在class内部,推荐把private方法放到后面,因为public方法定义了类对外提供的功能,阅读代码的时候应该先关注public方法
  • java支持嵌套类,嵌套类拥有访问private的权限

protected

  • protected作用于继承关系,定义为protected的字段和方法可以被子类访问,以及子类的子类

package

  • 包作用域是指一个内允许访问同一个package的没有public,private修饰的class,以及没有public,protected,private修饰的字段和方法
package abc;
// package权限的类:
class Hello {
    // package权限的方法:
    void hi() {
    }
}
package abc;

class Main {
    void foo() {
        // 可以访问package权限的类:
        Hello h = new Hello();
        // 可以调用package权限的方法:
        h.hi();
    }
}

局部变量

  • 在方法内部定义的变量成为局部变量
  • 局部变量作用域从变量声明处开始到对应的块结束
  • 方法参数也是局部变量
  • 使用局部变量时,应该尽可能把局部变量的作用域缩小,尽可能延后声明局部变量

final

  • final修饰class可以阻止被继承
  • final修饰的方法可以阻止被子类覆写
  • final修饰的字段可以阻止被重新赋值
  • final修饰的局部变量可以阻止被重新赋值
    注意:一个.java文件只能包含一个public类,但可以包含多个非public类,如果有public类,文件名必须和public类的名字相同
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • 面向对象笔记 一、 对象在内存中的存放方法以及被调用过程 class文件首先被加载到方法区中的class文件内容区...
    VictorBXv阅读 456评论 0 2
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,627评论 0 11
  • Java OOP 什么是面向对象思想? 把一组数据和处理他们的方法组成对象(object),把相同行为的对象归纳为...
    chonglingliu阅读 768评论 0 1
  • 面向对象 对象实例化过程 看方法区是否存在该对象类型,没有就使用类加载器去加载 在栈中创建对象类型的变量,在堆中开...
    ADMAS阅读 261评论 0 0
  • 整个11月,都在为一个医疗类客户提供传播服务。其中最重要的是给他家的年度论坛和新品发布制定传播方案和负责落地执行。...
    无常慢炖阅读 604评论 0 0