Java面向对象的三大特征:封装、继承、多态。
封装
Java中的封装是指一个类把自己内部的实现细节进行隐藏,只暴露对外的接口(setter和getter方法)。封装又分为属性的封装和方法的封装。把属性定义为私有的,它们通过setter和getter方法来对属性的值进行设定和获取。
封装的思想保证了类内部数据结构的完整性,使用户无法轻易直接操作类的内部数据,这样降低了对内部数据的影响,提高了程序的安全性和可维护性。
封装的目的:
只能通过规定的方法访问数据
隐藏类的实现细节
方便加入控制语句
方便实现修改
public class Person {
private int id;
private String name;
private Person person;
public int getId() {
return id;
}
public String getName() {
return name;
}
public Person getPerson() {
return person;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPerson(Person person) {
this.person = person;
}
}
private实现封装
private/ public 这两个关键字表示 “访问权限控制” .
被 public 修饰的成员变量或者成员方法, 可以直接被类的调用者使用.
被 private 修饰的成员变量或者成员方法, 不能被类的调用者使用.
换句话说, 类的使用者根本不需要知道, 也不需要关注一个类都有哪些 private 的成员. 从而让类调用者以更低的成本来使用类.
直接使用 public
class Person {
public String name = "张三";
public int age = 18;
}
class Test {
public static void main(String[] args) {
Person person = new Person();
System.out.println("我叫" + person.name + ", 今年" + person.age + "岁");
}
}
// 执行结果
我叫张三, 今年18岁
这样的代码导致类的使用者(main方法的代码)必须要了解 Person 类内部的实现, 才能够使用这个类. 学习成本较高
一旦类的实现者修改了代码(例如把 name 改成 myName), 那么类的使用者就需要大规模的修改自己的代码, 维护成本较高
使用 private 封装属性, 并提供 public 方法供类的调用者使用
class Person {
private String name = "张三";
private int age = 18;
public void show() {
System.out.println("我叫" + name + ", 今年" + age + "岁");
}
}
class Test {
public static void main(String[] args) {
Person person = new Person();
person.show();
}
}
// 执行结果
我叫张三, 今年18岁
此时字段已经使用 private 来修饰. 类的调用者(main方法中)不能直接使用. 而需要借助 show 方法. 此时类的使用者就不必了解 Person 类的实现细节.
同时如果类的实现者修改了字段的名字, 类的调用者不需要做出任何修改(类的调用者根本访问不到 name, age这样的字段).
注意事项:
①private 不光能修饰字段, 也能修饰方法
②通常情况下我们会把字段设为 private 属性, 但是方法是否需要设为 public, 就需要视具体情形而定. 一般我们希望一个类只提供 “必要的” public 方法, 而不应该是把所有的方法都无脑设为 public.
getter和setter方法
当我们使用 private 来修饰字段的时候, 就无法直接使用这个字段了.
代码示例:
class Person {
private String name = "张三";
private int age = 18;
public void show() {
System.out.println("我叫" + name + ", 今年" + age + "岁");
}
}
class Test {
public static void main(String[] args) {
Person person = new Person();
person.age = 20;
person.show();
}
}
// 编译出错
Test.java:13: 错误: age可以在Person中访问private
person.age = 20;
^
1 个错误
此时如果需要获取或者修改这个 private 属性, 就需要使用 getter / setter 方法.
代码示例:
class Person {
private String name;//实例成员变量
private int age;
public void setName(String name){
//name = name;//不能这样写
this.name = name;//this引用,表示调用该方法的对象
}
public String getName(){
return name;
}
public void show(){
System.out.println("name: "+name+" age: "+age);
}
}
public static void main(String[] args) {
Person person = new Person();
person.setName("caocao");
String name = person.getName();
System.out.println(name);
person.show();
}
// 运行结果
caocao
name: caocao age: 0
注意事项
<1> getName 即为 getter 方法, 表示获取这个成员的值.
<2> setName 即为 setter 方法, 表示设置这个成员的值.
<3> 当set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this, 相当于自赋值. this 表示当前实例的引用.
<4> 不是所有的字段都一定要提供 setter / getter 方法, 而是要根据实际情况决定提供哪种方法.
<5> 在 IDEA 中可以使用 alt + insert (或者 alt + F12) 快速生成 setter / getter 方法.在 VSCode 中可以使用鼠标右键菜单 -> 源代码操作 中自动生成 setter / getter 方法.
字段 vs 属性
我们通常将类的数据成员称为 “字段(field)” , 如果该字段同时提供了 getter / setter 方法, 那么我们也可以将它称为 “属性(property )”.
继承
继承就是子类继承父类的特征和行为,使得子类对象具有父类的信息,同时可以扩展。基本语法:
class 子类 extends 父类 {
}
a.使用 extends 指定父类.
b.Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).单继承多实现。
c.子类会继承父类的所有 public 的字段和方法.
d.对于父类的 private 的字段和方法, 子类中是无法访问的.
e.子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用
f. 子类可以拥有属于自己的属性和方法
子类访问父类成员:
父类无参的构造方法:super();
父类有参的构造方法:super(name);
访问父类属性:super.name;
访问父类方法:super.方法名();
子类不能被继承的父类成员:
构造方法
private成员
子类与父类不在同包,使用默认访问权限的成员
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Cat extends Animal {
public Cat(String name) {
// 使用 super 调用父类的构造方法.
super(name);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
cat.eat("猫粮");
Bird bird = new Bird("圆圆");
bird.fly();
}
}
protected 关键字
如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.两全其美的办法就是 protected 关键字.对于类的调用者来说, protected 修饰的字段和方法是不能访问的.对于类的子类和同一个包的其他类来说, protected 修饰的字段和方法是可以访问的.
// Animal.java
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
// Bird.java
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
// 对于父类的 protected 字段, 子类可以正确访问
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
}
}
// Test.java 和 Animal.java 不在同一个包中了.
public class Test {
public static void main(String[] args) {
Animal animal = new Animal("小动物");
System.out.println(animal.name); // 此时编译出错, 无法访问 name
}
}
小结: Java 中对于字段和方法共有四种访问权限
private: 类内部能访问, 类外部不能访问
默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
public : 类内部和类的调用者都能访问
final 关键字
final 关键字, 修饰一个变量或者字段的时候, 表示常量 (不能修改)
final int a = 10;
a = 20; // 编译出错
final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.
final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继承
final 关键字的功能是 限制 类被继承
“限制” 这件事情意味着 “不灵活”. 在编程中, 灵活往往不见得是一件好事. 灵活可能意味着更容易出错.
是用 final 修饰的类被继承的时候, 就会编译报错, 此时就可以提示我们这样的继承是有悖这个类设计的初衷的.
我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承.
super关键字
代码中由于使用了重写机制, 调用到的是子类的方法. 如果需要在子类内部调用父类方法可以使用super 关键字。
super只能出现在子类的方法和构造方法中;
super调用构造方法时,只能是第一句;
super不能访问法父类的private成员;
注意 super 和 this 功能有些相似, 但是还是要注意其中的区别
多态
多态就是统一接口,使用不同的实现,而执行不同的操作。
多态的实现方式: 静态多态(重载)、动态多态(重写)
重写和重载的区别:
重写:把在子类中把父类本身有的方法重新写一遍。
重载:在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。
向上转型和向下转型:
向上转型: 父类引用指向子类对象
父类 = new 子类();
向下转型: 子类引用指向父类对象
父类 对象1 = new 子类();
子类 对象2 = (子类)对象1;
向上转型:父类引用指向子类对象,将会丢失子类和父类中不一致的方法,并且父类引用调用的变量只能是父类中的。
class Person{
private String name = "Person";
int age=0;
public void function() {
System.out.println("this is person");
}
}
public class Child extends Person{
public String grade;
int age=10;
public void function() {
System.out.println("this is child");
}
public void b1(){
System.out.println("子类新方法");
} //B类定义了自己的新方法
@Test
public void test(){
Person p = new Child();
p.function();
System.out.println(p.age);
}
}
//输出:
“this is child”
0
并且我们是不能使用p.b1()的,编译错误,因为,此时该对象p已经失去了该方法。