内部类、枚举、注解
内部类
概述
1、什么是内部类?
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
2、为什么要声明内部类呢?
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,不在其他地方单独使用,那么整个内部的完整结构最好使用内部类。
3、内部类都有哪些形式?
根据内部类声明的位置(如同变量的分类),我们可以分为:
(1)成员内部类:
- 静态成员内部类
- 非静态成员内部类
(2)局部内部类
- 有名字的局部内部类
- 匿名的内部类
非静态成员内部类
语法格式:
【修饰符】 class 外部类{
【修饰符】 class 内部类{
}
}
非静态内部类的特点:
- 和外部类一样,它只是定义在外部类中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但是可以继承父类的静态成员,而且可以声明静态常量。
- 可以使用abstract修饰,因此它也可以被其他类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
- 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
- 外部类只允许public或缺省的
- 还可以在非静态内部类中使用外部类的所有成员,哪怕是私有的
- 在外部类的静态成员中不可以使用非静态内部类哦
- 就如同静态方法中不能访问本类的非静态成员变量和非静态方法一样
- 在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象
- 因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象
示例代码:
public class TestInner{
public static void main(String[] args){
Outer out = new Outer();
Outer.Inner in= out.new Inner();
in.inMethod();
Outer.Inner inner = out.getInner();
inner.inMethod();
}
}
class Father{
protected static int c = 3;
}
class Outer{
private static int a = 1;
private int b = 2;
protected class Inner extends Father{
// static int d = 4;//错误
int b = 5;
void inMethod(){
System.out.println("out.a = " + a);
System.out.println("out.b = " + Outer.this.b);
System.out.println("in.b = " + b);
System.out.println("father.c = " + c);
}
}
public static void outMethod(){
// Inner in = new Inner();//错误的
}
public Inner getInner(){
return new Inner();
}
}
静态内部类
语法格式:
【修饰符】 class 外部类{
【其他修饰符】 static class 内部类{
}
}
静态内部类的特点:
- 和外部类一样,它只是定义在外部类中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员
- 可以使用abstract修饰,因此它也可以被其他类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
- 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
- 外部类只允许public或缺省的
- 只可以在静态内部类中使用外部类的静态成员,哪怕是私有的
- 在静态内部类中不能使用外部类的非静态成员哦
- 在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象
示例代码:
public class TestInner{
public static void main(String[] args){
Outer.Inner in= new Outer.Inner();
in.inMethod();
Outer.Inner.inTest();
}
}
class Outer{
private static int a = 1;
private int b = 2;
protected static class Inner{
static int d = 4;//可以
void inMethod(){
System.out.println("out.a = " + a);
// System.out.println("out.b = " + b);//错误的
}
static void inTest(){
System.out.println("out.a = " + a);
}
}
}
其实严格的讲(在James Gosling等人编著的《The Java Language Specification》)静态内部类不是内部类,而是类似于C++的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已。所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的。例如:Map.Entry。
局部内部类
语法格式:
【修饰符】 class 外部类{
【修饰符】 返回值类型 方法名(【形参列表】){
【final/abstract】 class 内部类{
}
}
}
局部内部类的特点:
- 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父类继承的或静态常量
- 可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
- 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
- 和成员内部类不同的是,它前面不能有权限修饰符等
- 局部内部类如同局部变量一样,有作用域
- 局部内部类中使用能访问外部类的静态还是非静态的成员,取决于所在的方法
- 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量
- JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final
示例代码:
class Outer{
private static int a = 1;
private int b = 2;
public static void outMethod(){
final int c = 3;
class Inner{
public void inMethod(){
System.out.println("out.a = " + a);
// System.out.println("out.b = " + b);//错误的,因为outMethod是静态的
System.out.println("out.local.c = " + c);
}
}
Inner in = new Inner();
in.inMethod();
}
public void outTest(){
final int c = 3;
class Inner{
public void inMethod(){
System.out.println("out.a = " + a);
System.out.println("out.b = " + b);//可以,因为outTest是飞静态的
System.out.println("method.c = " + c);
}
}
Inner in = new Inner();
in.inMethod();
}
}
匿名内部类
补充:
其实匿名内部类是一种特色的局部内部类,只不过没有名称而已。因此
- 在匿名内部类中要访问访问外部类的静态还是非静态的成员,取决于所在的方法;
- 匿名内部类中还可以使用所在方法的局部常量,即用final声明的局部变量
- JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final
枚举与注解
枚举
概述
某些类型的对象是有限的几个,这样的例子举不胜举:
- 星期:Monday(星期一)…Sunday(星期天)
- 性别:Man(男)、Woman(女)
- 月份:January(1月)…December(12月)
在JDK1.5之前,需要程序员自己通过特殊的方式来定义枚举类型。
在JDK1.5之后,Java支持enum关键字来快速的定义枚举类型。
JDK1.5之前
在JDK1.5之前如何声明枚举类呢?
- 构造器私有化
- 本类内部创建一组对象
- 添加public static修饰符,对外暴露该对象
示例代码:
public class TestEnum {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
class Season{
public static final Season SPRING = new Season();
public static final Season SUMMER = new Season();
public static final Season AUTUMN = new Season();
public static final Season WINTER = new Season();
private Season(){
}
public String toString(){
if(this == SPRING){
return "春";
}else if(this == SUMMER){
return "夏";
}else if(this == AUTUMN){
return "秋";
}else{
return "冬";
}
}
}
JDK1.5之后
语法格式:
语法格式:
【修饰符】 enum 枚举类名{
常量对象列表
}
【修饰符】 enum 枚举类名{
常量对象列表;
其他成员列表;
}
示例代码:
public class TestEnum {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
enum Season{
SPRING,SUMMER,AUTUMN,WINTER
}
示例代码:
public class TestEnum {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
enum Season{
SPRING("春"),SUMMER("夏"),AUTUMN("秋"),WINTER("冬");
private String description;
private Season(String description){
this.description = description;
}
public String toString(){
//需要手动编写,无法使用Generate toString()...
return description;
}
}
枚举类的要求和特点:
- 枚举类的常量对象列表必须在枚举类的首行,因为是常量,所以建议大写。
- 如果常量对象列表后面没有其他代码,那么“;”可以省略,否则不可以省略“;”。
- 编译器给枚举类默认提供的是private的无参构造,如果枚举类需要的是无参构造,就不需要声明,写常量对象列表时也不用加参数。
- 如果枚举类需要的是有参构造,需要手动定义private的有参构造,调用有参构造的方法就是在常量对象名后面加(实参列表)就可以。
- 枚举类默认继承的是java.lang.Enum类,因此不能再继承其他的类型。
- JDK1.5之后switch,提供支持枚举类型,case后面可以写枚举常量名。
枚举类型常用方法
1.toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
2.name():返回的是常量名(对象名) 【很少使用】
3.ordinal():返回常量的次序号,默认从0开始
4.values():返回该枚举类的所有的常量对象,返回类型是当前枚举的数组类型,是一个静态方法
5.valueOf(String name):根据枚举常量对象名称获取枚举对象
示例代码:
public class TestEnum {
public static void main(String[] args) {
Season[] values = Season.values();
for (int i = 0; i < values.length; i++) {
switch(values[i]){
case SPRING:
System.out.println(values[i]+":春暖花开,万物复苏");
break;
case SUMMER:
System.out.println(values[i]+":百花争艳,郁郁葱葱");
break;
case AUTUMN:
System.out.println(values[i]+":菊桂飘香,百树凋零");
break;
case WINTER:
System.out.println(values[i]+":梅花独开,大地一色");
break;
}
}
}
}
enum Season{
SPRING,SUMMER,AUTUMN,WINTER
}
枚举类实现接口
枚举类也是类,可以实现接口,可以同时实现多个接口。可以统一实现,也可以用匿名内部类的形式,单独给某个常量对象实现抽象方法。
interface Windiness{
void wind();
}
enum Season implements Windiness{
SPRING,SUMMER(){
public void wind(){
System.out.println("刮台风");
}
},AUTUMN,WINTER;
public void wind(){
System.out.println("刮风");
}
}
枚举类中的 values 方法
//values 是 枚举中的一个方法 类.values()[] 来调用
代码演示(调用枚举中的方法!!!格式)
package com.atguigu.homework;
public class Homework2 {
public static void main(String[] args) {
java.util.Scanner scanner=new java.util.Scanner(System.in);
System.out.println("请输入月份(1——12):");
int num=scanner.nextInt();
//调用枚举内的静态方法!!!格式!!!!!!!!
Month month=Month.getByValue(num);
System.out.println(month);
int[] arr = {
1,2,3,4};
System.out.println(arr.length);
}
}
enum Month{
JANUARY(1,"1月份是一年的开始"),
FEBRUARY(2,"2月份是一年中最短的一个月"),
MARCH(3,"3月春暖花开"),
APRIL(4,"4月阳光明媚"),
MAY(5,"5月清凉初夏"),
JUNE(6,"6月骄阳似火"),
JULY(7,"7月下半年的第一个月"),
AUGUST(8,"8月人已晒干"),
SEPTEMBER(9,"秋风送爽"),
OCTOBER(10,"10月全国同欢"),
NOVEMBER(11,"11月寻找秋裤"),
DECMEBER(12,"12月冰天雪地");
private int value;
private String description;
Month(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
Month() {
}
public static Month getByValue(int value){
//这句话什么意思,为甚可以自己出来了个values的数组来装传进来的value,这个调用的格式是怎么回事????
//values 是 枚举中的一个方法 类.values()[] 来调用
return Month.values()[value-1];
}
@Override
public String toString() {
return "Month{" +
"value=" + value +
", description='" + description + '\'' +
'}';
}
}
枚举的实例可以加构造器和实例方法
代码演示
enum Payment implements Payable{
ALIPAY{
@Override
public void pay(){
System.out.println("扫码支付");
}},
WECHAT{
@Override
public void pay(){
System.out.println("扫码支付");
}},
CREDIT_CARD{
@Override
public void pay() {
System.out.println("输入卡号支付");
}},
DEPOSIT_CARD{
@Override
public void pay() {
System.out.println("输入卡号支付");
}};
}
interface Payable{
void pay();
}
单例模式
一个对象对外只提供一个对象
a.不能让外界实例化该类的对象(构造器私有化)反射是可以的
b.在类中对外界提供一个公有静态方法,提供该类的唯一对象
懒汉式和饿汉式区别:
实例化方面:
懒汉式默认不会实例化,外部什么时候调用什么时候new。
饿汉式在类加载的时候就实例化,并且创建单例对象。
线程安全方面:饿汉式线程安全 (在线程还没出现之前就已经实例化了,因此饿汉式线程一定是安全的)。
懒汉式:
/**
*是否 Lazy 初始化:是
*是否多线程安全:否
*实现难度:易
*描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
*这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
*/
class Single{
private static Single single;
private Single(){
}
public static Single getInstance(){
if(single==null)
single=new Single();
return single;
}
}
饿汉式:
/**
*是否 Lazy 初始化:否
*是否多线程安全:是
*实现难度:易
*描述:这种方式比较常用,但容易产生垃圾对象。
*优点:没有加锁,执行效率会提高。
*缺点:类加载时就初始化,浪费内存。
*它基于 classloder 机制避免了多线程的同步问题,
* 不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,
* 在单例模式中大多数都是调用 getInstance 方法,
* 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,
* 这时候初始化 instance 显然没有达到 lazy loading 的效果。
*/
class Single{
private static Single single=new Single();
private Single(){
}
public static Single getInstance(){
return single;
}
}
注解
用途:注解是编译器需要读取
语法 : @单词(实参)
位置:类,属性,方法,构造器,局部变量
系统预定义的三个最基本的注解
1、@Override
用于检测被修饰的方法为有效的重写方法,如果不是,则报编译错误!
只能标记在方法上。
它会被编译器程序读取。
2、@Deprecated
用于表示被标记的数据已经过时,不建议使用。
可以用于修饰 属性、方法、构造、类、包、局部变量、参数。
它会被编译器程序读取。
3、@SuppressWarnings
抑制编译警告。
可以用于修饰类、属性、方法、构造、局部变量、参数