bug国国王最近有些郁闷,单身的程序猿越来越多了,单身久了,写的bug也越来越多 经过商议,决定请小爪大师出手,誓要找到一种低成本的方法,给每个单身的程序猿配一个对象
引言
面向过程的思想
考虑问题时,以一个具体的流程为单位,考虑它的实现办法,关注的是功能的实现
简单来说,就是考虑事情怎么做
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤实现,使用时,依次调用就可以了
面向对象的思想
考虑问题时,以具体的事物为单位,考虑它的属性(特征)及行为 (动作),关注的是整体设计
简单来说,就是考虑事情由谁来做
面向对象是把构成问题事务分解给各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为
面向对象的三个基本特征
封装:隐藏对象内部的属性和行为,使用者只能看到对外提供的方法和公开的信息。
继承:一般到特殊的关系,子类可以使用父类的数据和方法。继承表示类与类之间的关系
多态:多态可以理解为同一个行为的不同表现形态,比如,实现上,多个类实现同一名称的接口,通过重写实现不同的功能
将大象放入冰箱分几步
面向过程:
打开冰箱门
放入大象
关上冰箱门
面向对象:
冰箱的对象,提供打开门和关闭门的方法
大象的对象,提供进入冰箱的方法
1 类和对象
类(class)和对象(Object)是面向对象的核心概念
1.1 类和对象的关系
生活中的类和对象:
类 对象
人类 乔布斯
汽车类 老任与码的宝马
昆虫类 星爷的小强
类:是具有相同属性的一类事物的总称,是模板,一个抽象的概念。类是对象的抽象,N多对象的概括
对象:是实实在在存在的具体个体,可以由类创建,是类的实例。对象是类的具体,由类创建,由同一个类创建的对象具有相同的属性,行为
1.2 类和对象的基本用法
1) 类定义
基本语法:
[修饰符] class 类名 {
// 属性,成员变量
// 行为:方法
}
注意:访问修饰符可以省略,建议写出来
类名要符合命令规范,大驼峰原则
类体中,有且仅有两样东西——变量、方法
2)成员变量
也可称为类中的全局变量,定义在类中,且在方法之外
成员变量定义:
[修饰符] 数据类型 变量名 [= 初始值];
数据类型:可以是8种基本数据类型中的任意一种,也可以是引用数据类型(类,接口,数组等)
注意:成员变量,在不赋值的情况下,具有默认的初始值
public class Girl {
String name;
int age;
int height;
}
3)类中的方法
方法也称为类中的成员方法,用于描述对象的行为。
方法在之前已经讲过,直接用
public class Girl {
String name;
int age;
int height;
public void sleep() {
System.out.println("睡前敷个面膜,美美的一觉睡到天亮");
}
public void eat(String food) {
System.out.println("最喜欢吃的食物是" + food);
}
public void makeup() {
System.out.println("化妆是女人的必修课");
}
}
4)对象的创建
基本语法:
类名 对象名 = new 类名()
对象名:变量名
new:创建对象的关键字, 创建对象时,会在堆内存中申请内存空间,并且清理整个空间中的所有数据。
类名():表示调用类的默认的构造方法
注意:创建的对象属于引用类型变量,其本质为对象的引用,而非对象本身
Girl ruhua = new Girl();
上述代码中,new Girl() 表示创建了一个Girl类的对象
创建对象后,可以通过对象名来访问成员变量和方法,其语法为:
对象名.成员变量
对象名.方法
Girl ruhua = new Girl();
// 调用成员变量
ruhua.name = "如花";
ruhua.age = 16;
ruhua.height = 180;
// 调用成员方法
ruhua.sleep();
5)关于变量的思考
同一方法中,两个局部变量可以重名吗?
那么针对成员变量:
两个成员变量可以重名吗?
成员变量和局部变量可以重名吗?
public class Girl {
String name;
int age;
int height;
public void makeup() {
int age = 30;
System.out.println("化妆是女人的必修课");
}
}
注意:Java中采用最小作用域最强原则。 当成员变量和局部变量重名时,以局部变量为准——我的地盘我做主
1.3 构造方法
通过构造方法,可以实现对象的创建。
并且通过带参的构造方法,可以在创建对象的同时,对成员变量进行赋值。
基本语法:
修饰符 类名([参数列表]) {
语句
}
修饰符:大多数情况下使用public
返回类型:无
方法名:和类名一样
参数列表:传的参数一般是为了进行成员变量的初始化
注意:构造方法也是方法,只不过比较特殊
思考:刚才使用new Girl()创建一个girl,使用了Girl()构造方法,但是Girl类中并没有这个构造方法?
当我们没有显示地在一个类中书写构造方法时,系统会“赠送”一个默认的无参构造方法,即:
public Girl() {
System.out.println("构造方法");
}
通过如下命令可查看字节码文件
javap -c XXX.class
当我们手动地书写了一个构造方法,不管是无参的,还是有参的,系统都不会再“赠送”无参的构造方法。
public Girl(String _name) {
name = _name;
}
1.4 方法的重载overload
方法的重载:同一个类中,方法名相同,参数列表不同的方法。
参数列表不同,包括参数的类型,个数,顺序不同
重载和方法的返回值类型、修饰符没有关系
类中的方法、构造方法都可以重载
1)构造方法的重载
public Girl(String _name, int _age) {
name = _name;
age = _age;
}
public Girl(int _age, String _name) {
name = _name;
age = _age;
}
public Girl(String _name, int _age, int _height) {
name = _name;
age = _age;
height = _height;
}
2)成员方法的重载
public void sleep() {
System.out.println("每天睡到自然醒");
}
public void sleep(int hour) {
System.out.println("每天睡" + hour + "个小时");
}
// 返回值不一样,不代表重载,编译会报错
public int sleep(int hour) {
return hour + 10;
}
1.5 this关键字
之前的方法中,当使用成员变量时,我们直接使用变量名,这种做法的可读性较差。如果成员变量和局部变量同名,直接调用时,会引起冲突
String name;
int age;
// 构造方法中,name表示哪个变量?
public Girl(String name, int age) {
name = name;
age = age;
}
对此,可以通过this关键字,this在类的方法中使用,用于访问其他的成员变量和方法。
this指向某个类的当前实例对象
注意:this不能用在静态方法中
1)调用成员变量
在构造方法中,可用于区分成员变量和局部变量,解决名称的冲突
// 构造方法传参,一般用于给成员变量赋值
public Girl(String name, int age) {
this.name = name;
this.age = age;
}
public void changeName(String newName) {
this.name = newName;
}
注意:在其他方法中调用时,如果不存在名称冲突,this可以不写
2)调用成员方法
public void eat() {
System.out.println("饱餐一顿");
// 调用其他的方法
this.sleep();
}
注意:this可以不写
3)调用构造方法
在构造方法中,可以通过this调用类中其他的构造方法。但是该语句必须放在第一行
public Girl(String _name, int _age) {
// 必须放在第1行
this();
age = _age;
}
1.6 static关键字
Java中,static关键字可以修饰类的成员变量、成员方法。
被static修饰后,其称为类的静态变量、静态方法,也可称为类属性、类方法等
如访问控制权限允许,可不必创建该类对象,而直接用类名加 "."调用static属性或方法
1)静态变量
static标记的成员变量由整个类(所有对象)共享,无论该类是否产生了对象,产生了多少对象,都有且只有一份该变量
public class Girl {
public static int LIKE_DIAMOND = 1;
public void makeup() {
// 同一个类中,可以直接调用,也可以通过类名调用
System.out.println(LIKE_DIAMOND);
System.out.println(Girl.LIKE_DIAMOND);
}
}
// 在类外,一般都是通过类名进行调用
System.out.println(Girl.LIKE_DIAMOND);
注意:static不能用于修饰局部变量
2)静态方法
public class Girl {
public static void shopping() {
System.out.println("大多数女孩都喜欢逛街");
}
public void sleep() {
// 和静态变量类似,在同一个类中,可以直接调用,也可通过类名调用
shopping();
Girl.shopping();
}
}
// 类外,一般通过类名直接调用
Girl.shopping();
注意:静态方法可以通过类名访问(常规做法),也可以通过对象名访问
3)修饰代码块
Java中,使用大括号括起来的一堆代码,称为代码块。代码块位于类的内部,方法的外部。
Java中的代码块分为:静态代码块,实例代码块。
public class Girl {
static {
System.out.println("这是静态代码块");
}
{
System.out.println("这是实例代码块");
}
public Girl() {
System.out.println("这是构造方法");
}
}
public static void main(String[] args) {
Girl girl = new Girl();
Girl girl1 = new Girl();
}
区别:
static代码块在其所属的类被加载时执行一次,实例代码块每次创建对象,都会执行
static代码先于实例代码块执行,二者都先于构造方法执行,即
静态代码块 > 实例代码块 > 构造方法
static代码块中可以用来初始化类属性,实例代码块可以用来初始化成员变量
1.7 包
在企业级软件开发中,业务逻辑庞大,复杂,类的数目也相当多。
为了结构清晰地管理,协同开发,以及解决类名重复的问题,Java引入了包(package)的概念。
用package来声明包,package语句必须是java源文件中的第一条语句。
1)创建包名
在package语句中,用“.”来指明包(目录)的层次。
包名一般全部小写,由公司域名倒置,业务模块名称等组成,比如com.glls.项目名.模块名称
package com.glls;
package com.glls.stusys.common;
我们见过的java提供的包:
java.util
2)导入包
为了使用定义在不同包中的JAVA类,需要使用import语句导入所需要的类。
基本语法格式:import package1 [.package2…] (classname | *);
package com.glls;
import java.util.Scanner;
public class Girl {
}
2 封装
封装是面向对象的核心思想,将对象的属性和行为进行封装,不让外界知道具体的实现细节。
通过类的封装,可以将类的信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的公开方法实现对隐藏信息的操作和访问
类的封装的实现:私有化类中的成员属性(私有化使用的关键字private),提供公开的get/set方法
3 继承
龙生龙,凤生凤,老鼠的儿子会打洞
程序中,继承用于描述事物之间的所属关系。
Java中,通过继承可以在现有类的基础上,构建一个新类。
我们可以将公共的属性,方法抽取出来,形成父类(公有类),通过继承,子类将自动拥有父类的属性、方法。 而且,子类可以扩充父类没有的属性,方法,从而满足更多的需求通过继承。通过继承可以达到类的简化,代码复用的目的
实现继承的类称为子类,也叫派生类,而被继承的类称为父类,也叫超类或基类
普通方法和属性可以被继承,但是构造器、私有的变量和方法不能被继承
基础语法:
[修饰符] class 类名 extends 父类名 {
}
public class Person {
private String name;
private int age;
public void sleep() {
}
public void eat() {
}
}
public class Girl extends Person {
public void makeup() {
}
}
调用时,虽然子类没有sleep等方法,但是却可以访问这些方法
Java类只支持单继承,不允许多重继承
一个子类只能有一个直接父类
一个父类可以派生出多个子类
public class Mammals {
}
public class Person extends Mamals {
}
public class Girl extends Person {
}
3.1 super关键字(理解)
应用场景:当想要调用父类中的属性/方法/构造方法时,可以使用super关键字。
1)调用父类的成员变量和方法
super.属性:访问父类中定义的属性
super.方法():调用父类中定义的成员方法(非静态)
super只能在成员方法和构造方法中使用,不能在静态方法中使用(和this相同)
public class Person {
String name;
int age;
public void eat() {
System.out.println("吃乎?肥也;不吃,馋也");
}
}
public class Girl extends Person {
public void sleep() {
//通过super调用父类的属性
super.name = "hehe";
//继承了父类,拥有了父类的属性
this.name = "hehe";
//this可以省略
name = "hehe";
//通过super调用父类的方法
super.eat();
this.eat();
eat();
System.out.println("中午不睡,下午崩溃");
}
}
2)调用父类的构造方法
super(...):用于在子类构造器中调用父类对应构造器
super()调用父类构造方法,只能在子类构造方法中,且必须放在第一行。
super()可以通过传参,调用父类重载的构造方法
子类构造方法中,默认会调用父类无参构造方法。
public class Person {
public Person() {
System.out.println("父类的构造方法");
}
public Person(String name) {
this.name = name;
System.out.println("父类的构造方法");
}
}
public class Girl extends Person {
int height;
public Girl() {
System.out.println("子类的构造方法");
}
public Girl(String name, int height) {
super(name);
this.height = height;
System.out.println("子类的构造方法");
}
}
3)子类对象实例化过程
创建子类对象时,按顺序执行:
父类static{}
子类static{}
父类{}
父类构造方法
子类{}
子类构造方法
3.2 访问控制修饰符(理解)
Java中,可以在类、属性、方法前面加上修饰符,进行访问权限的控制
Java中提供了4中访问级别:
private(私有的)、默认的、 protected(受保护的)、public(公共的)
注意:默认的 用来表示不加任何修饰符时的级别 private,protected不可以修饰类
访问控制级别从低到高顺序如下:
private -> 默认的 -> protected -> public
private:只对当前类可见
默认的:对同一包中的类可见
protected:对同一包中的类和所有子类(不管子类是否在同一个包中)可见
public:对一切类可见
参考代码:(注意包名)
package com.glls.privilege
package com.glls.privilege;
public class Animal {
private void eat() {
System.out.println("private eat");
}
void drink() {
// 私有的只能在类内部调用
this.eat();
System.out.println("default drink");
}
protected void sleep() {
this.eat();
System.out.println("protect sleep");
}
public void play() {
this.eat();
System.out.println("public play");
}
}
package com.glls.privilege;
public class Dog extends Animal {
public void jump() {
// 同一包中,子类方法不能访问私有的方法
// this.eat();
this.drink();
this.sleep();
this.play();
}
}
package com.glls.privilege;
public class App {
public static void main(String[] args) {
// TODO Auto-generated method stub
Dog dog = new Dog();
// 同一包中,对象可以访问default,protected,public方法
dog.drink();
dog.sleep();
dog.play();
dog.jump();
}
}
另一个包里
package com.glls.privilege2;
package com.glls.privilege2;
import com.glls.privilege.Animal;
public class Cat extends Animal {
public void jump() {
// 其他包中子类,不能访问private,defult方法
// this.eat();
// this.drink();
this.sleep();
this.play();
}
}
package com.glls.privilege2;
import com.glls.privilege.Dog;
public class App {
public static void main(String[] args) {
// TODO Auto-generated method stub
Dog dog = new Dog();
// 其他包的对象,不能访问default/protected
// dog.drink();
// dog.sleep();
dog.play();
dog.jump();
}
}
修饰符 | 同一个类中 | 同一个包中 | 子类中(不同包) | 全局 |
---|---|---|---|---|
private | Yes | |||
默认的 | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
注意: 局部变量,方法形参不能使用权限修饰符 访问成员的前提是:首先能访问成员所属的类
3.4 重写override
在子类中,可以根据需要,对从父类中继承来的方法进行改造,即方法的重写(覆盖)/override
重写方法必须和被重写方法具有相同的方法名、形式参数列表
重写方法的返回值类型不能大于被重写方法(相同或是其子类)
重写方法的访问权限不能严于被重写方法(相同或大于)
public class Father {
public void jump() {
System.out.println("跳远健将,10米小意思");
}
protected void drink() {
System.out.println("啤酒随便喝");
}
}
public class Son extends Father {
// 为了标明该方法是对父类方法的重写,使用@Override注解进行修饰(注解相当于一个标签)
@Override
public void jump() {
System.out.println("废了,只能跳1米");
}
//修饰符可以是public、protected,不能是default和private
@Override
public void drink() {
// TODO Auto-generated method stub
super.drink();
System.out.println("啤酒白酒掺着喝");
}
}
public class Daughter extends Father {
@Override
public void jump() {
// 调用父类的方法
super.jump();
System.out.println("不仅跳的远,还跳的高,高10米");
}
}
注意:父类的静态方法不能被子类重写
overload与override区别(重点)
名称 | overload | override |
---|---|---|
范围 | 同一个类 | 继承关系中 |
方法声明 | 方法名相同,参数列表不同 | 方法名相同,参数列表相同 |
返回值类型 | 没有要求 | 子类不能大于父类(了解) |
访问权限 | 没有要求 | 子类不能比父类更严格(了解) |
3.3 Object类
Object类是所有Java类的根父类(最高父类),任何一个java类(class)都直接或者间接继承Object类如果在类声明中未使用extends关键字指明其父类,则默认父类为Object类
public class Person {
...
}
等价于
public class Person extends Object {
...
}
常用方法:
方法 | 说明 |
---|---|
Object clone() | 创建并返回对象的拷贝,属于浅拷贝 |
boolean equals(Object) | 比较两对象是否相等 |
void finalize() | 释放对象时,垃圾回收器调用该方法 |
Class getClass() | 返回一个对象所属的类的Class类型 |
int hashCode() | 返回该对象的hash值(散列值) |
void notify() | 唤醒该对象上等待的某一个线程 |
void notifyAll() | 唤醒该对象上等待的全部线程 |
String toString() | 返回该对象的字符串表示 |
void wait() | 当前线程等待 |
hashCode()方法
返回对象的hash值
Dog dog = new Dog();
System.out.println(dog.hashCode());
String str = "hello";
System.out.println(str.hashCode());
toString()方法
Object类中方法,返回值为String类型,可用于描述当前类对象的有关信息。
打印对象时,默认显示:类名@hashcode值
如果想输出更有意义的信息,可以重写toString()
重写后,再次打印该对象,将自动调用该对象的toString()方法
public class Girl {
private String name;
private int age;
private int height;
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name + ":" + this.age;
}
}
3.6 final关键字
在Java中,声明类、属性和方法时,可使用final修饰符来修饰。
final表示最终、不可改变
具体如下:
final标记的类不能被继承。
final标记的方法不能被子类重写。
final标记的变量(成员变量或局部变量)即为常量,一般需要在声明时赋值,且之后不能改变。
1)final修饰类
public final class Bird {
}
2)final修饰方法
public class Bird {
public final void fly() {
System.out.println("想要飞却怎么样也飞不高");
}
}
3)final修饰变量
package com.glls.finals;
public class Bird {
private final int weight = 0;
// 定义时可以不赋值
private final String color;
// {
// // 可以在代码块中赋值一次
// color = "black";
// }
public Bird() {
//也可以通过构造方法赋值一次
this.color = "black";
}
public final void fly() {
System.out.println("想要飞却怎么样也飞不高");
}
public void work() {
// 无法修改再次修改final修饰的成员变量的值
// weight = 180;
// color = "white";
System.out.println(this.color);
// final修饰局部变量
final int height = 10;
// 定义的时候不赋值
final int age;
// 赋值一次
age = 1;
System.out.println(age);
}
}
注意:final修饰的成员变量必须在声明时或在每个构造方法或对应语句块中显式赋值(不会被默认初始化),才能使用。
4)Java中的常量
在Java中,可以将一些有固定意义、固定值、或者程序中需要,但基本不会发生变化的值,如:月份,圆周率,目前中国运营商的名字等,声明为常量,统一放到某个类中,方便管理、维护。
语法:
public static final 类型 变量名 = 值;
public static final int YES = 1;
public static final int NO = 0;
4 抽象类和接口
4.1 抽象类
应用场景:当每个子类有自己独特行为的时候,父类不需要提供具体的实现,只用规定每个子类拥有这个能力就可以了。此时,父类可以声明为抽象类,该行为(方法)可以声明为抽象方法。
语法:
[修饰符] abstract class 类名{
…
[修饰符] abstract 返回值类型 方法名([形参列表]);
}
理解:抽象类,抽象方法可以理解为“还没有竣工”的类,方法,需要其他类(子类)重新完善(重写)
抽象方法是没有方法体的方法,抽象方法必须包含在抽象类中
抽象类可以包含或不包含抽象方法,也可以包含或不包含普通方法
抽象类不能直接实例化,但是仍然有构造方法,子类继承抽象类后,构造方法的执行顺序和普通类相同
抽象类可以被继承,此时子类必须全部重写父类的抽象方法,否则子类也必须声明为抽象类
可以声明抽象类类型的引用,把它作为方法形参,返回值类型等
public abstract class Shape {
public int width; // 几何图形的长
public int height; // 几何图形的宽
public Shape() {}
public Shape(int width, int height) {
this.width = width;
this.height = height;
}
// 计算面积
public abstract double area();
}
//正方形的类
public class Square extends Shape {
// 重写抽象方法
@Override
public double area() {
// TODO Auto-generated method stub
return width * height;
}
}
public class Triangle extends Shape {
@Override
public double area() {
// TODO Auto-generated method stub
return width * height * 0.5;
}
}
static | 属性 | 静态变量(类变量),使用类名调用 |
---|---|---|
方法 | 静态方法(类方法),静态方法中只能调用静态变量或静态方法,不能使用this,super | |
代码块 | 类加载时静态代码块运行,而且只运行一次 | |
final | 变量 | 相当于常量,比如final int i=12,值不能被修改 |
方法 | 该方法不能被子类重写 | |
类 | 该类不能被继承 | |
abstract | 方法 | 抽象方法只有方法声明,没有方法体 |
构造方法和static方法不能是抽象的 | ||
有抽象方法的类称为抽象类,类名前必须加abstract | ||
类 | 抽象类不能被实例化 | |
抽象类可以作为类型引用,修饰形参,方法返回值 | ||
抽象类可以有成员变量和方法 | ||
子类必须实现父类的抽象方法,否则子类也要是抽象类 |
抽象类优势:
抽象类可以将已经实现的方法提供给其子类使用,使代码更加复用
抽象类中的抽象方法在子类中重写,保证子类具有自身的独特性、多样性
通过抽象类指向其子类的对象,可以实现多态
4.2 接口
如果抽象类中的所有方法都是抽象的,这时可以使用接口。接口,表示一种能力。
Java中,只支持单根继承,而且实际设计中,也会弱化继承的使用。那么,如果需要同时继承两个父类,比如:上古神兽,可以象鸟一样飞,还可以像鱼在水里游。我们已经有鱼类,鸟类,那要定义神兽类,是无法同时继承鸟类和鱼类两个父类的,该如何实现?
为了解决上述问题,Java引入接口概念。接口是一种特殊的抽象类,即“把抽象类抽象地更彻底”
接口基本语法:
[修饰符] interface 接口名 [extends 父接口1, 父接口2...]{
[public] [static] [final] 常量类型 常量名 = 常量值;
[public] [abstract] 返回值类型 方法名([形参列表]);
// 默认方法可以有方法体
[public] default 返回值类型 方法名([形参列表]){
.....
}
// 静态方法可以有方法体
[public] static 返回值类型 方法名([形参列表]){
.....
}
}
注意:
接口不可以直接被实例化,也不包含构造方法
接口中常量默认的修饰符是"public static final",定义时可以不写
接口中的方法默认的修饰符是"public abstract",定义时可以不写
JDK8后,接口中也可以定义带方法体的方法,default修饰的方法表示虚拟扩展方法(Virtual extension methods)
public interface BirdAbility {
// 定义常量
public static final int MAX_LENGTH = 108000;
// 定义没有实现的方法
void fly();
// 定义带方法体的方法
default void haha() {
System.out.println("haha");
}
// 定义带方法体的静态方法
static void hehe() {
System.out.println("hehe");
}
}
public interface FishAbility {
void swim();
}
实现接口的语法:
[修饰符] class 类名 [extends 父类名] [implements 接口1, 接口2...]{
一个类可以同时实现多个接口,用","分隔
public class GodMan implements BirdAbility, FishAbility {
@Override
public void swim() {
// TODO Auto-generated method stub
System.out.println("下五洋捉鳖");
}
@Override
public void fly() {
// TODO Auto-generated method stub
System.out.println("上九天揽月");
}
}
public class App {
public static void main(String[] args) {
System.out.println(BirdAbility.MAX_LENGTH);
BirdAbility.hehe();
GodMan wukong = new GodMan();
wukong.fly();
wukong.swim();
wukong.haha();
}
}
接口的使用:
接口体现为一种能力,如果需要复用该功能,或者让某个类拥有该能力,只需要使用implements关键字即可,方便功能的扩充、组装
同时,接口也是一种约束,规范,如果接口规定了“飞”的方法,而猪类实现了这个接口,那么猪也必须得会飞。使子类拥有相同的能力,只不过具体子类有自己的一套实现方式。
如果想整合这些“能力”,可以这么做:
public interface Ability extends BirdAbility, FishAbility{
// 额外定义其他方法
void transform();
}
public class GodMan2 implements Ability {
@Override
public void swim() {
// TODO Auto-generated method stub
System.out.println("下五洋捉鳖");
}
@Override
public void fly() {
// TODO Auto-generated method stub
System.out.println("上九天揽月");
}
@Override
public void transform() {
// TODO Auto-generated method stub
System.out.println("看我七十二变");
}
}
4.3 接口和抽象类比较
比较点 | 抽象类 | 接口 |
---|---|---|
定义 | 用abstract修饰 | 用interface修饰 |
组成 | 抽象方法,普通方法,构造方法,成员变量 | 抽象方法,静态常量 |
使用 | 子类继承(extends) 子抽象类(extends) | 实现类实现(implements) 子接口继承(extends)父接口 |
关系 | 抽象类可以实现接口 | 接口不能继承抽象类 |
对象类型 | 都可以通过指向子类对象的引用实现多态 | |
局限 | 不能多继承,不能实例化 | 多实现,不能实例化 |
选择应用 | 由于单根继承,从扩充、规范角度,建议优先选择接口 |
5 多态
多态可以理解为同一个行为的不同表现形态,即将父类的引用指向子类的对象。
Java的多态通过类继承、方法的重写及父类引用子类的对象体现。一个父类可以有多个子类,每个子类都重写父类的方法,把子类的对象赋值给父类的变量时,父类的变量调用不同子类的同一个方法,表现为不同的形态。
5.1 多态的常用实现形式
当子类中重写了父类的方法,方法将按照对象的运行时类型调用。
1)父类(或接口)引用指向子类对象
public class App {
public static void main(String[] args) {
Animal helloKetty = new Cat();
helloKetty.counterattack();
//无法调用,报错
helloKetty.eat();
Animal fugui = new Dog();
fugui.counterattack();
}
}
一个引用类型变量如果声明为父类的类型,但实际指向的是子类对象,那么该变量就不能再访问子类中独有的属性和方法
2)父类(或接口)作为方法的形参
public abstract class Animal {
/**
* 被打后,反击
*/
abstract void counterattack();
}
public class Cat extends Animal {
@Override
void counterattack() {
// TODO Auto-generated method stub
System.out.println("敢打我,挠花你了脸");
}
public void eat() {
System.out.println("吃吃喝喝挺好");
}
}
public class Dog extends Animal {
@Override
void counterattack() {
// TODO Auto-generated method stub
System.out.println("敢打我,咬你了小jj");
}
}
public class Outman {
//使用的是父类
void beat(Animal animal) {
System.out.println("见到啥动物,就欺负啥动物");
animal.counterattack();
}
}
public class App {
public static void main(String[] args) {
// TODO Auto-generated method stub
Outman dijia = new Outman();
Cat helloKetty = new Cat();
dijia.beat(helloKetty);
Dog wangcai = new Dog();
dijia.beat(wangcai);
}
}
3)父类(或接口)作为方法的返回值
参考附录的简单工厂模式
5.2 对象的类型转换
领养宠物时,返回的为Pet类型对象,而父类声明无法调用子类独有的方法,此时需要进行对象的类型转换。
自动转换:子类到父类的类型转换 (多态)
强制转换:父类到子类的类型转换,向下转型
注意:无继承关系的对象间无法进行转换
public class App {
public static void main(String[] args) {
// TODO Auto-generated method stub
Animal helloKetty = new Cat();
helloKetty.counterattack();
//强制类型转换
Cat ketty = (Cat)helloKetty;
ketty.eat();
}
}
转换前常使用instanceof操作符测试是否可以转换
instanceof:判断对象是否是某个类或接口的实例对象或子类的实例对象。避免向下转型时,出现转换异常,从而增强代码的健壮性
语法:
// 返回boolean类型的值
对象 instanceof 类|接口
public class App {
public static void main(String[] args) {
// TODO Auto-generated method stub
Animal helloKetty = new Cat();
helloKetty.counterattack();
if(helloKetty instanceof Cat) {
Cat ketty = (Cat)helloKetty;
ketty.eat();
}
}
}
6 内部类(了解)
在某些情况下,我们把一个类放在另外一个类的内部定义,定义在其他类内部的类就称为内部类(嵌套类),包含内部类的类称为外部类(宿主类)
内部类优势:提供了更好的封装,可以把内部类隐藏在外部类之中,不允许其他类访问。有效减少了无意义的源文件的数量
内部类编译完成后会产生.class文件,文件名称是”外部类名称$内部类名称.class”
内部类可以直接访问外部类的私有数据,因为内部类被当成其外部类成员。
但是外部类不能直接访问内部类的实现细节,如内部类成员变量,需要内部类对象.属性(private也可以)的方式。
内部类的访问修饰符、继承、实现接口和普通类相同
6.1成员内部类
public class Outer1 {
private int id = 10;
public void out(){
System.out.println("这是外部类的方法");
}
public class Inner{
public void in(){
// 可以访问外部类的变量和方法
id = 20;
out();
System.out.println("这是内部类的方法");
}
}
public class App {
public static void main(String[] args) {
// 创建内部类对象
Inner inner = new Outer1().new Inner();
inner.in();
}
}
6.2 静态内部类
如果一个内部类使用static声明,则此内部类就称为静态内部类。
package com.glls.inner;
public class Outer2 {
private int id = 10;
private static int a = 20;
public void out(){
System.out.println("这是外部类的方法");
}
public static class Inner2{
public void in(){
// 访问静态变量
a = 30;
System.out.println("这是内部类的方法");
}
}
}
package com.glls.inner;
import com.glls.inner.Outer2.Inner2;
public class App {
public static void main(String[] args) {
// 创建内部类对象
Inner2 inner2 = new Outer2.Inner2();
inner2.in();
Inner2 inner22 = new Inner2();
inner22.in();
}
}
静态内部类特性:
静态内部类属于外部类的类成员,创建时不用创建外部类实例对象
静态内部类可以直接访问宿主类的静态变量,如果要访问宿主类的成员变量,必须通过宿主类的实例对象访问
静态内部类可以包含静态变量、静态方法、static block,实例变量,实例方法,block
6.3 局部内部类
局部内部类也叫区域内嵌类
局部内部类是定义在方法中的类,所以类的作用范围仅限于该方法,由类生成的对象也只能在该方法中使用。
局部内部类不能使用private,protected,public,static中的任何一个修饰符修饰,也不能包含静态成员
局部内部类可以直接访问其方法所在的类的属性。
如果在类中使用其所在方法的形参,则该形参相当于是final的,不可修改
package com.glls.inner;
public class Outer3 {
String name = "王五";
static int age = 10;
public static void show() {
System.out.println("外部类中的show方法");
}
public void test() {
String name = "张三";
double height = 1.8;
//局部内部类不可使用权限修饰符 静态修饰符进行修饰 同局部变量相同
//局部内部类与局部变量使用范围一样 在此方法内部
//局部内部类可以直接访问方法中的属性 重名时使用参数传递完成访问
//局部内部类 可以访问方法外部类中属性和方法
class Inner{
String name = "李四";
public void showInner(String name) {
show();
System.out.println(age);
System.out.println(height);
System.out.println("这是:" + Outer3.this.name);
}
}
//局部内部类 创建对象 要在方法内部 局部内部类的外部声明
Inner inner = new Inner();
inner.showInner(name);
}
}
public class App {
public static void main(String[] args) {
Outer3 outer3 = new Outer3();
outer3.test();
}
}
6.4 匿名内部类(掌握)
如果一个类对象在整个操作中只使用一次的话,就可以使用匿名内部类,顾名思义,即没有名字的内部类
这是Java为了方便编写程序而设计的一个机制,匿名内部类不用声明类名称,只用new直接创建一个对象即可。
匿名内部类是在抽象类或接口的基础上发展起来
基础语法:
new 接口|抽象类() {
实现需要重写的方法
};
匿名内部类编译后生成的.class文件的命名方式是”外部类名称$编号.class”,编号为1,2,3…n,编号对应的是第x个匿名类
public interface Inter1 {
void show();
}
public class Outer4{
public static Inter1 method() {
return new Inter1() {
public void show() {
System.out.println("匿名内部类");
}
};
}
}
public class App {
public static void main(String[] args) {
Outer4.method().show();
}
}
匿名内部类用途:
匿名内部类创建对象赋给父类引用
匿名内部类作为方法实参
附录
简单工厂模式
也叫静态工厂模式
只需要知道要创建哪种类型的产品(例如用int/String/enum类型数据指定),不需要关注创建对象的具体过程,降低模块间的耦合
屏蔽底层产品类,以及具体实现,只需要知道哪个工厂可以创造这个产品(不需要知道iphone车间怎么制造,只需要知道有个厂,比如富士康,可以制造就可以了)
public interface IPay {
void pay();
}
public class Alipay implements IPay {
@Override
public void pay() {
// TODO Auto-generated method stub
System.out.println("支付宝支付");
}
}
public class Wxpay implements IPay {
@Override
public void pay() {
// TODO Auto-generated method stub
System.out.println("微信支付");
}
}
public class Constants {
public static final int PAY_ALI = 1;
public static final int PAY_WX = 2;
public static final int PAY_LIAN = 3;
public static final int PAY_PAYPAL = 4;
}
public class PayFactory {
// 简单工厂模式中,一般提供静态方法,根据不同的类型或者状态的值,决定创建哪种类型的对象
// 1 表示支付宝支付 2表示微信支付
public static IPay createInstance(int state) {
if(state == Constants.PAY_ALI) {
return new Alipay();
} else if (state == Constants.PAY_WX) {
return new Wxpay();
} else {
System.out.println("类型错误");
return null;
}
}
}
public class App {
public static void main(String[] args) {
// TODO Auto-generated method stub
IPay alipay = PayFactory.createInstance(Constants.PAY_ALI);
alipay.pay();
// ctrl+2, l(艾欧)
IPay wxpay = PayFactory.createInstance(Constants.PAY_WX);
wxpay.pay();
}
}