当前位置: 首页 > news >正文

Java中不太常见的语法-总结

简介

读源码时,或者看同事写的代码,经常看到一些不太常见的语法,这里做一个总结

不太常见的语法

成员变量的默认值

案例:

public class Person2 {private String name = "张三";private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Person2{" +"name='" + name + '\'' +", age=" + age +'}';}
}

实例化Person2,会发现,name已经被赋默认值了:

Person2 person2 = new Person2();
System.out.println("person2 = " + person2);   // person2 = Person2{name='张三', age=null}

普通的业务代码中不推荐这么写,建议把赋值语句写在构造方法中,语义更明确。

原始类型数据不能和null进行运算

案例:

int i = null;

这一行代码会报错,基础数据类型不是一个对象,不支持赋null值。

我在开发中遇到的一个实际案例,当时没有注意到这一点,这里我使用一段简单的代码来模拟一下:

public class Test3 {@Testpublic void test1() {Integer i = null;if (Color.RED.getId() == i) {  // 这行代码会报空指针异常,可以使用Objects.equals来避免System.out.println("aaa");} else {System.out.println("bbb");}}enum Color {RED(1, "红色"),BLACO(2, "黑色"),YELLOE(3, "黄色");private final int id;private final String name;Color(int id, String name) {this.id = id;this.name = name;}public int getId() {return id;}public String getName() {return name;}}
}

分析一下原因,因为基础数据类型不支持和null进行运算,而案例中Integer类型的变量实际上是一个null值,所以会报空指针异常,我当时遇到这个问题,怎么都想不到原因,结果发现,同事定义的枚举值中,居然使用了int类型,所以最好使用包装类来作为成员变量的类型,这个案例中,如果id的类型是Integer,这么比较不会有问题。

do while语句

do while循环,第一次时一定会进入循环,在某些情况下使用它更合适,例如,有一种场景,需要循环读取外部数据并处理,直到读完,第一次的时候肯定要读取外部数据,所以这种情况下使用do while会比较合适。

案例:

数据准备: 模拟读取外部数据,分页读取

private static final List<User> list = Lists.newArrayList(new User(1L, "aaa", "aaa@qq.com", 20),new User(2L, "bbb", "bbb@qq.com", 20),new User(3L, "ccc", "ccc@qq.com", 20));private PageBO<User> readList(int pageNo, int pageSize) {PageBO<User> bo = new PageBO<>();bo.setPageNo((long) pageNo);bo.setPageSize(pageSize);bo.setTotalCount((long) list.size());int offset = (pageNo - 1) * pageSize;int limit = Math.min(offset + pageSize, list.size());if (offset < list.size()) {List<User> resultList = new ArrayList<>(list.subList(offset, limit));bo.setRecordList(deepCopyByIO(resultList));}return bo;
}// 使用IO流实现深拷贝
@SuppressWarnings("unchecked")
public static <T> T deepCopyByIO(T object) {if (object == null) {return null;}// ByteArrayOutputStream、ByteArrayInputStream,是内存流,即使不关闭也不会造成资源泄露,例如文件句柄未// 释放等问题,即使如此,随时关闭流依然是一个好习惯,而且关闭输出流会保证缓冲区的数据被正确的写出try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {objectOutputStream.writeObject(object);try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) {return (T) objectInputStream.readObject();}} catch (Exception e) {e.printStackTrace(); // 打印异常信息return null;}
}@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBO<T> {/*** 总条数*/private Long totalCount;/*** 当前页码*/private Long pageNo;/*** 页面大小*/private Integer pageSize;/*** 本页数据*/private List<T> recordList;
}@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {private static final long serialVersionUID = 8683452581122892181L;private Long id;private String name;private String email;private Integer age;
}

案例1: 使用while语句来实现循环读取外部数据

@Test
public void test1() {int pageNo = 1;final int PAGE_SIZE = 1;PageBO<User> pageBO = readList(pageNo, PAGE_SIZE);while (CollectionUtils.isNotEmpty(pageBO.getRecordList())) {// 处理数据System.out.println("处理数据 = " + pageBO.getRecordList());// 循环读取pageNo++;pageBO = readList(pageNo, PAGE_SIZE);}
}

案例2: 将上面的while循环变成do while循环

@Test
public void doWhileTest() {int pageNo = 1;final int PAGE_SIZE = 1;PageBO<User> pageBO; // while 条件表达式中的变量,必须在do while之外才可以被看到do {pageBO = readList(pageNo, PAGE_SIZE);System.out.println("处理数据 = " + pageBO.getRecordList());pageNo++; // 循环读取} while (CollectionUtils.isNotEmpty(pageBO.getRecordList()));
}

案例3:案例2中有一个问题,最后一次一定会读到空数据,所以while不可以直接转换为do while,代码要做一点改变

@Test
public void doWhileTest2() {int pageNo = 1;final int PAGE_SIZE = 1;PageBO<User> pageBO;do {pageBO = readList(pageNo, PAGE_SIZE);if (CollectionUtils.isEmpty(pageBO.getRecordList())) {break;}System.out.println("处理数据 = " + pageBO.getRecordList());pageNo++; // 循环读取} while (true);
}

案例4:一个简单的案例

public static void main(String[] args) {java.util.Scanner scanner = new java.util.Scanner(System.in);int userInput;do {System.out.print("请输入一个1到10之间的数字: ");userInput = scanner.nextInt();} while (userInput < 1 || userInput > 10);System.out.println("你输入了: " + userInput);
}

总结:do while语句适合处理第一次一定会进入循环的情况,但是使用普通的while也可以,只是do wihle在语义上更加合适,不过要注意的是,判断是否需要继续循环的条件,和while的不太一样,它们不可以共用。

平时do while使用的不是很多,更多的使用的是while,遇到类似的情况,可以使用更合适的do while。

问题:a = b的返回值是什么?

答案:返回b

案例:

int a = 10;
int b = 11;
int c = a = b;
System.out.println("c = " + c); // 11

这是在阅读源码时看到的一段代码:

queued = UNSAFE.compareAndSwapObject(this, waitersOffset,q.next = waiters, q);

注意看方法中的参数3,q.next = waiters,最终是把什么值传递给了方法?答案是waiters,但同时,q.next的值也被更新为waiters了

双括号初始化

一种快捷的初始化集合或其他对象的方式

案例:

List<String> list = new ArrayList<String>() {{add("AA");add("BB");}
};

list被初始化为一个 ArrayList 的匿名子类。大括号中的代码块是一个实例初始化块,它在匿名子类创建时执行。

需要注意的是,虽然双括号初始化语法在某些情况下很方便,但它也创建了一个匿名的子类,会产生一些额外的内存开销,因为每个实例都是一个单独的类。此外,这种语法在某些上下文中可能会导致问题,比如在序列化时。

这种初始化方式会创建一个匿名内部类,相较于普通的初始化方式性能较低,不建议使用,而且序列化等方面,可能会有意想不到的坑

迭代器的使用

关于迭代器的几个问题:

  • 自己编写的类,怎样可以被for关键字遍历? 实现Iterable接口,这个接口要求返回Iterator实例,通常需要为自己编写的类实现一个独有的Iterator。
  • 迭代器的初始状态:迭代器被创建后,指针指向列表的第一个元素,hasNext的第一次判断,是判断列表的第一个元素存不存在,next方法,返回当前指向的元素并且指针后移。这里不是绝对的,取决于迭代器的实现逻辑,但是通常是这么实现的。

案例:模拟ArrayList,写一个基于数组的集合

public class ArrList<E> implements List<E>, Iterable<E>, Serializable, Cloneable {private static final long serialVersionUID = 1234L;private static final int DEFAULT_CAPACITY = 8;private Object[] arr;private int size;private int capacity;public ArrList() {capacity = DEFAULT_CAPACITY;size = 0;arr = new Object[capacity];}// 省略代码,这里只关系迭代器的实现@Overridepublic Iterator<E> iterator() {return new Itr();}private void checkIdx(int i) {if (i < 0 || i >= size) {throw new RuntimeException("下标越界:" + i);}}private class Itr implements Iterator<E> {private int idx;private int lastRet = -1;  // 上次访问@Overridepublic boolean hasNext() {return idx < size;}@Overridepublic E next() {checkIdx(idx);lastRet = idx;return get(idx++);}// remove方法,只有在调用next方法之后才可以调用remove方法,它用于移除当前元素,// 因为此时指针已经后移,所以特定使用一个变量来记录指针的上一个位置。@Overridepublic void remove() {checkIdx(idx);ArrList.this.remove(lastRet);  idx = lastRet;lastRet = -1;}}
}

重点查看迭代器的实现逻辑,迭代器初始化时,指向列表中的第一个元素,调用next方法,返回当前元素并且指针后移。

之前只是学习过迭代器的正确使用姿势,自己尝试写一个迭代器时,才注意到它内部的逻辑,所以这里做个简单总结

总结

这里记录了一些不太常见的语法,跟个人经验有关,比如我就不怎么使用do while循环来解决问题,但是有些情况下它确实更合适,还有迭代器的使用,不太清楚它的内部逻辑,剩下的就是某些不太常见的语法,或者容易被忽视的问题点。

http://www.dtcms.com/a/358840.html

相关文章:

  • static静态文件和requests请求对象
  • 内网穿透系列十二:一款基于 HTTP 传输和 SSH 加密保护的内网穿透工具 Chisel ,具备抗干扰、稳定、安全特性
  • PromptPerfect-将你的提示词提升到完美
  • 【Java基础知识 19】继承
  • BGP路由协议(三):路径属性
  • Cybero: 1靶场渗透
  • 2021-11-10 C++不变初心数
  • 从咒语到意念:编程语言的世纪演进与人机交互的未来
  • Carrier Aggregation Enabled MIMO-OFDM Integrated Sensing and Communication
  • 并发编程——09 CountDownLatch源码分析
  • 信息系统架构
  • Java面试-MyBatis篇
  • 【后端数据库】MySQL 索引生效/失效规则 + 核心原理
  • oha:一款轻量级HTTP负载测试工具
  • XHR 介绍及实践
  • 论文介绍:《Small Language Models are the Future of Agentic AI》
  • SSR降级CSR:高可用容灾方案详解
  • 使用axios封装post和get
  • istringviewstream 和 outstringstream
  • 嵌入式学习日记
  • 【3D算法技术入门】如何基于建筑图片重建三维数字资产?
  • 行内元素块元素
  • 【办公类-39-06】20250830通义万相水果图(万相2.1专业Q版线描风格+万相专业2.2默认简笔画效果)
  • “我店模式“当下观察:三方逻辑未变,三大升级重构竞争力
  • 如何提高微型导轨的生产效率?
  • 【Java EE进阶 --- SpringBoot】Spring Web MVC(Spring MVC)(二)
  • Qt中的QSS介绍
  • JavaScript 中的 this 关键字
  • 机器视觉学习-day11-图像噪点消除
  • VuePress添加自定义组件