Java速通 Day3 – 使用轮子和制作轮子(类)

/ 0评 / 0

对于Java这种工具,他还是内置了非常多的轮子,比如之前用到的数学计算,还有日期工具,甚至画图的AWT(废弃),JavaFX等等,轮子非常多,而Java作为面向对象的语言,我们也可以制作自己的类,可以封装成包,把这个类(轮子)给别人.

其中日期时间这个轮子,Java迭代了很多个版本,一路修BUG一路迭代,Java 1.0就有Date类,Java 1.1有Calendar类,但是从Java 8才有LocalDate和Instant.

Date和Calendar出现得比较早,存在1582年问题,如果不涉及这个,到是用起来没差,但是有新的当然还是推荐用新的.Date的使用也比较简单.

package com.hello;

import java.text.SimpleDateFormat;
import java.util.Date;

public class basic {
    public static void main(String[] args) {
        Date date_now = new Date(); // 创建一个新的日期实例,默认保存的是系统时间
        Date date_diff = new Date();
        date_diff.setYear(100); // 设置日期实例中的年份

        int year = date_diff.getYear() + 1900; // 获取日期实例中的年份
        System.out.println("现在date_diff被我设置成" + year + "年");

        int compareResult = date_diff.compareTo(date_now);
        System.out.println(compareResult);

        System.out.println("现在系统时间正处于" +  date_now.getMinutes() + "分!");

        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(fmt.format(new Date()));
    }
}

运行是可以运行了,但是目前Date的所有方法都处于废弃状态,虽然直到现在都还没删除,但是肯定不推荐了.

Calendar也差不多,只是方法上稍微改变,他是一个抽象类,由具体JDK实现,能解决一些Date不能解决的问题,虽然没被标记废弃,但是其实也不推荐,主要还是复杂.

package com.hello;

import java.util.Calendar;

public class basic {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();
        Calendar calendar2 = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 0);

        int hour = calendar.get(Calendar.HOUR_OF_DAY);

        System.out.println("现在hour_of_day被我设置成" + hour);

        int compareResult = calendar.compareTo(calendar2);
        System.out.println(compareResult);
    }
}

运行结果

现在hour_of_day被我设置成0
-1

现在主流推荐的是Java 8引入的LocalDate,以前的Date和Calendar工具,在getMonth之类的操作时候,返回1代表2月,返回0代表1月,这对于以前就是写C的人来看,这没什么不对劲的,但是Java毕竟还是简单且优美,所以在LocalDate这个工具上,就改进起来了.整体也简洁了很多.

package com.hello;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class basic {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.now();
        LocalDate dateManual = LocalDate.now();

        // 获得该日期所在的月份。注意getMonthValue返回的是数字月份,getMonth返回的是英文月份
        int month = localDate.getMonthValue();
        System.out.println("month=" + month + ", english month=" + localDate.getMonth());

        dateManual = dateManual.plusYears(3); // 增加3年
        dateManual = dateManual.minusMonths(1); // 减少1月
        boolean isBeforeDate = localDate.isBefore(dateManual); // 判断A日期是否在B日期之前,还有isAfter,和isEqual方法
        System.out.println("isBeforeDate=" + isBeforeDate);

        System.out.println("datetime=" + dateManual.toString()); // 直接可以输出时间
        System.out.println("Formatted LocalDateTime: " + localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); // 输出格式化
    }
}

在这里时间是没包含在Date里,这才符合理解嘛,Date本来就是日期.如果需要时间和日期组合,可以用这一个来获取,他就是LocalDate和LocalTime的组合体.

LocalDateTime currentDateTime = LocalDateTime.now();

还有Instant这个不能算时间,应该叫时间戳工具,他的精度理论是纳秒的.

package com.hello;

import java.time.Instant;

public class basic {
    public static void main(String[] args) {
        Instant instant1 = Instant.now();
        Instant instant2 = instant1.plusNanos(10);

        System.out.println("isBeforeDate=" + instant1.isBefore(instant2));
    }
}

所以我们找到一个轮子,知道他的方法,也就能用各种功能,只要我们知道有这个类,有他手册,岂不是可以做各种东西.但是总有没轮子的时候,就需要我们自己做.

我在com.hello下做了一个Fruit的类,暂时先这么设计.

package com.hello;

public class Fruit {
    public String name;
    public int weight;

    public Fruit(){
        System.out.println("Fruit");
    }

    public Fruit(String name, int weight){
        this.name = name;
        this.weight = weight;
    }

    public void setName(String name){
        this.name = name;
    }

    public void setWeight(int weight){
        this.weight = weight;
    }

    public String getName(){
        return this.name;
    }

    public int getWeight(){
        return this.weight;
    }

    public String toString(){
        return this.name + "的重量" + this.weight;
    }
}

这个类包含两个构造方法,如果传递了参数,直接可以赋值一些初始参数,否则需要调用setXXX来设置,最后调用getXXX可以获取属性,直接打印的话等于执行toString方法.来验证下.

package com.hello;

public class basic {
    public static void main(String[] args) {
        Fruit fruit = new Fruit("苹果",33);

        System.out.println(fruit);

        fruit.setName("雪梨");
        fruit.setWeight(50);

        System.out.println(fruit);
    }
}

运行结果

看起来符合预期,但是由于我们内部所有东西都是public的,所以不通过指定函数他也能修改.

赶紧把类里面的参数改成private,这样就只能通过我的方法来访问了,IDEA也同时告诉我们他是private就不能直接赋值了.

如果我们再新建一个关于Apple的类别,他依然和其他水果一样有重量,再设计一个感觉就有点多余了,所以我们新建一个Apple类继承于Fruit.

package com.hello;

public class Apple extends Fruit{
    private String type;
    private String color;

    public Apple(){
        this.setName("苹果");
    }

    public void setType(String type) {
        this.type = type;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String toString(){
        return super.toString() + ",同时我是一种苹果,类型是" + this.type + "颜色是" + this.color;
    }
}

这里this代表是本类,包括父类,而super就是强调父类了,我的toString会执行父类的toString方法,而苹果本身也有设置类型和颜色的方法,获取方法暂偷懒了...

我们这里实验可不可以在Apple类赋值父类的name呢,而不调用setName.

这里就要说说修饰符了.

所以上面name如果要被子类可改,那么他父类必须为protected才可以.

在这个Apple的类中,我们可以修改toString方法,有时候我们并不想什么方法都能被子类修改,比如子类可能额外重写setName,那么怎么办呢,可以使用final终态来标记,final可以标记变量,也可以标记方法,标记变量就有点像C语言的const.

先把Fruit类的setName改成final.

public final void setName(String name){
        this.name = name;
}

然后在Apple子类中就不允许再重写他了.

同理如果一个变量是final,他必须赋初值,并且也不能修改.

常规的类就基本这些了,那既然有常规的,也就有不常规的了,比如我现在描述一个树和开花的类,假设只有树能开花,单独开一个类来继承有点繁琐啊.

树有自己的名字,树可以发芽,树可以开花,内部类调用外部要指定类名,因为内部类并不是继承.

package com.hello;

public class Tree {
    private String tree_name;

    public Tree(String tree_name) {
        this.tree_name = tree_name;
    }

    public void sprout() {
        System.out.println(tree_name + "发芽啦");
    }

    // Flower类位于TreeInner类的内部,它是个内部类
    public class Flower {
        private String flower_name;

        public Flower(String flower_name) {
            this.flower_name = flower_name;
        }

        public void bloom() {
            Tree.this.sprout();
            System.out.println(Tree.this.tree_name + "的" + flower_name + "开花啦");
        }
    }
}

测试一下.

package com.hello;

public class basic {
    public static void main(String[] args) {
        Tree tree = new Tree("桃树");
        Tree.Flower flower = tree.new Flower("桃花");
        flower.bloom();
    }
}

应该能输出[桃树的桃花开花啦],内部类也是要new创建的,如果把内部类增加static的描述,他就成了嵌套类,嵌套类不能访问外部类.

这次正式有意地引入static这个关键词,那么,他是什么意思呢.如果一个变量被标记了static,外部即可通过[类名.属性名]直接访问静态属性,静态方法也是同理,比如之前的Math.sqrt就是静态方法.稍微修改一下,使用终态描述flower_name,因为每次实例化他都会给初值,使用静态修饰flower_count,这样他就每次实例化都是同一个变量,同时使用pulib修饰他,使得公开地方可以访问他.

在basic.java中也添加一个static块包裹的代码,这部分代码在类加载前就执行了.

执行后可以看到,被13行修改后,再开花就变成11朵了,被static修饰的也提前执行了.

如果final static来修饰,最后得到的效果就等效于完全不变常量,而且不会因为创建多次对象而复制.

嵌套类还可以做单例模式,就是某个类只能被实例化一次,不管是饿汉方式还是懒汉方式都不能保证严格的只实例化一次,但是单例模式可以,所以偶尔也会发现有些类的获取方法是getInstance而不是new Class()的大概原因吧.

public class SingletonNest {
	// 定义一个嵌套类,并在嵌套类的内部声明当前类的实例
	private static class SingletonHolder {
		private static SingletonNest instance = new SingletonNest(); // 创建一个外层类的实例
	}

	// 获取当前类的实例
	public static SingletonNest getInstance() {
		return SingletonHolder.instance;
	}
}

另一种和常量相似的我们叫枚举类,我们直接举例一个java.time.DayOfWeek就是枚举类,他也可以有自己的构造方法,主要表达哪些单纯数字看不出具体意思的,能形成一一对应关系.比如以下代码能输出数字5和FRIDAY字符串.

System.<em>out</em>.println(DayOfWeek.<em>FRIDAY</em>.getValue());
System.<em>out</em>.println(DayOfWeek.<em>FRIDAY</em>.name());

具体枚举类内部可以自行进去看看

解释一下他的构造方法,比如我们使用DayOfWeek.FRIDAY时候,其实是调用了这个类构造方法的DayOfWeek(6),因为他这个刚好是6,比如我写的这个枚举类,当你调用SUMMER时候,其实是给构造方法传递(2,"夏天").

public enum Season {
	// 在定义枚举变量的同时,调用该枚举变量的构造方法
	SPRING(1, "春天"), SUMMER(2, "夏天"), AUTUMN(3, "秋天"), WINTER(4, "冬天");

	private int value; // 季节的数字序号
	private String name; // 季节的中文名称

	// 在构造方法中传入该季节的阿拉伯数字和中文名称
	private Season(int value, String name) {
		this.value = value;
		this.name = name;
	}

    // 其他省略
}

今天卷得有点多,就到此打住了.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注