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默认方法为浅拷贝,即:
新(拷贝产生)、旧(元对象)对象不同,但是内部如果有引用类型的变量,新、旧对象引用的都是同一引用。

深拷贝则是全部拷贝原对象的内容,包括内存的引用类型也进行拷贝。(一般用于有父子类关系)

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;
    }
}
package clone;

/**
 * @author CTX
 */
public class Son implements Cloneable{
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
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 调用下面的方法,得到的返回值是什么?

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 表示出现异常的一种可能性,并不一定会发生这种异常。

  • 实例:
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 请说出下列输出:

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。

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:September 29th, 2020 at 09:35 am
喵ฅฅ