杂记——修饰符和内部类和回调函数

一、修饰符

Java语言提供了很多修饰符,主要分为以下两类:
  访问修饰符(访问控制修饰符)
  非访问修饰符

1.访问控制修饰符:

Java中,可以使用访问控制修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

  • default (即默认,什么也不写): 在同一包内的类可见。
    • 使用对象:类、接口、变量、方法。
    • 注意:接口里变量隐式声明为 public static final,方法为 public。
  • private : 在同一类内可见。
    • 使用对象:变量、方法。 注意:不能修饰外部类。
  • protected : 对同一包内的类和所有子类可见。
    • 使用对象:变量、方法。 注意:不能修饰外部类。
    • 注意:当子类与父类不在同一包中,那么在子类中,子类的实例(new)可以访问其从父类继承而来的 (protected) 方法,但是却不可以使用父类的实例去访问父类的(protected)方法。
  • public : 对所有类可见。
    • 使用对象:类、接口、变量、方法。

2.非访问控制修饰符:

为了实现一些其他的功能,Java 也提供了许多非访问修饰符。

  • static: 静态的。
    • 使用对象:成员内部类、类方法、类变量。
    • 注意:被staic修饰的方法只能访问静态成员
  • final: 最后的、最终的。
    • 使用对象:类、变量、方法。
    • 注意:final 类不能被继承;被 final 修饰的实例变量必须显式指定初始值,且变量一旦赋值后,不能被重新赋值,通常和 static 修饰符一起使用来创建类常量;父类中的 final 方法可以被子类继承,但是不能被子类重写。
  • abstract: 抽象的。
    • 使用对象:类,方法。
    • 注意:一个类不能同时被 abstract 和 final 修饰。抽象方法不能被声明成 final 和 static。如果一个类包含抽象方法,那么该类一定要声明为抽象类。
  • synchronized: 同步的。
    • 使用对象:方法。
    • 注意:synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。
  • transient: 临时的
    • 使用对象:变量。
    • 注意:用transient关键字标记的成员变量不参与序列化过程。
    • 用途:安全方面考虑,比如密码之类的数据,是不允许序列化到本地的。
  • volatile: volatile修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
    • 使用对象:变量。

二、内部类

内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$ 符号(A$B.class)

  • 1) 成员内部类: 是在类里和方法并列的。
    • 修饰符:访问控制修饰符、static、final、abstract(public abstract static class A{})
    • 注意:final和abstract不能同时修饰。如果内部类没有被static修饰,则不可以定义static修饰的属性和方法。
    • 说明:在一个类内部,需要操作某种属性,而这个属性需要涉及的面又很广,就可以考虑将这些属性操作设计为内部类。又或者好比你设计类 B 的目的只是为了给类 A 使用,那么,我们就可将其设定为内部类,没有必要将类 B 设置成单独的 Java 文件,防止与其他类产生依赖关系。
    • 访问:外部类可以通过创建成员内部类对象来访问成员内部类中的属性和方法【内部类名 name = this.new 内部类名();】
         成员内部类可以直接访问外部类的所有属性和方法,包括私有属性和方法
         非外部类访问内部类【外部类名.内部类名 name = new 外部类名().new 内部类名();】
    • 重要: java的类是单继承的,使用内部类后可以使java很容易实现多继承的功能
public class MainExample {
    
    private class test1 extends Example1 {
        public String name() {
            return super.name();
        }
    }

    private class test2 extends Example2 {
        public int age() {
            return super.age();
        }
    }
}
  • 2) 方法(局部)内部类: 是在一个方法或者一个作用域里面的。

    • 修饰符:abstract,final。
    • 注意:局部内部类只能访问方法中声明的final类型的变量。局部内部类可随意访问外部类的成员变量和方法,即使是私有的。创建内部类的实例只能在包含他的方法中。
  • 3)静态内部类: 内部类前加static修饰符就是静态内部类。

    • 修饰符:访问控制修饰符、abstract、final和static(static必须加)
    • 注意:静态内部类只能够访问外部类的静态成员。
    • 说明:由于静态的特殊性即可以不创建对象直接访问到。
  • 4)匿名内部类: 一个没有名字的类,是局部内部类的简化写法。

    • 本质:其实是继承该类或者实现接口的子类匿名对象。
    • 重点:
      • 1、匿名内部类的前提是存在一个类或者接口,且匿名内部类是写在方法中的。
      • 2、只针对重写一个方法时使用,需要重写多个方法时不建议使用。
    • 意义:方便,简洁。
      • 1、假如现在有一个接口,规范了某些功能,但是在别的类中突然想使用这个功能,但是又只用一次就行了,如果再创建一个类,实现该接口,然后再调用该类,多麻烦啊!
      • 2、假如我想使用一下一个类的protected 方法时,但是又不和这个类在同一个包下,你是没办法调用的。这时你可以声明一个匿名类继承该类,并定义一个方法,在这个方法内使用super调用你想调用的那个方法。
    • 格式:
      new 类名或接口名(){
        重写方法;
      };  //注意分号
      //以上就是内部类的格式,其实这整体就相当于是new出来的一个对象
    • 例子:
new Thread(){
    public void run(){
	    System.out.println(getName());
	}
}.start();

说明:上面例子,如果去除.start()就是一个线程的匿名内部类【创建匿名内部类的同时,也会创建一个对象】所以上文中的.start()就相当于调用了Thread的start方法。

三、为什么使用内部类

1、封装性
作为一个类的编写者,我们很显然需要对这个类的使用访问者的访问权限做出一定的限制,我们需要将一些我们不愿意让别人看到的操作隐藏起来,如果我们的内部类不想轻易被任何人访问,可以选择使用private修饰内部类,这样我们就无法通过创建对象的方法来访问,想要访问只需要在外部类中定义一个public修饰的方法,间接调用。
2、实现多继承
我们之前的学习知道,java是不可以多继承的,一次只能继承一个类,我们学习接口的时候,有提到可以用接口来实现多继承的效果,即一个接口有多个实现,但是这里也是有一点弊端的,那就是,一旦实现一个接口就必须实现里面的所有方法,有时候就会出现一些累赘,但是使用内部类可以很好的解决这些问题。
3、实现回调功能
我们用通俗讲解就是说在Java中,通常就是编写一个接口,然后你来实现这个接口,然后把这个接口的一个对象作以参数的形式传到另一个程序方法中, 然后通过接口调用你的方法,匿名内部类就可以很好的展现了这一种回调功能。

public interface Demo {
	void demoMethod();
}
  
public class MyDemo{
	public test(Demo demo){
		System.out.println("test method");
    }
      
    public static void main(String[] args) {
	    MyDemo md = new MyDemo();
        //这里我们使用匿名内部类的方式将接口对象作为参数传递到test方法中去了
        md.test(new Demo){
	        public void demoMethod(){
	            System.out.println("具体实现接口")
            }
        }
    }
}

四、扩展

1、静态变量,常量:

  • 静态变量:在调用静态变量的时候会引起类加载
  • 常量:static final修饰的变量叫做常量,常量分为编译期常量和非编译期常量。
  • 编译期常量:在程序编译阶段【不需要加载类的字节码】,就可以确定常量的值
  • 非编译期常量:在程序运行阶段【需要加载类的字节码】,可以确定常量的值。如要定义随机数的常量,只能在加载类后才能确定随机数。

2、回调函数:

总结这位文章:https://blog.csdn.net/lc545126483/article/details/79954612

开始之前,先想象一个场景:幼稚园的小朋友刚刚学习了10以内的加法。

第1章:故事的缘起

幼师在黑板上写一个式子 “1 + 1 = ”,由小明同学来填空。

由于已经学习了10以内的加法,小明同学可以完全靠自己来计算这个题目,模拟该过程的代码如下:

public class Student{
	private String name;

	public Student(String name){
		this.name = name;
	}
	
	public void setName(String name){
		this.name = name;
	}

	public String getName(){
		return name;
	}
	
	private int calcAdd(int i,int j){//小明自己心算加法
		return i+j;
	}
	
	public void fillBlank(int i,int j){
		int result = calcAdd(i,j);
		System.out.println(name+"计算:"+i+"+"+j+"="+result);
	}
}

小明同学在填空(fillBlank)的时候,直接心算(calcAdd)了一下,得出结果是2,并将结果写在空格里。测试代码如下:

public class Test
	public static void main(String[] args) {
		Student s = new Student("小明");
		s.fillBlank(1, 1);
	}
}

运行结果如下:
  小明计算:1+1=2

该过程完全由Student类的实例对象单独完成,并未涉及回调机制。

第2章:幼师的找茬

课间,幼师突发奇想在黑板上写了“168 + 291 = ”让小明完成,然后回办公室了。

花擦!为什么所有老师都跟小明过不去啊?明明超纲了好不好!这时候小明同学明显不能再像上面那样靠心算来完成了,正在懵逼的时候,班上的小红同学递过来一个只能计算加法的计算器(奸商啊)!!!!而小明同学恰好知道怎么用计算器,于是通过计算器计算得到结果并完成了填空。

计算器的代码为:

public class Calculator {
	public int add(int i,int j){
		return i + j;
	}
}

此时,小明要使用计算器。修改Student类,添加使用计算器的方法:

private int useCalculator(int i,int j){//小明使用计算器加法
	return new Calculator().add(i, j);
}
	
public void fillBlankByCalculator(int i,int j){
	int result = useCalculator(i,j);
	System.out.println(name+"使用计算器计算:"+i+"+"+j+"="+result);
}

测试代码如下:

public class Test
	public static void main(String[] args) {
		Student s = new Student("小明");
		s.fillBlankByCalculator(168, 291);
	}
}

运行结果如下:
  小明使用计算器计算:168+291=459
  
该过程中仍未涉及到回调机制,但是部分小明的部分工作已经实现了转移,由计算器来协助实现。

第3章:幼师回来了

发现小明完成了3位数的加法,老师觉得小明很聪明,是个可塑之才。于是又在黑板上写下了“26549 + 16487 = ”,让小明上课之前完成填空,然后又回办公室了。

小明看着教室外面撒欢儿的小伙伴,不禁悲从中来。再不出去玩,这个课间就要废了啊!!!! 看着小红再一次递上来的计算器,小明心生一计:让小红代劳。

小明告诉小红题目是“26549 + 16487 = ”,然后指出填写结果的具体位置,然后就出去快乐的玩耍了。

这里,不把小红单独实现出来,而是把这个只能算加法的计算器和小红看成一个整体,一个会算结果还会填空的超级计算器。这个超级计算器需要传的参数是两个加数和要填空的位置,而这些内容需要小明提前告知,也就是小明要把自己的一部分方法暴漏给小红,最简单的方法就是把自己的引用和两个加数一块告诉小红。

因此,超级计算器的add方法应该包含两个操作数和小明自身的引用,代码如下:

public class SuperCalculator {
	public void add(int i,int j,Student xiaoming){
		int result = i + j;
		xiaoming.fillBlank(i, j,result);
	}
}

小明这边现在已经不需要心算,也不需要使用计算器了,因此只需要有一个方法可以向小红寻求帮助和一个在哪填答案的方法就行了,代码如下:

public class Student{
	private String name;

	public Student(String name){
		this.name = name;
	}
	
	public void setName(String name){
		this.name = name;
	}

	public String getName(){
		return name;
	}		
	public void callHelp(int i,int j){//求助小红
		new SuperCalculator().add(i, j, this);
	}
	
	public void fillBlank(int i,int j,int result){
		System.out.println(name+"求助小红计算:"+i+"+"+j+"="+result);
	}
}

测试代码如下:

public class Test
	public static void main(String[] args) {
		Student s = new Student("小明");
		s.callHelp(26549, 16487);
	}
}

运行结果如下:
  小明求助小红计算:26549+16487=43036

执行流程为:小明通过自身的callHelp方法调用了小红的add方法,在调用的时候将自身的引用(this)当做参数一并传入,小红在使用计算器得出结果之后,回调了小明的fillBlank方法,将结果填在了黑板上的空格里。

灯灯灯!到这里,回调功能就正式登场了,小明的fillBlank方法就是我们常说的回调函数。

第4章:门口的婆婆

幼稚园的门口有一个头发花白的老婆婆,每天风雨无阻在那里摆着地摊卖一些快过期的垃圾食品。由于年纪大了,脑子有些糊涂,经常算不清楚自己挣了多少钱。有一天,她无意间听到了小明跟小伙伴们吹嘘自己如何在小红的帮助下与幼师斗智斗勇。于是,婆婆决定找到小红牌超级计算器来做自己的小帮手,并提供一包卫龙辣条作为报酬。小红经不住诱惑,答应了。

回看一下上一章的代码,我们发现小红牌超级计算器的add方法需要的参数是两个整型变量和一个Student对象,但是老婆婆她不是学生,是个小商贩啊,这里肯定要做修改。这种情况下,我们很自然的会想到继承和多态。如果让小明这个学生和老婆婆这个小商贩从一个父类进行继承,那么我们只需要给小红牌超级计算器传入一个父类的引用就可以啦。

不过,实际使用中,考虑到java的单继承,以及不希望把自身太多东西暴漏给别人,这里使用从接口继承的方式配合内部类来做。

换句话说,小红希望以后继续向班里的小朋友们提供计算服务,同时还能向老婆婆提供算账服务,甚至以后能够拓展其他人的业务,于是她向所有的顾客约定了一个办法,用于统一的处理,也就是自己做完计算之后应该怎么做。这个统一的方法,小红做成了一个接口,提供给了大家(就相当于小红自己有计算机,但是不知道计算的结果要怎么办,她出了个表单,告诉客户你把表单重新填写下,她就可以直接按照表单完成工作。即顾客呼叫小红计算的时候,把填好的表单也一起给小红。),代码如下:

public interface IDoJob {
	public void fillBlank(int i,int j,int result);
}

因为灵感来自帮小明填空,因此小红保留了初心,把所有业务都当做填空(fillBlank)来做。

同时,小红修改了自己的计算器,使其可以同时处理不同的实现了IDoJob接口的人,代码如下:

public class SuperCalculator {
	public void add(int i,int j,IDoJob customer){
		int result = i + j;
		customer.fillBlank(i, j,result);
	}
}

小明和老婆婆拿到这个接口之后,只要实现了这个接口,就相当于按照统一的模式告诉小红得到结果之后的处理办法,按照之前说的使用内部类来做,代码如下:

小明的:

public class Student{
	private String name;

	public Student(String name){
		this.name = name;
	}
	
	public void setName(String name){
		this.name = name;
	}

	public String getName(){
		return name;
	}

	public class DoHomeWord implements IDoJob{
		public void fillBlank(int i,int j,int result){
			System.out.println("小红代"+name+"计算:"+i+"+"+j+"="+result);
		}
	}
	
	public void callHelp(int i,int j){//求助小红
		new SuperCalculator().add(i, j, new DoHomeWord());
	}
}

老婆婆的:

public class Seller {
	private String name;

	public Seller(String name){
		this.name = name;
	}
	
	public void setName(String name){
		this.name = name;
	}

	public String getName(){
		return name;
	}
	
	public class DoBusiness implements IDoJob{
		public void fillBlank(int i,int j,int result){
			System.out.println("小红代"+name+"计算:"+i+"+"+j+"="+result);
		}
	}	
	
	public void callHelp(int i,int j){//求助小红
		new SuperCalculator().add(i, j, new DoBusiness());
	}
}

测试代码如下:

public class Test
	public static void main(String[] args) {
		Student s = new Student("小明");
		s.callHelp(26549, 16487);
		Seller s1 = new Seller("婆婆");
		s1.callHelp(5435325, 41364343);
	}
}

运行结果如下:
  小红代小明计算:26549+16487=43036
  小红代婆婆计算:5435325+41364343=46799668
  
第5章:结局
可以很明显的看到,小红已经把这件事情当做一个事业来做了,看她给接口命的名字doJob就知道了。

我门只知道,后来小红的业务不断扩大,终于在幼稚园毕业之前,用挣到的钱买了人生的第一套房子。

完!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
set的lower_bound函数是按照从小到大的顺序返回大于或等于给定值的第一个元素的迭代器。如果你想要逆序,可以使用set的reverse_iterator来遍历集合并输出元素。你可以参考下面的代码示例: ```cpp #include <iostream> #include <set> int main() { std::set<int> myset = {21, 64, 17, 78, 49}; std::set<int>::reverse_iterator rit; std::cout << "myset contains: "; for (rit = myset.rbegin(); rit != myset.rend(); ++rit) { std::cout << *rit << ' '; } std::cout << '\n'; return 0; } ``` 这段代码首先创建了一个set并初始化了一些元素。然后使用reverse_iterator来逆序遍历set并输出元素。输出结果为:78 64 49 21 17。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [c++ set 的常见操作](https://blog.csdn.net/qq_45752450/article/details/107459429)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [杂记1:正向反向迭代器,不同stl容器的lower_bound的使用(set,map,vector,arr,pair),数字转字符串](https://blog.csdn.net/weixin_50816938/article/details/123942734)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值