面向对象
面向对象的四大特性:
- 抽象
抽象是指将子类的共性方法和属性提取到父类中,并且父类不提供具体的方法实现,而是将方法声明为抽象方法,把类声明为抽象类。子类继承抽象类,必须实现父类中的所有抽象方法,或者把子类本身也声明为一个抽象类。 - 继承
继承是指父类中含有子类共性的方法和属性,子类通过extends关键字继承父类的非私有属性和方法。 - 封装
封装是指把数据和方法封装到一个类中,把实现细节部分包装、隐藏起来。通过封装可以防止一个类中的数据被外部类随意访问。 - 多态
多态是指父类引用可以指向不同的子类对象,从而通过父类引用调用子类对象中重写的父类方法。当通过父类引用调用方法时,先检查父类中是否有该方法,如果没有则报错,如果有则查看子类中是否对该方法进行了重写,如果是,则调用子类中的该方法,否则调用父类中的该方法。
类和对象
- 类是对一类事物的抽象描述(如建筑图纸)
- 对象时一类事物的具体事例(如建好的房子)
- 类是对象的模板,对象是类的实体
类中的成员变量和一般的局部变量有两个重要的区别:(1)类的成员变量在类被实例化后就有默认的初始值,而局部变量没有默认值,需要先定义,再赋值,最后使用;(2)类的成员变量在堆内存中,因为new一个对象的时候是在堆中分配内存的,局部变量在栈内存中,因为方法调用是在栈中执行的。
类的成员变量初始值:整数类型(short、int、long)的初始值为0,小数类型(double、float)的初始值为0.0d,引用类型(String)的初始值为null,布尔类型的初始值为false,char类型的初始值为’u0000’。
下面用一个例子简述类和对象的内存图,以进一步了解类和对象。
例1:
//Demo1.java
public class Demo1 {
public static void main(String[] args) {
Person person = new Person();
person.name = "张三";
person.age = 10;
person.callOne("张三");
}
}
//Person.java
public class Person {
String name;
int age;
public void callOne(String name) {
System.out.println("有人联系"+name);
}
}
Person类代码在方法区中,当调用某个方法的时候,该方法会被压入栈中,new一个对象,该对象存储在栈区,并且,对象的属性会赋初始值。
1.main函数执行完new Person()之后
2.main函数执行完name和age赋值之后
3.main函数调用person.callOne()之后
调用callOne方法后,该方法被压入栈中执行
权限修饰符
public | protected | 不写(default) | private | |
---|---|---|---|---|
同一个类 | √ | √ | √ | √ |
同一个包的子类和无关类 | √ | √ | √ | |
不同包的子类 | √ | √ | ||
不同包的无关类 | √ |
封装
将属性隐藏起来(private修饰符),对外提供访问属性的接口(get和set方法),而不能在外部直接通过对象访问属性。
方法中的this关键字:
this指向该方法的调用对象,即哪个对象调用的方法,方法中的this关键字就指向哪个对象。
构造方法:
- 构造方法用来实例化对象的时候做一些初始化操作。
- Java的类中可以有多个构造方法,同名方法直接遵循方法重载原则(参数个数不同,参数类型不同)。
- 每个类会有一个默认的构造方法,但是当显示地写了构造方法时,默认的构造方法将不再提供。
继承
将多个类中相同的属性和方法提取到一个单独的类(父类)中,其他类(子类)通过继承(extends)这个类即可。子类可以访问父类中非私有属性和方法。
这种继承关系提高了代码的复用性,并且是多态的前提。
静态方法和属性也可以被子类继承。
父子类成员变量重名:
例:2:
//Person.java
public class Person {
String name;
int age;
int common = 1;
public void Ptest() {
System.out.println(common);
}
}
//Child.java
public class Child extends Person{
int common = 2;
public void Ctest() {
System.out.println(common);
}
}
//Demo1.java
public class Demo1 {
public static void main(String[] args) {
Person person1 = new Child();
System.out.println(person1.common); //1
Child person2 = new Child();
System.out.println(person2.common); //2
person2.Ptest(); //1
person2.Ctest(); //2
}
}
如果通过实例对象访问属性,则实例对象是那个类类型,访问的属性就属于那个类。如果通过类的方法访问同名属性,方法属于那个类,访问到的属性就属于那个类。
父子类成员方法重名:
方法重写:父子类出现同样的方法(方法名称、参数列表都相同)。子类重写的方法的访问权限必须大于等于父类方法的权限(public>protected>(不写,即default)>private;子类返回值范围必须小于等于父类方法返回值范围(如String类就是Object的子范围)
构造方法:
- 构造方法不会被继承
- 子类默认会调用父类的无参数构造方法(默认执行super()),可以通过在子类的构造方法中显示执行super()调用父类的任意构造方法,但是只能执行一次super()
super:super还可以用来在子类中访问父类的非私有成员变量和成员方法。
例3:
Demo1.java
public class Demo1 {
public static void main(String[] args) {
Child person = new Child();
person.Ctest(); // 2 1
person.printCommon(); //2 1
}
}
Person.java
public class Person {
String name;
int age;
int common = 1;
public void printCommon() {
System.out.println(this.common);
}
public void Ptest() {
System.out.println(this.common);
}
}
Child.java
public class Child extends Person{
int common = 2;
@Override
public void printCommon() {
System.out.println(this.common);
System.out.println(super.common);
}
public void Ctest() {
System.out.println(this.common);
System.out.println(super.common);
}
}
下面用内存图的方式加深对继承的理解:
抽象
- 关键字 abstract
- 抽象类不能实例化
- 含有抽象方法的类必须申明为抽象类
- 抽象类可以有构造方法
- 抽象类的子类必须重写父类的所有抽象方法
接口
- 关键字 interface和implements
- 在Java7中,接口类中可以包含:
成员变量
抽象方法 - 在Java8中,接口类中还可以额外包含:
默认方法
静态方法 - 在Java9中,接口类中还可以额外包含:
私有方法
成员变量
接口中定义的成员变量需要使用public static final
三个关键字进行修饰(但是也可以省略那三个关键字,编译器默认会加上),并且必须赋初值,之后不能修改。
抽象方法
接口中的抽象方法需要用public abstract
两个关键字修饰,但是也可以忽略掉,如下:
public interface InterfaceName {
public abstract void method1();
abstract void method2();
public void method3();
void method4();
}
默认方法和静态方法
public interface InterFaceName {
public default void method1() {
}
public static void method2() {
}
}
默认方法可以供接口实现类对象调用,可以解决接口升级的问题,如需要在接口中添加一个方法,在Java8以前,只能加抽象方法,实现该接口的类都必须重写该抽象方法,这样的改动很大。从Java8开始,可以通过在接口中定义默认方法,从而不用改动接口的实现类,就可以通过接口实现类对象使用该方法。
当然,接口的实现类也可以重写接口的默认方法。
接口的静态方法直接通过接口名来调用:InterFaceName.method2()
私有方法
接口中的私有方法只有默认方法可以调用,私有方法也可以定义为静态的,静态的私有方法只有默认方法和静态方法可以调用。
接口中的私有方法的意义何在:正因为出现了默认方法,如果接口中的多个默认方法都有相同的代码,则可以将相同的代码提取出来成一个单独的方法,设置为私有的。静态私有方法也是一样的道理,提取出多个静态方法中相同的代码,组成一个单独的私有静态方法。
接口多继承
一个类可以继承多个接口。
- 如果继承的接口中有重名的抽象方法,没关系,因为实现类会重写抽象方法的。
- 如果继承的接口中有重名的默认方法,则实现类(或子接口)必须重写同名的默认方法。
- 如果继承的接口中有重名的静态方法没关系,因为静态方法是通过接口名调用的。
- 如果一个类的继承的父类中的某个方法和实现的接口的某个默认方法重名(参数列表也相同),子类会选择执行父类的成员方法。
多态
多态指同一行为,具有不同的表现形式。
多态有三个前提:
- 有继承或者接口实现关系
- 子类或子接口进行方法重写
- 父类应用指向子类对象
例4:
//Demo2.java
public class Demo2 {
public static void main(String[] args) {
Animal animal1 = new Monkey();
animal1.eat();
animal1.lovely();
Animal animal2 = new Rabbit();
animal2.eat();
animal2.lovely();
Animal animal3 = new Monkey();
Animal animal4 = new Rabbit();
Animal.showEat(animal3);
Animal.showEat(animal4);
// animal3.climbTree();
// animal4.jumpTree();
}
}
//Animal.java
public abstract class Animal {
public abstract void eat();
public static void showEat(Animal animal) {
animal.eat();
}
public void lovely() {
System.out.println("动物都很可爱");
}
}
//Rabbit.java
public class Rabbit extends Animal {
@Override
public void eat() {
System.out.println("兔子吃胡萝卜");
}
public void lovely() {
System.out.println("兔子很可爱");
}
public void jumpTree() {
System.out.println("兔子会跳");
}
}
//Monkey.java
public class Monkey extends Animal {
@Override
public void eat() {
System.out.println("猴子吃香蕉");
}
public void lovely() {
System.out.println("猴子很可爱");
}
public void climbTree() {
System.out.println("猴子会爬树");
}
}
//运行结果
猴子吃香蕉
猴子很可爱
兔子吃胡萝卜
兔子很可爱
猴子吃香蕉
兔子吃胡萝卜
猴子会爬树
兔子会跳
子类重写父类一般方法
如果子类重写了父类一般方法lovely(),则通过父类引用变量调用lovely()方法时,调用的是实例对象中重写的lovely()方法,否则,调用父类的lovely()方法。
向下转型
注意到Demo2.main方法中注释的代码,在多态中,父类引用变量无法调用子类实例特有的方法和成员变量,必须向下进行转型:
public static void main(String[] args) {
...
// animal3.climbTree();
// animal4.jumpTree();
Monkey animal5 = (Monkey)animal3;
Rabbit animal6 = (Rabbit)animal4;
animal5.climbTree();
animal6.jumpTree();
}
一般地,向下转型之前,要先用instanceof关键字对引用变量进行验证,防止ClassCastException异常,变量名animal5 instanceof 数据类型Monkey
,如果animal5是Monkey的对象或者子类或者实现类,则返回true,否则返回false。
内部类
内部类的使用和定义如下:
例5:
//Car.java
public class Car {
private String grade = "ordinary";
class Engine {
public void showGrade() {
if("ordinary".equals(grade)) {
System.out.println("普通车");
}else{
System.out.println("高级车");
}
}
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
}
//Demo3.java
public class Demo3 {
public static void main(String[] args) {
Car car1 = new Car();
Car.Engine engine1 = car1.new Engine();
engine1.showGrade();
System.out.println(car1.getGrade());
}
}
类别类可以访问外部类的成员包括私有的。外部类要访问内部类成员,需要实例化内部类对象,如Car.Engine engine1 = car1.new Engine();
匿名内部类
匿名内部类的使用如下:
new 父类名后者接口名() {
//重写方法
}
例6:
//Demo4.java
public class Demo4 {
public static void main(String[] args) {
Person p = new Person(){
public void printA(){
System.out.println("a");
}
};
p.printA();
}
}
//Person.java
public abstract class Person {
public abstract void printA();
}