目录
内部类的基本概念
当一个类的定义出现在另一个类的类体中时,那么这个类叫做内部类(Inner),而这个内部类所在的类叫做外部类(Outer)。
类中的内容:成员变量、成员方法、构造方法、静态成员、构造块和静态代码块、内部类。
实际作用
当一个类存在的价值仅仅是为某一个类单独服务时,那么就可以将这个类定义为所服务类中的内部类,这样可以隐藏该类的实现细节并且可以方便访问外部类的私有成员而不再需要提供公有的get、set方法。
内部类的分类
普通内部类---直接将一个类的定义放在另一个类的类体中。
静态内部类---使用static关键字修饰的内部类,隶属于类层级。
局部内部类---直接将一个类的定义放在方法体的内部时。
匿名内部类---就是指没有名字的内部类。
普通(成员)内部类的格式
访问修饰符 class 外部类的类名 {
访问修饰符 class 内部类的类名 {
内部类的类体;
}
}
普通内部类的使用方式
普通内部类和普通类一样可以自定义成员变量、成员方法以及构造方法等。
普通内部类和普通类一样可以使用final或者abstract关键字修饰。
普通内部类还可以使用private或者protrctred关键字修饰。
普通内部类需要使用外部类对象来创建对象。
如果内部类访问外部类中与本类内部同名的成员变量或方法时,需要使用this关键字。
/**
* 编程实现普通内部类的定义和使用
*/
public class NormalOuter {
private int cnt = 1;
//定义普通的内部类,隶属于外部类的成员,并且是对象层级
public class NormalInner {
private int ia = 2;
private int cnt = 3;
public NormalInner() {
System.out.println("普通内部类的构造方法执行到了!!");
}
public void show() {
System.out.println("外部类中变量cnt的数值为" + cnt);
System.out.println("ia = " + ia);
}
public void show2(int cnt) {
System.out.println("形参变量cnt = " + cnt);//局部变量优先原则
System.out.println("内部类中cnt = " + this.cnt);//3
System.out.println("外部类中cnt = " + NormalOuter.this.cnt);//1
}
}
}
public class NormalOuterTest {
public static void main(String[] args) {
//1.声明一个NormalOuter类的引用指向NormalOuter的对象
NormalOuter no = new NormalOuter();
//声明NormalOuter类中内部类的引用指向内部类的对象
//调用内部类中的show方法
NormalOuter.NormalInner ni =no. new NormalInner();
ni.show();
ni.show2(4);
}
}
运行结果:
普通内部类的构造方法执行到了!!
外部类中变量cnt的数值为3
ia = 2
形参变量cnt = 4
内部类中cnt = 3
外部类中cnt = 1
静态内部类的格式
访问修饰符 class 外部类的类名 {
访问修饰符 static class 内部类的类名 {
内部类的类体;
}
}
静态内部类的使用方式
静态内部类不能直接访问外部类的非静态成员。
静态内部类可以直接创建对象。
如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用类名.的方式访问。
/**
* 实现静态内部类的定义和使用
*/
public class StaticOuter {
private int cnt = 1;//隶属于对象层级
private static int snt = 2;//隶属于类层级
/**
* 定义静态内部类
*/
public static class StaticInner {
private int ia = 3;
private static int snt = 4;
public StaticInner () {
System.out.println("静态内部类的构造方法");
}
public void show() {
System.out.println("ia = " + ia);//3
System.out.println("外部类中的snt = " + snt);//2
//System.out.println("外部类中的cnt = " + cnt); 错误:静态上下文中,不能访问非静态的成员,因为此时可能还没有创建对象
}
public void show2(int snt) {
System.out.println("snt = " + snt);//5
System.out.println("内部类中的成员snt = " + StaticInner.snt);//4
System.out.println("外部类中的成员snt = " + StaticOuter.snt);//2
}
}
}
public class StaticOuterTest {
public static void main(String[] args) {
//1.声明StaticInner类型的引用指向该类型的对象
StaticOuter.StaticInner si = new StaticOuter.StaticInner();
//2.调用show方法进行测试
si.show();
si.show2(5);
}
}
局部(方法)内部类的格式
访问修饰符 class 外部类的类名{
访问修饰符 返回值类型 成员方法名 (形参列表) {
class 内部类的类名 {
内部类的类体;
}
}
}
局部内部类的使用方式
局部内部类只能在该方法的内部可以使用。
局部内部类可以在方法体的内部直接创建对象。
局部内部类不能使用访问控制符和static关键字修饰符。
局部内部类可以使用外部方法的局部变量,但必须是final的,由局部内部类和局部变量的声明周期不同所致。
/**
* 编程实现局部内部类的定义和使用
*/
public class AreaOuter {
private int cnt = 1;
public void show() {
//定义一个局部变量进行测试,从java8开始默认理解为final关键字修饰
//虽然可以省略final关键字,但建议加上
int ic = 4;
//定义局部内部类,只在当前方法体内部好使
class AreaInner {
private int ia = 2;
public AreaInner() {
System.out.println("局部内部类的构造方法");
}
public void test() {
System.out.println("ia = " + ia);//2
System.out.println("cnt = " + cnt);//1
//ic = 5;错误:
System.out.println("ic = " + ic);//4
}
}
//声明局部内部类的引用指向局部内部类的对象
AreaInner ai = new AreaInner();
ai.test();
}
}
public class AreaOuterTest {
public static void main(String[] args) {
//1.声明外部类类型的引用指向外部类的对象
AreaOuter ao = new AreaOuter();
//2.通过show方法的调用实现局部内容类的调用和使用
ao.show();
}
}
回调(diao)模式的概念
回调模式是指——如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象;而该方法在运行时则会调用到参数对象中所实现的方法(接口中定义的).
public interface AnonymousInterface {
//自定义抽象方法
public abstract void show();
}
public class AnonymousInterfaceImpl implements AnonymousInterface{
@Override
public void show() {
System.out.println("这是接口的实现类!");
}
}
public class AnonymousInterfaceTest {
//假设已有下面的方法
//AnonymousInterface ai = new AnonymousInterfaceImpl
//接口类型的引用指向实现类型的对象,形成了多态
public static void test(AnonymousInterface ai) {
//编译阶段调用父类版本,运行调用实现类重写的版本
ai.show();
}
public static void main(String[] args) {
//AnonymousInterface.test(new AnonymousInterface());错误:接口不能实例化
AnonymousInterfaceTest.test(new AnonymousInterfaceImpl());
}
}
当接口/类类型的引用作为方法的形参时,实参的传递方式有两种;
自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递;
使用上述匿名内部类的语法格式得到接口/类类型的引用即可。
匿名内部类的使用
public class AnonymousInterfaceTest {
//假设已有下面的方法
//AnonymousInterface ai = new AnonymousInterfaceImpl
//接口类型的引用指向实现类型的对象,形成了多态
public static void test(AnonymousInterface ai) {
//编译阶段调用父类版本,运行调用实现类重写的版本
ai.show();
}
public static void main(String[] args) {
//AnonymousInterface.test(new AnonymousInterface());错误:接口不能实例化
AnonymousInterfaceTest.test(new AnonymousInterfaceImpl());
System.out.println("------------------------");
//使用匿名内部类的语法格式来得到接口类型的引用,格式为:接口/父类类型 引用变量名 = new 接口/父类类型() {方法的重写};
//AnonymousInterface ait = new AnonymousInterface();
AnonymousInterface ait = new AnonymousInterface() {
@Override
public void show() {
System.out.println("匿名内部类就是这样");
}
};
AnonymousInterfaceTest.test(ait);//匿名内部类就是这样
//从java8开始提出新特性lamda表达式可以简化上述代码,格式为:(参数列表) -> {方法体}
AnonymousInterface ait2 = () -> System.out.println("lamda表达式原来是如此简单!");
AnonymousInterfaceTest.test(ait2);//lamda表达式原来是如此简单!
}
}
枚举的基本概念
能一一列举出来的——季节:春夏秋冬
枚举的定义
使用public static final表示的常量描述较为繁琐,使用enum关键字来定义枚举类型取代常量,枚举类型是从Java5开始增加的一种引用数据类型。
枚举值就是当前类的类型,也就是指向本类的对象,默认使用public static final关键字共同修饰,因此采用枚举类型.的方式调用。
枚举类可以自定义构造方法,但构造方法的修饰符必须是private,默认也是私有的。
Enum类的概念和方法
所有的枚举类都继承自java.lang.Enum类,常用的方法如下:
static T[] valuse() | 返回当前枚举类中的所有对象 |
String toString() | 返回当前枚举类对象的名称 |
int ordinal() | 获取枚举对象在枚举类中的索引位置 |
static T valueOf(String str) | 将参数指定的字符串名转为当前枚举类的对象 |
int compareTo(E o) | 比较两个枚举对象在定义时的顺序 |
/**
* 编程实现所有方向的枚举:向上向下向左向右
*/
public class Direction {
private final String desc;//用于描述方向字符串的成员变量 final修饰必须初始化不能改变 1.直接初始化2.通过构造块3.通过构造方法体
//2.声明本类类型的引用指向本类类型的对象
public static final Direction UP = new Direction("向上");
public static final Direction DOWN = new Direction("向下");
public static final Direction LEFT = new Direction("向左");
public static final Direction RIGHT = new Direction("向右");
//通过构造方法实现成员变量的初始化,更加灵活
//1.私有化构造方法 只能在该类内部使用
private Direction(String desc) {
this.desc = desc;
}
//通过公有的get方法可以在本类的外部访问该类成员变量的数值
public String getDesc() {
return desc;
}
}
public class DirectionTest {
public static void main(String[] args) {
//1.什么direction类型的引用指向该类型的对象并打印特征
/*Direction d1 = new Direction("向上");
System.out.println("获取的字符串是:" + d1.getDesc());//获取的字符串是:向上
Direction d2 = new Direction("向下");
System.out.println("获取的字符串是:" + d2.getDesc());//获取的字符串是:向下
Direction d3 = new Direction("向左");
System.out.println("获取的字符串是:" + d3.getDesc());//获取的字符串是:向左
Direction d4 = new Direction("向右");
System.out.println("获取的字符串是:" + d4.getDesc());//获取的字符串是:向右
System.out.println("————————————————————————————————————————————————");
Direction d5 = new Direction("向前");
System.out.println("获取的字符串是:" + d5.getDesc());//获取的字符串是:向前*/
Direction d1 = Direction.UP;
System.out.println("获取到的方法是" + d1.getDesc());//获取到的方法是向上
System.out.println("---------------------------");
//使用一下java5开始的枚举类型
DirectionEnum de = DirectionEnum.DOWN;
System.out.println("获取到的方向是:" + de.getDesc());//获取到的方向是:向下
}
}
public class DirectionUseTest {
//自定义静态方法实现根据参数指定的字符串内容来打印具体的方向信息
public static void test1(String str) {
switch (str) {
case "向上":
System.out.println("向上"); break;
case "向下":
System.out.println("向下"); break;
case "向左":
System.out.println("向左"); break;
case "向右":
System.out.println("向右"); break;
default:
System.out.println("没有这样的方法");
}
}
public static void test2(DirectionEnum de) {
switch (de) {
case UP:
System.out.println("向上"); break;
case DOWN:
System.out.println("向下"); break;
case LEFT:
System.out.println("向左"); break;
case RIGHT:
System.out.println("向右"); break;
default:
System.out.println("没有这样的方法");
}
}
public static void main(String[] args) {
DirectionUseTest.test1(Direction.UP.getDesc());//向上
DirectionUseTest.test1("今天是个好日子");//没有这样的方法
System.out.println("---------------");//向下
DirectionUseTest.test2(DirectionEnum.DOWN);
}
}
public interface DirectionInterface {
public abstract void show();
}
/**
* 编程实现所有方向的枚举,枚举类型要求所有枚举值必须放在枚举类型的最前面
*/
public enum DirectionEnum implements DirectionInterface {
//匿名内部类的语法格式,接口/父类类型 引用变量名 = new 接口/父类类型() {方法的重写}
UP("向上"){
@Override
public void show() {
System.out.println("向下移动一下!");
}
},DOWN("向下"){
@Override
public void show() {
System.out.println("向下移动一下!");
}
},LEFT("向左") {
@Override
public void show() {
System.out.println("向左移动一下!");
}
},RIGHT("向右") {
@Override
public void show() {
System.out.println("向右移动一下!");
}
};
private final String desc;//用于描述方向字符串的成员变量 final修饰必须初始化不能改变 1.直接初始化2.通过构造块3.通过构造方法体
//通过构造方法实现成员变量的初始化,更加灵活
//1.私有化构造方法 只能在该类内部使用
private DirectionEnum(String desc) {
this.desc = desc;
}
//通过公有的get方法可以在本类的外部访问该类成员变量的数值
public String getDesc() {
return desc;
}
/*整个枚举类型只重写一次,所有对象调用一个
@Override
public void show() {
System.out.println("现在可以实现接口中抽象方法的重写了!");
}*/
}
public class DirectionEnumTest {
public static void main(String[] args) {
//1.获取DerectionEnum类型中的所有对象
DirectionEnum[] arr = DirectionEnum.values();
//2.打印每个枚举对象在枚举类型中的名称和索引位置
for (int i = 0; i < arr.length; i++) {
System.out.println("获取到的枚举对象名称是:" + arr[i].toString());
System.out.println("获取到的枚举对象对应的索引位置是:" +arr[i].ordinal());
}
//3.根据参数指定的字符串得到枚举类型的对象,也就是将字符串转换为对象
//DirectionEnum de =DirectionEnum.valueOf("向下");//编译ok,运行发生IllegalArgumentException非法参数异常
DirectionEnum de =DirectionEnum.valueOf("DOWN");
//System.out.println("转换出来的枚举对象名称是:" + de.toString());//转换出来的枚举对象名称是:DOWN
System.out.println("转换出来的枚举对象名称是:" + de);//当打印引用变量时,会自动调用toString.方法
//使用获取到的枚举对象与枚举类中已有的对象比较先后顺序
System.out.println("————————————————————————————————");
for(int i = 0; i < arr.length ;i++) {
//当调用对象在参数对象之后时,获取到的比较结果为 正数
//当调用对象和参数对象位置相同时,获取到的比较结果为 零
//当调用对象在参数对象之前时,获取到的比较结果为 负数
System.out.println("调用对象与数组中对象比较的先后顺序结果是:" + de.compareTo(arr[i]));
}
//5.使用数组中每个DirectionEnum对象去调用show方法测试
for (int i = 0; i < arr.length; i++) {
arr[i].show();
}
}
}
/*
获取到的枚举对象名称是:UP
获取到的枚举对象对应的索引位置是:0
获取到的枚举对象名称是:DOWN
获取到的枚举对象对应的索引位置是:1
获取到的枚举对象名称是:LEFT
获取到的枚举对象对应的索引位置是:2
获取到的枚举对象名称是:RIGHT
获取到的枚举对象对应的索引位置是:3
*/
/*
调用对象与数组中对象比较的先后顺序结果是:1
调用对象与数组中对象比较的先后顺序结果是:0
调用对象与数组中对象比较的先后顺序结果是:-1
调用对象与数组中对象比较的先后顺序结果是:-2
*/
/*
现在可以实现接口中抽象方法的重写了!
现在可以实现接口中抽象方法的重写了!
现在可以实现接口中抽象方法的重写了!
现在可以实现接口中抽象方法的重写了!
*/
/*
向下移动一下!
向下移动一下!
向左移动一下!
向右移动一下!
*/
注解的基本概念
注解又叫标注,是从java5开始增加的一种引用数据类型。
注解本质上就是代码中的特殊标记,通过这些标记可以在编译、类加载、以及运行时执行指定的处理。
注解的语法格式
访问修饰符@interface注解名称{
注解成员;
}
自定义注解自动继承java.lang.annotation.Annotation接口。
通过@注解名称的方式可以修饰包、类、成员方法、成员变量、构造方法、参数、局部变量的声明等。
注解的使用方式
注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方式”形式来声明,其方法名定义了改成员变量的名字,其返回值定义来改成员变量的类型。
如果注解中只有一个参数成员,建议使用参数名为value,而类型只能是八种基本数据类型,String类型,Class类型,enum类型以及Annotation类型。
元注解
元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其他注解上面。
元注解主要有@Retention(保持 描述注解的有效范围)、@Documented(注解是否在文本注释中体现)、@Target(注解可以修饰哪些内容)、@Inherited(注解是否可以被继承所标记类的子类中)、@Repeatable(是否可重复)
元注解@Retention
@Retention应用到一个注解上用于说明改注解的生命周期,取值如下:
RetentionPolicy.SOURCE注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
RetentionPolicy.CLASS注解只被保留到编译进行到时候,它并不会被加载到JVM中(运行阶段不包括),注解默认方式。
RetentionPolicy.RUNTIME注解可以保留到程序运行到时候,它会被加载进入到JVM中,所以在程序运行时可以获取到它们。
元注解@Target
@Target用于指定被修饰的注解能用于哪些元素的修饰,取值如下:
ElementType.ANNOTATION_TYPE | 可以给一个注解进行注解 |
ElementType.CONSTRUCTOR | 可以给构造方法进行注解 |
ElementType.FIELD | 可以给属性进行注释 |
ElemengtType.LOCAL_VARIABLE | 可以给局部变量进行注释 |
ElementType.METHOD | 可以给方法进行注解 |
ElementType.PACKAGE | 可以给一个包进行注解 |
ElementType.PARAMETER | 可以给一个方法内的参数进行注解 |
ElementType.TYPE | 可以给类型进行注解,比如类、接口、枚举 |
元注解@Inherited
@Inheritred并不是说注解本身可以继承,而是说如果一个超类被该注解标记过的注解进行注解,如果子类没有被任何注解应用时,则子类就继承超类的注解。
元注解@Repeatable
@Repeatable表示自然可重复的含义,从java8开始增加的新特性。
从Java8开始对元注解@Target的参数类型ElementType枚举值增加了两个:
其中ElementType.TYPE_PARAMETER表示注解能写在类型变量的声明语句中,如:泛型。
其中ElementType.TYPE_USE表示改注解能写在使用类型的任何语句中。
常见的预制注解
预制注解就是Java语言自身提供的注解,具体如下:
@author | 标明开发该类模块的作者,多个作者之间使用,分割 |
@version | 表明该类模块的版本 |
@see | 参考转向,也就是相关主题 |
@since | 从哪个版本开始增加的 |
@param | 对方法中某参数的说明,如果没有参数就不能写。 |
@return | 对方法返回值的说明,如果改方法的返回值类型就是void就不能写 |
@exception | 对方法可能抛出的异常进行说明 |
@Override | 限定重新父类方法,改注解只能用于方法 |
@Deprecated | 用于表示所修饰的元素(类、方法等)已过时 |
@SuppressWarnings | 抑制编译器警告 |
package com.lagou.task08;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Target;
/**
* 自定义注解用于描述人物的角色
*/
@Target(ElementType.TYPE_USE)
@Repeatable(value = ManTypes.class)
public @interface ManType {
String value()default "";
}
package com.lagou.task08;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* 自定义注解可以描述多种角色
*/
@Target(ElementType.TYPE_USE)
public @interface ManTypes {
ManType[] value();
}
package com.lagou.task08;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.*;//导入所有
//@Retention(RetentionPolicy.SOURCE) //表示下面的注解在源代码中有效,一旦编译无效
//@Retention(RetentionPolicy.CLASS) //表示下面的注解在字节码文件中有效,默认方式
@Retention(RetentionPolicy.RUNTIME) //表示下面的注解在运行时有效。
@Documented //表示下面的注解信息可以被javadoc工具提取到API文档中,很少使用。
//表示下面的注解可以用于类型,构造方法,成员变量,成员方法的修饰
@Target({ElementType.TYPE,ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD}) //表示下面的注解可以用于类型的修饰
//若一个注解中没有任何成员,则叫做标识注解
public @interface MyAnnotation {
public String value() default "12";//声明一个String类型的成员变量,名字叫value default 给一个默认值
public String value2();
}
package com.lagou.task08;
//表示将标签MyAnnotation贴在Person类的代码中,使用注解时采用 成员参数名 = 成员参数值,...
//@MyAnnotation(value = "hello",value2 = "word")//表示将标签贴在Person类的代码中
@MyAnnotation(value2 = "word")
public class Person {
private String name;
private int age;
}
package com.lagou.task08;
@ManType(value = "职工")
@ManType(value = "超人")
//@ManTypes({@ManType("value = 职工"),@ManType(value = "超人")}) //在Java8以前处理多个注解的方式
public class Man {
public static void main(String[] args) {
int ia = 97;
char c1 = (@ManType char) ia;
}
}