封装
封装,顾名思义,就是把东西封存起来,不让每个人都能操作。
- 通常情况下,应该禁止直接访问一个对象中数据的实际表示,而应该通过操作接口来访问。这称为信息的隐藏。
- 程序设计的要追求"高内聚,低耦合"。高内聚:类的内部数据局操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用。
熟记这句话:属性私有,get/set
下面看一个封装的实例:(为了展示方便,我这里Student类里面只封装了一个属性,多个属性同理)
package oop.OopDemo; import oop.OopDemo.Demo01.Student; /* 封装的作用: 1.提高程序的安全性,保护数据 2.隐藏代码的实现细节 3.统一接口 4.增强系统的可维护性 */ public class Application { public static void main(String[] args) { Student student = new Student(); //student.age=10;//报错:'age' has private access in 'oop.OopDemo.Demo01.Student' //使用公共的方法获取属性的值 student.setAge(12);//使用公共的方法设置属性 System.out.println(student.getAge()); student.setAge(120); } }
package oop.OopDemo.Demo01; public class Student { //属性私有 private int age; //提供一些可以操作这些私有属性的方法 //快捷键Alt+Insert生成get/set方法 public int getAge() { return age; } public void setAge(int age) { if (age < 120 && age > 0) {//增加了验证机制,增强了数据的合法性 this.age = age; } else { System.out.println("Please input the right age!"); } } }
输出结果:
使用private修饰符修饰属性(字段,成员变量),属性就不能直接被访问,需要写get/set方法来访问或修改这些属性的值。get/set方法是公共的方法,初学感觉这样做是多此一举,封装是面向对象编程的必经之路。比如,想想一下那个内存分析图,new出来的每个对象都指向堆里面的一个内存区,把数据私有化以后,每个属性都被封装了,也就是被保护了,除了get/set方法之外不能操作它们,这样有效的提升了数据的安全性,这些get/set方法就是我们预留的口。这就是封装
封装的作用:
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 增强系统的可维护性
继承
- 继承的本质是对一批类的抽象,是类与类的关系;
- 使用关键字“extends”实现类的继承;
- 继承关系的类分为子类和父类,被继承的是父类,继承的是子类,子类拥有父类的全部东西;
- java中只有单继承,没有多继承。意思是:一个父类可以有多个子类,但是一个子类只能有一个父类,如下图1;
- 类与类的关系除了继承之外,还有依赖,组合,聚合等。
- 子类也叫派生类
图1
下面举一个关于继承的例子:
//测试主类 package oop.OopDemo; import oop.OopDemo.Demo02_extends.Student; import oop.OopDemo.Demo02_extends.Teacher; public class Application { public static void main(String[] args) { Student student = new Student(); Teacher teacher = new Teacher(); System.out.println(student.money); student.setName("fd1"); System.out.println(student.getName()); System.out.println(teacher.money); teacher.setName("fd2"); System.out.println(teacher.getName()); } }
//父类Person package oop.OopDemo.Demo02_extends; public class Person { public int money=10; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
//子类Teacher package oop.OopDemo.Demo02_extends; public class Teacher extends Person{ }
//子类Student package oop.OopDemo.Demo02_extends; public class Student extends Person{ }
运行结果:
结果分析:
- 子类Teacher和子类Student都继承了父类Person,所以这些子类都拥有了父类全部的属性和方法(如果是private修饰的属性,可以通过get/set方法读写,但是private修饰的方法,子类不能继承(不能使用)),虽然子类中都没有写任何属性和方法,但是在测试主类中,对子类分别进行实例化以后,均可访问/修改子类的对象的属性(从父类那里继承来的属性)。
- 测试主类中实例化了两个子类的对象,一个student,一个teacher,然后使用这两个子类的对象分别操作了父类的属性和方法,现在涉及到了一个关于内存的问题:子类Student的实例化对象student成功后,student对象压栈,并指向堆中的内存空间,由于本程序的Student类并未写任何属性和方法,那栈中的student对象指向的是哪个堆?是自己的堆,还是父类Person的堆?同样,子类Teacher也存在这个疑问。
Object类
Object类包含了众多java的默认方法,在java中所有的类都默认直接或者间接继承Object类。
- 把光标放在类主体中,使用快捷键“Ctrl+H”或者点击右侧边栏的“Hierarchy”,会显示当前类的继承列表,如下图:
super - this
- super的注意点:
- super调用父类的构造方法,必须放在子类构造构造器的首行;
- super必须出现在子类的方法或构造方法中;
- super和this不能同时调用构造方法、
- this的注意点:
- 代表对象不同
- this: 子类的对象
- super: 父类的对象
- 前提:
- this: 没有继承也可以使用
- super: 只能在继承条件下才能用
- 构造方法:
- this():本类的构造方法
- super(): 父类的构造方法
- 代表对象不同
下面看程序实例:
package oop.OopDemo; import oop.OopDemo.Demo02_extends.Student; public class Application { public static void main(String[] args) { Student student = new Student(); student.test1("3 dogs"); student.test2(); } }
package oop.OopDemo.Demo02_extends; public class Student extends Person { private String name = "2 dogs"; public void test1(String name) { System.out.println(super.getAge()); System.out.println(name);//形参 System.out.println(this.name);//当前类的name System.out.println(super.name);//父类的name } public void test2() { print(); this.print(); super.print(); } public void print() { System.out.println("I'm Student class!"); } }
package oop.OopDemo.Demo02_extends; public class Person { protected String name = "1 dog"; private int age=5; public void print() { System.out.println("I'm Person class!"); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
输出结果:
分析;子类Student继承了父类Person,所以拥有了父类的所有属性和方法(除去被private修饰的)。由运行结果可见,子类中的方法或者属性如果不加this.或者super.,指的就是当前类中的方法或者属性,加上this.指的是当前类中的方法或者属性,加上super.指的就是父类的方法或者属性。如果修饰符是private,那么就无法被子类继承,但是可以通过get/set方法来读写。
需要注意的是,自定义的父类中不要用super,自定义的父类的父类就是Object类了。
回顾:每个类中都有构造器,调用一个类的对象,首先调用这个类的无参构造器(且如果存在有参构造器,无参构造器必须显式表示)。
- 关于子类与父类的构造器问题:
上面的程序稍作修改如下:
package oop.OopDemo; import oop.OopDemo.Demo02_extends.Student; public class Application { public static void main(String[] args) { Student student = new Student(); } }
package oop.OopDemo.Demo02_extends; public class Student extends Person { public Student() {//无参构造器 System.out.println("Student is running!"); } }
package oop.OopDemo.Demo02_extends; public class Person { public Person() {//无参构造器 System.out.println("Person is running!"); } }
输出结果:
由上面的输出结果可见,new了一个子类的对象,父类的无参构造器先执行,然后执行子类的无参构造器,这说明子类的无参构造器里面首先执行了一句调用父类无参构造器的程序,经查证是super();,
只是隐式表示了,可以把它写出来,并且super();只能写在子类无参构造器的首行,否则会报错,如下:
public Student() { super(); System.out.println("Student is running!"); }
总结:
- 当主类里面new了一个子类的对象的那一刻,程序先执行父类的无参构造器先执行,然后执行子类的无参构造器,牢记!
- super(); 调用父类的无参构造器
- this(); 调用子类的无参构造器,并且只能放在子类无参构造器的首行,这里就产生了一个问题:super();只能放在首行,this("name");也只能放在首行,无论谁放在首行,另一个都会报错,如下图:
但是可以不写super();直接写this("name");,就不报错了,目前还不知道是什么原因?
文字不好描述,直接上图吧,对比区别进行理解。(有点出入)