首页 > 编程语言 >JAVA基础之八-方法变量作用域和编译器

JAVA基础之八-方法变量作用域和编译器

时间:2024-09-23 18:50:11浏览次数:10  
标签:JAVA 变量 作用域 class 编译器 方法 public

本文主要讨论方法中变量作用域。不涉及类属性变量、静态变量、线程变量共享等。

虽然知道某类变量的作用域非常重要,但是没有太多需要说的,因为许多东西是显而易见,不言自明。

 

在大部分情况下,或者在老一点版本中,java语法看起来都比较正常,或者说相对古典。

但是随着JAVA版本的迭代,已经愈发向JAVASCRIPT靠近了-随意。 也许JCP想把JAVA编程后端的JS。

但要只知道,用为JS过于随意邋遢,才会有TypeScript。

 

一、前言

闲话少说。

在经典场景中,每个变量/属性的作用域都是相对稳定/固定的,例如:

1.方法可以引用实例变量,类静态变量

2.方法内部定义的变量,其它方法,外部类无法看到

这些规则都很容易理解和遵守。

 

但是随着JAVA的语法变迁,其中一个方面,函数/方法中变量作用域也变得更加灵活(随意)。

大部分程序员,其实很少在一个方法内部定义类,反而是一些开源的组件用得多,不太明白这些意图。

如果还要让方法和方法内部类共享变量,那么就会让代码看起来古怪,且非常类似于JS的经典问题:闭包。

 

来看一下以下一段代码:

    @Override
    @Nullable
    public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
        Assert.notNull(sql, "SQL must not be null");
        Assert.notNull(rse, "ResultSetExtractor must not be null");
        if (logger.isDebugEnabled()) {
            logger.debug("Executing SQL query [" + sql + "]");
        }

        // Callback to execute the query.
        class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
            @Override
            @Nullable
            public T doInStatement(Statement stmt) throws SQLException {
                ResultSet rs = null;
                try {
                    rs = stmt.executeQuery(sql);
                    return rse.extractData(rs);
                }
                finally {
                    JdbcUtils.closeResultSet(rs);
                }
            }
            @Override
            public String getSql() {
                return sql;
            }
        }

        return execute(new QueryStatementCallback(), true);
    }

 

 这是Spring 6.2.0-SNAPSHOT 中 spring-jdbc的 org.springframework.jdbc.core.JdbcTemplate#query(String, ResultSetExtractor<T>) 方法的代码。

在这段代码中,spring直接定义一个内部类 QueryStatementCallback 。

妙的是QueryStatementCallback 直接利用了query中定义的参数rse

这种使用方式和js的对方法变量的使用(闭包)如出一辙。至少外表上是一样的。

 

到现在为止,我们知道这样一个事实:至少J17中可以这么写(其它没有研究)。

 

接下来,纯粹是出于技术人好奇,有几点:

1.这个对性能有什么影响

2.如何实现的?

3.如果性能没有好处,为什么要那么搞?

 

二、模仿例子

为了便于了解这个问题,创建了一个类似的例子,方便一些。

package study.base.varscope;

@FunctionalInterface
public interface ISum {
    long sum();
}

/**
 * 测试方法内的变量作用域,类似于js的闭包
 */
public class TestVarScope {

    public void test() {
         //创建一个有1000个元素的数组,每个元素都是介于1~999之间的随机数
        int[] arr = new int[1000];
        Random random = new Random();
        for (int i = 0; i < 1000; i++) {
            arr[i] = random.nextInt(1,1000);
        }
        class SumClass implements  ISum {
            @Override
            public long sum() {
                //累加 arr
                long total = 0;
                for (int i : arr) {
                    total += i;
                }
                return total;
            }
        }

        SumClass sc=new SumClass();
        long total=sc.sum();
        System.out.println(total);
    }

    public static void main(String[] args) {
        TestVarScope testVarScope = new TestVarScope();
        testVarScope.test();
    }

}

没有编译错误,可以执行。

所以奥妙一定在于编译器上,只要看看编译后的东西就明白了。

class文件

 可以看到含$是方法内部类.

再看看 TestVarScope$1SumClass.class的内容

先看反编译内容

 

再看私有成员

 

结合以上两张图,可以比较肯定地推测出如下:

内部类SumClass被改写了:

a.SumClass新增了一个私有final的数组  val$arr,用于存储上级对象的属性(数组)

b.SumClass有一个带两个参数的构造函数,前后分别是上级类的实例,需要接受的数组

c.sum()方法,访问val$arr,以便进行汇总

 

再用三方工具(jadx)可以查看出:

和预想的一致。

注:如果用idea(2024.2.1)的默认反编译工具,会得到错误的结果,所以用那个来看源码有点危险,倒是eclipse的做得不错。

以下是eclipse的class File viewer:

 

 

针对前一章节的问题,可以如下作答:

1.这个对性能有什么影响

基本没有什么影响。

2.如何实现的?

如上,通过编译器改写内部类和上级类方法来完成,一切功劳在于编译器.

3.如果性能没有好处,为什么要那么搞?

方便,或者偷懒而已。

三、小结

java的语言越来越随意,通过编译器的功能(或者所谓的语法糖),可以实现类似js那样随意的效果。

就本文所阐述的问题,本质上并不是说内部类可以访问上级的属性,而是一种错觉,在语法上让我们以为可以访问。

 

就我个人而言,并不喜欢这些隐藏了实现的编码方式,大概因为开始编程的时候,学习的都是古典语法。

现在的一些新的东西,虽然某些情况下会工程上的某些好处,但是反作用也是明显的:复杂化编译器;有可能培养不是很

好的编码习惯。

 

关于方法变量的作用域问题,目前暂时没有其它可以说的。

 

标签:JAVA,变量,作用域,class,编译器,方法,public
From: https://www.cnblogs.com/lzfhope/p/18412722

相关文章

  • JavaScript 学习路线图
    基础阶段主要内容:掌握JavaScript的基本语法,如变量、数据类型(字符串、数字、布尔、对象、数组等)、运算符等。理解程序的控制流,包括条件语句(如if-else)、循环语句(如for、while)。学会使用函数来封装代码,理解函数的参数、返回值以及作用域等概念。学习网站:W3Schools:https://w......
  • java如何调用外部程序
    java如何调用外部程序2017-03-1520:50179人阅读评论(0)收藏举报分类:Java应用(26)版权声明:本文为博主原创文章,未经博主允许不得转载。引言;有时候有些项目需求,直接使用Java编写比较麻烦,所有我们可能使用其他语言编写的程序来实现。那么我们如何在java中......
  • oracle数据类型和对应的java类型
    [转]oracle数据类型和对应的java类型 地址:http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/java.102/B19275-03/datacc.htm#BHCJBJCCSQL数据类型JDBC类型代码标准的Java类型Oracle扩展的Java类型 1.0标准的JDBC类型:  CHARjava.......
  • java 如何实现判断一个对象所有的属性是否为空
     能适配所有类型的:Personperson=newPerson();person.setId(0);//---truePersonperson=null;//---truePersonperson=newPerson();person.setName("xxx");//---falsePersonperson=newPers......
  • Javascript调试命令——你只会Console.log() ?
    Javascript调试命令——你只会Console.log()?https://segmentfault.com/a/1190000012957199Console对象提供对浏览器控制台的接入(如:Firefox的WebConsole)。不同浏览器上它的工作方式是不一样的,但这里会介绍一些大都会提供的接口特性。Console对象可以在任何全局对象中访问,......
  • JAVA Response 返回值再拿
    在Java中,当你需要从Response对象中获取返回值时,可以使用以下方法:首先,确保你已经导入了相关的库。例如,如果你使用的是java.net.HttpURLConnection,则需要导入以下包:importjava.io.BufferedReader;importjava.io.InputStreamReader;importjava.net.HttpURLConnection;importjav......
  • springboot基于Java的高校学生食堂系统(源码+vue+部署文档+前后端分离等)
    收藏关注不迷路!!......
  • 【解决方案】Java 互联网项目中常见的 Redis 缓存应用场景
    一、常见key-value首先介绍的是项目开发中常见的一些String类型的key-value结构场景,如:使用jsonStr结构存储的用户登录信息,包括:手机号、token、唯一uuid、昵称等;jsonStr结构某个热门商品的信息,包括:商品名称、商品唯一id、所属商家、价格等;String类型的、......
  • 【计算机专业毕设最新Java必过毕设选题2025】基于微信小程序的美食推荐小程序(源码+万
    作品简介 Hi,各位同学好呀!今天向大家分享一个最新完成的高质量毕业设计项目作品基于uniapp+springboot的XXX微信小程序项目评分(最低0分,满分5分)难度系数:3分工作量:5分创新点:3分界面美化:5分使用技术前端:uniapp后端:springboot数据库:MySql服务器:apache-tom......
  • [AI回答]Java中Long和long的区别,为什么在Springboot项目中一般使用Long
    Long和long的区别在Java中,Long和long是两个不同的概念,它们分别代表不同的数据类型:long:这是一个基本数据类型(primitivetype)。它用于存储64位带符号的整数。它的取值范围是-9,223,372,036,854,775,808到9,223,372,036,854,775,807。在使用long类型变量时,......