u03-特殊类型

141 阅读11分钟

1. 内部类

概念:

  • 我们可以将一个类写在另一个类中,称为内部类写法,内部类提供了更好的封装,只能让它的外部类直接访问,不允许同一个包中的其他类直接访问。
  • 一般内部类在只为外部类提供服务的情况下优先使用。
  • 内部类也需要使用public等权限修饰符类界定允许访问的范围。

1.1 成员内部类

概念:

  • 成员内部类特性:
    • 内部类可以访问外部类的成员(包括私有成员)。
    • 外部类不可以访问内部类的任何成员。
    • 成员内部类不能有静态方法、静态属性或静态块。
  • 成员内部类访问外部类成员:
    • 方法1:在内部类中使用外部类实例访问。
    • 方法2:在内部类中使用 外部类名.this.属性名 格式进行访问。
  • 获取成员内部类实例: 先获取外部类实例,然后使用 外部类.new 内部类() 的格式来获取内部类实例。
    • 内部类实例可以使用内部类类型接收,但是需要导包。
    • 内部类实例可以使用 外部类.内部类 的格式类型接收,无需导包。

内部类产生的class文件的格式是:外部类$内部类.class,如上面的案例,产生的class文件为:Outer$Inner.class

源码: /javase-oop/

  • src: c.y.innerclass.MemberInnerClassTest
/**
 * @author yap
 */
public class MemberInnerClassTest {
    @Test
    public void innerClass() {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.innerMethod();
    }
}

class Outer {
    private String field = "field-outer";
    class Inner {
        private String field = "field-inner";
        public void innerMethod() {
            System.out.println(field);
            System.out.println(new Outer().field);
            System.out.println(Outer.this.field);
        }
    }
}

1.2 静态内部类

概念:

  • 静态内部类特性:
    • 内部类可以访问外部类的成员(包括私有成员)。
    • 外部类不可以访问内部类的任何成员。
    • 静态内部类可以拥有静态属性、静态块和静态方法。
  • 静态内部类访问外部类成员: 允许在静态内部类中使用外部类实例访问,但不允许使用 外部类名.this 来访问。
  • 获取静态内部类实例: 先获取外部类实例,然后使用 外部类.内部类() 的格式来获取内部类实例(无需new静态内部类)。
    • 内部类实例可以使用内部类类型接收,但是需要导包。
    • 内部类实例可以使用 外部类.内部类 的格式类型接收,无需导包。

源码: /javase-oop/

  • src: c.y.innerclass.StaticInnerClassTest

/**
 * @author yap
 */
public class StaticInnerClassTest {
    @Test
    public void staticInnerClass() {
        StaticOuter.StaticInner staticInner = new StaticOuter.StaticInner();
        staticInner.method();
        StaticOuter.StaticInner.staticMethod();
    }
}

class StaticOuter {
    private int field = 1;
    private static int staticField = 2;

    static class StaticInner {
        private int field = 3;
        private static int staticField = 4;

        public void method() {
            System.out.println(field);
            System.out.println(staticField);
            System.out.println(new StaticOuter().field);
            System.out.println(StaticOuter.staticField);
            // System.out.println(Outer.this.field);
        }

        public static void staticMethod() {
            System.out.println(staticField);
            System.out.println(new StaticOuter().field);
            System.out.println(StaticOuter.staticField);
            // System.out.println(field);
            // System.out.println(Outer.this.field);
        }
    }
}

1.3 匿名内部类

概念:

  • 匿名内部类适合那种只需要使用一次的类,比如线程的创建等。
  • 匿名内部类格式为:new 父类构造/接口(){}
  • 匿名内部类一般用于快速实现抽象类和内部接口。

源码: /javase-oop/

  • src: c.y.innerclass.AnonymousInnerClassTest
/**
 * @author yap
 */
public class AnonymousInnerClassTest {
    @Test
    public void baseServiceByInnerClass() {
        new BaseService() {
            @Override
            public void create() {
                System.out.println("添加...");
            }
        }.create();
    }

    @Test
    public void userServiceByInnerClass() {
        new UserService() {
            @Override
            public void create() {
                System.out.println("添加...");
            }
        }.create();
    }
}

abstract class BaseService {
    /**
     * 添加数据
     */
    abstract void create();
}

interface UserService {
    /**
     * 添加数据
     */
    void create();
}

1.4 Lambda表达式

概念:

  • Lambda表达式是为了简化函数式接口的实现过程而存在的,它只为简化代码,不能提高效率。
    • 函数式接口:如果一个接口中只有一个抽象方法,那么它就可以称为函数式接口,函数式接口中允许存在静态方法和default方法。

1.4.1 表达式结构

概念:

  • 主要结构:(参数列表) -> {方法体},其中 -> 代表传递。
  • 如果参数只有一个,可以省略括号,没有参数的时候不能省略。
  • 如果过程中只有一行代码可以省略,如果过程中只有一个return也可以省略。

源码: /javase-oop/

  • src: c.y.innerclass.LambdaInnerClassTest
/**
 * @author yap
 */
public class LambdaInnerClassTest {
    @Test
    public void lambda() {
        AdditionService additionService = (numA, numB) -> numA + numB;
        System.out.println(additionService.add(10, 20));
    }
}

interface AdditionService {

    /**
     * 两个int数相加的方法
     *
     * @param numA 加法运算的一个数字
     * @param numB 加法运算的另一个数字
     * @return 返回两个数相加的结果
     */
    int add(int numA, int numB);
}

1.4.1 表达式返回值

概念:

  • Lambda表达式用来替代匿名内部类实现接口,得到接口实现类的对象,这也就意味着Lambda表达式具有自己的返回值类型。
  • Lambda表达式的返回值类型,就是其实现的接口的类型,且该接口必须是函数式接口。
  • 为了方便Lambda表达式的使用,Java8中提供了4类新的核心接口配合Lambda表达式的功能,它们都定义在 java.util.function 包下。
    • 消费型接口Consumer,内置 accpet(),只有参数,没有返回值。
    • 供给型接口Supplier,内置 get(),只有返回值,没有参数。
    • 函数型接口Function,内置 apply(),既有返回值,又有参数。
    • 断言型接口Predicate,内置 test(),有参数,且返回值固定为boolean类型。

源码: /javase-oop/

  • src: c.y.innerclass.LambdaReturnValueTest
/**
 * @author yap
 */
public class LambdaReturnValueTest {

    @Test
    public void returnValue(){
        Consumer consumer = p1 -> System.out.println("hello");
        Supplier supplier = () -> 100;
        Function function = p1 -> 100;
        Predicate predicate = p1 -> true;
    }
}

2. 枚举类

概念:

  • enum 全称 enumeration,是JDK1.5中引入的新特性,存放在 java.lang 包中,它的语法结构尽管和普通class的语法不一样,但是经过编译器编译之后产生的仍然是一个class文件。
  • 所有的 enum 都默认继承了抽象类 java.lang.Enum<E>,即不能再继承其他类。

2.1 枚举类定义

概念:

  • 创建枚举类时,关键字要使用 enum 替换 class
  • 枚举类中的每个属性都将映射到 Enum 类的唯一构造器中:
    • protected Enum(String name, int ordinal)
      • name:枚举属性的名称,是一个字符串。
      • ordinal:枚举属性被创建的顺序,从0开始。
    • 这个构造器不能被显示调用,而是编译器根据枚举属性自动调用的。
  • 枚举类中的属性建议使用大写。
  • 允许在枚举类中,像普通类那样自定义属性和方法,但是必须写在枚举常量的下面。
  • 枚举类名.枚举常量 可以返回一个该枚举类的枚举对象。

源码: /javase-oop/

  • src: c.y.enumclass.EnumTest
/**
 * @author yap
 */
public class EnumTest {
    @Test
    public void week(){
        Week mon = Week.MON;
        System.out.println(mon);
    }
}

enum Week{

    /**星期一到星期日*/
    MON, TUE, WED, THU, FRI, SAT, SUN;

    /*
        // 上面的枚举属性相当于调用了7次Enum类的构造器代码:
        new Enum<Week>("MON", 0);
        new Enum<Week>("TUE", 1);
        new Enum<Week>("WED", 2);
        new Enum<Week>("THU", 3);
        new Enum<Week>("FRI", 4);
        new Enum<Week>("SAT", 5);
        new Enum<Week>("SUN", 6);
    */
}

2.2 自定义构造器

概念: 枚举类可以定义自己的构造器来覆盖 Enum 的构造器,但默认且只能是private修饰的,一样不可以被调用。

源码: /javase-oop/

  • src: c.y.enumclass.CustomEnumTest

/**
 * @author yap
 */
public class CustomEnumTest {

    @Test
    public void rgbColorTest() {
        System.out.println(RgbColor.RED.getRgb());
    }
}

enum RgbColor {

    /**
     * RGB三原色
     */
    RED(1), GREEN(2), BLUE(3);

    private int rgbCode;

    RgbColor(int rgbCode) {
        this.rgbCode = rgbCode;
    }

    public int getRgb() {
        return rgbCode;
    }
}

枚举类中的方法需要使用某个枚举属性来调用。

2.3 枚举类常用API

概念: 枚举类常用的API方法如下:

  • 枚举类名.values()
    • 获取枚举类中所有的枚举属性组成的枚举数组。
    • 在我们编写自定义的enum时,是没有 values() 的,只有在编译java文件时,javac才会自动帮助我们生成这个方法。
  • 枚举类名.枚举常量.toString()
    • 返回该枚举对象的全部信息(名,序号等)的字符串。
    • 可以在枚举属性后添加匿名内部类来重写toString:
      • MON{@Override public String toString(){ return "星期一";}}
  • 枚举类名.枚举常量.name()
    • 返回该枚举对象的名字。
  • 枚举类名.枚举常量.ordinal()
    • 返回枚举对象从0开始的序号。
  • 枚举类名.枚举常量.compareTo(枚举类名.枚举常量)
    • 返回两个枚举对象的序号(ordinal)差。

源码: /javase-oop/

  • src: c.y.enumclass.EnumApiTest
/**
 * @author yap
 */
public class EnumApiTest {
    @Test
    public void api() {
        System.out.println(EnumDemo.ONE.toString());
        System.out.println(EnumDemo.SIX.name());
        System.out.println(EnumDemo.FOR.ordinal());
        System.out.println(EnumDemo.TWO.compareTo(EnumDemo.FIV));
        for (EnumDemo e : EnumDemo.values()) {
            System.out.print(e.toString() + "\t");
        }
    }
}

enum EnumDemo{
    /**1到6*/
    ONE, TWO, THR, FOR, FIV, SIX;
}

2.4 switch中使用枚举

概念: switch()中可以直接使用枚举类型。

源码: /javase-oop/

  • src: c.y.enumclass.EnumInSwitchTest
/**
 * @author yap
 */
public class EnumInSwitchTest {
    @Test
    public void enumForSwitch() {
        Week dayOfWeek = Week.SAT;
        switch (dayOfWeek) {
            case SAT:
                System.out.println("复习..");
                break;
            case SUN:
                System.out.println("旅游..");
                break;
            default:
                System.out.println("上课..");
                break;
        }
    }
}

3. 注解类

概念:

  • Annotation是从JDK5开始引入的新技术,注解不是注释(comment)、注解和注释一样,都可以对程序作出某种解释,但不同的是,注解可以被其它程序读取并操作。
  • 注解是一个特殊的类:
    • public @interface 注解名{} 的方式被创建。
    • @注解名(KV) 的格式被使用。
  • 注解可以修饰 packageclassmethodfield 等,但是在同一个位置,相同注解不能重复使用。

3.1 内置注解

概念: jdk提供的默认注解,我们叫做内置注解,可以直接使用,使用方式如下:

  • 注解如果只有一个参数,则可以使用 @注解名(KV) 的方法来使用。
  • 注解如果有多个参数,则必须使用 @注解名({KV, KV, KV...}) 的方法来使用,使用大括号修饰。

如果唯一的参数名是 value,则 value 也可以省略。

3.1.1 @Override

概念: 当子类重写了父类方法的时候,建议在重写方法上添加 @Override 来告诉javac,这个方法是一个重写方法,此时这个方法必须按照重写方法的格式来编写,否则javac将报错。

源码: /javase-oop/

  • src: c.y.annotation.OverrideTest
/**
 * @author yap
 */
public class OverrideTest {
    private static class OverrideDemo {
        @Override
        public String toString() {
            return "重写了toString()";
        }
    }

    @Test
    public void studentToString() {
        System.out.println(new OverrideDemo().toString());
    }
}

3.1.2 @Deprecated

概念: 表示不推荐使用的方法,当某个方法被此注解标识,则代表这个方法过时,在使用它的时候,javac会为此方法上添加删除线以作警示,但是这个方法仍能正确使用,不推荐不代表不能用。

源码: /javase-oop/

  • src: c.y.annotation.DeprecatedTest
/**
 * @author yap
 */
public class DeprecatedTest {

    private static class DeprecatedDemo {
        @Deprecated
        public void method() {
            System.out.println("deprecated method...");
        }
    }

    @Test
    public void studentDeprecated() {
        new DeprecatedDemo().method();
    }
}

3.1.3 @SuppressWarnings

概念: @SuppressWarnings 用于镇压、抑制编译器产生警告信息,常用值如下:

  • all:所有警告。
  • unused:未曾使用过这个属性或方法的警告。
  • boxing:装箱拆箱相关警告。
  • cast:类型转换相关警告。
  • dep-ann:使用了过时的注解的警告。
  • deprecation:使用了过时的类或方法的警告。
  • fallthrough:switch发生case穿透(未写break)时的警告。
  • finally:finally块不能完成时的警告。
  • hiding:不可见的局部变量警告。
  • incomplete-switch:switch语句中缺少条目警告。
  • nls:非NLS字符串文本警告。
  • null:空分析警告。
  • rawtypes:对类参数使用泛型时的非特定类型的警告。
  • restriction:不鼓励或禁止引用的用法的警告。
  • serial:可序列化的类中没有serialVersionUID属性。
  • static-access:不正确的静态访问警告。
  • synthetic-access:从内部类进行未优化的访问警告。
  • unchecked:使用了未检查警告。
  • unqualified-field-access:字段访问不合格相关的警告。

源码: /javase-oop/

  • src: c.y.annotation.SuppressWarningsTest
/**
 * @author yap
 */
public class SuppressWarningsTest {
    @SuppressWarnings(value = "unused")
    public void warning() {
        int num = 10;
    }
}

3.2 元注解

概念: 元注解(meta-annotation)就是注解其他注解的注解,它负责对其他注解进行说明,Java中定义了四种元注解:

  • @Target:描述注解的使用范围,即这个注解可以作用在什么地方,包上?方法上?类上?
  • @Retention:描述注解的保留策略,即注解的生命周期,注解何时生效、何时失效?
  • @Documented:标明这个注解会成为javadoc的一部分。
  • @Inherited:标明这个注解可以被子类继承。

3.2.1 @Target

概念: @Target描述注解的使用范围,注解中有一个参数value,值是 ElementType[] 枚举数组类型,ElementType枚举属性如下:

  • ElementType.TYPE:表示该注解可以定义在接口、类和枚举类上。
  • ElementType.FIELD:表示该注解可以定义在字段、枚举常量上。
  • ElementType.METHOD:表示该注解可以定义在方法上。
  • ElementType.PARAMETER:表示该注解可以定义在方法参数上。
  • ElementType.CONSTRUCTOR:表示该注解可以定义在构造器上。
  • ElementType.LOCAL_VARIABLE:表示该注解可以定义在局部变量上。
  • ElementType.ANNOTATION_TYPE:表示该注解可以定义在注解上。
  • ElementType.PACKAGE:表示该注解可以定义在包上。

3.2.2 @Retention

概念: 描述注解的生命周期,注解中有一个参数value,值是RetentionPolicy[]枚举数组类型,RetentionPolicy枚举属性如下:

  • RetentionPolicy.SOURCE:表示该注解只在源代码中保留。
  • RetentionPolicy.CLASS:表示该注解在类代码中保留,但在运行时无法获得。
  • RetentionPolicy.RUNTIME:表示该注解在整个运行期间都保留。

只有生命周期为RUNTIME的时候,该注解才能被反射到。

3.3 自定义注解

概念:

  • 使用@interface自定义注解时,自动继承了 java.lang.annotation.Annotaion 接口。
  • 自定义注解属性的时候,要遵循以下规定:
    • 方法名就是是参数名,如果只有一个参数,一般起名为value,因为使用者可以对它省略。
    • 方法返回值就是参数类型。
    • 可以通过default来声明参数的默认值。
  • 自定义注解的时候需要使用元注解,来声明你定义的注解可以用在什么地方,以及何时有效,如:
    • @Target({ElementType.TYPE, ElementType.METHOD}):可以作用在类上或者方法上。
    • @Retention(RetentionPolicy.RUNTIME):整个运行期间都能有效。

源码: /javase-oop/

  • src: c.y.annotation.CustomAnnotationTest
/**
 * @author yap
 */
public class CustomAnnotationTest {

    @Documented
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {

        // String name;
        String name();

        // int age = 18;
        int age() default 18;

        // String[] course = {"语文", "数学"}
        String[] course() default {"语文", "数学"};
    }


    @MyAnnotation(name = "赵四")
    private static class Student {

        @MyAnnotation(name = "刘能")
        public void method(){

        }
    }
}