首页 > 编程语言 >[Java]细节与使用经验

[Java]细节与使用经验

时间:2024-03-24 14:46:52浏览次数:29  
标签:经验 Java String int void System print 细节 println

【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://www.cnblogs.com/cnb-yuchen/p/18032072
出自【进步*于辰的博客

纯文字阐述,内容比较干。并且,由于考虑到时间长了恐有所遗漏,便即兴记录,并没有对内容进行筛选、排序。因此,大家在阅读时可以直接 Ctrl + F 进行检索。

1、细节积累

  1. 1000 == new Integer(1000)
  2. "CS" + ne w String("DN") = "CSDN"
  3. protected 的限制范围是同体系(继承关系)、同包。
  4. 构造方法无返回值,void属于一种返回值类型。
  5. final、static、private 修饰的方法不能被重写。
  6. 封装的主要作用:对外隐藏内部实现细节、增强程序的安全性。
  7. boolean、byte 类型变量都占一个字节。
  8. 每个类都有各自的常量池,需要时从JVM总常量池中分配;
  9. 装饰模式的原理类似继承,作用是实现读写的扩展。
  10. 表示“空指针”是null,不是NULL
  11. 进入同步代码块、wait()、读取输入流、降低优先级这些方法都可使线程停止。
  12. 接口的方法只能由publicabstractdefaultstatic修饰,不能是private,因为接口不能私有化。
  13. 形参可以声明为final,只是方法内不能修改。
  14. $文数$指正序与倒序相同的数,如:12321
  15. Scanner 类的next()获取输入以“ ”(空格)结尾,nextLine()以“\n”(回车)结尾。
  16. 无论程序多复杂,运行时都、且之会生成一个JVM进程。
  17. default的三个使用场景:switch、默认方法、自定义注解注解元素的默认值。
  18. float 和 double 都是小数,java规定:1.0隐式为1.0d,其中的d是 double 的标志。因此,float f1 = 1.0这条语句编译报错,因为 double 所占字节数大于 float。这就是给float类型变量赋初值时要加f的原因。
  19. 不能作为switch参数的类型有:float、double、long、boolean 和复杂表达式。
  20. java源文件(后缀是.java)编译时默认使用操作系统所设定的编码进行编译。这就是为什么使用记事本编写 java 源代码可以正常编译并在 JVM 运行的原因。
  21. 原始 / 基本数据类型:short、int、long、char、byte、float、double、boolean。
  22. 若仅实例化子类,由于this代表的是当前实例,故当在父类中使用this时,this代表的是子类实例,而非父类实例。
  23. 实例化时,会先从方法区中检查构造方法是否相符(相同),再初始化成员变量和成员方法。
  24. 在多层 for 循环中,有时for前面有一个outer:/inner:,作用是便于控制循环,这只是一个标识,不固定。
  25. boolean 不能为null的原因:(1)、boolean 只有true/false两种取值;(2)、null表示空指针,而 boolean 是基本数据类型。
  26. JVM 方法区用于存放静态资源和类信息,多线程共享(线程安全)。当多线程同时使用一个类时,若此类未加载,则只有一个线程去加载类,其他线程等待。

2、一些类的使用细节

2.1 Object

  1. 因为 Object 类型不能作比较(obj1 > obj2语法不允许),故不能通过 Object 作为“上转类型”来比较不同包装类,需使用 Comparable 类代替 Object。(暂不知原因)
  2. intint[]IntegerInteger[]都可上转为 Object,Integer[]也可上转为Object[],但int[]不可上转为Object[]。(暂不知原因)

2.2 String

为了标记字符串为 unicode 字符,在字符串末尾默认存在一个空格,此空格不存在于字符序列(value)中(即不包含在length中),但使用subString()截取字符串时,开始索引可以是length,能得到一个空格。

2.3 String与字符串缓冲区的区别

String是常量,不可变;字符串缓冲区(StringBuffer/StringBuilder)支持可变的字符串。当需要对字符串进行频繁操作时,两者的性能差异很大。

String 赋值的步骤:

String → StringBuffer/StringBuilder → append() → toString() → String

引用 String 类的 API 中的一张图:
在这里插入图片描述

2.4 基本数据类型、包装类与String三者间的转换关系

参考笔记一,P40.7。

在这里插入图片描述

2.5 Package

参考笔记二,P29.6。

  1. Java属跨平台语言,与操作系统无关,Package`是用来组织源文件的一种虚拟文件系统。
  2. import的作用是引入,而不是拷贝,目的是告诉编译器编译时应去哪读取外部文件。
  3. Java的包机制与 IDE 无关。
  4. 同一包下的类可不使用import而直接调用(不是指实例化时用全类名)。

3、关于比较

3.1 ==

==是运算符,只能用于基本数据类型之间、以及相同包装类之间的比较。

3.2 equals()

所有包装类都重写了 Object 类的equals(),但无论equals()底层调用的是toString()intValue()、还是longValue(),结果都是获取值,与地址无关。

当使用equals()比较包装类时,多用于相同包装类之间。之所以不用于不同包装类,是因为所有包装类重写的equals()底层都封装了类型判断,若是不同包装类,则直接返回 false,即无法比较。

4、自动装箱与自动拆箱

所有包装类都具备自动装箱和自动拆箱机制,只是 Double、Float、Boolean 这三个包装类变量的值始终是对象。其中,Double、Float 的常量未存放在 JVM 方法区常量池的原因是浮点数无限。

如果大家想进一步了解自动装箱和自动拆箱机制,可参考博文《[Java]基本数据类型与引用类型赋值的底层分析的小结》的第4.2项。

6、一个关于static容易混淆的细节

大家先看个示例。

int a;

public static void main(String[] args) throws Exception {
	C c1 = new C();// C 是类名
    int i = 10;
    while (i-- > 0) {
        new Thread(() -> {
            int n = 10000;
            while (n-- > 0)
                c1.a++;
        }).start();
    }
    Thread t1 = new Thread(() -> {});
    t1.start();
    t1.join();
    System.out.println(c1.a);
}

请问a的打印结果是多少,100000?不是,因为存在多线程并发访问。那如果把a改成类变量,结果是100000吗?仍然不是,依旧存在多线程并发访问。

不是由static修饰吗,为什么不是00000?这就是容易混淆的地方。

  • 类变量属于类,为所有对象共享;
  • 并发问题指线程对同一个数据的更改对其他线程不可见而导致的数据不一致问题。

所以,类变量不是为所有线程共享。

换言之,a必须进行同步处理(synchronized)。至于a定义为类变量、还是成员变量,看具体需求,与多线程无关。(注意下面这一点)

我突发奇想。(上面的线程在跑着)

public static void main(String[] args) throws Exception {
    System.out.println(new C().a);
}

请问能获取到a的实时数据吗?当然不能,为什么?因为是两个 JVM。

18、关于数组定义

定义数组的4种格式:

1、int[] arr1 = new int[5];------------A
2、int arr2[] = new int[5];
3、int[] arr3 = {1, 2, 3};-------------B
4、int[] arr4 = new int[]{1, 2, 3};----C // [] 内不能指定元素个数

特例:

public static void main(String[] args) throws Exception {
    print({1, 2, 3});// 编译报错
    int[] arr = {1, 2, 3};
    print(arr);// 打印:[1, 2, 3]
}

public static void print(int[] arr) {
    System.out.println(Arrays.toString(arr));
}

若形参类型为数组,调用时不能进行数组初始化。

19、实现多接口时的细节

1、方法 A 与 B 参数列表、返回值类型、方法名都相同时。

interface AnimalService {
    void print();----------------------A
}

interface LifeService {
    void print();----------------------B
}

class Person implements AnimalService, LifeService {
    @Override
    public void print() {--------------C // 重写A
    }
}

C 重写 A 还是 B,取决于类 Person 实现接口的顺序,故 C 重写A。

2、方法 A 与 B 仅返回值类型不同时。

interface AnimalService {
    void print();----------------------A
}

interface LifeService {
    int print();----------------------B
}

class Person implements AnimalService, LifeService {
    @Override
    public void print() {--------------C // 编译报错
    }
    或
    @Override
    public intC print() {--------------D // 编译报错
    }
}

A 与 B 仅返回值类型不同,这种情形不允许,故 C、D 都编译报错。

3、方法 A 与 B 完全不同时。

interface AnimalService {
    void print();----------------------A
}

interface LifeService {
    int print(int age);----------------------B
}

class Person implements AnimalService, LifeService {
    @Override
    public void print() {--------------C // 重写A
    }
    
    @Override
    public int print(int age) {--------D // 重写B
    }
}

这就是普遍实现多接口的情况,此时,C、D 也属于重载。

21、关于时间类和时间处理

21.1 Date、Calendar

Date 类倾向于获取 时间,Calendar 类倾向于处理 时间。

示例:获取30分钟后的时间。

Date client = new Date();// 当未指定参数时,获取当前时间
System.out.println(client);// 打印:Wed Apr 12 19:48:10 CST 2023

Calendar handler = Calendar.getInstance();// Calendar是抽象类,故需要通过调用getInstance()创建实例
handler.setTime(client);// 设置初始时间
handler.add(Calendar.MINUTE, 30);// 将初始时间增加30分钟
System.out.println(handler.getTime());// 打印:Wed Apr 12 20:18:10 CST 2023

21.2 SimpleDateFormat

普通的时间格式:yyyy-MM-dd HH:mm:ss
有时候会是“hh”,区别是:前者是24小时制。

22、我误解的一个基础

问题:子类修改父类成员,通过反射获取修改前后父类成员、值未改变。

示例1
待反射类。

class Platform {
    String name;
    public void initail() {
        name = "csdn";
    }
}

测试代码。

Platform p1 = new Platform();
Class z1 = p1.getClass();
Field f1 = z1.getDeclaredField("name");
System.out.println(f1.get(p1));// 打印:null
p1.initail();
System.out.println(f1.get(p1));// 打印:csdn

示例2。
待反射类。

class Platform {
    String name;
}
class CSDN extends Platform {
    public void initail() {
        name = "csdn";
    }
}

测试代码。

Platform p1 = new Platform();
Class z1 = p1.getClass();
Field f1 = z1.getDeclaredField("name");
System.out.println(f1.get(p1));// 打印:null

CSDN c1 = new CSDN();
c1.initail();
System.out.println(f1.get(p1));// 打印:null------A
System.out.println(p1.name);// 打印:null---------B
System.out.println(c1.name);// 打印:csdn

这2个示例的区别在哪?前者是通过父类自身实例修改其成员变量,而后者是通过子类实例修改父类成员变量。

明明已经调用c1.initail(),为何A、B两处的name仍为null

关于子类或父类初始化,详述可参考博文《Java知识点锦集》的第5.4项。

原因很简单:假设c1的父类实例是p0(当然,实际上 Platform 类仅进行了实例初始化,未实例化),p0p1是两个实例

上述只是一个基础知识,为何我觉得可作为一个细节?
下述代码是我研究 ArrayList<E> 类的迭代器Itr和子迭代器ListItr时的一个示例。

ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

Iterator<Integer> it = list.iterator();

Class z1 = Class.forName("java.util.ArrayList$Itr");
Field f1 = z1.getDeclaredField("expectedModCount");
f1.setAccessible(true);
System.out.println(f1.get(it));// 打印:3------A

ListIterator<Integer> lit = list.listIterator();
lit.add(4);
System.out.println(f1.get(it));// 打印:3------B
lit.add(5);
System.out.println(f1.get(it));// 打印:3------C

it.next();// 抛出:ConcurrentModificationException

关于expectedModCount,见ArrayList<E>的第7.1项。

由于之前调用了3次add(),故A处的值为3。同理,B、C两处的值应分别为45,可实际都是3

$PS$:我被潜意识误导了,即原因所在。

23、大家可能无意中避过的一个坑

相信大家都写过这么一段代码:

String str;
if (str != null && str.equals("")) {}

并且,可能自己领悟,或者他人指点,一般会这么写:

String str;
if (str != null && "".equals(str)) {}

因为这样可以避免空指针异常
其实,这两种写法并没有什么区别,用第二种纯粹是个人习惯或者是一种规范,我也是如此。

曾经我有一个疑惑:“第一种写法中同样会执行str.equals(),它是如何规避空指针异常的?”
由于这看起来只是一个细节,而且不影响敲代码,也就没关注。然而今天看源码,就碰到了这么一段判断:

if (useCaches &&
    reflectionData != null &&
    (rd = reflectionData.get()) != null &&
    rd.redefinedCount == classRedefinedCount) {}

四个 boolean,这若是无法确定每个 boolean,还如何解析?这就让我想起了开头说的例子。其实,我们并不需要确认所有的 boolean,听我细细道来。。。

这就要涉及运算符&&||的运算规则了,为了研究这个问题,翻了一些资料。果然,这两种运算符实在太基础了,没遇到我需要的,只能自己debug了。。。

看下述测试。
1、测试&&
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
&&结果为 true,要求每一段都为 true。从左往右逐个执行,若前面为 false,结果则为 false,后面就不会再执行;若前面为 true,则继续判断后面。(这就是开头我说那两种写法并没有区别的原因)

2、测试||
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
||结果为 true,要求其中一段为 true 即可。从左往右逐个执行,若前面为 true,结果则为 true,后面就不会再执行;若前面为 false,则继续判断后面。

PS:看到这6张不断播放的图片以及这绕口的说明,我自己都有点晕。。。大家可以根据图片自行理解。

补充一点:

String str;
if ("".equals(str)) {}

请问这样可以避免空指针异常吗?答案是不能,因为下述代码会抛出空指针异常:

Object anObject = null;
if(anObject instanceof String) {}

可能大家不明白我的意思,如果大家感兴趣,可以去看看equals()的源码。

直通车 -> String类的第2.14项。

24、关于程序与数据库之间数据类型转换说明(基于JDBC)

24.1 数据存储(DML)

  1. String → DateTime,需使用类java.sql.Timestamp。

24.2 数据查询(select)

  1. DateTime → String,先通过rs.getTimestamp()(rs是结果集ResultSet对象)获取Timestamp对象,再使用SimpleDateFormat类将其转为String。

最后

如果大家想要了解一些Java知识点,可查阅博文《Java知识点锦集》。

本文持续更新中。。。

标签:经验,Java,String,int,void,System,print,细节,println
From: https://www.cnblogs.com/cnb-yuchen/p/18032072

相关文章

  • Java为什么创建一个字符串对象需要在内存中创建两份
    在Java中,当你使用Strings=newString("abc")这样的语法创建一个字符串时,实际上会在内存中创建两个对象。这是因为Java中的字符串分为两种类型:一种是使用字面量的方式创建的字符串,另一种是通过new关键字创建的字符串。当我们使用字面量方式创建字符串时,例如Strings="a......
  • java SE(三):面向对象②
    一、this关键字1、概念this关键字代表当前对象①使用this关键字引用成员变量②使用this关键字引用成员方法或构造方法。在一个类的方法或构造方法内部,可以使用“this.成员变量名”这样的格式来引用成员变量名,常常用来区分同名的成员变量和局部变量。publicclassDe......
  • Java 学习路线:基础知识、数据类型、条件语句、函数、循环、异常处理、数据结构、面向
    Java基础什么是JavaJava是一种由SunMicrosystems于1995年首次发布的编程语言和计算平台。Java是一种通用的、基于类的、面向对象的编程语言,旨在减少实现依赖性。它是一个应用程序开发的计算平台。Java快速、安全、可靠,因此在笔记本电脑、数据中心、游戏机、科学超级计......
  • 【附源码】java松江大学城就餐推荐系统设计与实现(ssm毕业设计+maven+vue+计算机专业)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:在现代都市的快节奏生活中,餐饮服务已经成为人们日常生活中不可或缺的一部分。尤其是对于大学生这一群体,他们通常生活在学校周边的大学城,拥有丰富的就餐选......
  • 最长子字符串的长度(二)【华为OD机试JAVA&Python&C++&JS题解】
    一.题目-最长子字符串的长度(二)给你一个字符串s,字符串s首尾相连成一个环形,请你在环中找出’l’、‘o’、‘x’字符都恰好出现了偶数次最长子字符串的长度。输入描述:输入是一串小写的字母组成的字符串。输出描述:输出是一个整数补充说明:1<=s.length<=5x10^5......
  • 孙悟空吃蟠桃【华为OD机试JAVA&Python&C++&JS题解】
    一.题目-孙悟空吃蟠桃孙悟空爱吃蟠桃,有一天趁着蟠桃园守卫不在来偷吃。已知蟠桃园有N颗桃树,每颗树上都有桃子,守卫将在H小时后回来。孙悟空可以决定他吃蟠桃的速度K(个/小时),每个小时选一颗桃树,并从树上吃掉K个,如果树上的桃子少于K个,则全部吃掉,并且这一小时剩余的时间里不再......
  • 讲一下怎么在java项目中使用阿里云的短信服务
    要在Java项目中使用阿里云的短信服务,您需要完成以下步骤:步骤1:注册阿里云账号如果您还没有阿里云账号,您需要先注册一个账号,并购买短信服务。步骤2:创建AccessKey登录阿里云控制台,创建一个AccessKey以便在代码中验证身份。步骤3:引入阿里云短信服务SDK您需要在项目的pom.xml......
  • 智能停车场管理系统设计与实现|jsp+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档
    本项目包含可运行源码+数据库+LW,文末可获取本项目的所有资料。推荐阅读100套最新项目最新ssm+java项目文档+视频演示+可运行源码分享最新jsp+java项目文档+视频演示+可运行源码分享最新SpringBoot项目文档+视频演示+可运行源码分享2024年56套包含java,ssm,springboot的平台......
  • 医院预约挂号系统设计与实现|jsp+ Mysql+Java+ Tomcat(可运行源码+数据库+设计文档)
    本项目包含可运行源码+数据库+LW,文末可获取本项目的所有资料。推荐阅读100套最新项目最新ssm+java项目文档+视频演示+可运行源码分享最新jsp+java项目文档+视频演示+可运行源码分享最新SpringBoot项目文档+视频演示+可运行源码分享2024年56套包含java,ssm,springboot的平台......
  • 智能停车场管理系统设计与实现|jsp+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档
    本项目包含可运行源码+数据库+LW,文末可获取本项目的所有资料。推荐阅读100套最新项目最新ssm+java项目文档+视频演示+可运行源码分享最新jsp+java项目文档+视频演示+可运行源码分享最新SpringBoot项目文档+视频演示+可运行源码分享2024年56套包含java,ssm,springboot的平台......