Java 抽象类
1.内部类
1.1 内部类的基本使用
- 内部类概念
- 在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类
- 内部类定义格式
- 格式&举例:
/*
格式:
class 外部类名{
修饰符 class 内部类名{
}
}
*/
class Outer {
public class Inner {
}
}
-
内部类的访问特点:
-
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
-
示例代码:
/*
内部类访问特点:
内部类可以直接访问外部类的成员,包括私有
外部类要访问内部类的成员,必须创建对象
*/
public class Outer {
private int num = 10;
public class Inner {
public void show() {
System.out.println(num);
}
}
public void method() {
Inner i = new Inner();
i.show();
}
}
成员内部类访问当前外部类对象的内容:
public class Outer {
int a = 10;
public class Inner{
int a = 20;
public void method(){
int a = 30;
System.out.println(a); // 30
System.out.println(this.a); // 20
//System.out.println(super.a); //编译报错
//System.out.println(Outer.a); //编译报错
System.out.println(Outer.this.a); // 5
System.out.println(new Outer().a); //10
}
}
int b = 40;
}
public class Test {
public static void main(String[] args) {
Outer o = new Outer();
o.a = 5;
Outer.Inner oi = o.new Inner();
oi.method();
/*
Inner 他是不是属于 Outer的成员。
成员随着 对象存在的。
所以 Inner对象的存在 必须 随着 Outer对象的存在而存在。
开发中 单纯的Outer对象 是不是可以有很多个啊?
我只想找到 Inner对象所在的 Outer对象。
*/
}
}
1.2 成员内部类
-
成员内部类的定义位置
-
- 在类中方法,跟成员变量是一个位置
-
外界创建成员内部类格式
-
- 格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
- 举例:Outer.Inner oi = new Outer().new Inner();
-
私有成员内部类
-
- 将一个类,设计为内部类的目的,大多数都是不想让外界去访问,所以内部类的定义应该私有化,私有化之后,再提供一个可以让外界调用的方法,方法内部创建内部类对象并调用。
-
示例代码:
class Outer {
private int num = 10;
private class Inner {
public void show() {
System.out.println(num);
}
}
public void method() {
Inner i = new Inner();
i.show();
}
}
public class InnerDemo {
public static void main(String[] args) {
//Outer.Inner oi = new Outer().new Inner();
//oi.show();
Outer o = new Outer();
o.method();
}
}
-
静态成员内部类
-
- 静态成员内部类访问格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
- 静态成员内部类中的静态方法:外部类名.内部类名.方法名();
-
示例代码
class Outer {
static class Inner {
public void show(){
System.out.println("inner..show");
}
public static void method(){
System.out.println("inner..method");
}
}
}
public class Test3Innerclass {
/*
静态成员内部类演示
*/
public static void main(String[] args) {
// 外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner oi = new Outer.Inner();
oi.show();
Outer.Inner.method();
}
}
1.3 局部内部类
-
局部内部类定义位置
-
- 局部内部类是在方法中定义的类
-
局部内部类方式方式
-
- 局部内部类,外界是无法直接使用,需要在方法内部创建对象并使用
- 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
-
示例代码
class Outer {
private int num = 10;
public void method() {
int num2 = 20;
class Inner {
public void show() {
System.out.println(num);
System.out.println(num2);
}
}
Inner i = new Inner();
i.show();
}
}
public class OuterDemo {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
- 局部内部类如何把对象扔到外界使用?
//私有成员变量
private int num = 10;
//成员方法
public Face show(){
class Inner implements Face{
public void method(){
System.out.println(num);
}
}
Inner i = new Inner();
i.method();
// 如果你觉得此处多此一举。
// 那么你就想办法 让外界去使用 Inner对象。
// 也就是想办法 把 Inner 对象扔出方法去
// 怎么从方法扔出去呢? 方法的返回值就可以做到。
// 那么怎么定义 方法的返回值类型 就可以把Inner对象扔出去呢??
// 如果要把 Inner 对象扔出去, 返回值类型 定义为 Inner类型就可以了
// 但是 Inner 是局部的东西,不能直接在外面写。
// 这时候我们只能改变策略。
// 返回值类型 是Inner类型的时候 我们返回一个Inner对象, 但是 返回值类型是Inner的父类 或者父接口类型的时候,我们依然也可以返回一个Inner对象
return i;
}
}
interface Face{
void method();
}
public class Test {
public static void main(String[] args) {
//Outer.Inner oi = new Outer().new Inner(); 编译报错
//不可能
// Inner 是局部的。
/*
局部的东西的 生命周期
随着方法的调用而存在 随着方法调用完毕而结束。
*/
/*
Outer o = new Outer();
o.show();
*/
//Outer.Inner
Outer o = new Outer();
Face f = o.show();
// 你接收出来了Inner之后, 想用Inner干嘛
//是不是想用他里面的method方法。
f.method();
}
- 局部内部类访问局部变量, 局部变量前面必须有final修饰, 在jdk1.8的时候,这个final可以省略,但其实并不是真正的省略,而是 前面默认有一个 final ,只是你看不到而已。 这样做的目的是为了延长局部变量的声明周期。
public class Person {
public Digests digestion(){
int a = 10;
a = 20;
class Tract implements Digests{
public void diges(){
System.out.println("肠道消化 java");
//System.out.println(a); //编译报错, 因为a必须是常量才行。
}
}
Tract t = new Tract();
return t;
}
}
1.4 匿名内部类(应用)
-
匿名内部类的前提
-
- 存在一个类或者接口,这里的类可以是具体类也可以是抽象类
-
匿名内部类的格式
-
- 格式:new 类名 ( ) { 重写方法 } new 接口名 ( ) { 重写方法 }
-
举例:
new Inter(){
@Override
public void method(){}
}
-
匿名内部类的本质
-
- 本质:是一个继承了该类或者实现了该接口的子类匿名对象
-
匿名内部类的细节
-
- 匿名内部类可以通过多态的形式接受
Inter i = new Inter(){ @Override public void method(){ }}
- 匿名内部类直接调用方法
interface Inter{
void method();
}
class Test{
public static void main(String[] args){
new Inter(){
@Override
public void method(){
System.out.println("我是匿名内部类");
}
}.method(); // 直接调用方法
}
}
第一个体验就是很方便,因为它把实现接口,重写方法,创建实现类对象以及调用方法的步骤都给完成了。
匿名内部类的理解:将继承/实现,方法重写,创建对象,放在了一步执行
new Inter(){
@Override
public void method(){
System.out.println("我是匿名内部类");
}
}; //这一部分就相当于实现了一个inter接口的,一个实现类对象。
如果说一个接口中存在多个方法,但是想使用匿名内部类的方式去调用这两个方法,可以在前面加上一个父类或者父接口的引用来接一下,这样就能以多态的形式接收过来。通过引用对象来调用里面的多个方法。
- 匿名内部类的应用:匿名内部类一般 会作为方法的实参去传递 ,返回值去返回。
public interface Digests {
public void diges();
}
public class Person {
public Digests digestion(){
/*class Tract implements Digests{
public void diges(){
System.out.println("肠道消化 java");
}
}*/
/*
Tract t = new Tract();
return t;
*/
//return new Tract();
return new Digests() {
@Override
public void diges() {
System.out.println("肠道消化 java");
}
};
}
}
当方法的形式参数是接口或者抽象类时可以将匿名内部类作为实际参数进行传递。
-
匿名内部类的使用场景:
-
- 当一个接口 只有一个抽象方法的时候, 你用匿名内部类是比较合适的。 因为这样的时候 用匿名内部类比较方便 少建java文件,少写class类,
- 但是当一个接口中 有多个抽象方法的时候, 就不如 额外去建一个java文件键一个类 实现接口 好了,作为参数的传递
interface Inter {
void function();
}
class Demo {
public static void main(String[] args){
/*
Inter inte = new Inter(){
public void function(){
System.out.println("function");
}
};
show(inte);
*/
show(new Inter(){
public void function(){
System.out.println("function");
}
});
}
public static void show(Inter in){
in.function();
}
}
1.5 匿名内部类在开发中的使用(应用)
-
匿名内部类在开发中的使用
-
- 当发现某个方法需要,接口或抽象类的子类对象,我们就可以传递一个匿名内部类过去,来简化传统的代码
-
示例代码:
/*
游泳接口
*/
interface Swimming {
void swim();
}
public class TestSwimming {
public static void main(String[] args) {
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("铁汁, 我们去游泳吧");
}
});
}
/**
* 使用接口的方法
*/
public static void goSwimming(Swimming swimming){
/*
Swimming swim = new Swimming() {
@Override
public void swim() {
System.out.println("铁汁, 我们去游泳吧");
}
}
*/
swimming.swim();
}
}
2.Lambda表达式
函数式编程思想:在数学中,函数就是有输入量,输出量的一套计算方案。也就是“拿数据做操作”。面向对象思想强调“必须通过对象的形式来做事情”。函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
理解:对于Lambda表达式,对匿名内部类进行了优化。(其实就是为了优化匿名内部类的写法的)
2.1体验Lambda表达式
- 代码演示
/*
游泳接口
*/
interface Swimming {
void swim();
}
public class TestSwimming {
public static void main(String[] args) {
// 通过匿名内部类实现
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("铁汁, 我们去游泳吧");
}
});
/* 通过Lambda表达式实现
理解: 对于Lambda表达式, 对匿名内部类进行了优化
*/
goSwimming(() -> System.out.println("铁汁, 我们去游泳吧"));
}
/**
* 使用接口的方法
*/
public static void goSwimming(Swimming swimming) {
swimming.swim();
}
}
2.2Lambda表达式的标准格式
-
格式:(形式参数) -> {代码块}
-
- () -> { 重写方法的内容}
-
形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
-
->:由英文中画线和大于符号组成,固定写法。代表指向动作
-
代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
-
- {} 就是要写 你重写方法的 那个方法体。
Lambda表达式的适用前提:
- 有一个接口
- 接口中有且仅有一个抽象方法,否则有的方法有参数 有的方法没有参数,那我的()内要不要写参数呢?
- 必须有上下文的推导, jvm编译过程中 必须要给jvm一个逻辑 根据这个逻辑 可以推导出 这个lambda表达式 是在实现的哪个类。
public class Demo3 {
public static void main(String[] args) {
new Dao(){
@Override
public void findAll() {
System.out.println("abc");
}
};
()->{System.out.println("abc");}; // 编译报错 因为不知道实现的哪个接口。
Dao d1 = ()->{System.out.println("abc");}; // 编译正确 有上下文推导
show(()->{System.out.println("abc");}); // 编译正确。 有上下文推导
Service s = ()->{System.out.println("哈哈哈");}; // 编译报错
}
public static void show(Dao d){
d.findAll();
}
}
interface Dao{
void findAll();
//void show(int a);
}
interface Service{
void show();
void function(int a);
}
2.2 Lambda表达式的优化
- ()内如果只有一个参数 可以省略数据类型和小括号本身。
- ()内多个参数的 只能省略参数的数据类型不能省略小括号本身
- {}内如果只有一句话 可以省略分号 还有return 还有大括号本身
2.4Lambda表达式和匿名内部类的区别
-
所需类型不同
-
- 匿名内部类:接口,抽象类,普通类都乐意使用
- Lambda表达式:只能是接口
-
使用限制不同
-
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
-
实现原理不同
-
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成,不会保存到本地硬盘当中。
2.5Lambda表达式练习1
- 练习描述
无参无返回值抽象方法的练习
-
操作步骤
-
- 定义一个接口(Eatable),里面定义一个抽象方法:void eat();
- 定义一个测试类(EatableDemo),在测试类中提供两个方法
- 一个方法是:useEatable(Eatable e)
- 一个方法是主方法,在主方法中调用useEatable方法
-
示例代码
//接口
public interface Eatable {
void eat();
}
//实现类
public class EatableImpl implements Eatable {
@Override
public void eat() {
System.out.println("一天一苹果,医生远离我");
}
}
//测试类
public class EatableDemo {
public static void main(String[] args) {
//在主方法中调用useEatable方法
Eatable e = new EatableImpl();
useEatable(e);
//匿名内部类
useEatable(new Eatable() {
@Override
public void eat() {
System.out.println("一天一苹果,医生远离我");
}
});
//Lambda表达式
useEatable(() -> {
System.out.println("一天一苹果,医生远离我");
});
}
private static void useEatable(Eatable e) {
e.eat();
}
}
2.6Lambda表达式练习2【应用】
- 练习描述
有参无返回值抽象方法的练习
-
操作步骤
-
- 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);
- 定义一个测试类(FlyableDemo),在测试类中提供两个方法
- 一个方法是:useFlyable(Flyable f)
- 一个方法是主方法,在主方法中调用useFlyable方法
-
示例代码
public interface Flyable {
void fly(String s);
}
public class FlyableDemo {
public static void main(String[] args) {
//在主方法中调用useFlyable方法
//匿名内部类
useFlyable(new Flyable() {
@Override
public void fly(String s) {
System.out.println(s);
System.out.println("飞机自驾游");
}
});
System.out.println("--------");
//Lambda
useFlyable((String s) -> {
System.out.println(s);
System.out.println("飞机自驾游");
});
}
private static void useFlyable(Flyable f) {
f.fly("风和日丽,晴空万里");
}
}
2.7Lambda表达式练习
- 练习描述
有参有返回值抽象方法的练习
-
操作步骤
-
- 定义一个接口(Addable),里面定义一个抽象方法:int add(int x,int y);
- 定义一个测试类(AddableDemo),在测试类中提供两个方法
- 一个方法是:useAddable(Addable a)
- 一个方法是主方法,在主方法中调用useAddable方法
-
示例代码
public interface Addable {
int add(int x,int y);
}
public class AddableDemo {
public static void main(String[] args) {
//在主方法中调用useAddable方法
useAddable((int x,int y) -> {
return x + y;
});
}
private static void useAddable(Addable a) {
int sum = a.add(10, 20);
System.out.println(sum);
}
}
2.8Lambda表达式的省略模式
-
省略的规则
-
- 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,和return关键字
-
代码演示
public interface Addable {
int add(int x, int y);
}
public interface Flyable {
void fly(String s);
}
public class LambdaDemo {
public static void main(String[] args) {
// useAddable((int x,int y) -> {
// return x + y;
// });
//参数的类型可以省略
useAddable((x, y) -> {
return x + y;
});
// useFlyable((String s) -> {
// System.out.println(s);
// });
//如果参数有且仅有一个,那么小括号可以省略
// useFlyable(s -> {
// System.out.println(s);
// });
//如果代码块的语句只有一条,可以省略大括号和分号
useFlyable(s -> System.out.println(s));
//如果代码块的语句只有一条,可以省略大括号和分号,如果有return,return也要省略掉
useAddable((x, y) -> x + y);
}
private static void useFlyable(Flyable f) {
f.fly("风和日丽,晴空万里");
}
private static void useAddable(Addable a) {
int sum = a.add(10, 20);
System.out.println(sum);
}
}
抽象类
1、抽象类的概述
有抽象方法的类 就是抽象类
一个类中如果包含抽象方法,那么这个类就必须要定义为抽象类 abstract
2、抽象类的特点
- 抽象类和抽象方法必须使用 abstract 关键字修饰
//抽象类的定义
public abstract class 类名 {}
//抽象方法的定义
public abstract void eat();
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 抽象类不能创建对象
- 抽象类可以有构造方法
- 抽象类的子类
要么重写抽象类中的所有抽象方法
要么是抽象类
普通的类继承抽象类,则必须全部重写里面的抽象方法 ,不重写就报错。
抽象类继承抽象类 就不是必须要重写了。
3、抽象类的案例(应用)
- 案例需求
定义猫类(Cat)和狗类(Dog)
猫类成员方法:eat(猫吃鱼)drink(喝水…)
狗类成员方法:eat(狗吃肉)drink(喝水…)
-
实现步骤
1. 猫类和狗类中存在共性内容,应向上抽取出一个动物类(Animal)
2. 父类Animal中,无法将 eat 方法具体实现描述清楚,所以定义为抽象方法
3. 抽象方法需要存活在抽象类中,将Animal定义为抽象类
4. 让 Cat 和 Dog 分别继承 Animal,重写eat方法
5. 测试类中创建 Cat 和 Dog 对象,调用方法测试
-
代码实现
- 动物类
public abstract class Animal {
public void drink(){
System.out.println("喝水");
}
public Animal(){
}
public abstract void eat();
}
- 猫类
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
- 狗类
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}