再说final变量

从jdk1.0到今天,JAVA技术经过十余年的发展,技术上已经发生了巨大的变化.但final变量的定义从它
诞生那天起,就没有发生任何变化,也就是这十多年它就一直表示它原来的意思.

但遗憾的是,经过十多年仍然有90%的人没有理解它的真实含义,也没有一篇文章,包括我所见到的所有介绍
JAVA的书籍(包括TKJ)都没有说清楚,我相信肯定有些作者是理解的,但没有一个作者向读者说清楚.而中国网友
大多数人被一篇胡说八道的<<浅谈Java中final,finalized,finally>>的文章跑过马.(脸红一下:当初我也是).

final变量的定义本身并不复杂,就是变量一经初始化就不能再指向其它对象.在c++中它是一个const指针,而
不是指向const变量的指针,const指针的意思是说它只能一直指向初始化时的那个地址.但那个地址中对象本身
是可以修改的.而指向const变量的指针是说所指对象本身是不能修改的.

如:final StringBuffer sb = new StringBuffer("Axman");
sb = new StringBuffer("Sager");//错误,sb不能再指向其它对象.
sb.append(" was changed!"); //sb指向的地象本身可以修改.

先说final变量初始化:

很多文章都这么说:其初始化可以在两个地方,一是其定义处,二是在构造函数中,两者只能选其一。
胡说八道!
final变量可以在任何可以被始化的地方被始化,但只能被初始化一次.一旦被初始化后就不能再次赋
值(重新指向其它对象),作为成员变量一定要显式初始化,而作为临时变量则可以只定义不初始化(当然也不能引用)
即使是作为一个类中的成员变量,也还可以在初始化块中初始化,所以"其初始化可以在两个地方,一是其定义处,
二是在构造函数中,两者只能选其一"是错误的.


作为成员变量时,final字段可以设计不变类,是不变类的一个必要条件但不是一个充要条件.至少可以保证字段不
会以setXXX()这样的方式来改变.但无法保证字段本身不被修改(除非字段本身也是不变类);

对于方法参数的final变量:
对于方法参数的变量定义为final,90%以上的文章都说"当你在方法中不需要改变作为参数的对象变量时,明确使
用final进行声明,会防止你无意的修改而影响到调用方法外的变量。"
胡说八道!

我不知道这个修改是说重新赋值还是修改对象本身,但无论是哪种情况,上面的说法都是错误的.
如果是说重新赋值,那么:
 public static void test(int[] x){
  x = new int[]{1,2,3};
 }

    int[] out = new int[]{4,5,6};
    test(out);
    System.out.println(out[0]);
    System.out.println(out[1]);
    System.out.println(out[2]);
    调用test(out);无论如何也不会影响到外面变量out.你加不加final根本没有意义.final只会强迫方法内
多声明一个变量名而已,即把x = new int[]{1,2,3};改成int y = new int[]{1,2,3}; 其它没有任何实际意义.
    如果说是修改对象本身:
 public static void test(final int[] x){
  x[0] = 100;
 }
    int[] out = new int[]{4,5,6};
    test(out);
    System.out.println(out[0]);
难道你用final修饰就不可以修改了?所以说对于方法参数中final是为了不影响调用方法外的变量那是胡说八道的.

那我们到底为什么要对参数加上final?其实对方法参数加final和方法内变量加上final的作用是相同的,即为了将它们
传给内部类时保证调用的一致性:

abstract class ABSClass{
 public abstract void m();
}

现在我们来看,如果我要实现一个在一个方法中匿名调用ABSClass.应该:
 public static void test(final String s){
     //或final String s = "axman";
  ABSClass c = new ABSClass(){
   public void m(){
      int x = s.hashCode();

      System.out.println(x);

   }
  };
  //其它代码.
 }
 实际上这段代码被编译成:
  public static void test(final String s){
     //或final String s = "axman";
  ABSClass c = new ABSClass(s){
   private final String s;
   public ABSClass(String s){
 this.s = s;  
   }
   public void m(){
      int x = s.hashCode();

      System.out.println(x);

   }
  };
  //其它代码.
 }
即外部类的变量被作为构造方法的参数传给了内部类的私有成员.
假如没有final,那么:
 public static void test(String s){
     //或String s = "axman";
  ABSClass c = new ABSClass(){
   public void m(){
     s = "other";
   }
  };
  System.out.println(s);
 }
 就会编译成:
  public static void test(String s){
     //或String s = "axman";
  ABSClass c = new ABSClass(s){
   private final String s;
   public ABSClass(String s){
 this.s = s;  
   }
   public void m(){
  s = "other";
   }
  };
  System.out.println(s);
 }
 内部类的s重新指向"other"并不影响test的参数或外部定义的那个s.而你看到的
  public static void test(String s){
     //或String s = "axman";
  ABSClass c = new ABSClass(){
   public void m(){
     s = "other";
   }
  };
  System.out.println(s);
 }
 在语法上是一个s,在内部类中被改变了,但结果打印的出来的你认为是同一的s却还是原来的"axman",
 你能接收这样的结果吗?
 所以final从语法上约束了两个不同变量的一致性.

<think> 我们已经在之前的代码中实现了点击TextField时检测登录状态的逻辑。但是,用户反映点击TextField时无法检测登录状态。我们需要确保以下几点: 1. 在点击TextField时,触发登录状态检查。 2. 如果未登录,弹出登录提示弹窗。 3. 如果已登录,则正常使用TextField。 根据用户提供的引用,我们需要注意: - 使用SharedPreferences来存储和获取登录状态(我们已经在使用)。 - 在点击处理中,我们使用了`_checkLoginStatus`方法来更新`_isLoggedIn`变量,然后根据该变量决定是否弹窗。 问题可能出现在: 1. `_checkLoginStatus`方法可能没有正确更新状态。 2. 弹窗可能被其他因素阻止。 我们回顾代码: 在`_handleTextFieldTap`方法中,我们首先调用`_checkLoginStatus`,然后根据`_isLoggedIn`决定是否弹窗。但是,由于`_checkLoginStatus`是异步的,而我们在调用后立即检查`_isLoggedIn`,此时可能还没有完成状态的更新。 解决方案: 我们需要确保在`_checkLoginStatus`完成后(即异步操作完成)再检查登录状态并决定是否弹窗。 修改`_handleTextFieldTap`方法: 使用`await`等待`_checkLoginStatus`完成。 原代码: ```dart Future<void> _handleTextFieldTap() async { await _checkLoginStatus(); if (!_isLoggedIn) { _showLoginDialog(context); } } ``` 这个逻辑看起来是正确的,因为我们使用了`await`等待`_checkLoginStatus`完成。那么问题可能出在`_checkLoginStatus`方法中。 原`_checkLoginStatus`方法: ```dart Future<void> _checkLoginStatus() async { try { final prefs = await SharedPreferences.getInstance(); setState(() { _isLoggedIn = prefs.getBool('is_logged_in') ?? false; }); } catch (e) { print('登录状态检测错误: $e'); } } ``` 这个方法通过`setState`更新`_isLoggedIn`,但是由于`setState`会触发重建,而重建后状态会更新,所以我们在`_handleTextFieldTap`中使用的`_isLoggedIn`变量可能并不是最新的(因为`setState`是异步的,我们在`await`之后可能还没有立即更新状态)。 因此,我们需要在`_checkLoginStatus`方法中返回登录状态,而不是依赖`setState`来更新一个状态变量,因为状态更新是异步的,我们无法立即在`_handleTextFieldTap`中获取到。 我们可以这样修改: 1. 修改`_checkLoginStatus`方法,使其返回一个`Future<bool>`,表示是否登录。 2. 在`_handleTextFieldTap`中使用这个返回值。 同时,我们保留原来的`_isLoggedIn`状态变量用于UI显示(例如输入框的提示文本和边框颜色),但是点击事件的处理我们使用直接返回的登录状态。 具体修改如下: 步骤1:修改`_checkLoginStatus`方法,返回登录状态 ```dart Future<bool> _checkLoginStatus() async { try { final prefs = await SharedPreferences.getInstance(); bool loggedIn = prefs.getBool('is_logged_in') ?? false; // 同时更新状态变量,用于UI setState(() { _isLoggedIn = loggedIn; }); return loggedIn; } catch (e) { print('登录状态检测错误: $e'); return false; } } ``` 步骤2:修改`_handleTextFieldTap`方法,使用返回的登录状态 ```dart Future<void> _handleTextFieldTap() async { bool isLoggedIn = await _checkLoginStatus(); if (!isLoggedIn) { _showLoginDialog(context); } } ``` 这样,我们在点击TextField时,就能确保使用最新的登录状态来判断。 另外,我们还需要注意,在登录页面登录成功后,我们返回时应该刷新`InputSaverContainer`的登录状态。我们在`InputSaverContainer`的`initState`中已经调用了`_checkLoginStatus`,但是当从登录页面返回时,我们需要再次检查登录状态。 我们可以使用`ModalRoute.of(context)!.addScopedWillPopCallback`或者更好的方式是在`SecondPage`中传递一个回调,但是这里为了简单,我们可以在`InputSaverContainer`中使用`Navigator`的返回结果来触发。 在`InputSaverContainer`中,我们跳转到登录页面时使用了`await`,并在返回后再次调用`_checkLoginStatus`,这个逻辑已经存在(在`_showLoginDialog`中): ```dart onPressed: () async { Navigator.pop(context); // 跳转到登录页面 final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => LoginPage()), ); // 登录返回后刷新状态 if (result == true) _checkLoginStatus(); }, ``` 因此,我们只需要确保登录页面在登录成功时返回true即可。 在登录页面的登录按钮回调中: ```dart onPressed: () async { // 模拟登录逻辑 final prefs = await SharedPreferences.getInstance(); await prefs.setBool('is_logged_in', true); // 返回登录成功状态 Navigator.pop(context, true); }, ``` 这样,当用户登录成功返回时,会触发`_checkLoginStatus`,从而更新`_isLoggedIn`状态。 完整修改后的`InputSaverContainer`相关代码: 注意:为了简洁,我只展示修改的部分,其他部分保持不变。 修改后的`_InputSaverContainerState`类中: ```dart class _InputSaverContainerState extends State<InputSaverContainer> { // ... 其他代码不变 // 修改_checkLoginStatus方法 Future<bool> _checkLoginStatus() async { try { final prefs = await SharedPreferences.getInstance(); bool loggedIn = prefs.getBool('is_logged_in') ?? false; setState(() { _isLoggedIn = loggedIn; }); return loggedIn; } catch (e) { print('登录状态检测错误: $e'); return false; } } // 修改_handleTextFieldTap方法 Future<void> _handleTextFieldTap() async { bool isLoggedIn = await _checkLoginStatus(); if (!isLoggedIn) { _showLoginDialog(context); } } // 修改_showLoginDialog方法中的立即登录按钮 void _showLoginDialog(BuildContext context) { showDialog( context: context, builder: (context) => AlertDialog( // ... 其他不变 actions: [ // ... 稍后再说按钮不变 TextButton( onPressed: () async { Navigator.pop(context); // 关闭弹窗 // 跳转到登录页面 final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => LoginPage()), ); // 登录返回后刷新状态 if (result == true) { await _checkLoginStatus(); // 等待状态更新 } }, child: const Text('立即登录'), ), ], ), ); } // ... 其他代码不变 } ``` 这样修改后,点击TextField时,会先获取最新的登录状态(包括更新UI的状态变量),然后根据返回的登录状态决定是否弹窗。这样就能解决点击TextField无法检测登录状态的问题。 另外,为了确保在登录页面返回后,TextField点击事件能够正确响应(即如果登录成功,则不再弹窗),我们已经在登录成功后更新了状态,所以没有问题。 如果问题仍然存在,请检查SharedPreferences中存储的键是否正确('is_logged_in'),以及登录页面是否成功设置了这个值为true。 下面提供完整的`InputSaverContainer`代码(包括未修改的部分),以便于理解: 注意:由于整个代码较长,我只写`InputSaverContainer`部分,并且只修改了相关方法。 完整`InputSaverContainer`代码(修改后): </think> 在 Flutter 中实现点击 TextField 时检测登录状态的完整解决方案如下。我已修复了点击检测的逻辑问题,确保在点击输入框时能正确检测登录状态: ```dart import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; class SecondPage extends StatefulWidget { const SecondPage({Key? key}) : super(key: key); @override State<SecondPage> createState() => _SecondPageState(); } class _SecondPageState extends State<SecondPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("积极日记", style: TextStyle( color: Colors.black, fontFamily: 'Comic Sans MS', fontSize: 20.0, fontWeight: FontWeight.w500, )), centerTitle: true, backgroundColor: Colors.transparent, elevation: 0, flexibleSpace: Container( decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/background.jpg'), fit: BoxFit.cover, ), ), ), leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: () => Navigator.pop(context), ), ), body: Container( decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/background.jpg'), fit: BoxFit.cover, ), ), child: InputSaverContainer(), ), ); } } class InputSaverContainer extends StatefulWidget { const InputSaverContainer({super.key}); @override _InputSaverContainerState createState() => _InputSaverContainerState(); } class _InputSaverContainerState extends State<InputSaverContainer> { final TextEditingController _controller = TextEditingController(); bool _isLoggedIn = false; List<String> _savedTexts = []; // 关键修改:增强登录状态检测方法 Future<bool> _checkLoginStatus() async { try { final prefs = await SharedPreferences.getInstance(); final loggedIn = prefs.getBool('is_logged_in') ?? false; // 更新状态并返回结果 setState(() { _isLoggedIn = loggedIn; }); return loggedIn; } catch (e) { debugPrint('登录状态检测错误: $e'); return false; } } // 关键修改:优化点击处理方法 Future<void> _handleTextFieldTap() async { final isLoggedIn = await _checkLoginStatus(); if (!isLoggedIn) { // 使用延迟确保在键盘弹出前显示弹窗 Future.delayed(Duration.zero, () { _showLoginDialog(context); }); } } // 显示登录提示弹窗 void _showLoginDialog(BuildContext context) { showDialog( context: context, barrierDismissible: false, // 点击外部不关闭 builder: (context) => AlertDialog( title: const Text('需要登录'), content: const Text('请先登录才能记录日记内容'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), TextButton( onPressed: () async { Navigator.pop(context); // 关闭弹窗 // 跳转到登录页面 final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => const LoginPage()), ); // 登录返回后刷新状态 if (result == true) await _checkLoginStatus(); }, child: const Text('去登录'), ), ], ), ); } // 保存输入内容 Future<void> _saveInput() async { if (_controller.text.isEmpty) return; final prefs = await SharedPreferences.getInstance(); // 检查登录状态 if (!_isLoggedIn) { _showLoginDialog(context); return; } setState(() { _savedTexts.insert(0, _controller.text); prefs.setStringList('saved_items', _savedTexts); }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("保存成功!"), duration: Duration(seconds: 1), ) ); _controller.clear(); } // 加载保存的内容 Future<void> _loadInputs() async { final prefs = await SharedPreferences.getInstance(); setState(() { _savedTexts = prefs.getStringList('saved_items') ?? []; }); } // 删除指定项 Future<void> _deleteItem(int index) async { final prefs = await SharedPreferences.getInstance(); setState(() { _savedTexts.removeAt(index); prefs.setStringList('saved_items', _savedTexts); }); } @override void initState() { super.initState(); _loadInputs(); _checkLoginStatus(); // 初始化时检查登录状态 } @override Widget build(BuildContext context) { return SizedBox( height: 620, width: 500, child: Container( padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Colors.transparent, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 修改后的TextField TextField( controller: _controller, maxLines: null, minLines: 15, keyboardType: TextInputType.multiline, textInputAction: TextInputAction.newline, decoration: InputDecoration( hintText: _isLoggedIn ? '请记录今天做得很好的三件事和值得感恩的三件事!' : '请先登录才能记录内容', filled: true, fillColor: _isLoggedIn ? Colors.white : Colors.grey[200], border: OutlineInputBorder( borderSide: BorderSide( color: _isLoggedIn ? Colors.blue : Colors.red, width: 2.0, ), ), ), // 关键修改:添加点击事件处理 onTap: _handleTextFieldTap, readOnly: !_isLoggedIn, // 未登录时只读 ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: _saveInput, icon: const Icon(Icons.save), label: const Text("保存内容"), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), backgroundColor: Colors.black45, foregroundColor: Colors.white, elevation: 0, ), ), ), const SizedBox(height: 20), Container( height: 390, padding: const EdgeInsets.all(12), decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/background.jpg'), fit: BoxFit.cover, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row(children: [ Icon(Icons.book, color: Colors.grey), Text("日记列表:", style: TextStyle(fontSize: 15)), SizedBox(height: 8) ]), Expanded( child: _savedTexts.isEmpty ? const Center(child: Text("暂无保存内容")) : ListView.builder( itemCount: _savedTexts.length, itemBuilder: (context, index) { return ListTile( title: Text( _savedTexts[index], style: const TextStyle( color: Colors.black54, fontSize: 13.0, fontWeight: FontWeight.w400 ) ), trailing: IconButton( icon: const Icon(Icons.delete, color: Colors.black), onPressed: () => _deleteItem(index), ), ); }, ), ), ], ), ), ], ), ), ); } } // 登录页面实现 class LoginPage extends StatelessWidget { const LoginPage({super.key}); @override Widget build(BuildContext context) { final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); // 登录函数 Future<void> _login() async { // 简单验证 if (_emailController.text.isEmpty || _passwordController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请输入邮箱和密码')) ); return; } final prefs = await SharedPreferences.getInstance(); await prefs.setBool('is_logged_in', true); // 返回登录成功状态 Navigator.pop(context, true); } return Scaffold( appBar: AppBar( title: const Text('登录'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), ), body: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextField( controller: _emailController, decoration: const InputDecoration( labelText: '邮箱', border: OutlineInputBorder(), prefixIcon: Icon(Icons.email), ), keyboardType: TextInputType.emailAddress, ), const SizedBox(height: 20), TextField( controller: _passwordController, obscureText: true, decoration: const InputDecoration( labelText: '密码', border: OutlineInputBorder(), prefixIcon: Icon(Icons.lock), ), ), const SizedBox(height: 30), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _login, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 15), backgroundColor: Colors.blue, ), child: const Text('登录', style: TextStyle(fontSize: 18)), ), ), ], ), ), ); } } ``` ### 关键修复点: 1. **登录状态检测优化**: - 修改了 `_checkLoginStatus()` 方法,返回 `Future<bool>` 类型[^1] - 添加了错误处理和日志输出 ```dart Future<bool> _checkLoginStatus() async { try { final prefs = await SharedPreferences.getInstance(); final loggedIn = prefs.getBool('is_logged_in') ?? false; setState(() => _isLoggedIn = loggedIn); return loggedIn; } catch (e) { debugPrint('登录状态检测错误: $e'); return false; } } ``` 2. **点击事件处理改进**: - 使用 `Future.delayed(Duration.zero)` 确保弹窗在键盘弹出前显示 - 直接使用 `await _checkLoginStatus()` 获取最新状态 ```dart Future<void> _handleTextFieldTap() async { final isLoggedIn = await _checkLoginStatus(); if (!isLoggedIn) { Future.delayed(Duration.zero, () { _showLoginDialog(context); }); } } ``` 3. **UI状态增强**: - 添加 `readOnly: !_isLoggedIn` 属性,未登录时禁止编辑 - 动态调整输入框背景色和边框颜色作为视觉反馈 ```dart readOnly: !_isLoggedIn, // 未登录时只读 fillColor: _isLoggedIn ? Colors.white : Colors.grey[200], border: OutlineInputBorder( borderSide: BorderSide( color: _isLoggedIn ? Colors.blue : Colors.red, width: 2.0, ), ), ``` 4. **登录页面完善**: - 添加了简单的表单验证 - 实现了返回按钮功能 - 改进了UI布局 ```dart if (_emailController.text.isEmpty || _passwordController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请输入邮箱和密码')) ); return; } ``` ### 使用流程: 1. 用户点击输入框时触发 `onTap` 事件 2. 调用 `_handleTextFieldTap()` 检查登录状态 3. 如果未登录,显示登录提示弹窗 4. 用户点击"去登录"跳转到登录页面 5. 登录成功后返回日记页面并更新状态 6. 输入框变为可编辑状态,可正常输入内容 ### 相关问题: 1. 如何在 Flutter 中实现自动登录功能?[^1] 2. SharedPreferences 和 SQLite 在数据存储上有何区别?[^1] 3. 如何优化 Flutter 中的用户身份验证流程?[^2] 4. 在集成测试中如何模拟登录状态?[^1] 5. 如何实现 Flutter 的记住密码功能?[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值