Loading... ### java面向对象:Clone专题 > **1.1 为什么要clone?** 在实际编程过程中,我们常常要遇到这种情况:有一个对象 A,在某一时刻 A 中已经包含了一些有效值,此时可能会需要一个和 A 完全相同新对象 B,并且此后对 B 任何改动都不会影响到 A 中的值,也就是说,A 与 B 是两个独立的对象,但 B 的初始值是由 A 对象确定的。在 Java 语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现 clone()方法是其中最简单,也是最高效的手段。 > **1.2 new 一个对象的过程和 clone 一个对象的过程区别** new 操作符的本意是分配内存。程序执行到 new 操作符时,首先去看 new 操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。 **new会调用构造方法,clone不会。**clone 在第一步是和 new 相似的,都是分配内存,调用 clone 方法时,分配的内存和原对象(即调用 clone 方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,clone 方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。 > **1.3 写clone()方法时,通常都有一行代码,是什么?** clone 有缺省行为,`super.clone();`因为首先要把父类中的成员复制到位,然后才是复制自己的成员。 **补充说明** clone默认方法为浅拷贝,即: 新(拷贝产生)、旧(元对象)对象不同,但是内部如果有引用类型的变量,新、旧对象引用的都是同一引用。 深拷贝则是全部拷贝原对象的内容,包括内存的引用类型也进行拷贝。(一般用于有父子类关系) ```Java package clone; /** * @author CTX */ public class Father implements Cloneable{ private Son son; public Father(Son son){ this.son = son; } @Override protected Object clone() throws CloneNotSupportedException { // return super.clone(); Father newFather = (Father) super.clone(); newFather.son = (Son) this.son.clone(); return newFather; } public Son getSon() { return son; } } ``` ```Java package clone; /** * @author CTX */ public class Son implements Cloneable{ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } ``` ```Java package clone; /** * @author CTX */ public class Test { public static void main(String[] args) throws CloneNotSupportedException { Father father1 = new Father(new Son()); Father father2 = (Father) father1.clone(); System.out.println("f1:"+father1.hashCode()+" s1:"+father1.getSon().hashCode()); System.out.println("f2:"+father2.hashCode()+" s2:"+father2.getSon().hashCode()); } } f1:460141958 s1:1163157884 f2:1956725890 s2:356573597 ``` --- ### JavaSE语法 > **2.1 当一个对象被当做参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?** 是值传递。Java编程语言中只有由值传递参数的。当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。 注意: 在 Java应用程序中永远不会传递对象,而只传递对象引用。但Java应用程序按引用传递对象这一事实并不意味着 Java 应用程序按引用传递参数。参数可以是对象引用,而 Java应用程序是按值传递对象引用的。 --- ### Java异常处理 > **3.1 调用下面的方法,得到的返回值是什么?** ```Java public int getNum() { try { int a = 1 / 0; return 1; } catch (Exception e) { return 2; } finally { return 3; } } ``` 代码在走到第 3 行的时候遇到了一个 MathException,这时第四行的代码就不会执行了,代码直接跳转到 catch语句中,走到第 6 行的时候,异常机制有这么一个原则如果在 catch 中遇到了 return 或者异常等能使该函数终止的话那么有 finally 就必须先执行完 finally 代码块里面的代码然后再返回值。因此代码又跳到第 8 行,可惜第 8 行是一个return 语句,那么这个时候方法就结束了,因此第 6 行的返回结果就无法被真正返回。如果 finally 仅仅是处理了一个释放资源的操作,那么该道题最终返回的结果就是 2。因此上面返回值是 3。 > **3.2 throw 和 throws 的区别** * throw: 1)throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。 2)throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常。 * throws: 1)throws 语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。 2)throws 主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。 3)throws 表示出现异常的一种可能性,并不一定会发生这种异常。 * 实例: ```Java void testException(int a) throws IOException,{ try{ ... }catch(Exception1 e){ throw e; }catch(Exception2 e){ System.out.println("出错了!"); } if(a!=b) throw new Exception3("自定义异常"); } ``` --- ### JavaSE常用API > **4.1 华为面试题:什么情况下用“+”运算符进行字符串连接比调用 StringBuffer/StringBuilder对象的 append 方法连接字符串性能更好?** 通过查看String源码可以发现,String类中已经重载过 `+`,所以可以用它连接字符串。 而且之前通过查阅文章了解到反编译文件中 `+`被转换为了 `StringBulder`,所以无论使用哪种方法连接字符串实际上底层还是通过 `StringBulder`。但是进一步通过反编译程序可以发现对于存在for循环的连接语句,`+`转换的 `StringBulder`出现在for循环内部,每执行一遍就会产生一次 `StringBulder`对象;而创建的 `StringBulder`则在for循环外部,虽然代码更复杂但是换来了更高的效率和资源开销。 > **4.2 请说出下列输出:** ```Java class StringEqualTest { public static void main(String[] args) { String s1 = "Programming"; String s2 = new String("Programming"); String s3 = "Program"; String s4 = "ming"; String s5 = "Program" + "ming"; String s6 = s3 + s4; System.out.println(s1 == s2); //false System.out.println(s1 == s5); //true System.out.println(s1 == s6); //false /** * intern(): * 如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象; * 否则,将此String对象包含的字符添加到常量池中,并返回此String对象的引用。 * new String不会放入常量池中 */ System.out.println(s1 == s6.intern()); //true //s2指向堆栈中的引用地址,s2.intern()指向常量池的引用地址,故不相同 System.out.println(s2 == s2.intern()); //false } } ``` > **4.3 Java日期和时间问题** > 一定要用LocalDataTime而不用Data函数,因为Data格式化日期还需要构造输出格式并且很多构造函数已经被弃用了。 > 并且**Date时间类是可变类,这就意味着在多线程环境下对共享 Date变量进行操作时,必须由程序员自己来保证线程安全!而自 Java 8开始推出的 LocalDateTime却是不可变类,是线程安全的,开发人员不用再考虑并发问题。** > 而Data连用的SimpleDataFormat也是线程不安全,应该用DateTimeFormatter。 ```Java package data_and_time; import java.time.Clock; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.Month; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalAdjusters; import java.util.Calendar; /** * @author 唐宋丶 * @create 2020/9/28 */ public class LocaDataTimeDemo { public static void main(String[] args) { /** * 获取当前日期和时间 */ LocalDateTime rightNow = LocalDateTime.now(); System.out.println("当前时刻:" + rightNow); System.out.println("当前年:" + rightNow.getYear()); System.out.println("当前月:" + rightNow.getMonth()); System.out.println("当前日:" + rightNow.getDayOfMonth()); System.out.println("当前时:" + rightNow.getHour()); System.out.println("当前分:" + rightNow.getMinute()); System.out.println("当前秒:" + rightNow.getSecond()); /** * 获取从1970-01-01 00:00至今的毫秒数 */ System.out.println(Calendar.getInstance().getTimeInMillis()); System.out.println(System.currentTimeMillis()); System.out.println(Clock.systemDefaultZone().millis()); /** * 获取本月最后一天 * with(TemporalAdjusters)用于寻找第一天/最后一天 */ System.out.println("本月最后一天:" + rightNow.with(TemporalAdjusters.lastDayOfMonth())); /** * 构造指定年月日 一月 */ LocalDateTime beforeData = LocalDateTime.of(1970, Month.JANUARY,1,0,0,0); System.out.println("构造日期:" + beforeData); /** * 修改日期 */ System.out.println(rightNow); System.out.println("两年前:" + rightNow.minusYears(2)); System.out.println("两月后:" + rightNow.plusMonths(2)); System.out.println("改到22时:" + rightNow.withHour(22)); /** * 格式化日期(部分样式) */ String data1 = rightNow.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); String data2 = rightNow.format(DateTimeFormatter.ISO_DATE); String data3 = rightNow.format(DateTimeFormatter.ISO_DATE_TIME); String data4 = rightNow.format(DateTimeFormatter.BASIC_ISO_DATE); System.out.println("自定义样式:" + data1); System.out.println("基本样式一:" + data2); System.out.println("基本样式二:" + data3); System.out.println("基本样式三:" + data4); /** * 时间反解析:给你一个陌生的字符串,你可以按照你需要的格式把时间给反解出来 */ LocalDateTime time = LocalDateTime.parse("2020---01---02 03:04",DateTimeFormatter.ofPattern("yyyy---MM---dd HH:mm")); System.out.println("字符串反解析后的时间为:" + time); } } 当前时刻:2020-09-28T20:07:36.469 当前年:2020 当前月:SEPTEMBER 当前日:28 当前时:20 当前分:7 当前秒:36 1601294856473 1601294856481 1601294856481 本月最后一天:2020-09-30T20:07:36.469 构造日期:1970-01-01T00:00 2020-09-28T20:07:36.469 两年前:2018-09-28T20:07:36.469 两月后:2020-11-28T20:07:36.469 改到22时:2020-09-28T22:07:36.469 自定义样式:2020/09/28 基本样式一:2020-09-28 基本样式二:2020-09-28T20:07:36.469 基本样式三:20200928 字符串反解析后的时间为:2020-01-02T03:04 ``` **Java 8 日期/时间新特性:** * 不可变性:新日期/时间的API中,类都是不可变的,保证线程安全。 * 关注分离点:将日期时间和机器时间明确分离。为日期(Data)、时间(Time)、日期时间(DataTime)、时间戳(unix timestamp)以及时区定义了不同的类。 * 清晰:所有类的方法都被明确定义已完成相同行为,不像一起 `format()`/`parse()`都需要专门一个类。 * 实用操作:都实现了加、减、格式化、解析、提取部分等通用任务。 * 可扩展性:不仅仅只能工作在 ISO-8601 日历系统上。 **Java 8 日期/时间API包:** * java.time 包:这是新的 Java 日期/时间 API 的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate,LocalTime, LocalDateTime, Instant, Period, Duration 等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。 * java.time.chrono 包:这个包为非 ISO 的日历系统定义了一些泛化的 API,我们可以扩展AbstractChronology类来创建自己的日历系统。 * java.time.format 包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为 java.time 包中相应的类已经提供了格式化和解析的方法。 * java.time.temporal 包:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式。 * java.time.zone 包:这个包包含支持不同时区以及相关规则的类。 --- ### Java数据类型 > **5.1 String常用方法**  Last modification:August 14, 2022 © Allow specification reprint Like 0 喵ฅฅ