2.1 面向对象与面向过程

什么是面向过程?

面向过程,其实就是面向着具体的每一个步骤和过程,把每一个步骤和过程完成,然后由这些功能方法相互调用,完成需求。

例如:吃煎饼果子利用面向过程的思想:

  • 学习摊煎饼的技术
  • 买材料鸡蛋,油,葱等等
  • 开始摊
  • 收拾

什么是面向对象?

面向对象思想就是不断的创建对象,使用对象,指挥对象做事情。(如果有对象,直接用对象,对我们直接提供服务)

例如:吃煎饼果子利用面向对象的思想

  • 找会摊煎饼的大妈(创建一个摊煎饼大妈的对象)
  • 调用其摊煎饼的技能(功能),传递进去钱参数
  • 返回给我们一个煎饼

面向对象:着眼点在于找到一个能够帮助解决问题的实体, 然后委托这个实体来解决问题;

面向过程: 着眼点在于问题是怎样一步步的解决的, 然后亲力亲为的去解决这个问题;

Java是一种面向对象的语言:

用Java这门语言, 可以很容易的写出具有面向对象思维方式的代码

用面向对象的编程语言写出的代码, 一定是面向对象的代码? 错!

2.2 类和对象

类:是一组相关的属性和行为的集合(我们班所有的同学都具备相同的属性和行为,比如:姓名,年龄,学习,这样就把所有的学生成为学生类)

对象:是该类事物的具体体现(说某个同学时,他都具备自己特有的属性和行为)

类:学生 对象:具体的张三李四就是对象

类:车 对象:具体的开的奔驰、宝马,就是对象

2.3 类的定义

定义学生类:

定义成员变量和成员方法,其中成员变量和普通变量的区别是,成员变量是定义类的特征,在类内定义,成员变量是有默认值的(整型: 0, 浮点型: 0.0 ,布尔型: false,字符型: ‘\0’ ‘\u000’, 引用数据类型: null)

  1. public class Student {
  2. //特征:表现为属性,属性有默认值,整型: 0,浮点型: 0.0,布尔型: false
  3. //字符型: '\0' '\u000',引用数据类型: null
  4. //成员变量
  5. //姓名
  6. String name;
  7. //年龄
  8. int age;
  9. //行为:表现出的是方法
  10. //成员方法
  11. public void study() {
  12. System.out.println("好好学习,天天向上");
  13. }
  14. public void eat() {
  15. System.out.println("吃饱了才有力气减肥");
  16. }
  17. }

类的使用:

  1. public class StudentTest {
  2. public static void main(String[] args) {
  3. //创建对象
  4. //类名 对象名 = new 类名();
  5. Student s = new Student();
  6. //使用成员变量
  7. System.out.println("姓名:"+s.name);//null
  8. System.out.println("年龄:"+s.age);//0
  9. System.out.println("----------");
  10. //给成员变量赋值
  11. s.name = "孙俪";
  12. s.age = 30;
  13. //再次使用成员变量
  14. System.out.println("姓名:"+s.name);//孙俪
  15. System.out.println("年龄:"+s.age);//30
  16. System.out.println("----------");
  17. //调用成员方法
  18. s.study();
  19. s.eat();
  20. }
  21. }

注意事项:

  • 类名是一个标识符, 遵循大驼峰命名法
  • 一个java文件中可以写多个类, 但是只有和文件名相同的那个类名可以修饰为public
  • 在程序编译的时候, 每一个类都会生成一个.class字节码文件, 而且.class文件的名字和类名相同
  • 在程序中, 是先有类, 然后再从这个类中实例化一个对象

2.4 static关键字

static:

用关键字static修饰的成员, 叫做静态成员

没有用关键字static修饰的成员, 叫做非静态成员

静态成员:

  • 静态成员是属于类的, 在访问的时候, 需要用类来访问
  • 静态成员开辟空间, 是在这个类第一次被加载到内存中的时候开辟的

非静态成员:

  • 非静态成员是属于对象的, 在访问的时候, 需要用对象来访问
  • 非静态成员开辟空间, 是在这个对象被实例化的时候开辟的

注意:

  • 静态方法中, 不能直接访问非静态成员
  • 在非静态的方法中, 可以直接访问静态的成员
  1. class Person {
  2. String name;
  3. static int a;
  4. void eat() {}
  5. static void sleep() {}
  6. }
  7. class Program {
  8. public static void main(String[] args) {
  9. Person xiaomin = new Person();
  10. // 访问非静态成员
  11. xiaomin.name = "xiaomin";
  12. xiaomin.eat();
  13. // 访问静态成员
  14. Person.a = 10;
  15. Person.sleep();
  16. // 注:
  17. // 访问静态的成员, 也可以使用对象来访问, 但是会有警告
  18. // 推荐使用类来访问静态成员
  19. }
  20. }

2.5 private关键字

  • private是一个权限修饰符。
  • private可以修饰成员(成员变量和成员方法)
  • 被private修饰的成员只在本类中才能访问。
  1. public class Student {
  2. private String name;
  3. private int age;
  4. public void setName(String n) {
  5. name = n;
  6. }
  7. public String getName() {
  8. return name;
  9. }
  10. public void setAge(int a) {
  11. age = a;
  12. }
  13. public int getAge() {
  14. return age;
  15. }
  16. }
  17. /*
  18. * 测试类
  19. */
  20. public class StudentTest {
  21. public static void main(String[] args) {
  22. //创建对象
  23. Student s = new Student();
  24. System.out.println(s.getName()+"***"+s.getAge());
  25. //给成员变量赋值
  26. s.setName("杨幂");
  27. s.setAge(30);
  28. System.out.println(s.getName()+"***"+s.getAge());
  29. }
  30. }

2.6 this关键字

this:代表所在类的对象引用,方法被哪个对象调用,this就代表那个对象;

使用场景:用于解决成员变量被隐藏的问题,也就是局部变量和成员变量重名;

  1. public class Student {
  2. private String name;
  3. private int age;
  4. public void setName(String name) {
  5. //name = name;
  6. this.name = name;
  7. }
  8. public String getName() {
  9. return name;
  10. //return this.name;
  11. }
  12. public void setAge(int age) {
  13. this.age = age;
  14. }
  15. public int getAge() {
  16. return age;
  17. }
  18. }
  19. public class StudentTest {
  20. public static void main(String[] args) {
  21. // 创建对象
  22. Student s = new Student();
  23. System.out.println(s.getName() + "---" + s.getAge());
  24. s.setName("孙俪");
  25. s.setAge(30);
  26. System.out.println(s.getName() + "---" + s.getAge());
  27. }
  28. }

2.7 构造方法

构造方法作用

创建一个类的对象,并给对象的数据进行初始化

构造方法格式

方法名与类名相同;

没有返回值类型,连void都没有,没有具体的返回值。

  1. public class Student {
  2. public Student() {
  3. System.out.println("这是构造方法");
  4. }
  5. }
  6. public class StudentDemo {
  7. public static void main(String[] args) {
  8. //如何调用构造方法呢?
  9. //通过new关键字调用
  10. //格式:类名 对象名 = new 构造方法(...);
  11. Student s = new Student();
  12. }
  13. }

注意:

如果你不提供构造方法,系统会给出默认构造方法;

如果你提供了构造方法,系统将不再提供;

构造方法也是可以重载的,重载条件和普通方法相同。

  1. public class Student {
  2. private String name;
  3. private int age;
  4. public Student() {}
  5. public Student(String name) {
  6. this.name = name;
  7. }
  8. public Student(int age) {
  9. this.age = age;
  10. }
  11. public Student(String name,int age) {
  12. this.name = name;
  13. this.age = age;
  14. }
  15. public void show() {
  16. System.out.println(name+"---"+age);
  17. }
  18. }
  19. public class StudentTest {
  20. public static void main(String[] args) {
  21. //如何调用构造方法呢?
  22. //其实通过new关键字就可以
  23. //格式:类名 对象名 = new 构造方法名(...);
  24. Student s = new Student();
  25. s.show();
  26. //public Student(String name)
  27. Student s2 = new Student("孙俪");
  28. s2.show();
  29. //public Student(int age)
  30. Student s3 = new Student(30);
  31. s3.show();
  32. //public Student(String name,int age)
  33. Student s4 = new Student("孙俪",30);
  34. s4.show();
  35. }
  36. }

2.8 封装

封装是面向对象三大特征之一,是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。

封装原则,将不需要对外提供的内容都隐藏起来,把属性隐藏,提供公共方法对其访问,成员变量private,提供对应的getXxx()/setXxx()方法。

封装的好处,通过方法来控制成员变量的操作,提高了代码的安全性,把代码用方法进行封装,提高了代码的复用性。

2.9 继承

2.9.1 概述

如果有多个类中有相同的特征和行为(属性和方法),并且这多个类之间从逻辑上讲是有一定的关联的。那么此时,我们可以将这些共同的部分单独写到一个类中。

  • Monkey: name, age, gender, walk(), sleep(), eat()

  • Tiger: name, age, gender, walk(), sleep(), eat()

  • Elephent: name, age, gender, walk(), sleep(), eat()

可以将上述三种类中,共同的部分提取出来

Animal: name, age, gender, walk(), sleep(), eat()

此时,被提取出的这个类,称作是—父类,基类,超类

具有相同特征的那些类,称作是—子类,派生类

从A类派生出B类:A是B的父类,B是A的子类

他们之间的关系,是继承:子类继承自父类

为什么使用继承:

代码复用+功能拓展

继承的实现

通过extends关键字可以实现类与类的继承

格式:

public class 子类名 extends 父类名{}

  1. public class Person {
  2. private String name;
  3. private int age;
  4. public Person() {}
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. public int getAge() {
  12. return age;
  13. }
  14. public void setAge(int age) {
  15. this.age = age;
  16. }
  17. }
  18. public class Student extends Person {
  19. public void study() {
  20. System.out.println("学生要学到");
  21. }
  22. }
  23. public class Teacher extends Person {
  24. public void teach() {
  25. System.out.println("老师要讲到");
  26. }
  27. }

2.9.2 继承的优缺点

优点:

  • 提高了代码的复用性: 多个类相同的成员可以放到同一个类中;

  • 提高了代码的维护性:如果功能的代码需要修改,修改一处即可;

  • 让类与类之间产生了关系,是多态的前提;

缺点

  • 好处的第三点同时也是继承的弊端,类与类之间产生了关系,让类的耦合性增强了;
  • 设计原则:高内聚低耦合;

Java继承中成员变量的特点

  • 成员变量名称不一样,使用的时候非常简单

  • 成员变量名称一样的情况:

    • 在子类中访问变量:(就近原则)
    • 在方法的局部范围找,如果有就使用
    • 在子类的成员范围找,如果有就使用
    • 在父类的成员范围找,如果有就使用
    • 如果还找不到 就报错
  1. public class Father {
  2. //为了演示案,这里使用public修饰了成员变量,实际开发中用private
  3. //年龄
  4. public int age = 45;
  5. }
  6. public class Son extends Father {
  7. //身高
  8. public int height = 170;
  9. //年龄
  10. public int age = 20;
  11. public void show() {
  12. System.out.println(height);
  13. System.out.println(age);
  14. }
  15. public void printAge() {
  16. int age = 10;
  17. System.out.println(age);
  18. }
  19. }
  20. public class ExtendsTest {
  21. public static void main(String[] args) {
  22. Son s = new Son();
  23. //s.show();
  24. s.printAge();
  25. }
  26. }

2.9.3 super关键字

  • super含义

    • this代表本类对象的引用
    • super代表父类存储空间的标识(可以理解为父类对象引用)
  • 用法(this和super均可如下使用)

    • 访问成员变量
      this.成员变量 super.成员变量

    • 访问构造方法
      this(…) super(…)

    • 访问成员方法
      this.成员方法() super.成员方法()

  1. public class Father {
  2. public int age = 45;
  3. }
  4. public class Son extends Father {
  5. public int age = 20;
  6. public void printAge() {
  7. int age = 10;
  8. System.out.println(age);
  9. //我要访问子类成员范围的age
  10. System.out.println(this.age);
  11. //我要访问父类成员范围的age
  12. System.out.println(super.age);
  13. }
  14. }
  15. public class ExtendsTest {
  16. public static void main(String[] args) {
  17. Son s = new Son();
  18. s.printAge();
  19. }
  20. }

2.9.4 继承中的构造方法

一个对象在实例化的时候,需要先去实例化从父类继承到的成员,因为子类继承父类,会继承父类的非私有成员。

而子类在初始化的时候,可能会使用父类的数据,如果父类数据没有先初始化,子类就不能使用这些数据,所以,在子类初始化之前,一定要先完成父类数据的初始化。

在实例化父类部分的时候,默认使用父类中的无参构造

问题:如果父类中没有无参构造,或者父类中的无参构造子类无法访问(使用private修饰无参构造),则此时子类对象无法完成实例化。

解决:

  1. 给父类中添加一个子类能够访问到的无参构造方法

  2. 在子类的构造方法中,手动调用父类中能够访问到的构造方法,来实例化父类部分

  1. public class Father {
  2. public Father() {
  3. System.out.println("Father无参构造方法");
  4. }
  5. public Father(String name) {
  6. System.out.println("Father带参构造方法");
  7. System.out.println(name);
  8. }
  9. }
  10. public class Son extends Father {
  11. public Son() {
  12. //super();
  13. super("杨幂");
  14. System.out.println("Son无参构造方法");
  15. }
  16. public Son(String name) {
  17. //super();
  18. super("杨幂");
  19. System.out.println("Son带参构造方法");
  20. System.out.println(name);
  21. }
  22. }
  23. public class ExtendsTest {
  24. public static void main(String[] args) {
  25. Son s = new Son();
  26. System.out.println("---------");
  27. Son s2 = new Son("小幂");
  28. }
  29. }

2.9.5 继承中成员方法

通过子类对象去访问一个方法

  • 首先在子类中找

  • 然后在父类中找

  • 如果还是没有就会报错

  1. public class Father {
  2. public void show() {
  3. System.out.println("Father show");
  4. }
  5. }
  6. /*
  7. * Java继承中成员方法的访问特点:
  8. * A:子类中方法和父类中方法的声明不一样,这个太简单
  9. * B:子类中方法和父类中方法的声明一样,调用的到底是谁的呢?
  10. * 执行的是子类中的方法。
  11. */
  12. public class Son extends Father {
  13. public void method() {
  14. System.out.println("Son method");
  15. }
  16. public void show() {
  17. System.out.println("Son show");
  18. }
  19. }
  20. public class ExtendsTest {
  21. public static void main(String[] args) {
  22. Son s = new Son();
  23. s.method();
  24. s.show();
  25. //s.function();
  26. }
  27. }

2.9.6 方法重写

用子类的方法实现覆盖掉父类的实现

方法重写:子类中出现了和父类中一摸一样的方法声明

应用:当子类需要父类的功能,而功能主体子类有自己特有的内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容

注意:

注解

@Override:

是一个注解,常用在方法的重写中。表示在进行方法重写之前,进行一个验证。验证这个方法,到底是不是在重写父类中的方法。这个注解,可以添加,也可以不添加。但是,一般情况下,我们都是要加上去的

  • 表明该方法的重写父类的方法

  • 方法重写的注意事项

  • 父类中私有方法不能被重写

  • 子类重写父类方法时,访问权限不能更低

  • 子类重写父类方法时,建议访问权限一摸一样

注意:

  • 访问权限问题:
    子类方法的访问权限不能比父类方法中的访问权限低,要大于等于父类方法的访问权限
    public > protected > default > private

  • 关于返回值类型:在重写的时候,要求方法名和参数必须和父类中方法相同
    子类方法的返回值类型可以和父类方法中返回值类型相同。也可以是父类方法中返回值类型的子类型。
    重载和重写

  1. public class Phone {
  2. public void call(String name) {
  3. System.out.println("给"+name+"打电话");
  4. }
  5. }
  6. public class NewPhone extends Phone {
  7. public void call(String name) {
  8. System.out.println("开启视频功能");
  9. super.call(name);
  10. }
  11. }
  12. /*
  13. * 方法重写:子类中出现了和父类中一模一样的方法声明的情况。
  14. *
  15. * 方法重写的应用:
  16. * 当子类需要父类的功能,而功能主体子类又有自己的特有内容的时候,就考虑使用方法重写,
  17. * 这样即保证了父类的功能,还添加了子类的特有内容。
  18. */
  19. public class PhoneTest {
  20. public static void main(String[] args) {
  21. Phone p = new Phone();
  22. p.call("孙俪");
  23. System.out.println("-----------");
  24. NewPhone np = new NewPhone();
  25. np.call("孙俪");
  26. }
  27. }

继承的特点

  • Java语言是单继承的,一个类只能有一个父类,一个类可以有多个子类
    在某些语言中是支持多继承的。例如:C++、python…
    但是在多继承中,会有一个问题:二义性。
    虽然很多语言都抛弃了多继承,但是还是会用其他的方式来间接的实现类似多继承。
    例如:在java中是用接口实现的。

  • Java中所有的类都直接或者简介的继承自 Object

  • 子类可以访问到父类中能看的见的成员:被public或者protected修饰的

  • 构造方法不能继承。

2.10 抽象类与抽象方法

抽象类:

  1. abstract class 类名 {
  2. }

用关键字abstract修饰的类,就是抽象类

  1. 抽象类使用abstract来修饰,抽象类不能实例化对象。
  2. 抽象类中是可以写非静态的成员的,这时候这些非静态成员是可以继承给子类的。
  3. 抽象类中是可以包含构造方法的。

抽象方法:

用关键字abstract修饰的方法,就是抽象方法,抽象方法,只能够写在抽象类中。

  1. public abstract 返回值类型 方法名(参数);

特点:

抽象方法:

抽象方法使用abstract来修饰,只有声明,没有实现。

结合抽象类和抽象方法:

非抽象子类在继承一个抽象父类时,要实现父类中所有的抽象方法。

  1. //研发工程师
  2. abstract class Developer {
  3. public abstract void work();//抽象函数。需要abstract修饰,并分号;结束
  4. }
  5. //JavaEE工程师
  6. class JavaEE extends Developer{
  7. public void work() {
  8. System.out.println("正在研发淘宝网站");
  9. }
  10. }
  11. //移动端工程师
  12. class Android extends Developer {
  13. public void work() {
  14. System.out.println("正在研发某款app");
  15. }
  16. }

抽象类的特点:

  • 抽象类与抽象方法都必须使用 abstract来修饰
  • 抽象类不能直接创建对象
  • 抽象类中可以有抽象方法,也可以没有抽象方法
  • 抽象类的子类:实现了抽象方法的具体类或者抽象类

2.11 接口

2.11.1 接口概念

接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的”类”。

接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类来完成。这样将功能的定义与实现分离,优化了程序设计。

请记住:一切事物均有功能,即一切事物均有接口。

接口的特点:

  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
  • 接口中的方法都是公有的。

接口与类相似点:

  • 一个接口可以有多个方法。
  • 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在 .class 结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

接口与类的区别:

  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法。
  • 接口不能包含成员变量,除了 static 和 final 变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承。

抽象类和接口的区别

  • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  • 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
  • :JDK 1.8 以后,接口里可以有静态方法和方法体了。
  1. 访问权限修饰符 interface 接口名 {
  2. 抽象方法1;
  3. 抽象方法2;
  4. 抽象方法3;
  5. }

访问权限修饰符:和类一样,只能有 public 和默认的default权限。

  • 接口不是类,不能实例化对象。
  • 接口,暂时和类写成平级的关系。
  • 接口名字是一个标识符,遵循大驼峰命名法

接口中成员的定义:

  • 属性:接口中的属性,默认的修饰符是 public static final

  • 构造方法:接口中不能写构造方法

  • 方法:
    接口中的方法都是抽象方法
    接口中的方法访问权限修饰符都是public

2.11.2 接口的实现

接口不能实例化

按照多态的方式,由具体的实现类实例化。其实这也是多态的一种,接口多态。

接口的实现类,要么是抽象类,要么重写接口中的所有抽象方法

implements

  1. public class XX extends YY implements ZZ {
  2. XX
  3. }
  • 一个非抽象类在实现接口后,需要实现接口中所有的抽象方法。

  • 一个类在继承自一个父类后,还可以再去实现接口。
    如果同时有父类和接口,那么继承父类在前,实现接口在后

  • 一个类可以实现多个接口
    如果一个类实现的多个接口中有相同的方法,这个方法在实现类中只需要实现一次即可。

  • 接口之间是有继承关系的,而且接口之间的继承是多继承。

接口用关键字interface表示
格式:public interface 接口名 {}

类实现接口用implements表示

格式:public class 类名 implements 接口名 {}

  1. public class InterfaceDemo {
  2. public static void main(String[] args) {
  3. Cat cat = new Cat();
  4. cat.clumb()
  5. }
  6. }
  7. public class Cat implements Clumbing {
  8. @Override
  9. public void clumb() {
  10. System.out.println("猫会爬树");
  11. }
  12. }
  13. //定义了一个爬树的接口
  14. public interface Clumbing {
  15. //抽象方法
  16. public abstract void clumb();
  17. }

2.11.3 类、抽象类、接口

三者比较:

类与类

继承关系,只能单继承,但是可以多层继承(生物-》动物=》猫类)

类与接口

实现关系,可以单实现,也可以多实现。还可以在继承一个类的同时实现多个接口

接口与接口

继承关系,可以单继承,也可以多继承

三者区别

  • 成员区别

抽象类 变量,常量;有抽象方法;抽象方法,非抽象方法

接口 常量;抽象方法

  • 关系区别

类与类 继承,单继承

类与接口 实现,单实现,多实现

接口与接口 继承,单继承,多继承

  • 设计理念区别

抽象类 被继承体现的是:”is a”的关系。共性功能

接口 被实现体现的是:”like a”的关系。扩展功能

  1. interface 缉毒{
  2. public abstract void 缉毒();
  3. }
  4. //定义犬科的共性功能
  5. abstract class 犬科{
  6. public abstract void 吃饭();
  7. public abstract void 吼叫();
  8. }
  9. // 缉毒犬属于犬科一种,让其继承犬科,获取犬科的特性,
  10. //由于缉毒犬具有缉毒功能,那么它只要实现缉毒接口即可,这样即保证缉毒犬具备犬科的特性,也拥有了缉毒的功能
  11. class 缉毒犬 extends 犬科 implements 缉毒{
  12. public void 缉毒() {
  13. }
  14. void 吃饭() {
  15. }
  16. void 吼叫() {
  17. }
  18. }
  19. class 缉毒猪 implements 缉毒{
  20. public void 缉毒() {
  21. }
  22. }

2.12 多态

2.12.1概述

某一个事物,在不同时刻表现出来的不同状态。

  • 举例

猫可以是猫的类型。猫 m = new 猫();

同时猫也是动物的一种,也可以把猫称为动物

动物 d = new 猫();

  • 多态的前提和体现

    • 有继承关系
    • 有方法重写
    • 有父类引用指向子类对象
  1. public class TestDemo {
  2. public static void main(String[] args) {
  3. //多态
  4. Animal a = new Cat();
  5. a.eat();
  6. }
  7. }
  8. public class Cat extends Animal {
  9. public int age = 20;
  10. public int weight = 10;
  11. public void eat() {
  12. System.out.println("猫吃鱼");
  13. }
  14. public void playGame() {
  15. System.out.println("猫捉迷藏");
  16. }
  17. }
  18. public class Animal {
  19. public int age = 40;
  20. public void eat() {
  21. System.out.println("吃东西");
  22. }
  23. }

2.12.2多态的优缺点:

优点:

提高了程序的扩展性

缺点:

不能访问子类特有功能

  1. /*
  2. * 多态的好处:提高了程序的扩展性
  3. * 具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作。
  4. * 多态的弊端:不能使用子类的特有功能
  5. */
  6. public class TestDemo {
  7. public static void main(String[] args) {
  8. AnimalOperator ao = new AnimalOperator();
  9. Cat c = new Cat();
  10. ao.useAnimal(c);
  11. Dog d = new Dog();
  12. ao.useAnimal(d);
  13. Pig p = new Pig();
  14. ao.useAnimal(p);
  15. }
  16. }
  17. public class AnimalOperator {
  18. /*
  19. public void useAnimal(Cat c) { //Cat c = new Cat();
  20. c.eat();
  21. }
  22. public void useAnimal(Dog d) { //Dog d = new Dog();
  23. d.eat();
  24. }
  25. */
  26. public void useAnimal(Animal a) { //Animal a = new Cat();
  27. a.eat();
  28. }
  29. }
  30. public class Cat extends Animal {
  31. public void eat() {
  32. System.out.println("猫吃鱼");
  33. }
  34. }
  35. public class Dog extends Animal {
  36. public void eat() {
  37. System.out.println("狗啃骨头");
  38. }
  39. public void lookDoor() {
  40. System.out.println("狗看门");
  41. }
  42. }
  43. public class Pig extends Animal {
  44. public void eat() {
  45. System.out.println("猪拱白菜");
  46. }
  47. }

2.12.3多态中的转型

体现:

  • 父类的引用可以指向子类的对象
  • 接口的引用可以指向实现类的对象

转型:

  • 向上转型

由子类类型转型为父类类型,或者由实现类类型转型为接口类型

向上转型一定会成功,是一个隐式转换

向上转型后的对象,将只能访问父类或者接口中的成员

  • 向下转型

由父类类型转型为子类类型,或者由接口类型转型为实现类类型

向下转型可能会失败,是一个显式转换

向下转型后的对象,将可以访问子类或者实现类中特有的成员

  1. /*
  2. * 向上转型
  3. * 从子到父
  4. * 父类引用指向子类对象
  5. * 向下转型
  6. * 从父到子
  7. * 父类引用转为子类对象
  8. */
  9. public class TestDemo {
  10. public static void main(String[] args) {
  11. //多态
  12. Animal a = new Cat(); //向上转型
  13. a.eat();
  14. //a.playGame();
  15. //多态的弊端:无法访问子类特有方法
  16. //现在我就想使用子类特有方法,怎么办呢?
  17. //创建子类对象就可以了
  18. /*
  19. Cat c = new Cat();
  20. c.eat();
  21. c.playGame();
  22. */
  23. //现在的代码虽然可以访问子类的特有功能,但是不合理
  24. //因为我们发现内存中有两个猫类的对象
  25. //这个时候,我们得想办法把多态中的猫对象还原
  26. //这个时候,就要使用多态中的转型了
  27. //父类引用转为子类对象
  28. Cat c = (Cat)a;
  29. c.eat();
  30. c.playGame();
  31. }
  32. }
  33. public class Cat extends Animal {
  34. public void eat() {
  35. System.out.println("猫吃鱼");
  36. }
  37. public void playGame() {
  38. System.out.println("猫捉迷藏");
  39. }
  40. }
  41. public class Animal {
  42. public void eat() {
  43. System.out.println("吃东西");
  44. }
  45. }

2.12.4 instanceof关键字

针对于向下转型的。

如果向下转型不成功,会怎样?会有一个异常 ClassCastException

如何避免这种情况?在向下转型之前,我们先判断一下这个对象是不是要转型的类型怎么判断?

关键字 instanceof

  1. Animal animal = new Dog();
  2. if (animal instanceof Dog) {
  3. // 说明animal的确是一个Dog
  4. }

如果一个类中重写了父类的某一个方法。此时:

如果用这个类的对象来调用这个方法,最终执行的是子类的实现。

如果用向上转型后的对象来调用这个方法,执行的依然是子类的实现。

向上转型后的对象,归根到底还是子类对象。

  1. public class TestDemo {
  2. public static void main(String[] args) {
  3. //多态
  4. Animal a = new Cat(); //向下转型
  5. Cat c = new Cat();
  6. if(a instanceof Cat)
  7. c = a
  8. c.eat();
  9. c.playGame();
  10. }
  11. }
  12. public class Cat extends Animal {
  13. public void eat() {
  14. System.out.println("猫吃鱼");
  15. }
  16. public void playGame() {
  17. System.out.println("猫捉迷藏");
  18. }
  19. }
  20. public class Animal {
  21. public void eat() {
  22. System.out.println("吃东西");
  23. }
  24. }

2.13 包

java的包,类似电脑系统中的文件夹,包里存放的是类文件。

当类文件很多的时候,通常会采用多个包进行存放管理,这种方式称为分包管理。

在项目中,我们将相同功能的类放到一个包中,方便管理。并且日常项目的分工也是以包作为边界。

类中声明的包必须与实际class文件所在的文件夹情况相一致,即类声明在a包下,则生成的.class文件必须在a文件夹下,否则,程序运行时会找不到类。

声明格式:

通常使用公司网址反写,可以有多层包,包名采用全部小写字母,多层包之间用”.”连接

类中包的声明格式:

package 包名.包名.包名…;

注意:声明包的语句,必须写在程序有效代码的第一行(注释不算)

  1. package com.kaikeba
  2. import java.util.Scanner;
  3. import java.util.Random;

包的访问:

在访问类时,为了能够找到该类,必须使用含有包名的类全名(包名.类名)。

包名.包名….类名

  1. java.util.Scanner
  2. //带有包的类,创建对象格式:包名.类名 变量名 = new包名.类名();
  3. java.util.Scanner scan = new java.util.Scanner(System.in);

类的简化访问

  • 要使用一个类,这个类与当前程序在同一个包中(即同一个文件夹中),或者这个类是java.lang包中的类时通常可以省略掉包名,直接使用该类。
  • 要使用的类,与当前程序不在同一个包中(即不同文件夹中),要访问的类必须用public修饰才可访问。

包的导入:

我们每次使用类时,都需要写很长的包名。很麻烦,我们可以通过import导包的方式来简化。

可以通过导包的方式使用该类,可以避免使用全类名编写(即,包类.类名)。

导包的格式:

import 包名.类名;

当程序导入指定的包后,使用类时,就可以简化了。

  1. //导入包前的方式
  2. //创建对象
  3. java.util.Random r1 = new java.util.Random();
  4. java.util.Random r2 = new java.util.Random();
  5. java.util.Scanner sc1 = new java.util.Scanner(System.in);
  6. java.util.Scanner sc2 = new java.util.Scanner(System.in);
  7. //导入包后的方式
  8. import java.util.Random;
  9. import java.util.Scanner;
  10. //创建对象
  11. Random r1 = new Random();
  12. Random r2 = new Random();
  13. Scanner sc1 = new Scanner(System.in);
  14. Scanner sc2 = new Scanner(System.in);

import导包代码书写的位置:在声明包package后,定义所有类class前,使用导包import包名.包名.类名;

2.14 访问权限修饰符

用来描述一个类、方法、属性、接口、枚举…能够被访问到的一个范围

访问权限一共有四种:

公开(public)/保护(protected)/包(default / package)/私有(private)

public > protected > default > private

对应的访问权限修饰符一共有三个:

public/protected/private

注:包权限没有访问权限修饰符,如果一个方法、属性、类…没有使用任意的访问权限修饰符来修饰,那么他的访问权限就是包权限

访问权限 可以修饰什么 可以访问的范围
private 类成员 只能在当前的类中访问
default 类成员、类 只能在当前的包中进行访问
protected 类成员 可以在当前的包中访问,也可以在跨包的子类中访问
public 类成员、类 可以在项目中任意的位置进行访问
public protected default private
同一类中
同一包中(子类与无关类)
不同包的子类
不同包中的无关类

2.15 final关键字

修饰 意义
变量 这个变量的值不能改变,就是常量
表示是一个最终类,这个类无法被继承
方法 表示是一个最终方法,这个方法无法被重写

继承的出现提高了代码的复用性,并方便开发。但随之也有问题,有些类在描述完之后,不想被继承,或者有些类中的部分方法功能是固定的,不想让子类重写。

要解决上述的这些问题,需要使用到一个关键字final,final的意思为最终,不可变。final是个修饰符,它可以用来修饰类,类的成员,以及局部变量。

final使用:

final修饰类不可以被继承,但是可以继承其他类。

  1. class XX {}
  2. final class YY extends XX{} //可以继承XX类
  3. class ZZ extends YY{} //不能继承Fu类

final修饰的方法不可以被覆盖,但父类中没有被final修饰方法,子类覆盖后可以加final。

  1. class Father {
  2. // final修饰的方法,不可以被覆盖,但可以继承使用
  3. public final void method1(){}
  4. public void method2(){}
  5. }
  6. class Son extends Father {
  7. //重写method2方法
  8. public final void method2(){}
  9. }

final修饰的变量称为常量,这些变量只能赋值一次。

  1. final int i = 20;
  2. i = 30; //赋值报错,final修饰的变量只能赋值一次

引用类型的变量值为对象地址值,地址值不能更改,但是地址内的对象属性值可以修改。

  1. final Person s = new Person();
  2. Person p2 = new Person();
  3. p = p2; //final修饰的变量p,所记录的地址值不能改变
  4. p.name = "小明";//可以更改p对象中name属性值

修饰成员变量,需要在创建对象前赋值,否则报错。(当没有显式赋值时,多个构造方法的均需要为其赋值。)

  1. class Demo {
  2. //直接赋值
  3. final int m = 100;
  4. //final修饰的成员变量,需要在创建对象前赋值,否则报错。
  5. final int n;
  6. public Demo(){
  7. //可以在创建对象时所调用的构造方法中,为变量n赋值
  8. n = 2020;
  9. }
  10. }

注意:

抽象类可以用final来修饰吗?

不能!因为final表示这个类无法被继承。但是对于抽象类来说,如果无法被继承,则这个抽象类没有任何意义。

抽象方法可以用final修饰吗?

不能!因为final修饰的方法无法被重写。但是抽象方法又只能写在抽象类中。如果一个抽象方法用final来修饰了,此时这个方法将无法被非抽象子类重写,那这个子类就会有问题。

2.16 内部类

内部类概念

将类写在其他类的内部,可以写在其他类的成员位置和局部位置,这时写在其他类内部的类就称为内部类。其他类也称为外部类。

使用时机

在描述事物时,若一个事物内部还包含其他可能包含的事物,比如在描述汽车时,汽车中还包含这发动机,这时发动机就可以使用内部类来描述。

  1. class 汽车 { //外部类
  2. class 发动机 { //内部类
  3. }
  4. }

2.16.1 成员内部类

成员内部类,定义在外部类中的成员位置。与类中的成员变量相似,可通过外部类对象进行访问。

定义格式

  1. class 外部类 {
  2. 修饰符 class 内部类 {
  3. //其他代码
  4. }
  5. }

访问方式

  1. 外部类名.内部类名 变量名 = new 外部类名().new 内部类名();

案例:

  1. public class InnerClass {
  2. //访问内部类
  3. public static void main(String[] args) {
  4. //创建内部类对象
  5. Body.Heart bh = new Body().new Heart();
  6. //调用内部类中的方法
  7. bh.jump();
  8. }
  9. }
  10. class Body {//外部类,身体
  11. private boolean life= true; //生命状态
  12. public class Heart { //内部类,心脏
  13. public void jump() {
  14. System.out.println("心脏在跳动");
  15. System.out.println("生命状态" + life); //访问外部类成员变量
  16. }
  17. }
  18. }

2.16.2 局部内部类

定义在外部类方法中的局部位置。与访问方法中的局部变量相似,可通过调用方法进行访问。

定义格式

  1. class 外部类 {
  2. 修饰符 返回值类型 方法名(参数) {
  3. class 内部类 {
  4. //其他代码
  5. }
  6. }
  7. }

访问方式:在外部类方法中,创建内部类对象,进行访问

  1. class Party {//外部类,聚会
  2. public void puffBall(){// 吹气球方法
  3. class Ball {// 内部类,气球
  4. public void puff(){
  5. System.out.println("气球膨胀了");
  6. }
  7. }
  8. //创建内部类对象,调用puff方法
  9. new Ball().puff();
  10. }
  11. public class InnerClass {
  12. //访问内部类
  13. public static void main(String[] args) {
  14. //创建外部类对象
  15. Party p = new Party();
  16. //调用外部类中的puffBall方法
  17. p.puffBall();
  18. }
  19. }

2.16.3 匿名内部类

定义的匿名内部类有两个含义:

临时定义某一指定类型的子类

定义后即刻创建刚刚定义的这个子类的对象

  1. new 父类或接口(){
  2. //进行方法重写
  3. };
  1. //已经存在的父类:
  2. public abstract class Person{
  3. public abstract void eat();
  4. }
  5. //定义并创建该父类的子类对象,并用多态的方式赋值给父类引用变量
  6. Person p = new Person(){
  7. public void eat() {
  8. System.out.println(“eating.......”);
  9. }
  10. };
  11. //调用eat方法
  12. p.eat();

使用匿名对象的方式,将定义子类与创建子类对象两个步骤一次完成。

匿名内部类如果不定义变量引用,则也是匿名对象。

  1. new Pers
  2. on(){
  3. public void eat() {
  4. System.out.println(“eating.......”);
  5. }
  6. }.eat();