Java的强大功能

概述

java中访问权限、注解。你自以为自己都滚瓜烂熟了,但你可能认为它们都是小儿科,记住了简单用法就行。真到了你写框架的时候,什么时候用public,什么时候用private,什么时候又用protect,就不是很清晰,随便用一个,框架也是照常运行。但你的框架很容易被攻击,或者缺少功能使用。因为不该暴露的暴露,该暴露的又没有暴露。注解又是如何被读取,怎么使用其带的值。

访问权限控制

访问权限,首先应该明白在哪访问(是否同包),通过谁访问(当前类对象、子孙类对象)、访问什么(方法、属性)。在哪访问应该优先考虑

类访问

类修饰符public、abstract、final、缺省(没有修饰符)。

  • public修饰:允许任何地方,创建该类的实例。至于属性方法的访问,要看它门头上的修饰符。
  • abstract抽象类:允许该类有未实现的方法。不涉及访问权限。
  • 默认:包外不可见,本包内,其他类可以创建它的实例,本包内继承。
  • final类:包外不可见,本包内,其他类可以创建它的实例。不能被继承。

属性或方法访问权限

  • public公开的,在任何地点,任何访问者都可以访问
  • protected ,本包下,都可以访问。
  • 默认,本包下,自己的对象才能访问。
  • private ,本类中,自己对象才能访问。
作用域当前类对象同一个包(packege)子孙类对象其他包
public
protectedX
默认XX
privateXXX

列表中每次应该取对象(本类、子孙类对象)状态和包(本包、外包)状态做与运算。

可变参数

Object... args必须放在方法参数最后,如果放前面,后面的参数也会被认为是可变参数。调用Spring的getBean一定要分清,调用的四个方法中的哪一种,因为有可变长度的构造函数参数,一不小心就传成了就会出错。
factory.getBean("firstBean",null,"s",null)本次传参混淆了doGetBean的四参数,这样实际调用的是getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args),null和s被认为是可变长度参数。这样构造器覆盖构造参数时,由于传入的和构造器本身的参数长度不一致,会报错Error creating bean with name 'firstBean' defined in class path resource [beans.xml]: Could not resolve matching constructor (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)。Idea默认集合中null不显示,怎么都发现不了为什么参数长度会是2。
在这里插入图片描述

基于嵌套的访问控制

Nest-Based Access Control是JDK11新引入的访问控制规则,在java11之前,classfile用InnerClasses和EnclosingMethod两种属性来帮助编译器确认源码的嵌套关系,每一个嵌套的类型会编译到自己的class文件中,在使用上述属性来连接其他class文件。这些属性对于jvm确定嵌套关系上已经足够了,但是它们不直接适用于访问控制,并且和java语言绑定的太紧了。为了提供一种更大的,更广泛的,不仅仅是java语言的嵌套类型,并且补足访问控制检测的不足,引入了两个新的class文件属性。一个是嵌套宿主NestHost(也叫top-level class),将嵌套的宿主记录到当前类或接口声明中。另一个是嵌套成员NestMembers,记录了被授权可以声明在嵌套宿主类或接着口中的类或接口。class文件结构的属性表不能同时包含NestMembers属性和NestHost属性。这个规则避免了一个嵌套宿主在不同的嵌套中声明成员。

当且仅当以下条件为真时,一个私有成员字段或方法R是可以被类或接口D访问的:

R是声明在另一个类或接口C中,同时 C 和 D 是嵌套伙伴。
由于C和D是嵌套同伴,那么他们一定有同一个嵌套宿主类,在这里是Test。如果C在自己的嵌套宿主的属性中可以列举出D,那么C类就会被D 断言为自己的嵌套成员。如果D也可以在自己的NestMembers 属性中列举出C,那么这个嵌套同伴关系就是有效的。D是自己的隐性嵌套成员。

public class NestHost {

    class  NestMemberOne {
        private String R = "get到我了";
    };
    class NestMemberAnother {
        private void privateMethod(){
            System.out.println( (new NestMemberOne()).R);
        }
    };
    public Object Ap(){
        class  LocalInnerClass {}
        return new LocalInnerClass();
    }

    public static void main(String[] args) {
		/**1.非宿主类必须通过宿主类对象创建宿主类的非静态内部类对象(
		new NestHost().new NestMemberOne())。创建静态内部类对
		象:NestHost.NestMemberOne mo =new NestHost.NestMemberOne()
		2.宿主类可以提供非静态方法创建内部类对象(new NestMemberOne())。如
		果是提供的是静态方法,创建非静态内部类对象,必须使用宿主类的对象来创
		建内部类对象。如下:
		*/
        NestMemberOne mo = new NestHost().new NestMemberOne();
        for(Class nb:mo.getClass().getNestMembers()) System.out.println(nb);
        /**
        NestMemberOne和NestMemberAnother是嵌套伙伴关系,所有嵌套成员属性
        一致
    class com.bean.anotation.ownanotation.NestHost
	class com.bean.anotation.ownanotation.NestHost$NestMemberAnother
	class com.bean.anotation.ownanotation.NestHost$NestMemberOne
        */
        Class cl = (new NestHost()).Ap().getClass();
        System.out.print("Enclosing Method :");
        System.out.println(cl.getEnclosingMethod());
        /**
        当一个类为局部内部类或匿名嵌套类时才有EnclosingMethod属性,返回它的
        外围方法。
        Enclosing Method :public java.lang.Object 	   
        com.bean.anotation.ownanotation.NestHost.Ap()
		*/
		
    }
}

接口 Interface

接口中可以有final属性变量,默认方法,静态方法。

public interface IntefaceMmethod {
	/**会默认public static final类型,它调用了静态方法来赋值。
	* IntefaceMmethod在JVM初始化调用方法给final的HPQ赋值。外界通过接口
	* 调用test(String...args)。所以测试类启动的时候会走两次。不要惊讶。
	* 这样做的好处:我需要一个final常量,它有生成规则,而且这个规则也会给
	* 变量使用(我们还要生成其他IntefaceMmethod对象,就可以调用test方法
	* test方法中可以根据参数不同生成不同的IntefaceMmethod对象)。
	*/
    IntefaceMmethod  HPQ = test("dalas");

    static IntefaceMmethod test(String...args){
        System.out.println(args);
        return null;
    }
}

注解Anotation

这是注解接口,但我们没见过它被extends,我们自定义的接口又是怎么继承它的。其中的@interface是一个关键字,这个关键字声明隐含了一个信息:这个接口必须继承java.lang.annotation.Annotation接口,也就是说注解的本质还是接口。JDK动态代理对注解进行代理,这样注解就有了具体实现类。
在这里插入图片描述

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface  Table {
    String value();
}
//注解的class文件反编译之后
public interface Table extends java.lang.annotation.Annotation {
  //注解元素成为抽象方法
  public abstract java.lang.String value();
}

Annotation解析

运行时Annotation解析

运行时 Annotation 指 @Retention 为,RetentionPolicy.RUNTIME的 Annotation,可调用Class的常用 API 解析。其实就是通过反射

	getAnnotation(XXXAnnotation.class);
	getAnnotations();
	isAnnotationPresent(XXXAnnotation.class);

编译时Annotation解析

编译时Annotation指@Retention为RetentionPolicy.CLASS 的Annotation,由apt(Annotation Processing Tool) 自动解析。

  • 自定义类继承自AbstractProcessor
  • 重写其中的 process 方法
    实际是 apt(Annotation Processing Tool) 在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理
public class TableProcessor extends AbstractProcessor {
     @Override
    public boolean process(Set<?extends TypeElement> annotations, RoundEnvironment env) {
        HashMap<String, String> map = new HashMap<String, String>();
        for (TypeElement te : annotations) {
            for (Element element : env.getElementsAnnotatedWith(te)) {
                Table table = element.getAnnotation(Table.class);
                map.put(element.getEnclosingElement().toString(), table.value());
            }
        }
        return false;
    }
}

有篇博文对APT的运用很好的实践

@Repeatable

java8新增元注解,就是一语法糖。让一个注解可以在一个地方重复使用多次。JDK1.8以前:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileType {
	String fileSuffix() default ".java";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileTypes {
	FileType[] value();
}

@FileTypes({@FileType(".java"),@FileType(".css"),@FileType(".html"),@FileType(".js")})
public void readFile(){
         
        try {
            FileType[] fileTypes= this.getClass().getMethod("readFile").getAnnotationsByType(FileType.class);
            System.out.println("将从如下后缀名的文件中查找文件内容");
            for (FileType fileType : fileTypes) {
                System.out.println(fileType.value());
            }
        } catch (NoSuchMethodException | SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

这样解决了需求,但代码不宜读。用@Repeatable

```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FileTypes.class)
public @interface FileType {
	String fileSuffix() default ".java";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileTypes {
	FileType[] value();
}

@FileType(".java")
@FileType(".css")
@FileType(".html")
@FileType(".js")
public void readFile(){
         
        try {
            FileType[] fileTypes= this.getClass().getMethod("readFile").getAnnotationsByType(FileType.class);
            System.out.println("将从如下后缀名的文件中查找文件内容");
            for (FileType fileType : fileTypes) {
                System.out.println(fileType.value());
            }
        } catch (NoSuchMethodException | SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

lamda表达式

@FunctionalInterface

有了这个注解注释的接口就可以完成lamda功能。例如执行注解value属性的策略API

@FunctionalInterface
interface ValueExtractor {
	/**
	 * Extract the annotation attribute represented by the supplied {@link Method}
	 * from the supplied source {@link Object}.
	 */
	@Nullable
	Object extract(Method attribute, @Nullable Object object);

}

ReflectionUtils::invokeMethod由extract就知道该调用哪个invokeMethod方法

JEP

这里的JEP是JDK增强计划书(JDK增强功能索引中涉及到编码层面的功能),详细内容查看官方JEP

EdDSA

JEP 339:Edwards-Curve Digital Signature Algorithm (EdDSA)
爱德华兹曲线数字签名算法(EdDSA),和Schnorr,secp256k1, sm2 最大的区别在于没有使用随机数,这样产生的签名结果是确定性的,即每次对同一消息签名结果相同。一般说来随机数是安全措施中重要的一种方法,但是随机数的产生也是安全隐患,索尼的PS3 密钥泄露事件,就是随机数产生的问题导致的。签名过程中出现了这个相同的随机数 r,那么私钥将很容易被计算出来,造成泄露,当前很多兴起的区块链项目都在使用。

Sealed Classes(密封类)

JEP 360: Sealed Classes (Preview)
在 Java 语言中,代码的重用是通过类的继承实现的:多个子类可以继承同一个超类(并重用超类的方法)。但是重用代码并不是类层次结构的唯一目的,有时类层次结构仅仅是对某个领域的建模。以这种方式使用类层次结构时,限制子类集合可以简化建模。
比如在图形库中,Shape 类的开发者可能只希望有限个数的类扩展 Shape 类,开发者并不想为未知子类编写防御代码。以前的 Java 并不会限制 Shape 类的扩展属性,Shape 类可以拥有任意数量的子类。在封闭类(Sealed Classes)中,类层次结构是封闭的,但是代码仍然可以在有限范围内重用。

public sealed class Shape permits Circle, Rectangle, Square {
	//类中内容
}
final class Circle  extends Shape {}
//在子类数量很少时,在密封类的源文件中声明子类会很方便。密封类可以省略 permits子句
public  sealed class Shape
        {}
final  class  Circle extends Shape{
}

这个封闭接口的声明表明,只有 permits 关键字后面的类,包括 Circle, Rectangle, Square,能够实现这个接口。permits
关键字限定了封闭类或者封闭接口的可扩展范围。类修饰符 sealed 和 permits 关键字一起,使得封闭类和封闭接口只能在一个限定的、封闭的范围内,获得扩展性。

  1. 许可类可以声明为终极类(final),从而关闭扩展性;
  2. 许可类可以声明为封闭类(sealed),从而延续受限制的扩展性;
  3. 许可类可以声明为解封类(non-sealed), 从而支持不受限制的扩展性。

虚拟机层面支持

//InstanceKlass有成员变量
Array<jushort>* _permitted_subclasses;

尽管sealed关键字是类修饰符,但是ClassFile中并没有 ACC_SEALED 标志。密封类的Class文件有PermittedSubclasses属性(JVM规范中JDK15、16预览),该属性隐式指示密封修饰符,并显式指定允许的子类。
在这里插入图片描述

PermittedSubclasses_attribute {
//指向常量池中代表PermittedSubclasses字符串的索引
    u2 attribute_name_index;
//属性长度,不包括最初的六个字节,也就是记录的是number_of_classes和classes长度
    u4 attribute_length;
//封闭类允许子类个数,图中是2。和下一个属性classes中类的个数一致
    u2 number_of_classes;
    u2 classes[number_of_classes];
}

Records

JEP 359:Records (Preview)
数据载体Records是Java的一种新的类型。同枚举一样,records也是对类的一种限制。records放弃了类通常享有的特性:将API和表示解耦。但是作为回报,records使数据类变得非常简洁。不能用abstract修饰Record类,可以用final修饰Record类,但是这没有必要,因为Record类本身就是final的。成员Record类,还有本地Record类,本身就是static的,也可以用static修饰,但是没有必要。

声明一个Records

  1. 声明顶级类:单独作为一个java文件
public record Student(long id, String name, int age) {}
  1. 作为一个成员内部类
public class ClassMates{
    public record Student(long id, String name, int age) {}
}
  1. 作为局部内部类
public class ClassMates{
	public void callTheRoll() {
    	record Student(long id, String name, int age) {}
}

声明内部元素

  1. Record类属性必须在头部声明,在Record类体只能声明静态属性:
public record Student(long id, String name, int age) {
	/**不是传参的,只能是字段,不包含在此类的Record属性中。作为参数传递进来
	* 的会出现在Record属性,并且是final类型字段
	*/
    static long alaisId;
}
  1. Record类体可以声明方法(成员方法和静态方法都可以),和一般类一样。但是不能声明abstract或者native方法
public record Student(long id, String name, int age) {
    public void test(){}
    public static void test2(){}
}
  1. 类体也不能包含实例初始化块
public record Student(long id, String name, int age) {
 //不能声明实列初始化块,但可以是静态块(初始化出来的实例都是它的成员变量,成员变量应该是static的,所以不能是非静态初始化块或者实例)
   {
   ...
   }
}

类体中只能声明静态字段,静态块。可以安全的发布对象,因为这些静态属性在类加载的准备阶段就已经初始化。如果非静态需要等到类初始化的时候才能被初始化。可能会影响对象的发布和使用。同时也是严格区分了Record类和普通类。否则没有这些约束,和我们声明POJO对象没有任何区别。静态字段可以作为一个Record通用的属性,比如状态控制等。

protected List<JCTree> classOrInterfaceOrRecordBodyDeclaration(Name className, boolean isInterface, boolean isRecord) {
...
//标记
(token.kind == LBRACE &&
                       (mods.flags & Flags.StandardFlags & ~Flags.STATIC) == 0 &&
                       mods.annotations.isEmpty()) {
                if (isInterface) {
                    log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.InitializerNotAllowed);
                } else if (isRecord && (mods.flags & Flags.STATIC) == 0) {
                    log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.InstanceInitializerNotAllowedInRecords);
                }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值