首页> 标签> 编译器
"编译器"
共 15474 条结果
全部 问答 文章 公开课 课程 电子书 技术圈 体验
#define定义的宏
1.什么是#define举个例子:这里的PI就是宏的名字,3.1416就是宏的值#define PI 3.1416//这里的PI就是宏的名字,3.1416就是宏的值2.宏的功能:替换宏与值① 方便检测比如你在程序中,需要多次用到1这个值,但是值的含义又不相同,为了增加可读性,让检查代码的时候能够看到1就知道代表什么,这个时候你就可以给不同含义的1定义不同的宏的名字。② 方便维护如:可以用来定义数组容量 #define MAX_SIZE 100后续使用的时候可以这样使用: int a[MAX_SIZE];以后如果容量少了,直接修改这个宏,则使用的地方都跟着得到最新的了,就不需要一个个更改③ 简化写法如:可以这样的定义: #define unsigned int UINT则后续的时候就可以这样的写了: UINT a;//与unsigned int a;等价3. 编译器替换规则在预处理的过程中,将所有的含有宏定义的名字替换成值#define a 2+3int b = 2 * a ;                         //这个时候就替换成了 int b = 2 * 2 + 3 ; 所以要注意多添加括号printf ( " %s " , a is 2+3 ) ;    //而这里要注意字符串常量并不会被替换,打印结果是 a is 2+34. 宏的一些使用细节    和    # ① 首先你要知道,字符串有自动连接的特点​char *p = " welcome_" " to_you\0 ";    //_表示空格​printf(" %s " , welcome_to_you) ;        //打印welcome to you​printf(" %s " , p ) ;                              //打印welcome to you② #的使用 ​可以把宏参数编程对应的字符串,当你想让字符串里出现宏的时候就可使用。比如​***********仔细看代码**********​int i = 10 ;​#define PRINT (FORMAT , VALUE)  printf("the value of "#VALUE " is " FORMAT " \n " , VALUE) ;​PRINTF("%d" ,i+3);                                          //打印 the value of i+3 is 13          5. 两道宏的进阶题目① 写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。② offsetof宏的实现#define offsetof(StructType, MemberName) (size_t)&(((StructType *)0)->MemberName)StructType是结构体类型名,MemberName是成员名。具体操作方法是:1、先将0转换为一个结构体类型的指针,相当于某个结构体的首地址是0。此时,每一个成员的偏移量就成了相对0的偏移量,这样就不需要减去首地址了。2、对该指针用->访问其成员,并取出地址,由于结构体起始地址为0,此时成员偏移量直接相当于对0的偏移量,所以得到的值直接就是对首地址的偏移量。3、取出该成员的地址,强转成size_t并打印,就求出了这个偏移量。
文章
编译器
2023-02-01
嵌入式(三)——Linux基础命令
嵌入式Linux基础命令前言Linux命令:系统用户管理命令:su、sudo、adduser、passwd进程管理命令:ps、kill文件管理命令:(Linux一点哲学:一切皆为文件)文件四大类:-普通文件、d目录文件、c/b设备文件、l链接文件文件三小类:-s共享文件、-f堆栈文件、-p管道文件(Linux里shell做交互会看到。临时文件、重启后都不存在)一、快速开启ubuntu分享一篇好文章vmware虚拟机运行速度卡慢原因分析及解决办法大全真心不错,觉得开机太慢的小朋友可以看看。二、Linux指令大全网址推荐Linux命令大全(手册)全中文,很好搜索,超级推荐!!!三、用户基础命令1.ps:显示进程(直接输入)2.su:切换用户(su - 用户名)管理员:#普通用户:$3.sudo:提高该指令的权限(sudo 你要输的指令)4.adduser:创建用户(adduser 你需要创建的用户名)5.deluser:删除用户(deluser 你需要删除的用户名)deluser需要搭配kill指令kill -9 第几进程6.kill:杀死进程(当删除新用户是配合使用)四、文件1.读懂命令用ls -l(采用详细的格式来显示程序状况)读懂头名:drwxr-xr-xd就是文件类型r:可读(4)w:可写(2)x:可执行(1)(rwx)前三个:当前用户(r-x)中三个:与当前用户在同一组(r-x)后三个:其他用户chmod:修改权限(a+x/a-x)/(755/355)2.创建文件touch 创建文件(如:touch hello.c)cat 查看文件(cat 文件名)rm 删除文件(rm 文件名)可以空格加多个文件gedit 编辑文件 (gedit 文件名)(可以直接新文件有touch功能)gcc 生成可编译文件(c/c++编译器)(gcc 文件名)gcc hello.c -o demo(这个生成的可编译文件会是demo,而不是默认文件名a.out,防止覆盖)用(./文件名)运行附1.permisson denied 是表明权限不够2.Is the information correct? [Y/n]:敲回车默认就是Y,输入n退出这个选择3.按tab可以快速生成命令4.man 命令(可以直接查看)5.clear:只是清屏(直接输入,能查看历史命令)6.restart:清屏加清历史(直接输入,不·能查看历史命令)7.听说嵌入式用45-50个就够了,只有那些专业人士需要掌握300多的指令,hhhhh~
文章
Ubuntu  ·  Linux  ·  编译器  ·  Shell  ·  C语言  ·  虚拟化  ·  C++
2023-01-29
Java高手速成 | 高质量代码编写最佳实践
程序员之间交流时,会经常使用非程序员无法理解的行话,或者使用令操不同编程语言的程序员理解起来比较模糊的行话。但是,那些操相同编程语言的程序员理解起来不会产生什么问题。这有时也取决于程序员所掌握知识的渊博程度。一个新手或许不理解有经验的程序员说的是什么意思,而与此同时,久经沙场的同事会点头赞同并做出回应。本文向大家介绍一些Java编程行话,即Java惯用语,用以描述某些特性、功能、设计解决方案等。我们还将学习设计和编写应用程序代码中最流行和最有用的实践。读完本文,大家会彻底弄明白:其他Java程序员在讨论设计决策和所使用的功能时,讨论的内容到底是什么意思。本文涉及以下主题:Java行业惯用语、实现及用法最佳设计实践代码为人而写测试——通向高质量代码的捷径01、Java行业惯用语、实现及用法除了作为专业人员之间交流手段外,编程惯用语也是久经考验的编程解决方案和常见实践。这些都不是直接产生于语言规范,而是来自编程经验。我们将讨论最常用的惯用语。至于惯用语的完整列表,可以在Java官方文档中查找和学习。1●equals()方法和hashCode()方法java.lang.Object类中,equals()方法和hashCode()方法的默认实现,如下所示。public boolean equals(Object obj) { return (this == obj); } /** * Whenever it is invoked on the same object more than once during * an execution of a Java application, the hashCode method * must consistently return the same integer... * As far as is reasonably practical, the hashCode method defined * by class Object returns distinct integers for distinct objects. */ @HotSpotIntrinsicCandidate public native int hashCode();可以看到,equals()方法的默认实现仅比较指向对象存储地址的内存引用。类似地,从注释(引自源代码)中可以看出,hashCode()方法为相同的对象返回相同的整数,为不同的对象返回不同的整数。下面用Person类来演示,具体如下。public class Person { private int age; private String firstName, lastName; public Person(int age, String firstName, String lastName) { this.age = age; this.lastName = lastName; this.firstName = firstName; } public int getAge() { return age; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } }对于equals()方法和hashCode()方法的默认行为,举例如下:Person person1 = new Person(42, "Nick", "Samoylov"); Person person2 = person1; Person person3 = new Person(42, "Nick", "Samoylov"); System.out.println(person1.equals(person2)); //prints: true System.out.println(person1.equals(person3)); //prints: false System.out.println(person1.hashCode()); //prints: 777874839 System.out.println(person2.hashCode()); //prints: 777874839 System.out.println(person3.hashCode()); //prints: 596512129person1引用和person2引用及其散列码都是相等的,因为这两个引用指向相同的对象(相同的内存区域,以及相同的地址),而person3引用指向另一个对象。在实践中,经常希望对象相等是基于对象的全部或部分属性值。下面是equals()和hashCode()方法的一个典型实现。@Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if(!(o instanceof Person)) return false; Person person = (Person)o; return getAge() == person.getAge() && Objects.equals(getFirstName(), person.getFirstName()) && Objects.equals(getLastName(), person.getLastName()); } @Override public int hashCode() { return Objects.hash(getAge(), getFirstName(), getLastName()); }这样的实现在以前是比较复杂的,现在使用java.util.Objects实用程序令这样的实现简单多了。特别值得注意的是,Objects.equals()方法还能处理null值。将上述equals()和hashCode()方法的实现添加到Person1类中,执行同样的比较操作。具体如下:Person1 person1 = new Person1(42, "Nick", "Samoylov"); Person1 person2 = person1; Person1 person3 = new Person1(42, "Nick", "Samoylov"); System.out.println(person1.equals(person2)); //prints: true System.out.println(person1.equals(person3)); //prints: true System.out.println(person1.hashCode()); //prints: 2115012528 System.out.println(person2.hashCode()); //prints: 2115012528 System.out.println(person3.hashCode()); //prints: 2115012528可见,所做的更改不仅使相同的对象相等,而且使两个具有相同属性值的不同对象相等。此外,散列码值现在也基于了相同属性的值。在这些方法上使用@Override注解,可以确保这些方法确实覆盖了Object类中的默认实现。否则,方法名中的一个拼写错误可能会造成这样的错觉:使用了新的实现,但实际上并没有使用。事实证明,对这种情况的调试要比添加@Override注解困难得多,而且代价也要高得多。如果方法没有覆盖任何内容,就会产生错误。2●compareTo()方法基于此方法所建立的顺序(通过集合的元素实现的)被称为自然顺序(natural order)。下面创建了Person2类来演示该方法。具体如下:public class Person2 implements Comparable<Person2> { private int age; private String firstName, lastName; public Person2(int age, String firstName, String lastName) { this.age = age; this.lastName = lastName; this.firstName = firstName; } public int getAge() { return age; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } @Override public int compareTo(Person2 p) { int result = Objects.compare(getFirstName(), p.getFirstName(), Comparator.naturalOrder()); if (result != 0) { return result; } result = Objects.compare(getLastName(), p.getLastName(), Comparator.naturalOrder()); if (result != 0) { return result; } return Objects.compare(age, p.getAge(), Comparator.naturalOrder()); } @Override public String toString() { return firstName + " " + lastName + ", " + age; } }然后创建一个Person2对象的列表并进行排序:Person2 p1 = new Person2(15, "Zoe", "Adams"); Person2 p2 = new Person2(25, "Nick", "Brook"); Person2 p3 = new Person2(42, "Nick", "Samoylov"); Person2 p4 = new Person2(50, "Ada", "Valentino"); Person2 p6 = new Person2(50, "Bob", "Avalon"); Person2 p5 = new Person2(10, "Zoe", "Adams"); List<Person2> list = new ArrayList<>(List.of(p5, p2, p6, p1, p4, p3)); Collections.sort(list); list.stream().forEach(System.out::println);输出结果如图18-1所示。图 18-1 代码运行结果有三点值得注意:根据Comparable接口的定义,当对象小于、等于或大于另一个对象时,compareTo()方法必须返回一个负整数、零或正整数。在上述实现中,如果两个对象的相同属性值不同,则立即返回结果。因为不管其他属性如何,都已经知道这个Object是“更大的”还是“更小的”了。但是,对两个对象的属性加以比较,顺序对最终结果是有影响的。此方法定义了属性值影响顺序的优先级。这里把List.of()的结果存入new ArrayList()对象中。这样做是因为用of()工厂方法创建的集合是不可更改的。即不能添加或删除任何元素,也不能更改元素的顺序,然而这里需要对所创建的集合进行排序。这里之所以使用of()方法是因为这个方法更便捷,并提供了更短的表示。最后一点,使用java.util.Objects进行属性比较,令实现比自制编码更容易、更可靠。 在实现compareTo()方法时,重要的一点是:务必确保不要违反规则。规则如下:只有当返回值为0时,obj1.compareTo(obj2)才会返回与obj2.compareTo(obj1)相同的值。如果返回值不是0,则obj1.compareTo(obj2)的符号与obj2.compareTo(obj1)的符号相反。如果obj1.compareTo(obj2) > 0,以及obj2.compareTo(obj3) >0,则obj1.compareTo(obj3) > 0。如果obj1.compareTo(obj2) < 0,以及obj2. compareto (obj3) < 0,则obj1.compareTo(obj3) < 0。如果obj1.compareTo(obj2) == 0,那么obj2. compareto (obj3)和obj1.compareTo(obj3)具有相同的符号。obj1.compareto (obj2)和obj2.compareto (obj1)都抛出相同的异常(如果有的话)。如果obj1.equals(obj2),那么obj1.compareTo(obj2) == 0;同时,如果obj1.compareTo(obj2) == 0,那么obj1.equals(obj2)。建议你这样做,但不总是有这样做的需要。3●clone()方法在java.lang.Object中,clone()方法的实现,如下所示。@HotSpotIntrinsicCandidate protected native Object clone() throws CloneNotSupportedException;注释如下所示。/** * Creates and returns a copy of this object. The precise meaning * of "copy" may depend on the class of the object. ***此方法的默认结果是按原样返回对象字段的副本。如果值是基本类型的,这样做是可以的。但是,如果对象属性是另一个对象的引用,则只复制引用,而不复制引用的对象。这就是为什么这样的复制被称为浅复制(shallow copy)。要实现深复制(deep copy),必须覆盖clone()方法并克隆引用对象的每个对象属性。在任何情况下,为了能够克隆对象,类必须实现Cloneable接口,并确保沿着继承树的所有对象(以及对象的属性)也实现Cloneable接口(java.lang.Object除外)。Cloneable接口是一个标记接口,用以告诉编译器:程序员有意决定允许克隆这个对象(不管是因为浅复制足够好,还是因为clone()方法被覆盖了)。在未实现Cloneable接口的对象上尝试调用clone(),将引发CloneNotSupportedException异常。这看起来就够复杂了,但实践中甚至有更多陷阱存在。例如,假设Person类具有Address类型的address属性。Person对象p1的浅复制p2会引用相同的Address对象,从而导致p1.address == p2.address。举例说明,Address类如下所示。class Address { private String street, city; public Address(String street, String city) { this.street = street; this.city = city; } public void setStreet(String street) { this.street = street; } public String getStreet() { return street; } public String getCity() { return city; } }Person3类是这样使用Address类的:class Person3 implements Cloneable{ private int age; private Address address; private String firstName, lastName; public Person3(int age, String firstName, String lastName, Address address) { this.age = age; this.address = address; this.lastName = lastName; this.firstName = firstName; } public int getAge() { return age; } public Address getAddress() { return address; } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } @Override public Person3 clone() throws CloneNotSupportedException{ return (Person3) super.clone(); } }注意,clone()方法完成的是浅复制,因为这个方法没有克隆address属性。使用这种clone()方法实现的结果如下。Person3 p1 = new Person3(42, "Nick", "Samoylov", new Address("25 Main Street", "Denver")); Person3 p2 = p1.clone(); System.out.println(p1.getAge() == p2.getAge()); // true System.out.println(p1.getLastName() == p2.getLastName()); // true System.out.println(p1.getLastName().equals(p2.getLastName())); // true System.out.println(p1.getAddress() == p2.getAddress()); // true System.out.println(p2.getAddress().getStreet()); //prints: 25 Main Street p1.getAddress().setStreet("42 Dead End"); System.out.println(p2.getAddress().getStreet()); //prints: 42 Dead End可以看到,在克隆完成之后,对源对象的address属性所做的更改反映在副本的相同属性中。这不是很直观,对吧?克隆的时候,所期望的是独立的复制,没错吧?为了避免共享Address对象,还需要对此对象进行显式的克隆。要进行显式的克隆,就必须使Address对象具有可克隆的性质,具体操作如下。public class Address implements Cloneable{ private String street, city; public Address(String street, String city) { this.street = street; this.city = city; } public void setStreet(String street) { this.street = street; } public String getStreet() { return street; } public String getCity() { return city; } @Override public Address clone() throws CloneNotSupportedException { return (Address)super.clone(); } }有了这个实现,现在就可以添加address的克隆属性了:class Person4 implements Cloneable{ private int age; private Address address; private String firstName, lastName; public Person4(int age, String firstName, String lastName, Address address) { this.age = age; this.address = address; this.lastName = lastName; this.firstName = firstName; } public int getAge() { return age; } public Address getAddress() { return address; } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } @Override public Person4 clone() throws CloneNotSupportedException{ Person4 cl = (Person4) super.clone(); cl.address = this.address.clone(); return cl; } }现在,如果运行相同的测试,结果将与最初预期的一样。Person4 p1 = new Person4(42, "Nick", "Samoylov", new Address("25 Main Street", "Denver")); Person4 p2 = p1.clone(); System.out.println(p1.getAge() == p2.getAge()); // true System.out.println(p1.getLastName() == p2.getLastName()); // true System.out.println(p1.getLastName().equals(p2.getLastName())); // true System.out.println(p1.getAddress() == p2.getAddress()); // false System.out.println(p2.getAddress().getStreet()); //prints: 25 Main Street p1.getAddress().setStreet("42 Dead End"); System.out.println(p2.getAddress().getStreet()); //prints: 25 Main Street因此,如果应用程序希望所有属性都做深复制,那么所涉及的所有对象都必须是可克隆的。这样做是可以的,但有个前提:只要相关的对象——当前对象中的属性也好,父类(及其属性和父类)中的属性也罢——没有一个不需要新的对象属性,没有一个不需要属性是可克隆的,并且没有一个在容器对象的clone()方法中是可以显式克隆的。这话说得有些复杂。其复杂性的根源在于克隆过程潜在的复杂性。程序员经常避开,不让对象变成可克隆的,这就是原因所在。取而代之,如果需要,程序员更喜欢手动克隆对象。例如:Person4 p1 = new Person4(42, "Nick", "Samoylov", new Address("25 Main Street", "Denver")); Address address = new Address(p1.getAddress().getStreet(), p1.getAddress().getCity()); Person4 p2 = new Person4(p1.getAge(), p1.getFirstName(), p1.getLastName(), address); System.out.println(p1.getAge() == p2.getAge()); // true System.out.println(p1.getLastName() == p2.getLastName()); // true System.out.println(p1.getLastName().equals(p2.getLastName())); // true System.out.println(p1.getAddress() == p2.getAddress()); // false System.out.println(p2.getAddress().getStreet()); //prints: 25 Main Street p1.getAddress().setStreet("42 Dead End"); System.out.println(p2.getAddress().getStreet()); //prints: 25 Main Street如果将另一个属性添加到任何相关对象,采用这种途径仍然需要更改代码。但是,这种途径提供了对结果的更多控制,并减少了产生意想不到后果的机会。幸运的是,clone()方法并不经常使用。事实上,可能永远不会遇到需要使用此方法的情况。4●StringBuffer类和StringBuilder类在单线程情况下(这是绝大多数情况),应该首选StringBuilder类,因为其速度更快。5●try子句、catch子句和finally子句使用try-with-resources语句是释放资源的首选方法(传统上是在finally块中完成)。遵循各种库的原则编码,会使代码更简单、更可靠。02、最佳设计实践“最佳”这个词带有主观性,依赖于上下文。正因如此,下面所推荐的,是以主流编程中大多数案例为基础的。但是,盲目地和无条件地照搬照抄不可取。因为在某些上下文中,这些案例有些在实践中是没有用的,甚至是错误的。采纳某些案例前,尽力去理解其背后的动机,并把动机作为决策的指南。例如,规模大小就至关重要。如果应用程序代码不超过几千行,那么一种简单的单体式程序就足够好了,其代码具有细目清单式风格。但是,如果代码中有复杂的代码区块,并且由多人共同来编写,那么将代码分解成特定的区段将有利于代码的理解、维护和扩展(如果某个特定的代码区域比其他区域需要更多的资源)。下面就开始进行高级设计决策,这里没有固定的顺序。1●松耦合功能区的识别这样的设计决策可尽早做出,依靠的仅仅是对未来系统的主体部分、这些部分的功能,以及这些部分产生和交换的数据的一般性理解。这样做有以下几点好处:鉴别未来系统的结构,这影响到进一步的设计步骤和具体实施。各部分的专业化和更深层次的分析。各部分的并行开发。对数据流更好的理解。2●功能区的传统层划分每个功能区准备妥当后,就可以根据技术特性和所使用的技术进行专门化划分。从传统意义上看,技术的专门化划分如下:前端(用户图形界面或Web界面)。具有广泛业务逻辑的中间层。后端(数据存储或数据源)。这样做的好处包括:按层部署和扩展。基于经验知识的程序员专门化划分。各部分的并行开发。3●接口代码的编写以上面两个小节描述的决策为基础,专门化的部分必须通过接口中加以描述,而这个接口则隐藏实现的细节。这种设计的好处是为面向对象编程奠定了基础。4●工厂方法的使用从定义上看,接口没有描述也不能描述实现接口的类的构造方法。使用工厂方法允许将这个差距缩小,只向客户端公开一个接口。5●宁组合勿继承最初,面向对象编程关注的是继承,将继承视作对象间分享共同功能的方式。继承甚至是前面文章中描述的四个面向对象编程原则之一。但在实践中,这种功能共享的方法在同一继承链中所包含的类之间创建了太多的依赖关系。应用程序功能的演变通常是不可预测的,继承链中的一些类开始获得与类链的原始用途无关的功能。这里的主张是,有些设计解决方案允许不用继承,而是保持原始的类不被改变。然而,在实践中,经常发生这样的事情,子类可能会突然改变行为,只是因为它们通过继承获得了新的功能。父母是不能选择的,对吧?另外,继承以这种方式打破了封装,而封装则是OOP的另一个基本原则。另一方面,组合允许对使用类的哪个功能以及忽略类的哪个功能进行选择和控制。组合还允许对象保持轻量级别,而免受继承带来的负担。这样的设计更灵活,更具可扩展性和可预测性。6●库的使用使用Java类库(JCL)和外部(JDK之外的)库使编程更容易,还能编出更高质量的代码。库的创建者投入了大量的时间和精力,所以应该尽力在任何时候都利用好这样的库。这是利用好一个库的另一种方式——使用知名的共享接口集,而不是自己另起炉灶来定义接口。关于编写让他人容易理解的代码,上面最后一句话起到了一个很好的切换作用,切换到本文下一个主题。03、代码为人而写最初几十年时间里,编程是需要编写机器命令,以供电子设备执行。这不仅是一项乏味且容易出错的工作,而且还要求编写的指令尽可能获得最佳的性能。因为计算机运行缓慢,也不会作太多的代码优化。即使作了优化,也不多。从那时起,在硬件和编程方面都取得了很大的进步。在使所提交的代码尽可能快地运行方面,现代编译器功不可没,即便程序员没有认识到这一点。这就允许我们在无需过多考虑优化的情况下编写更多的代码行。但是传统的做法和许多关于编程的书籍继续要求作优化处理。有时我们仍然担心自己编写代码的性能,甚至担心的程度胜于代码产生的结果。遵循传统比背离传统更为容易。这就是为什么程序员更倾注于编写代码的方式,而不是使业务自动化,尽管实现了错误的业务逻辑的好代码也是无用的。还是文归正传。有了现代JVM来助力,程序员对代码优化的需求已经不像以前那么迫切了。时至今日,程序员主要是关注全局,以避免产生导致不良代码性能的结构性错误,也关注多次被使用的代码。后者的紧迫性也越来越小,因为JVM变得越来越复杂,可以实时地查看代码,且在使用相同的输入多次调用相同的代码块时,只是返回结果(不执行)。这就给我们留下了唯一可能的结论:编写代码时,必须确保代码让人而不是让计算机容易阅读、容易理解。在这个行业工作了一段时间的那些人,会对自己几年前编写的代码感到困惑不解。要是对编写的意图明确了然、透明清晰,就能改进代码编写的风格。对于注释的必要性,讨论多久都情有可原。十分明确的是,对于代码的用意作鹦鹉学舌式的注释是没有必要的。例如://Initialize variable int i = 0;对代码的用意予以解释的注释,更有价值:// In case of x > 0, we are looking for a matching group // because we would like to reuse the data from the account. // If we find the matching group, we either cancel it and clone, // or add the x value to the group, or bail out. // If we do not find the matching group, // we create a new group using data of the matched group.加了注释后,代码可能会变得很复杂。良好的注释用以说明意图,并帮助读者理解代码。问题是,程序员常常嫌麻烦而不去加注释。反对添加注释的人有两个典型的理由:注释必须与代码一起维护和演变。否则,注释可能会产生误导。但是,没有什么工具可以用来提示程序员在修改代码的同时调整注释。因此,加注释很危险。编写的代码本身(包括变量和方法的名称选择)自明,无需要额外的解释。这两种说法都没错,但同样没错的是:注释也可能大有裨益,特别是那些紧扣意图的注释。此外,这样的注释需要调整的地方不多,因为代码用意就算有变化,也不会经常变。04、测试——通向高质量代码的捷径最后讨论的一个最佳实践是:“测试不是开销或负担,而是程序员的成功指南。”唯一的问题是,何时编写测试代码。一个有说服力的观点认为,要在编写程序代码行之前编写测试代码。如果能做到这一点,那再好不过了。这里也不会劝阻读者那样做。但如果不那样做,也应在编写一行或全部代码行之后就尽力开始编写测试代码。实践中,许多有经验的程序员发现,在实现了一些新功能之后才开始编写测试代码很有益处,因为到了那时程序员才能更好地理解新代码如何能融进现有的上下文环境。程序员甚至可能试着对一些值实施硬编码,以查看新代码与调用新方法的代码的融合度好到什么程度。在确保新代码很好地得以融合之后,程序员可以继续实现和调优新代码,同时在调用代码的上下文中根据需求测试新的实现。必须添加一个重要的限定条件:在编写测试代码时,输入数据和测试标准最好不是由你来设置,而是由分配给你任务的人或测试员来设置。根据代码生成的结果来设置测试程序是一个众所周知的程序员陷阱。客观的自我评估不易做到,但如有可能要尽力做到。
文章
存储  ·  前端开发  ·  Java  ·  程序员  ·  编译器  ·  对象存储  ·  容器
2023-02-03
Java 结构化数据处理开源库SPL,再也不用苦哈哈写SQL了
现代Java应用架构越来越强调数据存储和处理分离,以获得更好的可维护性、可扩展性以及可移植性,比如火热的微服务就是一种典型。这种架构通常要求业务逻辑要在Java程序中实现,而不是像传统应用架构中放在数据库中。应用中的业务逻辑大都会涉及结构化数据处理。数据库(SQL)中对这类任务有较丰富的支持,可以相对简易地实现业务逻辑。但Java却一直缺乏这类基础支持,导致用Java实现业务逻辑非常繁琐低效。结果,虽然架构上有各种优势,但开发效率却反而大幅下降了。如果我们在Java中也提供有一套完整的结构化数据处理和计算类库,那这个问题就能得到解决:即享受到架构的优势,又不致于降低开发效率。需要什么样的能力?Java下理想的结构化数据处理类库应当具备哪些特征呢?我们可以从SQL来总结:1 集合运算能力结构化数据经常是批量(以集合形式)出现的,为了方便地计算这类数据,有必要提供足够的集合运算能力。如果没有集合运算类库,只有数组(相当于集合)这种基础数据类型,我们要对集合成员做个简单地求和也需要写四五行循环语句才能完成,过滤、分组聚合等运算则要写出数百行代码了。SQL提供有较丰富的集合运算,如 SUM/COUNT 等聚合运算,WHERE 用于过滤、GROUP 用于分组,也支持针对集合的交、并、差等基本运算。这样写出来的代码就会短小很多。2 Lambda语法有了集合运算能力是否就够了呢?假如我们为 Java 开发一批的集合运算类库,是否就可以达到 SQL 的效果呢?没有这么简单!以过滤运算为例。过滤通常需要一个条件,把满足条件的集合成员保留。在 SQL 中这个条件是以一个表达式形式出现的,比如写 WHERE x>0,就表示保留那些使得 x>0 计算结果为真的成员。这个表达式 x>0 并不是在执行这个语句之前先计算好的,而是在遍历时针对每个集合成员计算的。本质上,这个表达式本质上是一个函数,是一个以当前集合成员为参数的函数。对于 WHERE 运算而言,相当于把一个用表达式定义的函数用作了 WHERE 的参数。这种写法有一个术语叫做 Lambda 语法,或者叫函数式语言。如果没有 Lambda 语法,我们就要经常临时定义函数,代码会非常繁琐,还容易发生名字冲突。SQL中大量使用了 Lambda 语法,不在于必须过滤、分组运算中,在计算列等不必须的场景也可以使用,大大简化了代码。3 在 Lambda 语法中直接引用字段结构化数据并非简单的单值,而是带有字段的记录。我们发现,SQL 的表达式参数中引用记录字段时,大多数情况可以直接使用字段名称而不必指明字段所属的记录,只有在多个同名字段时才需要冠以表名(或别名)以区分。新版本的 Java 虽然也开始支持 Lambda 语法了,但只能把当前记录作为参数传入这个用 Lambda 语法定义的函数,然后再写计算式时就总要带上这个记录。比如用单价和数量计算金额时,如果用于表示当前成员的参数名为 x,则需要写成“x. 单价 *x. 数量”这种啰嗦的形式。而在 SQL 中可以更为直观地写成 " 单价 * 数量”。4 动态数据结构SQL还能很好地支持动态数据结构。结构化数据计算中,返回值经常也是有结构的数据,而结果数据结构和运算相关,没办法在代码编写之前就先准备好。所以需要支持动态的数据结构能力。SQL中任何一个 SELECT 语句都会产生一个新的数据结构,在代码中可以随意添加删除字段,而不必事先定义结构(类)。Java 这类语言则不行,在代码编译阶段就要把用到的结构(类)都定义好,原则上不能在执行过程中动态产生新的结构。5 解释型语言从前面几条的分析,我们已经可以得到结论:Java 本身并不适合用作结构化数据处理的语言。它的 Lambda 机制不支持特征 3,而且作为编译型语言,也不能实现特征 4。其实,前面说到的 Lambda 语法也不太适合采用编译型语言来实现。编译器不能确定这个写到参数位置的表达式是应该当场计算出表达式的值再传递,还是把整个表达式编译成一个函数传递,需要再设计更多的语法符号加以区分。而解释型语言则没有这个问题,作为参数的表达式是先计算还是遍历集合成员时再计算,可以由函数本身来决定。SQL确实是解释型语言。引入 SPLStream是Java8以官方身份推出的结构化数据处理类库,但并不符合上述的要求。它没有专业的结构化数据类型,缺乏很多重要的结构化数据计算函数,不是解释型语言,不支持动态数据类型,Lambda语法的接口复杂。Kotlin属于Java生态系统的一部分,它在Stream的基础上进行了小幅改进,也提供了结构化数据计算类型,但因为结构化数据计算函数不足,不是解释型语言,不支持动态数据类型,Lambda语法的接口复杂,仍然不是理想的结构化数据计算类库。Scala提供了较丰富的结构化数据计算函数,但编译型语言的特点,也使它不能成为理想的结构化数据计算类库。那么,Java生态下还有什么可以用呢?集算器SPL。SPL是由Java解释执行的程序语言,具备丰富的结构化数据计算类库、简单的Lambda语法和方便易用的动态数据结构,是Java下理想的结构化处理类库。丰富的集合运算函数SPL提供了专业的结构化数据类型,即序表。和SQL的数据表一样,序表是批量记录组成的集合,具有结构化数据类型的一般功能,下面举例说明。解析源数据并生成序表:Orders=T("d:/Orders.csv")按列名从原序表生成新的序表:Orders.new(OrderID, Amount, OrderDate)计算列:Orders.new(OrderID, Amount, year(OrderDate))字段改名:Orders.new(OrderID:ID, SellerId, year(OrderDate):y)按序号使用字段:Orders.groups(year(_5),_2; sum(_4))序表改名(左关联)join@1(Orders:o,SellerId ; Employees:e,EId).groups(e.Dept; sum(o.Amount))序表支持所有的结构化计算函数,计算结果也同样是序表,而不是Map之类的数据类型。比如对分组汇总的结果,继续进行结构化数据处理:Orders.groups(year(OrderDate):y; sum(Amount):m).new(y:OrderYear, m*0.2:discount)在序表的基础上,SPL提供了丰富的结构化数据计算函数,比如过滤、排序、分组、去重、改名、计算列、关联、子查询、集合计算、有序计算等。这些函数具有强大的计算能力,无须硬编码辅助,就能独立完成计算:组合查询:Orders.select(Amount>1000 && Amount<=3000 && like(Client,"*bro*"))排序:Orders.sort(-Client,Amount)分组汇总:Orders.groups(year(OrderDate),Client; sum(Amount))内关联:join(Orders:o,SellerId ; Employees:e,EId).groups(e.Dept; sum(o.Amount))简洁的Lambda语法SPL支持简单的Lambda语法,无须定义函数名和函数体,可以直接用表达式当作函数的参数,比如过滤:Orders.select(Amount>1000)修改业务逻辑时,也不用重构函数,只须简单修改表达式:Orders.select(Amount>1000 && Amount<2000)SPL是解释型语言,使用参数表达式时不必明确定义参数类型,使Lambda接口更简单。比如计算平方和,想在sum的过程中算平方,可以直观写作:Orders.sum(Amount*Amount)和SQL类似,SPL语法也支持在单表计算时直接使用字段名:Orders.sort(-Client, Amount)动态数据结构SPL是解释型语言,天然支持动态数据结构,可以根据计算结果结构动态生成新序表。特别适合计算列、分组汇总、关联这类计算,比如直接对分组汇总的结果再计算:Orders.groups(Client;sum(Amount):amt).select(amt>1000 && like(Client,"*S*"))或直接对关联计算的结果再计算:join(Orders:o,SellerId ; Employees:e,Eid).groups(e.Dept; sum(o.Amount))较复杂的计算通常都要拆成多个步骤,每个中间结果的数据结构几乎都不同。SPL支持动态数据结构,不必先定义这些中间结果的结构。比如,根据某年的客户回款记录表,计算每个月的回款额都在前10名的客户:Sales2021.group(month(sellDate)).(~.groups(Client;sum(Amount):sumValue)).(~.sort(-sumVa直接执行SQLSPL中还实现了SQL的解释器,可以直接执行SQL,从基本的WHERE、GROUP到JOIN、甚至WITH都能支持:$select * from d:/Orders.csv where (OrderDate<date('2020-01-01') and Amount<=100)or (OrderDate>=date('2020-12-31') and Amount>100)$select year(OrderDate),Client ,sum(Amount),count(1) from d:/Orders.csv group by year(OrderDate),Client having sum(Amount)<=100$select o.OrderId,o.Client,e.Name e.Dept from d:/Orders.csv o join d:/Employees.csv e on o.SellerId=e.Eid$with t as (select Client ,sum(amount) s from d:/Orders.csv group by Client) select t.Client, t.s, ct.Name, ct.address from t left join ClientTable ct on t.Client=ct.Client更多语言优势作为专业的结构化数据处理语言,SPL不仅覆盖了SQL的所有计算能力,在语言方面,还有更强大的优势:离散性及其支挂下的更彻底的集合化集合化是SQL的基本特性,即支持数据以集合的形式参与运算。但SQL的离散性很不好,所有集合成员必须作为一个整体参于运算,不能游离在集合之外。而Java等高级语言则支持很好的离散性,数组成员可以单独运算。但是,更彻底的集合化需要离散性来支持,集合成员可以游离在集合之外,并与其它数据随意构成新的集合参与运算 。SPL兼具了SQL的集合化和Java的离散性,从而可以实现更彻底的集合化。比如,SPL中很容易表达“集合的集合”,适合分组后计算。比如,找到各科成绩均在前10名的学生:A1=T(“score.csv”).group(subject)2=A2.(.rank(score).pselect@a(<=10))3=A1.(~(A3(#)).(name)).isect()SPL序表的字段可以存储记录或记录集合,这样可以用对象引用的方式,直观地表达关联关系,即使关系再多,也能直观地表达。比如,根据员工表找到女经理下属的男员工:Employees.select(性别:"男",部门.经理.性别:"女")有序计算是离散性和集合化的典型结合产物,成员的次序在集合中才有意义,这要求集合化,有序计算时又要将每个成员与相邻成员区分开,会强调离散性。SPL兼具集合化和离散性,天然支持有序计算。具体来说,SPL可以按绝对位置引用成员,比如,取第3条订单可以写成Orders(3),取第1、3、5条记录可以写成Orders([1,3,5])。SPL也可以按相对位置引用成员,比如,计算每条记录相对于上一条记录的金额增长率:Orders.derive(amount/amount[-1]-1)SPL还可以用#代表当前记录的序号,比如把员工按序号分成两组,奇数序号一组,偶数序号一组:Employees.group(#%2==1)更方便的函数语法大量功能强大的结构化数据计算函数,这本来是一件好事,但这会让相似功能的函数不容易区分。无形中提高了学习难度。SPL提供了特有的函数选项语法,功能相似的函数可以共用一个函数名,只用函数选项区分差别。比如select函数的基本功能是过滤,如果只过滤出符合条件的第1条记录,只须使用选项@1:Orders.select@1(Amount>1000)数据量较大时,用并行计算提高性能,只须改为选项@m:Orders.select@m(Amount>1000)对排序过的数据,用二分法进行快速过滤,可用@b:Orders.select@b(Amount>1000)函数选项还可以组合搭配,比如:Orders.select@1b(Amount>1000)结构化运算函数的参数常常很复杂,比如SQL就需要用各种关键字把一条语句的参数分隔成多个组,但这会动用很多关键字,也使语句结构不统一。SPL支持层次参数,通过分号、逗号、冒号自高而低将参数分为三层,用通用的方式简化复杂参数的表达:join(Orders:o,SellerId ; Employees:e,EId)扩展的Lambda语法普通的Lambda语法不仅要指明表达式(即函数形式的参数),还必须完整地定义表达式本身的参数,否则在数学形式上不够严密,这就让Lambda语法很繁琐。比如用循环函数select过滤集合A,只保留值为偶数的成员,一般形式是:A.select(f(x):{x%2==0} )这里的表达式是x%2==0,表达式的参数是f(x)里的x,x代表集合A里的成员,即循环变量。SPL用固定符号~代表循环变量,当参数是循环变量时就无须再定义参数了。在SPL中,上面的Lambda语法可以简写作:A.select(~ %2==0)普通Lambda语法必须定义表达式用到的每一个参数,除了循环变量外,常用的参数还有循环计数,如果把循环计数也定义到Lambda中,代码就更繁琐了。SPL用固定符号#代表循环计数变量。比如,用函数select过滤集合A,只保留序号是偶数的成员,SPL可以写作:A.select(# %2==0)相对位置经常出现在难度较大的计算中,而且相对位置本身就很难计算,当要使用相对位置时,参数的写法将非常繁琐。SPL用固定形式[序号]代表相对位置:序号AB1=T(“Orders.txt”)/订单序表2=A1.groups(year(Date):y,month(Date):m; sum(Amount):amt)/按年月分组汇总3=A2.derive(amt/amt[-1]:lrr, amt[-1:1].avg():ma)/计算比上期和移动平均无缝集成、低耦合、热切换作为用Java解释的脚本语言,SPL提供了JDBC驱动,可以无缝集成进Java应用程中。简单语句可以像SQL一样直接执行:Class.forName("com.esproc.jdbc.InternalDriver"); Connection conn =DriverManager.getConnection("jdbc:esproc:local://"); PrepareStatement st = conn.prepareStatement("=T(\"D:/Orders.txt\").select(Amount>1000 && Amount<=3000 && like(Client,\"*S*\"))"); ResultSet result=st.execute();复杂计算可以存成脚本文件,以存储过程方式调用Class.forName("com.esproc.jdbc.InternalDriver"); Connection conn =DriverManager.getConnection("jdbc:esproc:local://"); Statement st = connection.(); CallableStatement st = conn.prepareCall("{call splscript1(?, ?)}"); st.setObject(1, 3000); st.setObject(2, 5000); ResultSet result=st.execute();将脚本外置于Java程序,一方面可以降低代码耦合性,另一方面利用解释执行的特点还可以支持热切换,业务逻辑变动时只要修改脚本即可立即生效,不像使用Java时常常要重启整个应用。这种机制特别适合编写微服务架构中的业务处理逻辑。SPL资料SPL官网SPL下载SPL源代码
文章
SQL  ·  存储  ·  Java  ·  编译器  ·  数据库连接  ·  数据处理  ·  Scala  ·  数据库  ·  Kotlin  ·  微服务
2023-02-02
java11新特性(简述八大新特性)
@[toc]1、本地变量类型推断什么是局部变量类型推断?var javastack = "javastack"; System.out.println(javastack);大家看出来了,局部变量类型推断就是左边的类型直接使用 var 定义,而不用写具体的类型,编译器能根据右边的表达式自动推断类型,如上面的 String 。var javastack = "javastack";就等于:String javastack = "javastack";2、字符串加强Java 11 增加了一系列的字符串处理方法,还是如以下所示。// 判断字符串是否为空白 " ".isBlank(); // true // 去除首尾空格 " Javastack ".strip(); // "Javastack" // 去除尾部空格 " Javastack ".stripTrailing(); // " Javastack" // 去除首部空格 " Javastack ".stripLeading(); // "Javastack " // 复制字符串 "Java".repeat(3);// "JavaJavaJava" // 行数统计 "A\nB\nC".lines().count(); // 33、集合加强自 Java 9 开始,Jdk 里面为集合(List/ Set/ Map)都添加了 of 和 copyOf 方法,它们两个都用来创建不可变的集合,来看下它们的使用和区别。示例1:var list = List.of("Java", "Python", "C"); var copy = List.copyOf(list); System.out.println(list == copy); // true示例2:var list = new ArrayList<String>(); var copy = List.copyOf(list); System.out.println(list == copy); // false示例1和2代码差不多,为什么一个为true,一个为false?来看下它们的源码:static <E> List<E> of(E... elements) { switch (elements.length) { // implicit null check of elements case 0: return ImmutableCollections.emptyList(); case 1: return new ImmutableCollections.List12<>(elements[0]); case 2: return new ImmutableCollections.List12<>(elements[0], elements[1]); default: return new ImmutableCollections.ListN<>(elements); } } static <E> List<E> copyOf(Collection<? extends E> coll) { return ImmutableCollections.listCopy(coll); } static <E> List<E> listCopy(Collection<? extends E> coll) { if (coll instanceof AbstractImmutableList && coll.getClass() != SubList.class) { return (List<E>)coll; } else { return (List<E>)List.of(coll.toArray()); } }可以看出 copyOf 方法会先判断来源集合是不是 AbstractImmutableList 类型的,如果是,就直接返回,如果不是,则调用 of 创建一个新的集合。示例2因为用的 new 创建的集合,不属于不可变 AbstractImmutableList 类的子类,所以 copyOf 方法又创建了一个新的实例,所以为false.注意:使用of和copyOf创建的集合为不可变集合,不能进行添加、删除、替换、排序等操作,不然会报 java.lang.UnsupportedOperationException 异常。上面演示了 List 的 of 和 copyOf 方法,Set 和 Map 接口都有。 public static void main(String[] args) { Set<String> names = Set.of("Fred", "Wilma", "Barney", "Betty"); //JDK11之前我们只能这么写 System.out.println(Arrays.toString(names.toArray(new String[names.size()]))); //JDK11之后 可以直接这么写了 System.out.println(Arrays.toString(names.toArray(size -> new String[size]))); System.out.println(Arrays.toString(names.toArray(String[]::new))); }Collection.toArray(IntFunction)在java.util.Collection接口中添加了一个新的默认方法toArray(IntFunction)。此方法允许将集合的元素传输到新创建的所需运行时类型的数组。 public static void main(String[] args) { Set<String> names = Set.of("Fred", "Wilma", "Barney", "Betty"); //JDK11之前我们只能这么写 System.out.println(Arrays.toString(names.toArray(new String[names.size()]))); //JDK11之后 可以直接这么写了 System.out.println(Arrays.toString(names.toArray(size -> new String[size]))); System.out.println(Arrays.toString(names.toArray(String[]::new))); }新方法是现有toArray(T [])方法的重载,该方法将数组实例作为参数。添加重载方法会导致次要源不兼容。以前,形式为coll.toArray(null)的代码将始终解析为现有的toArray方法。使用新的重载方法,此代码现在不明确,将导致编译时错误。 (这只是源不兼容。现有的二进制文件不受影响。)应该更改模糊代码以将null转换为所需的数组类型,例如toArray((Object [])null)或其他一些数组类型。请注意,将null传递给toArray方法指定为抛出NullPointerException。4、Stream 加强Stream 是 Java 8 中的新特性,Java 9 开始对 Stream 增加了以下 4 个新方法。增加单个参数构造方法,可为nullStream.ofNullable(null).count(); // 0 //JDK8木有ofNullable方法哦源码可看看:/** * @since 9 */ public static<T> Stream<T> ofNullable(T t) { return t == null ? Stream.empty() : StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false); }增加 takeWhile 和 dropWhile 方法Stream.of(1, 2, 3, 2, 1) .takeWhile(n -> n < 3) .collect(Collectors.toList()); // [1, 2]takeWhile表示从开始计算,当 n < 3 时就截止。Stream.of(1, 2, 3, 2, 1) .dropWhile(n -> n < 3) .collect(Collectors.toList()); // [3, 2, 1]dropWhile这个和上面的相反,一旦 n < 3 不成立就开始计算3)iterate重载这个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代。 public static void main(String[] args) { // 这构造的是无限流 JDK8开始 Stream.iterate(0, (x) -> x + 1); // 这构造的是小于10就结束的流 JDK9开始 Stream.iterate(0, x -> x < 10, x -> x + 1); }5、Optional 加强Opthonal 也增加了几个非常酷的方法,现在可以很方便的将一个 Optional 转换成一个 Stream, 或者当一个空 Optional 时给它一个替代的。Optional.of("javastack") .orElseThrow(); // javastack Optional.of("javastack") .stream() .count(); // 1 Optional.ofNullable(null) .or(() -> Optional.of("javastack")) .get(); // javastackor方法和stream方法显然都是新增的6、InputStream 加强InputStream 终于有了一个非常有用的方法:transferTo,可以用来将数据直接传输到 OutputStream,这是在处理原始数据流时非常常见的一种用法,如下示例。var classLoader = ClassLoader.getSystemClassLoader(); var inputStream = classLoader.getResourceAsStream("javastack.txt"); var javastack = File.createTempFile("javastack2", "txt"); try (var outputStream = new FileOutputStream(javastack)) { inputStream.transferTo(outputStream); }7、HTTP Client API(重磅)在java9及10被标记incubator的模块jdk.incubator.httpclient,在java11被标记为正式,改为java.net.http模块。这是 Java 9 开始引入的一个处理 HTTP 请求的的孵化 HTTP Client API,该 API 支持同步和异步,而在 Java 11 中已经为正式可用状态,你可以在 java.net 包中找到这个 API。来看一下 HTTP Client 的用法:var request = HttpRequest.newBuilder() .uri(URI.create("https://javastack.cn")) .GET() .build(); var client = HttpClient.newHttpClient(); // 同步 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); // 异步 client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println);上面的 .GET() 可以省略,默认请求方式为 Get!更多使用示例可以看这个 API,后续有机会再做演示。现在 Java 自带了这个 HTTP Client API,我们以后还有必要用 Apache 的 HttpClient 工具包吗?我觉得没啥必要了8、化繁为简,一个命令编译运行源代码看下面的代码。# 编译 javac Javastack.java # 运行 java Javastack在我们的认知里面,要运行一个 Java 源代码必须先编译,再运行,两步执行动作。而在未来的 Java 11 版本中,通过一个 java 命令就直接搞定了,如以下所示。java Javastack.java移除项移除了com.sun.awt.AWTUtilities移除了sun.misc.Unsafe.defineClass,使用 java.lang.invoke.MethodHandles.Lookup.defineClass来替代移除了Thread.destroy()以及 Thread.stop(Throwable)方法移除了sun.nio.ch.disableSystemWideOverlappingFileLockCheck、sun.locale.formatasdefault属性移除了jdk.snmp模块移除了javafx,openjdk估计是从java10版本就移除了,oracle jdk10还尚未移除javafx,而java11版本则oracle的jdk版本也移除了javafx移除了Java Mission Control,从JDK中移除之后,需要自己单独下载移除了这些Root Certificates :Baltimore Cybertrust Code Signing CA,SECOM ,AOL and Swisscom废弃项废弃了Nashorn JavaScript Engine废弃了-XX+AggressiveOpts选项-XX:+UnlockCommercialFeatures以及-XX:+LogCommercialFeatures选项也不- 再需要废弃了Pack200工具及其API说到最后java11是java改为6个月发布一版的策略之后的第一个LTS(Long-Term Support)版本(oracle版本才有LTS),这个版本最主要的特性是:在模块方面移除Java EE以及CORBA模块,在JVM方面引入了实验性的ZGC,在API方面正式提供了HttpClient类。从java11版本开始,不再单独发布JRE或者Server JRE版本了,有需要的可以自己通过jlink去定制runtime image备注:ZGC作为实验性功能包含在内。要启用它,因此需要将-XX:+ UnlockExperimentalVMOptions选项与-XX:+ UseZGC选项结合使用。ZGC的这个实验版具有以下限制:它仅适用于Linux / x64。不支持使用压缩的oops和/或压缩的类点。默认情况下禁用-XX:+UseCompressedOops和-XX:+UseCompressedClassPointers选项。启用它们将不起作用。不支持类卸载。默认情况下禁用-XX:+ ClassUnloading和-XX:+ - - ClassUnloadingWithConcurrentMark选项。启用它们将不起作用。不支持将ZGC与Graal结合使用。附录:(JAVA 1.0 - JAVA 12的版本时间和代号)JDK Version发布时间代号备注1.01996-01-23Oak(橡树)初代版本,伟大的一个里程碑,但是是纯解释运行,使用外挂JIT,性能比较差,运行速度慢1.11997-02-19Sparkler(宝石)JDBC、支持内部类、RMI、反射。。。1.21998-12-08Playground(操场)集合框架、JIT。。。1.32000-05-08Kestrel(红隼)对Java的各个方面都做了大量优化和增强1.42004-02-06Merlin(隼)xml处理、支持IPV6、支持正则表达式52004-09-30Tiger(老虎)泛型、增强for、自动拆装箱、可变参数、静态导入、注解62006-12-11Mustang(野马)支持脚本语言、JDBC4.072011-07-28Dolphin(海豚)switc支持String类型、泛型推断、Nio.2开发包、数值类型可以用二进制字符串表示82014-03-18Spider(蜘蛛)Lambda 表达式、接口默认方法、Stream API 、新的日期API、Nashorn引擎 jjs92017-09-22Modularity(模块化)模块系统、HTTP 2 客户端、多版本兼容 JAR 包、私有接口方法、改进的 Stream API、响应式流(Reactive Streams) API:102018-03-21 var 局部变量类型推断、统一的垃圾回收接口112018-09-25 HTTP客户端(标准)、无操作垃圾收集器。首个LTS版本122019-03-19 新增一个名为 Shenandoah 的垃圾回收器、扩展switch语句的功能、改进 G1 垃圾回收器
文章
Oracle  ·  JavaScript  ·  前端开发  ·  Java  ·  关系型数据库  ·  编译器  ·  数据库连接  ·  API  ·  Apache  ·  数据格式
2023-02-02
最棘手的JAVA面试题(下)
在本文中,我们将从初学者和高级别进行提问, 这对新手和具有多年 Java 开发经验的高级开发人员同样有益。关于Java序列化的10个面试问题 大多数商业项目使用数据库或内存映射文件或只是普通文件, 来满足持久性要求, 只有很少的项目依赖于 Java 中的序列化过程。无论如何,这篇文章不是 Java 序列化教程或如何序列化在 Java 的对象, 但有关序列化机制和序列化 API 的面试问题, 这是值得去任何 Java 面试前先看看以免让一些未知的内容惊到自己。对于那些不熟悉 Java 序列化的人, Java 序列化是用来通过将对象的状态存储到带有.ser扩展名的文件来序列化 Java 中的对象的过程, 并且可以通过这个文件恢复重建 Java对象状态, 这个逆过程称为 deserialization。什么是 Java 序列化 序列化是把对象改成可以存到磁盘或通过网络发送到其他运行中的 Java 虚拟机的二进制格式的过程, 并可以通过反序列化恢复对象状态. Java 序列化API给开发人员提供了一个标准机制, 通过 java.io.Serializable 和 java.io.Externalizable 接口, ObjectInputStream 及ObjectOutputStream 处理对象序列化. Java 程序员可自由选择基于类结构的标准序列化或是他们自定义的二进制格式, 通常认为后者才是最佳实践, 因为序列化的二进制文件格式成为类输出 API的一部分, 可能破坏 Java 中私有和包可见的属性的封装。如何序列化 让 Java 中的类可以序列化很简单. 你的 Java 类只需要实现 java.io.Serializable 接口, JVM 就会把 Object 对象按默认格式序列化. 让一个类是可序列化的需要有意为之. 类可序列会可能为是一个长期代价, 可能会因此而限制你修改或改变其实现. 当你通过实现添加接口来更改类的结构时, 添加或删除任何字段可能会破坏默认序列化, 这可以通过自定义二进制格式使不兼容的可能性最小化, 但仍需要大量的努力来确保向后兼容性。序列化如何限制你更改类的能力的一个示例是 SerialVersionUID。如果不显式声明 SerialVersionUID, 则 JVM 会根据类结构生成其结构, 该结构依赖于类实现接口和可能更改的其他几个因素。假设你新版本的类文件实现的另一个接口, JVM 将生成一个不同的 SerialVersionUID 的, 当你尝试加载旧版本的程序序列化的旧对象时, 你将获得无效类异常 InvalidClassException。问题 1) Java 中的可序列化接口和可外部接口之间的区别是什么?这是 Java 序列化访谈中最常问的问题。下面是我的版本 Externalizable 给我们提供 writeExternal() 和 readExternal() 方法, 这让我们灵活地控制 Java 序列化机制, 而不是依赖于 Java 的默认序列化。正确实现 Externalizable 接口可以显著提高应用程序的性能。问题 2) 可序列化的方法有多少?如果没有方法,那么可序列化接口的用途是什么?可序列化 Serializalbe 接口存在于java.io包中,构成了 Java 序列化机制的核心。它没有任何方法, 在 Java 中也称为标记接口。当类实现 java.io.Serializable 接口时, 它将在 Java 中变得可序列化, 并指示编译器使用 Java 序列化机制序列化此对象。问题 3) 什么是 serialVersionUID ?如果你不定义这个, 会发生什么?我最喜欢的关于Java序列化的问题面试问题之一。serialVersionUID 是一个 private static final long 型 ID, 当它被印在对象上时, 它通常是对象的哈希码,你可以使用 serialver 这个 JDK 工具来查看序列化对象的 serialVersionUID。SerialVerionUID 用于对象的版本控制。也可以在类文件中指定 serialVersionUID。不指定 serialVersionUID的后果是,当你添加或修改类中的任何字段时, 则已序列化类将无法恢复, 因为为新类和旧序列化对象生成的 serialVersionUID 将有所不同。Java 序列化过程依赖于正确的序列化对象恢复状态的, ,并在序列化对象序列版本不匹配的情况下引发 java.io.InvalidClassException 无效类异常,了解有关 serialVersionUID 详细信息,请参阅这篇文章, 需要 FQ。问题 4) 序列化时,你希望某些成员不要序列化?你如何实现它?另一个经常被问到的序列化面试问题。这也是一些时候也问, 如什么是瞬态 trasient 变量, 瞬态和静态变量会不会得到序列化等,所以,如果你不希望任何字段是对象的状态的一部分, 然后声明它静态或瞬态根据你的需要, 这样就不会是在 Java 序列化过程中被包含在内。问题 5) 如果类中的一个成员未实现可序列化接口, 会发生什么情况?关于Java序列化过程的一个简单问题。如果尝试序列化实现可序列化的类的对象,但该对象包含对不可序列化类的引用,则在运行时将引发不可序列化异常 NotSerializableException, 这就是为什么我始终将一个可序列化警报(在我的代码注释部分中), 代码注释最佳实践之一, 指示开发人员记住这一事实, 在可序列化类中添加新字段时要注意。问题 6) 如果类是可序列化的, 但其超类不是, 则反序列化后从超级类继承的实例变量的状态如何?Java 序列化过程仅在对象层次都是可序列化结构中继续, 即实现 Java 中的可序列化接口, 并且从超级类继承的实例变量的值将通过调用构造函数初始化, 在反序列化过程中不可序列化的超级类。一旦构造函数链接将启动, 就不可能停止, 因此, 即使层次结构中较高的类实现可序列化接口, 也将执行构造函数。正如你从陈述中看到的, 这个序列化面试问题看起来非常棘手和有难度, 但如果你熟悉关键概念, 则并不难。问题 7) 是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?答案是肯定的, 你可以。我们都知道,对于序列化一个对象需调用 ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。如果在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。你可以在此处通过执行任何类型的预处理或后处理任务来自定义对象序列化和反序列化的行为。需要注意的重要一点是要声明这些方法为私有方法, 以避免被继承、重写或重载。由于只有 Java 虚拟机可以调用类的私有方法, 你的类的完整性会得到保留, 并且 Java 序列化将正常工作。在我看来, 这是在任何 Java 序列化面试中可以问的最好问题之一, 一个很好的后续问题是, 为什么要为你的对象提供自定义序列化表单?问题 8) 假设新类的超级类实现可序列化接口, 如何避免新类被序列化?在 Java 序列化中一个棘手的面试问题。如果类的 Super 类已经在 Java 中实现了可序列化接口, 那么它在 Java 中已经可以序列化, 因为你不能取消接口, 它不可能真正使它无法序列化类, 但是有一种方法可以避免新类序列化。为了避免 Java 序列化,你需要在类中实现 writeObject() 和 readObject() 方法, 并且需要从该方法引发不序列化异常NotSerializableException。这是自定义 Java 序列化过程的另一个好处, 如上述序列化面试问题中所述, 并且通常随着面试进度, 它作为后续问题提出。问题 9) 在 Java 中的序列化和反序列化过程中使用哪些方法?这是很常见的面试问题, 在序列化基本上面试官试图知道: 你是否熟悉 readObject() 的用法、writeObject()、readExternal() 和 writeExternal()。Java 序列化由java.io.ObjectOutputStream类完成。该类是一个筛选器流, 它封装在较低级别的字节流中, 以处理序列化机制。要通过序列化机制存储任何对象, 我们调用 ObjectOutputStream.writeObject(savethisobject), 并反序列化该对象, 我们称之为 ObjectInputStream.readObject()方法。调用以 writeObject() 方法在 java 中触发序列化过程。关于 readObject() 方法, 需要注意的一点很重要一点是, 它用于从持久性读取字节, 并从这些字节创建对象, 并返回一个对象, 该对象需要类型强制转换为正确的类型。问题 10) 假设你有一个类,它序列化并存储在持久性中, 然后修改了该类以添加新字段。如果对已序列化的对象进行反序列化, 会发生什么情况?这取决于类是否具有其自己的 serialVersionUID。正如我们从上面的问题知道, 如果我们不提供 serialVersionUID, 则 Java 编译器将生成它, 通常它等于对象的哈希代码。通过添加任何新字段, 有可能为该类新版本生成的新 serialVersionUID 与已序列化的对象不同, 在这种情况下, Java 序列化 API 将引发 java.io.InvalidClassException, 因此建议在代码中拥有自己的 serialVersionUID, 并确保在单个类中始终保持不变。11) Java序列化机制中的兼容更改和不兼容更改是什么?真正的挑战在于通过添加任何字段、方法或删除任何字段或方法来更改类结构, 方法是使用已序列化的对象。根据 Java 序列化规范, 添加任何字段或方法都面临兼容的更改和更改类层次结构或取消实现的可序列化接口, 有些接口在非兼容更改下。对于兼容和非兼容更改的完整列表, 我建议阅读 Java 序列化规范。12) 我们可以通过网络传输一个序列化的对象吗?是的 ,你可以通过网络传输序列化对象, 因为 Java 序列化对象仍以字节的形式保留, 字节可以通过网络发送。你还可以将序列化对象存储在磁盘或数据库中作为 Blob。13) 在 Java 序列化期间,哪些变量未序列化?这个问题问得不同, 但目的还是一样的, Java开发人员是否知道静态和瞬态变量的细节。由于静态变量属于类, 而不是对象, 因此它们不是对象状态的一部分, 因此在 Java 序列化过程中不会保存它们。由于 Java 序列化仅保留对象的状态,而不是对象本身。瞬态变量也不包含在 Java 序列化过程中, 并且不是对象的序列化状态的一部分。在提出这个问题之后,面试官会询问后续内容, 如果你不存储这些变量的值, 那么一旦对这些对象进行反序列化并重新创建这些变量, 这些变量的价值是多少?这是你们要考虑的。9 为什么Java中 wait 方法需要在 synchronized 的方法中调用?另一个棘手的核心 Java 问题,wait 和 notify。它们是在有 synchronized 标记的方法或 synchronized 块中调用的,因为 wait 和 modify 需要监视对其上调用 wait 或 notify-get 的 Object。大多数Java开发人员都知道对象类的 wait(),notify() 和 notifyAll()方法必须在Java中的 synchronized 方法或 synchronized 块中调用, 但是我们想过多少次, 为什么在 Java 中 wait, notify 和 notifyAll 来自 synchronized 块或方法?最近这个问题在Java面试中被问到我的一位朋友,他思索了一下,并回答说: 如果我们不从同步上下文中调用 wait() 或 notify() 方法,我们将在 Java 中收到 IllegalMonitorStateException。他的回答从实际效果上年是正确的,但面试官对这样的答案不会完全满意,并希望向他解释这个问题。面试结束后 他和我讨论了同样的问题,我认为他应该告诉面试官关于 Java 中 wait()和 notify()之间的竞态条件,如果我们不在同步方法或块中调用它们就可能存在。让我们看看竞态条件如何在Java程序中发生。它也是流行的线程面试问题之一,并经常在电话和面对面的Java开发人员面试中出现。因此,如果你正在准备Java面试,那么你应该准备这样的问题,并且可以真正帮助你的一本书是《Java程序员面试公式书》的。这是一本罕见的书,涵盖了Java访谈的几乎所有重要主题,例如核心Java,多线程,IO 和 NIO 以及 Spring 和 Hibernate 等框架。你可以在这里查看。为什么要等待来自 Java中的 synchronized 方法的 wait方法为什么必须从 Java 中的 synchronized 块或方法调用 ?我们主要使用 wait(),notify() 或 notifyAll() 方法用于 Java 中的线程间通信。一个线程在检查条件后正在等待,例如,在经典的生产者 - 消费者问题中,如果缓冲区已满,则生产者线程等待,并且消费者线程通过使用元素在缓冲区中创建空间后通知生产者线程。调用notify()或notifyAll()方法向单个或多个线程发出一个条件已更改的通知,并且一旦通知线程离开 synchronized 块,正在等待的所有线程开始获取正在等待的对象锁定,幸运的线程在重新获取锁之后从 wait() 方法返回并继续进行。让我们将整个操作分成几步,以查看Java中wait()和notify()方法之间的竞争条件的可能性,我们将使用Produce Consumer 线程示例更好地理解方案:Producer 线程测试条件(缓冲区是是否完整)并确认必须等待(找到缓冲区已满)。Consumer 线程在使用缓冲区中的元素后设置条件。Consumer 线程调用 notify() 方法; 这是不会被听到的,因为 Producer 线程还没有等待。Producer 线程调用 wait() 方法并进入等待状态。因此,由于竞态条件,我们可能会丢失通知,如果我们使用缓冲区或只使用一个元素,生产线程将永远等待,你的程序将挂起。“在java同步中等待 notify 和 notifyall 现在让我们考虑如何解决这个潜在的竞态条件?这个竞态条件通过使用 Java 提供的 synchronized 关键字和锁定来解决。为了调用 wait(),notify() 或 notifyAll(), 在Java中,我们必须获得对我们调用方法的对象的锁定。由于 Java 中的 wait() 方法在等待之前释放锁定并在从 wait() 返回之前重新获取锁定方法,我们必须使用这个锁来确保检查条件(缓冲区是否已满)和设置条件(从缓冲区获取元素)是原子的,这可以通过在 Java 中使用 synchronized 方法或块来实现。我不确定这是否是面试官实际期待的,但这个我认为至少有意义,请纠正我如果我错了,请告诉我们是否还有其他令人信服的理由调用 wait(),notify() 或 Java 中的 notifyAll() 方法。总结一下,我们用 Java 中的 synchronized 方法或 synchronized 块调用 Java 中的 wait(),notify() 或 notifyAll() 方法来避免:1) Java 会抛出 IllegalMonitorStateException,如果我们不调用来自同步上下文的wait(),notify()或者notifyAll()方法。2) Javac 中 wait 和 notify 方法之间的任何潜在竞争条件。10 你能用Java覆盖静态方法吗?如果我在子类中创建相同的方法是编译时错误?不,你不能在Java中覆盖静态方法,但在子类中声明一个完全相同的方法不是编译时错误,这称为隐藏在Java中的方法。你不能覆盖Java中的静态方法,因为方法覆盖基于运行时的动态绑定,静态方法在编译时使用静态绑定进行绑定。虽然可以在子类中声明一个具有相同名称和方法签名的方法,看起来可以在Java中覆盖静态方法,但实际上这是方法隐藏。Java不会在运行时解析方法调用,并且根据用于调用静态方法的 Object 类型,将调用相应的方法。这意味着如果你使用父类的类型来调用静态方法,那么原始静态将从父类中调用,另一方面如果你使用子类的类型来调用静态方法,则会调用来自子类的方法。简而言之,你无法在Java中覆盖静态方法。如果你使用像Eclipse或Netbeans这样的Java IDE,它们将显示警告静态方法应该使用类名而不是使用对象来调用,因为静态方法不能在Java中重写。/** *Java program which demonstrate that we can not override static method in Java.Had Static method can be overridden, with Super class type and sub class objectstatic method from sub class would be called in our example, which is not the case.*/public class CanWeOverrideStaticMethod {public static void main(String args[]) { Screen scrn = new ColorScreen(); //if we can override static , this should call method from Child class scrn.show(); //IDE will show warning, static method should be called from classname } }class Screen{/* * public static method which can not be overridden in Java */ public static void show(){ System.out.printf("Static method from parent class"); }}class ColorScreen extends Screen{/* * static method of same name and method signature as existed in super * class, this is not method overriding instead this is called * method hiding in Java */ public static void show(){ System.err.println("Overridden static method in Child Class in Java"); }}输出:Static method from parent class此输出确认你无法覆盖Java中的静态方法,并且静态方法基于类型信息而不是基于Object进行绑定。如果要覆盖静态mehtod,则会调用子类或 ColorScreen 中的方法。这一切都在讨论中我们可以覆盖Java中的静态方法。我们已经确认没有,我们不能覆盖静态方法,我们只能在Java中隐藏静态方法。创建具有相同名称和mehtod签名的静态方法称为Java隐藏方法。IDE将显示警告:"静态方法应该使用类名而不是使用对象来调用", 因为静态方法不能在Java中重写。这些是我的核心Java面试问题和答案的清单。对于有经验的程序员来说,一些Java问题看起来并不那么难,但对于Java中的中级和初学者来说,它们真的很难回答。
文章
存储  ·  IDE  ·  Java  ·  程序员  ·  编译器  ·  数据库连接  ·  API  ·  开发工具  ·  数据库  ·  Android开发
2023-02-02
最棘手的JAVA面试题(下)
在本文中,我们将从初学者和高级别进行提问, 这对新手和具有多年 Java 开发经验的高级开发人员同样有益。关于Java序列化的10个面试问题 大多数商业项目使用数据库或内存映射文件或只是普通文件, 来满足持久性要求, 只有很少的项目依赖于 Java 中的序列化过程。无论如何,这篇文章不是 Java 序列化教程或如何序列化在 Java 的对象, 但有关序列化机制和序列化 API 的面试问题, 这是值得去任何 Java 面试前先看看以免让一些未知的内容惊到自己。对于那些不熟悉 Java 序列化的人, Java 序列化是用来通过将对象的状态存储到带有.ser扩展名的文件来序列化 Java 中的对象的过程, 并且可以通过这个文件恢复重建 Java对象状态, 这个逆过程称为 deserialization。什么是 Java 序列化 序列化是把对象改成可以存到磁盘或通过网络发送到其他运行中的 Java 虚拟机的二进制格式的过程, 并可以通过反序列化恢复对象状态. Java 序列化API给开发人员提供了一个标准机制, 通过 java.io.Serializable 和 java.io.Externalizable 接口, ObjectInputStream 及ObjectOutputStream 处理对象序列化. Java 程序员可自由选择基于类结构的标准序列化或是他们自定义的二进制格式, 通常认为后者才是最佳实践, 因为序列化的二进制文件格式成为类输出 API的一部分, 可能破坏 Java 中私有和包可见的属性的封装。如何序列化 让 Java 中的类可以序列化很简单. 你的 Java 类只需要实现 java.io.Serializable 接口, JVM 就会把 Object 对象按默认格式序列化. 让一个类是可序列化的需要有意为之. 类可序列会可能为是一个长期代价, 可能会因此而限制你修改或改变其实现. 当你通过实现添加接口来更改类的结构时, 添加或删除任何字段可能会破坏默认序列化, 这可以通过自定义二进制格式使不兼容的可能性最小化, 但仍需要大量的努力来确保向后兼容性。序列化如何限制你更改类的能力的一个示例是 SerialVersionUID。如果不显式声明 SerialVersionUID, 则 JVM 会根据类结构生成其结构, 该结构依赖于类实现接口和可能更改的其他几个因素。假设你新版本的类文件实现的另一个接口, JVM 将生成一个不同的 SerialVersionUID 的, 当你尝试加载旧版本的程序序列化的旧对象时, 你将获得无效类异常 InvalidClassException。问题 1) Java 中的可序列化接口和可外部接口之间的区别是什么?这是 Java 序列化访谈中最常问的问题。下面是我的版本 Externalizable 给我们提供 writeExternal() 和 readExternal() 方法, 这让我们灵活地控制 Java 序列化机制, 而不是依赖于 Java 的默认序列化。正确实现 Externalizable 接口可以显著提高应用程序的性能。问题 2) 可序列化的方法有多少?如果没有方法,那么可序列化接口的用途是什么?可序列化 Serializalbe 接口存在于java.io包中,构成了 Java 序列化机制的核心。它没有任何方法, 在 Java 中也称为标记接口。当类实现 java.io.Serializable 接口时, 它将在 Java 中变得可序列化, 并指示编译器使用 Java 序列化机制序列化此对象。问题 3) 什么是 serialVersionUID ?如果你不定义这个, 会发生什么?我最喜欢的关于Java序列化的问题面试问题之一。serialVersionUID 是一个 private static final long 型 ID, 当它被印在对象上时, 它通常是对象的哈希码,你可以使用 serialver 这个 JDK 工具来查看序列化对象的 serialVersionUID。SerialVerionUID 用于对象的版本控制。也可以在类文件中指定 serialVersionUID。不指定 serialVersionUID的后果是,当你添加或修改类中的任何字段时, 则已序列化类将无法恢复, 因为为新类和旧序列化对象生成的 serialVersionUID 将有所不同。Java 序列化过程依赖于正确的序列化对象恢复状态的, ,并在序列化对象序列版本不匹配的情况下引发 java.io.InvalidClassException 无效类异常,了解有关 serialVersionUID 详细信息,请参阅这篇文章, 需要 FQ。问题 4) 序列化时,你希望某些成员不要序列化?你如何实现它?另一个经常被问到的序列化面试问题。这也是一些时候也问, 如什么是瞬态 trasient 变量, 瞬态和静态变量会不会得到序列化等,所以,如果你不希望任何字段是对象的状态的一部分, 然后声明它静态或瞬态根据你的需要, 这样就不会是在 Java 序列化过程中被包含在内。问题 5) 如果类中的一个成员未实现可序列化接口, 会发生什么情况?关于Java序列化过程的一个简单问题。如果尝试序列化实现可序列化的类的对象,但该对象包含对不可序列化类的引用,则在运行时将引发不可序列化异常 NotSerializableException, 这就是为什么我始终将一个可序列化警报(在我的代码注释部分中), 代码注释最佳实践之一, 指示开发人员记住这一事实, 在可序列化类中添加新字段时要注意。问题 6) 如果类是可序列化的, 但其超类不是, 则反序列化后从超级类继承的实例变量的状态如何?Java 序列化过程仅在对象层次都是可序列化结构中继续, 即实现 Java 中的可序列化接口, 并且从超级类继承的实例变量的值将通过调用构造函数初始化, 在反序列化过程中不可序列化的超级类。一旦构造函数链接将启动, 就不可能停止, 因此, 即使层次结构中较高的类实现可序列化接口, 也将执行构造函数。正如你从陈述中看到的, 这个序列化面试问题看起来非常棘手和有难度, 但如果你熟悉关键概念, 则并不难。问题 7) 是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?答案是肯定的, 你可以。我们都知道,对于序列化一个对象需调用 ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。如果在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。你可以在此处通过执行任何类型的预处理或后处理任务来自定义对象序列化和反序列化的行为。需要注意的重要一点是要声明这些方法为私有方法, 以避免被继承、重写或重载。由于只有 Java 虚拟机可以调用类的私有方法, 你的类的完整性会得到保留, 并且 Java 序列化将正常工作。在我看来, 这是在任何 Java 序列化面试中可以问的最好问题之一, 一个很好的后续问题是, 为什么要为你的对象提供自定义序列化表单?问题 8) 假设新类的超级类实现可序列化接口, 如何避免新类被序列化?在 Java 序列化中一个棘手的面试问题。如果类的 Super 类已经在 Java 中实现了可序列化接口, 那么它在 Java 中已经可以序列化, 因为你不能取消接口, 它不可能真正使它无法序列化类, 但是有一种方法可以避免新类序列化。为了避免 Java 序列化,你需要在类中实现 writeObject() 和 readObject() 方法, 并且需要从该方法引发不序列化异常NotSerializableException。这是自定义 Java 序列化过程的另一个好处, 如上述序列化面试问题中所述, 并且通常随着面试进度, 它作为后续问题提出。问题 9) 在 Java 中的序列化和反序列化过程中使用哪些方法?这是很常见的面试问题, 在序列化基本上面试官试图知道: 你是否熟悉 readObject() 的用法、writeObject()、readExternal() 和 writeExternal()。Java 序列化由java.io.ObjectOutputStream类完成。该类是一个筛选器流, 它封装在较低级别的字节流中, 以处理序列化机制。要通过序列化机制存储任何对象, 我们调用 ObjectOutputStream.writeObject(savethisobject), 并反序列化该对象, 我们称之为 ObjectInputStream.readObject()方法。调用以 writeObject() 方法在 java 中触发序列化过程。关于 readObject() 方法, 需要注意的一点很重要一点是, 它用于从持久性读取字节, 并从这些字节创建对象, 并返回一个对象, 该对象需要类型强制转换为正确的类型。问题 10) 假设你有一个类,它序列化并存储在持久性中, 然后修改了该类以添加新字段。如果对已序列化的对象进行反序列化, 会发生什么情况?这取决于类是否具有其自己的 serialVersionUID。正如我们从上面的问题知道, 如果我们不提供 serialVersionUID, 则 Java 编译器将生成它, 通常它等于对象的哈希代码。通过添加任何新字段, 有可能为该类新版本生成的新 serialVersionUID 与已序列化的对象不同, 在这种情况下, Java 序列化 API 将引发 java.io.InvalidClassException, 因此建议在代码中拥有自己的 serialVersionUID, 并确保在单个类中始终保持不变。11) Java序列化机制中的兼容更改和不兼容更改是什么?真正的挑战在于通过添加任何字段、方法或删除任何字段或方法来更改类结构, 方法是使用已序列化的对象。根据 Java 序列化规范, 添加任何字段或方法都面临兼容的更改和更改类层次结构或取消实现的可序列化接口, 有些接口在非兼容更改下。对于兼容和非兼容更改的完整列表, 我建议阅读 Java 序列化规范。12) 我们可以通过网络传输一个序列化的对象吗?是的 ,你可以通过网络传输序列化对象, 因为 Java 序列化对象仍以字节的形式保留, 字节可以通过网络发送。你还可以将序列化对象存储在磁盘或数据库中作为 Blob。13) 在 Java 序列化期间,哪些变量未序列化?这个问题问得不同, 但目的还是一样的, Java开发人员是否知道静态和瞬态变量的细节。由于静态变量属于类, 而不是对象, 因此它们不是对象状态的一部分, 因此在 Java 序列化过程中不会保存它们。由于 Java 序列化仅保留对象的状态,而不是对象本身。瞬态变量也不包含在 Java 序列化过程中, 并且不是对象的序列化状态的一部分。在提出这个问题之后,面试官会询问后续内容, 如果你不存储这些变量的值, 那么一旦对这些对象进行反序列化并重新创建这些变量, 这些变量的价值是多少?这是你们要考虑的。9 为什么Java中 wait 方法需要在 synchronized 的方法中调用?另一个棘手的核心 Java 问题,wait 和 notify。它们是在有 synchronized 标记的方法或 synchronized 块中调用的,因为 wait 和 modify 需要监视对其上调用 wait 或 notify-get 的 Object。大多数Java开发人员都知道对象类的 wait(),notify() 和 notifyAll()方法必须在Java中的 synchronized 方法或 synchronized 块中调用, 但是我们想过多少次, 为什么在 Java 中 wait, notify 和 notifyAll 来自 synchronized 块或方法?最近这个问题在Java面试中被问到我的一位朋友,他思索了一下,并回答说: 如果我们不从同步上下文中调用 wait() 或 notify() 方法,我们将在 Java 中收到 IllegalMonitorStateException。他的回答从实际效果上年是正确的,但面试官对这样的答案不会完全满意,并希望向他解释这个问题。面试结束后 他和我讨论了同样的问题,我认为他应该告诉面试官关于 Java 中 wait()和 notify()之间的竞态条件,如果我们不在同步方法或块中调用它们就可能存在。让我们看看竞态条件如何在Java程序中发生。它也是流行的线程面试问题之一,并经常在电话和面对面的Java开发人员面试中出现。因此,如果你正在准备Java面试,那么你应该准备这样的问题,并且可以真正帮助你的一本书是《Java程序员面试公式书》的。这是一本罕见的书,涵盖了Java访谈的几乎所有重要主题,例如核心Java,多线程,IO 和 NIO 以及 Spring 和 Hibernate 等框架。你可以在这里查看。为什么要等待来自 Java中的 synchronized 方法的 wait方法为什么必须从 Java 中的 synchronized 块或方法调用 ?我们主要使用 wait(),notify() 或 notifyAll() 方法用于 Java 中的线程间通信。一个线程在检查条件后正在等待,例如,在经典的生产者 - 消费者问题中,如果缓冲区已满,则生产者线程等待,并且消费者线程通过使用元素在缓冲区中创建空间后通知生产者线程。调用notify()或notifyAll()方法向单个或多个线程发出一个条件已更改的通知,并且一旦通知线程离开 synchronized 块,正在等待的所有线程开始获取正在等待的对象锁定,幸运的线程在重新获取锁之后从 wait() 方法返回并继续进行。让我们将整个操作分成几步,以查看Java中wait()和notify()方法之间的竞争条件的可能性,我们将使用Produce Consumer 线程示例更好地理解方案:Producer 线程测试条件(缓冲区是是否完整)并确认必须等待(找到缓冲区已满)。Consumer 线程在使用缓冲区中的元素后设置条件。Consumer 线程调用 notify() 方法; 这是不会被听到的,因为 Producer 线程还没有等待。Producer 线程调用 wait() 方法并进入等待状态。因此,由于竞态条件,我们可能会丢失通知,如果我们使用缓冲区或只使用一个元素,生产线程将永远等待,你的程序将挂起。“在java同步中等待 notify 和 notifyall 现在让我们考虑如何解决这个潜在的竞态条件?这个竞态条件通过使用 Java 提供的 synchronized 关键字和锁定来解决。为了调用 wait(),notify() 或 notifyAll(), 在Java中,我们必须获得对我们调用方法的对象的锁定。由于 Java 中的 wait() 方法在等待之前释放锁定并在从 wait() 返回之前重新获取锁定方法,我们必须使用这个锁来确保检查条件(缓冲区是否已满)和设置条件(从缓冲区获取元素)是原子的,这可以通过在 Java 中使用 synchronized 方法或块来实现。我不确定这是否是面试官实际期待的,但这个我认为至少有意义,请纠正我如果我错了,请告诉我们是否还有其他令人信服的理由调用 wait(),notify() 或 Java 中的 notifyAll() 方法。总结一下,我们用 Java 中的 synchronized 方法或 synchronized 块调用 Java 中的 wait(),notify() 或 notifyAll() 方法来避免:1) Java 会抛出 IllegalMonitorStateException,如果我们不调用来自同步上下文的wait(),notify()或者notifyAll()方法。2) Javac 中 wait 和 notify 方法之间的任何潜在竞争条件。10 你能用Java覆盖静态方法吗?如果我在子类中创建相同的方法是编译时错误?不,你不能在Java中覆盖静态方法,但在子类中声明一个完全相同的方法不是编译时错误,这称为隐藏在Java中的方法。你不能覆盖Java中的静态方法,因为方法覆盖基于运行时的动态绑定,静态方法在编译时使用静态绑定进行绑定。虽然可以在子类中声明一个具有相同名称和方法签名的方法,看起来可以在Java中覆盖静态方法,但实际上这是方法隐藏。Java不会在运行时解析方法调用,并且根据用于调用静态方法的 Object 类型,将调用相应的方法。这意味着如果你使用父类的类型来调用静态方法,那么原始静态将从父类中调用,另一方面如果你使用子类的类型来调用静态方法,则会调用来自子类的方法。简而言之,你无法在Java中覆盖静态方法。如果你使用像Eclipse或Netbeans这样的Java IDE,它们将显示警告静态方法应该使用类名而不是使用对象来调用,因为静态方法不能在Java中重写。/** *Java program which demonstrate that we can not override static method in Java.Had Static method can be overridden, with Super class type and sub class objectstatic method from sub class would be called in our example, which is not the case.*/public class CanWeOverrideStaticMethod {public static void main(String args[]) { Screen scrn = new ColorScreen(); //if we can override static , this should call method from Child class scrn.show(); //IDE will show warning, static method should be called from classname } }class Screen{/* * public static method which can not be overridden in Java */ public static void show(){ System.out.printf("Static method from parent class"); }}class ColorScreen extends Screen{/* * static method of same name and method signature as existed in super * class, this is not method overriding instead this is called * method hiding in Java */ public static void show(){ System.err.println("Overridden static method in Child Class in Java"); }}输出:Static method from parent class此输出确认你无法覆盖Java中的静态方法,并且静态方法基于类型信息而不是基于Object进行绑定。如果要覆盖静态mehtod,则会调用子类或 ColorScreen 中的方法。这一切都在讨论中我们可以覆盖Java中的静态方法。我们已经确认没有,我们不能覆盖静态方法,我们只能在Java中隐藏静态方法。创建具有相同名称和mehtod签名的静态方法称为Java隐藏方法。IDE将显示警告:"静态方法应该使用类名而不是使用对象来调用", 因为静态方法不能在Java中重写。这些是我的核心Java面试问题和答案的清单。对于有经验的程序员来说,一些Java问题看起来并不那么难,但对于Java中的中级和初学者来说,它们真的很难回答。
文章
存储  ·  IDE  ·  Java  ·  程序员  ·  编译器  ·  数据库连接  ·  API  ·  开发工具  ·  数据库  ·  Android开发
2023-02-02
最棘手的JAVA面试题(上)
这是收集的10个最棘手的Java面试问题列表。这些问题主要来自 Java 核心部分 ,不涉及 Java EE 相关问题。你可能知道这些棘手的 Java 问题的答案,或者觉得这些不足以挑战你的 Java 知识,但这些问题都是容易在各种 Java 面试中被问到的,而且包括我的朋友和同事在内的许多程序员都觉得很难回答。1 为什么等待和通知是在 Object 类而不是 Thread 中声明的?一个棘手的 Java 问题,如果 Java编程语言不是你设计的,你怎么能回答这个问题呢。Java编程的常识和深入了解有助于回答这种棘手的 Java 核心方面的面试问题。为什么 wait,notify 和 notifyAll 是在 Object 类中定义的而不是在 Thread 类中定义这是有名的 Java 面试问题,招2~4年经验的到高级 Java 开发人员面试都可能碰到。这个问题的好在它能反映了面试者对等待通知机制的了解, 以及他对此主题的理解是否明确。就像为什么 Java 中不支持多继承或者为什么 String 在 Java 中是 final 的问题一样,这个问题也可能有多个答案。为什么在 Object 类中定义 wait 和 notify 方法,每个人都能说出一些理由。从我的面试经验来看, wait 和 nofity 仍然是大多数Java 程序员最困惑的,特别是2到3年的开发人员,如果他们要求使用 wait 和 notify, 他们会很困惑。因此,如果你去参加 Java 面试,请确保对 wait 和 notify 机制有充分的了解,并且可以轻松地使用 wait 来编写代码,并通过生产者-消费者问题或实现阻塞队列等了解通知的机制。为什么等待和通知需要从同步块或方法中调用, 以及 Java 中的 wait,sleep 和 yield 方法之间的差异,如果你还没有读过,你会觉得有趣。为何 wait,notify 和 notifyAll 属于 Object 类? 为什么它们不应该在 Thread 类中? 以下是我认为有意义的一些想法:1) wait 和 notify 不仅仅是普通方法或同步工具,更重要的是它们是 Java 中两个线程之间的通信机制。对语言设计者而言, 如果不能通过 Java 关键字(例如 synchronized)实现通信此机制,同时又要确保这个机制对每个对象可用, 那么 Object 类则是的正确声明位置。记住同步和等待通知是两个不同的领域,不要把它们看成是相同的或相关的。同步是提供互斥并确保 Java 类的线程安全,而 wait 和 notify 是两个线程之间的通信机制。2) 每个对象都可上锁,这是在 Object 类而不是 Thread 类中声明 wait 和 notify 的另一个原因。3) 在 Java 中为了进入代码的临界区,线程需要锁定并等待锁定,他们不知道哪些线程持有锁,而只是知道锁被某个线程持有, 并且他们应该等待取得锁, 而不是去了解哪个线程在同步块内,并请求它们释放锁定。4) Java 是基于 Hoare 的监视器的思想。在Java中,所有对象都有一个监视器。线程在监视器上等待,为执行等待,我们需要2个参数:一个线程、一个监视器(任何对象)在 Java 设计中,线程不能被指定,它总是运行当前代码的线程。但是,我们可以指定监视器(这是我们称之为等待的对象)。这是一个很好的设计,因为如果我们可以让任何其他线程在所需的监视器上等待,这将导致“入侵”,导致在设计并发程序时会遇到困难。请记住,在 Java 中,所有在另一个线程的执行中侵入的操作都被弃用了(例如 stop 方法)。2 为什么Java中不支持多重继承?我发现这个 Java 核心问题很难回答,因为你的答案可能不会让面试官满意,在大多数情况下,面试官正在寻找答案中的关键点,如果你提到这些关键点,面试官会很高兴。在 Java 中回答这种棘手问题的关键是准备好相关主题, 以应对后续的各种可能的问题。这是非常经典的问题,与为什么 String 在 Java 中是不可变的很类似; 这两个问题之间的相似之处在于它们主要是由 Java 创作者的设计决策使然。为什么Java不支持多重继承, 可以考虑以下两点:1)第一个原因是围绕钻石形继承问题产生的歧义,考虑一个类 A 有 foo() 方法, 然后 B 和 C 派生自 A, 并且有自己的 foo() 实现,现在 D 类使用多个继承派生自 B 和C,如果我们只引用 foo(), 编译器将无法决定它应该调用哪个 foo()。这也称为 Diamond 问题,因为这个继承方案的结构类似于菱形,见下图: A foo() / / foo() B C foo() / / D foo()即使我们删除钻石的顶部 A 类并允许多重继承,我们也将看到这个问题含糊性的一面。如果你把这个理由告诉面试官,他会问为什么 C++ 可以支持多重继承而 Java不行。嗯,在这种情况下,我会试着向他解释我下面给出的第二个原因,它不是因为技术难度, 而是更多的可维护和更清晰的设计是驱动因素, 虽然这只能由 Java 言语设计师确认,我们只是推测。维基百科链接有一些很好的解释,说明在使用多重继承时,由于钻石问题,不同的语言地址问题是如何产生的。2)对我来说第二个也是更有说服力的理由是,多重继承确实使设计复杂化并在转换、构造函数链接等过程中产生问题。假设你需要多重继承的情况并不多,简单起见,明智的决定是省略它。此外,Java 可以通过使用接口支持单继承来避免这种歧义。由于接口只有方法声明而且没有提供任何实现,因此只有一个特定方法的实现,因此不会有任何歧义。3 为什么Java不支持运算符重载?另一个类似棘手的Java问题。为什么 C++ 支持运算符重载而 Java 不支持? 有人可能会说+运算符在 Java 中已被重载用于字符串连接,不要被这些论据所欺骗。与 C++ 不同,Java 不支持运算符重载。Java 不能为程序员提供自由的标准算术运算符重载,例如+, - ,*和/等。如果你以前用过 C++,那么 Java 与 C++ 相比少了很多功能,例如 Java 不支持多重继承,Java中没有指针,Java中没有引用传递。另一个类似的问题是关于 Java 通过引用传递,这主要表现为 Java 是通过值还是引用传参。虽然我不知道背后的真正原因,但我认为以下说法有些道理,为什么 Java 不支持运算符重载。1)简单性和清晰性。清晰性是Java设计者的目标之一。设计者不是只想复制语言,而是希望拥有一种清晰,真正面向对象的语言。添加运算符重载比没有它肯定会使设计更复杂,并且它可能导致更复杂的编译器, 或减慢 JVM,因为它需要做额外的工作来识别运算符的实际含义,并减少优化的机会, 以保证 Java 中运算符的行为。2)避免编程错误。Java 不允许用户定义的运算符重载,因为如果允许程序员进行运算符重载,将为同一运算符赋予多种含义,这将使任何开发人员的学习曲线变得陡峭,事情变得更加混乱。据观察,当语言支持运算符重载时,编程错误会增加,从而增加了开发和交付时间。由于 Java 和 JVM 已经承担了大多数开发人员的责任,如在通过提供垃圾收集器进行内存管理时,因为这个功能增加污染代码的机会, 成为编程错误之源, 因此没有多大意义。3)JVM复杂性。从JVM的角度来看,支持运算符重载使问题变得更加困难。通过更直观,更干净的方式使用方法重载也能实现同样的事情,因此不支持 Java 中的运算符重载是有意义的。与相对简单的 JVM 相比,复杂的 JVM 可能导致 JVM 更慢,并为保证在 Java 中运算符行为的确定性从而减少了优化代码的机会。4)让开发工具处理更容易。这是在 Java 中不支持运算符重载的另一个好处。省略运算符重载使语言更容易处理,这反过来又更容易开发处理语言的工具,例如 IDE 或重构工具。Java 中的重构工具远胜于 C++。4 为什么 String 在 Java 中是不可变的?我最喜欢的 Java 面试问题,很棘手,但同时也非常有用。一些面试者也常问这个问题,为什么 String 在 Java 中是 final 的。字符串在 Java 中是不可变的,因为 String 对象缓存在 String 池中。由于缓存的字符串在多个客户之间共享,因此始终存在风险,其中一个客户的操作会影响所有其他客户。例如,如果一段代码将 String “Test” 的值更改为 “TEST”,则所有其他客户也将看到该值。由于 String 对象的缓存性能是很重要的一方面,因此通过使 String 类不可变来避免这种风险。同时,String 是 final 的,因此没有人可以通过扩展和覆盖行为来破坏 String 类的不变性、缓存、散列值的计算等。String 类不可变的另一个原因可能是由于 HashMap。由于把字符串作为 HashMap 键很受欢迎。对于键值来说,重要的是它们是不可变的,以便用它们检索存储在 HashMap 中的值对象。由于 HashMap 的工作原理是散列,因此需要具有相同的值才能正常运行。如果在插入后修改了 String 的内容,可变的 String将在插入和检索时生成两个不同的哈希码,可能会丢失 Map 中的值对象。如果你是印度板球迷,你可能能够与我的下一句话联系起来。字符串是Java的 VVS Laxman,即非常特殊的类。我还没有看到一个没有使用 String 编写的 Java 程序。这就是为什么对 String 的充分理解对于 Java 开发人员来说非常重要。String 作为数据类型,传输对象和中间人角色的重要性和流行性也使这个问题在 Java 面试中很常见。为什么 String 在 Java 中是不可变的是 Java 中最常被问到的字符串访问问题之一,它首先讨论了什么是 String,Java 中的 String 如何与 C 和 C++ 中的 String 不同,然后转向在Java中什么是不可变对象,不可变对象有什么好处,为什么要使用它们以及应该使用哪些场景。这个问题有时也会问:“为什么 String 在 Java 中是 final 的”。在类似的说明中,如果你正在准备Java 面试,我建议你看看《Java程序员面试宝典(第4版) 》,这是高级和中级Java程序员的优秀资源。它包含来自所有重要 Java 主题的问题,包括多线程,集合,GC,JVM内部以及 Spring和 Hibernate 框架等。正如我所说,这个问题可能有很多可能的答案,而 String 类的唯一设计者可以放心地回答它。我在 Joshua Bloch 的 Effective Java 书中期待一些线索,但他也没有提到它。我认为以下几点解释了为什么 String 类在 Java 中是不可变的或 final 的:1)想象字符串池没有使字符串不可变,它根本不可能,因为在字符串池的情况下,一个字符串对象/文字,例如 “Test” 已被许多参考变量引用,因此如果其中任何一个更改了值,其他参数将自动受到影响,即假设String A="Test";String B="Test";现在字符串 B 调用 "Test".toUpperCase(), 将同一个对象改为“TEST”,所以 A 也是 “TEST”,这不是期望的结果。下图显示了如何在堆内存和字符串池中创建字符串。2)字符串已被广泛用作许多 Java 类的参数,例如,为了打开网络连接,你可以将主机名和端口号作为字符串传递,你可以将数据库 URL 作为字符串传递, 以打开数据库连接,你可以通过将文件名作为参数传递给 File I/O 类来打开 Java 中的任何文件。如果 String 不是不可变的,这将导致严重的安全威胁,我的意思是有人可以访问他有权授权的任何文件,然后可以故意或意外地更改文件名并获得对该文件的访问权限。由于不变性,你无需担心这种威胁。这个原因也说明了,为什么 String 在 Java 中是最终的,通过使 java.lang.String final,Java设计者确保没有人覆盖 String 类的任何行为。3)由于 String 是不可变的,它可以安全地共享许多线程,这对于多线程编程非常重要. 并且避免了 Java 中的同步问题,不变性也使得String 实例在 Java 中是线程安全的,这意味着你不需要从外部同步 String 操作。关于 String 的另一个要点是由截取字符串 SubString 引起的内存泄漏,这不是与线程相关的问题,但也是需要注意的。4)为什么 String 在 Java 中是不可变的另一个原因是允许 String 缓存其哈希码,Java 中的不可变 String 缓存其哈希码,并且不会在每次调用 String 的 hashcode 方法时重新计算,这使得它在 Java 中的 HashMap 中使用的 HashMap 键非常快。简而言之,因为 String 是不可变的,所以没有人可以在创建后更改其内容,这保证了 String 的 hashCode 在多次调用时是相同的。5)String 不可变的绝对最重要的原因是它被类加载机制使用,因此具有深刻和基本的安全考虑。如果 String 是可变的,加载“java.io.Writer” 的请求可能已被更改为加载 “mil.vogoon.DiskErasingWriter”. 安全性和字符串池是使字符串不可变的主要原因。顺便说一句,上面的理由很好回答另一个Java面试问题: “为什么String在Java中是最终的”。要想是不可变的,你必须是最终的,这样你的子类不会破坏不变性。你怎么看?5 为什么 char 数组比 Java 中的 String 更适合存储密码?另一个基于 String 的棘手 Java 问题,相信我只有很少的 Java 程序员可以正确回答这个问题。这是一个真正艰难的核心Java面试问题,并且需要对 String 的扎实知识才能回答这个问题。这是最近在 Java 面试中向我的一位朋友询问的问题。他正在接受技术主管职位的面试,并且有超过6年的经验。如果你还没有遇到过这种情况,那么字符数组和字符串可以用来存储文本数据,但是选择一个而不是另一个很难。但正如我的朋友所说,任何与 String 相关的问题都必须对字符串的特殊属性有一些线索,比如不变性,他用它来说服访提问的人。在这里,我们将探讨为什么你应该使用char[]存储密码而不是String的一些原因。字符串:1)由于字符串在 Java 中是不可变的,如果你将密码存储为纯文本,它将在内存中可用,直到垃圾收集器清除它. 并且为了可重用性,会存在 String 在字符串池中, 它很可能会保留在内存中持续很长时间,从而构成安全威胁。由于任何有权访问内存转储的人都可以以明文形式找到密码,这是另一个原因,你应该始终使用加密密码而不是纯文本。由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产生新的字符串,而如果你使用char[],你就可以将所有元素设置为空白或零。因此,在字符数组中存储密码可以明显降低窃取密码的安全风险。2)Java 本身建议使用 JPasswordField 的 getPassword() 方法,该方法返回一个 char[] 和不推荐使用的getTex() 方法,该方法以明文形式返回密码,由于安全原因。应遵循 Java 团队的建议, 坚持标准而不是反对它。3)使用 String 时,总是存在在日志文件或控制台中打印纯文本的风险,但如果使用 Array,则不会打印数组的内容而是打印其内存位置。虽然不是一个真正的原因,但仍然有道理。String strPassword =“Unknown”; char [] charPassword = new char [] {'U','n','k','w','o','n'}; System.out.println(“字符密码:”+ strPassword); System.out.println(“字符密码:”+ charPassword);输出字符串密码:Unknown字符密码:[C @110b053我还建议使用散列或加密的密码而不是纯文本,并在验证完成后立即从内存中清除它。因此,在Java中,用字符数组用存储密码比字符串是更好的选择。虽然仅使用char[]还不够,还你需要擦除内容才能更安全。6 如何使用双重检查锁定在 Java 中创建线程安全的单例?这个 Java 问题也常被问: 什么是线程安全的单例,你怎么创建它。好吧,在Java 5之前的版本, 使用双重检查锁定创建单例 Singleton 时,如果多个线程试图同时创建 Singleton 实例,则可能有多个 Singleton 实例被创建。从 Java 5 开始,使用 Enum 创建线程安全的Singleton很容易。但如果面试官坚持双重检查锁定,那么你必须为他们编写代码。记得使用volatile变量。为什么枚举单例在 Java 中更好枚举单例是使用一个实例在 Java 中实现单例模式的新方法。虽然Java中的单例模式存在很长时间,但枚举单例是相对较新的概念,在引入Enum作为关键字和功能之后,从Java5开始在实践中。本文与之前关于 Singleton 的内容有些相关, 其中讨论了有关 Singleton 模式的面试中的常见问题, 以及 10 个 Java 枚举示例, 其中我们看到了如何通用枚举可以。这篇文章是关于为什么我们应该使用Eeame作为Java中的单例,它比传统的单例方法相比有什么好处等等。Java 枚举和单例模式Java 中的枚举单例模式是使用枚举在 Java 中实现单例模式。单例模式在 Java 中早有应用, 但使用枚举类型创建单例模式时间却不长. 如果感兴趣, 你可以了解下构建者设计模式和装饰器设计模式。1) 枚举单例易于书写这是迄今为止最大的优势,如果你在Java 5之前一直在编写单例, 你知道, 即使双检查锁定, 你仍可以有多个实例。虽然这个问题通过 Java 内存模型的改进已经解决了, 从 Java 5 开始的 volatile 类型变量提供了保证, 但是对于许多初学者来说, 编写起来仍然很棘手。与同步双检查锁定相比,枚举单例实在是太简单了。如果你不相信, 那就比较一下下面的传统双检查锁定单例和枚举单例的代码:在 Java 中使用枚举的单例这是我们通常声明枚举的单例的方式,它可能包含实例变量和实例方法,但为了简单起见,我没有使用任何实例方法,只是要注意,如果你使用的实例方法且该方法能改变对象的状态的话, 则需要确保该方法的线程安全。默认情况下,创建枚举实例是线程安全的,但 Enum 上的任何其他方法是否线程安全都是程序员的责任/**使用 Java 枚举的单例模式示例*/public enum EasySingleton{INSTANCE;}你可以通过EasySingleton.INSTANCE来处理它,这比在单例上调用getInstance()方法容易得多。具有双检查锁定的单例示例下面的代码是单例模式中双重检查锁定的示例,此处的 getInstance() 方法检查两次,以查看 INSTANCE 是否为空,这就是为什么它被称为双检查锁定模式,请记住,双检查锁定是代理之前Java 5,但Java5内存模型中易失变量的干扰,它应该工作完美。/**单例模式示例,双重锁定检查*/public class DoubleCheckedLockingSingleton{ private volatile DoubleCheckedLockingSingleton INSTANCE; private DoubleCheckedLockingSingleton(){} public DoubleCheckedLockingSingleton getInstance(){ if(INSTANCE == null){ synchronized(DoubleCheckedLockingSingleton.class){ //double checking Singleton instance if(INSTANCE == null){ INSTANCE = new DoubleCheckedLockingSingleton(); } } } return INSTANCE; }}你可以调用DoubleCheckedLockingSingleton.getInstance() 来获取此单例类的访问权限。现在,只需查看创建延迟加载的线程安全的 Singleton 所需的代码量。使用枚举单例模式, 你可以在一行中具有该模式, 因为创建枚举实例是线程安全的, 并且由 JVM 进行。人们可能会争辩说,有更好的方法来编写 Singleton 而不是双检查锁定方法, 但每种方法都有自己的优点和缺点, 就像我最喜欢在类加载时创建的静态字段 Singleton, 如下面所示, 但请记住, 这不是一个延迟加载单例:单例模式用静态工厂方法这是我最喜欢的在 Java 中影响 Singleton 模式的方法之一,因为 Singleton 实例是静态的,并且最后一个变量在类首次加载到内存时初始化,因此实例的创建本质上是线程安全的。/**单例模式示例与静态工厂方法*/public class Singleton{//initailzed during class loading private static final Singleton INSTANCE = new Singleton(); //to prevent creating another instance of Singleton private Singleton(){} public static Singleton getSingleton(){ return INSTANCE; }}你可以调用 Singleton.getSingleton() 来获取此类的访问权限。2) 枚举单例自行处理序列化传统单例的另一个问题是,一旦实现可序列化接口,它们就不再是 Singleton, 因为 readObject() 方法总是返回一个新实例, 就像 Java 中的构造函数一样。通过使用 readResolve() 方法, 通过在以下示例中替换 Singeton 来避免这种情况://readResolve to prevent another instance of Singletonprivate Object readResolve(){return INSTANCE;}如果 Singleton 类保持内部状态, 这将变得更加复杂, 因为你需要标记为 transient(不被序列化),但使用枚举单例, 序列化由 JVM 进行。3) 创建枚举实例是线程安全的如第 1 点所述,因为 Enum 实例的创建在默认情况下是线程安全的, 你无需担心是否要做双重检查锁定。总之, 在保证序列化和线程安全的情况下,使用两行代码枚举单例模式是在 Java 5 以后的世界中创建 Singleton 的最佳方式。你仍然可以使用其他流行的方法, 如你觉得更好, 欢迎讨论。7 编写 Java 程序时, 如何在 Java 中创建死锁并修复它?经典但核心Java面试问题之一。如果你没有参与过多线程并发 Java 应用程序的编码,你可能会失败。如何避免 Java 线程死锁?如何避免 Java 中的死锁?是 Java 面试的热门问题之一, 也是多线程的编程中的重口味之一, 主要在招高级程序员时容易被问到, 且有很多后续问题。尽管问题看起来非常基本, 但大多数 Java 开发人员一旦你开始深入, 就会陷入困境。面试问题总是以“什么是死锁?”开始 当两个或多个线程在等待彼此释放所需的资源(锁定)并陷入无限等待即是死锁。它仅在多任务或多线程的情况下发生。如何检测 Java 中的死锁?虽然这可以有很多答案, 但我的版本是首先我会看看代码, 如果我看到一个嵌套的同步块,或从一个同步的方法调用其他同步方法, 或试图在不同的对象上获取锁, 如果开发人员不是非常小心,就很容易造成死锁。另一种方法是在运行应用程序时实际锁定时找到它, 尝试采取线程转储,在 Linux 中,你可以通过kill -3命令执行此操作, 这将打印应用程序日志文件中所有线程的状态, 并且你可以看到哪个线程被锁定在哪个线程对象上。你可以使用 fastthread.io 网站等工具分析该线程转储, 这些工具允许你上载线程转储并对其进行分析。另一种方法是使用 jConsole 或 VisualVM, 它将显示哪些线程被锁定以及哪些对象被锁定。如果你有兴趣了解故障排除工具和分析线程转储的过程, 我建议你看看 Uriah Levy 在多元视觉(PluraIsight)上《分析 Java 线程转储》课程。旨在详细了解 Java 线程转储, 并熟悉其他流行的高级故障排除工具。编写一个将导致死锁的Java程序?一旦你回答了前面的问题,他们可能会要求你编写代码,这将导致Java死锁。这是我的版本之一/**Java 程序通过强制循环等待来创建死锁。* * */public class DeadLockDemo {/* * 此方法请求两个锁,第一个字符串,然后整数 */ public void method1() { synchronized (String.class) { System.out.println("Aquired lock on String.class object"); synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object"); } } } /* * 此方法也请求相同的两个锁,但完全 * 相反的顺序,即首先整数,然后字符串。 * 如果一个线程持有字符串锁,则这会产生潜在的死锁 * 和其他持有整数锁,他们等待对方,永远。 */ public void method2() { synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object"); synchronized (String.class) { System.out.println("Aquired lock on String.class object"); } } }}如果 method1() 和 method2() 都由两个或多个线程调用,则存在死锁的可能性, 因为如果线程 1 在执行 method1() 时在 Sting 对象上获取锁, 线程 2 在执行 method2() 时在 Integer 对象上获取锁, 等待彼此释放 Integer 和 String 上的锁以继续进行一步, 但这永远不会发生。此图精确演示了我们的程序, 其中一个线程在一个对象上持有锁, 并等待其他线程持有的其他对象锁。你可以看到, Thread1 需要 Thread2 持有的 Object2 上的锁,而 Thread2 希望获得 Thread1 持有的 Object1 上的锁。由于没有线程愿意放弃, 因此存在死锁, Java 程序被卡住。其理念是, 你应该知道使用常见并发模式的正确方法, 如果你不熟悉这些模式,那么 Jose Paumard 《应用于并发和多线程的常见 Java 模式》是学习的好起点。如何避免Java中的死锁?现在面试官来到最后一部分, 在我看来, 最重要的部分之一; 如何修复代码中的死锁?或如何避免Java中的死锁?如果你仔细查看了上面的代码,那么你可能已经发现死锁的真正原因不是多个线程, 而是它们请求锁的方式, 如果你提供有序访问, 则问题将得到解决。下面是我的修复版本,它通过避免循环等待,而避免死锁, 而不需要抢占, 这是需要死锁的四个条件之一。public class DeadLockFixed {/** * 两种方法现在都以相同的顺序请求锁,首先采用整数,然后是 String。 * 你也可以做反向,例如,第一个字符串,然后整数, * 只要两种方法都请求锁定,两者都能解决问题 * 顺序一致。 */ public void method1() { synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object"); synchronized (String.class) { System.out.println("Aquired lock on String.class object"); } } } public void method2() { synchronized (Integer.class) { System.out.println("Aquired lock on Integer.class object"); synchronized (String.class) { System.out.println("Aquired lock on String.class object"); } } }}现在没有任何死锁,因为两种方法都按相同的顺序访问 Integer 和 String 类文本上的锁。因此,如果线程 A 在 Integer 对象上获取锁, 则线程 B 不会继续, 直到线程 A 释放 Integer 锁, 即使线程 B 持有 String 锁, 线程 A 也不会被阻止, 因为现在线程 B 不会期望线程 A 释放 Integer 锁以继续。8 如果你的Serializable类包含一个不可序列化的成员,会发生什么?你是如何解决的?任何序列化该类的尝试都会因NotSerializableException而失败,但这可以通过在 Java中 为 static 设置瞬态(trancient)变量来轻松解决。Java 序列化相关的常见问题 Java 序列化是一个重要概念, 但它很少用作持久性解决方案, 开发人员大多忽略了 Java 序列化 API。根据我的经验, Java 序列化在任何 Java核心内容面试中都是一个相当重要的话题, 在几乎所有的网面试中, 我都遇到过一两个 Java 序列化问题, 我看过一次面试, 在问几个关于序列化的问题之后候选人开始感到不自在, 因为缺乏这方面的经验。他们不知道如何在 Java 中序列化对象, 或者他们不熟悉任何 Java 示例来解释序列化, 忘记了诸如序列化在 Java 中如何工作, 什么是标记接口, 标记接口的目的是什么, 瞬态变量和可变变量之间的差异, 可序列化接口具有多少种方法, 在 Java 中,Serializable 和 Externalizable 有什么区别, 或者在引入注解之后, 为什么不用 @Serializable 注解或替换 Serializalbe 接口。
文章
存储  ·  设计模式  ·  缓存  ·  安全  ·  Java  ·  程序员  ·  编译器  ·  开发工具  ·  数据安全/隐私保护  ·  C++
2023-02-02
小题 错题总结
要是对象具有序列化,应该实现的接口是 Java.IO.Serializable在 JVM 内存划分中 ,方法通常存储在 方法区多态的3种表现形式: 继承重写  重载  向上转型Java 中继承可以间接继承,即便中间跨过一个类,栗子:所有的类都是Object的子类静态 (static) 的含义是 一个成员数据/方法属于它所在的类,而一般的成员数据或方法属于他的对象static 修饰变量不能再方法中定义 ; 静态方法里的变量默认就是静态,不能加static  。否则会编译失败(语法错误)抽象类中,可以有普通方法和抽象方法。抽象方法不能被 static  final  private 修饰,抽象方法不能有方法体(也就是不能写大括号)类在声明成员变量的区域不能进行 计算,计算要在方法体内HashSet 和 HashSet 的键 / 值都可以为 nullVolatile指令关键字  只是确保本条指令不会因编译器的优化而省略,且要求每次直接读值。ArrayList 和 LinkedList 的区别 :① ArrayList 的实现基于数组,LinkedList的实现基于双向链表② ArrayList 基于数组实现,是线性表数据结构,内存空间是连续的。LinkedList 是链表的数据结构,内存空间并不连续,而是通过指向建立连接。③ LinkedList 的插入删除效率比较高,只需要切断链条再重新连接即可。而数组则在操作时候需要进行重排。④相比于ArrayList ,LinkedList更占内存,因为每个节点还需要存储两个引用。基类 == 父类    派生类 == 子类创建派生对象的时候,构造函数执行顺序:、① 初始化父类中静态成员变量和静态代码块② 初始化子类中的静态成员变量和静态代码块③ 初始化父类的普通成员变量和代码块,再执行父类构造方法④ 初始化子类的普通成员变量和代码块,再执行子类构造方法接口的特征:① 每一个方法都是 public abstract 修饰 (其他修饰就会报错)② 可以有成员变量,但会被隐式的指定为 public static final 变量③ 接口的方法不能在接口中实现,只能由实现接口的类重写实现④ 类必须实现接口的所有功能(抽象类不一样,可以不实现完)装箱、拆箱 操作就是数据类型和包装类的转换  值类型==基本数据类型  包装类==引用数据类型。栗子 装箱: int ---> Integer   拆箱: Double ---> doublefinally 能确保是否有异常都能执行到这一段代码 。如果 已经return 则 finally里的代码不会改变return 的结果,会先保存return的值,待finally里的代码执行结束,再执行return。如果finally里有return,则会覆盖掉其他return语句。throw:在方法内使用,抛出某种异常对象throws:在方法的定义上使用,表示这个方法可能会抛出哪些异常public 哪里都能使用   protected 不同包中同一子类使用   deafult 同一个包中(默认)一个字节八个比特位  byte类型的范围是 -128 -----  127 一个int四个字节         int类型范围是 -2的31次方   -----   2的31次方 - 1  (-20亿 到 20亿没问题)Java 可以使用二进制(0b开头),八进制(0开头),10进制,16进制(0X开头)进行赋值。把字符串转换成整数 要注意的事情: ① 数据上下溢出 ② 空字符串 ③ 只有正负号 ④ 有无正负号⑤ 错误标志输出super :① 引用直接父类的实例变量 ② 调用直接父类方法:档子类和父类具有相同方法名时,可使用super访问父类方法 ③ 调用直接父类构造方法(不能夸父):必须写在子类方法的第一行super 调用父类构造方法  this 调用自己的构造方法,但他俩不能同时出现在一个构造方法中,原因是 this 调用的本类的构造方法中可能存在 super,而导致重复调用super的问题。比int类型小的类型  进行基本运算的时候 会默认转换成int类型进行运算,这样的计算效率相对较高数据库中 DATEADD(datepatt,number,date) 函数是在日期中添加或减去指定的时间间隔。http://t.csdn.cn/ZwZBV数据定义语言:负责数据的模式定义与数据的物理存储构建数据操纵语言:负责数据的操纵,包括 增 删 查 改 等操作数据控制语言:负责数据完整性、安全性的定义检查以及并发控制、故障恢复等功能关系数据库是以“二维表”的形式组织和存放数据的。关系数据库的表采用二维表格来存储数据,是一种按行与列排列的具有相关信息的逻辑组,它类似于Excel工作表;一个数据库可以包含任意多个数据表。Mysql 数据库使用的是TCP/IP协议,而不是 UDPalter 翻译:改变修改 删除 S 表 中的 Age 属性 : ALTER TABLE S DROP Age  增加 S 表中 一个 属性 :     ALTER TABLE S ADD Name Type从表TABLE_NAME中提取前10条记录:  select TOP 10 * from TABLE_NAME为用户A分配数据库user表info的查询和插入数据权限:“ grant select,insert on user.info to'A'@'localhost'  ”什么是数据库索引 : 是对数据库表中一个或多个列(例如,employee 表的姓名 (name) 列)的值进行排序的结构。            例如这样一个查询:select * from table1 where id=10000。如果没有索引,必须遍历整个表,直到ID等于10000的这一行被找到为止;有了 索引 之后(必须是在ID这一列上建立的索引),即可在索引中查找。由于索引是经过某种算法优化过的,因而查找次数要少的多。可见,索引是用来定位的。where 和 having 的区别:都是条件限制语句,对分组结果进行过滤的时候要使用having,必须与 group by配合使用和where类似,区别是分组前用where 分组后用havinglike模糊匹配,&表示多个任意字符 ,_表示一个字符查找 student表中所有电话号码(列名:telephone)的第一位为8或6,第三位为0的电话号码:SELECT telephone FROM student WHERE telephone LIKE ‘[8,6]_0%’数据库事务的 4 个特性是:原子性、一致性、持续性、隔离性对于顺序存储的线性表,访问结点和增加结点的时间复杂度分别为 0(1)  0(n),注意顺序存储的线性表,其实就是数组。最适合 对基本有序的 对象进行排序的方法是 冒泡排序。循环队列是队列的一种顺序的存储结构。循环链表是用不连续的存储单元 存储数据。二叉链表是树的二叉链表实现方式。栈是一种特殊存取方式的线 性表。堆是一种特殊的 完全二叉树,从任一结点出发到根的路径上所经过的结点序列按其关键字有序。二叉排序树 是左边小 右边大,中序遍历是有序的。堆是每个结点的值都大于或等于其左右孩子结点的值(这里的讨论以大根堆为例,小根堆相反)。具有 记忆功能 的数据结构是 栈 ,很简单的场景 进入A网页 再跳转到B网页 再跳转到C网页,就相当于  栈里有 ABC,然后我们还可以通过返回键,从C网页返回到B网页,也就相当于出栈,这种功能就叫做记忆功能。对递归程序的优化的一般的手段为  尾递归优化。尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。 尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
文章
存储  ·  网络协议  ·  算法  ·  Java  ·  关系型数据库  ·  MySQL  ·  编译器  ·  数据库  ·  索引
2023-02-01
如何确定Visual Studio的_MSC_VER值?
  本文介绍查看Visual Studio软件_MSC_VER值的方法。  _MSC_VER是微软公司推出的C/C++ 编译器——MSVC编译器的一个内置宏,其值表示当前Visual Studio软件中MSVC编译器的具体版本。不同的Visual Studio软件版本对应着不同的MSVC编译器版本——无论是不同发布年份的版本(例如Visual Studio 2017与Visual Studio 2019),还是同一发布年份的不同版本(例如Visual Studio 2017 version 15.8与Visual Studio 2017 version 15.9),其MSVC编译器版本都有差异,因此其_MSC_VER值各不相同。而在代码开发过程中,我们有时会需要获取、用到当前版本Visual Studio软件对应的_MSC_VER值;因此就需要找到一种获取_MSC_VER值的方法。  在Visual Studio软件中,我们可以通过顶部菜单栏中的“帮助”→“关于 Microsoft Visual Studio”选项,获取Visual Studio软件的版本信息。  其中,获得的Visual Studio软件版本信息如下图所示。  可以看到,其中仅仅显示了Visual Studio软件的发布年份、当前版本,以及.NET Framework的版本,还有已安装的产品的版本;而并没有MSVC编译器的版本。因此,这就需要我们通过以下方法来获得MSVC编译器的版本。  方法也很简单,只需要运行如下所示的代码即可。#include <iostream> using namespace std; int main() { int num = _MSC_VER; // get the version cout << "My MSVC version is: " << num << endl; return 0; }  其中,如果是第一次接触Visual Studio软件,大家可以参考文章Visual Studio软件的下载、使用(https://developer.aliyun.com/article/1148417)后半部分提到的操作方法,新建项目并运行上述代码。  上述代码运行后,将会出现如下所示的界面,从而获取MSVC编译器的版本。本文中1933就是我这里的MSVC编译器版本。  至此,大功告成。
文章
编译器  ·  C++
2023-02-02
1 2 3 4 5 6 7 8 9
...
20
跳转至:
开发与运维
5622 人关注 | 131425 讨论 | 301869 内容
+ 订阅
  • 记CentOS7.6 zabix5.0-0 —agent2监控Mysql数据库
  • 【算法日记】快速幂:关于我知道答案却做不出来这档事
  • 【Java实用技术】java中关于整数的几个冷知识,总有一个你不知道
查看更多 >
数据库
252583 人关注 | 50737 讨论 | 94860 内容
+ 订阅
  • 记CentOS7.6 zabix5.0-0 —agent2监控Mysql数据库
  • Run 32-bit applications on 64-bit Linux kernel
  • Logback基本使用
查看更多 >
云计算
21783 人关注 | 59350 讨论 | 55786 内容
+ 订阅
  • 淘宝小游戏背后的质量保障方案
  • 与客户手拉手|OceanBase客户实践精选Top 10
  • 走进 Yarn 资源管理和调度|青训营笔记
查看更多 >
大数据
188297 人关注 | 29195 讨论 | 80458 内容
+ 订阅
  • 数据结构 | 堆的向上调整和向下调整算法【奇妙的堆排序】
  • How to Deploy a Neural Network on TH1520
  • 我开源了一个Go学习仓库|笔记分享(三)
查看更多 >
人工智能
2793 人关注 | 11325 讨论 | 96990 内容
+ 订阅
  • 【算法日记】快速幂:关于我知道答案却做不出来这档事
  • 数据结构 | 堆的向上调整和向下调整算法【奇妙的堆排序】
  • 本周推荐 | JDK 11 升级实践 和 Java 新特性浅探
查看更多 >