一、封装
1.什么叫封装?
语法层次上:字段和方法都被private所修饰,此时就说把这个字段或方法进行了封装。
2.为什么进行封装?
1.安全
2.降低了类的调用者的学习和使用成本 从而降低了复杂程度
3.private实现封装(访问权限控制)
访问权限控制:private/public
被public修饰的成员变量或者成员方法可以直接被类的调用者使用
被private修饰的成员变量或成员方法只能在当前类中访问 无法在类外访问
4.getter(获取成员的值)和setter(设置成员的值)方法
被private修饰的字段无法直接使用,我们就提供一个公开的方法,即:getter和setter方法
此时如果需要获取或者修改这个private属性就需要使用getter和setter方法
//代码示例
class Person {
private int age;//private封装成员变量
//通过鼠标右键 Generate 选择getter和setter方法 点击需要的成员属性(idea快捷键)
public int getAge() {
return age;
}
public void setAge(int age) {
//this关键字代表当前对象的引用
this.age = age;
}
}
public class TestDemo {
public static void main(String[] args) {
Person person = new Person();//通过new实例化对象
//System.out.println(person.age);//age被封装起来,不能直接调用
person.setAge(10);//setAge方法赋值
System.out.println(person.getAge());//getAge方法获取
}
}
二、继承
1.什么叫继承?
语法层次上:将子类的共性抽取出来,使用关键字extends来实现继承(is-a语义:dog is a animal.)
2.为什么进行继承?
可以达到代码重用的效果
3.extends实现继承及注意事项
1.java中一个子类只能继承一个父类(单继承)
2.子类会继承父类的全部字段和方法(静态的方法和属性存在于方法区,不依赖于对象,因此不会被继承)
3.父类的private修饰的字段和方法,子类虽然继承了但不能访问
4.子类实例中也包含着父类实例,可以用super关键字得到父类实例的引用
5.子类对象要构造,先要帮助父类对象进行构造
(自己没构造时,编译器会自动给每个类提供一个无参构造方法;一旦某个类自己进行了构造,编译器将不会自动提供构造方法)
6.
//代码规则
class 子类/派生类 extends 父类/基类/超类 {
}
4.super关键字和this关键字
super关键字的使用:(super只能指向直接父类,而不能指向间接父类)
super.data //访问父类的成员
super.func() //访问父类的成员方法
super() //访问父类的成员构造方法
this关键字的使用:
this.data //调用当前对象的属性
this.func() //调用当前对象的方法
this() //调用当前对象的构造方法
相同点:1.this和super调用构造方法时都必须放在第一行
2.他们都是依赖对象的,都不能在静态方法中使用(eg:main函数)
不同点:
概念:this关键字代表访问本类中的属性和方法
super代表由子类访问父类的属性和方法
查找范围:this关键字先查找本类,本类如果没有就调用父类
super关键字不查找本类而直接调用
特殊:this关键字表示当前对象的引用
super关键字代表父类对象的引用
5.protected关键字
当父类的字段关键字变为private时,子类就不能访问了,改成public有违背了“封装”的初衷,因此解决方法如下:
1.给name提供getter和setter方法
2.把private改成protected(对于类的子类和同一个包的其它类来说,它修饰的字段是可以访问的)
6.访问权限控制关键字的访问权限 
注:default是包访问权限,是默认的,字段前面不写东西即可。
7.更复杂的继承关系
class A {
}
class B extends A {
}
final class C extends B {
}
public class D extends C {
//编译错误 ,无法从class C进行继承
}
8.final关键字(限制类被继承)
之前接触过final关键字修饰一个字段或者变量时,表示常量(不能修改)
eg:final int a = 10;
final关键字也能修饰类,此时表示被修饰的类不能被继承(如上面代码)
我们平时用的String字符串类,就是用final修饰的,不能被继承
三、组合
1.什么叫组合?
和继承相似,组合也是一种表达类之间关系的方式(a-part-of 或者 has-a的关系)
//一个学校包含若干老师和学生
class Student {
}
class Teacher {
}
class School {
//组合没有涉及到特殊语法以及关键字仅仅是将一个类的实例作为另外一个类的字段
public Student[] students;
public Teacher[] students;
}
2.为什么进行组合?
能够达到代码重用的效果
四、多态(一种思想)
1.向上转型
(1)什么是向上转型?
父类(animal)的引用,引用了子类(Bird)对象的写法称为向上转型(子类对象转成父类对象)
注意:向上转型之后只能通过父类引用访问父类自己特有的属性和方法,不能访问子类特有的属性和方法
Bird bird = new Bird("鸟");
Animal animal = bird;
//向上转型将上面两行代码写成如下形式
Animal animal = new Bird("鸟");
(2)向上转型发生的时机
①直接赋值(如上所示)
子类对象直接赋值给父类对象的引用(Animal animal = new Bird("鸟");)
②方法传参
1.只是通过一个animal.eat();
2.传递的参数不一样,代表这个引用引用的对象不一样
3.通过这个引用,调用同一个方法,表现出了不同的形式,这种情况叫做“多态”。
public class Test {
public static void func(Animal animal) {
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog("狗");
func(dog);//运行结果:执行狗的eat方法
Bird bird = new Bird("鸟");
func(bird);//运行结果:执行鸟的eat方法
}
}
③方法返回
public class Test {
public static Animal func2() {
Dog dog = new Dog("狗");
//原本应该返回Animal类型,但由于dog是animal的子类,因此可以返回
return dog;
}
public static void main(String[]args){
Animal animal = func2();
}
}
2.向下转型
(1)什么是向下转型?
父类对象转成子类对象称为向下转型
Animal animal = new Bird("鸟");
//(Bird)表示类型强制转换
Bird bird = (Bird)animal;
//刚才通过父类引用调用小鸟特有的fly方法是不被允许的,但通过向下转型鸟可以飞起来了
bird.fly();
(2)向下转型为什么不安全?
//父类引用的对象是Dog,狗类没有fly方法,虽然表面把它强转为Bird类,但本质上引用的是Dog类
Animal animal = new Dog("狗");
Bird bird = (Bird)animal;
bird.fly();
//编译不爆错,但执行结果抛出ClassCastException(类型转换异常)异常
因此为了让向下转型更安全,我们先判定animal本质上是不是Bird实例(instanceof),再进行转换,如下:
Animal animal = new Dog("狗");
//instanceof可以判定一个引用是否是某个类的实例,如果是,则返回true,此时在进行向下转型就比较安全
if(animal instanceof Bird) {
Bird bird = (Bird)animal;
bird.fly();
}
//Dog类不是Bird的实例,因此该if语句无法进入
3.运行时绑定(动态绑定)(多态的基础!!)
(1)什么是运行时绑定?
在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);
}
}
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void eat(String food) {
System.out.println(this.name + "鸟正在吃" + food);
}
}
//针对animal和animal1指向的对象不同,调用的类的方法不同
public class Test {
public static void main(String[] args) {
Animal animal = new Animal("yeye");
animal.eat("谷子");
Animal animal1 = new Bird("鸟");
animal1.eat("谷子");
}
}
(2)运行时绑定的前提?
1.父类实例化子类对象(发生向上转型)
2.子类和父类含有同名方法(函数名称相同,函数参数个数和类型相同,函数返回值相同)
3.通过父类引用调用该方法
(3)运行时绑定的是谁?
发生运行时绑定,绑定的是子类的方法(引用哪个子类对象,调用的是哪个子类方法)
4.方法重写
(1)什么是方法重写?
截取上述代码片段:
//父类eat方法
public void eat(String food) {
System.out.println(this.name + "动物正在吃" + food);
}
//子类eat方法
public void eat(String food) {
System.out.println(this.name + "鸟正在吃" + food);
}
我们发现,子类和父类的同名方法具有以下几个特征:
(函数名称相同,函数参数个数和类型相同,函数返回值相同)
这种情况我们称为重写/覆写/覆盖(Override)
(2)方法重写注意事项
1.关于返回值可以不相同,但是返回值之间要构成父子类关系(eg:返回值为Animal和Dog关系)【协变类型】
但是建议返回值最好相同
2.static修饰的静态方法不能重写
3.private方法不能重写(private方法只作用在当前类)
4.重写中子类方法的访问权限要大于等于父类方法的访问权限
(private<default<protected<public)(父类<=子类)
5.被final修饰的方法(密封方法)不能被重写
(3)重写和重载的区别
(4)在构造方法中调用重写的方法(一个坑)
class Animal {
protected String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
//此处调用eat方法,执行时结果为gou的eat方法,发生了动态绑定
eat();
}
public void eat() {
System.out.println(name + "eat()");
}
}
class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
}
public class Test {
public static void main(String[] args) {
//此处没有发生向上转型
Dog dog = new Dog("狗");
}
}
5.理解多态(一个引用,能表现出多种不同的形态)
父类引用,引用子类对象 ,通过父类引用调用父类和子类同名的覆盖/重写方法。
此时,如果父类引用引用的子类对象不一样,那么调用这个重写的方法表现的行为也不一样。
//打印多种形状
class Shape {
public void draw() {
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("⚪");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("□");
}
}
public class Test {
public static void drawMap(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Cycle cycle = new Cycle();
Rect rect = new Rect();
drawMap(cycle);
drawMap(rect);
}
}
6.为什么要使用多态?
1.类的调用者对类的使用成本降低
2.降低代码的“圈复杂度(条件语句和循环语句出现个数)”,避免使用大量的if-else
3.可扩展能力更强(上面但如果要新增一种形状,只需创建一个新类的实例就可以了)
//使用多态避免写很多if-else分支语句
public static void drawShapes() {
shape[] shapes = {new Cycle(),new Rect(),new Cycle(),new Rect()};
for(Shape shape : shapes) {
shape.draw();
}
}