1 抽象类
1.1 概述
-
随着继承层次一个个新的子类的定义,类变得越来越具体,而父类则更一般,更通用。
-
类的设计应该保证父类和子类能够共享特征。
-
这些公共特征应该抽取到一个公共的父类中,而这些方法在父类中又无法给出具体的实现,而是应该交给子类各自去实现。
-
在父类中声明这些方法的时候,就只有方法签名,没有方法体,我们将这些没有方法体的方法称为
抽象方法
。 -
在Java中,将包含抽象方法类称为
抽象类
。
1.2 语法格式
-
抽象类
: -
- 定义:使用abstract关键字修饰的类。
-
- 语法:
权限修饰符 abstract class 类名 {}
权限修饰符 abstract class 类名 extends 父类 {}
-
抽象方法
: -
- 定义:没有方法体的方法。
-
- 语法:
权限修饰符 abstract 返回值类型 方法名(参数列表);
- 示例:
package com.github.demo;
/**
* 抽象类 Animal
*
* @author open1024
* @version 1.0
* @since 2021-09-09 15:44
*/
public abstract class Animal {
/**
* 吃饭的方法
*/
public abstract void eat();
}
package com.github.demo;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 15:46
*/
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃老鼠");
}
}
package com.github.demo;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 15:45
*/
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
package com.github.demo;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 15:48
*/
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 狗吃肉
Cat cat = new Cat();
cat.eat(); // 猫吃老鼠
}
}
1.3 注意事项
- ① 抽闲类不能创建对象,如果创建,编译将无法通过。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
- ② 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。。
理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类的构造方法。
- ③ 抽象类中,不一定包含抽象方法,但是有抽象方法的类一定属于抽象类。
理解:没有包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
- ④ 抽象类的子类,必须重写抽象父类的所有抽象方法;否则,编译无法通过。除非,该子类也是抽象类。
理解:假设不重写所有的抽象方法,则类中可能包含抽象方法,那么在创建对象后,调用抽象的方法,没有意义。
-
⑤ 不能用
abstract
修饰变量、代码块、构造器。 -
⑥ 不能用
abstract
修饰私有方法、静态方法、final修饰的方法和final修饰的类。
1.4 练习
1.4.1 练习1
-
定义一个几何图形父类Graphic。所有几何图形都应该具备一个计算面积的方法。但是不同的几何图形计算面积的方式完全不同。
-
示例:
package com.github.demo2;
/**
* 图形
*
* @author open1024
* @version 1.0
* @since 2021-09-09 16:56
*/
public abstract class Graphic {
/**
* 计算面积
*
* @return double
*/
public abstract double getArea();
}
package com.github.demo2;
/**
* 圆
*
* @author open1024
* @version 1.0
* @since 2021-09-09 16:58
*/
public class Circle extends Graphic {
private double radius;
public Circle() {}
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return this.radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * Math.pow(this.radius, 2);
}
}
package com.github.demo2;
/**
* 矩形
*
* @author open1024
* @version 1.0
* @since 2021-09-09 17:00
*/
public class Rectangle extends Graphic {
private double width;
private double height;
public Rectangle() {}
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double getWidth() {
return this.width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return this.height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public double getArea() {
return this.width * this.height;
}
}
package com.github.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 17:01
*/
public class Test {
public static void main(String[] args) {
Circle circle = new Circle(2);
double area = circle.getArea();
System.out.println("circle:" + area);
Rectangle rectangle = new Rectangle(2, 2);
area = rectangle.getArea();
System.out.println("rectangle:" + area);
}
}
1.4.2 练习2
- ① 声明抽象父类:Person,包含抽象方法:
public abstract void walk();
public abstract void eat();
-
② 声明子类Man,继承Person:
-
- 重写walk():大步流星走路。
-
- 重写eat():狼吞虎咽吃饭。
-
- 新增方法:
public void smoke()
实现为吞云吐雾
- 新增方法:
-
③ 声明子类Woman,继承Person:
-
- 重写walk():婀娜多姿走路。
-
- 重写eat():细嚼慢咽吃饭。
-
- 新增方法:
public void buy()
实现为买买买...
- 新增方法:
-
④ 在测试类中创建子类对象,调用方法测试。
-
示例:
package com.github.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 17:13
*/
public abstract class Person {
/**
* 走路
*/
public abstract void walk();
/**
* 吃饭
*/
public abstract void eat();
}
package com.github.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 17:14
*/
public class Man extends Person {
@Override
public void walk() {
System.out.println("大步流星走路");
}
@Override
public void eat() {
System.out.println("狼吞虎咽吃饭");
}
/**
* 抽烟
*/
public void smoke() {
System.out.println("吞云吐雾");
}
}
package com.github.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 17:15
*/
public class Woman extends Person {
@Override
public void walk() {
System.out.println("婀娜多姿走路");
}
@Override
public void eat() {
System.out.println("细嚼慢咽吃饭");
}
/**
* 购物
*/
public void buy() {
System.out.println("买买买...");
}
}
package com.github.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-09 17:16
*/
public class Test {
public static void main(String[] args) {
Man man = new Man();
man.eat();
man.walk();
man.smoke();
System.out.println("----------------------");
Woman woman = new Woman();
woman.buy();
woman.eat();
woman.walk();
}
}
2 接口
2.1 概述
-
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
-
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有
is a
的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打 印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB接口
连接。 -
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的
is-a
关系,而接口实现则是 "能不能"的has-a
关系。 -
- 例如:我们能不能用USB进行连接,或是否具备USB通信功能,就看我们是否遵循USB接口规范。
-
- 例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品有没有实现Java设计的JDBC规范。
2.2 语法
2.2.1 概述
- 接口的定义,和定义类的语法类似,但是使用的是
interface
关键字。它也会编译成.class
文件,但是一定要明确的是它不是累,而是另外一种引用数据类型。
引用数据类型:数组、类、接口。
2.2.2 接口的语法格式
- 语法:
权限修饰符 interface 接口名 {
// 接口的成员列表:
// ① 静态常量
// ② 抽象方法
// ③ 默认方法
// ④ 静态方法
// ⑤ 私有方法
}
- 示例:
package com.github.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 08:12
*/
public interface Fly {
void fly();
}
package com.github.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 08:13
*/
public class Bird implements Fly {
@Override
public void fly() {
System.out.println("小鸟在飞");
}
}
package com.github.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 08:13
*/
public class SuperMan implements Fly {
@Override
public void fly() {
System.out.println("超人在飞");
}
}
package com.github.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 08:09
*/
public class Test {
public static void main(String[] args) {
Fly bird = new Bird();
bird.fly();
System.out.println("-----------------");
Fly superman = new SuperMan();
superman.fly();
}
}
2.2.3 接口中的成员说明
-
接口定义的是多个类共同的公共行为规范,这些行为规范是和外部交流的通道,这就意味着接口里通常定义一组公共方法。
-
在JDK8之前,接口中只允许出现:
-
- ① 公共的静态常量:其中
public static final
可以省略。
- ① 公共的静态常量:其中
-
- ② 公共的抽象方法:其中
public abstract
可以省略。
- ② 公共的抽象方法:其中
理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现。
-
在JDK8时,接口中允许声明默认方法和静态方法:
-
- ① 公共的默认方法:其中
public
可以省略,但是建议保留,但是default
不能省略。
- ① 公共的默认方法:其中
-
- ② 公共的静态方法:其中
public
可以省略,但是建议保留,但是static
不能省略。
- ② 公共的静态方法:其中
-
在JDK9的时候,接口允许私有方法。
目前而言,接口中除了静态常量、抽象方法、默认方法、静态方法、私有方法外,接口中不能有其他成员(构造器、初始化块),接口中没有成员变量需要初始化。
2.2.4 接口中成员变量的思考
-
问:为什么接口中只能声明公共的静态常量?
-
答:因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范的时候,不能随意的去修改和触碰这些底线边界值,否则就有
危险
。比如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA。 USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA。 -
问:为什么JDB8之后,允许接口中定义静态方法和默认方法?
-
答:
-
- 静态方法:在之前的标准类库设计中,有很多Collection/Collections、Path/Paths这样的成对的接口和类,后面的类中都是静态方法,而这样静态方法都是为了前面的接口服务的,那么在设计这样一对API的时候,还不如将静态方法直接定义到接口中,这样使用和维护更为方便。
-
- 默认方法:
-
-
- ① 如果要在老版本的接口中提供方法,如果添加抽象方法,就会涉及到原来使用这些接口的实现类都需要重写抽象方法,为了保持和旧版本的代码的兼容性,只能允许接口中定义默认方法,比如:JDK8中对Collection、List、Comparator等接口都提供了丰富的默认方法。
-
-
-
- ② 当我们接口中的某个抽象方法,在很多类中的实现代码都是一样的,那么此时就可以将这个抽象方法设计为默认方法更为合适,那么实现类可以选择重写,也可以选择不重写。
-
-
问:为什么JDK9要允许接口中定义私有方法?
-
答:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由相同的代码抽取,而这些相同的代码抽取出来的代码又希望只在接口内部使用,所以增加了私有方法。
2.3 实现接口
2.3.1 实现接口的语法
- 语法:
权限访问修饰符 class implements 接口{
// 重写接口中的抽象方法【必须】,如果实现类是抽象类,可以不重写
// 重写接口中的默认方法【可选】
}
权限访问修饰符 class extends 父类 implements 接口{
// 重写接口中的抽象方法【必须】,如果实现类是抽象类,可以不重写
// 重写接口中的默认方法【可选】
}
注意:
-
① 如果接口的实现类是非抽象类,那么必须重写接口中所有的抽象方法。
-
② 默认方法可以选择重写,也可以选择不重写。重写的时候,在子类中,default单词就不需要写了,就如果重写abstract抽象方法一样。
-
③ 不能重写静态方法。
-
示例:
package com.github.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 08:12
*/
public interface Fly {
void fly();
default void addOil() {
System.out.println("加油");
}
}
package com.github.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 08:13
*/
public class Bird implements Fly {
@Override
public void fly() {
System.out.println("小鸟在飞");
}
}
package com.github.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 08:54
*/
public class Plane implements Fly {
@Override
public void fly() {
System.out.println("飞机在飞");
}
@Override
public void addOil() {
System.out.println("飞机加航空煤油");
}
}
package com.github.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 08:13
*/
public class SuperMan implements Fly {
@Override
public void fly() {
System.out.println("超人在飞");
}
}
package com.github.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 08:09
*/
public class Test {
public static void main(String[] args) {
Bird bird = new Bird();
bird.fly();
System.out.println("-----------------");
SuperMan superman = new SuperMan();
superman.fly();
System.out.println("-----------------");
Plane plane = new Plane();
plane.fly();
plane.addOil();
}
}
2.3.2 如果调用对应的方法?
-
① 对于接口中的静态方法,只能通过
接口名.方法名()
调用。 -
② 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用。
2.3.3 练习
- 示例:
package com.github.demo5;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 09:39
*/
public interface LiveAble {
/**
* 喝水
*/
static void drink() {
System.out.println("喝水");
}
/**
* 呼吸
*/
void breathe();
/**
* 吃饭
*/
void eat();
/**
* 睡觉
*/
default void sleep() {
System.out.println("静止不动");
}
}
package com.github.demo5;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 09:40
*/
public class Animal implements LiveAble {
@Override
public void breathe() {
System.out.println("吸入氧气呼出二氧化碳");
}
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void sleep() {
System.out.println("闭上眼睛睡觉");
}
}
package com.github.demo5;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 09:41
*/
public class Plant implements LiveAble {
@Override
public void breathe() {
System.out.println("吸入二氧化碳呼出氧气");
}
@Override
public void eat() {
System.out.println("吸收营养");
}
}
package com.github.demo5;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 09:42
*/
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
animal.breathe();
animal.eat();
animal.sleep();
System.out.println("-------------------");
Plant plant = new Plant();
plant.breathe();
plant.sleep();
plant.eat();
System.out.println("-------------------");
LiveAble.drink();
}
}
2.4 接口的多实现
-
在之前学过,在继承体系中,一个类只能继承一个接口。但是,对于接口而言,一个类是可以实现多个接口的,这叫做
接口的多实现
。与此同时,一个类能继承一个父类,同时实现多个接口。 -
语法:
权限修饰符 class 实现类 implements 接口1,接口2,接口3,...{
// 重写接口中的抽象方法【必须】,如果实现类是抽象类,可以不重写
// 重写接口中的默认方法【可选】
}
权限访问修饰符 class extends 父类 implements 接口1,接口2,接口3,...{{
// 重写接口中的抽象方法【必须】,如果实现类是抽象类,可以不重写
// 重写接口中的默认方法【可选】
}
注意:接口中多个抽象方法的时候,实现类必须重写所有的抽象方法。如果抽象方法有重名的,只需要重写一次。
- 示例:
package com.github.demo6;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 09:48
*/
public interface A {
void showA();
void show();
}
package com.github.demo6;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 09:48
*/
public interface B {
void showB();
void show();
}
package com.github.demo6;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 09:49
*/
public class C implements A, B {
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
@Override
public void show() {
System.out.println("show");
}
}
package com.github.demo6;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 09:49
*/
public class Test {
public static void main(String[] args) {
C c = new C();
c.show();
c.showA();
c.showB();
}
}
2.5 默认方法冲突问题
2.5.1 亲爹优先原则
-
当一个类,既继承了一个父类,又实现了若干个接口的时候,父类中的成员方法和接口中的默认方法重名,子类就近选择执行父类的成员方法。
-
示例:
package com.github.demo7;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:39
*/
public interface A {
default void method() {
System.out.println("接口A中的method方法");
}
}
package com.github.demo7;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:39
*/
public class B {
public void method() {
System.out.println("B类中的method方法");
}
}
package com.github.demo7;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:40
*/
public class C extends B implements A {
// 不重写method方法
}
package com.github.demo7;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:40
*/
public class D extends B implements A {
@Override
public void method() {
System.out.println("D中的method方法");
}
}
package com.github.demo7;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:41
*/
public class Test {
public static void main(String[] args) {
C c = new C();
c.method(); // B类中的method方法
System.out.println("------------");
D d = new D();
d.method(); // D中的method方法
}
}
2.5.2 必须做出选择
-
当一个类同时实现多个接口,而多个接口中包含方法签名相同的默认方法时,必须进行重写,否则编译报错。在重写的方法中,可以选中使用
接口名.super.方法名
的方法选择保留哪个接口中的默认方法,也可以选择完全自己重写。 -
示例:
package com.github.demo8;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:47
*/
public interface A {
default void method() {
System.out.println("今天晚上陪我吃饭");
}
}
package com.github.demo8;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:48
*/
public interface B {
default void method() {
System.out.println("今天晚上陪我逛街");
}
}
package com.github.demo8;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:48
*/
public class C implements A, B {
@Override
public void method() {
// 选择保留其中一个,通过“接口名.super.方法名"的方法选择保留哪个接口的默认方法。
A.super.method();
}
}
package com.github.demo8;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:49
*/
public class D implements A, B {
@Override
public void method() {
System.out.println("滚,写代码,它不香吗?");
}
}
package com.github.demo8;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:49
*/
public class Test {
public static void main(String[] args) {
C c = new C();
c.method(); // 今天晚上陪我吃饭
System.out.println("----------");
D d = new D();
d.method(); // 滚,写代码,它不香吗?
}
}
2.6 接口的多继承
- 一个接口能继承另一个接口或者多个接口,接口的继承也使用
extends
关键字,子接口继承父接口的方法。
温馨提示:
-
① 子接口重写默认方法的时候,default关键字可以保留。
-
② 子类重写默认方法的时候,default关键字不可以保留。
-
示例:
package com.github.demo9;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:54
*/
public interface A {
void a();
default void methodA() {
System.out.println("A接口的default修饰的methodA方法");
}
}
package com.github.demo9;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:54
*/
public interface B {
void b();
default void methodB() {
System.out.println("A接口的default修饰的methodB方法");
}
}
package com.github.demo9;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:55
*/
public interface C extends A, B {
@Override
default void methodB() {
System.out.println("C接口的default修饰的默认方法");
}
}
package com.github.demo9;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:56
*/
public class D implements C {
@Override
public void a() {
System.out.println("D:a");
}
@Override
public void b() {
System.out.println("D:b");
}
}
package com.github.demo9;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:57
*/
public class E implements A, B, C {
@Override
public void a() {
System.out.println("E: a");
}
@Override
public void b() {
System.out.println("E: b");
}
}
package com.github.demo9;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 10:58
*/
public class Test {
public static void main(String[] args) {
D d = new D();
d.a(); // D:a
d.b(); // D:b
d.methodA(); // A接口的default修饰的methodA方法
d.methodB(); // C接口的default修饰的默认方法
System.out.println("--------------");
E e = new E();
e.a(); // E: a
e.b(); // E: b
e.methodA(); // A接口的default修饰的methodA方法
e.methodB(); // C接口的default修饰的默认方法
}
}
2.7 接口和实现类对象的多态引用
-
实现类实现接口,类似于子类继承父类,因此,接口类型的变量和实现类对象之间,也构成多态引用。通过接口类型的变量调用方法,最终执行的是实现类对象重写的方法。
-
示例:
package com.github.demo10;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 11:09
*/
public interface Fly {
void fly();
}
package com.github.demo10;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 11:09
*/
public class Bird implements Fly {
@Override
public void fly() {
System.out.println("小鸟飞");
}
}
package com.github.demo10;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 11:10
*/
public class Plane implements Fly {
@Override
public void fly() {
System.out.println("飞机在飞");
}
}
package com.github.demo10;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 11:10
*/
public class Test {
public static void main(String[] args) {
Fly bird = new Bird();
bird.fly();// 小鸟飞
System.out.println("----------------");
Fly plane = new Plane();
plane.fly();// 飞机在飞
}
}
2.8 经典接口的介绍
2.8.1 java.lang.Comparable
-
基本数据类型的数据(除了boolean类型以外)比较大小的话,直接使用比较运算符即可;但是,引用数据类型是不能直接使用比较运算符来比较大小的,那么,怎么办呢?
-
Java给所有的引用数据类型的大小比较,提供了一个标准接口,就是
java.lang.Comparable
接口。
package java.lang;
import java.util.*;
public interface Comparable<T> {
public int compareTo(T o);
}
-
那么,如果我们想是的我们某个类的对象可以比较大小,怎么做?
-
- ① 那个类的对象要比较大小,那个类就需要实现
java.lang.Comparable
接口,并重写方法。
- ① 那个类的对象要比较大小,那个类就需要实现
-
-
- 方法体就是如何比较当前对象和指定的另一个对象大小的规则。
-
-
- ② 对象比较大小的时候,通过对象调用compareTo方法,根据方法的返回值决定谁大谁小。
-
-
- this对象(调用compareTo方法的对象)大于指定对象(传入compareTo()的参数对象),返回正整数。
-
-
-
- this对象(调用compareTo方法的对象)小于指定对象(传入compareTo()的参数对象),返回负整数。
-
-
-
- this对象(调用compareTo方法的对象)等于指定对象(传入compareTo()的参数对象),返回零。
-
温馨提示:这种方式称为自然排序或内部比较器排序。
-
自然排序:Comparable接口强行对实现它的每个类的对象进行整体排序,类的 compareTo 方法被称为它的自然比较方法。
-
内部比较器排序:在比较对象所在类的内部完成比较规则的制定。
-
示例:
package com.github.demo11;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 13:14
*/
public class Student implements Comparable<Student> {
private String name;
private int age;
private double score;
public Student() {}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
return Integer.compare(this.age, o.age);
}
@Override
public String toString() {
return "Student{" + "name='" + this.name + '\'' + ", age=" + this.age + ", score=" + this.score + '}';
}
}
package com.github.demo11;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 13:30
*/
public class Test {
public static void main(String[] args) {
Student student = new Student("张三", 35, 99);
Student student2 = new Student("李四", 25, 88);
/*
* 按照年龄进行比较
*
* 如果是0,表示两数相等。
* 如果是正数,那么第一个数大于第二个数。
* 如果是负数,那么第一个数小于第二个数。
*/
int sort = student.compareTo(student2);
if (sort > 0) {
System.out.println("student比student2大");
} else if (sort == 0) {
System.out.println("student和student2一样大");
} else {
System.out.println("student比student2小");
}
}
}
- 示例:冒泡排序
package com.github.demo12;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 14:38
*/
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + '}';
}
@Override
public int compareTo(Person o) {
return Integer.compare(this.age, o.age);
}
}
package com.github.demo12;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 14:40
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person("张三", 25);
Person p2 = new Person("李四", 74);
Person p3 = new Person("王五", 10);
Person p4 = new Person("赵六", 34);
Person p5 = new Person("天气", 59);
Person[] persons = new Person[] {p1, p2, p3, p4, p5};
System.out.println("---------------排序前:---------------");
printArray(persons);
System.out.println("---------------排序后:---------------");
for (int i = 0; i < persons.length -1; i++) {
for (int j = 0; j < persons.length - i - 1; j++) {
if (persons[j].compareTo(persons[j + 1]) > 0) {
Person temp = persons[j];
persons[j] = persons[j + 1];
persons[j + 1] = temp;
}
}
}
printArray(persons);
}
private static void printArray(Person[] persons) {
for (Person person : persons) {
System.out.println(person);
}
}
}
- 示例:自定义数组工具排序类
package com.github.demo13;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 15:02
*/
public class Arrays {
public static void sort(Object[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果arr数组中的元素实现了Comparable接口,就进行排序
// 否则,原样输出
if (arr[j] instanceof Comparable) {
Comparable comparable = (Comparable)arr[j];
if (comparable.compareTo(arr[j + 1]) > 0) {
Object temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
} else {
break;
}
}
}
}
}
package com.github.demo13;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 14:38
*/
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + '}';
}
@Override
public int compareTo(Person o) {
return Integer.compare(this.age, o.age);
}
}
package com.github.demo13;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 14:40
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person("张三", 25);
Person p2 = new Person("李四", 74);
Person p3 = new Person("王五", 10);
Person p4 = new Person("赵六", 34);
Person p5 = new Person("天气", 59);
Person[] persons = new Person[] {p1, p2, p3, p4, p5};
System.out.println("---------------排序前:---------------");
printArray(persons);
System.out.println("---------------排序后:---------------");
Arrays.sort(persons);
printArray(persons);
}
private static void printArray(Person[] persons) {
for (Person person : persons) {
System.out.println(person);
}
}
}
2.8.2 java.util.Comparator
-
思考如下场景:
-
- ① 如果一个类,没有实现
java.lang.Comparable
接口,而这个类不方便修改(比如:我们拿到的是第三方类,只有.class
文件,没有源文件),那么这样的对象要进行比较大小该怎么办?
- ① 如果一个类,没有实现
-
- ② 如果一个类,实现了
java.lang.Comparable
接口,也指定了两个对象比较大小的规则,但是此时我不想按照它预定义的规则进行对象大小的比较,但是我又不能去修改这个类,因为会影响到其他地方的使用,那么这样的对象要进行比较大小该怎么办?
- ② 如果一个类,实现了
-
Java在设计类库之初,已经考虑到这种情况了,所以又增加了一个
java.util.Comparator
接口。
package java.util;
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
-
那么,我们想要比较某个类的两个对象的大小,该怎么办?
-
- ① 编写一个类,称为比较器类型,实现
java.util.Comparator
接口,并重写方法。
- ① 编写一个类,称为比较器类型,实现
-
-
- 方法体就是如何比较当前对象和指定的另一个对象大小的规则。
-
-
- ② 比较大小的时候,通过比较器类型的对象调用compare()方法,将要比较大小的两个对象作为compare方法的实参传入,根据方法的返回值决定谁大谁小。
-
-
- o1对象大于o2,返回正整数。
-
-
-
- o1对象小于o2,返回负整数。
-
-
-
- o1对象等于o2,返回零。
-
温馨提示:这种方式称为自定义排序或外部比较器排序。
- 示例:
package com.github.demo14;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 15:43
*/
public class Person {
private String name;
private int age;
private double salary;
public Person() {}
public Person(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + ", salary=" + this.salary + '}';
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return this.salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
package com.github.demo14;
import java.util.Comparator;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 15:57
*/
public class PersonAgeCompare implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return Integer.compare(o1.getAge(), o2.getAge());
}
}
package com.github.demo14;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 15:45
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person("张三", 56, 5000);
Person p2 = new Person("李四", 20, 9000);
PersonAgeCompare compare = new PersonAgeCompare();
int sort = compare.compare(p1, p2);
if (sort > 0) {
System.out.println(p1.getName() + "的年龄比" + p2.getName() + "大");
} else if (sort < 0) {
System.out.println(p1.getName() + "的年龄比" + p2.getName() + "小");
} else {
System.out.println(p1.getName() + "的年龄和" + p2.getName() + "相等");
}
}
}
- 示例:冒泡排序
package com.github.demo15;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 15:43
*/
public class Person {
private String name;
private int age;
private double salary;
public Person() {}
public Person(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + ", salary=" + this.salary + '}';
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return this.salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
package com.github.demo15;
import java.util.Comparator;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 15:57
*/
public class PersonAgeCompare implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return Integer.compare(o1.getAge(), o2.getAge());
}
}
package com.github.demo15;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 15:45
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person("张三", 78, 100);
Person p2 = new Person("李四", 16, 9000);
Person p3 = new Person("王五", 39, 500);
Person p4 = new Person("赵六", 20, 3000);
Person[] persons = new Person[] {p1, p2, p3, p4};
System.out.println("----------------排序前----------------");
printArray(persons);
System.out.println("----------------排序后----------------");
PersonAgeCompare compare = new PersonAgeCompare();
for (int i = 0; i < persons.length - 1; i++) {
for (int j = 0; j < persons.length - i - 1; j++) {
if (compare.compare(persons[j], persons[j + 1]) > 0) {
Person temp = persons[j];
persons[j] = persons[j + 1];
persons[j + 1] = temp;
}
}
}
printArray(persons);
}
private static void printArray(Person[] persons) {
for (Person person : persons) {
System.out.println(person);
}
}
}
- 示例:自定义数组工具排序类
package com.github.demo16;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 15:43
*/
public class Person {
private String name;
private int age;
private double salary;
public Person() {}
public Person(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Person{" + "name='" + this.name + '\'' + ", age=" + this.age + ", salary=" + this.salary + '}';
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return this.salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
package com.github.demo16;
import java.util.Comparator;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 16:08
*/
public class Arrays {
public static void sort(Object[] arr, Comparator comparator) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (comparator.compare(arr[j], arr[j + 1]) > 0) {
Object temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
package com.github.demo16;
import com.github.demo15.Person;
import com.github.demo15.PersonAgeCompare;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 15:45
*/
public class Test {
public static void main(String[] args) {
Person p1 = new Person("张三", 78, 100);
Person p2 = new Person("李四", 16, 9000);
Person p3 = new Person("王五", 39, 500);
Person p4 = new Person("赵六", 20, 3000);
Person[] persons = new Person[] {p1, p2, p3, p4};
System.out.println("----------------排序前----------------");
printArray(persons);
System.out.println("----------------排序后----------------");
PersonAgeCompare compare = new PersonAgeCompare();
Arrays.sort(persons, compare);
printArray(persons);
}
private static void printArray(com.github.demo15.Person[] persons) {
for (Person person : persons) {
System.out.println(person);
}
}
}
2.8.3 java.lang.Cloneable
- 在Object类中,有如下的方法:
protected native Object clone() throws CloneNotSupportedException;
-
所有的类都可以重写这个方法,它是获取一个对象的克隆体对象,就是造一个和当前对象属性一模一样的对象,地址是不同的。
-
要求,重写这个方法的类需要实现
java.lang.Cloneable
接口,否则会报CloneNotSupportedException
的异常。 -
示例:
package com.github.demo17;
import java.util.Objects;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 16:33
*/
public class Teacher implements Cloneable {
private int id;
private String name;
public Teacher() {}
public Teacher(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
Teacher teacher = (Teacher)o;
return this.id == teacher.id && Objects.equals(this.name, teacher.name);
}
@Override
public int hashCode() {
return Objects.hash(this.id, this.name);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Teacher{" + "id=" + this.id + ", name='" + this.name + '\'' + '}';
}
}
package com.github.demo17;
/**
* @author open1024
* @version 1.0
* @since 2021-09-10 16:33
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher(1, "张三");
Object clone = teacher.clone();
System.out.println(teacher == clone); // false
System.out.println(teacher.equals(clone)); // true
}
}
3 包装类
3.1 概述
-
Java提供了两种数据类型:基本数据类型和引用数据类型。
-
使用基本数据类型在于效率,然而要使用只针对对象设计的API或新特性(如:泛型),那么基本数据类型的数据就需要用包装类来包装了。
-
Java针对基本数据类型定义了相应的引用类型,这些对应的引用类型称为包装类。
-
有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。
序号 | 基本数据类型 | 包装类(java.lang包) |
---|---|---|
1 | byte | Byte |
2 | short | Short |
3 | int | Integer |
4 | long | Long |
5 | float | Float |
6 | double | Double |
7 | char | Character |
8 | boolean | Boolean |
9 | void | Void |
温馨提示:Byte、Short、Integer、Long、Float、Double的父类是Number。
- 示例:
package com.wrapper.demo1;
/**
* @author open1024
* @version 1.0
* @since 2021-09-13 11:05
*/
public class Test {
public static void main(String[] args) {
System.out.println("int数据类型的最小值 = " + Integer.MIN_VALUE); // -2147483648
System.out.println("int数据类型的足最大值 = " + Integer.MAX_VALUE); // 2147483647
// 将一个十进制的数转换为十六进制
String s = Integer.toHexString(11);
System.out.println("s = " + s); // b
// 将一个十进制的数转换为八进制
String s1 = Integer.toOctalString(11);
System.out.println("s1 = " + s1); // 13
// 将一个十进制的数转换为二进制
String s2 = Integer.toBinaryString(11);
System.out.println("s2 = " + s2); // 1011
}
}
3.2 装箱和拆箱
-
装箱:
-
- 定义:将基本数据类型转换为包装类对象。
-
- 目的:为了使用专门为对象设计的API和特性。
-
拆箱:
-
- 定义:将包装类对象转换为基本数据类型。
-
- 目的:一般是因为需要运算,Java中的大多数的运算符都是为基本数据类型而设计的,比如:比较、算术等。
-
在JDK 5之后,可以自动进行装箱和拆箱,但是需要注意的是:
只能和自己对应的类型之间才能实现自动装箱和拆箱
。 -
示例:装箱
package com.wrapper.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-13 11:12
*/
public class Test {
public static void main(String[] args) {
int num = 10;
// 通过构造函数将基本数据类型转换为包装类对象
Integer integer = new Integer(num);
System.out.println("integer = " + integer);
// 通过Integer.valueOf()方法将基本数据类型转换为包装类对象
Integer integer1 = Integer.valueOf(num);
System.out.println("integer1 = " + integer1);
}
}
- 示例:拆箱
package com.wrapper.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-13 11:14
*/
public class Test2 {
public static void main(String[] args) {
Integer integer = new Integer(10);
// 通过Integer对象的intValue方法,将包装类对象转换为基本数据类型
int i = integer.intValue();
System.out.println("i = " + i);
}
}
- 示例:自动装箱
package com.wrapper.demo2;
/**
* 自动装箱:直接将基本类型的数据赋值给包装类类型
*
* @author open1024
* @version 1.0
* @since 2021-09-13 11:21
*/
public class Test3 {
public static void main(String[] args) {
// 自动装箱
Integer num = 10;
System.out.println("num = " + num);
}
}
- 示例:自动拆箱
package com.wrapper.demo2;
/**
* 自动拆箱:将包装类对象直接赋值给基本数据类型
*
* @author open1024
* @version 1.0
* @since 2021-09-13 11:22
*/
public class Test4 {
public static void main(String[] args) {
Integer num = 10;
int i = num;
System.out.println("i = " + i);
}
}
3.3 基本数据类型、包装类和String间的相互转换
-
基本数据类型转换为字符串:
-
- ① 使用
+
拼接""
:
- ① 使用
String str = 5 + "";
-
- ② 使用String的重载方法
valueOf()
静态方法:
- ② 使用String的重载方法
String str = String.valueOf(5);
-
字符串转换为基本数据类型:
-
- ① 通过包装类(除了Character类)的构造器:
int i = new Integer("12");
-
- ② 通过包装类(除了Character类)的
parsetXxx(String s)
的静态方法:
- ② 通过包装类(除了Character类)的
int i = Integer.parsetInt("12");
-
- ③ 通过包装类除了Character类)的
valueOf(String s)
的静态方法:
- ③ 通过包装类除了Character类)的
int i = Integer.valueOf("12");
注意:如果字符串参数的内容无法正确的转换为对应的基本数据类型,则会抛出java.lang.NumberFormatException
异常。
- 示例:
package com.wrapper.demo3;
/**
* 字符串转换为基本数据类型
*
* @author open1024
* @version 1.0
* @since 2021-09-13 13:21
*/
public class Test {
public static void main(String[] args) {
String str = "12";
int num = Integer.parseInt(str);
System.out.println("num = " + num);
num = new Integer(str);
System.out.println("num = " + num);
num = Integer.valueOf(str);
System.out.println("num = " + num);
}
}
3.4 包装类对象的缓存问题
包装类 | 缓存对象 |
---|---|
Byte | -128~127 |
Short | -128~127 |
Integer | -128~127 |
Long | -128~127 |
Float | 没有 |
Double | 没有 |
Character | 0~127 |
Boolean | true和false |
- 示例:
package com.wrapper.demo4;
/**
* @author open1024
* @version 1.0
* @since 2021-09-13 13:56
*/
public class Test {
public static void main(String[] args) {
Integer i1 = 100; // Integer.valueOf(100)
Integer i2 = 100;
System.out.println(i1 == i2); // true
i1 = new Integer(100);
i2 = new Integer(100);
System.out.println(i1 == i2); // false
i1 = 400;
i2 = 400;
System.out.println(i1 == i2); // false
Double d1 = 1.0;
Double d2 = 1.0;
System.out.println(d1 == d2);// false 比较地址,没有缓存对象,每一个都是新new的
}
}
4 内部类
4.1 概述
-
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
-
在Java中,允许一个类A的定义位于另一个类B的内部,类A就被称为内部类,而类B就被称为外部类。
-
因为内部类在外部类的里面,因此可以直接访问外部类的私有成员。
4.2 内部类的分类
-
根据内部类声明的位置不同,我们可以将内部类分为如下两类:
-
- ① 成员内部类。
-
-
- 静态成员内部类。
-
-
-
- 非静态成员内部类。
-
-
- ② 局部内部类。
-
-
- 有名字的局部内部类。
-
-
-
- 匿名内部类。
-
-
成员内部类作为类的成员角色:
-
- 外部类只能通过public和default(缺省)修饰,而内部类还可以声明为private和protected。
-
- 可以调用外部类的结构。
-
- 内部类可以声明为static的,那么此时不能再调用外部类的非static的成员。
-
成员内部类作为类的角色:
-
- 可以在内部定义属性、方法、构造器等结构。
-
- 可以声明为
abstract
类,因此可以被其它的内部类继承。
- 可以声明为
-
- 可以声明为
final
的。
- 可以声明为
-
- 编译以后生成
OuterClass$InnerClass.class
类似的字节码文件。
- 编译以后生成
注意:
-
① 非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类才可以声明static成员。
-
② 外部类访问成员内部类的成员,需要通过
内部类.成员
或内部类对象.成员
的方式。 -
③ 成员内部类可以直接使用外部类的所有成员,包括私有数据。
-
④ 当想要在外部类的静态成员中使用内部类的时候,可以考虑将内部类声明为
static
的。
4.3 静态内部类
- 语法:
[权限修饰符2种] class 外部类名{
[权限修饰符4种] static [final] class 内部类名 {
...
}
}
-
特点:
-
- ① 和其他类一样,它只是定义在外部类中的另一个完整的类结构。
-
-
- 可以继承自己想要继承的父类,实现自己想要实现的接口,和外部类的父类以及外部类的父接口无关。
-
-
-
- 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员。
-
-
-
- 可以使用
abstract
修饰,因此它也可以被其他类继承。
- 可以使用
-
-
-
- 可以使用
final
修饰,表示不能被继承。
- 可以使用
-
-
-
- 编译后有自己的独立的字节码文件,只不过在内部类前面冠以
外部类名和$符号
。
- 编译后有自己的独立的字节码文件,只不过在内部类前面冠以
-
-
- ② 和外部类不同的是,它允许四种权限修饰符:public、protected、缺省、private;而外部类只允许public和缺省。
-
- ③ 静态内部类可以访问外部类的资源:
-
-
- 静态的属性。
-
-
-
- 静态的方法。
-
-
- ④ 外部类使用内部类的资源:
-
-
- 如果是静态资源,可以直接通过
内部类.资源名
。
- 如果是静态资源,可以直接通过
-
-
-
- 如果是非静态资源,那么需要通过
内部类的对象.资源名
。
- 如果是非静态资源,那么需要通过
-
-
- ⑤ 如果在内部类中有变量和外部类的成员变量相同,可以使用
外部类.变量名
进行区别。
- ⑤ 如果在内部类中有变量和外部类的成员变量相同,可以使用
其实严格的讲(在James Gosling等人编著的《The Java Language Specification》)静态内部类不是内部类,而是类似于C++的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已。所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的(即public static)。例如:Map.Entry。
- 示例:
package top.open1024.inner.demo1;
/**
* @author open1024
* @version 1.0
* @since 2021-09-13 14:25
*/
public class Outer { // 外部类
private static final int age = 66;
int num = 10;
public static void outerMethod2() {
System.out.println("outerMethod2");
}
public void outerMethod1() {
System.out.println(Inner.a);
System.out.println(new Inner().num);
}
static class Inner { // 内部类
static int a = 100;
static int age = 100;
int num = 100;
public void method() {
System.out.println(age);
System.out.println(Outer.age);
outerMethod2();
}
}
}
package top.open1024.inner.demo1;
/**
* @author open1024
* @version 1.0
* @since 2021-09-13 16:16
*/
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
System.out.println("outer.num = " + outer.num);
Outer.Inner inner = new Outer.Inner();
System.out.println("inner.num = " + inner.num);
inner.method();
}
}
4.4 非静态成员内部类
- 语法:
[权限修饰符2种] class 外部类名{
[权限修饰符4种] [final] class 内部类名 {
...
}
}
-
特点:
-
- ① 和其他类一样,它只是定义在外部类中的另一个完整的类结构。
-
-
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关。
-
-
-
- 可以在非静态内部类中声明属性、方法、构造器等结构,但是
不允许声明静态成员
,但是可以继承
父类的静态成员,而且可以声明静态常量
。
- 可以在非静态内部类中声明属性、方法、构造器等结构,但是
-
-
-
- 可以使用
abstract
修饰,因此它也可以被其他类继承。
- 可以使用
-
-
-
- 可以使用
final
修饰,表示不能被继承。
- 可以使用
-
-
-
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以
外部类名和$符号
。
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以
-
-
- ② 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private。
-
-
- 外部类只允许public或缺省的。
-
-
- ③ 非静态内部类可以直接使用外部类的所有资源。
-
- ④ 外部类使用内部的资源:
-
-
- 首选创建非静态内部类的对象,然后才能使用。
-
-
-
- 如果是内部类中的静态常量,可以直接使用。
-
-
- ⑤ 如果在内部类中有变量和外部类的成员变量相同,可以使用
外部类.this.变量名
进行区别。
- ⑤ 如果在内部类中有变量和外部类的成员变量相同,可以使用
-
示例:
package top.open1024.inner.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-13 16:24
*/
public class Outer {
static int age = 20;
int num = 30;
public static void outerMethod2() {
System.out.println("outerMethod2");
}
public void outerMethod1() {
System.out.println("outerMethod1");
System.out.println(Inner.a);
Inner inner = new Inner();
System.out.println(inner.name);
inner.innerMethod();
}
class Inner {
static final int a = 10;
int num = 100;
String name = "张三";
public void innerMethod() {
System.out.println(age);
System.out.println(Outer.this.num);
System.out.println(this.num);
outerMethod2();
}
}
}
package top.open1024.inner.demo2;
/**
* @author open1024
* @version 1.0
* @since 2021-09-13 17:08
*/
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
System.out.println("outer.num = " + outer.num);
Outer.Inner inner = new Outer().new Inner();
inner.innerMethod();
System.out.println(inner.num);
}
}
4.5 局部内部类(极其不重要)
- 语法:
[权限修饰符] class 外部类 {
[权限修饰符] 返回值类型 方法名(形参列表){
[abstract|final] class 内部类 {
...
}
}
}
-
特点:
-
- ① 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构。
-
-
- 可以继承自己想要继承的父类,实现自己想要实现的父接口,和外部类的父类和父接口无关。
-
-
-
- 可以在局部内部类中声明属性、方法、构造器等结构,但是
不包括静态成员,除非从父类继承而来的静态常量
。
- 可以在局部内部类中声明属性、方法、构造器等结构,但是
-
-
-
- 可以用
abstract
修饰,因为它可以被同一个方法在它后面的其他内部类继承。
- 可以用
-
-
-
- 可以用
final
修饰,表示不能被继承。
- 可以用
-
-
-
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以
外部类名、$符号、编号
。
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以
-
-
- ② 局部内部类只能被
缺省
权限访问修饰符修饰。
- ② 局部内部类只能被
-
- ③ 局部内部类如同局部变量一样,有作用域。
-
- ④ 局部内部类中是否能访问外部类的静态还是非静态的成员,取决于所在的方法。
-
- ⑤ 局部内部类中不能存在静态属性,但是可以存在静态的常量。
-
- ⑥ 在方法内创建内部类对象,然后通过内部类对象调用内部类中的成员。
-
- ⑦ 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量(JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final)。
-
示例:
package top.open1024.inner.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-13 19:56
*/
public class Outer {
static String name = "张三";
int age = 30;
public static void outerMethod() {
class Inner2 {
public void innerMethod() {
System.out.println(name);
}
}
}
public void method() {
// 局部变量
int number = 10;
// 局部内部类
class Inner {
public void innerMethod() {
System.out.println(Outer.this.age);
System.out.println(name);
System.out.println(number);
}
}
Inner inner = new Inner();
inner.innerMethod();
}
}
package top.open1024.inner.demo3;
/**
* @author open1024
* @version 1.0
* @since 2021-09-13 20:23
*/
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
评论区