5-内部类+包装类

本文围绕Java展开,介绍了内部类的四种类型及编译后的字节码文件命名,分析成员内部类不能有静态变量或方法的原因。还提及Java8新特性Lambda表达式,它可实现函数式编程,能替代匿名内部类完成接口实现。此外,讲解了包装类的概念、装箱拆箱操作及享元原则等。

内部类

/*
 * 内部类:
 *     1.成员内部类,类没有使用static修饰
 *     2.静态内部类,类使用了static修饰
 *     3.局部内部类,定义在方法中
 *     4.匿名内部类,没有具体的类名,并且仅使用一次,属于局部内部类
 * ps:对于每个内部类通过java编译之后都会产生一个独立的字节码文件
 * 成员内部类:外部类的类名$成员内部类的类名.class
 * 静态内部类:外部类的类名$静态内部类的类名.class
 * 局部内部类:外部类的类名$数字内部类的类名.class
 * 匿名内部类:外部类的类名$数字.class
 */

        内部类也是类,可extends可implements

  • 成员内部类:
/*
 * 成员内部类:
 * 		声明在类的内部不使用static修饰的类
 * 		成员内部类的权限修饰符默认是default /public/private/protected
 * 		成员内部类属于外部的对象,不属于外部类本身
 */
public class InnerClass {//外部类,可以使用final/abstract修饰
						 //不能用static修饰
	String name = "小白";
	public void display(){
		
		System.out.println("外部类的成员方法");
		
		//外部类中调用内部类的属性 --> 创建内部类对象
		/*
		 * 1.先创建外部类对象
		 * 2.通过外部类对象创建内部类对象
		 */
//		InnerClass innerClass = new InnerClass();
//		InnerClass.Inner inner = innerClass.new Inner();
//		inner.show();
		
		//简化成一步
		InnerClass.Inner inner2 = new InnerClass().new Inner();
		inner2.show();
	}
	
	//成员内部类是写在类的内部,并且与成员变量或成员方法平级
	public class Inner{
		//定义成员属性和行为,但是不能定义static修饰的
		String name = "小黑";
		
		//在内部类中访问同名属性问题
		public void show(){
			String name = "小红";
			//就近原则,打印方法体内部的变量   (小红)
			System.out.println(name);
			//内部类的成员变量  (小黑)
			System.out.println(this.name);
			//外部类的name  (小白)
			System.out.println(InnerClass.this.name);
			
			//若不是同名,并且是成员
//			display();
		}
	}
	
	public static void main(String[] args) {
		new InnerClass().display();
//		InnerClass.Inner inner2 = new InnerClass().new Inner();
//		inner2.show();
	}
			
}

        非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。

        1、static类型的属性和方法,在类加载的时候就会存在于内存中。

        2、要使用某个类的static属性或者方法,那么这个类必须要加载到jvm中。

        基于以上两点,可以看出,如果一个非static的内部类如果具有static的属性或者方法,那么就会出现一种情况:内部类未加载,但是却试图在内存中创建static的属性和方法,这当然是错误的,即类还不存在,但却希望操作它的属性和方法。

  • 静态内部类:
package note;

/*
 * 静态内部类:
 * 		定义在类的内部,并且使用static修饰的类
 * 		静态内部类的权限修饰符默认是default /public/private/protected
 */
package day01;

public class Outter {//外部类绝对不能使用static修饰
	
	String name = "小白";
	static int age = 20;
	
	public void display(){
		System.out.println("外部类的成员方法");
		
		//外部类成员方法访问静态内部类的静态变量
		System.out.println(Inner.name);
	}
	public static void showInfos(){
		System.out.println("外部类的静态方法");
		
		//外部类的静态方法中访问静态内部类的静态变量
		System.out.println(Inner.name);
		
		/*
		 * 创建静态内部类对象
		 */
		Outter.Inner inner = new Outter.Inner();
		inner.show();
	}
	
	//静态内部类
	public static class Inner{
		//可以定义静态属性和方法,也可以定义成员变量和方法
		static String name = "小黑";
		public void show(){
			System.out.println("----------------------");
			System.out.println("静态内部类访问外部类变量");
			
			//访问外部类的静态属性,不重名直接访问,重名使用类名确认属于谁
			System.out.println(Outter.age);
			//访问外部类的成员变量,
			//因为静态内部类不会持有外部类的对象
			//所以不能通过Outter.this访问,只能通过创建对象
			System.out.println(new Outter().name);
			
			System.out.println("----------------------");
			System.out.println("静态内部类访问外部类方法");
			//外部类的静态方法
//			showInfos();
			//成员方法访问方式
			new Outter().display();
		}
	}
	
	public static void main(String[] args) {
		Outter outter = new Outter();
		outter.display();
		System.out.println("----------------------");
		outter.showInfos();
	}

}

  • 局部内部类:
package note;

/*
 * 局部内部类:(一个基本上不会使用的类)
 * 		定义在方法体内的内部
 * 		不能使用public/protected/static修饰,不能包含静态成员
 * 		
 */
package day01;

public class Outter {
	
	String name = "小白";
	
	public void show(){
		//在方法中创建局部变量 -->JDK1.8之前必须显式的声明该"变量"为常量才可以
		//1.8开始 final可以不写,只要局部内部类访问该变量默认添加
		final int age = 30;
		//局部内部类
		class Inner{
			//不可以定义静态属性和方法
			String info = "信息";
			public void display(){
				//访问外部类的成员属性
				System.out.println(name);
				//在局部类中访问方法中的局部变量,这个局部变量必须使用final修饰
				//不能称为变量  而是  常量
				
				/*
				 * 正常方法的调用通过压栈和弹栈完成
				 * 因为局部内部类是声明在方法的内部,所以此处存在一个引用问题
				 * 一旦方法弹栈,整个方法都销毁了
				 * 局部内部类的空间是在堆中,虽然栈中的方法弹栈销毁了,但是堆这段空间不能及时回收(GC)
				 * 若局部内部类中引用了方法中的变量,此时就会出现引用问题
				 * 为了防止这个问题,将数据存储到常量池中,这样就可以避免问题发生了
				 */
				System.out.println(age);
				
				System.out.println(info);
			}
		}
		//局部内部类只能在方法中创建对象
		new Inner().display();
	}
	
	public static void main(String[] args) {
		new Outter().show();
	}
}
  • 匿名内部类:

        实现抽象类,接口,甚至重写普通类

        接口多态:

public interface ISmoking {
	/**
	 * 抽某个品牌的香烟
	 * @param name 香烟名
	 */
	void smoke(String name);

}

public class Man implements ISmoking {

	@Override
	public void smoke(String name) {
		System.out.println(name+"牌子的烟");
	}

}

public class Outter {
	
	public static void main(String[] args) {
		Man man = new Man();
		man.smoke("小熊猫");
		//接口是存在多态的  --> 接口是一个特殊的父类
		ISmoking iSmoking = new Man();
		iSmoking.smoke("芙蓉王");
	}
}

运行结果:
小熊猫牌子的烟
芙蓉王牌子的烟

 /*
 * 匿名内部类:(没有名字)
 *         可以写在类中,也可以写在方法体中,是一个特殊的局部内部类
 *         本身是没有构造方法的,需要使用父类的构造方法来完成初始化
 *         匿名内部类中是不会定义属性和方法的,没有意义不能调用
 *         匿名内部类是用来处理接口实现的
 * ps:若只需要使用一次接口中的方法,推荐使用匿名内部类,节省代码
 */

        定义在方法中:

       
        public static void main(String[] args) {
		//匿名内部类
		ISmoking isk = new ISmoking() {
			
			@Override
			public void smoke(String name) {
				System.out.println(name+"的烟");
			}
		};
		isk.smoke("中华");
	}

        定义在类中: 

public class Outter {
	
	public static void main(String[] args) {
		new Outter().show();
	}
	
	//为了方便在类中使用,会将匿名内部类作为类的属性存在
	ISmoking instance = new ISmoking() {
		
		/*
		 * 在匿名内部类中定义了属性和方法
		 * 这属性和方法无法被外部调用所以会成为垃圾代码
		 * 此种定义无意义
		 */
//		int money = 50;
//		public void doSomething(){
//			System.out.println("演示");
//		}
		
		/*
		 * 开发中需要在匿名内部类中所实现的的方法中添加其他变量,
		 * 最好的方式是定义在方法的内部,而不是成员变量
		 * 若定义成员变量,必须是约定好的
		 */
		@Override
		public void smoke(String name) {
			System.out.println(name+"牌子的烟");
		}
	};
	
	public void show(){
		instance.smoke("利群");
	}
	
}

        重写普通类:

public class InnerClass {
    public void display() {
        System.out.println("InnerClass.display");
    }

    public void display2() {
        InnerClass.Inner inner2 = new InnerClass().new Inner();
        inner2.show();
    }

    public class Inner {
        public void show() {
            System.out.println("InnerClass.Inner.show");
        }
    }
}

class Outter {
    public static void main(String[] args) {
        //匿名内部类实现普通类
        InnerClass innerClass = new InnerClass() {
            @Override
            public void display() {
                super.display();
                System.out.println("匿名内部类实现普通类 重写display方法");
            }
            @Override
            public void display2() {
                super.display2();
                System.out.println("匿名内部类实现普通类 重写display2方法");
            }
        };
        System.out.println("===============匿名内部类 --> 普通类===============");
        innerClass.display();
        System.out.println("========匿名内部类 --> 普通类 --> 成员内部类========");
        innerClass.display2();
    }
}

        Lambda表达式:

        Lambda表达式是Java8中的新特性

        Java8中引入Lambda表达式,使得java可以函数式编程,在并发性能上迈出了实质性的一步。

        什么是函数式编程?

        函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。ps:λ这个符号可以在搜狗输入法的符号中显示

        而在面向对象编程中,面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程范型,同时也是一种程序开发的方法。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关联的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象。

        Lambda表达式可以替代匿名内部类完成接口的实现:

public class TestA {
	public static void main(String[] args) {
		//实现接口匿名内部类
		InterfaceA interfaceA = new InterfaceA() {
			@Override
			public void show() {
				System.out.println("匿名内部类实现的借口");
			}
		};
		interfaceA.show();
		
		//实现接口的Lambda表达式
		InterfaceA interfaceA2 = () -> {
			System.out.println("Lambda表达式实现的接口");
		};
		interfaceA2.show();
	}
}

//函数式接口 --> 明确表示当前接口是可以使用lambda表达式实现的
//函数中必须提供一个要被实现的方法
@FunctionalInterface  
interface InterfaceA{
	void show();
}

运行结果:
匿名内部类实现的借口
Lambda表达式实现的接口

         语法:(参数) -> { 具体实现的方法逻辑 };
        1.(参数) 
                参数列表中的数据类型是可以省略,直接写形参名,没有参数会空()的形式,只有一个参数,小括号都可以省略
                例如:(a,b) --> 两个参数     () --> 没有参数       (a) ---> 一个参数   /  直接写a,省略小括号
                lambda表达式中的参数对应是接口中方法的参数列表,因为接口中的参数列表中数据类型已经明确表示,所以在lambda中是可以省略的,若需要添加参数类型,一定要和接口中方法的参数类型一致
        2.-> 箭头    (语法要求必须写,必须是英文字符 减号 + 大于号)
        3.代码块{ }
                代码块是方法的具体实现,方法逻辑都需要写到这个代码块中
                ps:Lambda允许省略{ } --> 只有一句可以省略,Lambda允许省略return --> 只有一条return的时候可以省略

public class TestA {
    public static void main(String[] args) {
        //Lanbda标准形式实现接口
        InterfaceA interfaceA = () -> {
            System.out.println("Lambda表达式实现A接口");
        };
        InterfaceB interfaceB = () -> {
            System.out.println("B接口中的方法");
        };
        InterfaceC interfaceC = (int a) -> {
            System.out.println("C接口中的方法" + a);
        };
        InterfaceD interfaceD = (int a, int b) -> {
            System.out.println("D接口中的方法" + a + b);
        };
        interfaceA.show();
        interfaceB.showB();
        interfaceC.showC(3);
        interfaceD.showD(4, 4);

        System.out.println("--------------------------------");

        //开始进行简化
        InterfaceB interfaceB2 = () ->
                System.out.println("B2接口中的方法");
        InterfaceC interfaceC2 = a ->
                System.out.println("C2接口中的方法" + a);
        InterfaceD interfaceD2 = (a, b) ->
                System.out.println("D2接口中的方法" + a + " " + b);

        interfaceB2.showB();
        interfaceC2.showC(5);
        interfaceD2.showD(6, 7);

        System.out.println("----------------------------------");

        showInfos("测试匿名内部类", new InterfaceD() {
            public void showD(int a, int b) {
                methD();
                System.out.println(a + b);
            }
        });

        showInfos("测试Lambda表达式", (a, b) -> {
//            methD();
            System.out.println(a + b);
        });

    }

    public static void showInfos(String msg, InterfaceD d) {
        System.out.println("信息是:" + msg + " " + d);
        d.showD(1, 2);
    }
}

@FunctionalInterface
interface InterfaceA {
    void show();
}
interface InterfaceB {
    void showB();
}
interface InterfaceC {
    void showC(int a);
}
interface InterfaceD {
    default void methD() {
        System.out.println("InterfaceD.default");
    }
    void showD(int a, int b);
}

        运行结果:

        1.匿名内部类可以为任意接口创建实例,不管接口中包含多少个抽象方法,匿名内部类都可以实现,但Lambda表达式只能为函数式接口提供实例(即只能实现一个抽象方法)
        2.匿名内部类不仅可以为接口创建实例,也可以为抽象类和普通类创建实例(一般不这样用),但是Lambda表达式只能为函数式接口提供实例
        3.匿名内部类实现的抽象方法可以在方法体中调用接口默认方法(default),但是Lambda表达式不提供该功能,所以Lambda是一种函数式编程,不能完全替代匿名内部类的作用

包装类

        包装类相当于基本数据类型的 “引用类型版” ,是序列化后的数据,传输效率高

        包装类提供非常方便的方法,是一个特殊的引用类型,就算作为方法参数效果和值类型是一样的(传值而不是传址)

public class Demo {
	
	//就算作为方法参数 效果和值类型是一样的(传值而不是传址)
	public static void main(String[] args) {
		Integer aInteger = 1;
		Integer bInteger = 2;
		new Demo().swapInteger(aInteger, bInteger);
		System.out.println(aInteger);
		System.out.println(bInteger);
	}
	
	
    public void swapInteger(Integer a, Integer b) {
        System.out.println(a+"==="+b);
        Integer tempInteger = a;
        a = b;
        b = tempInteger;
        System.out.println(a+"==="+b);
    }

}

  • 数据的装箱和拆箱:

        装箱:从基本数据类型转换到包装类类型

        拆箱:从包装类类型转换为基本数据类型

        在jdk1.5之前都是手动装箱和拆箱:

                装箱:包装类类型 名字 = new 包装类类型(基本数据类型值);

                拆箱:基本数据类型 名字 = 包装类类型变量.XXXvalue();

                ps:XXX--> 对应的是基本数据类型

        在jdk1.5之后都是自动装箱和拆箱:

                装箱:包装类型 名字 = 基本数据类型的值;

                拆箱:基本数据类型 名字 = 包装类类型的变量


        基本数据类型之间存在兼容关系(互相存储):

                例如 整数 和 小数 和 字符

                基本类型所对应的包装类彼此之间没有任何关系

                 ps: 数值类型的包装类的父类 是 Number

        拆箱所使用方法: 

        final类:Byte,Double,Float,Integer,Long,Short           -->        间接说明了传值而不是传址(特殊的引用类型)

public class IntegerDemo {
	
	public static void main(String[] args) {
		//装箱和拆箱操作
		//1.自动装箱和拆箱
		int age = 12;//基本数据类型
		Integer objAge= age;//装箱
		int ageInt = objAge;//拆箱
		System.out.println(objAge + objAge);//可以计算
		
		//2.手动装箱和拆箱
		Integer objAge1 = new Integer(age);
		int age2 = objAge1.intValue();
		
		//剩余的包装类和值类型使用方式一致
	}
}
  • Integer  <-->  String:
/*
 * 转换的时候,String类型的值,必须是数值字符串     不能是其他字符串不然会出现异常
 */
public class IntegerDemo2 {
	
	public static void main(String[] args) {
		//将String转换为Integer
		//1.Integer中的静态方法,参数是一个"数值型的字符串"
		Integer integer = Integer.valueOf("123");
		System.out.println(integer);
		
		//不可以,出现异常NumberFormatException
//		System.out.println(Integer.valueOf("123a"));
		
		//2.在创建Integer对象的时候来转换,需要是一个"数值字符串"
		Integer integer2 = new Integer("123");
		System.out.println(integer2);
		
		//将Integer转换为String类型
		String str = integer + "";
		System.out.println(str + str);
		
		//将String类型转换为基本数据类型
		Integer integer3 = Integer.valueOf("123");
		int i4 = integer3;
		//下面的写法比上边的好
		String str2 = "12345";
		int i5 = Integer.parseInt(str2);//ps:int这个位置可以替换为其他的数据类型
		
		//特殊的包装类Boolean,只认true和false
		//除他们两个之外,任何数据得到的结果都是false
		Boolean boolean1 = new Boolean("String");
		Boolean boolean2 = new Boolean("true");
		System.out.println(boolean1);
		System.out.println(boolean2);
	}

}
  • 包装类中的享元原则(享元模式):
public class IntegerDemo3 {
	
	public static void main(String[] args) {
		//Integer的equals方法重写过
		/*
		 * public boolean equals(Object obj) {
		 * 		if (obj instanceof Integer) {
		 * 			return value == ((Integer)obj).intValue();
		 * 		}
		 * 		return false;
		 * }
		 */
		
		Integer integer1 = new Integer(123);
		Integer integer2 = new Integer(123);
		System.out.println(integer1 == integer2);//false 地址
		System.out.println(integer1.equals(integer2));//true
		System.out.println("--------------------------");
		
		/*
		 * public static Integer valueOf(int i) {
		 * 		final int offset = 128;
		 * 		if (i >= -128 && i <= 127) { // must cache 
		 * 			return IntegerCache.cache[i + offset];
		 * 		}
		 * 		return new Integer(i);
		 * }
		 */
		//因为Integer存在一个缓冲区 --> 享元原则(模式) 
		//默认[-128,127]之间所产生的数据,使用地址都是同一个(减少内存开销)
		Integer integer3 = Integer.valueOf(123);
		Integer integer4 = Integer.valueOf(123);
		System.out.println(integer3 == integer4);//true
		System.out.println(integer3.equals(integer4));//true
		System.out.println("--------------------------");
		
		Integer integer5 = 123;//自动装箱的底层实现就是Integer.valueOf(123);
		Integer integer6 = 123;
		System.out.println(integer5 == integer6);//true
		System.out.println(integer5.equals(integer6));//true
		System.out.println("--------------------------");
		
		Integer integer7 = new Integer(250);
		Integer integer8 = new Integer(250);
		System.out.println(integer7 == integer8);//false 堆空间地址
		System.out.println(integer7.equals(integer8));//true
		System.out.println("--------------------------");
		
		
		Integer integer9 = Integer.valueOf(250);//return new Integer(i);
		Integer integer10 = Integer.valueOf(250);
		System.out.println(integer9 == integer10);//false 不在范围内
		System.out.println(integer9.equals(integer10));//true
		System.out.println("--------------------------");
		
		Integer integer11 = 250;//自动装箱的底层实现就是Integer.valueOf(123);
		Integer integer12 = 250;
		System.out.println(integer11 == integer12);//false
		System.out.println(integer11.equals(integer12));//true
		System.out.println("--------------------------");
		
		Integer integer13 = new Integer(123);
		Integer integer14 = 123;
		System.out.println(integer13 == integer14);//false
	}

}

        ps:整数包装类:缓存区间:[-128,127]          character:缓存:[0,127]

        Integer 和 int 之间的区别:

                Integer:引用类型,存储在堆中,初始值为null

                Int:值类型,存储在栈中,初始值为0

        Integer 提供些方便操作的方法来进行操作,集合中只能存储包装类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值